diff --git a/README.es.md b/README.es.md index 596f74f..ec79971 100644 --- a/README.es.md +++ b/README.es.md @@ -370,6 +370,16 @@ Dependencias Go (mantenidas a 2 a propósito): ## Registro de cambios +### v2.9.22 + +- **Nuevo sistema de temas con cuatro skins (#130)**: la interfaz incorpora cuatro looks distintos, conmutables desde *Ajustes → Apariencia*, manteniendo el clásico intacto para los usuarios actuales. + - **Clásico** — el look de siempre (claro/oscuro/sistema; es el default para usuarios existentes). + - **Moderno** — el default en instalaciones nuevas (oscuro o claro según tu SO): acento violeta, tarjetas sin borde con elevación suave, navegación activa en píldora y un halo radial sutil. + - **Fósforo** — estética CRT/terminal: JetBrains Mono en todo, radio 0, scanlines y rejilla de fondo, tarjetas con esquinas HUD, medidores segmentados y botones/badges cuadrados en mayúsculas, con identidad verde fósforo. + - **Brutalista** — papel crema con bordes negros de 2 px, sombras duras desplazadas, botón primario amarillo señal, títulos subrayados "marcador" y navegación activa negra con texto amarillo. +- **Tema y modo ahora van separados**: eliges una familia (*Clásico / Moderno / Fósforo / Brutalista*) y, para Clásico y Moderno, un modo (*Sistema / Claro / Oscuro*). Cada familia ofrece su propia paleta de acentos (con su acento default preseleccionado) y el picker funciona en todos los temas, mientras los colores de estado siguen siendo semánticos. Densidad y reducción de animaciones se conservan bajo cada skin. +- **Tipografía algo más grande** en el Dashboard y las secciones tipo Disk health, y el banner de rebuild ahora usa acento azul (info) en el tema Moderno. + ### v2.9.21 - **Reduce la recolección de ZFS cuando la UI está cerrada (#126)**: la web UI mantiene un stream SSE abierto contra `/api/events`, así que `ZpoolCollector` usa el número de suscriptores del hub como señal de presencia. Con la UI abierta ejecuta una recolección completa cada `EASYZFS_ZPOOL_INTERVAL` (10 s por defecto). Con la UI cerrada pasa a un heartbeat ligero de salud/alertas cada `EASYZFS_ZPOOL_ALERT_INTERVAL` (60 s por defecto) y solo hace una recolección completa cada `EASYZFS_ZPOOL_IDLE_INTERVAL` (300 s por defecto). Las alertas en segundo plano (DEGRADED, scrub, capacidad) siguen evaluándose al menos cada minuto, y las series de capacidad siguen persistiendo cada 10 minutos, mientras que los nodos en reposo emiten muchísimos menos comandos `zpool`/`zfs`. diff --git a/README.md b/README.md index 211f21c..3ddd4ca 100644 --- a/README.md +++ b/README.md @@ -362,6 +362,16 @@ Go dependencies (kept to 2 on purpose): ## Changelog +### v2.9.22 + +- **New theme system with four visual skins (#130)**: the interface now ships four distinct looks, switchable from *Settings → Appearance* while keeping the classic one intact for existing users. + - **Classic** — the familiar light/dark/system look (the default for existing users). + - **Modern** — the default for new installs (dark or light following your OS): violet accent, borderless cards with soft elevation, pill-shaped active nav and a subtle radial halo. + - **Phosphor** — a CRT/terminal aesthetic: JetBrains Mono everywhere, zero radius, scanlines and a grid background, HUD-cornered cards, segmented meters and squared uppercase buttons/badges, with a phosphor-green identity. + - **Brutalist** — cream paper with 2px black borders, hard offset shadows, signal-yellow primary buttons, marker-underlined titles and black active nav with yellow text. +- **Theme and mode are now separate**: pick a family (*Classic / Modern / Phosphor / Brutalist*) and, for Classic and Modern, a mode (*System / Light / Dark*). Each family offers its own accent palette (its default accent preselected) and the accent picker works on every theme while state colors stay semantic. Density and reduced-motion are preserved under every skin. +- **Slightly larger type** in the Dashboard and the disk-health sections, and the rebuild banner now uses a blue (info) accent in the Modern theme. + ### v2.9.21 - **Throttle ZFS collector when no UI is connected (#126)**: the web UI keeps an SSE stream open to `/api/events`, so the `ZpoolCollector` now uses the hub's subscriber count as a presence signal. While the UI is open it runs a full collection every `EASYZFS_ZPOOL_INTERVAL` (default 10 s). When the UI is closed it falls back to a cheap health/alert heartbeat every `EASYZFS_ZPOOL_ALERT_INTERVAL` (default 60 s) and only runs a full collection every `EASYZFS_ZPOOL_IDLE_INTERVAL` (default 300 s). Background alerts (DEGRADED, scrub, capacity) keep firing at least every minute, and capacity series still persist every 10 minutes, while idle nodes issue dramatically fewer `zpool`/`zfs` commands. diff --git a/web/index.html b/web/index.html index 61b1cc3..c862cdc 100644 --- a/web/index.html +++ b/web/index.html @@ -25,9 +25,6 @@ if(o!==null){ls.setItem(nuevo,o);ls.removeItem(legacy);} return o; } - var t=getk('easyzfs-theme-mode','zfc-theme'); - var dark=t==='dark'||(t!=='light'&&window.matchMedia('(prefers-color-scheme: dark)').matches); - if(dark)document.documentElement.dataset.theme='dark'; // ESPEJO de ACCENTS en ui/theme.ts: si cambia una, cambia la otra. var ACC={ cyan:{l:['#0e7c93','#dff0f4'],d:['#4cc3d9','#13292f']}, @@ -35,10 +32,40 @@ emerald:{l:['#2f7d5f','#e3f0e9'],d:['#5cb893','#1d2f27']}, amber:{l:['#a8741f','#f6ecd9'],d:['#d9a84e','#33291a']} }; - var a=ACC[getk('easyzfs-accent','zfc-accent')]||ACC.emerald; - var v=dark?a.d:a.l,st=document.documentElement.style; - st.setProperty('--accent',v[0]);st.setProperty('--accent-soft',v[1]); - // --ok NO sigue al acento: es semántico (salud OK); vive solo en el CSS. + // ESPEJO de SKIN_BG en ui/theme.ts: si cambia una, cambia la otra. + var SKINBG={'modern-dark':'#0d0e12','modern-light':'#f4f5f8','phosphor':'#050810','brutalist':'#f2ede1'}; + var st=document.documentElement.style, ms=document.querySelector('meta[name="theme-color"]'); + // Modelo: familia (easyzfs-theme) + modo (easyzfs-theme-mode). Migración + // desde el modelo combinado anterior (auto|light|dark|modern-*|phosphor|brutalist). + var fam=ls.getItem('easyzfs-theme'), mod=ls.getItem('easyzfs-theme-mode'); + if(fam===null){ + var legacy=ls.getItem('easyzfs-theme-mode')||ls.getItem('zfc-theme'); + if(legacy==='light'||legacy==='dark'||legacy==='auto'){fam='classic';mod=legacy==='auto'?'system':legacy;} + else if(legacy==='modern-dark'){fam='modern';mod='dark';} + else if(legacy==='modern-light'){fam='modern';mod='light';} + else if(legacy==='phosphor'||legacy==='brutalist'){fam=legacy;mod='system';} + else {fam='modern';mod='system';} + ls.setItem('easyzfs-theme',fam);ls.setItem('easyzfs-theme-mode',mod); + } + var systemDark=window.matchMedia('(prefers-color-scheme: dark)').matches; + var dark=(mod==='dark'||(mod==='system'&&systemDark)); + var dt; + if(fam==='classic'){dt=dark?'dark':'';} + else if(fam==='modern'){dt=dark?'modern-dark':'modern-light';} + else {dt=fam;} // phosphor | brutalist + document.documentElement.dataset.theme=dt; + if(ms)ms.setAttribute('content',SKINBG[dt]||(dt==='dark'?'#0e1210':'#f6f6f3')); + // Acento: en familias no-classic manda su CSS (salvo override); en clásico se aplica inline. + if(fam!=='classic'){ + st.removeProperty('--accent');st.removeProperty('--accent-soft'); + var lum=fam==='modern'?(dark?'dark':'light'):(fam==='brutalist'?'light':'dark'); + var ov=ls.getItem('easyzfs-accent-'+fam); + if(ov&&ACC[ov]){var ar=ACC[ov][lum];st.setProperty('--accent',ar[0]);st.setProperty('--accent-soft',ar[1]);} + }else{ + var a=ACC[getk('easyzfs-accent','zfc-accent')]||ACC.emerald; + var v=dark?a.d:a.l; + st.setProperty('--accent',v[0]);st.setProperty('--accent-soft',v[1]); + } if(getk('easyzfs-density','zfc-density')==='compact'){ document.documentElement.dataset.density='compact';st.fontSize='13.5px'; } @@ -49,8 +76,6 @@ var L=getk('easyzfs-lang','zfc-lang'); document.documentElement.lang=(L==='es'||L==='en')?L: (((navigator.language||'es').toLowerCase().indexOf('en')===0)?'en':'es'); - var m=document.querySelector('meta[name="theme-color"]'); - if(m)m.setAttribute('content',dark?'#0e1210':'#f6f6f3'); }catch(e){}})(); diff --git a/web/src/App.tsx b/web/src/App.tsx index 1d870ad..4092569 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -119,7 +119,7 @@ function AlertsPanel({ onClose }: { onClose: () => void }) { } function Shell() { - const { t, route, navigate, demo, exitDemo, user, isAdmin, ready, themeMode, themeEff, setTheme } = useApp(); + const { t, route, navigate, demo, exitDemo, user, isAdmin, ready, family, mode, themeEff, setMode } = useApp(); const { openModal } = useModal(); const [showAlerts, setShowAlerts] = useState(false); const [hasPending, setHasPending] = useState(false); @@ -301,23 +301,27 @@ function Shell() { {hasPending && } - -
- {([['auto', IconMonitor], ['light', IconSun], ['dark', IconMoon]] as const).map(([m, Icon]) => ( - - ))} -
+
+ {([['system', IconMonitor], ['light', IconSun], ['dark', IconMoon]] as const).map(([m, Icon]) => ( + + ))} +
+ + )} - ); - })} + {/* Tema (familia) */} +
+ {t('s_theme')} +