@higinio/design-mcp 0.1.1 → 0.2.1

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.
@@ -15,29 +15,36 @@
15
15
  })();
16
16
  </script>
17
17
  <style>
18
- /* ==========================================================================
19
- CELESTE CLÁSICA — Framework CSS v2.0 (embebido)
20
- Idéntico a dist/celeste-clasica/celeste-clasica.css
21
- ========================================================================== */
18
+ /* ============================================================
19
+ Celeste Clásica — Design Tokens v2.0
20
+ Sistema de diseño de Higinio Sosa · light-first
21
+ Fuente de verdad: DESIGN-celeste-clasica.md
22
+ Uso: importar ANTES de cualquier estilo propio.
23
+ Los componentes consumen SOLO aliases semánticos, nunca hex.
24
+ ============================================================ */
22
25
 
23
26
  @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");
24
27
 
25
28
  :root {
29
+ /* == Primitivos: primario (Azul Acero) == */
26
30
  --primary-50: #f2f6fa; --primary-100: #dfe9f2; --primary-200: #c3d5e6;
27
31
  --primary-300: #a0c0d4; --primary-400: #87a8c7; --primary-500: #7192ba;
28
32
  --primary-600: #5d7ba3; --primary-700: #4d6588; --primary-800: #40536e;
29
33
  --primary-900: #31343e;
30
34
 
35
+ /* == Primitivos: neutrales (Gris Azulado) == */
31
36
  --neutral-0: #ffffff; --neutral-50: #f7f8fa; --neutral-100: #eef0f3;
32
37
  --neutral-200: #dcdfe5; --neutral-300: #b8bdc7; --neutral-400: #8a8f9c;
33
38
  --neutral-500: #666b78; --neutral-600: #4d5160; --neutral-700: #3d414e;
34
39
  --neutral-800: #31343e; --neutral-900: #22242b;
35
40
 
41
+ /* == Primitivos: semánticos (ramp de 3: sólido / fondo / texto AA) == */
36
42
  --success-500: #3d9970; --success-100: #e2f3ec; --success-text: #2b6e50;
37
43
  --warning-500: #c9922e; --warning-100: #faf1de; --warning-text: #8a651c;
38
44
  --danger-500: #c04d4d; --danger-100: #f9e7e7; --danger-text: #8f3232;
39
45
  --info-500: #7192ba; --info-100: #dfe9f2; --info-text: #4d6588;
40
46
 
47
+ /* == Aliases semánticos (los componentes consumen SOLO esto) == */
41
48
  --bg: var(--neutral-50);
42
49
  --surface: var(--neutral-0);
43
50
  --surface-alt: var(--neutral-100);
@@ -54,11 +61,9 @@
54
61
  --accent-active: var(--primary-700);
55
62
  --accent-soft: var(--primary-50);
56
63
  --accent-soft-strong: var(--primary-100);
57
- --link: var(--primary-700);
58
- --overlay: rgba(49, 52, 62, .5);
59
- --tooltip-bg: var(--neutral-900);
60
- --tooltip-text: #ffffff;
64
+ --link: var(--primary-700); /* AA ~6:1 — texto de links y ghost; primary-500 solo para UI */
61
65
 
66
+ /* == Tipografía == */
62
67
  --font-display: "Fraunces", Georgia, serif;
63
68
  --font-body: "Public Sans", system-ui, -apple-system, sans-serif;
64
69
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;
@@ -72,6 +77,7 @@
72
77
  --weight-regular: 400; --weight-medium: 500; --weight-semibold: 600; --weight-bold: 700;
73
78
  --measure: 65ch;
74
79
 
80
+ /* == Espaciado (escala base 4px) == */
75
81
  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
76
82
  --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px;
77
83
  --space-12: 48px; --space-16: 64px; --space-20: 80px; --space-24: 96px;
@@ -81,6 +87,7 @@
81
87
  --control-height-sm: 36px; --control-height-md: 44px; --control-height-lg: 52px;
82
88
  --gutter: 24px; --gutter-mobile: 16px;
83
89
 
90
+ /* == Radios, bordes y sombras (teñidas de tinta, nunca negro puro) == */
84
91
  --radius-sm: 6px; --radius-md: 10px; --radius-lg: 14px;
85
92
  --radius-xl: 20px; --radius-full: 9999px;
86
93
  --border-width: 1px;
@@ -90,20 +97,60 @@
90
97
  --shadow-lg: 0 12px 24px rgba(49,52,62,.12), 0 4px 8px rgba(49,52,62,.06);
91
98
  --shadow-xl: 0 24px 48px rgba(49,52,62,.16);
92
99
 
100
+ /* == Focus == */
93
101
  --ring: 0 0 0 3px rgba(113,146,186,.25);
94
102
  --ring-color: rgba(113,146,186,.25);
95
- --ring-danger: 0 0 0 3px rgba(192,77,77,.22);
96
103
 
104
+ /* == Motion == */
97
105
  --duration-instant: 100ms; --duration-fast: 150ms; --duration-base: 200ms;
98
106
  --duration-slow: 250ms; --duration-page: 320ms;
99
107
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
100
108
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
101
109
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* moverse de A a B en pantalla */
102
110
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
111
+ --stagger-step: 30ms;
103
112
 
104
113
  color-scheme: light;
105
- }
106
114
 
115
+ --overlay: rgba(49, 52, 62, .5);
116
+ --tooltip-bg: var(--neutral-900);
117
+ --tooltip-text: #ffffff;
118
+ --ring-danger: 0 0 0 3px rgba(192,77,77,.22);
119
+
120
+ --action-bg: var(--primary-700);
121
+ --action-hover: var(--primary-800);
122
+ --action-active: var(--primary-900);
123
+ --text-on-action: var(--text-on-primary);
124
+ --control-border: var(--neutral-400);
125
+ --placeholder: var(--text-muted);
126
+ --selection-bg: var(--action-bg);
127
+ --selection-text: var(--text-on-action);
128
+ --font-ui-heading: var(--font-body);
129
+ --text-ui-title: 2rem;
130
+ --touch-target: 44px;
131
+ --focus-color: var(--accent);
132
+ --text-display-fluid: clamp(2.75rem, 1.7rem + 3vw, 4.75rem);
133
+ --border-hover: var(--neutral-500);
134
+ --text-on-danger: #ffffff;
135
+
136
+ /* Series categóricas: orden estable; acompañar con etiqueta o forma. */
137
+ --chart-1: #4a8cc1;
138
+ --chart-2: #853f63;
139
+ --chart-3: #129a92;
140
+ --chart-4: #8e4031;
141
+ --chart-5: #5b975e;
142
+ --chart-6: #8a600a;
143
+ --chart-7: #857bc1;
144
+ /* Ritmo de contenido y formularios. */
145
+ --field-gap: 6px;
146
+ --heading-gap: var(--space-2);
147
+ --content-gap: var(--space-4);
148
+ --card-content-gap: var(--space-4);
149
+ --card-group-gap: var(--space-6);
150
+
151
+ }
152
+
153
+ /* == Modo oscuro (variante) — alternar data-theme en <html> == */
107
154
  [data-theme="dark"] {
108
155
  --bg: #16181e;
109
156
  --surface: #1d2028;
@@ -120,28 +167,58 @@
120
167
  --accent-soft: rgba(113,146,186,.12);
121
168
  --accent-soft-strong: rgba(113,146,186,.18);
122
169
  --text-on-primary: #16181e;
123
- --text-on-primary-solid: #a0c0d4;
124
170
  --link: #a0c0d4;
125
- --overlay: rgba(0, 0, 0, .6);
126
- --tooltip-bg: #e6e9ee;
127
- --tooltip-text: #31343e;
128
171
  --success-500: #5cbf93; --success-100: #12291f; --success-text: #7fd4ae;
129
172
  --warning-500: #e0ad4d; --warning-100: #2b2210; --warning-text: #ecc57e;
130
173
  --danger-500: #e07777; --danger-100: #2e1515; --danger-text: #eb9e9e;
131
174
  --info-500: #87a8c7; --info-100: #1a2430; --info-text: #a0c0d4;
132
175
  --ring: 0 0 0 3px rgba(160,192,212,.3);
133
- --ring-danger: 0 0 0 3px rgba(224,119,119,.25);
176
+ --ring-color: rgba(160,192,212,.3);
134
177
  --shadow-sm: 0 1px 2px rgba(0,0,0,.35);
135
178
  --shadow-md: 0 4px 12px rgba(0,0,0,.45);
136
179
  --shadow-lg: 0 12px 32px rgba(0,0,0,.55);
137
180
  color-scheme: dark;
181
+
182
+ --text-on-primary-solid: #a0c0d4;
183
+ --overlay: rgba(0, 0, 0, .6);
184
+ --tooltip-bg: #e6e9ee;
185
+ --tooltip-text: #31343e;
186
+ --ring-danger: 0 0 0 3px rgba(224,119,119,.25);
187
+
188
+ --selection-bg: var(--action-bg);
189
+ --selection-text: var(--text-on-action);
190
+ --action-bg: var(--accent);
191
+ --action-hover: var(--accent-hover);
192
+ --action-active: var(--accent-active);
193
+ --control-border: var(--text-muted);
194
+ --border-hover: var(--text-secondary);
195
+ --text-on-danger: #16181e;
196
+ --focus-color: var(--accent);
197
+
198
+ /* Series categóricas: orden estable; acompañar con etiqueta o forma. */
199
+ --chart-1: #5798ce;
200
+ --chart-2: #a0587c;
201
+ --chart-3: #2aa79e;
202
+ --chart-4: #aa5849;
203
+ --chart-5: #67a369;
204
+ --chart-6: #946a1c;
205
+ --chart-7: #877cc3;
206
+ }
207
+
208
+ /* == Accesibilidad del movimiento == */
209
+
210
+ @media (prefers-reduced-motion: reduce) {
211
+ :root {
212
+ --duration-instant: 0ms; --duration-fast: 0ms; --duration-base: 0ms;
213
+ --duration-slow: 0ms; --duration-page: 0ms; --stagger-step: 0ms;
214
+ }
138
215
  }
139
216
 
140
217
  *, *::before, *::after { box-sizing: border-box; }
141
- html, body { overflow-x: clip; }
142
218
 
219
+ /* Focus ring global de teclado — especificidad cero vía :where() */
143
220
  :where(a, button, [role="button"], input, select, textarea, summary, [tabindex]):focus-visible {
144
- outline: 2px solid var(--primary-500);
221
+ outline: 2px solid var(--focus-color);
145
222
  outline-offset: 2px;
146
223
  border-radius: 2px;
147
224
  }
@@ -178,18 +255,14 @@ html, body { overflow-x: clip; }
178
255
  .celeste-reset h6 { font-size: var(--text-base); line-height: var(--leading-base); }
179
256
  .celeste-reset p { max-width: var(--measure); margin: 0 0 1em; }
180
257
  .celeste-reset table { font-variant-numeric: tabular-nums; }
181
- .celeste-reset ::placeholder { color: var(--text-disabled); }
258
+ .celeste-reset ::placeholder { color: var(--placeholder); }
182
259
  .celeste-reset ::selection { background: rgba(113,146,186,.25); }
183
260
 
184
- @media (prefers-reduced-motion: reduce) {
185
- *, *::before, *::after {
186
- animation-duration: 0.01ms !important;
187
- animation-iteration-count: 1 !important;
188
- transition-duration: 0.01ms !important;
189
- scroll-behavior: auto !important;
190
- }
191
- }
192
261
 
262
+
263
+ /* --------------------------------------------------------------------------
264
+ 3. LAYOUT — contenedor, grid 12 col (§14)
265
+ -------------------------------------------------------------------------- */
193
266
  .container { width: 100%; margin-inline: auto; padding-inline: var(--gutter-mobile); }
194
267
  @media (min-width: 640px) { .container { padding-inline: var(--space-4); } }
195
268
  @media (min-width: 768px) { .container { max-width: 720px; } }
@@ -211,6 +284,9 @@ html, body { overflow-x: clip; }
211
284
  .col-lg-6{grid-column:span 6}.col-lg-8{grid-column:span 8}.col-lg-12{grid-column:span 12}
212
285
  }
213
286
 
287
+ /* --------------------------------------------------------------------------
288
+ 4. BOTONES (§10.1) · ICON BUTTON (§10.2) · SPINNER (§10.3)
289
+ -------------------------------------------------------------------------- */
214
290
  .btn {
215
291
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
216
292
  height: var(--control-height-md); padding: var(--button-padding-md);
@@ -227,15 +303,15 @@ html, body { overflow-x: clip; }
227
303
  .btn:disabled { cursor: not-allowed; }
228
304
  .btn svg { width: 20px; height: 20px; flex: none; }
229
305
 
230
- /* Gating de hover (§11.5): los hover que mueven/elevan van dentro de
231
- @media (hover: hover) and (pointer: fine) — en touch el tap deja el hover pegado.
232
- Los hover de solo color/fondo quedan fuera. */
233
- .btn--primary { background: var(--accent); color: var(--text-on-primary); }
234
- .btn--primary:hover:not(:disabled) { background: var(--accent-hover); }
306
+ /* Gating de hover (§11.5): los hover que mueven/elevan (transform, box-shadow) van
307
+ dentro de @media (hover: hover) and (pointer: fine) — en touch el tap dispara
308
+ hovers falsos que se quedan pegados. Los hover de solo color/fondo quedan fuera. */
309
+ .btn--primary { background: var(--action-bg); color: var(--text-on-action); }
310
+ .btn--primary:hover:not(:disabled) { background: var(--action-hover); }
235
311
  @media (hover: hover) and (pointer: fine) {
236
312
  .btn--primary:hover:not(:disabled) { box-shadow: var(--shadow-sm); }
237
313
  }
238
- .btn--primary:active:not(:disabled) { background: var(--accent-active); box-shadow: none; }
314
+ .btn--primary:active:not(:disabled) { background: var(--action-active); box-shadow: none; }
239
315
  .btn--primary:disabled { background: var(--border); color: var(--text-disabled); box-shadow: none; }
240
316
 
241
317
  .btn--secondary { background: var(--surface); border-color: var(--border-strong); color: var(--text-primary); }
@@ -253,7 +329,7 @@ html, body { overflow-x: clip; }
253
329
  .btn--ghost:active:not(:disabled) { background: var(--accent-soft-strong); }
254
330
  .btn--ghost:disabled { background: var(--border); color: var(--text-disabled); }
255
331
 
256
- .btn--danger { background: var(--danger-500); color: #fff; }
332
+ .btn--danger { background: var(--danger-500); color: var(--text-on-danger); }
257
333
  .btn--danger:hover:not(:disabled) { background: color-mix(in srgb, var(--danger-500) 92%, #000); }
258
334
  .btn--danger:active:not(:disabled) { background: color-mix(in srgb, var(--danger-500) 86%, #000); }
259
335
  .btn--danger:disabled { opacity: .5; }
@@ -261,9 +337,10 @@ html, body { overflow-x: clip; }
261
337
  .btn--sm { height: var(--control-height-sm); padding: var(--button-padding-sm); font-size: var(--text-sm); }
262
338
  .btn--lg { height: var(--control-height-lg); padding: 14px 24px; font-size: var(--text-lg); }
263
339
 
340
+ /* IconButton — requiere aria-label */
264
341
  .icon-btn {
265
342
  display: inline-grid; place-items: center;
266
- width: var(--control-height-md); height: var(--control-height-md); padding: 0;
343
+ width: 40px; height: 40px; padding: 0;
267
344
  border: var(--border-width) solid transparent; border-radius: var(--radius-md);
268
345
  background: transparent; color: var(--text-secondary); cursor: pointer;
269
346
  transition: background-color var(--duration-fast) var(--ease-out),
@@ -273,7 +350,7 @@ html, body { overflow-x: clip; }
273
350
  .icon-btn:active:not(:disabled) { transform: scale(.98); }
274
351
  .icon-btn:disabled { opacity: .5; cursor: not-allowed; }
275
352
  .icon-btn svg { width: 18px; height: 18px; }
276
- .icon-btn--sm { width: var(--control-height-sm); height: var(--control-height-sm); } .icon-btn--sm svg { width: 16px; height: 16px; }
353
+ .icon-btn--sm { width: 32px; height: 32px; } .icon-btn--sm svg { width: 16px; height: 16px; }
277
354
  .icon-btn--lg { width: 48px; height: 48px; } .icon-btn--lg svg { width: 20px; height: 20px; }
278
355
  .icon-btn--ghost:hover:not(:disabled) { background: var(--surface-alt); color: var(--text-primary); }
279
356
  .icon-btn--primary { background: var(--accent); color: var(--text-on-primary); }
@@ -281,15 +358,19 @@ html, body { overflow-x: clip; }
281
358
  .icon-btn--secondary { background: var(--surface); border-color: var(--border-strong); }
282
359
  .icon-btn--secondary:hover:not(:disabled) { background: var(--bg); }
283
360
 
361
+ /* Spinner */
284
362
  .spinner { width: 16px; height: 16px; animation: cc-spin .8s linear infinite; color: var(--accent); }
285
363
  .spinner circle { opacity: .25; }
286
364
  .spinner path { opacity: 1; }
287
365
  @keyframes cc-spin { to { transform: rotate(360deg); } }
288
366
 
367
+ /* --------------------------------------------------------------------------
368
+ 5. FORMULARIOS — field, input, textarea, select (§10.4–10.6)
369
+ -------------------------------------------------------------------------- */
289
370
  .field { display: flex; flex-direction: column; gap: 6px; }
290
371
  .field__label { font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-secondary); }
291
372
  .field__help { font-size: var(--text-sm); color: var(--text-muted); }
292
- .field__error { font-size: var(--text-sm); color: var(--danger-500); display: inline-flex; align-items: center; gap: 6px; }
373
+ .field__error { font-size: var(--text-sm); color: var(--danger-text); display: inline-flex; align-items: center; gap: 6px; }
293
374
  .field__error svg { width: 14px; height: 14px; flex: none; }
294
375
  /* Campo obligatorio (§10.4): asterisco decorativo; el input lleva required/aria-required */
295
376
  .field--required .field__label::after { content: " *"; color: var(--danger-500); }
@@ -297,14 +378,14 @@ html, body { overflow-x: clip; }
297
378
  .input, .textarea, .select {
298
379
  width: 100%; font-family: var(--font-body); font-size: var(--text-base);
299
380
  color: var(--text-primary); background: var(--surface);
300
- border: var(--border-width) solid var(--border-strong); border-radius: var(--radius-sm);
381
+ border: var(--border-width) solid var(--control-border); border-radius: var(--radius-sm);
301
382
  transition: border-color var(--duration-fast) var(--ease-out),
302
383
  box-shadow var(--duration-fast) var(--ease-out);
303
384
  }
304
385
  .input, .select { height: var(--control-height-md); padding: 0 14px; }
305
386
  .input:hover:not(:disabled):not(:focus),
306
387
  .textarea:hover:not(:disabled):not(:focus),
307
- .select:hover:not(:disabled):not(:focus) { border-color: var(--text-disabled); }
388
+ .select:hover:not(:disabled):not(:focus) { border-color: var(--border-hover); }
308
389
  .input:focus, .textarea:focus, .select:focus {
309
390
  outline: none; border-color: var(--accent); box-shadow: var(--ring);
310
391
  }
@@ -322,7 +403,7 @@ html, body { overflow-x: clip; }
322
403
  .input-icon .input { padding-left: 40px; }
323
404
 
324
405
  .textarea { min-height: 96px; padding: 12px 14px; resize: vertical; line-height: var(--leading-base); }
325
- .textarea-counter { font-size: var(--text-xs); color: var(--text-disabled); text-align: right; }
406
+ .textarea-counter { font-size: var(--text-xs); color: var(--text-muted); text-align: right; }
326
407
 
327
408
  .select-wrap { position: relative; }
328
409
  .select { appearance: none; padding-right: 38px; cursor: pointer; }
@@ -335,6 +416,9 @@ html, body { overflow-x: clip; }
335
416
  transition: transform var(--duration-base) var(--ease-out);
336
417
  }
337
418
 
419
+ /* --------------------------------------------------------------------------
420
+ 6. SEGMENTED CONTROL (§10.7) · SLIDER (§10.8)
421
+ -------------------------------------------------------------------------- */
338
422
  .segmented {
339
423
  display: inline-flex; align-items: stretch;
340
424
  background: var(--surface-alt); border-radius: var(--radius-md); padding: 3px;
@@ -355,7 +439,7 @@ html, body { overflow-x: clip; }
355
439
  background: var(--surface); color: var(--text-primary);
356
440
  font-weight: var(--weight-semibold); box-shadow: var(--shadow-xs);
357
441
  }
358
- .segmented input:focus-visible + label { outline: 2px solid var(--primary-500); outline-offset: 2px; }
442
+ .segmented input:focus-visible + label { outline: 2px solid var(--focus-color); outline-offset: 2px; }
359
443
 
360
444
  .slider-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; }
361
445
  .slider-row .slider-label { font-size: var(--text-sm); color: var(--text-secondary); }
@@ -380,6 +464,9 @@ html, body { overflow-x: clip; }
380
464
  }
381
465
  .slider:active::-moz-range-thumb { transform: scale(1.15); }
382
466
 
467
+ /* --------------------------------------------------------------------------
468
+ 7. SELECCIÓN — checkbox, radio, switch (§10.9–10.10)
469
+ -------------------------------------------------------------------------- */
383
470
  .checkbox, .radio {
384
471
  display: inline-flex; align-items: center; gap: 10px;
385
472
  font-size: var(--text-base); color: var(--text-primary); cursor: pointer; position: relative;
@@ -389,7 +476,7 @@ html, body { overflow-x: clip; }
389
476
  }
390
477
  .checkbox__box {
391
478
  width: 20px; height: 20px; flex: none; display: grid; place-items: center;
392
- background: var(--surface); border: 2px solid var(--border-strong); border-radius: 4px;
479
+ background: var(--surface); border: 2px solid var(--control-border); border-radius: 4px;
393
480
  transition: background-color var(--duration-fast) var(--ease-out),
394
481
  border-color var(--duration-fast) var(--ease-out);
395
482
  }
@@ -399,7 +486,7 @@ html, body { overflow-x: clip; }
399
486
  stroke-dasharray: 20; stroke-dashoffset: 20;
400
487
  transition: stroke-dashoffset var(--duration-base) var(--ease-spring);
401
488
  }
402
- .checkbox:hover .checkbox__box { border-color: var(--primary-400); }
489
+ .checkbox:hover .checkbox__box { border-color: var(--focus-color); }
403
490
  .checkbox input:checked ~ .checkbox__box { background: var(--accent); border-color: var(--accent); }
404
491
  .checkbox input:checked ~ .checkbox__box svg { stroke-dashoffset: 0; }
405
492
  .checkbox input:focus-visible ~ .checkbox__box { box-shadow: var(--ring); }
@@ -416,7 +503,7 @@ html, body { overflow-x: clip; }
416
503
 
417
504
  .radio__dot {
418
505
  width: 20px; height: 20px; flex: none; display: grid; place-items: center;
419
- background: var(--surface); border: 2px solid var(--border-strong); border-radius: var(--radius-full);
506
+ background: var(--surface); border: 2px solid var(--control-border); border-radius: var(--radius-full);
420
507
  transition: background-color var(--duration-fast) var(--ease-out),
421
508
  border-color var(--duration-fast) var(--ease-out);
422
509
  }
@@ -425,7 +512,7 @@ html, body { overflow-x: clip; }
425
512
  background: var(--text-on-primary); transform: scale(0);
426
513
  transition: transform var(--duration-base) var(--ease-spring);
427
514
  }
428
- .radio:hover .radio__dot { border-color: var(--primary-400); }
515
+ .radio:hover .radio__dot { border-color: var(--focus-color); }
429
516
  .radio input:checked ~ .radio__dot { background: var(--accent); border-color: var(--accent); }
430
517
  .radio input:checked ~ .radio__dot::after { transform: scale(1); }
431
518
  .radio input:focus-visible ~ .radio__dot { box-shadow: var(--ring); }
@@ -447,6 +534,9 @@ html, body { overflow-x: clip; }
447
534
  .switch input:focus-visible ~ .switch__track { box-shadow: var(--ring); }
448
535
  .switch input:disabled ~ * { opacity: .5; cursor: not-allowed; }
449
536
 
537
+ /* --------------------------------------------------------------------------
538
+ 8. SUPERFICIES — card, badge, tag, avatar, divider, accordion (§10.11–10.14)
539
+ -------------------------------------------------------------------------- */
450
540
  .card {
451
541
  background: var(--surface); border: var(--border-width) solid var(--border);
452
542
  border-radius: var(--radius-lg); padding: var(--card-padding);
@@ -572,6 +662,10 @@ html, body { overflow-x: clip; }
572
662
  }
573
663
  .accordion__item.is-open .accordion__content { opacity: 1; }
574
664
 
665
+ /* --------------------------------------------------------------------------
666
+ 9. FEEDBACK — tooltip, alert, toast, progress, skeleton, empty state
667
+ (§10.15–10.18, §10.25)
668
+ -------------------------------------------------------------------------- */
575
669
  [data-tooltip] { position: relative; }
576
670
  [data-tooltip]::after {
577
671
  content: attr(data-tooltip);
@@ -608,14 +702,14 @@ html, body { overflow-x: clip; }
608
702
  }
609
703
  .alert > svg { width: 20px; height: 20px; flex: none; margin-top: 1px; }
610
704
  .alert__title { font-weight: var(--weight-semibold); margin: 0 0 2px; }
611
- .alert__body { opacity: .92; margin: 0; }
705
+ .alert__body { margin: 0; }
612
706
  .alert__close {
613
707
  margin-left: auto; flex: none; display: grid; place-items: center;
614
- width: 36px; height: 36px; border: 0; border-radius: var(--radius-sm);
708
+ width: 24px; height: 24px; border: 0; border-radius: var(--radius-sm);
615
709
  background: transparent; color: inherit; cursor: pointer; opacity: .7;
616
710
  }
617
711
  .alert__close:hover { opacity: 1; }
618
- .alert__close svg { width: 18px; height: 18px; }
712
+ .alert__close svg { width: 16px; height: 16px; }
619
713
  .alert--info { background: var(--info-100); color: var(--info-text); }
620
714
  .alert--info > svg { color: var(--info-500); }
621
715
  .alert--success { background: var(--success-100); color: var(--success-text); }
@@ -707,6 +801,9 @@ html, body { overflow-x: clip; }
707
801
  .empty-state__title { font-size: var(--text-lg); font-weight: var(--weight-semibold); color: var(--text-primary); margin: 0; }
708
802
  .empty-state__desc { font-size: var(--text-sm); color: var(--text-muted); max-width: 40ch; margin: 0; }
709
803
 
804
+ /* --------------------------------------------------------------------------
805
+ 10. OVERLAYS — modal, menu (§10.19–10.20)
806
+ -------------------------------------------------------------------------- */
710
807
  .modal {
711
808
  position: fixed; inset: 0; z-index: 100;
712
809
  display: grid; place-items: center; padding: var(--space-4);
@@ -745,10 +842,14 @@ html, body { overflow-x: clip; }
745
842
  background: var(--surface); border: var(--border-width) solid var(--border);
746
843
  border-radius: var(--radius-md); box-shadow: var(--shadow-lg);
747
844
  opacity: 0; transform: translateY(-4px); pointer-events: none;
748
- transition: opacity 160ms var(--ease-out), transform 160ms var(--ease-out);
845
+ transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
846
+
847
+ visibility: hidden;
848
+
849
+ display: none;
749
850
  }
750
851
  .menu--right { left: auto; right: 0; }
751
- .menu.is-open { opacity: 1; transform: none; pointer-events: auto; }
852
+ .menu.is-open { opacity: 1; transform: none; pointer-events: auto; visibility: visible; display: block; }
752
853
  .menu__item {
753
854
  display: flex; align-items: center; gap: 10px; width: 100%;
754
855
  padding: 9px 12px; border: 0; border-radius: var(--radius-sm);
@@ -758,10 +859,13 @@ html, body { overflow-x: clip; }
758
859
  }
759
860
  .menu__item:hover { background: var(--bg); }
760
861
  .menu__item svg { width: 16px; height: 16px; color: var(--text-muted); flex: none; }
761
- .menu__item--danger, .menu__item--danger svg { color: var(--danger-500); }
862
+ .menu__item--danger, .menu__item--danger svg { color: var(--danger-text); }
762
863
  .menu__item--danger:hover { background: var(--danger-100); }
763
864
  .menu__sep { height: var(--border-width); background: var(--border); margin: 6px 4px; }
764
865
 
866
+ /* --------------------------------------------------------------------------
867
+ 11. DATOS — tabla, stat, pagination (§10.21–10.23)
868
+ -------------------------------------------------------------------------- */
765
869
  .table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
766
870
  .table thead th {
767
871
  height: 40px; padding: 0 16px; text-align: left;
@@ -823,6 +927,32 @@ html, body { overflow-x: clip; }
823
927
  .stat__delta--up { color: var(--success-text); }
824
928
  .stat__delta--down { color: var(--danger-text); }
825
929
 
930
+ /* Chip de métrica (§10.22): rótulo y valor en una pastilla de una línea */
931
+ .metric-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
932
+ .metric-chip {
933
+ display: inline-flex; align-items: center; gap: var(--space-2);
934
+ padding: var(--space-1) var(--space-3);
935
+ border: var(--border-width) solid var(--border); border-radius: var(--radius-full);
936
+ background: var(--surface);
937
+ font-family: var(--font-body); font-size: var(--text-sm); line-height: 1.5;
938
+ color: var(--text-secondary); white-space: nowrap;
939
+ }
940
+ .metric-chip svg { width: 16px; height: 16px; flex: none; color: var(--text-muted); }
941
+ .metric-chip__value {
942
+ font-weight: var(--weight-semibold); color: var(--link);
943
+ font-variant-numeric: tabular-nums;
944
+ }
945
+ button.metric-chip {
946
+ min-height: var(--control-height-md); padding-inline: var(--space-4);
947
+ cursor: pointer; text-align: left;
948
+ transition: background-color var(--duration-fast) var(--ease-out),
949
+ border-color var(--duration-fast) var(--ease-out),
950
+ transform var(--duration-instant) var(--ease-out);
951
+ }
952
+ button.metric-chip:hover { background: var(--accent-soft); border-color: var(--accent); }
953
+ button.metric-chip:active { background: var(--accent-soft-strong); transform: scale(.98); }
954
+ button.metric-chip:focus-visible { outline: 2px solid var(--focus-color); outline-offset: 2px; }
955
+
826
956
  .pagination { display: flex; align-items: center; gap: 4px; list-style: none; padding: 0; margin: 0; }
827
957
  .pagination__btn {
828
958
  display: inline-grid; place-items: center;
@@ -840,6 +970,9 @@ html, body { overflow-x: clip; }
840
970
  .pagination__btn svg { width: 16px; height: 16px; }
841
971
  .pagination__ellipsis { color: var(--text-disabled); padding: 0 4px; user-select: none; }
842
972
 
973
+ /* --------------------------------------------------------------------------
974
+ 12. NAVEGACIÓN — navbar, sidebar, tabs, breadcrumbs (§10.24)
975
+ -------------------------------------------------------------------------- */
843
976
  .navbar {
844
977
  display: flex; align-items: center; gap: var(--space-8);
845
978
  height: 64px; padding: 0 var(--space-6);
@@ -868,7 +1001,7 @@ html, body { overflow-x: clip; }
868
1001
  .sidebar__item {
869
1002
  display: flex; align-items: center; gap: 10px;
870
1003
  padding: 10px 12px; border-radius: var(--radius-md);
871
- font-size: var(--text-sm); font-weight: var(--weight-medium);
1004
+ font-family: var(--font-body); font-size: var(--text-sm); font-weight: var(--weight-medium);
872
1005
  color: var(--text-secondary); text-decoration: none; border: 0; background: transparent;
873
1006
  cursor: pointer; text-align: left; width: 100%;
874
1007
  transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
@@ -878,6 +1011,68 @@ html, body { overflow-x: clip; }
878
1011
  .sidebar__item.is-active { background: var(--accent-soft-strong); color: var(--link); }
879
1012
  .sidebar__item.is-active svg { color: var(--accent); }
880
1013
 
1014
+ /* Grupos con rótulo dentro del sidebar */
1015
+ .sidebar__group { display: flex; flex-direction: column; gap: 2px; }
1016
+ .sidebar__group + .sidebar__group { margin-top: var(--space-4); }
1017
+ .sidebar__group-label {
1018
+ padding: 0 12px var(--space-1);
1019
+ font-size: var(--text-xs); font-weight: var(--weight-semibold);
1020
+ text-transform: uppercase; letter-spacing: var(--tracking-label);
1021
+ color: var(--text-muted); white-space: nowrap;
1022
+ }
1023
+ .sidebar__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1024
+
1025
+ /* Sidebar contraíble (§10.24): expandido 260 px, riel de iconos de 64 px.
1026
+ El ancho cambia sin transición; solo los rótulos se funden con opacity y transform. */
1027
+ .sidebar--collapsible .sidebar__label {
1028
+ transition: opacity var(--duration-fast) var(--ease-out),
1029
+ transform var(--duration-fast) var(--ease-out);
1030
+ }
1031
+ .sidebar__toggle { margin-top: auto; }
1032
+ .sidebar.is-collapsed { width: 64px; padding-inline: var(--space-3); }
1033
+ .sidebar.is-collapsed .sidebar__item { position: relative; justify-content: center; padding-inline: 0; }
1034
+ .sidebar.is-collapsed .sidebar__toggle svg { transform: rotate(180deg); }
1035
+ .sidebar.is-collapsed .sidebar__group + .sidebar__group { margin-top: var(--space-2); }
1036
+ /* En el riel, el rótulo de grupo es un separador de 1 px; su texto sigue en el árbol de accesibilidad */
1037
+ .sidebar.is-collapsed .sidebar__group-label {
1038
+ height: 1px; padding: 0; margin: 0 var(--space-2) var(--space-2);
1039
+ overflow: hidden; color: transparent; background: var(--border);
1040
+ }
1041
+ /* Oculto a la vista pero accesible (patrón de .u-visually-hidden) */
1042
+ .sidebar.is-collapsed .sidebar__label,
1043
+ .sidebar.is-collapsed .sidebar__group:first-child > .sidebar__group-label {
1044
+ position: absolute; width: 1px; height: 1px; margin: -1px;
1045
+ clip-path: inset(50%); overflow: hidden; white-space: nowrap;
1046
+ }
1047
+ .sidebar.is-collapsed .sidebar__label {
1048
+ border-radius: var(--radius-sm); background: var(--tooltip-bg); color: var(--tooltip-text);
1049
+ font-size: var(--text-xs); line-height: 16px;
1050
+ pointer-events: none; z-index: 70;
1051
+ opacity: 0; transform: translateX(-4px);
1052
+ }
1053
+ /* Tooltip del riel: el propio rótulo aparece a la derecha del ítem, también con teclado */
1054
+ .sidebar.is-collapsed .sidebar__item:focus-visible .sidebar__label {
1055
+ left: calc(100% + 8px); top: calc(50% - 14px); width: auto; height: auto;
1056
+ margin: 0; padding: 6px 10px; clip-path: none; overflow: visible;
1057
+ opacity: 1; transform: none; transition-delay: 400ms;
1058
+ }
1059
+ /* Por hover solo con puntero real — en touch quedaría pegado tras el tap */
1060
+ @media (hover: hover) and (pointer: fine) {
1061
+ .sidebar.is-collapsed .sidebar__item:hover .sidebar__label {
1062
+ left: calc(100% + 8px); top: calc(50% - 14px); width: auto; height: auto;
1063
+ margin: 0; padding: 6px 10px; clip-path: none; overflow: visible;
1064
+ opacity: 1; transform: none; transition-delay: 400ms;
1065
+ }
1066
+ }
1067
+ /* Por debajo de 768 px la navegación vive en un Drawer --left (§10.29), que abre el botón ☰
1068
+ .sidebar-trigger (combinado con .icon-btn); desde 768 px el sidebar es fijo y el botón sobra */
1069
+ @media (max-width: 767px) {
1070
+ .sidebar--collapsible { display: none; }
1071
+ }
1072
+ @media (min-width: 768px) {
1073
+ .sidebar-trigger { display: none; }
1074
+ }
1075
+
881
1076
  .tabs { display: flex; gap: var(--space-6); border-bottom: var(--border-width) solid var(--border); }
882
1077
  .tabs__tab {
883
1078
  padding: 12px 4px; margin-bottom: -1px;
@@ -896,6 +1091,9 @@ html, body { overflow-x: clip; }
896
1091
  .breadcrumbs__sep { user-select: none; }
897
1092
  .breadcrumbs__current { color: var(--text-primary); }
898
1093
 
1094
+ /* --------------------------------------------------------------------------
1095
+ 13. TIPOGRÁFICOS — link, code
1096
+ -------------------------------------------------------------------------- */
899
1097
  .link {
900
1098
  color: var(--link); text-decoration: none;
901
1099
  background-image: linear-gradient(currentColor, currentColor);
@@ -912,17 +1110,21 @@ html, body { overflow-x: clip; }
912
1110
  border-radius: var(--radius-sm); padding: 1px 6px;
913
1111
  }
914
1112
 
1113
+ /* --------------------------------------------------------------------------
1114
+ 13b. COMBOBOX · BÚSQUEDA · DRAWER · POPOVER · BUTTONGROUP (§10.28–10.31)
1115
+ -------------------------------------------------------------------------- */
1116
+
915
1117
  /* Combobox / Autocomplete (§10.28) */
916
1118
  .combobox { position: relative; }
917
1119
  .combobox__input {
918
1120
  width: 100%; height: var(--control-height-md); padding: 0 14px;
919
1121
  font-family: var(--font-body); font-size: var(--text-base);
920
1122
  color: var(--text-primary); background: var(--surface);
921
- border: var(--border-width) solid var(--border-strong); border-radius: var(--radius-sm);
1123
+ border: var(--border-width) solid var(--control-border); border-radius: var(--radius-sm);
922
1124
  transition: border-color var(--duration-fast) var(--ease-out),
923
1125
  box-shadow var(--duration-fast) var(--ease-out);
924
1126
  }
925
- .combobox__input:hover:not(:disabled):not(:focus) { border-color: var(--text-disabled); }
1127
+ .combobox__input:hover:not(:disabled):not(:focus) { border-color: var(--border-hover); }
926
1128
  .combobox__input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
927
1129
  .combobox__input:disabled { background: var(--surface-alt); color: var(--text-disabled); cursor: not-allowed; }
928
1130
  .combobox__list {
@@ -960,12 +1162,12 @@ html, body { overflow-x: clip; }
960
1162
  width: 100%; height: var(--control-height-md); padding: 0 40px;
961
1163
  font-family: var(--font-body); font-size: var(--text-base);
962
1164
  color: var(--text-primary); background: var(--surface);
963
- border: var(--border-width) solid var(--border-strong); border-radius: var(--radius-sm);
1165
+ border: var(--border-width) solid var(--control-border); border-radius: var(--radius-sm);
964
1166
  transition: border-color var(--duration-fast) var(--ease-out),
965
1167
  box-shadow var(--duration-fast) var(--ease-out);
966
1168
  }
967
1169
  .search__input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
968
- .search__input:hover:not(:focus) { border-color: var(--text-disabled); }
1170
+ .search__input:hover:not(:focus) { border-color: var(--border-hover); }
969
1171
  .search__input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
970
1172
  .search__clear {
971
1173
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
@@ -1028,8 +1230,12 @@ html, body { overflow-x: clip; }
1028
1230
  transform-origin: top left; opacity: 0; transform: scale(.96); pointer-events: none;
1029
1231
  transition: opacity var(--duration-base) var(--ease-out),
1030
1232
  transform var(--duration-base) var(--ease-out);
1233
+
1234
+ visibility: hidden;
1235
+
1236
+ display: none;
1031
1237
  }
1032
- .popover.is-open .popover__panel { opacity: 1; transform: none; pointer-events: auto; }
1238
+ .popover.is-open .popover__panel { opacity: 1; transform: none; pointer-events: auto; visibility: visible; display: block; }
1033
1239
  .popover--right .popover__panel { left: auto; right: 0; transform-origin: top right; }
1034
1240
 
1035
1241
  /* ButtonGroup (§10.31) */
@@ -1044,17 +1250,21 @@ html, body { overflow-x: clip; }
1044
1250
  .btn-group .btn:not(:first-child) { margin-left: calc(var(--border-width) * -1); }
1045
1251
  .btn-group .btn:hover, .btn-group .btn:focus-visible { z-index: 1; }
1046
1252
 
1253
+ /* --------------------------------------------------------------------------
1254
+ 13c. DATEPICKER · FILEUPLOAD · STEPPER · KBD (§10.32–10.35)
1255
+ -------------------------------------------------------------------------- */
1256
+
1047
1257
  /* DatePicker (§10.32) */
1048
1258
  .datepicker { position: relative; display: inline-block; }
1049
1259
  .datepicker__input {
1050
1260
  width: 100%; height: var(--control-height-md); padding: 0 40px 0 14px;
1051
1261
  font-family: var(--font-body); font-size: var(--text-base);
1052
1262
  color: var(--text-primary); background: var(--surface);
1053
- border: var(--border-width) solid var(--border-strong); border-radius: var(--radius-sm);
1263
+ border: var(--border-width) solid var(--control-border); border-radius: var(--radius-sm);
1054
1264
  transition: border-color var(--duration-fast) var(--ease-out),
1055
1265
  box-shadow var(--duration-fast) var(--ease-out);
1056
1266
  }
1057
- .datepicker__input:hover:not(:disabled):not(:focus) { border-color: var(--text-disabled); }
1267
+ .datepicker__input:hover:not(:disabled):not(:focus) { border-color: var(--border-hover); }
1058
1268
  .datepicker__input:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
1059
1269
  .datepicker__input:disabled { background: var(--surface-alt); color: var(--text-disabled); cursor: not-allowed; }
1060
1270
  .datepicker__icon {
@@ -1069,8 +1279,12 @@ html, body { overflow-x: clip; }
1069
1279
  transform-origin: top left; opacity: 0; transform: scale(.96); pointer-events: none;
1070
1280
  transition: opacity var(--duration-base) var(--ease-out),
1071
1281
  transform var(--duration-base) var(--ease-out);
1282
+
1283
+ visibility: hidden;
1284
+
1285
+ display: none;
1072
1286
  }
1073
- .datepicker.is-open .datepicker__calendar { opacity: 1; transform: none; pointer-events: auto; }
1287
+ .datepicker.is-open .datepicker__calendar { opacity: 1; transform: none; pointer-events: auto; visibility: visible; display: block; }
1074
1288
  .datepicker--right .datepicker__calendar { left: auto; right: 0; transform-origin: top right; }
1075
1289
 
1076
1290
  .calendar { width: 15.75rem; }
@@ -1082,6 +1296,7 @@ html, body { overflow-x: clip; }
1082
1296
  .calendar__weekdays,
1083
1297
  .calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
1084
1298
  .calendar__weekdays { margin-bottom: 4px; }
1299
+ .calendar__week { display: contents; }
1085
1300
  .calendar__weekday {
1086
1301
  display: flex; align-items: center; justify-content: center; height: 28px;
1087
1302
  font-size: var(--text-xs); font-weight: var(--weight-medium); color: var(--text-muted);
@@ -1096,7 +1311,7 @@ html, body { overflow-x: clip; }
1096
1311
  color var(--duration-fast) var(--ease-out);
1097
1312
  }
1098
1313
  .calendar__day:hover:not(:disabled):not(.calendar__day--selected) { background: var(--accent-soft); }
1099
- .calendar__day--outside { color: var(--text-disabled); }
1314
+ .calendar__day--outside { color: var(--text-muted); }
1100
1315
  .calendar__day--today { font-weight: var(--weight-semibold); box-shadow: inset 0 0 0 1px var(--border-strong); }
1101
1316
  .calendar__day--selected { background: var(--accent); color: var(--text-on-primary); font-weight: var(--weight-semibold); }
1102
1317
  .calendar__day:disabled { color: var(--text-disabled); cursor: not-allowed; }
@@ -1204,6 +1419,9 @@ html, body { overflow-x: clip; }
1204
1419
  }
1205
1420
  .kbd-combo { display: inline-flex; align-items: center; gap: 4px; color: var(--text-muted); font-size: var(--text-xs); }
1206
1421
 
1422
+ /* --------------------------------------------------------------------------
1423
+ 14. UTILIDADES MÍNIMAS
1424
+ -------------------------------------------------------------------------- */
1207
1425
  .u-visually-hidden {
1208
1426
  position: absolute; width: 1px; height: 1px; margin: -1px;
1209
1427
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
@@ -1212,6 +1430,172 @@ html, body { overflow-x: clip; }
1212
1430
  .u-muted { color: var(--text-muted); }
1213
1431
  .u-measure { max-width: var(--measure); }
1214
1432
 
1433
+ /* CONTRATOS COMPARTIDOS GENERADOS */
1434
+ /* Contratos comunes de interacción; la identidad y los valores viven en tokens.css. */
1435
+ .grid { grid-template-columns: repeat(12, minmax(0, 1fr)); }
1436
+ .grid > *, .field, .card { min-inline-size: 0; }
1437
+ .stack, .card__content, .form-stack, .text-group { display: flex; flex-direction: column; align-items: stretch; }
1438
+ .stack, .card__content { gap: var(--content-gap); }
1439
+ .stack--tight, .text-group { gap: var(--heading-gap); }
1440
+ .stack--cards { gap: var(--card-group-gap); }
1441
+ .form-stack { gap: var(--form-gap); }
1442
+ .cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
1443
+ .card { display: flex; flex-direction: column; gap: var(--card-content-gap); }
1444
+ .field { gap: var(--field-gap); }
1445
+ .field__label, .field__help, .field__error, .field > :is(label, p, span, div) { margin-block: 0; }
1446
+ :where(.field + .field, .field + .btn, .field + .cluster) { margin-block-start: var(--form-gap); }
1447
+ :where(.card + .card) { margin-block-start: var(--card-group-gap); }
1448
+ .stack > *, .card__content > *, .form-stack > *, .text-group > *, .grid > .card,
1449
+ .card > :is(h1, h2, h3, h4, h5, h6, p), .text-group > :is(h1, h2, h3, h4, h5, h6, p),
1450
+ .stack > :is(h1, h2, h3, h4, h5, h6, p) { margin-block: 0; }
1451
+ .form-stack > .field { margin-block: 0; }
1452
+ .btn {
1453
+ block-size: auto;
1454
+ min-block-size: var(--control-height-md);
1455
+ max-inline-size: 100%;
1456
+ white-space: normal;
1457
+ line-height: 1.35;
1458
+ text-align: center;
1459
+ }
1460
+ .btn--sm { min-block-size: var(--control-height-sm, 36px); }
1461
+ .btn--lg { min-block-size: var(--control-height-lg, 52px); }
1462
+ .btn--primary, .icon-btn--primary, .calendar__day--selected, .pagination__btn.is-active {
1463
+ background: var(--action-bg);
1464
+ color: var(--text-on-action);
1465
+ }
1466
+ .btn--primary:hover:not(:disabled), .icon-btn--primary:hover:not(:disabled) { background: var(--action-hover); color: var(--text-on-action); }
1467
+ .btn--primary:active:not(:disabled), .icon-btn--primary:active:not(:disabled) { background: var(--action-active); color: var(--text-on-action); }
1468
+ .input, .textarea, .select, .combobox__input, .search__input, .datepicker__input {
1469
+ max-inline-size: 100%;
1470
+ caret-color: var(--focus-color);
1471
+ }
1472
+ .input, .select, .combobox__input, .search__input, .datepicker__input {
1473
+ block-size: auto; min-block-size: var(--control-height-md);
1474
+ padding-block: var(--space-2); line-height: 1.5;
1475
+ }
1476
+ :is(.input, .textarea, .select, .combobox__input, .search__input, .datepicker__input):not(.input--error, .input--success, .textarea--error, [aria-invalid="true"]) { border-color: var(--control-border); }
1477
+ .input:hover:not(:disabled):not(:focus):not(.input--error, .input--success, [aria-invalid="true"]), .textarea:hover:not(:disabled):not(:focus):not(.textarea--error, [aria-invalid="true"]),
1478
+ .select:hover:not(:disabled):not(:focus), .combobox__input:hover:not(:disabled):not(:focus),
1479
+ .search__input:hover:not(:focus), .datepicker__input:hover:not(:disabled):not(:focus) { border-color: var(--border-hover); }
1480
+ .input:focus-visible, .textarea:focus-visible, .select:focus-visible,
1481
+ .combobox__input:focus-visible, .search__input:focus-visible, .datepicker__input:focus-visible {
1482
+ outline: 2px solid var(--focus-color);
1483
+ outline-offset: 2px;
1484
+ }
1485
+ .input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--danger-text, var(--danger)); }
1486
+ .input.input--error, .textarea.textarea--error,
1487
+ .input.input--error:hover:not(:disabled):not(:focus), .textarea.textarea--error:hover:not(:disabled):not(:focus),
1488
+ :is(.input, .textarea, .select, .combobox__input, .search__input, .datepicker__input)[aria-invalid="true"]:hover:not(:disabled):not(:focus) {
1489
+ border-color: var(--danger-text, var(--danger));
1490
+ }
1491
+ .input.input--success, .input.input--success:hover:not(:disabled):not(:focus) { border-color: var(--success-text, var(--success)); }
1492
+ .pagination { max-inline-size: 100%; flex-wrap: wrap; row-gap: var(--space-2); }
1493
+ .pagination__ellipsis { color: var(--text-muted); }
1494
+ .step--current .step__marker { color: var(--link); border-color: var(--focus-color); }
1495
+ .checkbox input:focus-visible ~ .checkbox__box,
1496
+ .radio input:focus-visible ~ .radio__dot,
1497
+ .switch input:focus-visible ~ .switch__track {
1498
+ outline: 2px solid var(--focus-color);
1499
+ outline-offset: 2px;
1500
+ }
1501
+ input.checkbox, input.radio { border-color: var(--control-border); }
1502
+ input.checkbox:hover, input.radio:hover { border-color: var(--focus-color); }
1503
+ input.checkbox:checked, input.checkbox:indeterminate, input.radio:checked { border-color: var(--focus-color); }
1504
+ .switch__track { background: var(--control-border); }
1505
+ .tabs { max-inline-size: 100%; overflow-x: auto; }
1506
+ .tabs__tab { flex-shrink: 0; }
1507
+ .segmented { max-inline-size: 100%; block-size: auto; min-block-size: var(--control-height-md); overflow-x: auto; }
1508
+ .segmented label { padding-block: var(--space-2); }
1509
+ .stat__row, .breadcrumbs, .check-row { flex-wrap: wrap; row-gap: var(--space-2); }
1510
+ .progress-row, .slider-row { flex-wrap: wrap; gap: var(--space-2); }
1511
+ .stat__value { max-inline-size: 100%; white-space: normal; overflow-wrap: anywhere; }
1512
+ .file-upload, .file-upload__label { min-inline-size: 0; max-inline-size: 100%; }
1513
+ .file-upload__button { max-inline-size: 100%; white-space: normal; text-align: center; }
1514
+ .file-upload__hint { overflow-wrap: anywhere; }
1515
+ .table-scroll { max-inline-size: 100%; overflow-x: auto; overscroll-behavior-inline: contain; }
1516
+ .popover__panel, .datepicker__calendar, .combobox__list {
1517
+ max-inline-size: calc(100vw - 2rem);
1518
+ max-block-size: min(32rem, 70dvh);
1519
+ overflow: auto;
1520
+ }
1521
+ .file-upload__input:focus-visible + .file-upload__label { outline: 2px solid var(--focus-color); outline-offset: 2px; }
1522
+ .calendar { max-inline-size: calc(100vw - 2rem); }
1523
+ .ui-title, :is(h1, h2, h3, h4, h5, h6).ui-title, [data-mode="operate"] :is(h1, h2, h3) {
1524
+ font-family: var(--font-ui-heading);
1525
+ letter-spacing: 0;
1526
+ line-height: 1.3;
1527
+ font-weight: var(--weight-semibold);
1528
+ }
1529
+ .ui-title, :is(h1, h2, h3, h4, h5, h6).ui-title, [data-mode="operate"] h1 { font-size: var(--text-ui-title); }
1530
+ [data-mode="operate"] h2 { font-size: var(--text-xl); }
1531
+ [data-mode="operate"] h3 { font-size: var(--text-lg); }
1532
+ .display, .reading-title, :is(h1, h2, h3, h4, h5, h6):is(.display, .reading-title) {
1533
+ font-family: var(--font-display);
1534
+ font-size: var(--text-display-fluid);
1535
+ line-height: 1.08;
1536
+ letter-spacing: var(--tracking-display);
1537
+ text-wrap: balance;
1538
+ }
1539
+ :where(.celeste-reset, .salvia-reset, .bruma-reset, .nocturna-reset, body[data-system]) {
1540
+ scrollbar-color: var(--control-border) var(--bg);
1541
+ }
1542
+ ::selection,
1543
+ .celeste-reset ::selection, .salvia-reset ::selection, .bruma-reset ::selection {
1544
+ background: var(--selection-bg); color: var(--selection-text);
1545
+ }
1546
+ ::placeholder { color: var(--placeholder); opacity: 1; }
1547
+ :where(a) { text-underline-offset: .2em; text-decoration-thickness: .08em; }
1548
+ .skip-link {
1549
+ position: fixed; inset-block-start: var(--space-2); inset-inline-start: var(--space-2);
1550
+ z-index: 1000; padding: var(--space-3) var(--space-4);
1551
+ background: var(--action-bg); color: var(--text-on-action);
1552
+ border-radius: var(--radius-sm); transform: translateY(-180%);
1553
+ }
1554
+ .skip-link:focus { transform: none; outline: 2px solid var(--focus-color); outline-offset: 2px; }
1555
+ [data-tooltip]::before, [data-tooltip]::after { display: none; }
1556
+ [data-tooltip]:focus-visible::before, [data-tooltip]:focus-visible::after { display: block; }
1557
+ .has-tooltip .tooltip { display: none; }
1558
+ .has-tooltip:has(:focus-visible) .tooltip { display: block; opacity: 1; transform: translate(-50%, 0); }
1559
+ @media (hover: hover) and (pointer: fine) {
1560
+ [data-tooltip]:hover::before, [data-tooltip]:hover::after { display: block; }
1561
+ .has-tooltip:hover .tooltip { display: block; }
1562
+ }
1563
+ @media (pointer: coarse) {
1564
+ .btn, .icon-btn, .segmented label, .pagination button,
1565
+ .tag__close, .search__clear, .toast__close, .check-row,
1566
+ .checkbox:not(input), .radio:not(input), .switch:not(input) {
1567
+ min-block-size: var(--touch-target);
1568
+ min-inline-size: var(--touch-target);
1569
+ }
1570
+ }
1571
+ @media (max-width: 479px) {
1572
+ .btn-group { flex-wrap: wrap; }
1573
+ .modal { max-inline-size: calc(100vw - 2rem); }
1574
+ .stepper { flex-direction: column; align-items: stretch; gap: var(--space-4); }
1575
+ .step { flex-direction: row; align-items: center; gap: var(--space-3); text-align: start; min-inline-size: 0; }
1576
+ .step__label { overflow-wrap: anywhere; }
1577
+ .step__connector { display: none; }
1578
+ }
1579
+ @media (prefers-reduced-motion: reduce) {
1580
+ html, html:focus-within { scroll-behavior: auto; }
1581
+ .spinner, .skeleton, .skeleton::after,
1582
+ .progress--indeterminate .progress__bar, .alert.is-leaving, .toast.is-leaving { animation: none; }
1583
+ .progress--indeterminate .progress__bar { transform: none; }
1584
+ .btn, .icon-btn, .card--interactive, .slider::-webkit-slider-thumb { transform: none; }
1585
+ .btn:active:not(:disabled), .icon-btn:active:not(:disabled), .card--interactive:hover { transform: none; }
1586
+ .btn, .icon-btn { transition: background-color 80ms linear, color 80ms linear; }
1587
+ .menu, .popover__panel, .datepicker__calendar, .link::after { transition: none; }
1588
+ .progress__bar, .progress-ring__value, .accordion__content, .accordion__body { transition: none; }
1589
+ }
1590
+ @media (forced-colors: active) {
1591
+ input.switch, .switch__track { border: 1px solid CanvasText; background: Canvas; }
1592
+ input.switch::after, .switch__track::after { background: CanvasText; }
1593
+ input.switch:checked, .switch input:checked ~ .switch__track { border-color: Highlight; }
1594
+ input.switch:checked::after, .switch input:checked ~ .switch__track::after { background: Highlight; }
1595
+ .checkbox__box, .radio__dot { border-color: CanvasText; }
1596
+ }
1597
+
1598
+
1215
1599
  /* ==========================================================================
1216
1600
  ESTILOS DE LA PÁGINA DE DOCUMENTACIÓN (.ds-*)
1217
1601
  ========================================================================== */
@@ -1287,6 +1671,14 @@ html, body { overflow-x: clip; }
1287
1671
  .ds-demo--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); align-items: stretch; }
1288
1672
  .ds-demo:has(table) { overflow-x: auto; }
1289
1673
 
1674
+ /* Demo del sidebar contraíble: rejilla auto 1fr, el contenido sigue al riel sin animar el ancho */
1675
+ .ds-app-demo { display: grid; grid-template-columns: auto 1fr; align-items: stretch; gap: 0; min-height: 400px; padding: 0; }
1676
+ .ds-app-demo > .sidebar { border-radius: var(--radius-lg) 0 0 var(--radius-lg); }
1677
+ .ds-app-demo__main { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3); min-width: 0; padding: var(--space-6); }
1678
+ @media (max-width: 767px) {
1679
+ .ds-app-demo { grid-template-columns: 1fr; min-height: 0; }
1680
+ }
1681
+
1290
1682
  .ds-code { margin: var(--space-2) 0 0; }
1291
1683
  .ds-code summary {
1292
1684
  cursor: pointer; user-select: none; font-size: var(--text-sm);
@@ -1350,7 +1742,7 @@ html, body { overflow-x: clip; }
1350
1742
  .ds-type-row__meta { width: 190px; flex: none; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-muted); }
1351
1743
  .ds-type-row__sample { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1352
1744
 
1353
- .ds-font-card h4 { margin-bottom: 2px; }
1745
+ .ds-font-card h3 { margin-bottom: 2px; }
1354
1746
  .ds-font-card .ds-font-sample { font-size: var(--text-3xl); line-height: 1.2; margin: 6px 0; }
1355
1747
  .ds-font-card p { font-size: var(--text-sm); color: var(--text-muted); margin: 0; max-width: none; }
1356
1748
 
@@ -1388,10 +1780,10 @@ html, body { overflow-x: clip; }
1388
1780
  @media (hover: hover) and (pointer: fine) {
1389
1781
  .ds-ease-chip:hover { transform: translateX(24px); }
1390
1782
  }
1391
- .ds-ease-chip--out { transition: transform 400ms var(--ease-out); }
1392
- .ds-ease-chip--in { transition: transform 400ms var(--ease-in); }
1393
- .ds-ease-chip--inout { transition: transform 400ms var(--ease-in-out); }
1394
- .ds-ease-chip--spring{ transition: transform 400ms var(--ease-spring); }
1783
+ .ds-ease-chip--out { transition: transform var(--duration-page) var(--ease-out); }
1784
+ .ds-ease-chip--in { transition: transform var(--duration-page) var(--ease-in); }
1785
+ .ds-ease-chip--inout { transition: transform var(--duration-page) var(--ease-in-out); }
1786
+ .ds-ease-chip--spring{ transition: transform var(--duration-page) var(--ease-spring); }
1395
1787
 
1396
1788
  .ds-icon-grid { display: flex; flex-wrap: wrap; gap: var(--space-4); }
1397
1789
  .ds-icon-cell {
@@ -1420,9 +1812,118 @@ html, body { overflow-x: clip; }
1420
1812
  .ds-main, .ds-footer { margin-left: 0; }
1421
1813
  .ds-main { padding: var(--space-8) var(--space-4) var(--space-16); }
1422
1814
  }
1815
+ /* ESTILOS COMPARTIDOS GENERADOS */
1816
+ /* La documentación comparte navegación y deja que cada marca determine el ritmo. */
1817
+ .ds-topbar { gap: var(--space-3); }
1818
+ .ds-brand { min-inline-size: 0; }
1819
+ .ds-brand, .ds-brand__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1820
+ .ds-theme-toggle, .ds-hamburger { flex-shrink: 0; }
1821
+ .ds-main { padding-block-start: var(--space-12); }
1822
+ .ds-main > section { scroll-margin-block-start: 88px; }
1823
+ .ds-main > section > h2 { text-wrap: balance; }
1824
+ .ds-main :is(h1, h2, h3, h4, h5, h6) { overflow-wrap: anywhere; }
1825
+ .ds-hero {
1826
+ display: grid;
1827
+ grid-template-columns: minmax(0, 1.45fr) minmax(12rem, .65fr);
1828
+ column-gap: var(--space-12);
1829
+ row-gap: var(--space-6);
1830
+ padding-block-start: var(--space-6);
1831
+ }
1832
+ .ds-hero > h1 {
1833
+ grid-column: 1 / -1;
1834
+ font-size: var(--text-display-fluid);
1835
+ line-height: 1.08;
1836
+ letter-spacing: var(--tracking-display);
1837
+ text-wrap: balance;
1838
+ margin: 0;
1839
+ }
1840
+ .ds-hero > .ds-lead { grid-column: 1; margin: 0; max-inline-size: 55ch; }
1841
+ .ds-hero > .ds-actions { grid-column: 1; margin: 0; display: flex; flex-wrap: wrap; gap: var(--space-3); }
1842
+ .ds-hero__palette {
1843
+ grid-column: 2; grid-row: 2 / 4;
1844
+ display: grid; grid-template-columns: 2fr 1.25fr .75fr;
1845
+ gap: var(--space-1); margin: 0; min-block-size: 150px;
1846
+ align-self: stretch; overflow: hidden; border-radius: var(--radius-lg);
1847
+ }
1848
+ .ds-hero__chip { inline-size: auto; block-size: auto; border: 0; border-radius: 0; }
1849
+ .ds-principles {
1850
+ grid-column: 1 / -1; display: grid;
1851
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1852
+ gap: var(--space-6) var(--space-10); margin-block: var(--space-4) 0;
1853
+ }
1854
+ .ds-principle { padding-block-start: var(--space-4); border-block-start: 1px solid var(--border); }
1855
+ .ds-principle h2 { font-family: var(--font-body); font-size: var(--text-base); line-height: 1.5; margin: 0 0 var(--space-2); }
1856
+ .ds-principle p { font-size: var(--text-sm); color: var(--text-secondary); margin: 0; max-inline-size: 48ch; }
1857
+ .ds-hero > .alert, .ds-hero > .ds-callout { grid-column: 1 / -1; margin: var(--space-3) 0 0; }
1858
+ .ds-callout { border: 1px solid var(--border); border-radius: var(--radius-md); }
1859
+ .ds-recipes { margin: 0; }
1860
+ .ds-recipes > div {
1861
+ display: grid; grid-template-columns: minmax(8rem, .4fr) minmax(0, 1fr);
1862
+ gap: var(--space-4); padding-block: var(--space-5); border-block-start: 1px solid var(--border);
1863
+ }
1864
+ .ds-recipes dt { font-weight: var(--weight-semibold); }
1865
+ .ds-recipes dd { margin: 0; color: var(--text-secondary); max-inline-size: 65ch; }
1866
+ .ds-chart-palette { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-4); }
1867
+ .ds-chart-palette li { display: grid; gap: var(--space-2); }
1868
+ .ds-chart-palette__color { display: block; block-size: 56px; border-radius: var(--radius-sm); }
1869
+ .ds-chart-palette code { font-family: var(--font-mono); font-size: var(--text-sm); }
1870
+ .ds-chart-palette [data-chart-color] { color: var(--text-muted); font-size: var(--text-sm); }
1871
+ .ds-nav a { min-block-size: 36px; display: flex; align-items: center; }
1872
+ .ds-nav__sub a { font-size: var(--text-sm); }
1873
+ .ds-demo { min-inline-size: 0; max-inline-size: 100%; }
1874
+ .ds-demo > * { min-inline-size: 0; max-inline-size: 100%; }
1875
+ .ds-demo :has(> .divider--vertical) { flex-wrap: wrap; row-gap: var(--space-3); }
1876
+ .ds-wordmark-demo { display: inline-block; max-inline-size: 100%; overflow-x: auto; vertical-align: bottom; }
1877
+ .ds-main code:not(.ds-pre code), .ds-main .code, .ds-main .u-mono { overflow-wrap: anywhere; white-space: normal; }
1878
+ .ds-demo .popover__panel { max-inline-size: calc(100vw - var(--space-20)); }
1879
+ .ds-demo--grid, .ds-semantic-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); }
1880
+ .ds-copy { min-block-size: 36px; }
1881
+ .ds-code summary { padding-block: var(--space-3); }
1882
+ .ds-pre { max-inline-size: 100%; }
1883
+ .ds-type-row__sample { min-inline-size: 0; white-space: normal; overflow-wrap: anywhere; }
1884
+ .ds-main table { min-inline-size: 0; }
1885
+ .ds-theme-toggle[data-tooltip]::after { left: auto; right: 0; transform: translateY(4px); }
1886
+ .ds-theme-toggle[data-tooltip]:hover::after, .ds-theme-toggle[data-tooltip]:focus-visible::after { transform: translateY(0); }
1887
+ [data-system="nocturna"] .ds-wordmark-badge { color: var(--night-50); }
1888
+ .ds-main p { overflow-wrap: anywhere; }
1889
+ .ds-type-row { min-inline-size: 0; }
1890
+ [data-system="celeste"] .ds-hero { padding-block-start: var(--space-10); }
1891
+ [data-system="salvia"] .ds-principles { row-gap: var(--space-8); }
1892
+ [data-system="bruma"] .ds-principles { gap: var(--space-4) var(--space-8); }
1893
+ [data-system="nocturna"] .ds-main { padding-block-start: calc(64px + var(--space-10)); }
1894
+ @media (max-width: 1023px) {
1895
+ .ds-main { padding-inline: var(--space-5); }
1896
+ .ds-hero { column-gap: var(--space-6); }
1897
+ .ds-nav a { min-block-size: var(--touch-target); }
1898
+ .ds-sidenav:not(.is-open), .ds-sidebar:not(.is-open) { visibility: hidden; }
1899
+ }
1900
+ @media (max-width: 639px) {
1901
+ .ds-topbar { padding-inline: var(--space-4); }
1902
+ .ds-version { max-inline-size: 44vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1903
+ .ds-main { padding-inline: var(--space-4); padding-block-start: var(--space-8); }
1904
+ .ds-hero { grid-template-columns: minmax(0, 1fr); row-gap: var(--space-5); padding-block-start: var(--space-2); }
1905
+ .ds-hero > h1, .ds-hero > .ds-lead, .ds-hero > .ds-actions { grid-column: 1; }
1906
+ .ds-hero__palette { grid-column: 1; grid-row: auto; min-block-size: 64px; }
1907
+ .ds-principles { grid-template-columns: minmax(0, 1fr); }
1908
+ .ds-demo { padding: var(--space-4); }
1909
+ .ds-type-row { display: grid; gap: var(--space-2); }
1910
+ .ds-type-row__meta { inline-size: auto; }
1911
+ .ds-space-item { min-inline-size: 0; }
1912
+ .ds-space-item .u-mono { inline-size: 96px; flex: none; }
1913
+ .ds-space-bar { max-inline-size: calc(100vw - 164px); }
1914
+ .ds-recipes > div { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
1915
+ .ds-actions > .btn { flex: 1 1 12rem; }
1916
+ .ds-copy { min-block-size: var(--touch-target); }
1917
+ }
1918
+ @media (prefers-reduced-motion: reduce) {
1919
+ .ds-sidenav, .ds-sidebar, .ds-swatch, .ds-ease-chip { transition: none; }
1920
+ .ds-swatch:hover, .ds-ease-chip:hover { transform: none; }
1921
+ }
1922
+
1423
1923
  </style>
1424
1924
  </head>
1425
- <body class="celeste-reset">
1925
+ <body class="celeste-reset" data-system="celeste" data-mode="read">
1926
+ <a class="skip-link" href="#contenido">Saltar al contenido</a>
1426
1927
 
1427
1928
  <!-- Iconos (estilo Lucide, trazo 2px) -->
1428
1929
  <svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
@@ -1481,10 +1982,13 @@ html, body { overflow-x: clip; }
1481
1982
  <nav class="ds-nav" id="dsNav">
1482
1983
  <div class="ds-nav__group">Fundamentos</div>
1483
1984
  <a href="#introduccion">Introducción</a>
1985
+ <a href="#datos">Color para datos</a>
1986
+ <a href="#criterio">Criterio de uso</a>
1484
1987
  <a href="#voz">Voz y contenido</a>
1485
1988
  <a href="#colores">Colores</a>
1486
1989
  <a href="#tipografia">Tipografía</a>
1487
1990
  <a href="#espaciado">Espaciado</a>
1991
+ <a href="#ritmo">Ritmo de contenido</a>
1488
1992
  <a href="#bordes">Bordes, radios y focus</a>
1489
1993
  <a href="#elevacion">Elevación</a>
1490
1994
  <a href="#iconografia">Iconografía</a>
@@ -1537,11 +2041,10 @@ html, body { overflow-x: clip; }
1537
2041
  </nav>
1538
2042
  </aside>
1539
2043
 
1540
- <main class="ds-main">
2044
+ <main class="ds-main" id="contenido" tabindex="-1">
1541
2045
 
1542
2046
  <!-- ============================== INTRODUCCIÓN ============================== -->
1543
2047
  <section id="introduccion" class="ds-hero">
1544
- <p class="ds-eyebrow">Sistema de diseño · Higinio Sosa</p>
1545
2048
  <h1>Celeste Clásica</h1>
1546
2049
  <p class="ds-lead u-measure">
1547
2050
  Sistema de marca <strong>light-first</strong>, sereno y confiable, para todos los productos de
@@ -1554,17 +2057,17 @@ html, body { overflow-x: clip; }
1554
2057
  <div class="ds-hero__chip" style="background:#31343e"></div>
1555
2058
  </div>
1556
2059
 
1557
- <div style="display:flex;gap:16px;flex-wrap:wrap;margin:24px 0;">
2060
+ <div class="ds-actions">
1558
2061
  <button class="btn btn--primary" id="dsHeroStart">Empezar a construir</button>
1559
2062
  <button class="btn btn--outline" id="dsHeroTokens">Ver tokens</button>
1560
2063
  </div>
1561
2064
 
1562
2065
  <div class="ds-principles">
1563
- <div class="card"><span class="ds-num">01</span><h4>Claridad primero</h4><p>El contenido siempre por encima de la decoración.</p></div>
1564
- <div class="card"><span class="ds-num">02</span><h4>Calma visual</h4><p>Superficies claras, whitespace generoso, contraste solo donde importa.</p></div>
1565
- <div class="card"><span class="ds-num">03</span><h4>Un acento a la vez</h4><p>El azul acero guía la acción; nunca compite consigo mismo.</p></div>
1566
- <div class="card"><span class="ds-num">04</span><h4>Consistencia sobre novedad</h4><p>Mismos tokens en todos los productos; cero valores mágicos.</p></div>
1567
- <div class="card"><span class="ds-num">05</span><h4>Movimiento con propósito</h4><p>La animación explica — aparece, cambia, desaparece — nunca decora sola.</p></div>
2066
+ <div class="ds-principle"><h2>Claridad primero</h2><p>El contenido siempre por encima de la decoración.</p></div>
2067
+ <div class="ds-principle"><h2>Calma visual</h2><p>Superficies claras, whitespace generoso, contraste solo donde importa.</p></div>
2068
+ <div class="ds-principle"><h2>Un acento a la vez</h2><p>El azul acero guía la acción; nunca compite consigo mismo.</p></div>
2069
+ <div class="ds-principle"><h2>Consistencia sobre novedad</h2><p>Mismos tokens en todos los productos; cero valores mágicos.</p></div>
2070
+ <div class="ds-principle"><h2>Movimiento con propósito</h2><p>La animación explica — aparece, cambia, desaparece — nunca decora sola.</p></div>
1568
2071
  </div>
1569
2072
 
1570
2073
  <div class="alert alert--info" style="margin-top:24px">
@@ -1577,8 +2080,43 @@ html, body { overflow-x: clip; }
1577
2080
  </section>
1578
2081
 
1579
2082
  <!-- ============================== VOZ Y CONTENIDO ============================== -->
2083
+ <section id="datos">
2084
+ <h2>Color para datos</h2>
2085
+ <p>Serie categórica de orden estable, con pasos propios en cada tema. El color identifica; etiquetas, formas o patrones conservan el significado. Los labels usan tinta, no el color de la serie.</p>
2086
+ <ul class="ds-chart-palette"><li><span class="ds-chart-palette__color" style="background:var(--chart-1)" aria-hidden="true"></span><code>--chart-1</code><span data-chart-color="--chart-1"></span></li><li><span class="ds-chart-palette__color" style="background:var(--chart-2)" aria-hidden="true"></span><code>--chart-2</code><span data-chart-color="--chart-2"></span></li><li><span class="ds-chart-palette__color" style="background:var(--chart-3)" aria-hidden="true"></span><code>--chart-3</code><span data-chart-color="--chart-3"></span></li><li><span class="ds-chart-palette__color" style="background:var(--chart-4)" aria-hidden="true"></span><code>--chart-4</code><span data-chart-color="--chart-4"></span></li><li><span class="ds-chart-palette__color" style="background:var(--chart-5)" aria-hidden="true"></span><code>--chart-5</code><span data-chart-color="--chart-5"></span></li><li><span class="ds-chart-palette__color" style="background:var(--chart-6)" aria-hidden="true"></span><code>--chart-6</code><span data-chart-color="--chart-6"></span></li><li><span class="ds-chart-palette__color" style="background:var(--chart-7)" aria-hidden="true"></span><code>--chart-7</code><span data-chart-color="--chart-7"></span></li></ul>
2087
+ <p class="u-muted">Las pruebas comprueban contraste con la superficie y separación perceptual entre colores adyacentes. Ofrece una tabla equivalente; más de siete series necesitan agrupación u otra composición.</p>
2088
+ </section>
2089
+
2090
+ <section id="ritmo">
2091
+ <h2>Ritmo de contenido</h2>
2092
+ <p>El espacio sigue a la relación: 6px dentro de un campo, 8px entre título y descripción, 16px entre bloques, 20px entre campos y 24px entre tarjetas. Las secciones se separan 64px.</p>
2093
+ <div class="stack stack--cards" data-spacing-example data-mode="operate">
2094
+ <section class="card">
2095
+ <header class="text-group"><h3>Datos de contacto</h3><p>Ejemplo de composición. El título y su descripción forman un grupo.</p></header>
2096
+ <form class="form-stack" onsubmit="return false">
2097
+ <div class="field"><label class="field__label" for="rh-name">Nombre</label><input class="input" id="rh-name" autocomplete="name" placeholder="Tu nombre"><p class="field__help">Escribe el nombre que quieres mostrar.</p></div>
2098
+ <div class="field"><label class="field__label" for="rh-email">Correo</label><input class="input" id="rh-email" type="email" autocomplete="email" placeholder="nombre@ejemplo.com"><p class="field__help">La ayuda puede ocupar varias líneas sin juntarse con el siguiente control.</p></div>
2099
+ <div class="cluster"><button class="btn btn--primary" type="button">Acción de ejemplo</button><button class="btn btn--secondary" type="button">Cancelar</button></div>
2100
+ </form>
2101
+ </section>
2102
+ <section class="card">
2103
+ <header class="text-group"><h3>Contenido relacionado</h3><p>Una tarjeta distinta conserva su propia distancia.</p></header>
2104
+ <div class="stack"><p>Los párrafos y los divs no necesitan una colección de márgenes manuales.</p><div>Este bloque mantiene 16px respecto al párrafo anterior.</div></div>
2105
+ </section>
2106
+ </div>
2107
+ </section>
2108
+
2109
+ <section id="criterio">
2110
+ <h2>Diseñar con Celeste Clásica</h2>
2111
+ <dl class="ds-recipes">
2112
+ <div><dt>Operar</dt><dd>Una tarea clara por región. Títulos en la familia de UI, datos estables y una acción dominante por contexto. Usa <code>data-mode="operate"</code> en formularios, paneles y tablas.</dd></div>
2113
+ <div><dt>Leer</dt><dd>Display para abrir la lectura; cuerpo de 65–75 caracteres por línea. Agrupa lo relacionado y separa las secciones con espacio, sin convertir cada párrafo en una tarjeta.</dd></div>
2114
+ <div><dt>Decidir</dt><dd>La composición sigue a la decisión: contexto, evidencia y acción. Conserva la identidad de marca y evita trasladar la densidad de un dashboard a una landing.</dd></div>
2115
+ <div><dt>Interactuar</dt><dd>Los botones sólidos usan <code>--action-bg</code> y <code>--text-on-action</code>. Foco, hover y error deben seguir siendo reconocibles en ambos temas, con teclado y con movimiento reducido.</dd></div>
2116
+ </dl>
2117
+ </section>
2118
+
1580
2119
  <section id="voz">
1581
- <p class="ds-eyebrow">Fundamentos</p>
1582
2120
  <h2>Voz y contenido</h2>
1583
2121
  <p class="ds-lead">Una consultora seria pero cálida — "banca privada boutique", no "startup ruidosa".</p>
1584
2122
  <div class="ds-demo ds-demo--col">
@@ -1598,7 +2136,6 @@ html, body { overflow-x: clip; }
1598
2136
 
1599
2137
  <!-- ============================== COLORES ============================== -->
1600
2138
  <section id="colores">
1601
- <p class="ds-eyebrow">Fundamentos</p>
1602
2139
  <h2>Colores</h2>
1603
2140
  <p class="ds-lead">Un solo acento — azul acero <span class="u-mono">#7192ba</span> — sobre neutrales grisáceo-azulados derivados de la tinta <span class="u-mono">#31343e</span>. Haz clic en cualquier muestra para copiar su variable.</p>
1604
2141
 
@@ -1616,7 +2153,7 @@ html, body { overflow-x: clip; }
1616
2153
  <p>Los componentes consumen <strong>solo esta capa</strong> — así el cambio de tema es un cambio de atributo.</p>
1617
2154
  <div class="ds-demo ds-demo--col" style="padding:16px">
1618
2155
  <table class="table" id="aliasTable">
1619
- <thead><tr><th style="width:52px"></th><th>Alias</th><th>Valor (light)</th><th>Rol</th></tr></thead>
2156
+ <thead><tr><th style="width:min(100%, 52px)"></th><th>Alias</th><th>Valor (light)</th><th>Rol</th></tr></thead>
1620
2157
  <tbody></tbody>
1621
2158
  </table>
1622
2159
  </div>
@@ -1633,14 +2170,13 @@ html, body { overflow-x: clip; }
1633
2170
  <div class="ds-ratio">
1634
2171
  <div style="flex:6;background:var(--bg);color:var(--text-secondary)">60% neutrales claros</div>
1635
2172
  <div style="flex:3;background:var(--neutral-600);color:#fff">30% tinta y estructura</div>
1636
- <div style="flex:1;background:var(--accent);color:var(--text-on-primary)">10%</div>
2173
+ <div style="flex:1;background:var(--action-bg);color:var(--text-on-action)">10%</div>
1637
2174
  </div>
1638
2175
  <p><strong>Un solo botón primario visible por pantalla</strong>; el resto secundarios o ghost. Fondos siempre planos: sin gradientes, texturas ni patrones. El acento pierde poder si supera el 10&nbsp;%.</p>
1639
2176
  </section>
1640
2177
 
1641
2178
  <!-- ============================== TIPOGRAFÍA ============================== -->
1642
2179
  <section id="tipografia">
1643
- <p class="ds-eyebrow">Fundamentos</p>
1644
2180
  <h2>Tipografía</h2>
1645
2181
  <p class="ds-lead">Tres familias con roles estrictos. Escala 1.25 (tercera mayor) desde 16&nbsp;px.</p>
1646
2182
 
@@ -1669,7 +2205,6 @@ html, body { overflow-x: clip; }
1669
2205
 
1670
2206
  <!-- ============================== ESPACIADO ============================== -->
1671
2207
  <section id="espaciado">
1672
- <p class="ds-eyebrow">Fundamentos</p>
1673
2208
  <h2>Espaciado</h2>
1674
2209
  <p class="ds-lead">Escala base 4&nbsp;px — cero valores mágicos. El número del token = px ÷ 4.</p>
1675
2210
  <div class="ds-space-list" id="spaceScale"></div>
@@ -1691,7 +2226,6 @@ html, body { overflow-x: clip; }
1691
2226
 
1692
2227
  <!-- ============================== BORDES, RADIOS Y FOCUS ============================== -->
1693
2228
  <section id="bordes">
1694
- <p class="ds-eyebrow">Fundamentos</p>
1695
2229
  <h2>Bordes, radios y focus</h2>
1696
2230
  <p class="ds-lead">Bordes de 1&nbsp;px, radios generosos y un focus siempre visible — la geometría que sostiene la calma de la marca.</p>
1697
2231
  <h3>Radios</h3>
@@ -1700,14 +2234,13 @@ html, body { overflow-x: clip; }
1700
2234
  <div class="ds-radius-box" style="border-radius:var(--radius-md)">md · 10</div>
1701
2235
  <div class="ds-radius-box" style="border-radius:var(--radius-lg)">lg · 14</div>
1702
2236
  <div class="ds-radius-box" style="border-radius:var(--radius-xl)">xl · 20</div>
1703
- <div class="ds-radius-box" style="border-radius:var(--radius-full);width:72px">full</div>
2237
+ <div class="ds-radius-box" style="border-radius:var(--radius-full);width:min(100%, 72px)">full</div>
1704
2238
  </div>
1705
2239
  <p>Borde por defecto <span class="u-mono">1px solid var(--border)</span>. Focus ring global de teclado: <span class="u-mono">2px var(--primary-500)</span> con offset 2px vía <span class="u-mono">:focus-visible</span> — el click de ratón no lo dispara. Único uso de transparencia: overlay de modal y ring de focus. <strong>No hay blur ni glassmorphism en Celeste Clásica.</strong></p>
1706
2240
  </section>
1707
2241
 
1708
2242
  <!-- ============================== ELEVACIÓN ============================== -->
1709
2243
  <section id="elevacion">
1710
- <p class="ds-eyebrow">Fundamentos</p>
1711
2244
  <h2>Elevación</h2>
1712
2245
  <p class="ds-lead">Sombras frías teñidas con la tinta <span class="u-mono">rgba(49,52,62,…)</span>, nunca negro puro. Máximo 3 niveles de elevación visibles por pantalla. Sin resplandor de acento: el <span class="u-mono">glow</span> es exclusivo de Nocturna Eléctrica.</p>
1713
2246
  <h3>Niveles</h3>
@@ -1729,7 +2262,6 @@ html, body { overflow-x: clip; }
1729
2262
 
1730
2263
  <!-- ============================== ICONOGRAFÍA ============================== -->
1731
2264
  <section id="iconografia">
1732
- <p class="ds-eyebrow">Fundamentos</p>
1733
2265
  <h2>Iconografía</h2>
1734
2266
  <p class="ds-lead">Set sustituto: <a class="link" href="https://lucide.dev" target="_blank" rel="noopener noreferrer">Lucide</a> — trazo 1.5–2&nbsp;px, esquinas redondeadas, geométrico. Si existe un set propio de marca, reemplazar.</p>
1735
2267
  <div class="ds-icon-grid">
@@ -1749,7 +2281,6 @@ html, body { overflow-x: clip; }
1749
2281
 
1750
2282
  <!-- ============================== MARCA / WORDMARK ============================== -->
1751
2283
  <section id="marca">
1752
- <p class="ds-eyebrow">Fundamentos</p>
1753
2284
  <h2>Marca / wordmark</h2>
1754
2285
  <p class="ds-lead">No hay logo ni imágenes de marca. Donde un mockup necesite logotipo se renderiza <strong>«Higinio»</strong> (o el nombre del producto) en <span class="u-mono">--font-display</span> (Fraunces) — nunca se dibuja un logo aproximado.</p>
1755
2286
  <div class="ds-demo" style="gap:32px">
@@ -1766,7 +2297,6 @@ html, body { overflow-x: clip; }
1766
2297
 
1767
2298
  <!-- ============================== COMPONENTES ============================== -->
1768
2299
  <section id="componentes">
1769
- <p class="ds-eyebrow">Biblioteca</p>
1770
2300
  <h2>Componentes</h2>
1771
2301
  <p class="ds-lead">Los 35 componentes del sistema, renderizados en vivo con las clases del framework <span class="u-mono">celeste-clasica.css</span>. Cada bloque incluye su código de uso.</p>
1772
2302
 
@@ -1824,7 +2354,7 @@ html, body { overflow-x: clip; }
1824
2354
  &lt;svg&gt;…icono 18px…&lt;/svg&gt;
1825
2355
  &lt;/button&gt;
1826
2356
  &lt;!-- Variantes: --ghost (default) · --secondary · --primary
1827
- Tamaños: --sm 32px · (md 40px) · --lg 48px --&gt;</code></pre>
2357
+ Tamaños: --sm 36px · (md 44px) · --lg 48px --&gt;</code></pre>
1828
2358
  </div>
1829
2359
  </details>
1830
2360
  </div>
@@ -1835,8 +2365,8 @@ html, body { overflow-x: clip; }
1835
2365
  <p class="ds-spec">Trazo 2.5, rotación 0.8 s linear. Si la carga dura &lt;300 ms, no mostrar spinner. Blanco dentro de botones primarios.</p>
1836
2366
  <div class="ds-demo">
1837
2367
  <svg class="spinner" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2.5"/><path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
1838
- <svg class="spinner" style="width:24px;height:24px" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2.5"/><path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
1839
- <svg class="spinner" style="width:32px;height:32px;color:var(--text-muted)" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2.5"/><path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
2368
+ <svg class="spinner" style="width:min(100%, 24px);height:24px" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2.5"/><path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
2369
+ <svg class="spinner" style="width:min(100%, 32px);height:32px;color:var(--text-muted)" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2.5"/><path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
1840
2370
  </div>
1841
2371
  <details class="ds-code"><summary>Ver código</summary>
1842
2372
  <div class="ds-code-box"><button class="ds-copy">Copiar</button>
@@ -2297,11 +2827,11 @@ html, body { overflow-x: clip; }
2297
2827
  <div class="ds-demo ds-demo--col" style="max-width:420px">
2298
2828
  <div>
2299
2829
  <div class="progress-row"><span class="progress-label">Almacenamiento</span><span class="progress-value js-progress-value">68%</span></div>
2300
- <div class="progress"><div class="progress__bar js-progress-bar" style="width:68%" role="progressbar" aria-valuenow="68" aria-valuemin="0" aria-valuemax="100"></div></div>
2830
+ <div class="progress"><div class="progress__bar js-progress-bar" style="width:68%" role="progressbar" aria-label="Almacenamiento" aria-valuenow="68" aria-valuemin="0" aria-valuemax="100"></div></div>
2301
2831
  </div>
2302
2832
  <div>
2303
2833
  <div class="progress-row"><span class="progress-label">Tareas completadas</span><span class="progress-value">24/30</span></div>
2304
- <div class="progress"><div class="progress__bar progress__bar--success" style="width:80%" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div></div>
2834
+ <div class="progress"><div class="progress__bar progress__bar--success" style="width:80%" role="progressbar" aria-label="Tareas completadas" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div></div>
2305
2835
  </div>
2306
2836
  <div style="display:flex;align-items:center;gap:24px">
2307
2837
  <div class="progress-ring js-ring" data-value="68">
@@ -2366,7 +2896,7 @@ html, body { overflow-x: clip; }
2366
2896
  <p class="ds-spec">Panel 190 px mín. con <span class="u-mono">shadow-lg</span> · entrada fade + translateY(-4→0) 160 ms · ítem danger con hover <span class="u-mono">danger-100</span>.</p>
2367
2897
  <div class="ds-demo" style="padding-bottom:180px">
2368
2898
  <div class="menu-wrap">
2369
- <button class="btn btn--secondary js-menu-toggle">Acciones<svg style="width:16px;height:16px"><use href="#i-chevron-down"/></svg></button>
2899
+ <button class="btn btn--secondary js-menu-toggle">Acciones<svg style="width:min(100%, 16px);height:16px"><use href="#i-chevron-down"/></svg></button>
2370
2900
  <div class="menu" role="menu">
2371
2901
  <button class="menu__item" role="menuitem"><svg><use href="#i-pencil"/></svg>Editar</button>
2372
2902
  <button class="menu__item" role="menuitem"><svg><use href="#i-copy"/></svg>Duplicar</button>
@@ -2472,13 +3002,13 @@ html, body { overflow-x: clip; }
2472
3002
  <!-- Stat -->
2473
3003
  <div class="ds-component" id="comp-stat">
2474
3004
  <h3>Stat</h3>
2475
- <p class="ds-spec">KPI único, componer dentro de Card. Valor en mono <span class="u-mono">text-3xl</span> · delta con flecha en tonos <span class="u-mono">--*-text</span> · count-up al entrar en viewport.</p>
3005
+ <p class="ds-spec">KPI único, componer dentro de Card. Valor en mono <span class="u-mono">text-3xl</span> · delta con flecha en tonos <span class="u-mono">--*-text</span> · valor final visible desde la carga.</p>
2476
3006
  <div class="grid">
2477
3007
  <div class="card col-12 col-md-4">
2478
3008
  <div class="stat">
2479
3009
  <span class="stat__label"><svg><use href="#i-chart"/></svg>Ingresos del mes</span>
2480
3010
  <div class="stat__row">
2481
- <span class="stat__value js-countup" data-target="128400" data-prefix="$ ">$ 0</span>
3011
+ <span class="stat__value " data-target="128400" data-prefix="$ ">$ 128,400</span>
2482
3012
  <span class="stat__delta stat__delta--up"><svg><use href="#i-trending-up"/></svg>+12.4%</span>
2483
3013
  </div>
2484
3014
  </div>
@@ -2487,7 +3017,7 @@ html, body { overflow-x: clip; }
2487
3017
  <div class="stat">
2488
3018
  <span class="stat__label"><svg><use href="#i-user"/></svg>Clientes activos</span>
2489
3019
  <div class="stat__row">
2490
- <span class="stat__value js-countup" data-target="1284">0</span>
3020
+ <span class="stat__value " data-target="1284">1,284</span>
2491
3021
  <span class="stat__delta stat__delta--down"><svg><use href="#i-trending-down"/></svg>-2.1%</span>
2492
3022
  </div>
2493
3023
  </div>
@@ -2496,7 +3026,7 @@ html, body { overflow-x: clip; }
2496
3026
  <div class="stat">
2497
3027
  <span class="stat__label"><svg><use href="#i-folder"/></svg>Proyectos abiertos</span>
2498
3028
  <div class="stat__row">
2499
- <span class="stat__value js-countup" data-target="37">0</span>
3029
+ <span class="stat__value " data-target="37">37</span>
2500
3030
  <span class="stat__delta"><svg><use href="#i-minus"/></svg>0%</span>
2501
3031
  </div>
2502
3032
  </div>
@@ -2516,6 +3046,30 @@ html, body { overflow-x: clip; }
2516
3046
  &lt;!-- Delta: --up (success-text) · --down (danger-text) · neutro (text-muted) --&gt;</code></pre>
2517
3047
  </div>
2518
3048
  </details>
3049
+
3050
+ <h4 style="margin-top:var(--space-10)">Chip de métrica</h4>
3051
+ <p class="ds-spec">Pastilla de una línea con rótulo y valor en <span class="u-mono">--link</span> · fila con <span class="u-mono">gap</span> que salta de línea en móvil · el chip interactivo es un <span class="u-mono">&lt;button&gt;</span> de 44 px con nombre accesible completo.</p>
3052
+ <div class="ds-demo">
3053
+ <div class="metric-chips">
3054
+ <span class="metric-chip"><span class="metric-chip__label">Pasos</span> <span class="metric-chip__value">6,240</span></span>
3055
+ <span class="metric-chip"><span class="metric-chip__label">Sueño</span> <span class="metric-chip__value">7 h 20 min</span></span>
3056
+ <button class="metric-chip" type="button" id="dsWaterChip" aria-label="Agua: 3 de 8 vasos. Sumar un vaso"><svg aria-hidden="true"><use href="#i-plus"/></svg><span class="metric-chip__label">Agua</span> <span class="metric-chip__value">3 de 8</span></button>
3057
+ </div>
3058
+ </div>
3059
+ <details class="ds-code"><summary>Ver código</summary>
3060
+ <div class="ds-code-box"><button class="ds-copy">Copiar</button>
3061
+ <pre class="ds-pre"><code>&lt;div class="metric-chips"&gt;
3062
+ &lt;span class="metric-chip"&gt;
3063
+ &lt;span class="metric-chip__label"&gt;Pasos&lt;/span&gt; &lt;span class="metric-chip__value"&gt;6,240&lt;/span&gt;
3064
+ &lt;/span&gt;
3065
+ &lt;button class="metric-chip" type="button" aria-label="Agua: 3 de 8 vasos. Sumar un vaso"&gt;
3066
+ &lt;svg aria-hidden="true"&gt;…plus…&lt;/svg&gt;
3067
+ &lt;span class="metric-chip__label"&gt;Agua&lt;/span&gt; &lt;span class="metric-chip__value"&gt;3 de 8&lt;/span&gt;
3068
+ &lt;/button&gt;
3069
+ &lt;/div&gt;
3070
+ &lt;!-- El aria-label se actualiza con el valor; empieza por el texto visible --&gt;</code></pre>
3071
+ </div>
3072
+ </details>
2519
3073
  </div>
2520
3074
 
2521
3075
  <!-- Pagination -->
@@ -2603,6 +3157,51 @@ html, body { overflow-x: clip; }
2603
3157
  &lt;/nav&gt;</code></pre>
2604
3158
  </div>
2605
3159
  </details>
3160
+
3161
+ <h4 style="margin-top:var(--space-10)">Sidebar contraíble</h4>
3162
+ <p class="ds-spec">Expandido 260 px · riel de iconos de 64 px con grupos separados por una línea · en el riel, el nombre aparece como tooltip al pasar el cursor o al enfocar con teclado · el ancho cambia sin transición y solo los rótulos se funden · por debajo de 768 px lo sustituye un Drawer <span class="u-mono">--left</span>.</p>
3163
+ <div class="ds-demo ds-app-demo">
3164
+ <nav class="sidebar sidebar--collapsible" id="demoAppNav" aria-label="Navegación de ejemplo">
3165
+ <div class="sidebar__group" role="group" aria-labelledby="demoAppNavG1">
3166
+ <span class="sidebar__group-label" id="demoAppNavG1">Trabajo</span>
3167
+ <a class="sidebar__item is-active" href="#comp-navegacion" aria-current="page"><svg aria-hidden="true"><use href="#i-home"/></svg><span class="sidebar__label">Inicio</span></a>
3168
+ <a class="sidebar__item" href="#comp-navegacion"><svg aria-hidden="true"><use href="#i-folder"/></svg><span class="sidebar__label">Proyectos</span></a>
3169
+ <a class="sidebar__item" href="#comp-navegacion"><svg aria-hidden="true"><use href="#i-calendar"/></svg><span class="sidebar__label">Calendario</span></a>
3170
+ </div>
3171
+ <div class="sidebar__group" role="group" aria-labelledby="demoAppNavG2">
3172
+ <span class="sidebar__group-label" id="demoAppNavG2">Equipo</span>
3173
+ <a class="sidebar__item" href="#comp-navegacion"><svg aria-hidden="true"><use href="#i-chart"/></svg><span class="sidebar__label">Informes</span></a>
3174
+ <a class="sidebar__item" href="#comp-navegacion"><svg aria-hidden="true"><use href="#i-user"/></svg><span class="sidebar__label">Personas</span></a>
3175
+ <a class="sidebar__item" href="#comp-navegacion"><svg aria-hidden="true"><use href="#i-inbox"/></svg><span class="sidebar__label">Bandeja</span></a>
3176
+ </div>
3177
+ <button class="sidebar__item sidebar__toggle" type="button" aria-expanded="true" aria-controls="demoAppNav"><svg aria-hidden="true"><use href="#i-chevron-left"/></svg><span class="sidebar__label">Contraer</span></button>
3178
+ </nav>
3179
+ <div class="ds-app-demo__main">
3180
+ <button class="icon-btn icon-btn--secondary sidebar-trigger" type="button" data-drawer-open="demoNavDrawer" aria-label="Abrir navegación" aria-expanded="false" aria-controls="demoNavDrawer"><svg aria-hidden="true"><use href="#i-menu"/></svg></button>
3181
+ <h5 style="margin:0">Inicio</h5>
3182
+ <p class="ds-spec" style="margin:0">El contenido ocupa el ancho que deja el sidebar: la rejilla <span class="u-mono">auto 1fr</span> sigue al riel al instante, sin animar el ancho.</p>
3183
+ </div>
3184
+ </div>
3185
+ <details class="ds-code"><summary>Ver código</summary>
3186
+ <div class="ds-code-box"><button class="ds-copy">Copiar</button>
3187
+ <pre class="ds-pre"><code>&lt;nav class="sidebar sidebar--collapsible" id="appNav" aria-label="Principal"&gt;
3188
+ &lt;div class="sidebar__group" role="group" aria-labelledby="navTrabajo"&gt;
3189
+ &lt;span class="sidebar__group-label" id="navTrabajo"&gt;Trabajo&lt;/span&gt;
3190
+ &lt;a class="sidebar__item is-active" href="/" aria-current="page"&gt;
3191
+ &lt;svg aria-hidden="true"&gt;…&lt;/svg&gt;&lt;span class="sidebar__label"&gt;Inicio&lt;/span&gt;
3192
+ &lt;/a&gt;
3193
+ &lt;/div&gt;
3194
+ &lt;button class="sidebar__item sidebar__toggle" type="button"
3195
+ aria-expanded="true" aria-controls="appNav"&gt;
3196
+ &lt;svg aria-hidden="true"&gt;…chevron-left…&lt;/svg&gt;&lt;span class="sidebar__label"&gt;Contraer&lt;/span&gt;
3197
+ &lt;/button&gt;
3198
+ &lt;/nav&gt;
3199
+ &lt;!-- Contraído: .is-collapsed en el nav, aria-expanded="false" y texto «Expandir».
3200
+ Por debajo de 768 px: botón ☰ .sidebar-trigger que abre un .drawer.drawer--left con los mismos ítems. --&gt;
3201
+ &lt;button class="icon-btn sidebar-trigger" type="button" aria-label="Abrir navegación"
3202
+ aria-expanded="false" aria-controls="navDrawer"&gt;…menu…&lt;/button&gt;</code></pre>
3203
+ </div>
3204
+ </details>
2606
3205
  </div>
2607
3206
 
2608
3207
  <!-- Skeleton y EmptyState -->
@@ -2612,7 +3211,7 @@ html, body { overflow-x: clip; }
2612
3211
  <div class="grid">
2613
3212
  <div class="card col-12 col-md-6">
2614
3213
  <div style="display:flex;gap:12px;align-items:center;margin-bottom:16px">
2615
- <div class="skeleton skeleton--circle" style="width:40px;height:40px"></div>
3214
+ <div class="skeleton skeleton--circle" style="width:min(100%, 40px);height:40px"></div>
2616
3215
  <div style="flex:1;display:flex;flex-direction:column;gap:8px">
2617
3216
  <div class="skeleton" style="height:12px;width:60%"></div>
2618
3217
  <div class="skeleton" style="height:10px;width:40%"></div>
@@ -2634,7 +3233,7 @@ html, body { overflow-x: clip; }
2634
3233
  <details class="ds-code"><summary>Ver código</summary>
2635
3234
  <div class="ds-code-box"><button class="ds-copy">Copiar</button>
2636
3235
  <pre class="ds-pre"><code>&lt;div class="skeleton" style="height:10px;width:85%"&gt;&lt;/div&gt;
2637
- &lt;div class="skeleton skeleton--circle" style="width:40px;height:40px"&gt;&lt;/div&gt;
3236
+ &lt;div class="skeleton skeleton--circle" style="width:min(100%, 40px);height:40px"&gt;&lt;/div&gt;
2638
3237
 
2639
3238
  &lt;div class="empty-state"&gt;
2640
3239
  &lt;svg&gt;…icono 40px…&lt;/svg&gt;
@@ -2804,7 +3403,7 @@ html, body { overflow-x: clip; }
2804
3403
  <label class="field__label" for="dpInput">Fecha de entrega</label>
2805
3404
  <div class="datepicker" id="demoDatepicker">
2806
3405
  <input class="datepicker__input" id="dpInput" type="text" readonly autocomplete="off"
2807
- aria-haspopup="dialog" aria-expanded="false" aria-controls="dpCal" placeholder="Elige una fecha">
3406
+ aria-haspopup="dialog" aria-controls="dpCal" placeholder="Elige una fecha">
2808
3407
  <svg class="datepicker__icon" aria-hidden="true"><use href="#i-calendar"/></svg>
2809
3408
  <div class="datepicker__calendar" id="dpCal" role="dialog" aria-label="Elegir fecha" tabindex="-1">
2810
3409
  <div class="calendar">
@@ -2813,6 +3412,7 @@ html, body { overflow-x: clip; }
2813
3412
  <span class="calendar__title" aria-live="polite">—</span>
2814
3413
  <button class="icon-btn icon-btn--ghost icon-btn--sm" type="button" data-cal-next aria-label="Mes siguiente"><svg><use href="#i-chevron-right"/></svg></button>
2815
3414
  </div>
3415
+ <div class="calendar__table" role="grid" aria-label="Calendario">
2816
3416
  <div class="calendar__weekdays" role="row">
2817
3417
  <span class="calendar__weekday" role="columnheader" aria-label="lunes">lu</span>
2818
3418
  <span class="calendar__weekday" role="columnheader" aria-label="martes">ma</span>
@@ -2822,7 +3422,8 @@ html, body { overflow-x: clip; }
2822
3422
  <span class="calendar__weekday" role="columnheader" aria-label="sábado">sá</span>
2823
3423
  <span class="calendar__weekday" role="columnheader" aria-label="domingo">do</span>
2824
3424
  </div>
2825
- <div class="calendar__grid" id="dpGrid" role="grid" aria-label="Calendario"></div>
3425
+ <div class="calendar__grid" id="dpGrid" role="rowgroup"></div>
3426
+ </div>
2826
3427
  </div>
2827
3428
  </div>
2828
3429
  </div>
@@ -2832,7 +3433,7 @@ html, body { overflow-x: clip; }
2832
3433
  <div class="ds-code-box"><button class="ds-copy">Copiar</button>
2833
3434
  <pre class="ds-pre"><code>&lt;div class="datepicker"&gt;
2834
3435
  &lt;input class="datepicker__input" type="text" readonly
2835
- aria-haspopup="dialog" aria-expanded="false" aria-controls="cal"&gt;
3436
+ aria-haspopup="dialog" aria-controls="cal"&gt;
2836
3437
  &lt;svg class="datepicker__icon" aria-hidden="true"&gt;…calendario…&lt;/svg&gt;
2837
3438
  &lt;div class="datepicker__calendar" id="cal" role="dialog"
2838
3439
  aria-label="Elegir fecha" tabindex="-1"&gt;
@@ -2842,17 +3443,21 @@ html, body { overflow-x: clip; }
2842
3443
  &lt;span class="calendar__title"&gt;julio 2026&lt;/span&gt;
2843
3444
  &lt;button aria-label="Mes siguiente"&gt;…&lt;/button&gt;
2844
3445
  &lt;/div&gt;
3446
+ &lt;div class="calendar__table" role="grid" aria-label="julio 2026"&gt;
2845
3447
  &lt;div class="calendar__weekdays" role="row"&gt;
2846
3448
  &lt;span class="calendar__weekday" role="columnheader"
2847
3449
  aria-label="lunes"&gt;lu&lt;/span&gt; …
2848
3450
  &lt;/div&gt;
2849
- &lt;div class="calendar__grid" role="grid" aria-label="julio 2026"&gt;
3451
+ &lt;div class="calendar__grid" role="rowgroup"&gt;
3452
+ &lt;div class="calendar__week" role="row"&gt;
2850
3453
  &lt;button class="calendar__day calendar__day--today" role="gridcell"
2851
3454
  aria-label="viernes, 10 de julio de 2026"
2852
3455
  aria-selected="false"&gt;10&lt;/button&gt;
2853
3456
  &lt;button class="calendar__day calendar__day--selected" role="gridcell"
2854
3457
  aria-label="viernes, 17 de julio de 2026"
2855
3458
  aria-selected="true"&gt;17&lt;/button&gt; …
3459
+ &lt;/div&gt;
3460
+ &lt;/div&gt;
2856
3461
  &lt;/div&gt;
2857
3462
  &lt;/div&gt;
2858
3463
  &lt;/div&gt;
@@ -3034,7 +3639,6 @@ html, body { overflow-x: clip; }
3034
3639
 
3035
3640
  <!-- ============================== MOTION ============================== -->
3036
3641
  <section id="motion">
3037
- <p class="ds-eyebrow">Sistema</p>
3038
3642
  <h2>Motion</h2>
3039
3643
  <p class="ds-lead">Solo <span class="u-mono">transform</span> y <span class="u-mono">opacity</span>. La salida siempre ~20 % más corta que la entrada. Distancias de 4–8 px: nada "vuela" por la pantalla.</p>
3040
3644
  <div class="ds-demo ds-demo--col" style="padding:16px">
@@ -3059,13 +3663,12 @@ html, body { overflow-x: clip; }
3059
3663
  <ul style="max-width:var(--measure)">
3060
3664
  <li>Stagger en listas: 30 ms por ítem, máximo 6 ítems.</li>
3061
3665
  <li>Si la carga dura &lt;300 ms, no mostrar spinner (evita parpadeo).</li>
3062
- <li><span class="u-mono">prefers-reduced-motion</span> colapsa toda animación a 0.01 ms.</li>
3666
+ <li><span class="u-mono">prefers-reduced-motion</span> conserva estados finales, evita desplazamientos y detiene la carga animada.</li>
3063
3667
  </ul>
3064
3668
  </section>
3065
3669
 
3066
3670
  <!-- ============================== ACCESIBILIDAD ============================== -->
3067
3671
  <section id="accesibilidad">
3068
- <p class="ds-eyebrow">Sistema</p>
3069
3672
  <h2>Accesibilidad</h2>
3070
3673
  <p class="ds-lead">WCAG AA mínimo: 4.5:1 texto normal, 3:1 texto grande y componentes UI. Touch targets de 44×44 px. Estado nunca comunicado solo con color.</p>
3071
3674
  <div class="ds-demo ds-demo--col" style="padding:16px">
@@ -3089,73 +3692,208 @@ html, body { overflow-x: clip; }
3089
3692
 
3090
3693
  <!-- ============================== TOKENS Y DESCARGA ============================== -->
3091
3694
  <section id="tokens">
3092
- <p class="ds-eyebrow">Sistema</p>
3093
3695
  <h2>Tokens y descarga</h2>
3094
3696
  <p class="ds-lead">El sistema completo vive en <span class="u-mono">dist/celeste-clasica/</span>: <span class="u-mono">celeste-clasica.css</span> (framework completo), <span class="u-mono">tokens.css</span> (solo variables), <span class="u-mono">tailwind.css</span> (@theme para Tailwind v4) y <span class="u-mono">tokens.json</span> (W3C DTCG 2025.10, interoperable con Figma / Tokens Studio / Style Dictionary).</p>
3095
3697
 
3096
3698
  <h3>Variables CSS</h3>
3097
3699
  <div class="ds-code-box"><button class="ds-copy">Copiar</button>
3098
- <pre class="ds-pre"><code>:root {
3099
- /* Primario */
3700
+ <pre class="ds-pre"><code>/* ============================================================
3701
+ Celeste Clásica — Design Tokens v2.0
3702
+ Sistema de diseño de Higinio Sosa · light-first
3703
+ Fuente de verdad: DESIGN-celeste-clasica.md
3704
+ Uso: importar ANTES de cualquier estilo propio.
3705
+ Los componentes consumen SOLO aliases semánticos, nunca hex.
3706
+ ============================================================ */
3707
+
3708
+ @import url("https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,600;0,9..144,700;1,9..144,600&amp;family=Public+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&amp;family=IBM+Plex+Mono:wght@400;500&amp;display=swap");
3709
+
3710
+ :root {
3711
+ /* == Primitivos: primario (Azul Acero) == */
3100
3712
  --primary-50: #f2f6fa; --primary-100: #dfe9f2; --primary-200: #c3d5e6;
3101
3713
  --primary-300: #a0c0d4; --primary-400: #87a8c7; --primary-500: #7192ba;
3102
3714
  --primary-600: #5d7ba3; --primary-700: #4d6588; --primary-800: #40536e;
3103
3715
  --primary-900: #31343e;
3104
3716
 
3105
- /* Neutrales */
3717
+ /* == Primitivos: neutrales (Gris Azulado) == */
3106
3718
  --neutral-0: #ffffff; --neutral-50: #f7f8fa; --neutral-100: #eef0f3;
3107
3719
  --neutral-200: #dcdfe5; --neutral-300: #b8bdc7; --neutral-400: #8a8f9c;
3108
3720
  --neutral-500: #666b78; --neutral-600: #4d5160; --neutral-700: #3d414e;
3109
3721
  --neutral-800: #31343e; --neutral-900: #22242b;
3110
3722
 
3111
- /* Semánticos (ramp de 3) */
3723
+ /* == Primitivos: semánticos (ramp de 3: sólido / fondo / texto AA) == */
3112
3724
  --success-500: #3d9970; --success-100: #e2f3ec; --success-text: #2b6e50;
3113
3725
  --warning-500: #c9922e; --warning-100: #faf1de; --warning-text: #8a651c;
3114
3726
  --danger-500: #c04d4d; --danger-100: #f9e7e7; --danger-text: #8f3232;
3115
3727
  --info-500: #7192ba; --info-100: #dfe9f2; --info-text: #4d6588;
3116
3728
 
3117
- /* Aliases (los componentes consumen SOLO esto) */
3118
- --bg: var(--neutral-50); --surface: var(--neutral-0); --surface-alt: var(--neutral-100);
3119
- --border: var(--neutral-200); --border-strong: var(--neutral-300);
3120
- --text-primary: var(--neutral-800); --text-secondary: var(--neutral-600);
3121
- --text-muted: var(--neutral-500); --text-disabled: var(--neutral-400);
3122
- --text-on-primary: #ffffff; --text-on-primary-solid: var(--primary-700);
3123
- --accent: var(--primary-500); --accent-hover: var(--primary-600);
3124
- --accent-active: var(--primary-700); --accent-soft: var(--primary-50);
3125
- --accent-soft-strong: var(--primary-100); --link: var(--primary-700);
3126
-
3127
- /* Tipografía */
3729
+ /* == Aliases semánticos (los componentes consumen SOLO esto) == */
3730
+ --bg: var(--neutral-50);
3731
+ --surface: var(--neutral-0);
3732
+ --surface-alt: var(--neutral-100);
3733
+ --border: var(--neutral-200);
3734
+ --border-strong: var(--neutral-300);
3735
+ --text-primary: var(--neutral-800);
3736
+ --text-secondary: var(--neutral-600);
3737
+ --text-muted: var(--neutral-500);
3738
+ --text-disabled: var(--neutral-400);
3739
+ --text-on-primary: #ffffff;
3740
+ --text-on-primary-solid: var(--primary-700);
3741
+ --accent: var(--primary-500);
3742
+ --accent-hover: var(--primary-600);
3743
+ --accent-active: var(--primary-700);
3744
+ --accent-soft: var(--primary-50);
3745
+ --accent-soft-strong: var(--primary-100);
3746
+ --link: var(--primary-700); /* AA ~6:1 — texto de links y ghost; primary-500 solo para UI */
3747
+
3748
+ /* == Tipografía == */
3128
3749
  --font-display: "Fraunces", Georgia, serif;
3129
3750
  --font-body: "Public Sans", system-ui, -apple-system, sans-serif;
3130
3751
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;
3131
3752
  --text-xs: .75rem; --text-sm: .875rem; --text-base: 1rem; --text-lg: 1.125rem;
3132
3753
  --text-xl: 1.25rem; --text-2xl: 1.5625rem; --text-3xl: 1.9375rem;
3133
3754
  --text-4xl: 2.4375rem; --text-5xl: 3.0625rem;
3134
- --tracking-display: -0.02em; --tracking-label: 0.04em; --measure: 65ch;
3755
+ --leading-xs: 1.5; --leading-sm: 1.5; --leading-base: 1.6; --leading-lg: 1.55;
3756
+ --leading-xl: 1.4; --leading-2xl: 1.3; --leading-3xl: 1.25;
3757
+ --leading-4xl: 1.15; --leading-5xl: 1.1;
3758
+ --tracking-display: -0.02em; --tracking-body: 0; --tracking-label: 0.04em;
3759
+ --weight-regular: 400; --weight-medium: 500; --weight-semibold: 600; --weight-bold: 700;
3760
+ --measure: 65ch;
3135
3761
 
3136
- /* Espaciado (base 4px) */
3762
+ /* == Espaciado (escala base 4px) == */
3137
3763
  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px;
3138
3764
  --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px;
3139
3765
  --space-12: 48px; --space-16: 64px; --space-20: 80px; --space-24: 96px;
3766
+ --card-padding: var(--space-6); --card-padding-mobile: var(--space-4);
3767
+ --section-gap: var(--space-16); --form-gap: var(--space-5);
3768
+ --button-padding-md: 12px 20px; --button-padding-sm: 8px 14px;
3140
3769
  --control-height-sm: 36px; --control-height-md: 44px; --control-height-lg: 52px;
3770
+ --gutter: 24px; --gutter-mobile: 16px;
3141
3771
 
3142
- /* Radios y sombras */
3772
+ /* == Radios, bordes y sombras (teñidas de tinta, nunca negro puro) == */
3143
3773
  --radius-sm: 6px; --radius-md: 10px; --radius-lg: 14px;
3144
3774
  --radius-xl: 20px; --radius-full: 9999px;
3775
+ --border-width: 1px;
3145
3776
  --shadow-xs: 0 1px 2px rgba(49,52,62,.06);
3146
3777
  --shadow-sm: 0 1px 3px rgba(49,52,62,.08), 0 1px 2px rgba(49,52,62,.05);
3147
3778
  --shadow-md: 0 4px 8px rgba(49,52,62,.09), 0 2px 4px rgba(49,52,62,.05);
3148
3779
  --shadow-lg: 0 12px 24px rgba(49,52,62,.12), 0 4px 8px rgba(49,52,62,.06);
3149
3780
  --shadow-xl: 0 24px 48px rgba(49,52,62,.16);
3781
+
3782
+ /* == Focus == */
3150
3783
  --ring: 0 0 0 3px rgba(113,146,186,.25);
3784
+ --ring-color: rgba(113,146,186,.25);
3151
3785
 
3152
- /* Motion */
3786
+ /* == Motion == */
3153
3787
  --duration-instant: 100ms; --duration-fast: 150ms; --duration-base: 200ms;
3154
3788
  --duration-slow: 250ms; --duration-page: 320ms;
3155
3789
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
3156
3790
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
3157
- --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
3791
+ --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* moverse de A a B en pantalla */
3158
3792
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
3793
+ --stagger-step: 30ms;
3794
+
3795
+ color-scheme: light;
3796
+
3797
+ --overlay: rgba(49, 52, 62, .5);
3798
+ --tooltip-bg: var(--neutral-900);
3799
+ --tooltip-text: #ffffff;
3800
+ --ring-danger: 0 0 0 3px rgba(192,77,77,.22);
3801
+
3802
+ --action-bg: var(--primary-700);
3803
+ --action-hover: var(--primary-800);
3804
+ --action-active: var(--primary-900);
3805
+ --text-on-action: var(--text-on-primary);
3806
+ --control-border: var(--neutral-400);
3807
+ --placeholder: var(--text-muted);
3808
+ --selection-bg: var(--action-bg);
3809
+ --selection-text: var(--text-on-action);
3810
+ --font-ui-heading: var(--font-body);
3811
+ --text-ui-title: 2rem;
3812
+ --touch-target: 44px;
3813
+ --focus-color: var(--accent);
3814
+ --text-display-fluid: clamp(2.75rem, 1.7rem + 3vw, 4.75rem);
3815
+ --border-hover: var(--neutral-500);
3816
+ --text-on-danger: #ffffff;
3817
+
3818
+ /* Series categóricas: orden estable; acompañar con etiqueta o forma. */
3819
+ --chart-1: #4a8cc1;
3820
+ --chart-2: #853f63;
3821
+ --chart-3: #129a92;
3822
+ --chart-4: #8e4031;
3823
+ --chart-5: #5b975e;
3824
+ --chart-6: #8a600a;
3825
+ --chart-7: #857bc1;
3826
+ /* Ritmo de contenido y formularios. */
3827
+ --field-gap: 6px;
3828
+ --heading-gap: var(--space-2);
3829
+ --content-gap: var(--space-4);
3830
+ --card-content-gap: var(--space-4);
3831
+ --card-group-gap: var(--space-6);
3832
+
3833
+ }
3834
+
3835
+ /* == Modo oscuro (variante) — alternar data-theme en &lt;html&gt; == */
3836
+ [data-theme="dark"] {
3837
+ --bg: #16181e;
3838
+ --surface: #1d2028;
3839
+ --surface-alt: #252935;
3840
+ --border: #3a3f4d;
3841
+ --border-strong: #4a5060;
3842
+ --text-primary: #e6e9ee;
3843
+ --text-secondary: #b9bfcb;
3844
+ --text-muted: #8b91a0;
3845
+ --text-disabled: #6f7585;
3846
+ --accent: #87a8c7;
3847
+ --accent-hover: #a0c0d4;
3848
+ --accent-active: #7192ba;
3849
+ --accent-soft: rgba(113,146,186,.12);
3850
+ --accent-soft-strong: rgba(113,146,186,.18);
3851
+ --text-on-primary: #16181e;
3852
+ --link: #a0c0d4;
3853
+ --success-500: #5cbf93; --success-100: #12291f; --success-text: #7fd4ae;
3854
+ --warning-500: #e0ad4d; --warning-100: #2b2210; --warning-text: #ecc57e;
3855
+ --danger-500: #e07777; --danger-100: #2e1515; --danger-text: #eb9e9e;
3856
+ --info-500: #87a8c7; --info-100: #1a2430; --info-text: #a0c0d4;
3857
+ --ring: 0 0 0 3px rgba(160,192,212,.3);
3858
+ --ring-color: rgba(160,192,212,.3);
3859
+ --shadow-sm: 0 1px 2px rgba(0,0,0,.35);
3860
+ --shadow-md: 0 4px 12px rgba(0,0,0,.45);
3861
+ --shadow-lg: 0 12px 32px rgba(0,0,0,.55);
3862
+ color-scheme: dark;
3863
+
3864
+ --text-on-primary-solid: #a0c0d4;
3865
+ --overlay: rgba(0, 0, 0, .6);
3866
+ --tooltip-bg: #e6e9ee;
3867
+ --tooltip-text: #31343e;
3868
+ --ring-danger: 0 0 0 3px rgba(224,119,119,.25);
3869
+
3870
+ --selection-bg: var(--action-bg);
3871
+ --selection-text: var(--text-on-action);
3872
+ --action-bg: var(--accent);
3873
+ --action-hover: var(--accent-hover);
3874
+ --action-active: var(--accent-active);
3875
+ --control-border: var(--text-muted);
3876
+ --border-hover: var(--text-secondary);
3877
+ --text-on-danger: #16181e;
3878
+ --focus-color: var(--accent);
3879
+
3880
+ /* Series categóricas: orden estable; acompañar con etiqueta o forma. */
3881
+ --chart-1: #5798ce;
3882
+ --chart-2: #a0587c;
3883
+ --chart-3: #2aa79e;
3884
+ --chart-4: #aa5849;
3885
+ --chart-5: #67a369;
3886
+ --chart-6: #946a1c;
3887
+ --chart-7: #877cc3;
3888
+ }
3889
+
3890
+ /* == Accesibilidad del movimiento == */
3891
+
3892
+ @media (prefers-reduced-motion: reduce) {
3893
+ :root {
3894
+ --duration-instant: 0ms; --duration-fast: 0ms; --duration-base: 0ms;
3895
+ --duration-slow: 0ms; --duration-page: 0ms; --stagger-step: 0ms;
3896
+ }
3159
3897
  }</code></pre>
3160
3898
  </div>
3161
3899
 
@@ -3216,12 +3954,11 @@ html, body { overflow-x: clip; }
3216
3954
 
3217
3955
  <!-- ============================== INSTALACIÓN ============================== -->
3218
3956
  <section id="instalacion">
3219
- <p class="ds-eyebrow">Sistema</p>
3220
3957
  <h2>Instalación</h2>
3221
3958
  <p class="ds-lead">El sistema se distribuye como paquete npm (<span class="u-mono">@higinio/design-css</span>) y también como archivos sueltos en <span class="u-mono">dist/celeste-clasica/</span>. Instálalo o cópialos, y elige qué importar según tu stack (A/B/C). En todas: <strong>light por defecto</strong>; <span class="u-mono">&lt;html data-theme="dark"&gt;</span> activa el modo oscuro, y los componentes consumen <strong>solo aliases semánticos</strong>, nunca hex.</p>
3222
3959
 
3223
3960
  <h3>Instalar desde npm (recomendado)</h3>
3224
- <p>Un solo paquete con los dos sistemas en subrutas <span class="u-mono">/celeste/</span> y <span class="u-mono">/nocturna/</span>. Instálalo y usa las vías A/B/C apuntando a la subruta, en vez de a un archivo copiado.</p>
3961
+ <p>Un solo paquete con todos los sistemas, cada uno en su subruta; Celeste Clásica vive en <span class="u-mono">/celeste/</span>. Instálalo y usa las vías A/B/C apuntando a la subruta, en vez de a un archivo copiado.</p>
3225
3962
  <div class="ds-code-box"><button class="ds-copy">Copiar</button>
3226
3963
  <pre class="ds-pre"><code>npm i @higinio/design-css
3227
3964
 
@@ -3281,22 +4018,24 @@ npm i tailwindcss @tailwindcss/postcss
3281
4018
 
3282
4019
  <!-- ============================== MCP ============================== -->
3283
4020
  <section id="mcp">
3284
- <p class="ds-eyebrow">Sistema</p>
3285
4021
  <h2>MCP</h2>
3286
- <p class="ds-lead">El paquete <span class="code">@higinio/design-mcp</span> expone ambos sistemas de diseño — Celeste Clásica y Nocturna Eléctrica — a cualquier IDE o cliente compatible con Model Context Protocol: tokens, componentes, reglas duras y validación de código. Complementa a las skills de Claude, no las reemplaza: misma fuente de verdad, accesible también fuera del entorno de Claude.</p>
4022
+ <p class="ds-lead">El paquete <span class="code">@higinio/design-mcp</span> expone los sistemas de diseño de Higinio Sosa, entre ellos Celeste Clásica (<span class="u-mono">system="celeste"</span>), a cualquier IDE o cliente compatible con Model Context Protocol: tokens, componentes, reglas duras y validación de código. Complementa a las skills de Claude, no las reemplaza: misma fuente de verdad, accesible también fuera del entorno de Claude.</p>
3287
4023
 
3288
- <h3>Las 8 tools</h3>
4024
+ <h3>Las 11 tools</h3>
3289
4025
  <div class="ds-demo ds-demo--col" style="padding:0;overflow-x:auto">
3290
4026
  <table class="table">
3291
4027
  <thead><tr><th>Tool</th><th>Qué hace</th></tr></thead>
3292
4028
  <tbody>
3293
4029
  <tr><td class="u-mono">list_design_systems</td><td>Lista los sistemas de diseño disponibles.</td></tr>
3294
- <tr><td class="u-mono">get_token</td><td>Devuelve el valor resuelto de un token o alias de un sistema.</td></tr>
4030
+ <tr><td class="u-mono">get_token</td><td>Resuelve un alias semántico o un token DTCG por path a su valor.</td></tr>
3295
4031
  <tr><td class="u-mono">search_tokens</td><td>Busca tokens por nombre, categoría o valor.</td></tr>
3296
4032
  <tr><td class="u-mono">list_components</td><td>Lista los componentes documentados de un sistema.</td></tr>
3297
4033
  <tr><td class="u-mono">get_component_spec</td><td>Devuelve la especificación completa de un componente: estados, medidas, variantes.</td></tr>
3298
4034
  <tr><td class="u-mono">get_rules</td><td>Devuelve las reglas duras (no negociables) del sistema.</td></tr>
3299
4035
  <tr><td class="u-mono">validate_code</td><td>Audita CSS/HTML contra las reglas del sistema — hex directos, contraste, aliases.</td></tr>
4036
+ <tr><td class="u-mono">validate_motion</td><td>Audita el motion contra las reglas del sistema: <span class="u-mono">transition</span> sobre todas las propiedades, ease-in en entradas, propiedades de layout, duraciones por encima de <span class="u-mono">--duration-page</span>.</td></tr>
4037
+ <tr><td class="u-mono">check_slop</td><td>Detecta clichés de diseño generado por IA: gradientes, glassmorphism, sombras de negro puro, fuentes genéricas y copy de marketing.</td></tr>
4038
+ <tr><td class="u-mono">checklist_preflight</td><td>Checklist estático de pre-producción sobre HTML/CSS: metadatos, headings, imágenes y accesibilidad de controles.</td></tr>
3300
4039
  <tr><td class="u-mono">generate_snippet</td><td>Genera un snippet de componente listo para usar, con los aliases correctos.</td></tr>
3301
4040
  </tbody>
3302
4041
  </table>
@@ -3390,6 +4129,31 @@ npm i tailwindcss @tailwindcss/postcss
3390
4129
  </div>
3391
4130
  </div>
3392
4131
 
4132
+ <!-- Navegación móvil del Sidebar contraíble (§10.24): Drawer --left -->
4133
+ <div class="drawer drawer--left" id="demoNavDrawer" role="dialog" aria-modal="true" aria-labelledby="demoNavDrawerTitle">
4134
+ <div class="drawer__overlay" data-drawer-close></div>
4135
+ <div class="drawer__panel" tabindex="-1">
4136
+ <div class="drawer__header">
4137
+ <h4 class="drawer__title" id="demoNavDrawerTitle">Navegación</h4>
4138
+ <button class="icon-btn icon-btn--ghost" data-drawer-close aria-label="Cerrar"><svg><use href="#i-x"/></svg></button>
4139
+ </div>
4140
+ <nav class="drawer__body" aria-label="Navegación de ejemplo">
4141
+ <div class="sidebar__group" role="group" aria-labelledby="demoNavDrawerG1">
4142
+ <span class="sidebar__group-label" id="demoNavDrawerG1">Trabajo</span>
4143
+ <a class="sidebar__item is-active" href="#comp-navegacion" aria-current="page" data-drawer-close><svg aria-hidden="true"><use href="#i-home"/></svg><span class="sidebar__label">Inicio</span></a>
4144
+ <a class="sidebar__item" href="#comp-navegacion" data-drawer-close><svg aria-hidden="true"><use href="#i-folder"/></svg><span class="sidebar__label">Proyectos</span></a>
4145
+ <a class="sidebar__item" href="#comp-navegacion" data-drawer-close><svg aria-hidden="true"><use href="#i-calendar"/></svg><span class="sidebar__label">Calendario</span></a>
4146
+ </div>
4147
+ <div class="sidebar__group" role="group" aria-labelledby="demoNavDrawerG2">
4148
+ <span class="sidebar__group-label" id="demoNavDrawerG2">Equipo</span>
4149
+ <a class="sidebar__item" href="#comp-navegacion" data-drawer-close><svg aria-hidden="true"><use href="#i-chart"/></svg><span class="sidebar__label">Informes</span></a>
4150
+ <a class="sidebar__item" href="#comp-navegacion" data-drawer-close><svg aria-hidden="true"><use href="#i-user"/></svg><span class="sidebar__label">Personas</span></a>
4151
+ <a class="sidebar__item" href="#comp-navegacion" data-drawer-close><svg aria-hidden="true"><use href="#i-inbox"/></svg><span class="sidebar__label">Bandeja</span></a>
4152
+ </div>
4153
+ </nav>
4154
+ </div>
4155
+ </div>
4156
+
3393
4157
  <!-- Drawer de demostración (§10.29) -->
3394
4158
  <div class="drawer drawer--right" id="demoDrawer" role="dialog" aria-modal="true" aria-labelledby="demoDrawerTitle">
3395
4159
  <div class="drawer__overlay" data-drawer-close></div>
@@ -3454,24 +4218,6 @@ npm i tailwindcss @tailwindcss/postcss
3454
4218
  if (heroStart) heroStart.addEventListener("click", function () { scrollToId("componentes"); });
3455
4219
  if (heroTokens) heroTokens.addEventListener("click", function () { scrollToId("tokens"); });
3456
4220
 
3457
- /* ---------- Menú móvil ---------- */
3458
- var burger = d.getElementById("dsHamburger");
3459
- var sidenav = d.getElementById("dsSidenav");
3460
- if (burger && sidenav) {
3461
- burger.addEventListener("click", function (e) {
3462
- e.stopPropagation();
3463
- sidenav.classList.toggle("is-open");
3464
- });
3465
- d.addEventListener("click", function (e) {
3466
- if (sidenav.classList.contains("is-open") && !sidenav.contains(e.target) && e.target !== burger) {
3467
- sidenav.classList.remove("is-open");
3468
- }
3469
- });
3470
- sidenav.addEventListener("click", function (e) {
3471
- if (e.target.closest("a")) sidenav.classList.remove("is-open");
3472
- });
3473
- }
3474
-
3475
4221
  /* ---------- Copiar al portapapeles ---------- */
3476
4222
  function copyText(text, feedbackFn) {
3477
4223
  function done() { if (feedbackFn) feedbackFn(); }
@@ -3495,7 +4241,7 @@ npm i tailwindcss @tailwindcss/postcss
3495
4241
  if (!code) return;
3496
4242
  copyText(code.textContent, function () {
3497
4243
  var prev = btn.textContent;
3498
- btn.textContent = "¡Copiado!";
4244
+ btn.textContent = "Copiado";
3499
4245
  setTimeout(function () { btn.textContent = prev; }, 1600);
3500
4246
  });
3501
4247
  });
@@ -3547,10 +4293,10 @@ npm i tailwindcss @tailwindcss/postcss
3547
4293
  ["--text-primary", "neutral-800", "Texto principal"],
3548
4294
  ["--text-secondary", "neutral-600", "Texto secundario"],
3549
4295
  ["--text-muted", "neutral-500", "Terciario, captions"],
3550
- ["--text-disabled", "neutral-400", "Deshabilitado, placeholder"],
3551
- ["--text-on-primary", "#ffffff", "Texto sobre botón primario"],
3552
- ["--accent", "primary-500", "Rellenos de acción, bordes activos, focus"],
3553
- ["--accent-hover", "primary-600", "Hover del primario"],
4296
+ ["--text-disabled", "neutral-400", "Solo controles deshabilitados"],
4297
+ ["--text-on-primary", "#ffffff", "Texto de compatibilidad sobre acento"],
4298
+ ["--accent", "primary-500", "Firma de marca y acentos de UI"],
4299
+ ["--accent-hover", "primary-600", "Hover del acento de marca"],
3554
4300
  ["--accent-active", "primary-700", "Pressed"],
3555
4301
  ["--accent-soft", "primary-50", "Hover de ghost, fondos sutiles"],
3556
4302
  ["--accent-soft-strong", "primary-100", "Seleccionados suaves"],
@@ -3598,7 +4344,7 @@ npm i tailwindcss @tailwindcss/postcss
3598
4344
  var tr = d.createElement("tr");
3599
4345
  var chipColor = a[1].indexOf("#") === 0 ? a[1] : "var(--" + a[1] + ")";
3600
4346
  tr.innerHTML =
3601
- '<td><span style="display:inline-block;width:24px;height:24px;border-radius:6px;border:1px solid var(--border);background:' + chipColor + '"></span></td>' +
4347
+ '<td><span style="display:inline-block;width:min(100%, 24px);height:24px;border-radius:6px;border:1px solid var(--border);background:' + chipColor + '"></span></td>' +
3602
4348
  '<td class="u-mono">' + a[0] + "</td>" +
3603
4349
  '<td class="u-mono" style="color:var(--text-muted)">' + a[1] + "</td>" +
3604
4350
  "<td>" + a[2] + "</td>";
@@ -3612,7 +4358,7 @@ npm i tailwindcss @tailwindcss/postcss
3612
4358
  var nameEl = sw.querySelector(".ds-swatch__name") || sw.querySelector(".ds-swatch__hex");
3613
4359
  if (!nameEl) return;
3614
4360
  var prev = nameEl.textContent;
3615
- nameEl.textContent = "¡Copiado!";
4361
+ nameEl.textContent = "Copiado";
3616
4362
  setTimeout(function () { nameEl.textContent = prev; }, 1200);
3617
4363
  });
3618
4364
  });
@@ -3843,6 +4589,31 @@ npm i tailwindcss @tailwindcss/postcss
3843
4589
  });
3844
4590
  });
3845
4591
 
4592
+ /* ---------- Sidebar contraíble (§10.24): riel de iconos ---------- */
4593
+ Array.prototype.forEach.call(d.querySelectorAll(".sidebar__toggle"), function (btn) {
4594
+ var nav = d.getElementById(btn.getAttribute("aria-controls"));
4595
+ var label = btn.querySelector(".sidebar__label");
4596
+ if (!nav) return;
4597
+ btn.addEventListener("click", function () {
4598
+ var collapsed = nav.classList.toggle("is-collapsed");
4599
+ btn.setAttribute("aria-expanded", collapsed ? "false" : "true");
4600
+ if (label) label.textContent = collapsed ? "Expandir" : "Contraer";
4601
+ });
4602
+ });
4603
+
4604
+ /* ---------- Chip de métrica (§10.22): sumar un vaso ---------- */
4605
+ var waterChip = d.getElementById("dsWaterChip");
4606
+ if (waterChip) {
4607
+ var waterValue = waterChip.querySelector(".metric-chip__value");
4608
+ var waterMax = 8, water = 3;
4609
+ waterChip.addEventListener("click", function () {
4610
+ water = water >= waterMax ? 0 : water + 1;
4611
+ waterValue.textContent = water + " de " + waterMax;
4612
+ waterChip.setAttribute("aria-label", "Agua: " + water + " de " + waterMax + " vasos. " +
4613
+ (water < waterMax ? "Sumar un vaso" : "Empezar de nuevo"));
4614
+ });
4615
+ }
4616
+
3846
4617
  /* ---------- Toast ---------- */
3847
4618
  var TOAST_DATA = {
3848
4619
  success: ["i-circle-check", "Cambios guardados", "El proyecto se actualizó correctamente."],
@@ -3872,7 +4643,8 @@ npm i tailwindcss @tailwindcss/postcss
3872
4643
  function dismiss() {
3873
4644
  if (!toast.parentElement) return;
3874
4645
  toast.classList.add("is-leaving");
3875
- toast.addEventListener("animationend", function () { toast.remove(); });
4646
+ toast.addEventListener("animationend", function () { toast.remove(); }, { once: true });
4647
+ setTimeout(function () { toast.remove(); }, 400);
3876
4648
  if (reduceMotion) toast.remove();
3877
4649
  }
3878
4650
  }
@@ -3948,46 +4720,13 @@ npm i tailwindcss @tailwindcss/postcss
3948
4720
  renderPagination();
3949
4721
  }
3950
4722
 
3951
- /* ---------- Count-up de Stat ---------- */
3952
- function countUp(el) {
3953
- var target = parseFloat(el.getAttribute("data-target") || 0);
3954
- var prefix = el.getAttribute("data-prefix") || "";
3955
- var suffix = el.getAttribute("data-suffix") || "";
3956
- function fmt(v) { return prefix + Math.round(v).toLocaleString("es-MX") + suffix; }
3957
- if (reduceMotion) { el.textContent = fmt(target); return; }
3958
- var start = null, DUR = 600;
3959
- function tick(ts) {
3960
- if (start === null) start = ts;
3961
- var t = Math.min(1, (ts - start) / DUR);
3962
- var eased = 1 - Math.pow(1 - t, 3);
3963
- el.textContent = fmt(target * eased);
3964
- if (t < 1) requestAnimationFrame(tick);
3965
- }
3966
- requestAnimationFrame(tick);
3967
- }
3968
- var countEls = d.querySelectorAll(".js-countup");
3969
- if ("IntersectionObserver" in window) {
3970
- var seen = new WeakSet();
3971
- var counter = new IntersectionObserver(function (entries) {
3972
- entries.forEach(function (entry) {
3973
- if (entry.isIntersecting && !seen.has(entry.target)) {
3974
- seen.add(entry.target);
3975
- countUp(entry.target);
3976
- counter.unobserve(entry.target);
3977
- }
3978
- });
3979
- }, { threshold: 0.4 });
3980
- Array.prototype.forEach.call(countEls, function (el) { counter.observe(el); });
3981
- } else {
3982
- Array.prototype.forEach.call(countEls, countUp);
3983
- }
3984
-
3985
4723
  /* ---------- Drawer (§10.29): focus-trap + retorno de foco + Esc ---------- */
3986
4724
  var drawerTrigger = null;
3987
4725
  function openDrawer(dr, trigger) {
3988
4726
  if (!dr) return;
3989
4727
  drawerTrigger = trigger || d.activeElement;
3990
4728
  dr.classList.add("is-open");
4729
+ if (drawerTrigger.hasAttribute("aria-expanded")) drawerTrigger.setAttribute("aria-expanded", "true");
3991
4730
  var f = modalFocusables(dr);
3992
4731
  var panel = dr.querySelector(".drawer__panel");
3993
4732
  window.setTimeout(function () { (f[0] || panel).focus(); }, 0);
@@ -3995,6 +4734,7 @@ npm i tailwindcss @tailwindcss/postcss
3995
4734
  function closeDrawer(dr) {
3996
4735
  if (!dr || !dr.classList.contains("is-open")) return;
3997
4736
  dr.classList.remove("is-open");
4737
+ if (drawerTrigger && drawerTrigger.hasAttribute("aria-expanded")) drawerTrigger.setAttribute("aria-expanded", "false");
3998
4738
  if (drawerTrigger && typeof drawerTrigger.focus === "function") drawerTrigger.focus();
3999
4739
  drawerTrigger = null;
4000
4740
  }
@@ -4152,6 +4892,7 @@ npm i tailwindcss @tailwindcss/postcss
4152
4892
  var dpInput = d.getElementById("dpInput");
4153
4893
  var dpCal = d.getElementById("dpCal");
4154
4894
  var dpGrid = d.getElementById("dpGrid");
4895
+ var dpTable = dpGrid.parentNode;
4155
4896
  var dpTitle = dp.querySelector(".calendar__title");
4156
4897
  var MONTHS = ["enero", "febrero", "marzo", "abril", "mayo", "junio",
4157
4898
  "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"];
@@ -4168,13 +4909,20 @@ npm i tailwindcss @tailwindcss/postcss
4168
4909
  function dpRender() {
4169
4910
  var y = view.getFullYear(), m = view.getMonth();
4170
4911
  dpTitle.textContent = MONTHS[m] + " " + y;
4171
- dpGrid.setAttribute("aria-label", MONTHS[m] + " " + y);
4912
+ dpTable.setAttribute("aria-label", MONTHS[m] + " " + y);
4172
4913
  dpGrid.innerHTML = "";
4173
4914
  var startDow = (new Date(y, m, 1).getDay() + 6) % 7; // lunes = 0
4174
4915
  var daysInMonth = new Date(y, m + 1, 0).getDate();
4175
4916
  var weeks = Math.ceil((startDow + daysInMonth) / 7);
4176
4917
  var start = new Date(y, m, 1 - startDow);
4918
+ var week = null;
4177
4919
  for (var i = 0; i < weeks * 7; i++) {
4920
+ if (i % 7 === 0) {
4921
+ week = d.createElement("div");
4922
+ week.className = "calendar__week";
4923
+ week.setAttribute("role", "row");
4924
+ dpGrid.appendChild(week);
4925
+ }
4178
4926
  var cell = new Date(start.getFullYear(), start.getMonth(), start.getDate() + i);
4179
4927
  var btn = d.createElement("button");
4180
4928
  btn.type = "button";
@@ -4189,7 +4937,7 @@ npm i tailwindcss @tailwindcss/postcss
4189
4937
  btn.setAttribute("aria-label", fmtLong.format(cell));
4190
4938
  btn.tabIndex = sameDay(cell, focusDate) ? 0 : -1;
4191
4939
  btn.setAttribute("data-ts", String(cell.getTime()));
4192
- dpGrid.appendChild(btn);
4940
+ week.appendChild(btn);
4193
4941
  }
4194
4942
  }
4195
4943
  function dpButtonFor(dt) { return dpGrid.querySelector('[data-ts="' + atMidnight(dt).getTime() + '"]'); }
@@ -4208,7 +4956,6 @@ npm i tailwindcss @tailwindcss/postcss
4208
4956
  }
4209
4957
  function dpOpen() {
4210
4958
  dp.classList.add("is-open");
4211
- dpInput.setAttribute("aria-expanded", "true");
4212
4959
  focusDate = new Date(selected);
4213
4960
  view = new Date(focusDate.getFullYear(), focusDate.getMonth(), 1);
4214
4961
  dpRender();
@@ -4217,7 +4964,6 @@ npm i tailwindcss @tailwindcss/postcss
4217
4964
  function dpClose(returnFocus) {
4218
4965
  if (!dp.classList.contains("is-open")) return;
4219
4966
  dp.classList.remove("is-open");
4220
- dpInput.setAttribute("aria-expanded", "false");
4221
4967
  if (returnFocus) dpInput.focus();
4222
4968
  }
4223
4969
  function dpSelect(dt) {
@@ -4292,6 +5038,75 @@ npm i tailwindcss @tailwindcss/postcss
4292
5038
  });
4293
5039
  }
4294
5040
  })();
5041
+ </script>
5042
+ <!-- INTERACCIONES COMPARTIDAS GENERADAS -->
5043
+ <script>
5044
+ (function () {
5045
+ "use strict";
5046
+ var trigger = document.getElementById("dsHamburger");
5047
+ var panel = document.getElementById("dsSidenav") || document.getElementById("dsSidebar");
5048
+ var mobile = window.matchMedia("(max-width: 1023px)");
5049
+
5050
+ /**
5051
+ * Actualiza la navegación como disclosure, sin crear una trampa de foco.
5052
+ * @param {boolean} open - Si la navegación móvil debe estar disponible.
5053
+ * @param {boolean} restore - Si el cierre debe devolver foco al disparador.
5054
+ * @returns {void}
5055
+ */
5056
+ function setNavigation(open, restore) {
5057
+ if (!trigger || !panel) return;
5058
+ panel.classList.toggle("is-open", open);
5059
+ panel.inert = mobile.matches && !open;
5060
+ trigger.setAttribute("aria-expanded", String(open));
5061
+ trigger.setAttribute("aria-label", open ? "Cerrar navegación" : "Abrir navegación");
5062
+ if (restore) trigger.focus();
5063
+ }
5064
+
5065
+ if (trigger && panel) {
5066
+ trigger.setAttribute("aria-controls", panel.id);
5067
+ setNavigation(false, false);
5068
+ trigger.addEventListener("click", function () { setNavigation(!panel.classList.contains("is-open"), false); });
5069
+ panel.addEventListener("click", function (event) {
5070
+ var link = event.target.closest("a[href^='#']");
5071
+ if (!link || !mobile.matches) return;
5072
+ var target = document.getElementById(link.hash.slice(1));
5073
+ setNavigation(false, false);
5074
+ if (target) {
5075
+ target.setAttribute("tabindex", "-1");
5076
+ target.focus({ preventScroll: true });
5077
+ }
5078
+ });
5079
+ document.addEventListener("keydown", function (event) {
5080
+ if (event.key === "Escape" && mobile.matches && panel.classList.contains("is-open")) setNavigation(false, true);
5081
+ });
5082
+ document.addEventListener("click", function (event) {
5083
+ if (mobile.matches && !panel.contains(event.target) && !trigger.contains(event.target)) setNavigation(false, false);
5084
+ });
5085
+ mobile.addEventListener("change", function () { setNavigation(false, false); });
5086
+ }
5087
+
5088
+ var themeButton = document.getElementById("dsThemeToggle") || document.getElementById("dsTheme");
5089
+ /**
5090
+ * Nombra la acción de tema y mantiene semántica de ubicación en el índice.
5091
+ * @returns {void}
5092
+ */
5093
+ function updateLabels() {
5094
+ if (themeButton) themeButton.setAttribute("aria-label", document.documentElement.dataset.theme === "dark" ? "Usar tema claro" : "Usar tema oscuro");
5095
+ document.querySelectorAll(".ds-nav a").forEach(function (link) {
5096
+ if (link.classList.contains("is-active")) link.setAttribute("aria-current", "location");
5097
+ else link.removeAttribute("aria-current");
5098
+ });
5099
+ var style = getComputedStyle(document.documentElement);
5100
+ document.querySelectorAll("[data-chart-color]").forEach(function (label) {
5101
+ label.textContent = style.getPropertyValue(label.getAttribute("data-chart-color")).trim();
5102
+ });
5103
+ }
5104
+ updateLabels();
5105
+ new MutationObserver(updateLabels).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
5106
+ var nav = document.querySelector(".ds-nav");
5107
+ if (nav) new MutationObserver(updateLabels).observe(nav, { subtree: true, attributes: true, attributeFilter: ["class"] });
5108
+ })();
5109
+
4295
5110
  </script>
4296
5111
  </body>
4297
5112
  </html>