Versão 0.6.0: saúde do disco no estilo do CrystalDiskInfo e nova logo
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s

SMART completo (estado com % de vida útil, temperatura, horas e vezes
ligado, total lido/gravado, ficha do dispositivo e tabela de atributos
ATA e NVMe), leitura pelo disco que contém a partição e comando
`guara health`. Nova logo: a cabeça do guará recortada das trilhas de um
disco, nas cores do app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5.5 committed 2026-10-18 22:05:48 -03:00
1 parent f65e7ae615
commit c3b5f79009
22 files changed
+638 -139

No files matched your search

+49 -123
View File
@@ -1,128 +1,54 @@
#!/usr/bin/env python3
"""Gera a logo do Guará em estilo geométrico facetado (low-poly).
"""Gera a logo do Guará: a cabeça do lobo-guará, de frente, recortada das
trilhas de um disco.
A cabeça do lobo-guará foi contornada sobre uma foto de referência, com as
regiões de cor do animal real: focinho escuro subindo até entre os olhos, juba
escura na nuca, interior das orelhas e garganta claros. As facetas (Delaunay)
seguem essas regiões, e o pescoço se solta em estilhaços para a esquerda.
As trilhas são arcos concêntricos com centro abaixo do focinho. Na metade
direita estão inteiras (dados recuperados) e na esquerda quebradas em
fragmentos (dados perdidos). Focinho e olhos usam a cor do texto. As cores
são as do app (apps/gui/ui/app.css): --accent e --accent-2 no tema claro e
seus equivalentes no escuro, trocados por prefers-color-scheme.
Uso: python3 scripts/make-logo.py recorte.png [saida.svg]
Requer numpy, scipy e pillow. O ícone (guara.svg) embute esta mesma arte.
Uso: python3 scripts/make-logo.py [pasta] (padrão: apps/gui/ui)
Grava guara-logo.svg e guara.svg (mesma arte; o segundo é o ícone).
O PNG para os ícones do app (src-tauri/icons/source.png) sai desta arte
renderizada em 1024x1024.
"""
import math, random, sys
import numpy as np
from PIL import Image
from scipy.spatial import Delaunay
OUT=[(98,382),(104,350),(110,318),(114,290),(116,262),(110,235),(100,212),
(84,178),(66,140),(54,100),(50,68),(54,44),(70,52),(98,70),(128,94),(156,118),(176,146),
(180,112),(190,78),(204,50),(216,32),(230,40),(252,64),(274,94),(292,120),(305,135),
(330,138),(370,142),(420,150),(500,165),(500,460),(320,460),(300,420),(260,385),(225,372),(190,380),(150,398),(118,404),(102,396)]
EARS=[[(52,78),(80,96),(115,118),(150,142),(130,170),(105,185),(85,160),(65,120)],
[(222,48),(245,62),(270,95),(285,125),(255,165),(215,165),(195,130),(200,85)]]
EYES=[(125,270),(200,268)]
ZONES=[
# interior claro das orelhas (com borda verde em volta)
('pale',[(62,62),(92,78),(122,100),(150,124),(160,144),(138,166),(116,190),(98,166),(76,124),(62,92)]),
('pale',[(214,50),(228,56),(248,78),(268,106),(282,128),(258,150),(226,160),(200,146),(196,112),(204,76)]),
# testa um pouco mais clara entre as orelhas
('brow',[(160,150),(185,148),(240,170),(270,190),(250,220),(200,230),(160,225),(130,205)]),
# área dos olhos, um tom mais escuro
('mask',[(108,250),(140,250),(165,246),(195,250),(222,262),(220,284),(196,292),(165,282),(140,290),(112,290)]),
# juba escura na nuca
('mane',[(298,136),(500,166),(500,236),(430,214),(360,198),(318,182),(300,160)]),
# garganta clara e linha clara do lábio
('pale',[(214,350),(234,332),(266,324),(302,320),(316,344),(306,372),(274,388),(240,380),(220,370)]),
('pale',[(150,398),(186,382),(212,368),(222,374),(204,388),(166,404)]),
# focinho preto subindo pelo dorso do nariz até entre os olhos
('muzzle',[(96,384),(102,352),(108,322),(114,296),(132,284),(150,266),(164,258),(176,268),(184,294),(196,322),(206,352),(200,374),(170,392),(130,404),(104,398)]),
]
import os, sys
from PIL import ImageFilter
REF = sys.argv[1] # recorte da cabeça da foto de referência (390x460), não versionado
img=np.array(Image.open(REF).convert('RGB').filter(ImageFilter.MedianFilter(9)).filter(ImageFilter.GaussianBlur(4))).astype(float)
Hc,Wc=img.shape[:2]
rnd=random.Random(7)
# contorno: estende o pescoço para a esquerda (depois do espelhamento)
C=[p for p in OUT if p[0]<=500]
def inside(poly,x,y):
c=False; j=len(poly)-1
for i in range(len(poly)):
xi,yi=poly[i]; xj,yj=poly[j]
if (yi>y)!=(yj>y) and x<(xj-xi)*(y-yi)/(yj-yi)+xi: c=not c
j=i
return c
# pontos: contorno reamostrado + traços anatômicos + preenchimento com jitter
pts=[]
for i in range(len(C)):
a=C[i]; b=C[(i+1)%len(C)]; n=max(1,int(math.dist(a,b)/16))
for k in range(n): pts.append((a[0]+(b[0]-a[0])*k/n, a[1]+(b[1]-a[1])*k/n))
FEAT=[(125,270),(200,268),(110,300),(160,286),(185,300),(100,383),(118,404),(150,396),(200,360),
(215,330),(260,322),(300,318),(310,350),(270,386),(140,140),(110,190),(80,120),(255,100),(270,150),(225,160),
(205,110),(175,180),(230,230),(150,230),(300,200),(340,250),(320,170),(360,330),(400,260),(450,330),(260,280)]
pts+=FEAT
# vértices nas bordas das regiões: as facetas seguem os contornos das cores
for k_,Z in ZONES:
for i in range(len(Z)):
a=Z[i]; b=Z[(i+1)%len(Z)]; n=max(1,int(math.dist(a,b)/18))
for j in range(n):
q=(a[0]+(b[0]-a[0])*j/n, a[1]+(b[1]-a[1])*j/n)
if inside(C,*q) and min(math.dist(q,p_) for p_ in pts)>6: pts.append(q)
sp=24
for y in np.arange(20,470,sp):
for x in np.arange(20,510,sp):
jx,jy=x+rnd.uniform(-8,8),y+rnd.uniform(-8,8)
if inside(C,jx,jy) and min(math.dist((jx,jy),p) for p in pts)>15: pts.append((jx,jy))
P=np.array(pts)
tri=Delaunay(P)
def sample(t):
a,b,c=P[t]
cols=[]
for _ in range(40):
u,v=rnd.random(),rnd.random()
if u+v>1: u,v=1-u,1-v
x,y=a+(b-a)*u+(c-a)*v
x=min(max(x,0),Wc-1); y=min(max(y,0),Hc-1)
if x>380: x=380-(x-380)*0.2 # o pescoço além da foto herda a cor da borda
cols.append(img[int(y),int(x)])
m=np.mean(cols,axis=0); return m
RAMP=["#083a45","#0f766e","#14b8a6","#5eead4","#c9faf1"]
def tone(rgb):
r,g,b=rgb; L=(0.299*r+0.587*g+0.114*b)/255
TH=[0.17,0.30,0.50,0.64]
return RAMP[sum(L>t for t in TH)]
def hx(c): return tuple(int(c[i:i+2],16) for i in (1,3,5))
def mix(c1,c2,t):
a,b=hx(c1),hx(c2); return "#"+"".join(f"{round(p+(q-p)*t):02x}" for p,q in zip(a,b))
BASE={'muzzle':("#07323c","#0d5563"),'mane':("#0b4f5c","#0f766e"),'pale':("#9ef0e2","#e3fdf8"),'face':("#0f9e94","#3fdcc8"),'brow':("#1fb9a9","#52dccb"),'mask':("#0c7d76","#139c92")}
def zone_of(x,y):
for k,P_ in reversed(ZONES):
if inside(P_,x,y): return k
return 'face'
F=[]
for t in tri.simplices:
cx,cy=P[t].mean(axis=0)
if not inside(C,cx,cy): continue
z=zone_of(cx,cy)
lo,hi=BASE[z]
# luz vinda do alto e da frente (focinho), com variação sutil por faceta
light=0.55*(1-cy/470)+0.35*(1-min(1,cx/420))+rnd.uniform(-0.12,0.12)
F.append((t, mix(lo,hi,max(0,min(1,light))), cx, cy))
# espelha e encaixa no quadro 160x100 da logo
S=92/430
def T(x,y): return (150-(x-38)*S, 4+(y-30)*S)
out=[]
for t,col,cx,cy in F:
q=[T(*P[i]) for i in t]
# estilhaços: o pescoço (esquerda) se solta e se desfaz
gx=T(cx,cy)[0]
k=max(0,(78-gx)/40)
if k>0 and rnd.random()<min(1,k*1.25):
mx=sum(p[0] for p in q)/3; my=sum(p[1] for p in q)/3
dx=-k*22-rnd.uniform(0,10)*k; dy=-k*6+rnd.uniform(-5,5)*k
sc=max(0.25,1-k*0.8)
q=[(mx+dx+(p[0]-mx)*sc, my+dy+(p[1]-my)*sc) for p in q]
op=max(0.12,1-k*0.95)
else: op=1
out.append(f'<polygon points="{" ".join(f"{x:.2f},{y:.2f}" for x,y in q)}" fill="{col}" stroke="{col}" stroke-width="0.25" stroke-linejoin="round"{"" if op==1 else f" fill-opacity=\"{op:.2f}\" stroke-opacity=\"{op:.2f}\""}/>')
eyes="".join(f'<circle cx="{T(x,y)[0]:.2f}" cy="{T(x,y)[1]:.2f}" r="1.25" fill="#04232b"/>' for x,y in EYES)
open(sys.argv[2] if len(sys.argv) > 2 else 'guara-logo.svg','w').write(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 100" role="img" aria-label="Guará">{"".join(out)}{eyes}</svg>\n')
HEAD = ("M50 95 C46 88 43 80 40 74 C33 70 25 64 22 56 L14 6 L36 30 "
"C40 28 45 27 50 27 C55 27 60 28 64 30 L86 6 L78 56 "
"C75 64 67 70 60 74 C57 80 54 88 50 95 Z")
STYLE = (".a{stroke:#0f766e}.b{stroke:#14b8a6}.t{fill:#1d1d1f}"
"@media (prefers-color-scheme:dark){.a{stroke:#2dd4bf}.b{stroke:#5eead4}.t{fill:#f5f5f7}}")
def tracks():
out = []
for i, r in enumerate(range(7, 104, 7)):
top, bot = 100 - r, 100 + r
out.append(f'<path class="a" d="M50 {top}A{r} {r} 0 0 1 50 {bot}"/>')
dash = f"{3 + i % 3 * 3} {3 + (i * 5) % 4}"
out.append(f'<path class="b" d="M50 {top}A{r} {r} 0 0 0 50 {bot}" '
f'stroke-dasharray="{dash}" stroke-dashoffset="{i * 3}"/>')
return "".join(out)
def svg():
return (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img" aria-label="Guará">'
f'<style>{STYLE}</style>'
f'<defs><clipPath id="g-head"><path d="{HEAD}"/></clipPath></defs>'
f'<g clip-path="url(#g-head)" fill="none" stroke-width="4.2">{tracks()}'
# o focinho é a ponta da própria cabeça (recortada pelo mesmo contorno),
# então nenhuma trilha escapa pelas laterais
'<rect class="t" stroke="none" x="0" y="74" width="100" height="30"/></g>'
'<path class="t" d="M37 50Q42 47 46 52Q41 53 37 50ZM63 50Q58 47 54 52Q59 53 63 50Z"/>'
'</svg>\n'
)
if __name__ == "__main__":
out = sys.argv[1] if len(sys.argv) > 1 else "apps/gui/ui"
for name in ("guara-logo.svg", "guara.svg"):
with open(os.path.join(out, name), "w") as f:
f.write(svg())