@higinio/design-mcp 0.1.0

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.
@@ -0,0 +1,842 @@
1
+ # DESIGN.md — Sistema de Diseño "Celeste Clásica"
2
+
3
+ > Sistema de marca **light-first**, sereno y confiable, de **Higinio Sosa** para todos sus productos de software (web, móvil, dashboards, landing pages).
4
+ > Paleta base: `#a0c0d4` (celeste, decorativo) · `#7192ba` (primario) · `#31343e` (tinta)
5
+ >
6
+ > **Versión 2.0** — sincronizado con el proyecto [Celeste Clásica Design System](https://claude.ai/design/p/4569c4e4-6c41-41d5-955f-ac4c55b6ad89) de claude.ai/design.
7
+ > Entregables: `celeste-clasica.html` (style guide), `dist/celeste-clasica/` (tokens CSS · Tailwind v4 · DTCG JSON), skill `~/.claude/skills/celeste-clasica-design/`.
8
+
9
+ ---
10
+
11
+ ## 1. Identidad y principios
12
+
13
+ **Personalidad de marca:** profesional, calmada, precisa. Transmite confianza sin ser fría; la elegancia viene del orden y el aire, no de la decoración. Vibe: *una consultora seria pero cálida — "banca privada boutique", no "startup ruidosa"*.
14
+
15
+ 1. **Claridad primero** — El contenido siempre por encima de la decoración.
16
+ 2. **Calma visual** — Superficies claras, whitespace generoso, contraste solo donde importa.
17
+ 3. **Un acento a la vez** — El azul `#7192ba` guía la acción; nunca compite consigo mismo.
18
+ 4. **Consistencia sobre novedad** — Mismos tokens en todos los productos; cero valores mágicos.
19
+ 5. **Movimiento con propósito** — La animación explica (aparece, cambia, desaparece), nunca decora sola.
20
+
21
+ **Regla de oro de implementación:** los componentes consumen **solo tokens semánticos** (`--surface`, `--accent`, `--text-primary`, `--link`…), nunca hex directos. El theme switch es un cambio de atributo, no un refactor.
22
+
23
+ ---
24
+
25
+ ## 2. Voz y contenido
26
+
27
+ - **Idioma:** español (México/LatAm neutro) — "tú", nunca "usted".
28
+ - **Tono:** calmado, directo, profesional sin ser distante. Frases cortas. Cero jerga de marketing, **cero signos de exclamación**.
29
+ - **Voz:** segunda persona ("tu correo", "tus proyectos"), nunca primera persona del producto ("nosotros").
30
+ - **Casing:** sentence case en todo — botones, headings, labels. Nunca Title Case ni ALL CAPS, salvo labels uppercase muy puntuales (`+0.04em` tracking) para eyebrows/headers de tabla.
31
+ - **Botones:** 1–3 palabras, imperativos ("Guardar", "Eliminar cuenta").
32
+ - **Errores:** explican qué falló **y qué hacer**, no solo "Error".
33
+ - **Estados vacíos:** siempre invitan a una acción concreta — nunca un simple "No hay datos".
34
+ - **Emoji:** no se usan. El estado se comunica con color + icono + texto.
35
+ - **Fechas y números:** siempre con `Intl.DateTimeFormat` / `Intl.NumberFormat` (`es-MX`), nunca formatos hardcodeados ("12/07/2026" a mano se rompe al cambiar de locale).
36
+ - **`translate="no"`** en nombres de marca y tokens de código, para que la traducción automática no los corrompa.
37
+ - **Numerales para conteos:** "8 despliegues", no "ocho despliegues" — se escanean más rápido.
38
+
39
+ ---
40
+
41
+ ## 3. Paleta de colores
42
+
43
+ ### 3.1 Primario — Azul Acero (base `#7192ba`)
44
+
45
+ | Token | Hex | Uso |
46
+ |---|---|---|
47
+ | `primary-50` | `#f2f6fa` | Fondos hover sutiles, chips |
48
+ | `primary-100` | `#dfe9f2` | Badges, estados seleccionados suaves, chip de avatar |
49
+ | `primary-200` | `#c3d5e6` | Bordes activos, divisores destacados |
50
+ | `primary-300` | `#a0c0d4` | **Celeste de marca** — ilustraciones, gráficas, decorativo. **Nunca texto** |
51
+ | `primary-400` | `#87a8c7` | Iconos secundarios |
52
+ | `primary-500` | `#7192ba` | **Primario** — fondo de botones, bordes activos, focus, rellenos de gráficas |
53
+ | `primary-600` | `#5d7ba3` | Hover de botones primarios |
54
+ | `primary-700` | `#4d6588` | Pressed/active, **`--link`: texto de links y botones ghost** |
55
+ | `primary-800` | `#40536e` | Texto de marca con alto contraste |
56
+ | `primary-900` | `#31343e` | **Tinta de marca** — headings, texto principal |
57
+
58
+ ### 3.2 Neutrales — Gris Azulado (derivados de `#31343e`)
59
+
60
+ | Token | Hex | Uso |
61
+ |---|---|---|
62
+ | `neutral-0` | `#ffffff` | Superficie de tarjetas |
63
+ | `neutral-50` | `#f7f8fa` | Fondo general de la app |
64
+ | `neutral-100` | `#eef0f3` | Fondos alternos, filas zebra |
65
+ | `neutral-200` | `#dcdfe5` | Bordes y divisores por defecto |
66
+ | `neutral-300` | `#b8bdc7` | Bordes de inputs, iconos deshabilitados |
67
+ | `neutral-400` | `#8a8f9c` | Placeholder, texto deshabilitado |
68
+ | `neutral-500` | `#666b78` | Texto terciario, captions |
69
+ | `neutral-600` | `#4d5160` | Texto secundario |
70
+ | `neutral-700` | `#3d414e` | Párrafos largos |
71
+ | `neutral-800` | `#31343e` | Texto principal, headings |
72
+ | `neutral-900` | `#22242b` | Texto de máximo énfasis |
73
+
74
+ ### 3.3 Semánticos (ramp de 3 tonos por estado)
75
+
76
+ Cada estado tiene tres tokens: el sólido (`-500`), el fondo suave (`-100`) y **el tono de texto AA sobre el fondo suave (`-text`)**.
77
+
78
+ | Estado | Sólido `-500` | Fondo `-100` | Texto `-text` | Uso |
79
+ |---|---|---|---|---|
80
+ | `success` | `#3d9970` | `#e2f3ec` | `#2b6e50` | Confirmaciones, estados positivos |
81
+ | `warning` | `#c9922e` | `#faf1de` | `#8a651c` | Advertencias, pendientes |
82
+ | `danger` | `#c04d4d` | `#f9e7e7` | `#8f3232` | Errores, acciones destructivas |
83
+ | `info` | `#7192ba` | `#dfe9f2` | `#4d6588` | Informativos (reutiliza primario) |
84
+
85
+ ### 3.4 Capa de aliases semánticos
86
+
87
+ Los componentes **nunca** usan los primitivos directamente: consumen esta capa.
88
+
89
+ | Alias | Valor | Rol |
90
+ |---|---|---|
91
+ | `--bg` | `neutral-50` | Fondo de la app |
92
+ | `--surface` | `neutral-0` | Tarjetas, paneles |
93
+ | `--surface-alt` | `neutral-100` | Fondos alternos, zebra, tracks |
94
+ | `--border` | `neutral-200` | Borde por defecto |
95
+ | `--border-strong` | `neutral-300` | Bordes de inputs y botones secundarios |
96
+ | `--text-primary` | `neutral-800` | Texto principal |
97
+ | `--text-secondary` | `neutral-600` | Texto secundario |
98
+ | `--text-muted` | `neutral-500` | Terciario, captions |
99
+ | `--text-disabled` | `neutral-400` | Deshabilitado, placeholder |
100
+ | `--text-on-primary` | `#ffffff` | Texto sobre botón primario |
101
+ | `--text-on-primary-solid` | `primary-700` | Texto de peso normal sobre `primary-100` |
102
+ | `--accent` | `primary-500` | Rellenos de acción, bordes activos, focus |
103
+ | `--accent-hover` | `primary-600` | Hover del primario |
104
+ | `--accent-active` | `primary-700` | Pressed |
105
+ | `--accent-soft` | `primary-50` | Hover de ghost, fondos sutiles |
106
+ | `--accent-soft-strong` | `primary-100` | Seleccionados suaves |
107
+ | `--link` | `primary-700` | **Texto de links y botones ghost (AA ~6:1)** |
108
+ | `--ring` | `0 0 0 3px rgba(113,146,186,.25)` | Focus ring de inputs |
109
+
110
+ > ⚠️ **Regla AA de acento:** `primary-500` sobre blanco da ~3:1 — válido solo para **componentes UI** (rellenos, bordes, focus). Para **texto** (links, ghost) usar `--link` (`primary-700`, ~6:1). `primary-300` jamás como texto.
111
+
112
+ ### 3.5 Proporción de uso (regla 60-30-10)
113
+
114
+ - **60% — Neutrales claros** (`neutral-0/50/100`): fondos y superficies.
115
+ - **30% — Tinta y estructura** (`neutral-600→900`, bordes): texto, iconos, divisores.
116
+ - **10% — Primario y semánticos**: botones, links, estados, gráficas. **El acento pierde poder si supera el 10%.** (Excepción: una visualización de datos multiserie usa la paleta categórica validada de la skill `dataviz-marca`, exenta de este techo — son datos, no cromo decorativo.)
117
+
118
+ Reglas prácticas:
119
+ - **Un solo botón primario visible por pantalla**; el resto secundarios o ghost.
120
+ - **Fondos siempre planos** (`--bg`): sin gradientes, texturas ni patrones; sin full-bleed decorativo — la superficie de trabajo (tarjetas blancas) es la protagonista.
121
+ - Estado nunca solo con color: siempre color + icono + texto.
122
+
123
+ ---
124
+
125
+ ## 4. Tipografía
126
+
127
+ ### 4.1 Familias (roles estrictos)
128
+
129
+ | Rol | Fuente | Fallback | Uso |
130
+ |---|---|---|---|
131
+ | Display | **Fraunces** (600–700) | Georgia, serif | **Solo h1–h2**, cifras hero, títulos de marketing — nunca UI ni párrafos |
132
+ | Body/UI | **Public Sans** (400–600) | system-ui, sans-serif | Párrafos, botones, formularios, h3–h6 |
133
+ | Mono | **IBM Plex Mono** (400–500) | ui-monospace, monospace | Datos numéricos, código, IDs, montos en tablas |
134
+
135
+ Carga vía Google Fonts CDN (familias exactas del brief, no sustitutas — si llegan binarios licenciados, sustituir por `@font-face` con los mismos `font-family`, cero cambios downstream):
136
+
137
+ ```css
138
+ @import url("https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,600;0,9..144,700;1,9..144,600&family=Public+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=IBM+Plex+Mono:wght@400;500&display=swap");
139
+ ```
140
+
141
+ ### 4.2 Escala (ratio 1.25 — tercera mayor, base 16px, en `rem`)
142
+
143
+ | Token | Tamaño | Line-height (token) | Peso | Uso |
144
+ |---|---|---|---|---|
145
+ | `text-xs` | 12px | 1.5 `--leading-xs` | 400–500 | Captions, labels |
146
+ | `text-sm` | 14px | 1.5 `--leading-sm` | 400 | Texto secundario, tablas |
147
+ | `text-base` | 16px | 1.6 `--leading-base` | 400 | Párrafos, inputs |
148
+ | `text-lg` | 18px | 1.55 `--leading-lg` | 500 | Lead, subtítulos |
149
+ | `text-xl` | 20px | 1.4 `--leading-xl` | 600 | h4 |
150
+ | `text-2xl` | 25px | 1.3 `--leading-2xl` | 600 | h3 (Public Sans) |
151
+ | `text-3xl` | 31px | 1.25 `--leading-3xl` | 600 | h2 (Fraunces) |
152
+ | `text-4xl` | 39px | 1.15 `--leading-4xl` | 700 | h1 (Fraunces) |
153
+ | `text-5xl` | 49px | 1.1 `--leading-5xl` | 700 | Display/hero |
154
+
155
+ Pesos tokenizados: `--weight-regular 400` · `--weight-medium 500` · `--weight-semibold 600` · `--weight-bold 700`.
156
+
157
+ ### 4.3 Reglas
158
+
159
+ - Letter-spacing tokenizado: `--tracking-display: -0.02em` (display ≥31px), `--tracking-body: 0`, `--tracking-label: +0.04em` (labels uppercase).
160
+ - Longitud de línea: `--measure: 65ch` (60–75 caracteres).
161
+ - Números en tablas: `font-variant-numeric: tabular-nums` o IBM Plex Mono.
162
+ - Mínimo absoluto: 12px con line-height 1.5.
163
+
164
+ ### 4.4 Jerarquía y craft (Butterick)
165
+
166
+ - **Máx. 3 niveles de heading visualmente distintos por vista.** La escala define tamaños para h1–h4, pero en una misma pantalla no uses más de tres niveles claramente diferenciados; el exceso aplana la jerarquía.
167
+ - **Jerarquía por espacio y peso, no solo por tamaño.** Deja más espacio arriba de un heading que abajo (lo ata a su contenido). Distingue niveles con espacio y peso, no solo con puntos de tamaño.
168
+ - **Comillas y apóstrofos curvos** (« », " ", ' '), nunca rectos (`"`, `'`). Un solo espacio tras el punto seguido (nunca doble, herencia de máquina de escribir).
169
+ - **Interlineado generoso en cuerpo de texto: 150–160%** (ya son los tokens `--leading-base`/`--leading-sm`, §4.2); no bajes de 130% ni en texto denso (tablas, captions).
170
+ - **En dash (–) para rangos y series** ("10–20 páginas", "lun–vie"); **em dash (—) solo para incisos**, con moderación (la skill `anti-slop-diseno` marca >2 por bloque como cliché de IA).
171
+ - **Kerning activado** (`font-kerning: normal`); ligaduras por defecto.
172
+ - En JSX/TSX y en strings usa el carácter UTF-8 real (`'`, `"`), no escapes ni entidades (`’`, `’`), que no se decodifican igual en todos los contextos.
173
+
174
+ ---
175
+
176
+ ## 5. Espaciado
177
+
178
+ Escala base 4px — tokens `--space-1…24` (número = px/4): `4 · 8 · 12 · 16 · 20 · 24 · 32 · 40 · 48 · 64 · 80 · 96`
179
+
180
+ Aliases de uso (consumir estos, no números sueltos):
181
+
182
+ | Alias | Valor | Uso |
183
+ |---|---|---|
184
+ | `--card-padding` | 24px (`--space-6`) | Padding de tarjetas (`--card-padding-mobile: 16px`) |
185
+ | `--section-gap` | 64px (`--space-16`) | Separación entre secciones (48–64px) |
186
+ | `--form-gap` | 20px (`--space-5`) | Gap entre campos de formulario |
187
+ | `--button-padding-md` | 12px 20px | Botón md (`--button-padding-sm: 8px 14px`) |
188
+ | `--control-height-sm/md/lg` | 36 / 44 / 52px | Alturas de controles |
189
+ | `--gutter` | 24px | Gutter del grid (`--gutter-mobile: 16px`) |
190
+
191
+ ---
192
+
193
+ ## 6. Bordes, radios y focus
194
+
195
+ | Token | Valor | Uso |
196
+ |---|---|---|
197
+ | `radius-sm` | 6px | Inputs, chips, badges |
198
+ | `radius-md` | 10px | Botones, dropdowns, menús |
199
+ | `radius-lg` | 14px | Tarjetas, modales, accordion |
200
+ | `radius-xl` | 20px | Hero, imágenes destacadas |
201
+ | `radius-full` | 9999px | Avatares, pills, toggles |
202
+
203
+ - Borde por defecto: `var(--border-width, 1px) solid var(--border)`.
204
+ - **Focus ring global de teclado** (en `base.css`, especificidad cero vía `:where()` — funciona aunque no uses el reset):
205
+
206
+ ```css
207
+ :where(a, button, [role="button"], input, select, textarea, summary, [tabindex]):focus-visible {
208
+ outline: 2px solid var(--primary-500);
209
+ outline-offset: 2px;
210
+ border-radius: 2px;
211
+ }
212
+ ```
213
+
214
+ Solo lo dispara el teclado (`:focus-visible`), nunca el click de ratón.
215
+
216
+ **Transparencia y blur:** únicos usos permitidos — overlay de modal `rgba(49,52,62,.5)` y ring de focus. **No hay glassmorphism ni blur en Celeste Clásica**; las superficies son opacas y planas.
217
+
218
+ ---
219
+
220
+ ## 7. Elevación
221
+
222
+ Sombras frías teñidas con la tinta `#31343e`, nunca negro puro. Máximo **3 niveles visibles por pantalla**. **Sin resplandor de acento:** el `glow` es exclusivo de Nocturna Eléctrica.
223
+
224
+ ### Niveles
225
+
226
+ | Nivel | Superficie | Sombra | Uso |
227
+ |---|---|---|---|
228
+ | 0 | `--bg`, sin sombra, borde discontinuo | — | Reposo del lienzo |
229
+ | 1 | `--surface` + borde `--border` | `--shadow-xs` / `--shadow-sm` | Inputs, tarjetas en reposo |
230
+ | 2 | `--surface` + borde `--border` | `--shadow-md` | Hover, dropdowns |
231
+ | 3 | `--surface` + borde `--border-strong` | `--shadow-lg` / `--shadow-xl` | Modales, popovers, diálogos críticos |
232
+
233
+ ### Sombras
234
+
235
+ | Token | Valor | Uso |
236
+ |---|---|---|
237
+ | `shadow-xs` | `0 1px 2px rgba(49,52,62,.06)` | Inputs, indicador de SegmentedControl |
238
+ | `shadow-sm` | `0 1px 3px rgba(49,52,62,.08), 0 1px 2px rgba(49,52,62,.05)` | Tarjetas en reposo |
239
+ | `shadow-md` | `0 4px 8px rgba(49,52,62,.09), 0 2px 4px rgba(49,52,62,.05)` | Hover, dropdowns |
240
+ | `shadow-lg` | `0 12px 24px rgba(49,52,62,.12), 0 4px 8px rgba(49,52,62,.06)` | Modales, popovers, menús |
241
+ | `shadow-xl` | `0 24px 48px rgba(49,52,62,.16)` | Diálogos críticos |
242
+
243
+ ---
244
+
245
+ ## 8. Iconografía
246
+
247
+ - **Set:** [Lucide](https://lucide.dev) como sustituto *(flagged: no hay set propio de marca — si existe, reemplazar)*. Trazo 1.5–2px, esquinas redondeadas, geométrico — casa con la calma y precisión de la marca.
248
+ - **Componente `Icon`:** inserta el SVG **inline** para heredar `currentColor` (un componente sirve para cualquier tono) y sobrevivir impresión/exportación. Acepta nombres kebab-case de Lucide; los nombres clásicos pre-renombre (`home`, `alert-circle`, `check-circle`, `alert-triangle`, `more-horizontal`…) se traducen automáticamente.
249
+ - **Tamaños:** 14px inline con texto pequeño · 16–18px inputs/chevrons · 20px botones/toolbars · 40px empty states (en `primary-300` decorativo).
250
+ - **Alineación óptica:** los iconos asimétricos (play, flechas, chevrons) se centran a ojo, no geométricamente — el centro matemático engaña con formas que pesan hacia un lado. Ajustar 1–2px a mano hasta que se vea centrado.
251
+ - Sin emoji; sin Unicode-como-icono (excepto el `/` tipográfico de breadcrumbs).
252
+
253
+ ---
254
+
255
+ ## 9. Marca / Wordmark
256
+
257
+ No hay logo ni imágenes de marca — el wordmark es puramente tipográfico. Donde un mockup necesite logotipo, se renderiza **"Higinio"** (o el nombre del producto) en `--font-display` — nunca se dibuja un logo aproximado.
258
+
259
+ - **"Higinio"** en Fraunces peso 700 (`--weight-bold`), tracking `--tracking-display` (`-0.02em`), color `--text-primary` (tinta). Tamaño de referencia: 31px (`--text-3xl`).
260
+ - **Variante badge:** mismo texto/tamaño dentro de caja con fondo `--surface`, borde `1px --border`, radius `md` (10px), `shadow-sm`, padding `14px 22px`.
261
+ - El wordmark es texto, no un icono: Fraunces 600–700, nunca en UI ni párrafos.
262
+ - **Sentence case siempre:** "Higinio", nunca "HIGINIO" ni "higinio".
263
+ - Color `--text-primary`; `primary-300` jamás como texto, tampoco en el wordmark.
264
+ - No se genera ilustración ni isotipo para no inventar assets: usar placeholders y pedir los materiales reales.
265
+
266
+ ---
267
+
268
+ ## 10. Biblioteca de componentes
269
+
270
+ ### 10.1 Botones
271
+
272
+ Alturas: 36 (sm) / 44 (md) / 52px (lg). Radius `md`. Peso 600, `text-sm`/`text-base`; lg usa padding `14px 24px` y `text-lg`. Un solo primario visible por pantalla; ghost y outline usan `--link` (AA), no `primary-500`.
273
+
274
+ | Variante | Reposo | Hover | Active | Disabled |
275
+ |---|---|---|---|---|
276
+ | Primario | fondo `--accent`, texto `--text-on-primary` | `--accent-hover` + `shadow-sm` | `--accent-active`, sin sombra | fondo `neutral-200`, texto `--text-disabled` |
277
+ | Secundario | fondo `--surface`, borde `--border-strong`, texto `neutral-800` | fondo `neutral-50` | fondo `neutral-100` | opacidad 50% |
278
+ | Outline | fondo transparente, borde `--accent`, **texto `--link`** | fondo `--accent-soft`, borde `--accent-hover` | fondo `--accent-soft-strong` | opacidad 50% |
279
+ | Ghost | **texto `--link`** (`primary-700`) | fondo `--accent-soft` | fondo `--accent-soft-strong` | fondo `neutral-200`, texto `--text-disabled` |
280
+ | Destructivo | fondo `danger-500`, texto blanco | oscurecer 8% | oscurecer 14% | opacidad 50% |
281
+
282
+ - Con icono: gap 8px, icono 20px.
283
+ - Loading: spinner 16px reemplaza el texto (el botón conserva su ancho — fijar `min-width`).
284
+
285
+ ### 10.2 IconButton
286
+
287
+ Botón cuadrado solo-icono para toolbars, acciones de fila y chrome. **Requiere `label` accesible** (`aria-label` + `title`). Radius `md`.
288
+
289
+ - Tamaños: sm 32px (icono 16) · md 40px (icono 18) · lg 48px (icono 20).
290
+ - Variantes espejo de Button: `primary` (fondo `--accent`, icono blanco; hover `--accent-hover`), `secondary` (fondo `--surface`, borde `--border-strong`; hover `neutral-50`), `ghost` (transparente, icono `--text-secondary`; hover fondo `neutral-100`) — **default `ghost`** porque vive en chrome denso, no como CTA.
291
+ - Disabled: opacidad 0.5. Transición de fondo 150ms.
292
+
293
+ ### 10.3 Spinner
294
+
295
+ Export propio (extraído del estado loading de Button). SVG 16px default: círculo al 25% de opacidad + arco de ¼ sólido, stroke 2.5, rotación 0.8s linear infinita. Color default `--accent`; blanco dentro de botones primary/destructive.
296
+
297
+ ### 10.4 Input
298
+
299
+ - Fondo blanco, borde `1px --border-strong`, radius `sm`, altura `--control-height-md`, padding `0 14px`, `text-base`.
300
+ - **Label:** `text-sm`, peso 500, `neutral-700`, margen inferior 6px. Siempre visible (no usar placeholder como label).
301
+ - **Placeholder:** `--text-disabled`.
302
+ - **Hover:** borde `neutral-400`.
303
+ - **Focus:** borde `--accent` + `--ring`. Transición 150ms.
304
+ - **Error:** borde `danger-500` + ring rojo suave; mensaje `text-sm` `danger-500` con icono, 6px debajo.
305
+ - **Éxito (validación):** borde `success-500` + icono check dentro del input.
306
+ - **Disabled:** fondo `neutral-100`, texto `--text-disabled`, cursor `not-allowed`.
307
+ - **Texto de ayuda:** `text-sm` `--text-muted` debajo del input.
308
+ - Con icono leading: padding-left 40px, icono 18px `--text-disabled`.
309
+ - **`inputmode` según el dato** (`numeric`, `email`, `tel`, `decimal`) para el teclado correcto en móvil; `spellcheck="false"` en emails, códigos y usernames.
310
+ - **Nunca bloquear el pegado** (`onpaste`): rompe gestores de contraseñas y códigos de verificación.
311
+ - **Al enviar con errores,** mover el foco al primer campo inválido (no solo pintarlo de rojo).
312
+
313
+ ### 10.5 Textarea
314
+
315
+ Mismos estados que input. Min-height 96px, `resize: vertical`, padding 12px 14px. Contador de caracteres `text-xs` `--text-disabled` alineado a la derecha.
316
+
317
+ ### 10.6 Select
318
+
319
+ - Trigger: igual que input + chevron 16px `--text-muted` (rota 180° al abrir, 200ms).
320
+ - Menú: fondo blanco, radius `md`, `shadow-lg`, padding 6px, max-height 320px con scroll.
321
+ - Opción: padding `10px 12px`, radius `sm`, hover fondo `neutral-50`, seleccionada fondo `--accent-soft` + texto `primary-700` + check.
322
+ - Entrada del menú: fade + translateY(-4px→0), 180ms.
323
+
324
+ ### 10.7 SegmentedControl
325
+
326
+ Pill compacta de **2–4 opciones mutuamente excluyentes** (switches de vista, filtros rápidos). Para >4 opciones o labels largos, usar `Select`.
327
+
328
+ - Contenedor: fondo `--surface-alt`, radius `md`, padding 3px. Altura 40px (md) / 32px (sm); segmentos = altura − 6px, padding horizontal 16px.
329
+ - Indicador blanco (`--surface`) deslizante, radius `calc(radius-md - 3px)`, `shadow-xs`; **desliza left/width 200ms ease-out**.
330
+ - Segmento activo: `--text-primary` semibold; inactivo: `--text-secondary` medium; transición de color 150ms. Texto `text-sm`.
331
+
332
+ ### 10.8 Slider
333
+
334
+ Rango de valor único.
335
+
336
+ - Track: 6px de alto, radius full; porción rellena `--accent`, resto `--border` (gradiente vivo, sin elementos extra).
337
+ - Thumb: círculo blanco 18px, borde `2px --accent`, `shadow-sm`; **al presionar escala 1.15** (100ms).
338
+ - Label opcional `text-sm --text-secondary` arriba-izquierda; valor en `--font-mono --text-muted` arriba-derecha (`showValue`).
339
+ - Disabled: opacidad 0.5. Props: `min/max/step/value/onChange`.
340
+
341
+ ### 10.9 Checkbox y Radio
342
+
343
+ - Tamaño 20px, borde `2px --border-strong`, radius 4px (checkbox) / full (radio).
344
+ - Checked: fondo `--accent`, check blanco animado (stroke-dashoffset, 200ms).
345
+ - Hover: borde `primary-400`. Focus: ring como inputs.
346
+ - Label clicable, gap 10px, `text-base`. `RadioGroup` agrupa con `role="radiogroup"`.
347
+
348
+ ### 10.10 Switch
349
+
350
+ - Track 44×24px, radius full. Off: fondo `--border-strong`. On: fondo `--accent`.
351
+ - Thumb 20px blanco con `shadow-xs`; desliza 20px en 200ms `ease-spring`.
352
+
353
+ ### 10.11 Badge y Tag
354
+
355
+ - Badge: `text-xs` peso 500, padding `2px 8px`, radius full. Fondo suave semántico (`*-100`) + **texto `--*-text`** del mismo tono.
356
+ - Tag removible: incluye botón × de 14px, hover fondo más oscuro.
357
+
358
+ ### 10.12 Avatar
359
+
360
+ - Imagen de usuario con fallback automático a iniciales (máx. 2, del nombre) sobre chip `primary-100` con texto `primary-700` semibold. Si la imagen falla, cae a iniciales.
361
+ - Tamaños: sm 28px (fuente 12) · md 36px (14) · lg 48px (18) · xl 64px (24), o px directo. Radius full.
362
+ - Dot de presencia opcional: 28% del diámetro (mín. 8px), anillo `0 0 0 2px var(--surface)`: online `success-500` · busy `danger-500` · away `warning-500` · offline `neutral-300`.
363
+
364
+ ### 10.13 Divider
365
+
366
+ - Regla hairline `1px --border`. `role="separator"`.
367
+ - Horizontal (default) · horizontal con label centrado (`text-xs` uppercase, `--tracking-label`, `--text-muted`, medium; líneas flexibles a los lados, gap 12px — patrón "o continúa con") · vertical (se estira al alto del padre flex).
368
+
369
+ ### 10.14 Accordion
370
+
371
+ - Contenedor: borde `--border`, radius `lg`, fondo `--surface`, ítems separados por borde interno.
372
+ - Header: botón full-width, padding `16px 20px`, `text-base` medium `--text-primary`; chevron 18px `--text-muted` rota 180° (200ms). `aria-expanded`.
373
+ - Panel: altura animada con `grid-template-rows` 0fr→1fr 250ms ease-out (sin reflow); contenido `text-sm --text-secondary`, padding `0 20px 16px`.
374
+ - Un panel abierto a la vez salvo `allowMultiple`; `defaultOpen` por ids.
375
+
376
+ ### 10.15 Tooltip
377
+
378
+ Fondo `neutral-900`, texto blanco `text-xs`, padding `6px 10px`, radius `sm`, flecha 6px. Delay de aparición 400ms, fade + translateY(4px→0) en 150ms. Máximo 1 línea de ayuda; si necesita más, usar popover.
379
+
380
+ **Skip-delay:** solo el primer tooltip del grupo espera los 400ms; mientras haya un tooltip visible, los hermanos aparecen al instante y sin animación de entrada. El usuario ya está explorando — repetirle el delay y el fade en cada icono estorba.
381
+
382
+ ### 10.16 Alert
383
+
384
+ Banner de estado **inline y persistente** (vs Toast, transitorio): errores de formulario, avisos de plan.
385
+
386
+ - 4 tonos: fondo suave semántico (`*-100`), radius `md`, padding `14px 16px`.
387
+ - Icono semántico 20px en `*-500`: info→`info` · success→`circle-check` · warning→`triangle-alert` · danger→`circle-alert`.
388
+ - Título `text-sm` semibold y cuerpo `text-sm` en `--*-text` (cuerpo con opacidad 0.92).
389
+ - Botón cerrar opcional (× 16px en el color del tono) vía `onDismiss`. `role="status"`.
390
+
391
+ ### 10.17 Toast
392
+
393
+ - Posición: top-right (desktop), top-center (móvil). Ancho 360px. `ToastStack` los apila (gap 12px, máximo 3).
394
+ - Fondo blanco, borde izquierdo 3px semántico, `shadow-lg`, radius `md`, padding 14px 16px, icono semántico 20px.
395
+ - Entrada: slide-in desde la derecha + fade, 250ms. Salida: fade + translateX, 200ms.
396
+ - Auto-dismiss 5s (pausable con hover).
397
+ - **Accesibilidad:** el contenedor lleva `role="status"` + `aria-live="polite"`; los toasts de error usan `role="alert"` para anunciarse de inmediato.
398
+
399
+ ### 10.18 Progress y ProgressRing
400
+
401
+ Progreso **determinado** (para carga indeterminada usar `Skeleton`, no Progress).
402
+
403
+ - **Lineal:** track 8px radius full fondo `--surface-alt`; relleno en tono (`--accent`/`success-500`/`warning-500`/`danger-500`), width anima 250ms ease-out; label `text-sm --text-secondary` + porcentaje en `--font-mono --text-muted` opcionales. `role="progressbar"` + `aria-valuenow`.
404
+ - **ProgressRing:** SVG rotado -90°, track `--surface-alt`, arco en tono con `strokeLinecap: round`, `stroke-dashoffset` anima 250ms; tamaño default 56px, stroke 5; porcentaje centrado en mono (24% del tamaño).
405
+
406
+ ### 10.19 Modal
407
+
408
+ - Overlay: `rgba(49,52,62,.5)`, fade 200ms.
409
+ - Panel: fondo blanco, radius `lg`, `shadow-xl`, max-width 480px (confirm) / 640px (formulario), padding 24px.
410
+ - Entrada: fade + scale(0.96→1), 250ms. Salida: 180ms.
411
+ - Header con título `text-xl` + IconButton cerrar; footer con acciones alineadas a la derecha (primaria al extremo).
412
+ - Cerrar con Esc y click en overlay (excepto flujos destructivos).
413
+ - `overscroll-behavior: contain` en el panel scrolleable — al llegar al fondo, el scroll no se encadena a la página de atrás.
414
+
415
+ ### 10.20 Menu
416
+
417
+ Lista de acciones desplegable anclada a un trigger (acciones de fila, botones "más", menú de cuenta).
418
+
419
+ - Panel: fondo `--surface`, radius `md`, `shadow-lg`, padding 6px, min-width 190px, offset 6px bajo el trigger, `align` left/right.
420
+ - Entrada: fade + translateY(-4px→0) 160ms ease-out.
421
+ - Ítem: `role="menuitem"`, padding `9px 12px`, radius `sm`, `text-sm --text-primary`, icono opcional 16px `--text-muted`, gap 10px; hover fondo `neutral-50`.
422
+ - Ítem `danger`: texto e icono `danger-500`, hover fondo `danger-100`.
423
+ - Separador: 1px `--border`, margen `6px 4px`. Cierra con click fuera, Esc o al seleccionar.
424
+
425
+ ### 10.21 Tabla
426
+
427
+ - Header: fondo `neutral-50`, `text-xs` uppercase `--tracking-label`, `--text-muted`, borde inferior `--border`.
428
+ - Filas: altura 52px, borde inferior `neutral-100`, hover fondo `neutral-50`.
429
+ - Celdas numéricas alineadas a la derecha con `tabular-nums`.
430
+ - Fila seleccionada: fondo `--accent-soft`.
431
+
432
+ ### 10.22 Stat
433
+
434
+ Tile de KPI único (componer dentro de `Card` para métricas de dashboard).
435
+
436
+ - Label `text-sm --text-muted` con icono opcional 16px (gap 8px, margen inferior 8px).
437
+ - Valor grande en `--font-mono text-3xl` semibold `--text-primary` (tracking -0.01em, line-height 1).
438
+ - Delta opcional en baseline (gap 10px): `text-sm` semibold con flecha 14px (`trending-up`/`trending-down`/`minus`). Tono derivado del signo (− → danger, resto success) u override `deltaTone`; colores del delta en `--*-text` (no los `-500`).
439
+
440
+ ### 10.23 Pagination
441
+
442
+ Navegador de páginas 1-indexado, bajo una `Table`.
443
+
444
+ - Flechas prev/next (chevron 16px) + lista de páginas que **trunca con elipsis a partir de 7 páginas** (patrones `1…n`, ventana `page±1`).
445
+ - Celdas: min-width 36px, altura 36px, radius `sm`, `text-sm` medium `--text-secondary`; hover fondo `neutral-100`.
446
+ - Página activa: chip sólido `--accent` texto blanco semibold, `aria-current="page"`.
447
+ - Flechas deshabilitadas en extremos (`--text-disabled`); elipsis no clicable.
448
+
449
+ ### 10.24 Navegación
450
+
451
+ - **Navbar:** altura 64px, fondo blanco, borde inferior `--border`. Link activo: texto `primary-700` + indicador inferior 2px `--accent`.
452
+ - **Sidebar:** ancho 260px, fondo `neutral-50`. Ítem: radius `md`, padding `10px 12px`; activo: fondo `primary-100`, texto `primary-800`, icono `primary-600`.
453
+ - **Tabs:** borde inferior `--border`; tab activa con indicador 2px `--accent` que se desliza entre tabs (250ms).
454
+ - **Breadcrumbs:** `text-sm` `--text-muted`, separador `/` (tipográfico), último ítem `--text-primary`.
455
+
456
+ ### 10.25 Skeleton y EmptyState
457
+
458
+ - Skeleton: fondo `--surface-alt` con shimmer (gradiente que barre en 1.4s, loop). Radius igual al contenido que reemplaza.
459
+ - Estado vacío (`EmptyState`): icono 40px en `primary-300` decorativo, título `text-lg`, descripción `text-sm` `--text-muted`, CTA claro. Un estado vacío siempre invita a actuar.
460
+
461
+ ### 10.26 Card, Link y Code
462
+
463
+ - **Card:** fondo `--surface`, borde `1px --border`, radius `lg`, padding 24px (16px en móvil), `shadow-sm`.
464
+ - **Card interactiva:** cursor pointer; hover eleva — `translateY(-2px)` + `shadow-md`; transición de transform/sombra 200ms (`--duration-base`) ease-out. Sin glow: en light-first la elevación es sombra + desplazamiento, nunca brillo.
465
+ - **Link:** color `--link` (primary-700, AA ≈6:1); subrayado animado 1px `currentColor` que crece de 0%→100% del ancho desde la izquierda (`background-size`, 200ms ease-out). Externo: `target="_blank" rel="noopener noreferrer"` + icono flecha 12px, gap 4px.
466
+ - **Code (inline):** IBM Plex Mono `0.9em`, texto `--text-primary`, fondo `--surface-alt`, borde `1px --border`, radius `sm`, padding `1px 6px`.
467
+
468
+ ---
469
+
470
+ ## 11. Animaciones y motion
471
+
472
+ ### 11.0 Cuándo animar (frequency gate)
473
+
474
+ La frecuencia de una acción decide su intensidad de movimiento:
475
+
476
+ | Frecuencia de la acción | Motion |
477
+ |---|---|
478
+ | Rara / memorable (primer uso, éxito) | Puede deleitar, con presencia contenida |
479
+ | Ocasional (abrir un panel, un modal) | Transición clara y sobria |
480
+ | Diaria / repetida (navegar, alternar) | Sutil y rápida, casi imperceptible |
481
+ | Muy frecuente (100+/día) o de teclado | Sin animación; el movimiento estorbaría |
482
+
483
+ Antes de animar algo, pregúntate: ¿de dónde vino, qué cambió, a dónde se fue? Si no hay respuesta, no lo animes.
484
+
485
+ ### 11.1 Tokens
486
+
487
+ ```css
488
+ :root {
489
+ --duration-instant: 100ms; /* feedback táctil: press, toggle */
490
+ --duration-fast: 150ms; /* hover, focus, color */
491
+ --duration-base: 200ms; /* dropdowns, tooltips, switches */
492
+ --duration-slow: 250ms; /* modales, toasts */
493
+ --duration-page: 320ms; /* transiciones de página/vista */
494
+
495
+ --ease-out: cubic-bezier(0.2, 0, 0, 1); /* entradas (default) */
496
+ --ease-in: cubic-bezier(0.4, 0, 1, 1); /* salidas */
497
+ --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* moverse de A a B en pantalla */
498
+ --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1); /* toggles, checks (rebote sutil) */
499
+ }
500
+ ```
501
+
502
+ ### 11.2 Principios de movimiento
503
+
504
+ 1. **Entra rápido, sale más rápido** — La salida siempre es ~20% más corta que la entrada.
505
+ 2. **Solo `transform` y `opacity`** — Nunca animar `width/height/top/left` (evita reflow). Excepciones controladas: width de Progress y `grid-template-rows` del Accordion.
506
+ 3. **Distancias cortas** — Desplazamientos de entrada de 4–8px; nada "vuela" por la pantalla.
507
+ 4. **Jerarquía temporal** — En listas, stagger de 30ms por ítem (máximo 6 ítems con stagger).
508
+ 5. **La animación explica** — Todo movimiento responde a: ¿de dónde vino, qué cambió, a dónde se fue?
509
+ 6. **Origen consciente** — Popovers, menús y dropdowns aparecen desde el `transform-origin` de su disparador; los modales, desde el centro. Nunca un origen arbitrario.
510
+ 7. **Interrumpible** — Toda animación puede invertirse desde su valor visible actual, sin saltar al destino y reanimar. Clave en hover y gestos. En la práctica: para UI que se dispara con frecuencia (toasts, dropdowns, hover) usa `transition` — con `@starting-style` para la entrada al montar —, no `@keyframes`: un keyframe no puede invertirse a mitad de camino. Los `@keyframes` quedan para loops de carga (spinner, shimmer).
511
+ 8. **La salida es más discreta que la entrada** — Además de ~20% más corta, atenúa: menor desplazamiento y menor opacidad. Entrar se nota; salir se difumina.
512
+ 9. **El easing según el trabajo** — Entrar → `--ease-out`; salir → `--ease-in`; un elemento que se mueve de un punto a otro de la pantalla (indicador de tabs o segmented, reorder) → `--ease-in-out`, que acelera y frena en espejo; hover y cambios de color → `--ease-out` corto; loops de carga → `linear`.
513
+ 10. **`will-change` solo mientras anima** — Se pone justo antes de animar y se quita al terminar; nunca permanente en el CSS. Presupuesto de capas GPU: menos de 10 elementos promovidos a la vez.
514
+
515
+ ### 11.3 Micro-interacciones por componente
516
+
517
+ | Elemento | Trigger | Animación |
518
+ |---|---|---|
519
+ | Botón | hover | color de fondo 150ms + `shadow-sm` |
520
+ | Botón | press | `scale(0.98)` 100ms |
521
+ | Link | hover | color 150ms + subrayado que crece de 0 a 100% (200ms) |
522
+ | Tarjeta interactiva | hover | `translateY(-2px)` + `shadow-md`, 200ms |
523
+ | Input | focus | ring que aparece con fade 150ms |
524
+ | Checkbox | check | trazo del check dibujándose 200ms `ease-spring` |
525
+ | Toggle | cambio | thumb desliza 200ms `ease-spring` |
526
+ | SegmentedControl | cambio | indicador desliza left/width 200ms `--ease-in-out` |
527
+ | Slider | press | thumb escala 1.15, 100ms |
528
+ | Acordeón | abrir | `grid-template-rows` (0fr→1fr) 250ms + fade del contenido |
529
+ | Dropdown / Menu | abrir | fade + translateY(-4px→0) 160–180ms |
530
+ | Modal | abrir | overlay fade 200ms; panel fade + scale(0.96→1) 250ms |
531
+ | Toast | entrar | slide-in + fade 250ms `ease-out` |
532
+ | Icon swap (p. ej. copiar→check) | cambio | cross-fade: opacity + scale(0.9→1), 150ms; nunca blur |
533
+ | Progress | cambio | width / stroke-dashoffset 250ms `ease-out` |
534
+ | Página/vista | cambio | fade + translateY(8px→0) 320ms |
535
+
536
+ ### 11.4 Loading
537
+
538
+ - Spinner: ver §10.3.
539
+ - Progress: ver §10.18.
540
+ - Skeleton shimmer: 1.4s loop (ver §10.25).
541
+ - Si la carga dura <300ms, **no mostrar spinner** (evita parpadeo).
542
+
543
+ ### 11.5 Accesibilidad del movimiento
544
+
545
+ ```css
546
+ @media (prefers-reduced-motion: reduce) {
547
+ *, *::before, *::after {
548
+ animation-duration: 0.01ms !important;
549
+ animation-iteration-count: 1 !important;
550
+ transition-duration: 0.01ms !important;
551
+ scroll-behavior: auto !important;
552
+ }
553
+ }
554
+ ```
555
+
556
+ Este reset con `*` es el **piso de seguridad**: garantiza que ningún motion sobreviva si el usuario pide reducirlo, incluso en componentes que no se revisaron a mano. Donde el movimiento comunica información real (progreso, un check, un conteo) y eliminarlo del todo perjudicaría la comprensión, un componente puede sobrescribir el reset con un selector más específico y una duración corta (p. ej. `transition-duration: 80ms`) en vez de colapsar a 0.01ms — hoy ningún componente del sistema ejerce esta excepción; todos usan el piso global.
557
+
558
+ **Gating de hover:** los efectos de hover que mueven o elevan (transform, box-shadow) van dentro de `@media (hover: hover) and (pointer: fine)` — en touch no hay hover real y el tap dispara hovers falsos que se quedan pegados hasta el siguiente toque. Los hover de solo color/fondo pueden quedar fuera: un cambio de color pegado no estorba.
559
+
560
+ ```css
561
+ @media (hover: hover) and (pointer: fine) {
562
+ .card--interactive:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
563
+ }
564
+ ```
565
+
566
+ ### 11.6 Anti-patrones de motion
567
+
568
+ Tells de motion genérico (a menudo generado por IA). La tool `validate_motion` del MCP los detecta con estos umbrales:
569
+
570
+ | Anti-patrón | Umbral / detección | En su lugar |
571
+ |---|---|---|
572
+ | `transition: all` | cualquier uso | Enumera: `transition: transform 200ms, opacity 200ms` |
573
+ | `ease-in` en entradas | `ease-in` en una entrada de UI | `--ease-out`; reserva `ease-in` para salidas |
574
+ | Animar propiedades de layout | `width/height/top/left/right/bottom/margin/padding` | `transform`/`opacity`. Excepciones: `width` de Progress, `grid-template-rows` del Accordion |
575
+ | Blur como recurso | `filter`/`backdrop-filter: blur()` | Elimínalo; fondos planos, sombras teñidas de tinta |
576
+ | Duración excesiva | > el techo del sistema (`--duration-page`) fuera de un loop de carga | Recorta a ≤ `--duration-page` |
577
+ | `scale(0)` | arrancar desde escala 0 | `scale(0.9–0.98)` + `opacity: 0` |
578
+ | Rebote en utilitarios | spring/overshoot en dropdown, menú, modal, tooltip, popover, select | `--ease-out`; el spring sutil se reserva a check y toggle |
579
+ | Overshoot excesivo | curva con y1 > 1.6 | Rebote apenas perceptible (y1 ≤ ~1.4) |
580
+ | blur-everywhere | ≥3 elementos con blur | Rediseña sin blur |
581
+ | hover-scale-en-todo | ≥3 elementos que escalan en hover | Reserva el scale para 1–2 acciones clave |
582
+ | stagger-spam | ≥2 listas con stagger en una vista | Una lista con stagger por vista (máx. 6 ítems) |
583
+ | uniform-fade | ≥4 entradas idénticas | Varía duración/origen o justifícalo |
584
+ | pulsing | indicadores que laten en loop (pulse/ping/glow) | Estado estático claro; spinner solo en carga real |
585
+ | motion-on-mount-for-static | contenido estático que se anima al montar sin razón — no lo detecta la tool; juicio manual | Nada: aplica el frequency gate (§11.0) — si no responde de dónde/qué/adónde, no se anima |
586
+
587
+ ### 11.7 Motion gap
588
+
589
+ Un "motion gap" es UI que aparece o desaparece sin transición y debería tenerla. Zonas donde suele faltar:
590
+
591
+ - Render condicional (`{cond && <X/>}`) y ternarios que cambian de vista.
592
+ - Tabs, secciones colapsables y acordeones.
593
+ - Campos condicionales de formularios.
594
+ - Toasts y notificaciones.
595
+ - Estados de carga y de error.
596
+ - Paneles de settings / inspector.
597
+
598
+ Regla: un gap en UI primaria (el flujo principal) es crítico; en UI secundaria, medio. Toda entrada/salida condicional necesita su transición de `opacity`/`transform`, con su contraparte de salida más discreta.
599
+
600
+ ---
601
+
602
+ ## 12. Accesibilidad y contraste
603
+
604
+ - `neutral-800` sobre blanco: **≈ 10.4:1** ✅ AAA.
605
+ - Blanco sobre `primary-500`: **≈ 3.4:1** ⚠️ solo texto ≥18px/bold o componentes UI; para texto normal usar fondo `primary-700`.
606
+ - `--link` (`primary-700`) sobre blanco: **≈ 6:1** ✅ AA — por eso links y ghost usan `--link`, no `primary-500`.
607
+ - `primary-300` (#a0c0d4) **nunca como texto sobre blanco** — solo decorativo.
608
+ - Tonos `--*-text` garantizan AA sobre los fondos suaves `*-100`.
609
+ - Mínimos WCAG AA: 4.5:1 texto normal, 3:1 texto grande y UI.
610
+ - Focus visible siempre (ring global `:focus-visible`, §6); estado nunca comunicado solo con color (icono + texto).
611
+ - Touch targets mínimos: 44×44px.
612
+ - IconButton siempre con `aria-label`; Progress con `role="progressbar"`; Alert con `role="status"`; Divider con `role="separator"`; Pagination con `aria-current="page"`.
613
+
614
+ ---
615
+
616
+ ## 13. Tokens CSS consolidados
617
+
618
+ ```css
619
+ :root {
620
+ /* == Primitivos: primario == */
621
+ --primary-50: #f2f6fa; --primary-100: #dfe9f2; --primary-200: #c3d5e6;
622
+ --primary-300: #a0c0d4; --primary-400: #87a8c7; --primary-500: #7192ba;
623
+ --primary-600: #5d7ba3; --primary-700: #4d6588; --primary-800: #40536e;
624
+ --primary-900: #31343e;
625
+
626
+ /* == Primitivos: neutrales == */
627
+ --neutral-0: #ffffff; --neutral-50: #f7f8fa; --neutral-100: #eef0f3;
628
+ --neutral-200: #dcdfe5; --neutral-300: #b8bdc7; --neutral-400: #8a8f9c;
629
+ --neutral-500: #666b78; --neutral-600: #4d5160; --neutral-700: #3d414e;
630
+ --neutral-800: #31343e; --neutral-900: #22242b;
631
+
632
+ /* == Primitivos: semánticos (ramp de 3) == */
633
+ --success-500: #3d9970; --success-100: #e2f3ec; --success-text: #2b6e50;
634
+ --warning-500: #c9922e; --warning-100: #faf1de; --warning-text: #8a651c;
635
+ --danger-500: #c04d4d; --danger-100: #f9e7e7; --danger-text: #8f3232;
636
+ --info-500: #7192ba; --info-100: #dfe9f2; --info-text: #4d6588;
637
+
638
+ /* == Aliases semánticos (los componentes consumen SOLO esto) == */
639
+ --bg: var(--neutral-50);
640
+ --surface: var(--neutral-0);
641
+ --surface-alt: var(--neutral-100);
642
+ --border: var(--neutral-200);
643
+ --border-strong: var(--neutral-300);
644
+ --text-primary: var(--neutral-800);
645
+ --text-secondary: var(--neutral-600);
646
+ --text-muted: var(--neutral-500);
647
+ --text-disabled: var(--neutral-400);
648
+ --text-on-primary: #ffffff;
649
+ --text-on-primary-solid: var(--primary-700);
650
+ --accent: var(--primary-500);
651
+ --accent-hover: var(--primary-600);
652
+ --accent-active: var(--primary-700);
653
+ --accent-soft: var(--primary-50);
654
+ --accent-soft-strong: var(--primary-100);
655
+ --link: var(--primary-700);
656
+
657
+ /* == Tipografía == */
658
+ --font-display: "Fraunces", Georgia, serif;
659
+ --font-body: "Public Sans", system-ui, -apple-system, sans-serif;
660
+ --font-mono: "IBM Plex Mono", ui-monospace, monospace;
661
+ --text-xs: .75rem; --text-sm: .875rem; --text-base: 1rem; --text-lg: 1.125rem;
662
+ --text-xl: 1.25rem; --text-2xl: 1.5625rem; --text-3xl: 1.9375rem;
663
+ --text-4xl: 2.4375rem; --text-5xl: 3.0625rem;
664
+ --leading-xs: 1.5; --leading-sm: 1.5; --leading-base: 1.6; --leading-lg: 1.55;
665
+ --leading-xl: 1.4; --leading-2xl: 1.3; --leading-3xl: 1.25;
666
+ --leading-4xl: 1.15; --leading-5xl: 1.1;
667
+ --tracking-display: -0.02em; --tracking-body: 0; --tracking-label: 0.04em;
668
+ --weight-regular: 400; --weight-medium: 500; --weight-semibold: 600; --weight-bold: 700;
669
+ --measure: 65ch;
670
+
671
+ /* == Espaciado == */
672
+ --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
673
+ --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px;
674
+ --space-12: 48px; --space-16: 64px; --space-20: 80px; --space-24: 96px;
675
+ --card-padding: var(--space-6); --card-padding-mobile: var(--space-4);
676
+ --section-gap: var(--space-16); --form-gap: var(--space-5);
677
+ --button-padding-md: 12px 20px; --button-padding-sm: 8px 14px;
678
+ --control-height-sm: 36px; --control-height-md: 44px; --control-height-lg: 52px;
679
+ --gutter: 24px; --gutter-mobile: 16px;
680
+
681
+ /* == Radios, bordes y sombras == */
682
+ --radius-sm: 6px; --radius-md: 10px; --radius-lg: 14px;
683
+ --radius-xl: 20px; --radius-full: 9999px;
684
+ --border-width: 1px;
685
+ --shadow-xs: 0 1px 2px rgba(49,52,62,.06);
686
+ --shadow-sm: 0 1px 3px rgba(49,52,62,.08), 0 1px 2px rgba(49,52,62,.05);
687
+ --shadow-md: 0 4px 8px rgba(49,52,62,.09), 0 2px 4px rgba(49,52,62,.05);
688
+ --shadow-lg: 0 12px 24px rgba(49,52,62,.12), 0 4px 8px rgba(49,52,62,.06);
689
+ --shadow-xl: 0 24px 48px rgba(49,52,62,.16);
690
+
691
+ /* == Focus == */
692
+ --ring: 0 0 0 3px rgba(113,146,186,.25);
693
+ --ring-color: rgba(113,146,186,.25);
694
+
695
+ /* == Motion == */
696
+ --duration-instant: 100ms; --duration-fast: 150ms; --duration-base: 200ms;
697
+ --duration-slow: 250ms; --duration-page: 320ms;
698
+ --ease-out: cubic-bezier(0.2, 0, 0, 1);
699
+ --ease-in: cubic-bezier(0.4, 0, 1, 1);
700
+ --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
701
+ --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
702
+
703
+ color-scheme: light;
704
+ }
705
+ ```
706
+
707
+ Estilos base (extracto de `base.css` — clase `.celeste-reset` aplicable a `body` o contenedor): fondo `--bg`, color `--text-primary`, `--font-body`/`--text-base`/`--leading-base`, antialiased; **h1–h2 en display bold** con tracking display; **h3–h6 en body semibold**; `p { max-width: var(--measure) }`; `table { font-variant-numeric: tabular-nums }`; + focus ring global (§6).
708
+
709
+ ---
710
+
711
+ ## 14. Breakpoints y grid
712
+
713
+ | Token | Ancho | Contenedor |
714
+ |---|---|---|
715
+ | `sm` | 640px | 100% - 32px |
716
+ | `md` | 768px | 720px |
717
+ | `lg` | 1024px | 960px |
718
+ | `xl` | 1280px | 1200px |
719
+
720
+ Grid de 12 columnas, gutter 24px (16px móvil). Mobile-first. Navbar fija de 64px; sidebar de 260px cuando aplica.
721
+
722
+ **Touch y viewport:**
723
+
724
+ - `touch-action: manipulation` en botones y links — elimina el delay de double-tap en móvil.
725
+ - Alturas de viewport con `100dvh`, no `100vh` (la barra de iOS cambia el alto visible).
726
+ - **Receta de overflow:** `min-width: 0` en los hijos flex que deben truncar; texto largo con `text-overflow: ellipsis` o `line-clamp`, siempre con `title` o tooltip que exponga el contenido completo.
727
+
728
+ ---
729
+
730
+ ## 15. Modo oscuro (variante)
731
+
732
+ El sistema es **light-first**, pero todo producto debe soportar dark mode con los mismos tokens semánticos. La marca no cambia: cambia el escenario.
733
+
734
+ ### 15.1 Filosofía de la inversión
735
+
736
+ - Los fondos se derivan de la tinta `#31343e` (nunca negro puro ni gris genérico): el dark hereda el matiz azulado de la marca.
737
+ - El primario se **aclara un paso**: sobre fondo oscuro, `#7192ba` pierde fuerza, así que el rol de acento pasa a `primary-400 (#87a8c7)` y el celeste `#a0c0d4` gana protagonismo en links y datos.
738
+ - Las sombras pierden peso; la elevación se apoya en superficies más claras + bordes (mismo patrón que cualquier dark bien hecho).
739
+
740
+ ### 15.2 Mapeo de tokens
741
+
742
+ | Token semántico | Light | Dark |
743
+ |---|---|---|
744
+ | `--bg` | `#f7f8fa` | `#16181e` |
745
+ | `--surface` | `#ffffff` | `#1d2028` |
746
+ | `--surface-2` (modales, dropdowns) | `#ffffff` | `#252935` |
747
+ | `--border` | `#dcdfe5` | `#3a3f4d` |
748
+ | `--border-strong` | `#b8bdc7` | `#4a5060` |
749
+ | `--text-primary` | `#31343e` | `#e6e9ee` |
750
+ | `--text-secondary` | `#4d5160` | `#b9bfcb` |
751
+ | `--text-muted` | `#666b78` | `#8b91a0` |
752
+ | `--accent` (botones, focus) | `#7192ba` | `#87a8c7` |
753
+ | `--accent-hover` | `#5d7ba3` | `#a0c0d4` |
754
+ | `--accent-active` | `#4d6588` | `#7192ba` |
755
+ | `--text-on-primary` | `#ffffff` | `#16181e` |
756
+ | `--link` | `#4d6588` | `#a0c0d4` |
757
+ | `--success` | `#3d9970` | `#5cbf93` |
758
+ | `--warning` | `#c9922e` | `#e0ad4d` |
759
+ | `--danger` | `#c04d4d` | `#e07777` |
760
+ | `--ring` | `rgba(113,146,186,.25)` | `rgba(160,192,212,.3)` |
761
+
762
+ Fondos suaves semánticos en dark: `success #12291f` · `warning #2b2210` · `danger #2e1515` · `info #1a2430`.
763
+
764
+ ### 15.3 Elevación en dark
765
+
766
+ | Nivel | Superficie | Sombra |
767
+ |---|---|---|
768
+ | 0 | `#16181e` | — |
769
+ | 1 | `#1d2028` | `0 1px 2px rgba(0,0,0,.35)` |
770
+ | 2 | `#252935` | `0 4px 12px rgba(0,0,0,.45)` |
771
+ | 3 | `#252935` + borde `#4a5060` | `0 12px 32px rgba(0,0,0,.55)` |
772
+
773
+ ### 15.4 Ajustes de componentes en dark
774
+
775
+ - **Botón primario:** fondo `#87a8c7`, texto `#16181e` (contraste ≈ 7:1 ✅). Hover `#a0c0d4`.
776
+ - **Inputs:** fondo `#252935`, borde `#3a3f4d`, placeholder `#8b91a0`; focus borde `#a0c0d4` + ring.
777
+ - **Tabla:** header `#1d2028`, hover de fila `#252935`, seleccionada `rgba(160,192,212,.1)`.
778
+ - **Sidebar activo:** fondo `rgba(113,146,186,.15)`, texto `#e6e9ee`, icono `#a0c0d4`.
779
+ - **Tooltip:** se invierte — fondo `#e6e9ee`, texto `#31343e`.
780
+ - **Overlay de modal:** `rgba(0,0,0,.6)`.
781
+ - **Skeleton:** base `#252935`, shimmer hacia `#2e3340`.
782
+ - Texto claro sobre oscuro "engorda": bajar un peso donde sea posible (500→400).
783
+
784
+ ### 15.5 Contraste en dark
785
+
786
+ - `#e6e9ee` sobre `#16181e`: ≈ 13:1 ✅ AAA.
787
+ - `#a0c0d4` sobre `#16181e`: ≈ 8:1 ✅ — en dark el celeste **sí** puede ser texto/link (al revés que en light).
788
+ - `#7192ba` sobre `#16181e`: ≈ 4.6:1 ✅ AA — usable en texto normal.
789
+
790
+ ### 15.6 Implementación
791
+
792
+ ```css
793
+ [data-theme="dark"] {
794
+ --bg: #16181e;
795
+ --surface: #1d2028;
796
+ --surface-alt: #252935;
797
+ --border: #3a3f4d;
798
+ --border-strong: #4a5060;
799
+ --text-primary: #e6e9ee;
800
+ --text-secondary: #b9bfcb;
801
+ --text-muted: #8b91a0;
802
+ --text-disabled: #6f7585;
803
+ --accent: #87a8c7;
804
+ --accent-hover: #a0c0d4;
805
+ --accent-active: #7192ba;
806
+ --text-on-primary: #16181e;
807
+ --link: #a0c0d4;
808
+ --success-500: #5cbf93; --success-100: #12291f; --success-text: #7fd4ae;
809
+ --warning-500: #e0ad4d; --warning-100: #2b2210; --warning-text: #ecc57e;
810
+ --danger-500: #e07777; --danger-100: #2e1515; --danger-text: #eb9e9e;
811
+ --info-500: #87a8c7; --info-100: #1a2430; --info-text: #a0c0d4;
812
+ --ring: 0 0 0 3px rgba(160,192,212,.3);
813
+ --shadow-sm: 0 1px 2px rgba(0,0,0,.35);
814
+ --shadow-md: 0 4px 12px rgba(0,0,0,.45);
815
+ --shadow-lg: 0 12px 32px rgba(0,0,0,.55);
816
+ color-scheme: dark;
817
+ }
818
+ ```
819
+
820
+ ---
821
+
822
+ ## 16. Distribución y consumo
823
+
824
+ El sistema se distribuye en varios formatos para poder usarlo desde cualquier stack:
825
+
826
+ | Entregable | Ubicación | Uso |
827
+ |---|---|---|
828
+ | **Este documento** | `DESIGN-celeste-clasica.md` | Fuente de verdad legible |
829
+ | **Style guide interactivo** | `celeste-clasica.html` | Referencia visual siempre disponible (abrir en navegador) |
830
+ | **Tokens CSS** | `dist/celeste-clasica/tokens.css` | Drop-in para cualquier proyecto (vanilla, React, Angular, Vue) |
831
+ | **Tailwind v4** | `dist/celeste-clasica/tailwind.css` | Bloque `@theme` — genera utilidades `bg-primary-500`, `text-neutral-800`, etc. |
832
+ | **DTCG JSON** | `dist/celeste-clasica/tokens.json` | Formato estándar W3C DTCG 2025.10 — interoperable con Figma/Tokens Studio/Style Dictionary |
833
+ | **Skill de Claude** | `~/.claude/skills/celeste-clasica-design/` | Invocable desde cualquier sesión de Claude Code |
834
+ | **Proyecto claude.ai/design** | [Celeste Clásica Design System](https://claude.ai/design/p/4569c4e4-6c41-41d5-955f-ac4c55b6ad89) | Componentes React + specimens + UI kit dashboard |
835
+
836
+ **Cómo consumirlo en un proyecto nuevo:**
837
+
838
+ 1. Copiar `dist/celeste-clasica/tokens.css` e importarlo antes que cualquier estilo propio, **o** pegar el bloque `@theme` en el CSS de Tailwind v4.
839
+ 2. Aplicar la clase `.celeste-reset` al `body` (o usar los estilos base equivalentes).
840
+ 3. Los componentes consumen solo aliases semánticos (§3.4) — nunca hex.
841
+ 4. Para light/dark: alternar `data-theme` en `<html>`; los tokens hacen el resto.
842
+ 5. En el `<head>`: `<meta name="theme-color" content="#f7f8fa">` (el `--bg` claro); con el tema oscuro activo, actualizarlo a `#16181e` para que la barra del navegador acompañe al fondo.