@netzi/tokens 0.1.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +965 -0
- package/LICENSE.md +33 -0
- package/README.md +1008 -0
- package/lib/brand-context.d.ts +10 -0
- package/lib/brand-context.js +65 -0
- package/lib/brand.d.ts +90 -0
- package/lib/brand.js +256 -0
- package/lib/contrast.d.ts +14 -0
- package/lib/contrast.js +34 -0
- package/package.json +56 -0
- package/public-tokens.json +460 -0
- package/styles/base.css +139 -0
- package/styles/fonts/LICENSE-FiraCode.txt +93 -0
- package/styles/fonts/LICENSE-PlusJakartaSans.txt +93 -0
- package/styles/fonts/fira-code-latin-ext.woff2 +0 -0
- package/styles/fonts/fira-code-latin.woff2 +0 -0
- package/styles/fonts/plus-jakarta-sans-latin-ext.woff2 +0 -0
- package/styles/fonts/plus-jakarta-sans-latin.woff2 +0 -0
- package/styles/fonts.css +136 -0
- package/styles/index.css +7 -0
- package/styles/tokens.css +1426 -0
package/README.md
ADDED
|
@@ -0,0 +1,1008 @@
|
|
|
1
|
+
# @netzi/tokens
|
|
2
|
+
|
|
3
|
+
Design tokens de Netzi como **CSS custom properties**. Agnóstico de framework
|
|
4
|
+
(sirve para Angular, React, HTML plano, Tailwind, etc.). Es la base sobre la que
|
|
5
|
+
se construyen `@netzi/ui` y cualquier app de Netzi Tech.
|
|
6
|
+
|
|
7
|
+
- Tema **claro** (`:root`, tema claro) y **oscuro** (`[data-theme="dark"]`, Netzi navy).
|
|
8
|
+
- Acentos de marca constantes en ambos temas.
|
|
9
|
+
- Todos los tokens usan el prefijo `--netzi-*` para evitar colisiones.
|
|
10
|
+
|
|
11
|
+
## Instalación
|
|
12
|
+
|
|
13
|
+
En el **npm público**, bajo el scope `@netzi` de Netzi Tech: sin registry especial ni
|
|
14
|
+
token. Mientras el kit esté en beta, cada versión nueva es una beta y es la que se
|
|
15
|
+
instala por defecto:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @netzi/tokens
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Licencia de uso libre con código privado: ver `LICENSE.md` dentro del paquete. Las
|
|
22
|
+
tipografías incluidas van bajo la SIL Open Font License 1.1.
|
|
23
|
+
|
|
24
|
+
## Uso
|
|
25
|
+
|
|
26
|
+
Importa la entrada por defecto (tokens + reset base):
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
@import '@netzi/tokens';
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
O por partes:
|
|
33
|
+
|
|
34
|
+
```css
|
|
35
|
+
@import '@netzi/tokens/tokens.css'; /* solo las variables (sin reset) */
|
|
36
|
+
@import '@netzi/tokens/base.css'; /* reset + tipografía base + scrollbar */
|
|
37
|
+
@import '@netzi/tokens/fonts.css'; /* opcional: fuentes de marca, autoalojadas */
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
En tu CSS, consume los tokens:
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
.boton {
|
|
44
|
+
background: var(--netzi-primary);
|
|
45
|
+
color: var(--netzi-on-primary);
|
|
46
|
+
border-radius: var(--netzi-radius-control);
|
|
47
|
+
box-shadow: var(--netzi-elevation-floating);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Usa los tokens por **papel** (superficie, texto, espaciado de grupo, radio de control,
|
|
52
|
+
estilo de título), no los pasos de una escala: el papel dice para qué es y la escala de
|
|
53
|
+
debajo puede cambiar sin tocar tu app. Las escalas con nombre de paso (`--netzi-space-4`,
|
|
54
|
+
`--netzi-radius-sm`, `--netzi-font-size-md`…) están en desuso para las apps (ver
|
|
55
|
+
«Escalas que pasan a internas»).
|
|
56
|
+
|
|
57
|
+
## Espaciado (`--netzi-space-*`)
|
|
58
|
+
|
|
59
|
+
Espaciado por **papel**, de lo más cercano a lo más lejano: cuanto más relacionadas están
|
|
60
|
+
dos cosas, menos aire hay entre ellas (proximidad). Úsalo en vez de píxeles o `rem` sueltos
|
|
61
|
+
para mantener el ritmo del sistema; detrás hay una escala de base 4px que es de la fuente.
|
|
62
|
+
|
|
63
|
+
| Token | Valor | Compacto | Para qué |
|
|
64
|
+
| ------------------------ | ----- | -------- | ----------------------------------------------------------------------------- |
|
|
65
|
+
| `--netzi-space-related` | 4px | 4px | piezas de un mismo elemento: icono y texto, chips de una lista, campo y ayuda |
|
|
66
|
+
| `--netzi-space-group` | 8px | 8px | elementos hermanos de un grupo: botones de una barra, cabecera y contenido |
|
|
67
|
+
| `--netzi-space-stack` | 12px | 10px | grupos apilados en un panel o un formulario: campos, filas, columnas |
|
|
68
|
+
| `--netzi-space-inset-sm` | 12px | 10px | relleno de un contenedor compacto: panel flotante, aviso |
|
|
69
|
+
| `--netzi-space-inset` | 16px | 12px | relleno de una tarjeta o un panel |
|
|
70
|
+
| `--netzi-space-inset-lg` | 24px | 20px | relleno de un diálogo, un panel lateral o una tarjeta grande |
|
|
71
|
+
| `--netzi-space-section` | 32px | 28px | entre secciones de una página |
|
|
72
|
+
| `--netzi-space-page` | 48px | 40px | entre regiones grandes de una página y su margen en pantallas anchas |
|
|
73
|
+
|
|
74
|
+
Bajo `[data-density='compact']` se reducen desde `stack` hacia arriba (los finos no
|
|
75
|
+
cambian), coherente con la densidad de los controles, también en una sección con su propia
|
|
76
|
+
densidad.
|
|
77
|
+
|
|
78
|
+
```css
|
|
79
|
+
.tarjeta {
|
|
80
|
+
padding: var(--netzi-space-inset);
|
|
81
|
+
display: flex;
|
|
82
|
+
gap: var(--netzi-space-group);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Fuentes de marca (`fonts.css`)
|
|
87
|
+
|
|
88
|
+
`fonts.css` trae **Plus Jakarta Sans** (texto) y **Fira Code** (monoespaciada) como
|
|
89
|
+
fuentes variables woff2 **dentro del paquete** (~95 KB en total, en dos subconjuntos
|
|
90
|
+
`latin` y `latin-ext` que el navegador descarga según haga falta). No se pide nada
|
|
91
|
+
a Google: política 14 (autoalojar) y 08b (sin terceros antes del consentimiento).
|
|
92
|
+
Va aparte de `index.css`; impórtalo si tu app usa las fuentes de marca:
|
|
93
|
+
|
|
94
|
+
```css
|
|
95
|
+
@import '@netzi/tokens/fonts.css';
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Las rutas son relativas al paquete y las reescriben Vite y el CLI de Angular, que
|
|
99
|
+
copian los woff2 al build.
|
|
100
|
+
|
|
101
|
+
Mientras llegan, el texto se pinta con un **respaldo del sistema con las métricas
|
|
102
|
+
ajustadas** (Arial o Liberation Sans, y Courier New o Liberation Mono, con `size-adjust`,
|
|
103
|
+
`ascent-override`, `descent-override` y `line-gap-override`): ocupa casi lo mismo que la
|
|
104
|
+
fuente de marca y la página no salta al cambiar. Los tokens `--netzi-font-sans` y
|
|
105
|
+
`--netzi-font-mono` ya lo nombran detrás de la de marca; si cambias la familia en línea,
|
|
106
|
+
deja `'Plus Jakarta Sans Fallback'` (o `'Fira Code Fallback'`) justo detrás. De dónde
|
|
107
|
+
salen los números está en el propio `fonts.css`. Licencia OFL-1.1, adjunta en `styles/fonts/`. Ninguna hoja
|
|
108
|
+
publicada carga nada desde fuera: todo viaja dentro del paquete.
|
|
109
|
+
|
|
110
|
+
## Tipografía (`--netzi-typography-*`)
|
|
111
|
+
|
|
112
|
+
Un **estilo de texto** por papel (título, cuerpo, metadato), con familia, tamaño, peso,
|
|
113
|
+
interlineado y espaciado entre letras juntos: es lo que usa una app. Todos escalan con
|
|
114
|
+
`--netzi-font-scale` (el ajuste de accesibilidad del panel), igual que los controles; `body-ui`
|
|
115
|
+
(14px) es el 100 %. El texto corrido (`body`) mide 16px desde la #219 (ADR 0015). Las familias son `--netzi-font-sans` (texto) y `--netzi-font-mono`
|
|
116
|
+
(código y cifras en columna).
|
|
117
|
+
|
|
118
|
+
**Medida legible** (`--netzi-measure`, `33em`, #213): el ancho máximo del texto corrido, para
|
|
119
|
+
que cada línea se quede en unos 40 a 75 caracteres (64 de mediana) en cualquier ventana (criterio
|
|
120
|
+
C.9). Va en el párrafo (`max-inline-size`), nunca en la página ni en la sección: el ancho que
|
|
121
|
+
sobra lo ocupan el contenido auxiliar, las acciones, otro panel, la rejilla o la prosa en columnas entre párrafos; un párrafo no se parte en columnas, y una descripción corta puede dejar hueco. Va en `em` y
|
|
122
|
+
no en `ch`: el `ch` es el ancho del cero de la fuente que pinta en ese momento, y al llegar la
|
|
123
|
+
fuente de marca cambiaría el ancho de cada párrafo y lo haría saltar (criterio C.12).
|
|
124
|
+
|
|
125
|
+
```css
|
|
126
|
+
.descripcion {
|
|
127
|
+
max-inline-size: var(--netzi-measure);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Los tamaños de los **controles** (`--netzi-control-fs-sm/md/lg`) son otra familia,
|
|
132
|
+
ligada a la altura del control; no salen de la escala de texto. La densidad compacta no
|
|
133
|
+
cambia el texto (encoge el aire, no la letra).
|
|
134
|
+
|
|
135
|
+
### Estilos de texto
|
|
136
|
+
|
|
137
|
+
En CSS cada estilo es el atajo `font` (tokens `typography` de DTCG) y su espaciado entre letras
|
|
138
|
+
va aparte, porque `font` no lo incluye. El tamaño solo sale también aparte
|
|
139
|
+
(`--netzi-typography-body-font-size`, #155), para la hoja que pone el peso y el interlineado
|
|
140
|
+
por su cuenta y no quiere que el atajo se los pise:
|
|
141
|
+
|
|
142
|
+
| Estilo | Tamaño | Peso | Interlineado | Espaciado | Para qué |
|
|
143
|
+
| ----------------------------- | ------ | ---- | ------------ | --------- | ----------------------------------------- |
|
|
144
|
+
| `--netzi-typography-display` | 28px | 800 | 1.25 | -0.5px | cifras grandes, portadas |
|
|
145
|
+
| `--netzi-typography-headline` | 22px | 700 | 1.25 | -0.2px | títulos de página |
|
|
146
|
+
| `--netzi-typography-title` | 18px | 700 | 1.25 | 0 | títulos de sección |
|
|
147
|
+
| `--netzi-typography-title-sm` | 16px | 600 | 1.25 | 0 | texto destacado, títulos de tarjeta |
|
|
148
|
+
| `--netzi-typography-body` | 16px | 400 | 1.5 | 0 | texto corrido: párrafos, descripciones |
|
|
149
|
+
| `--netzi-typography-body-ui` | 14px | 400 | 1.5 | 0 | interfaz: opciones, pestañas, celdas |
|
|
150
|
+
| `--netzi-typography-body-sm` | 12.5px | 400 | 1.5 | 0 | líneas secundarias, pistas, pies |
|
|
151
|
+
| `--netzi-typography-caption` | 11px | 600 | 1.25 | 0.5px | metadatos, etiquetas cortas en mayúsculas |
|
|
152
|
+
|
|
153
|
+
```css
|
|
154
|
+
.titulo-de-seccion {
|
|
155
|
+
font: var(--netzi-typography-title);
|
|
156
|
+
letter-spacing: var(--netzi-typography-title-letter-spacing);
|
|
157
|
+
}
|
|
158
|
+
.meta {
|
|
159
|
+
font: var(--netzi-typography-caption);
|
|
160
|
+
letter-spacing: var(--netzi-typography-caption-letter-spacing);
|
|
161
|
+
text-transform: uppercase;
|
|
162
|
+
}
|
|
163
|
+
.celda {
|
|
164
|
+
font-size: var(--netzi-typography-body-ui-font-size);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Siguen a `--netzi-font-sans` si la app la cambia. El atajo `font` reinicia también el
|
|
169
|
+
interlineado y el peso: ponlo primero y cambia después lo que quieras (una familia mono para
|
|
170
|
+
un código, por ejemplo). Las mayúsculas de `caption` las pone quien lo usa (`text-transform`),
|
|
171
|
+
no el estilo. Detrás hay una escala de tamaños, pesos e interlineados que es de la fuente: sus
|
|
172
|
+
tokens (`--netzi-font-size-*`, `--netzi-font-weight-*`, `--netzi-line-height-*`) están en
|
|
173
|
+
desuso para las apps.
|
|
174
|
+
|
|
175
|
+
## Movimiento (`--netzi-duration-*`, `--netzi-ease-*`)
|
|
176
|
+
|
|
177
|
+
Duraciones y curvas con **nombre de intención**, no de valor: el componente dice qué hace
|
|
178
|
+
(un hover, un panel que entra) y el token decide cuánto dura. Así el ritmo del kit se
|
|
179
|
+
cambia en un sitio y el movimiento reducido también.
|
|
180
|
+
|
|
181
|
+
| Duración | Valor | Para qué |
|
|
182
|
+
| -------------------------- | ----- | ----------------------------------------------------- |
|
|
183
|
+
| `--netzi-duration-instant` | 100ms | la respuesta a una pulsación |
|
|
184
|
+
| `--netzi-duration-fast` | 150ms | hover, foco, cambios de color; lo pequeño que aparece |
|
|
185
|
+
| `--netzi-duration-base` | 200ms | lo que se despliega o gira (flechas, acordeón) |
|
|
186
|
+
| `--netzi-duration-slow` | 280ms | los paneles y capas que entran (modal, drawer) |
|
|
187
|
+
|
|
188
|
+
| Curva | Valor | Para qué |
|
|
189
|
+
| ----------------------- | ---------------------------------- | ------------------------------------------ |
|
|
190
|
+
| `--netzi-ease-standard` | `cubic-bezier(0.25, 0.1, 0.25, 1)` | lo que cambia sin entrar ni salir (`ease`) |
|
|
191
|
+
| `--netzi-ease-enter` | `cubic-bezier(0.16, 1, 0.3, 1)` | lo que aparece: frena al llegar |
|
|
192
|
+
| `--netzi-ease-exit` | `cubic-bezier(0.4, 0, 1, 1)` | lo que se va: acelera al irse |
|
|
193
|
+
| `--netzi-ease-settle` | `cubic-bezier(0.34, 1.45, 0.5, 1)` | lo que llega, se pasa un poco y se asienta |
|
|
194
|
+
|
|
195
|
+
### Bucles y destello
|
|
196
|
+
|
|
197
|
+
Lo que se repite sin fin no es un cambio de estado: su token da la duración de **una vuelta**
|
|
198
|
+
y queda fuera de la escala de arriba (que no pasa de 300 ms). El destello es un aviso que se
|
|
199
|
+
apaga solo, largo a propósito para que dé tiempo a verlo.
|
|
200
|
+
|
|
201
|
+
| Duración | Valor | Para qué |
|
|
202
|
+
| ----------------------------- | ------ | ----------------------------------------------------------------- |
|
|
203
|
+
| `--netzi-duration-loop-spin` | 700ms | el giro de un indicador de carga |
|
|
204
|
+
| `--netzi-duration-loop-orbit` | 1100ms | el anillo que recorre el borde de un ítem o una pestaña |
|
|
205
|
+
| `--netzi-duration-loop-pulse` | 1200ms | el latido de opacidad que sustituye al giro (movimiento reducido) |
|
|
206
|
+
| `--netzi-duration-loop-sweep` | 1200ms | la barra de progreso indeterminada |
|
|
207
|
+
| `--netzi-duration-loop-shine` | 1300ms | el brillo del skeleton |
|
|
208
|
+
| `--netzi-duration-flash` | 1400ms | un aviso que se enciende, se deja ver y se apaga solo |
|
|
209
|
+
|
|
210
|
+
| Curva | Valor | Para qué |
|
|
211
|
+
| -------------------- | -------------------------------- | -------------------------------------------------------------- |
|
|
212
|
+
| `--netzi-ease-swing` | `cubic-bezier(0.42, 0, 0.58, 1)` | lo que va y vuelve: latidos, barridos, brillos (`ease-in-out`) |
|
|
213
|
+
| `--netzi-ease-spin` | `cubic-bezier(0, 0, 1, 1)` | lo que gira sin fin a velocidad constante (`linear`) |
|
|
214
|
+
|
|
215
|
+
Los bucles **no cambian** con `prefers-reduced-motion`: una duración de bucle no para una
|
|
216
|
+
animación infinita. Cada componente quita su bucle o lo cambia por un latido de opacidad en
|
|
217
|
+
su propia regla reducida:
|
|
218
|
+
|
|
219
|
+
```css
|
|
220
|
+
.cargando {
|
|
221
|
+
animation: giro var(--netzi-duration-loop-spin) var(--netzi-ease-spin) infinite;
|
|
222
|
+
}
|
|
223
|
+
@media (prefers-reduced-motion: reduce) {
|
|
224
|
+
.cargando {
|
|
225
|
+
animation: latido var(--netzi-duration-loop-pulse) var(--netzi-ease-swing) infinite;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Desplazamiento y fundido: dos familias, un solo sitio para el movimiento reducido
|
|
231
|
+
|
|
232
|
+
Con `prefers-reduced-motion: reduce` lo que **se desplaza, gira, escala o cambia de
|
|
233
|
+
geometría** no debe moverse (WCAG 2.3.3), pero un cambio de color o de opacidad no es
|
|
234
|
+
movimiento y puede seguir suavizándose. Por eso hay dos familias:
|
|
235
|
+
|
|
236
|
+
- `--netzi-duration-{instant,fast,base,slow}` son las de los **fundidos** (color, fondo,
|
|
237
|
+
borde, sombra, opacidad). No cambian con la preferencia.
|
|
238
|
+
- `--netzi-duration-move-{fast,base,slow}` son las del **desplazamiento** (`transform`,
|
|
239
|
+
`translate`, `rotate`, `scale`, `top`/`left`, alturas, filas de rejilla, márgenes). Valen
|
|
240
|
+
lo mismo que su fundido, y bajo `@media (prefers-reduced-motion: reduce)` `tokens.css` las
|
|
241
|
+
pone a `0.01ms` **en un solo bloque**: todo lo que las usa salta a su estado final.
|
|
242
|
+
|
|
243
|
+
Van a `0.01ms` y no a `0` para que `transitionend` y `animationend` sigan llegando a quien
|
|
244
|
+
los espera (con duración cero no hay transición y el evento no se dispara nunca).
|
|
245
|
+
|
|
246
|
+
```css
|
|
247
|
+
.panel {
|
|
248
|
+
transition:
|
|
249
|
+
transform var(--netzi-duration-move-slow) var(--netzi-ease-enter),
|
|
250
|
+
opacity var(--netzi-duration-slow) var(--netzi-ease-enter);
|
|
251
|
+
}
|
|
252
|
+
/* Con reduce, el panel ya no se desliza (salta) pero sigue apareciendo con un fundido:
|
|
253
|
+
no hace falta escribir ninguna regla @media. */
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Para cambiar el ritmo en tu app, redefine `--netzi-duration-{fast,base,slow}`: las `move`
|
|
257
|
+
apuntan a ellas y las siguen, y el bloque de movimiento reducido las sigue anulando. Si
|
|
258
|
+
redefines un token `move` directamente, tu regla de `:root` gana a la de `tokens.css` también
|
|
259
|
+
con la preferencia, así que repite la anulación en tu propio `@media`:
|
|
260
|
+
|
|
261
|
+
```css
|
|
262
|
+
:root {
|
|
263
|
+
--netzi-duration-move-slow: 320ms;
|
|
264
|
+
}
|
|
265
|
+
@media (prefers-reduced-motion: reduce) {
|
|
266
|
+
:root {
|
|
267
|
+
--netzi-duration-move-slow: 0.01ms;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Una transición que mueve con una duración de **fundido** sí sigue moviéndose con la
|
|
273
|
+
preferencia: elige la familia por lo que anima, no por cuánto dura. Si una capa mezcla
|
|
274
|
+
las dos (`transition: 0.15s`, que es `all`), necesita su propia regla reducida. Las
|
|
275
|
+
**animaciones** con `@keyframes` también: la duración de una animación infinita no la para.
|
|
276
|
+
En `@netzi/ui` lo vigilan dos guardianes: el de movimiento reducido entiende estos tokens
|
|
277
|
+
(una transición que solo mueve con `move` ya está reducida) y otro falla con cualquier
|
|
278
|
+
duración o curva literal en las hojas del kit, también en los bucles (solo pasan `0s` y el
|
|
279
|
+
`0.01ms` de la regla reducida).
|
|
280
|
+
|
|
281
|
+
La salida DTCG resuelta publica la escala sin la preferencia (tipos `duration` y
|
|
282
|
+
`cubicBezier`): cada plataforma nativa lee el ajuste de movimiento reducido de su sistema.
|
|
283
|
+
|
|
284
|
+
## Tamaños de ventana (`--netzi-window-*`)
|
|
285
|
+
|
|
286
|
+
El diseño cambia en cinco **tamaños de ventana con nombre** (ADR 0016 del repo), siempre en
|
|
287
|
+
los mismos cortes. Cada token es el ancho en el que **empieza** ese tamaño:
|
|
288
|
+
|
|
289
|
+
| Token | Valor | Tamaño | Ancho | Paneles |
|
|
290
|
+
| ---------------------------- | ------ | ----------- | ------------ | ------- |
|
|
291
|
+
| (ninguno) | 0 | compacto | < 600 px | 1 |
|
|
292
|
+
| `--netzi-window-medium` | 600px | medio | 600–839 px | 1 o 2 |
|
|
293
|
+
| `--netzi-window-expanded` | 840px | expandido | 840–1199 px | 2 |
|
|
294
|
+
| `--netzi-window-large` | 1200px | grande | 1200–1599 px | 2 |
|
|
295
|
+
| `--netzi-window-extra-large` | 1600px | extragrande | ≥ 1600 px | hasta 3 |
|
|
296
|
+
|
|
297
|
+
`--netzi-window-reflow` (375px) no es un tamaño: es el corte de **reflujo** dentro de
|
|
298
|
+
compacto. Por debajo (teléfonos de 320 y 360 y el zoom al 400 %, WCAG 1.4.10) lo que va en
|
|
299
|
+
fila dentro de un panel flotante se apila.
|
|
300
|
+
|
|
301
|
+
Una `var()` no vale dentro de `@media`, así que en una hoja se escribe el número del token,
|
|
302
|
+
siempre en forma de rango y con el mismo sentido:
|
|
303
|
+
|
|
304
|
+
```css
|
|
305
|
+
/* Compacto: por debajo del medio. */
|
|
306
|
+
@media (width < 600px) {
|
|
307
|
+
}
|
|
308
|
+
/* Del expandido hacia arriba. */
|
|
309
|
+
@media (width >= 840px) {
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
En `@netzi/ui` un guardián falla con cualquier otro corte de ventana en las hojas del kit.
|
|
314
|
+
Lo que depende de su **propia caja** y no de la ventana (una rejilla que cabe o no en su
|
|
315
|
+
columna) usa una consulta de contenedor con nombre, `@container <nombre> (…)`. En el código,
|
|
316
|
+
`@netzi/ui` exporta los mismos cortes (`NETZI_WINDOW_BREAKPOINTS`) y una señal con el tamaño
|
|
317
|
+
actual (`NetziWindowSizeService`). La salida DTCG resuelta los publica como `dimension` en px.
|
|
318
|
+
|
|
319
|
+
## Capas (`--netzi-layer-*`)
|
|
320
|
+
|
|
321
|
+
El orden de apilamiento (`z-index`) sale de una escala con **nombre de intención**: quien
|
|
322
|
+
pinta algo encima dice qué es (un desplegable, un aviso) y la escala decide qué tapa a qué.
|
|
323
|
+
De abajo arriba:
|
|
324
|
+
|
|
325
|
+
| Token | Valor | Para qué |
|
|
326
|
+
| ------------------------ | ----- | --------------------------------------------------------------------------- |
|
|
327
|
+
| `--netzi-layer-content` | 0 | el contenido de la página: el punto de partida |
|
|
328
|
+
| `--netzi-layer-sticky` | 2 | encabezados y columnas fijas (la tabla de datos) |
|
|
329
|
+
| `--netzi-layer-bar` | 20 | lo del marco que pasa sobre el contenido: plegar el menú, la barra de carga |
|
|
330
|
+
| `--netzi-layer-flyout` | 50 | submenús del menú plegado y menús contextuales anclados al contenido |
|
|
331
|
+
| `--netzi-layer-scrim` | 80 | el velo de un panel lateral que se abre encima (el menú en el móvil) |
|
|
332
|
+
| `--netzi-layer-modal` | 100 | diálogos y paneles laterales, con su velo dentro |
|
|
333
|
+
| `--netzi-layer-dropdown` | 120 | paneles anclados a un control y la paleta de comandos |
|
|
334
|
+
| `--netzi-layer-toast` | 200 | el aviso, que se ve aunque haya un modal o un desplegable abiertos |
|
|
335
|
+
| `--netzi-layer-tooltip` | 300 | la etiqueta de lo que tiene el puntero o el foco |
|
|
336
|
+
| `--netzi-layer-blocking` | 1000 | la pantalla que bloquea la app hasta resolver algo (sin conexión) |
|
|
337
|
+
| `--netzi-layer-top` | 10000 | lo que nunca puede quedar tapado: el enlace «Ir al contenido» con foco |
|
|
338
|
+
|
|
339
|
+
Reglas, las mismas que sigue `@netzi/ui` (un guardián falla con cualquier número suelto en
|
|
340
|
+
sus hojas):
|
|
341
|
+
|
|
342
|
+
- Dentro de la capa de un componente, el orden local va con `-1`, `0` o `1` (la capa de
|
|
343
|
+
cristal detrás de su panel, una celda fijada sobre las demás).
|
|
344
|
+
- Lo que tiene que quedar justo encima o debajo de otra cosa de su misma capa va un escalón
|
|
345
|
+
más allá: `calc(var(--netzi-layer-dropdown) + 1)`. Nunca más de uno: si hace falta más,
|
|
346
|
+
falta una capa en la escala.
|
|
347
|
+
- En `@netzi/ui` cada uso lleva el valor del token como respaldo
|
|
348
|
+
(`var(--netzi-layer-dropdown, 120)`): sin `tokens.css` el kit apila igual, y el guardián
|
|
349
|
+
comprueba que el número coincide con el token.
|
|
350
|
+
- Un desplegable dentro de un modal no necesita nada: el modal abre su propio contexto de
|
|
351
|
+
apilamiento y lo que lleva dentro va en su capa.
|
|
352
|
+
|
|
353
|
+
```css
|
|
354
|
+
.mi-barra-fija {
|
|
355
|
+
position: sticky;
|
|
356
|
+
top: 0;
|
|
357
|
+
z-index: var(--netzi-layer-bar); /* por encima de los encabezados fijos de la tabla */
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Los huecos entre valores dejan sitio a la app: una capa propia entre dos del kit se escribe
|
|
362
|
+
con `calc()` sobre la de debajo. La salida DTCG publica la escala como `number`; en móvil el
|
|
363
|
+
orden lo da la composición, y la escala sirve de referencia si una pieza tiene que ordenar.
|
|
364
|
+
|
|
365
|
+
## Forma y elevación (`--netzi-radius-*`, `--netzi-elevation-*`)
|
|
366
|
+
|
|
367
|
+
Radios por **papel**:
|
|
368
|
+
|
|
369
|
+
| Token | Valor | Compacto | Para qué |
|
|
370
|
+
| --------------------------- | ------ | -------- | --------------------------------------------------------------------------- |
|
|
371
|
+
| `--netzi-radius-control` | 6px | 5px | controles y piezas pequeñas: botones, campos, ítems de menú, iconos en caja |
|
|
372
|
+
| `--netzi-radius-surface-sm` | 8px | 6px | superficies pequeñas o pegadas a un control: el panel de un select, avisos |
|
|
373
|
+
| `--netzi-radius-surface` | 10px | 8px | superficies: tarjetas, diálogos, popovers, paneles |
|
|
374
|
+
| `--netzi-radius-pill` | 9999px | 9999px | lo del todo redondeado: píldoras, puntos, avatares, interruptores |
|
|
375
|
+
|
|
376
|
+
En una pieza cuadrada, `pill` es un círculo; en una alargada, una píldora. Sustituye a los
|
|
377
|
+
`999px` y `50%` sueltos, y la densidad no lo toca.
|
|
378
|
+
|
|
379
|
+
La elevación dice **cuánto se despega** una pieza de lo que tiene debajo, con nombre de
|
|
380
|
+
intención y de menos a más:
|
|
381
|
+
|
|
382
|
+
| Token | Para qué |
|
|
383
|
+
| ---------------------------- | -------------------------------------------------------------------------------- |
|
|
384
|
+
| `--netzi-elevation-base` | pegado a su superficie, sin sombra: para devolver a cero lo que la hereda |
|
|
385
|
+
| `--netzi-elevation-raised` | levantado sobre su superficie: el pulgar de un interruptor, una tecla |
|
|
386
|
+
| `--netzi-elevation-floating` | pequeño y suelto sobre el contenido: el tooltip, la etiqueta de una gráfica |
|
|
387
|
+
| `--netzi-elevation-modal` | lo que se abre encima y lo aparta: desplegables, menús, aviso, diálogos, paneles |
|
|
388
|
+
|
|
389
|
+
```css
|
|
390
|
+
.mi-tarjeta-flotante {
|
|
391
|
+
border: 1px solid var(--netzi-border);
|
|
392
|
+
border-radius: var(--netzi-radius-surface);
|
|
393
|
+
box-shadow: var(--netzi-elevation-floating);
|
|
394
|
+
}
|
|
395
|
+
.mi-punto {
|
|
396
|
+
border-radius: var(--netzi-radius-pill);
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Una sombra **nunca es lo único** que separa una capa: lleva su borde. En oscuro las sombras son
|
|
401
|
+
más densas, en contraste alto `raised` se marca más y en papel no hay ninguna. La salida DTCG
|
|
402
|
+
publica los mismos niveles (tipo `shadow`): en móvil cada plataforma pinta el nivel a su
|
|
403
|
+
manera.
|
|
404
|
+
|
|
405
|
+
## Tema claro / oscuro
|
|
406
|
+
|
|
407
|
+
El tema se controla con el atributo `data-theme` en un ancestro (normalmente `<html>`):
|
|
408
|
+
|
|
409
|
+
```html
|
|
410
|
+
<html data-theme="dark">
|
|
411
|
+
...
|
|
412
|
+
</html>
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Sin el atributo, aplica el tema claro (`:root`). Con `data-theme="light"`, una sección vuelve
|
|
416
|
+
al claro dentro de una página oscura (#237): pinta lo mismo que una página clara con el preset,
|
|
417
|
+
el contraste y el cristal de la página. Si tu app redefine tokens en `:root` para el claro,
|
|
418
|
+
ponlos también en `[data-theme='light']`, como haces en `[data-theme='dark']` para el oscuro.
|
|
419
|
+
|
|
420
|
+
## Impresión
|
|
421
|
+
|
|
422
|
+
Al imprimir, el papel sale claro, opaco y quieto sin que la app haga nada:
|
|
423
|
+
|
|
424
|
+
- Los bloques que fijan `data-theme`, `data-glass` o `data-ambient` van en
|
|
425
|
+
`@media not print`: en papel no aplican y sale el tema claro y opaco, con el preset, el
|
|
426
|
+
contraste alto y la densidad intactos.
|
|
427
|
+
- En `@media print`, la raíz pierde las sombras y el desenfoque del velo, y cada duración
|
|
428
|
+
baja a `0.01ms`.
|
|
429
|
+
- El bloque de papel se declara en la raíz y en `[data-contrast='high']`, que redefine la
|
|
430
|
+
sombra pequeña: una sección con su propio contraste alto tampoco imprime sombras.
|
|
431
|
+
- El contrato de contraste se comprueba también en papel: un color que el contexto de
|
|
432
|
+
impresión redefina y no llegue a su mínimo rompe el build.
|
|
433
|
+
- `base.css` pone el papel en blanco, subraya los enlaces (en blanco y negro el color no los
|
|
434
|
+
distingue) y quita animaciones y transiciones a medio camino.
|
|
435
|
+
- Con el atributo `data-print-urls` en un enlace o en un contenedor, cada enlace externo
|
|
436
|
+
imprime su dirección detrás. Es opcional: en toda la página llenaría de direcciones el menú.
|
|
437
|
+
|
|
438
|
+
En `@netzi/ui`, el marco de `netzi-layout` y las capas flotantes no se imprimen. Un modal o
|
|
439
|
+
un drawer abierto sí: una vez, sin el velo y en el flujo de la página, donde está el
|
|
440
|
+
componente, sin tapar nada. Fijo, el navegador lo repetiría encima de cada hoja, y puede ser
|
|
441
|
+
justo lo que se quiere en papel (un recibo).
|
|
442
|
+
|
|
443
|
+
Lo propio de la app que no va en papel se oculta con su regla `@media print`. Si redefines
|
|
444
|
+
un token del tema oscuro en tu hoja, envuélvelo también en `@media not print`.
|
|
445
|
+
|
|
446
|
+
## Overrides (white-label por app)
|
|
447
|
+
|
|
448
|
+
El mecanismo de personalización **sobrevive a las actualizaciones**: redefine los
|
|
449
|
+
tokens que quieras en tu propio scope. Las nuevas versiones de `@netzi/tokens`
|
|
450
|
+
cambian solo los _defaults_; nunca pisan lo que tu app sobreescribe.
|
|
451
|
+
|
|
452
|
+
```css
|
|
453
|
+
/* tema de la app "Acme": radios propios */
|
|
454
|
+
:root {
|
|
455
|
+
--netzi-radius: 8px;
|
|
456
|
+
--netzi-radius-lg: 14px;
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
El movimiento se personaliza por las duraciones de fundido (`--netzi-duration-{fast,base,slow}`),
|
|
461
|
+
que arrastran a las de desplazamiento. Redefinir un `--netzi-duration-move-*` obliga a repetir
|
|
462
|
+
su `0.01ms` dentro de tu `@media (prefers-reduced-motion: reduce)`: ver «Movimiento».
|
|
463
|
+
|
|
464
|
+
Los colores no se cambian de uno en uno: cada token de color se pinta encima o
|
|
465
|
+
debajo de otro, y cambiar solo uno rompe el par. Para poner el color de tu marca,
|
|
466
|
+
sigue el contrato de la sección siguiente.
|
|
467
|
+
|
|
468
|
+
## Estados de interacción
|
|
469
|
+
|
|
470
|
+
Hover, foco, presionado, arrastrado, seleccionado y deshabilitado son un solo sistema (#156).
|
|
471
|
+
Cada estado es una **capa** translúcida del color del texto que se pinta encima del fondo del
|
|
472
|
+
control, sin cambiarlo; la opacidad va aparte para quien pinte la capa con su propio color:
|
|
473
|
+
|
|
474
|
+
| Token | Opacidad | Para qué |
|
|
475
|
+
| -------------------------------- | -------- | ---------------------------------------------------- |
|
|
476
|
+
| `--netzi-state-hover` | 0.08 | El puntero encima |
|
|
477
|
+
| `--netzi-state-focus` | 0.12 | Lo enfocado dentro de un compuesto (más su anillo) |
|
|
478
|
+
| `--netzi-state-pressed` | 0.12 | Mientras se pulsa, con el ratón o con el dedo |
|
|
479
|
+
| `--netzi-state-dragged` | 0.16 | La pieza que se lleva con el puntero |
|
|
480
|
+
| `--netzi-state-selected` | — | Lo seleccionado: el tinte del primario |
|
|
481
|
+
| `--netzi-state-disabled-opacity` | 0.6 | Lo deshabilitado, sobre el control entero |
|
|
482
|
+
| `--netzi-primary-pressed` | — | El relleno de marca presionado (cada preset el suyo) |
|
|
483
|
+
|
|
484
|
+
```css
|
|
485
|
+
.mi-control:active:not(:disabled) {
|
|
486
|
+
background-image: linear-gradient(var(--netzi-state-pressed), var(--netzi-state-pressed));
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Como imagen de fondo la capa no se interpola: aparece al instante, también con movimiento
|
|
491
|
+
reducido. El build comprueba que `--netzi-text` llega a 4.5:1 sobre cada superficie con
|
|
492
|
+
cualquier capa encima y que el texto deshabilitado se queda a 3:1. Un relleno de marca no lleva
|
|
493
|
+
la capa (acercaría el fondo a su tinta): usa `--netzi-primary-pressed`.
|
|
494
|
+
|
|
495
|
+
## Marca propia (`@netzi/tokens/brand`)
|
|
496
|
+
|
|
497
|
+
La marca va en el **primario**, no en `--netzi-blue`. `--netzi-blue` es un acento de
|
|
498
|
+
Netzi y además el color de estado `--netzi-info`, y `--netzi-on-accent` (la tinta de
|
|
499
|
+
los contadores y de los rellenos de acento) se mide sobre él: cambiarlo a un morado
|
|
500
|
+
deja esa tinta en 3.35:1. Si aun así lo cambias, redefine con él `--netzi-blue-text`,
|
|
501
|
+
`--netzi-blue-dark` y `--netzi-on-accent`, y comprueba el contraste de los tres.
|
|
502
|
+
|
|
503
|
+
### El contrato: ocho tokens que se redefinen juntos
|
|
504
|
+
|
|
505
|
+
| Token | Papel | Se mide sobre | Mínimo |
|
|
506
|
+
| ------------------------- | --------------------------------------------- | ----------------------------------------------- | ------ |
|
|
507
|
+
| `--netzi-primary` | Relleno de marca: botón, interruptor, barra | (fondo de `on-primary`) | — |
|
|
508
|
+
| `--netzi-primary-hover` | El relleno al pasar el puntero | (fondo de `on-primary`) | — |
|
|
509
|
+
| `--netzi-primary-pressed` | El relleno presionado (#156) | (fondo de `on-primary`) | — |
|
|
510
|
+
| `--netzi-on-primary` | Tinta sobre el relleno, su hover y presionado | `primary`, `primary-hover`, `primary-pressed` | 4.5:1 |
|
|
511
|
+
| `--netzi-primary-dark` | Relleno de lo marcado (checkbox, radio) | cada superficie | 3:1 |
|
|
512
|
+
| `--netzi-on-primary-dark` | Tinta sobre el primario oscuro (blanco) | `primary-dark` | 3:1 |
|
|
513
|
+
| `--netzi-primary-text` | El primario que se lee (enlaces, ítem activo) | cada superficie y `primary-tint` sobre cada una | 4.5:1 |
|
|
514
|
+
| `--netzi-primary-graphic` | Lo no textual que comunica (progreso, anillo) | cada superficie y `primary-tint` sobre cada una | 3:1 |
|
|
515
|
+
|
|
516
|
+
Lo demás sale solo del primario: los tintes (`--netzi-primary-tint`, 10 % en claro y
|
|
517
|
+
15 % en oscuro; `--netzi-primary-tint-strong`, 22 % y 28 %), el anillo de foco
|
|
518
|
+
(`--netzi-focus-ring` es `--netzi-primary-text`) y el fondo ambiental. Pero el
|
|
519
|
+
relleno también se mide por ellos: `--netzi-text` tiene que llegar a 4.5:1 sobre el
|
|
520
|
+
tinte marcado y el pulgar de las barras de desplazamiento (`--netzi-text-2`) a 3:1
|
|
521
|
+
sobre el suave. Todo en claro y en oscuro, sobre `bg`, `surface`, `surface-2` y
|
|
522
|
+
`surface-3`; el alto contraste no cambia el primario y solo sube los textos.
|
|
523
|
+
|
|
524
|
+
### Derivarlo de un color
|
|
525
|
+
|
|
526
|
+
`deriveBrand` devuelve los ocho tokens de cada modo ya calibrados. El relleno es tu
|
|
527
|
+
color tal cual mientras alguna de las dos tintas del kit (`#04121f` o blanco) llegue a
|
|
528
|
+
4.5:1 encima; en los tonos medios no llega ninguna, y entonces se mueve al tono más
|
|
529
|
+
cercano que sí (`adjusted: true`). El texto, el gráfico y el primario oscuro son el
|
|
530
|
+
tono más cercano al relleno que llega a su mínimo en cada modo; el hover oscurece lo
|
|
531
|
+
justo para notarse sin perder la tinta, y el presionado es el hover con la capa de
|
|
532
|
+
presionado (12 %) del extremo contrario a la tinta, que la aleja en vez de acercarla.
|
|
533
|
+
|
|
534
|
+
```js
|
|
535
|
+
import { brandVariables, deriveBrand, validateBrand } from '@netzi/tokens/brand';
|
|
536
|
+
|
|
537
|
+
const marca = deriveBrand('#6c3cff'); // { color, adjusted, light, dark }
|
|
538
|
+
validateBrand(marca).ok; // true
|
|
539
|
+
brandVariables(marca.dark); // { '--netzi-primary': '#6c3cff', '--netzi-primary-text': '#9f80ff', … }
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
En CSS, el resultado para el morado de «Acme». Carga esta hoja **después** de
|
|
543
|
+
`@netzi/tokens` y no pongas `data-preset`, que redefine el primario:
|
|
544
|
+
|
|
545
|
+
<!-- ejemplo-marca -->
|
|
546
|
+
|
|
547
|
+
```css
|
|
548
|
+
/* Marca "Acme": deriveBrand('#6c3cff') de @netzi/tokens/brand */
|
|
549
|
+
[data-theme='light'],
|
|
550
|
+
:root {
|
|
551
|
+
--netzi-primary: #6c3cff;
|
|
552
|
+
--netzi-primary-hover: #5f35e0;
|
|
553
|
+
--netzi-primary-pressed: #542fc5;
|
|
554
|
+
--netzi-primary-dark: #6c3cff;
|
|
555
|
+
--netzi-primary-text: #693af7;
|
|
556
|
+
--netzi-primary-graphic: #6c3cff;
|
|
557
|
+
--netzi-on-primary: #ffffff;
|
|
558
|
+
--netzi-on-primary-dark: #ffffff;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
[data-theme='dark'] {
|
|
562
|
+
--netzi-primary: #6c3cff;
|
|
563
|
+
--netzi-primary-hover: #5f35e0;
|
|
564
|
+
--netzi-primary-pressed: #542fc5;
|
|
565
|
+
--netzi-primary-dark: #764aff;
|
|
566
|
+
--netzi-primary-text: #9f80ff;
|
|
567
|
+
--netzi-primary-graphic: #7f55ff;
|
|
568
|
+
--netzi-on-primary: #ffffff;
|
|
569
|
+
--netzi-on-primary-dark: #ffffff;
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
La regla clara va también en `[data-theme='light']`: el paquete vuelve a declarar ahí los
|
|
574
|
+
valores claros de las capas para una sección clara dentro de una página oscura (#237), y sin
|
|
575
|
+
ella la sección perdería la marca. Un test del paquete lee este bloque: si deja de cumplir, o
|
|
576
|
+
de ser lo que da `deriveBrand('#6c3cff')`, la suite se pone roja.
|
|
577
|
+
|
|
578
|
+
### Validar una marca hecha a mano
|
|
579
|
+
|
|
580
|
+
`validateBrand({ light, dark })` mide cada par del contrato y devuelve
|
|
581
|
+
`{ ok, checks, failures }`; cada fallo dice el modo, la tinta, el fondo, el ratio y el
|
|
582
|
+
mínimo. Lanza `TypeError` si falta un modo o un token no es `#rgb` o `#rrggbb`. Mide contra las
|
|
583
|
+
superficies del tema por defecto: si tu app redefine también las superficies, ya no
|
|
584
|
+
es este contrato, y conviene medir el resultado en la página. Sobre las capas de
|
|
585
|
+
**cristal** el texto se lee contra lo que haya detrás; el kit lo comprueba para sus
|
|
586
|
+
presets, no para una marca propia.
|
|
587
|
+
|
|
588
|
+
En Angular, `@netzi/ui` lo hace por ti: `provideNetziBrand('#6c3cff')` (o
|
|
589
|
+
`NetziThemeService.setBrand`) deriva la marca, la escribe en una hoja propia por encima
|
|
590
|
+
del preset (en claro, en cada sección clara y en cada sección oscura) y, en desarrollo, avisa en la consola si el color no cumple tal cual.
|
|
591
|
+
|
|
592
|
+
El contrato no es una copia: un test del paquete mete lo que deriva `deriveBrand` en la
|
|
593
|
+
fuente DTCG y le pasa el mismo contrato de contraste que el build, en claro, oscuro y
|
|
594
|
+
alto contraste.
|
|
595
|
+
|
|
596
|
+
## Catálogo de tokens
|
|
597
|
+
|
|
598
|
+
| Grupo | Tokens |
|
|
599
|
+
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
600
|
+
| Estado | `--netzi-info` `--netzi-success` `--netzi-warning` `--netzi-danger` (+ su `-text`) |
|
|
601
|
+
| Superficies | `--netzi-bg` `--netzi-surface` `--netzi-surface-2` `--netzi-surface-3` |
|
|
602
|
+
| Bordes | `--netzi-border` `--netzi-border-2` |
|
|
603
|
+
| Flotantes | `--netzi-float-bg` `--netzi-float-border` `--netzi-float-border-2` `--netzi-float-surface` (`-2`, `-3`) `--netzi-float-filter` `--netzi-float-glow` |
|
|
604
|
+
| Velo modal | `--netzi-scrim` `--netzi-scrim-filter` |
|
|
605
|
+
| Lente | `--netzi-float-lens` `--netzi-float-lens-size` (opt-in, ver abajo) |
|
|
606
|
+
| Texto | `--netzi-text` `--netzi-text-2` `--netzi-text-3` `--netzi-on-accent` |
|
|
607
|
+
| Elevación | `--netzi-elevation-{base,raised,floating,modal}` |
|
|
608
|
+
| Acento | `--netzi-tint` `--netzi-primary-tint` `--netzi-primary-tint-strong` |
|
|
609
|
+
| Forma | `--netzi-radius-control` `--netzi-radius-surface` `--netzi-radius-surface-sm` `--netzi-radius-pill` |
|
|
610
|
+
| Controles | `--netzi-control-h-{sm,md,lg}` `--netzi-control-px-*` `--netzi-control-fs-*` `--netzi-touch-target` `--netzi-touch-gap` |
|
|
611
|
+
| Tipografía | `--netzi-font-sans` `--netzi-font-mono` `--netzi-font-scale` `--netzi-typography-*` `--netzi-measure` |
|
|
612
|
+
| Espaciado | `--netzi-space-{related,group,stack,section,page}` `--netzi-space-inset` (`-sm`, `-lg`) |
|
|
613
|
+
| Movimiento | `--netzi-duration-{instant,fast,base,slow}` `--netzi-duration-move-{fast,base,slow}` `--netzi-ease-{standard,enter,exit}` |
|
|
614
|
+
| Capas | `--netzi-layer-{content,sticky,bar,flyout,scrim,modal,dropdown,toast,tooltip,blocking,top}` |
|
|
615
|
+
|
|
616
|
+
El catálogo son los tokens **públicos**: los que puedes usar y redefinir. La lista exacta va
|
|
617
|
+
en el paquete (`public-tokens.json`, ver «Públicos e internos»).
|
|
618
|
+
|
|
619
|
+
Un token que **no** existe no da error: la declaración entera se descarta y el elemento se
|
|
620
|
+
queda sin fondo, sin color o sin radio, sin aviso en build ni en consola. Por eso cada papel
|
|
621
|
+
tiene **un solo nombre**: los sinónimos que había están en desuso (ver abajo).
|
|
622
|
+
|
|
623
|
+
### Un panel de cristal entra por transición, nunca por animación
|
|
624
|
+
|
|
625
|
+
Una animación CSS terminada se queda retenida en el elemento y, si animó
|
|
626
|
+
`opacity`, Chromium deja el panel compuesto como su propia raíz de fondo: el
|
|
627
|
+
`backdrop-filter` de su capa de cristal solo ve el fondo del propio panel y la
|
|
628
|
+
página se lee nítida a través de él. Por eso todos los paneles del kit entran con
|
|
629
|
+
`transition` + `@starting-style`, y un guardián de la suite lo vigila. Si añades
|
|
630
|
+
una capa de cristal nueva, no le pongas `@keyframes` de entrada.
|
|
631
|
+
|
|
632
|
+
### La lente del canto, y por qué es opt-in
|
|
633
|
+
|
|
634
|
+
Con `data-glass-lens="on"` en la raíz, el borde de las capas modales (modal,
|
|
635
|
+
drawer, paleta ⌘K) lleva **su propio desenfoque**, más fuerte que el del interior:
|
|
636
|
+
es lo que da sensación de espesor.
|
|
637
|
+
|
|
638
|
+
No es refracción de verdad —el fondo no se curva— y **hoy no puede serlo**:
|
|
639
|
+
Chromium acepta `backdrop-filter: url(#filtro-svg)` y luego lo ignora (el mismo
|
|
640
|
+
filtro como `filter` normal sí deforma, así que el problema es el
|
|
641
|
+
`backdrop-filter`, no el filtro), y Safari ni lo acepta.
|
|
642
|
+
|
|
643
|
+
Es opt-in porque **cuesta**: es una segunda capa de `backdrop-filter`. Medido con
|
|
644
|
+
el fondo en movimiento:
|
|
645
|
+
|
|
646
|
+
| escenario | fps |
|
|
647
|
+
| ------------------------------------ | ----- |
|
|
648
|
+
| una capa con cristal | 60 |
|
|
649
|
+
| una capa con cristal **+ lente** | 60 |
|
|
650
|
+
| tres capas apiladas | 30-38 |
|
|
651
|
+
| tres capas apiladas **+ lente** | 26 |
|
|
652
|
+
| capa con lente, con el shell apagado | 51 |
|
|
653
|
+
|
|
654
|
+
Con una sola capa en pantalla sale gratis; apilada, se nota. La última fila es
|
|
655
|
+
un experimento **descartado**: apagar el filtro del topbar y del sidebar bajo una
|
|
656
|
+
capa modal daba esos frames, pero el velo es translúcido y el salto de tono del
|
|
657
|
+
shell se veía al abrir. `NetziOverlayService` y su marca `data-netzi-overlay="open"`
|
|
658
|
+
se quedan para que la app reaccione a «hay algo encima» (pausar una animación
|
|
659
|
+
cara, un vídeo de fondo).
|
|
660
|
+
|
|
661
|
+
### Estados y tintes: relleno o texto, nunca los dos
|
|
662
|
+
|
|
663
|
+
`--netzi-success` y compañía son **relleno** (un punto de estado, un fondo). Para texto va su
|
|
664
|
+
`-text`, que es la versión que llega a 4.5:1. Con los tintes del primario pasa lo mismo por otro
|
|
665
|
+
lado: `--netzi-primary-tint` (10 %) es para superficies grandes y aguanta `--netzi-primary-text`
|
|
666
|
+
justo; en una pieza pequeña se pierde contra el fondo, y para eso está
|
|
667
|
+
`--netzi-primary-tint-strong` (22 %), cuya tinta es `--netzi-text`.
|
|
668
|
+
|
|
669
|
+
Lo mismo vale para el primario y los acentos a secas: `--netzi-primary`,
|
|
670
|
+
`--netzi-blue`, `--netzi-green`… son **relleno** y no llegan a 4.5:1 como texto
|
|
671
|
+
sobre fondo claro (`--netzi-blue` da 2.54:1 sobre el fondo y 2.68:1 sobre blanco;
|
|
672
|
+
`--netzi-primary-dark` baja a 4.23:1 en `surface-3`). Si se lee, va
|
|
673
|
+
`--netzi-primary-text` o el `-text` del color, que están calibrados por tema y por
|
|
674
|
+
preset. Es lo que le pasó al botón `soft` (#56).
|
|
675
|
+
|
|
676
|
+
Y un relleno del primario tiene **dos fondos oscuros** según lo que lleve encima.
|
|
677
|
+
`--netzi-primary-hover` es el del estado hover de un botón relleno: su tinta es la
|
|
678
|
+
misma `--netzi-on-primary` del reposo, y cada preset lo calibra para que no baje de
|
|
679
|
+
4.5:1 (5.4:1 en azul). `--netzi-primary-dark` es el fondo de lo que lleva **blanco**
|
|
680
|
+
(`--netzi-on-primary-dark`): la pista de un interruptor encendido, la marca de un
|
|
681
|
+
checkbox. Si se cruzan, la tinta oscura sobre el azul oscuro se queda en 3.9:1 (#68).
|
|
682
|
+
|
|
683
|
+
### Lo no textual que comunica: `--netzi-primary-graphic`
|
|
684
|
+
|
|
685
|
+
El relleno de una barra de progreso, un anillo de carga, una barra de navegación: no son
|
|
686
|
+
texto, pero dicen algo, y WCAG 1.4.11 les pide **3:1** frente a lo que tienen al lado. El
|
|
687
|
+
primario crudo no llega en claro con azul (2.15:1 en el peor fondo), verde (1.36), naranja
|
|
688
|
+
(1.78), rojo (2.51) ni cian (1.92). `--netzi-primary-graphic` es el tono más cercano al
|
|
689
|
+
primario que llega a 3:1 sobre cada superficie y sobre `--netzi-primary-tint` (el fondo de un
|
|
690
|
+
ítem activo), calibrado por preset y por tema; su contrato `non-text` lo comprueba el build en
|
|
691
|
+
las 24 combinaciones (#140). `--netzi-primary-text` también cumple, pero se aleja más del
|
|
692
|
+
color de marca.
|
|
693
|
+
|
|
694
|
+
Dos casos en que no basta:
|
|
695
|
+
|
|
696
|
+
- **Lo que late en opacidad.** En el valle del latido el color se mezcla con el fondo y
|
|
697
|
+
baja; con el valle de `netzi-progress` (0,75) `--netzi-primary-graphic` se queda por debajo de
|
|
698
|
+
3:1. Lo que late va con `--netzi-primary-text`, que deja margen.
|
|
699
|
+
- **Los colores de estado.** Una barra de éxito, aviso o error va con su `-text`
|
|
700
|
+
(`--netzi-success-text`…) o con su `-graphic` (abajo): el crudo da 1.43:1 (verde) o
|
|
701
|
+
1.50:1 (amarillo) en claro.
|
|
702
|
+
|
|
703
|
+
Un degradado cuenta en cada punto que se ve: si se desvanece hasta transparente, su cola
|
|
704
|
+
baja de cualquier mínimo. El del kit va de un primario que cumple a otro que cumple.
|
|
705
|
+
|
|
706
|
+
### Lo no textual de un estado: `--netzi-*-graphic` y `--netzi-*-veil`
|
|
707
|
+
|
|
708
|
+
La estrella marcada, el punto de presencia de un avatar, el borde de un campo inválido o el
|
|
709
|
+
icono de un resultado también son no textuales que comunican, y el crudo del estado no llega
|
|
710
|
+
en claro: azul 2.08:1, verde 1.33, amarillo 1.41 y rojo 2.15 en el peor fondo (#189). Cada
|
|
711
|
+
estado tiene su gráfico, el tono más cercano al crudo que llega a **3:1** sobre cada superficie
|
|
712
|
+
y sobre su propio velo. En oscuro el crudo ya cumple y es su valor.
|
|
713
|
+
|
|
714
|
+
| Token | Claro | Oscuro | Velo (14 %) |
|
|
715
|
+
| ------------------------- | --------- | --------- | ---------------------- |
|
|
716
|
+
| `--netzi-info-graphic` | `#0e84cd` | `#0ba5ff` | `--netzi-info-veil` |
|
|
717
|
+
| `--netzi-success-graphic` | `#069047` | `#0ae972` | `--netzi-success-veil` |
|
|
718
|
+
| `--netzi-warning-graphic` | `#9d7a0c` | `#f0c040` | `--netzi-warning-veil` |
|
|
719
|
+
| `--netzi-danger-graphic` | `#df464b` | `#ff6b6b` | `--netzi-danger-veil` |
|
|
720
|
+
|
|
721
|
+
Los alias de estado apuntan a los de marca (`--netzi-blue-graphic`, `--netzi-green-graphic`,
|
|
722
|
+
`--netzi-yellow-graphic`, `--netzi-red-graphic`), como los `-text`. El velo es el fondo redondo
|
|
723
|
+
de un icono de estado: el contrato `non-text` de cada gráfico lo mide compuesto sobre cada
|
|
724
|
+
superficie, en las 24 combinaciones de modo, contraste y preset. Si pones un icono sobre una
|
|
725
|
+
mezcla más opaca, ya no está medido.
|
|
726
|
+
|
|
727
|
+
### Con el dedo, objetivos de 44 px (`--netzi-touch-target`)
|
|
728
|
+
|
|
729
|
+
Con puntero táctil (`@media (pointer: coarse)`) nada de lo que se toca en un campo baja de
|
|
730
|
+
`--netzi-touch-target` (44 px, WCAG 2.5.5). Los tokens suben `--netzi-control-h-sm` y
|
|
731
|
+
`--netzi-control-h-md` a 46 px también con la densidad compacta (el borde de 1 px del
|
|
732
|
+
contenedor no le quita al input), y cada componente agranda lo suyo: filas de casillas y
|
|
733
|
+
radios, opciones, días del calendario, chips, estrellas, la franja de los sliders… Los
|
|
734
|
+
iconos pequeños (las × de los chips, la flecha de un desplegable) amplían solo su zona de toque, sin mover
|
|
735
|
+
el dibujo. Con ratón no cambia nada. Una app que ponga controles propios puede usar el
|
|
736
|
+
mismo token en su `@media (pointer: coarse)`.
|
|
737
|
+
|
|
738
|
+
Entre dos objetivos vecinos queda además `--netzi-touch-gap` (8 px, criterio A.5 de la
|
|
739
|
+
política 17), medido entre zonas de toque y no entre dibujos: botones de la paginación,
|
|
740
|
+
estrellas, pestañas, chips, celdas, días del calendario, ítems de un menú, la barra del
|
|
741
|
+
editor… Las piezas de un control segmentado se tocan a propósito y quedan fuera, como pide
|
|
742
|
+
la política. También quedan fuera, mientras la política no las recoja (#226), las piezas de
|
|
743
|
+
un campo de una línea (el input y su botón: −/+, el ojo, la flecha del desplegable) y las
|
|
744
|
+
opciones de una lista. El token es para usarlo en un `@media (pointer: coarse)` propio; con
|
|
745
|
+
ratón, cada componente conserva su hueco.
|
|
746
|
+
|
|
747
|
+
### El foco va con `--netzi-focus-ring`, no con el primario
|
|
748
|
+
|
|
749
|
+
Un indicador de foco, contorno o borde, tiene que llegar a **3:1** sobre el fondo (WCAG
|
|
750
|
+
1.4.11). El primario no llega en claro con azul (2.7:1), verde (1.6), naranja (2.2) ni cian
|
|
751
|
+
(2.4). `--netzi-focus-ring` se apoya en `--netzi-primary-text`, que cada preset y cada tema
|
|
752
|
+
calibran a 4.5:1 sobre las superficies, también en alto contraste. Se declara en la raíz y
|
|
753
|
+
en cada elemento con `data-theme` o `data-preset`, así que una sección oscura dentro de una
|
|
754
|
+
página clara tiene su propio anillo.
|
|
755
|
+
|
|
756
|
+
Todo `:focus`, `:focus-visible` y `:focus-within` del kit lo usa como contorno o borde (los
|
|
757
|
+
halos del tinte no cuentan: dan 1.1:1). Dos guardianes de la suite del kit impiden volver
|
|
758
|
+
al primario y quitar el contorno sin poner otro indicador (#76).
|
|
759
|
+
|
|
760
|
+
Sobre una capa de **cristal** el anillo se ve contra lo que haya detrás: sobre el fondo del
|
|
761
|
+
kit cumple, pero con una imagen muy oscura (en claro) o muy clara (en oscuro) debajo puede
|
|
762
|
+
bajar de 3:1. Si la app pone cristal sobre fondos así, conviene darle a esa capa un tinte
|
|
763
|
+
más opaco.
|
|
764
|
+
|
|
765
|
+
## Los tres niveles de texto
|
|
766
|
+
|
|
767
|
+
Tienen **papeles distintos**, no son tres grados de gris intercambiables. El ratio
|
|
768
|
+
es el peor de las cuatro superficies del tema (`bg`, `surface`, `surface-2`,
|
|
769
|
+
`surface-3`); `surface-3` es siempre el caso malo.
|
|
770
|
+
|
|
771
|
+
| Token | Claro | Oscuro | Para qué |
|
|
772
|
+
| ---------------- | ------ | ------ | ---------------------------------------------------------------------------------- |
|
|
773
|
+
| `--netzi-text` | 13.7:1 | 15.2:1 | Títulos, valores y cuerpo. |
|
|
774
|
+
| `--netzi-text-2` | 4.8:1 | 5.0:1 | **Cualquier** texto secundario: rótulos, ayudas, pies, metadatos, placeholders. |
|
|
775
|
+
| `--netzi-text-3` | 3.2:1 | 3.3:1 | **No textual**: iconos decorativos, chevrons, separadores, líneas, deshabilitados. |
|
|
776
|
+
|
|
777
|
+
WCAG 2.1 pide **4.5:1** para texto normal (SC 1.4.3) y **3:1** para lo no textual
|
|
778
|
+
con significado (SC 1.4.11). Por eso `--netzi-text-3` **no vale para texto**: si
|
|
779
|
+
lo que pintas se lee, va `--netzi-text-2`.
|
|
780
|
+
|
|
781
|
+
Con `data-contrast='high'` cada nivel toma el valor del de encima: `text-2` sube a
|
|
782
|
+
11.3:1 en claro y 7.2:1 en oscuro, y `text-3` a 4.8:1 y 5.0:1, así que el modo cumple
|
|
783
|
+
incluso donde alguien haya usado `text-3` para un rótulo.
|
|
784
|
+
|
|
785
|
+
Los ratios no son una promesa del README: cada token que se pinta sobre algo declara en
|
|
786
|
+
la fuente su **contrato de contraste** (texto, 4.5:1; no textual, 3:1; y sobre qué fondos),
|
|
787
|
+
y el build lo comprueba en las 24 combinaciones de modo, contraste y preset antes de
|
|
788
|
+
generar `tokens.css`. Si un valor deja de cumplir, el paquete no se construye. Ver
|
|
789
|
+
«Cómo se genera» al final.
|
|
790
|
+
|
|
791
|
+
## Capas flotantes y superficies de cristal
|
|
792
|
+
|
|
793
|
+
Las capas que se pintan **encima** del contenido —topbar, sidebar, paneles
|
|
794
|
+
anclados, modal, drawer, menús y toasts— no usan `--netzi-surface` directamente,
|
|
795
|
+
sino su propio juego de tokens:
|
|
796
|
+
|
|
797
|
+
| Token | Para qué |
|
|
798
|
+
| ------------------------- | ----------------------------------------------- |
|
|
799
|
+
| `--netzi-float-bg` | Fondo de la capa |
|
|
800
|
+
| `--netzi-float-border` | Su borde |
|
|
801
|
+
| `--netzi-float-border-2` | Su borde marcado |
|
|
802
|
+
| `--netzi-float-surface` | Lo ANIDADO dentro de la capa: la lámina misma |
|
|
803
|
+
| `--netzi-float-surface-2` | … a un paso de la lámina (tarjetas, hovers) |
|
|
804
|
+
| `--netzi-float-surface-3` | … a dos pasos |
|
|
805
|
+
| `--netzi-float-filter` | Valor de `backdrop-filter` (por defecto `none`) |
|
|
806
|
+
|
|
807
|
+
Cada capa re-escopa `--netzi-surface`, `-2` y `-3` a los tres `float-surface`, así
|
|
808
|
+
que **todo lo que va dentro** (una tarjeta, la burbuja de un comentario, el hover
|
|
809
|
+
de un botón) hereda el vidrio sin saber que está en una capa. Con cristal son
|
|
810
|
+
tintes translúcidos **oscuros** que se alejan de la lámina un paso, dos y tres
|
|
811
|
+
(una tarjeta es más gris que la lámina, como en opaco). En claro eso resta
|
|
812
|
+
contraste, y por eso `--netzi-text-2` con cristal baja dos niveles (el mismo
|
|
813
|
+
valor que en alto contraste, `#2a333c`); el test de contraste del paquete exige que
|
|
814
|
+
sobre el tinte más fuerte siga por encima de su suelo incluso con negro puro debajo.
|
|
815
|
+
|
|
816
|
+
Con el cristal apagado valen exactamente lo mismo que los tokens de siempre, así
|
|
817
|
+
que no cambia nada. Con `data-glass="on"` en un ancestro (lo pone
|
|
818
|
+
`NetziThemeService` en `<html>`, y también vale en un scope acotado) esas capas
|
|
819
|
+
pasan a ser translúcidas con el fondo desenfocado y saturado:
|
|
820
|
+
|
|
821
|
+
```html
|
|
822
|
+
<html data-theme="dark" data-glass="on">
|
|
823
|
+
...
|
|
824
|
+
</html>
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
Las superficies de **contenido** (tarjetas, secciones, tablas) siguen opacas a
|
|
828
|
+
propósito: ahí el cristal solo resta legibilidad.
|
|
829
|
+
|
|
830
|
+
### Fondo ambiental: darle al cristal algo que desenfocar
|
|
831
|
+
|
|
832
|
+
Un cristal sobre un fondo liso se ve como una placa gris: no hay nada detrás
|
|
833
|
+
que atravesar. Con `data-ambient="on"` en la raíz (`NetziThemeService.setAmbient`,
|
|
834
|
+
interruptor «Fondo con color» en `netzi-settings`) el layout pinta bajo
|
|
835
|
+
`--netzi-bg` unas manchas de color suaves —el primario del preset, morado y
|
|
836
|
+
verde— que se ven a través del topbar, del sidebar y de cualquier capa que pase
|
|
837
|
+
por encima. Token: `--netzi-ambient` (`none` sin el eje). Los porcentajes son
|
|
838
|
+
bajos y el texto se sigue leyendo encima (`--netzi-text-3` sobre la mancha más
|
|
839
|
+
fuerte: 4.4:1 en claro, 4.6:1 en oscuro). En alto contraste se apaga solo.
|
|
840
|
+
|
|
841
|
+
```html
|
|
842
|
+
<html data-theme="dark" data-glass="on" data-ambient="on"></html>
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
**El suelo de contraste no lo pone solo el alfa.** Sobre cristal el texto se lee
|
|
846
|
+
contra lo que haya debajo, que cambia al scrollear, así que el caso a cumplir es
|
|
847
|
+
el fondo más contrario posible. Taparlo con opacidad exige un alfa de 0.82, y a
|
|
848
|
+
0.82 esto ya no parece cristal; por eso el filtro **acota el fondo** antes de
|
|
849
|
+
componerlo:
|
|
850
|
+
|
|
851
|
+
| función | qué hace |
|
|
852
|
+
| ---------------- | -------------------------------------------------------------- |
|
|
853
|
+
| `saturate(180%)` | aviva el color de debajo al difuminarse (lo vistoso) |
|
|
854
|
+
| `contrast(0.6)` | comprime el rango del fondo hacia el gris medio |
|
|
855
|
+
| `brightness()` | lo empuja al tono de la capa: `1.5` en claro, `0.66` en oscuro |
|
|
856
|
+
| `blur(28px)` | lo difumina; cuanto más ancho, más vidrio esmerilado |
|
|
857
|
+
|
|
858
|
+
Bastan **0.44** de alfa y brillo 1.25 en claro,
|
|
859
|
+
**0.12** y brillo 0.56 en oscuro, 10 px de desenfoque en los dos. El precio es
|
|
860
|
+
el plano contrario puro debajo (negro en claro, blanco en oscuro): ahí el texto
|
|
861
|
+
secundario se queda en **4.1-4.2:1**, y el primario no baja de 4.5:1
|
|
862
|
+
nunca; contra el fondo del kit y contra un gris medio, ambos pasan de 6:1. El test de
|
|
863
|
+
contraste del paquete modela la cadena entera, no solo el alfa: tocar cualquiera de sus
|
|
864
|
+
funciones mueve el veredicto.
|
|
865
|
+
|
|
866
|
+
Degradación automática, sin JS y sin polyfill: con
|
|
867
|
+
`prefers-reduced-transparency: reduce` y donde no haya `backdrop-filter`, la
|
|
868
|
+
superficie opaca de siempre.
|
|
869
|
+
|
|
870
|
+
## Públicos e internos
|
|
871
|
+
|
|
872
|
+
`tokens.css` declara todo lo que el kit necesita, pero no todo es para las apps. Cada token de
|
|
873
|
+
la fuente dice si es **público** (el contrato: lo puedes usar y redefinir, y cambiarlo de
|
|
874
|
+
forma incompatible sigue el procedimiento de desuso) o **interno** (materia prima de la fuente
|
|
875
|
+
o pieza del kit: puede cambiar o desaparecer en cualquier versión).
|
|
876
|
+
|
|
877
|
+
- La capa **semántica** es pública entera: superficies, texto, bordes, primario, estados,
|
|
878
|
+
elevación, capas, espaciado y radios por papel, estilos de texto, familias, duraciones y
|
|
879
|
+
curvas.
|
|
880
|
+
- Las **primitivas** son internas: son la materia prima de la fuente (política 17). Los colores
|
|
881
|
+
de marca crudos (`blue`, `green`, `yellow`, `red`, `purple` y `blue-dark`) se usan por el
|
|
882
|
+
primario y los estados, que siguen al preset y al contraste alto, y `radius-full` por
|
|
883
|
+
`radius-pill`. Las escalas con nombre de paso (espacio, radios, tamaños, pesos e
|
|
884
|
+
interlineados) siguen públicas **en desuso** mientras convive su retirada (ver abajo).
|
|
885
|
+
- Las de **componente** son internas salvo las que el kit ofrece: capas flotantes, velo,
|
|
886
|
+
controles, objetivo táctil, anillo de foco, paleta de gráficas y fondo ambiental. Quedan
|
|
887
|
+
internos los pulgares de los interruptores y de las barras de desplazamiento.
|
|
888
|
+
|
|
889
|
+
La lista va en el paquete, generada por el build en el orden de la fuente:
|
|
890
|
+
|
|
891
|
+
```js
|
|
892
|
+
import publicos from '@netzi/tokens/public-tokens.json' with { type: 'json' };
|
|
893
|
+
|
|
894
|
+
// [{ name: '--netzi-primary-text', path: 'primary.text', layer: 'semantic' }, …]
|
|
895
|
+
const nombres = new Set(publicos.tokens.map((t) => t.name));
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
Cada entrada lleva su custom property, su ruta en la fuente (el nombre canónico), su capa
|
|
899
|
+
(`theme` si solo existe en un tema, como la lente del cristal) y, si está en desuso, el
|
|
900
|
+
aviso. La salida DTCG resuelta marca lo mismo en `$extensions["tech.netzi"].visibility`. Un
|
|
901
|
+
test del paquete falla si este README, el contrato de marca o el sustituto de un token en
|
|
902
|
+
desuso dependen de un nombre que no sea público.
|
|
903
|
+
|
|
904
|
+
## En desuso
|
|
905
|
+
|
|
906
|
+
Un token en desuso sigue saliendo en `tokens.css` con el mismo valor que su sustituto
|
|
907
|
+
(apunta a él), así que nada se rompe al actualizar; convive al menos una beta publicada y se
|
|
908
|
+
retira en un cambio incompatible propio, con su guía de migración (ADR 0012). Mientras
|
|
909
|
+
tanto lo marcan:
|
|
910
|
+
|
|
911
|
+
- `$deprecated` en la fuente DTCG y en la salida resuelta, con el sustituto y la versión;
|
|
912
|
+
- un comentario `/* deprecated: … */` encima de su declaración en `tokens.css`;
|
|
913
|
+
- el build, que los lista cada vez que corre;
|
|
914
|
+
- y esta tabla.
|
|
915
|
+
|
|
916
|
+
Cuando el paquete llega a la versión de retirada (o la pasa), el build **avisa sin fallar**:
|
|
917
|
+
«el token X debía retirarse en @netzi/tokens 0.1.0-beta.11; retíralo o mueve la versión». Es
|
|
918
|
+
el recordatorio de que el sinónimo ya ha convivido lo que tocaba; retirarlo es un cambio
|
|
919
|
+
incompatible propio, y el momento lo decide quien corta la release.
|
|
920
|
+
|
|
921
|
+
| En desuso | Usa | Deprecado en | Se retira, como pronto |
|
|
922
|
+
| ------------------- | ---------------------------- | ----------------------------- | ----------------------------- |
|
|
923
|
+
| `--netzi-surface-1` | `--netzi-surface` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
924
|
+
| `--netzi-text-1` | `--netzi-text` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
925
|
+
| `--netzi-radius-md` | `--netzi-radius-surface-sm` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
926
|
+
| `--netzi-shadow-sm` | `--netzi-elevation-raised` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
927
|
+
| `--netzi-shadow` | `--netzi-elevation-floating` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
928
|
+
| `--netzi-shadow-lg` | `--netzi-elevation-modal` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
929
|
+
|
|
930
|
+
El nombre canónico de cada papel es la raíz de su grupo (`surface`, `text`), que es el que usa
|
|
931
|
+
la política de diseño como nombre de ruta; el radio medio es el de una superficie pequeña. Para migrar basta con buscar y
|
|
932
|
+
sustituir: `var(--netzi-surface-1)` por `var(--netzi-surface)`, y lo mismo con los demás.
|
|
933
|
+
`@netzi/ui` y el playground ya solo usan los canónicos, y un guardián lo vigila.
|
|
934
|
+
|
|
935
|
+
Las sombras de antes (`shadow-sm`, `shadow`, `shadow-lg`) pasan a la escala de elevación
|
|
936
|
+
(#220): mismo valor en cada tema, con nombre de intención. Como el anillo de foco, se vuelven a
|
|
937
|
+
declarar en cada sección con su propio tema o contraste, así que siguen al tema igual que
|
|
938
|
+
antes. Lo mismo hacen los otros tres sinónimos.
|
|
939
|
+
|
|
940
|
+
### Escalas que pasan a internas
|
|
941
|
+
|
|
942
|
+
La política de diseño reserva las primitivas a la fuente: una app usa papeles. Las escalas con
|
|
943
|
+
nombre de paso eran públicas porque este README las enseñaba; desde `@netzi/tokens`
|
|
944
|
+
`0.1.0-beta.10` cada una tiene su papel y la escala está en desuso para las apps (#228). No
|
|
945
|
+
desaparecen: los papeles se apoyan en ellas, así que siguen en `tokens.css` con su valor y, al
|
|
946
|
+
retirarse, **pasan a internas** (salen de `public-tokens.json` y pueden cambiar sin aviso). Se
|
|
947
|
+
marcan igual que un sinónimo (`$deprecated`, el comentario en `tokens.css`, el build, esta
|
|
948
|
+
tabla), y el build avisa cuando el paquete llega a la versión:
|
|
949
|
+
|
|
950
|
+
| En desuso | Usa | Deprecado en | Pasa a interna, como pronto |
|
|
951
|
+
| ---------------------------- | ---------------------------------------------------------------------- | ----------------------------- | ----------------------------- |
|
|
952
|
+
| `--netzi-space-0` | `0` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
953
|
+
| `--netzi-space-1` | `--netzi-space-related` (cambia el valor: 2px no es un paso del ritmo) | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
954
|
+
| `--netzi-space-2` | `--netzi-space-related` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
955
|
+
| `--netzi-space-3` | `--netzi-space-group` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
956
|
+
| `--netzi-space-4` | `--netzi-space-stack` o `--netzi-space-inset-sm` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
957
|
+
| `--netzi-space-5` | `--netzi-space-inset` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
958
|
+
| `--netzi-space-6` | `--netzi-space-inset-lg` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
959
|
+
| `--netzi-space-7` | `--netzi-space-section` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
960
|
+
| `--netzi-space-8` | `--netzi-space-page` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
961
|
+
| `--netzi-radius-sm` | `--netzi-radius-control` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
962
|
+
| `--netzi-radius` | `--netzi-radius-surface-sm` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
963
|
+
| `--netzi-radius-lg` | `--netzi-radius-surface` | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
964
|
+
| `--netzi-font-size-{xs…3xl}` | el estilo de su papel (`caption` … `display`, arriba) | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
965
|
+
| `--netzi-font-weight-*` | el estilo de su papel (`medium` no tiene: `body` cambia el valor) | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
966
|
+
| `--netzi-line-height-*` | el estilo de su papel (`none` y `relaxed` no tienen: cambia el valor) | `@netzi/tokens 0.1.0-beta.10` | `@netzi/tokens 0.1.0-beta.11` |
|
|
967
|
+
|
|
968
|
+
Para migrar el espaciado y los radios basta con buscar y sustituir por el papel de cada uso
|
|
969
|
+
(`var(--netzi-space-3)` por `var(--netzi-space-group)`; el `4` tiene dos papeles, y el que
|
|
970
|
+
toca lo dice el sitio: aire entre grupos o relleno). Para el texto, cambia el trío
|
|
971
|
+
`font-size` + `font-weight` + `line-height` por el atajo del estilo:
|
|
972
|
+
|
|
973
|
+
```css
|
|
974
|
+
/* Antes */
|
|
975
|
+
.meta {
|
|
976
|
+
font-size: var(--netzi-font-size-xs);
|
|
977
|
+
font-weight: var(--netzi-font-weight-semibold);
|
|
978
|
+
line-height: var(--netzi-line-height-tight);
|
|
979
|
+
}
|
|
980
|
+
/* Después */
|
|
981
|
+
.meta {
|
|
982
|
+
font: var(--netzi-typography-caption);
|
|
983
|
+
letter-spacing: var(--netzi-typography-caption-letter-spacing);
|
|
984
|
+
}
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
Las duraciones, las curvas y las familias no cambian: ya tenían nombre de intención y pasan a
|
|
988
|
+
la capa semántica con el mismo nombre. `@netzi/ui` y el playground ya solo usan los papeles, y
|
|
989
|
+
el mismo guardián de los sinónimos lo vigila.
|
|
990
|
+
|
|
991
|
+
## Cómo se genera
|
|
992
|
+
|
|
993
|
+
`tokens.css` **no se escribe a mano**: sale de una fuente única en JSON con el formato del
|
|
994
|
+
W3C Design Tokens Community Group (DTCG 2025.10), con Style Dictionary. La fuente tiene
|
|
995
|
+
tres capas —primitivos (marca y escalas), semánticos (papeles: superficie, texto, borde,
|
|
996
|
+
primario, estado) y de componente (controles, capas flotantes, foco)— y un archivo por eje
|
|
997
|
+
de tema (modo, preset, contraste, cristal, fondo ambiental, densidad, puntero táctil y
|
|
998
|
+
movimiento reducido).
|
|
999
|
+
|
|
1000
|
+
La misma fuente sirve a quien no lee CSS: una salida DTCG ya resuelta, un archivo por
|
|
1001
|
+
combinación de modo, contraste y preset, pensada para librerías nativas (Compose,
|
|
1002
|
+
SwiftUI). En CSS, las referencias entre tokens se mantienen como `var()`, así que un
|
|
1003
|
+
override en tu scope sigue arrastrando a los tokens que se apoyan en el tuyo.
|
|
1004
|
+
|
|
1005
|
+
## Versionado
|
|
1006
|
+
|
|
1007
|
+
SemVer. Cambiar el **valor** de un token es `minor`/`patch`; **renombrar o quitar**
|
|
1008
|
+
un token es `major` (rompe la API pública).
|