@higinio/design-mcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@
16
16
  /* -- Primitivos: acento -- */
17
17
  --accent-100: #d9f0fb; --accent-200: #a8ddf6; --accent-300: #7dccf1;
18
18
  --accent-400: #52baed; --accent-500: #2fa3dd; --accent-600: #1f87bb; --accent-700: #186a94;
19
+ --accent-800: #12506f; /* solo light: texto de acción en hover */
19
20
 
20
21
  /* -- Primitivos: índigo -- */
21
22
  --indigo-300: #7a83c4; --indigo-400: #565fa3; --indigo-500: #353d7c;
@@ -24,6 +25,7 @@
24
25
  /* -- Primitivos: escala nocturna -- */
25
26
  --night-950: #010203; --night-900: #0b0d12; --night-800: #14171f;
26
27
  --night-700: #1e222d; --night-600: #2a2f3d; --night-500: #3d4354;
28
+ --night-450: #5f6575; /* texto atenuado en light */
27
29
  --night-400: #6b7183; --night-300: #9aa0b0; --night-200: #c5c9d4;
28
30
  --night-100: #e8eaef; --night-50: #f6f7f9;
29
31
 
@@ -39,7 +41,10 @@
39
41
  --surface-2: var(--night-800);
40
42
  --surface-3: var(--night-700);
41
43
  --border: var(--night-500);
42
- --border-strong: var(--night-600);
44
+ /* night-400, no night-600: en dark, oscurecer un borde lo DEBILITA (principio 2:
45
+ la elevación se comunica aclarando). n600 daba 1.46:1 vs los 1.97:1 del borde
46
+ normal — el énfasis se veía más flojo. n400 = 3.99:1 sobre surface-1. */
47
+ --border-strong: var(--night-400);
43
48
  --text-primary: var(--night-100);
44
49
  --text-secondary: var(--night-200);
45
50
  --text-muted: var(--night-300);
@@ -110,7 +115,7 @@
110
115
  --radius-sm: 8px; --radius-md: 12px; --radius-lg: 16px;
111
116
  --radius-xl: 24px; --radius-full: 9999px;
112
117
  --border-default: 1px solid var(--night-500);
113
- --border-emphasis: 1px solid var(--night-600);
118
+ --border-emphasis: 1px solid var(--night-400); /* ver --border-strong */
114
119
  --focus-ring: 2px solid var(--accent-400);
115
120
  --focus-ring-offset: 2px;
116
121
 
@@ -143,30 +148,33 @@
143
148
  --surface-2: #ffffff;
144
149
  --surface-3: #eaf1f7;
145
150
  --border: #d8dfe8;
146
- --border-strong: #b9c3d1;
151
+ /* border-strong NO se redefine en claro: night-400 (:root) cumple 1.4.11 en
152
+ AMBOS temas (3.26-3.99:1 en dark, 4.27-4.87:1 en light). #b9c3d1 daba
153
+ 1.53-1.78:1, bajo el mínimo de 3:1 para UI. */
147
154
  --text-primary: #14171f;
148
155
  --text-secondary: #3d4354;
149
- --text-muted: #6b7183;
156
+ --text-muted: #5f6575; /* night-400 caía a 4.27:1 sobre --surface-3 */
150
157
  --text-disabled: #9aa0b0;
151
158
  --text-heading: #0b0d12;
152
159
  --text-on-accent: #010203; /* el CTA no cambia */
153
- --success: #15803d; --success-bg: #e3f5ea;
154
- --warning: #a16207; --warning-bg: #fbf3dd;
155
- --danger: #c03535; --danger-bg: #fbe7e7;
156
- --info: #1f87bb; --info-bg: #e3f2fb;
160
+ /* Los semánticos son texto sobre su propio --*-bg tintado: se calibran contra ese fondo. */
161
+ --success: #166534; --success-bg: #e3f5ea;
162
+ --warning: #854d0e; --warning-bg: #fbf3dd;
163
+ --danger: #b02c2c; --danger-bg: #fbe7e7;
164
+ --info: #186a94; --info-bg: #e3f2fb;
157
165
  --accent-ghost-hover: rgba(31,135,187,.08);
158
166
  --accent-ghost-active: rgba(31,135,187,.14);
159
167
  --accent-selected: rgba(82,186,237,.12);
160
168
  --overlay: rgba(20,23,31,.45);
161
169
 
162
- --link: #1f87bb;
163
- --link-hover: #186a94;
164
- --focus-color: #1f87bb;
170
+ --link: #186a94; /* accent-700 · 5.95:1 — accent-600 solo daba 4.01:1 */
171
+ --link-hover: #12506f; /* accent-800 · en light el hover oscurece */
172
+ --focus-color: #1f87bb; /* accent-600 se queda: es UI (4.01 ≥ 3), no texto */
165
173
  --border-hover: #b9c3d1;
166
174
  --outline-hover-bg: #eaf1f7;
167
175
  --outline-active-bg: #d8dfe8;
168
176
  --text-on-danger: #ffffff;
169
- --data-color: #1f87bb;
177
+ --data-color: #186a94; /* pinta cifras y .progress__value: es TEXTO, necesita 4.5 */
170
178
  --elevated-border: #d8dfe8;
171
179
  --card-border: #d8dfe8;
172
180
  --code-color: #186a94;
@@ -376,6 +384,8 @@ a:hover { color: var(--link-hover); }
376
384
  ============================================================ */
377
385
  .field { display: flex; flex-direction: column; margin-bottom: var(--form-gap); }
378
386
  .field__label { font-size: var(--text-sm); font-weight: 500; color: var(--text-secondary); margin-bottom: 6px; }
387
+ /* Campo obligatorio: asterisco decorativo; la semántica va en required/aria-required del control */
388
+ .field--required > .field__label::after { content: "*"; margin-left: 2px; color: var(--danger); font-weight: 600; }
379
389
  .field__help { font-size: var(--text-sm); color: var(--text-muted); margin-top: 6px; }
380
390
  .field__error {
381
391
  display: inline-flex; align-items: center; gap: 6px;
@@ -411,7 +421,7 @@ a:hover { color: var(--link-hover); }
411
421
  .input-wrap .input { padding-left: 40px; }
412
422
 
413
423
  .textarea { min-height: 96px; padding: 12px 14px; resize: vertical; line-height: var(--leading-body); }
414
- .field__count { font-size: var(--text-xs); color: var(--text-disabled); text-align: right; margin-top: 4px; }
424
+ .field__count { font-size: var(--text-xs); color: var(--text-muted); text-align: right; margin-top: 4px; }
415
425
 
416
426
  /* Select nativo estilizado (el menú custom usa .menu) */
417
427
  .select-wrap { position: relative; }
@@ -519,6 +529,12 @@ a:hover { color: var(--link-hover); }
519
529
  margin-top: -2px;
520
530
  }
521
531
  .checkbox:checked::after { transform: rotate(-45deg) scale(1); }
532
+ /* Indeterminate: guion horizontal, no el check. Solo por JS (el.indeterminate = true) */
533
+ .checkbox:indeterminate { background: var(--accent-400); border-color: var(--accent-400); }
534
+ .checkbox:indeterminate::after {
535
+ content: ""; width: 10px; height: 2px; margin-top: 0;
536
+ border: 0; border-radius: var(--radius-full); background: var(--text-on-accent); transform: none;
537
+ }
522
538
  .radio::after {
523
539
  content: ""; width: 8px; height: 8px; border-radius: var(--radius-full);
524
540
  background: var(--text-on-accent); transform: scale(0);
@@ -600,6 +616,17 @@ a:hover { color: var(--link-hover); }
600
616
  .avatar__dot--away { background: var(--warning); }
601
617
  .avatar__dot--offline { background: var(--border); }
602
618
 
619
+ /* AvatarGroup — avatares solapados + chip +N.
620
+ Borde 2px del color del fondo (--surface-1 en tarjetas, --bg en el lienzo) para separar. */
621
+ .avatar-group { display: inline-flex; align-items: center; }
622
+ .avatar-group .avatar { border: 2px solid var(--surface-1); }
623
+ .avatar-group .avatar:not(:first-child) { margin-left: -8px; }
624
+ .avatar-group__more {
625
+ background: var(--surface-3); color: var(--text-secondary);
626
+ font-family: var(--font-body); font-weight: 600;
627
+ letter-spacing: 0; text-transform: none;
628
+ }
629
+
603
630
  /* ============================================================
604
631
  12B. DIVIDER — .divider (hairline), .divider--label, .divider--vertical
605
632
  ============================================================ */
@@ -635,6 +662,32 @@ a:hover { color: var(--link-hover); }
635
662
  .stat__delta--down { color: var(--danger); }
636
663
  .stat__delta--neutral { color: var(--text-muted); }
637
664
 
665
+ /* Chip de métrica: rótulo y valor en una pastilla de una línea */
666
+ .metric-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
667
+ .metric-chip {
668
+ display: inline-flex; align-items: center; gap: var(--space-2);
669
+ padding: var(--space-1) var(--space-3);
670
+ border: 1px solid var(--border); border-radius: var(--radius-full);
671
+ background: var(--surface-1);
672
+ font-family: var(--font-body); font-size: var(--text-sm); line-height: var(--leading-snug);
673
+ color: var(--text-secondary); white-space: nowrap;
674
+ }
675
+ .metric-chip svg { width: 16px; height: 16px; flex: none; color: var(--text-muted); }
676
+ .metric-chip__value {
677
+ font-family: var(--font-mono); font-weight: 500; color: var(--link);
678
+ font-variant-numeric: tabular-nums;
679
+ }
680
+ button.metric-chip {
681
+ min-height: var(--touch-target); padding-inline: var(--space-4);
682
+ cursor: pointer; text-align: left;
683
+ transition: background-color var(--duration-fast) var(--ease-out),
684
+ border-color var(--duration-fast) var(--ease-out),
685
+ transform var(--duration-instant) var(--ease-out);
686
+ }
687
+ button.metric-chip:hover { background: var(--outline-hover-bg); border-color: var(--border-hover); }
688
+ button.metric-chip:active { background: var(--outline-active-bg); transform: scale(.98); }
689
+ button.metric-chip:focus-visible { outline: var(--focus-ring); outline-offset: var(--focus-ring-offset); }
690
+
638
691
  /* ============================================================
639
692
  13B. PAGINATION — .pagination (activa: fondo acento + peso, no solo color)
640
693
  ============================================================ */
@@ -717,7 +770,7 @@ a:hover { color: var(--link-hover); }
717
770
  }
718
771
  .alert > svg { width: 20px; height: 20px; flex: none; margin-top: 1px; }
719
772
  .alert__title { font-weight: 600; margin: 0 0 2px; color: inherit; }
720
- .alert__body { margin: 0; color: inherit; opacity: .88; }
773
+ .alert__body { margin: 0; color: inherit; }
721
774
  .alert__close {
722
775
  margin-left: auto; flex: none; display: grid; place-items: center;
723
776
  width: 36px; height: 36px; border: 0; border-radius: var(--radius-sm);
@@ -807,9 +860,39 @@ a:hover { color: var(--link-hover); }
807
860
  }
808
861
  .table tbody tr { transition: background-color var(--duration-fast) var(--ease-out); }
809
862
  .table tbody tr:hover { background: var(--row-hover); }
810
- .table tbody tr.is-selected { background: var(--accent-selected); box-shadow: inset 2px 0 0 var(--accent-400); }
811
863
  .table .num, .table td.num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
812
864
 
865
+ /* Zebra — filas pares en surface-2; hover y selección mandan sobre la banda */
866
+ .table--zebra tbody tr:nth-child(even) { background: var(--surface-2); }
867
+ .table--zebra tbody tr:hover { background: var(--row-hover); }
868
+
869
+ /* Sticky header — dentro de un contenedor con overflow: auto y altura acotada */
870
+ .table--sticky thead th { position: sticky; top: 0; z-index: 1; }
871
+
872
+ /* Header ordenable — botón dentro del th + icono; el estado vive en aria-sort del th */
873
+ .th-sort {
874
+ display: inline-flex; align-items: center; gap: 6px;
875
+ margin: 0; padding: 0; border: 0; background: transparent;
876
+ font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
877
+ cursor: pointer;
878
+ }
879
+ .th-sort:hover { color: var(--text-secondary); }
880
+ .th-sort:focus-visible { outline: var(--focus-ring); outline-offset: 2px; border-radius: var(--radius-sm); }
881
+ .sort-icon {
882
+ width: 14px; height: 14px; flex: none; opacity: .45;
883
+ transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
884
+ }
885
+ .table th[aria-sort="ascending"] .sort-icon,
886
+ .table th[aria-sort="descending"] .sort-icon { opacity: 1; color: var(--accent-400); }
887
+ .table th[aria-sort="descending"] .sort-icon { transform: rotate(180deg); }
888
+
889
+ /* Columna de selección (checkbox en th y en cada fila) */
890
+ .table .col-select { width: 44px; text-align: center; padding-left: var(--space-2); padding-right: var(--space-2); }
891
+
892
+ /* Fila seleccionada — .row--selected (contrato); .is-selected se mantiene como alias */
893
+ .table tbody tr.is-selected,
894
+ .table tbody tr.row--selected { background: var(--accent-selected); box-shadow: inset 2px 0 0 var(--accent-400); }
895
+
813
896
  /* ============================================================
814
897
  19. NAVEGACIÓN — .navbar, .sidebar, .tabs, .breadcrumbs
815
898
  ============================================================ */
@@ -849,6 +932,70 @@ a:hover { color: var(--link-hover); }
849
932
  .sidebar__item svg { width: 18px; height: 18px; color: var(--text-muted); flex: none; }
850
933
  .sidebar__item.is-active svg { color: var(--sidebar-active-icon); }
851
934
 
935
+ /* Grupos con rótulo dentro del sidebar */
936
+ .sidebar__group { display: flex; flex-direction: column; gap: 2px; }
937
+ .sidebar__group + .sidebar__group { margin-top: var(--space-4); }
938
+ .sidebar__group-label {
939
+ padding: 0 12px var(--space-1);
940
+ font-size: var(--text-xs); font-weight: 500; text-transform: uppercase;
941
+ letter-spacing: var(--tracking-overline); color: var(--text-muted); white-space: nowrap;
942
+ }
943
+ .sidebar__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
944
+
945
+ /* Sidebar contraíble: expandido 260 px, riel de iconos de 64 px.
946
+ El ancho cambia sin transición; solo los rótulos se funden con opacity y transform. */
947
+ .sidebar--collapsible .sidebar__label {
948
+ transition: opacity var(--duration-fast) var(--ease-out),
949
+ transform var(--duration-fast) var(--ease-out);
950
+ }
951
+ .sidebar__toggle {
952
+ margin-top: auto; width: 100%; border: 0; background: transparent;
953
+ font-family: var(--font-body); text-align: left; cursor: pointer;
954
+ }
955
+ .sidebar.is-collapsed { width: 64px; }
956
+ .sidebar.is-collapsed .sidebar__item { position: relative; justify-content: center; padding-inline: 0; }
957
+ .sidebar.is-collapsed .sidebar__toggle svg { transform: rotate(180deg); }
958
+ .sidebar.is-collapsed .sidebar__group + .sidebar__group { margin-top: var(--space-2); }
959
+ /* En el riel, el rótulo de grupo es un separador de 1 px; su texto sigue en el árbol de accesibilidad */
960
+ .sidebar.is-collapsed .sidebar__group-label {
961
+ height: 1px; padding: 0; margin: 0 var(--space-2) var(--space-2);
962
+ overflow: hidden; color: transparent; background: var(--border);
963
+ }
964
+ /* Oculto a la vista pero accesible (patrón de .visually-hidden) */
965
+ .sidebar.is-collapsed .sidebar__label,
966
+ .sidebar.is-collapsed .sidebar__group:first-child > .sidebar__group-label {
967
+ position: absolute; width: 1px; height: 1px; margin: -1px;
968
+ clip-path: inset(50%); overflow: hidden; white-space: nowrap;
969
+ }
970
+ .sidebar.is-collapsed .sidebar__label {
971
+ background: var(--tooltip-bg); border: 1px solid var(--tooltip-border); border-radius: var(--radius-sm);
972
+ color: var(--tooltip-text); font-size: var(--text-xs); line-height: 16px;
973
+ pointer-events: none; z-index: 70;
974
+ opacity: 0; transform: translateX(-4px);
975
+ }
976
+ /* Tooltip del riel: el propio rótulo aparece a la derecha del ítem, también con teclado */
977
+ .sidebar.is-collapsed .sidebar__item:focus-visible .sidebar__label {
978
+ left: calc(100% + 8px); top: calc(50% - 15px); width: auto; height: auto;
979
+ margin: 0; padding: 6px 10px; clip-path: none; overflow: visible;
980
+ opacity: 1; transform: none; transition-delay: 400ms;
981
+ }
982
+ /* Por hover solo con puntero real — en touch quedaría pegado tras el tap */
983
+ @media (hover: hover) and (pointer: fine) {
984
+ .sidebar.is-collapsed .sidebar__item:hover .sidebar__label {
985
+ left: calc(100% + 8px); top: calc(50% - 15px); width: auto; height: auto;
986
+ margin: 0; padding: 6px 10px; clip-path: none; overflow: visible;
987
+ opacity: 1; transform: none; transition-delay: 400ms;
988
+ }
989
+ }
990
+ /* Por debajo de 768 px la navegación vive en un Drawer --left, que abre el botón ☰
991
+ .sidebar-trigger (combinado con .icon-btn); desde 768 px el sidebar es fijo y el botón sobra */
992
+ @media (max-width: 767px) {
993
+ .sidebar--collapsible { display: none; }
994
+ }
995
+ @media (min-width: 768px) {
996
+ .sidebar-trigger { display: none; }
997
+ }
998
+
852
999
  .tabs { position: relative; display: flex; gap: var(--space-2); border-bottom: 1px solid var(--border-strong); }
853
1000
  .tabs__btn {
854
1001
  padding: 10px 14px; border: 0; background: transparent; cursor: pointer;
@@ -990,3 +1137,270 @@ a:hover { color: var(--link-hover); }
990
1137
  position: absolute; width: 1px; height: 1px; margin: -1px;
991
1138
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
992
1139
  }
1140
+
1141
+ /* ============================================================
1142
+ 25. COMBOBOX / AUTOCOMPLETE — .combobox + SEARCH — .search
1143
+ El listado vive sobre --surface-2 (superficie de dropdowns).
1144
+ ============================================================ */
1145
+ .combobox { position: relative; }
1146
+ .combobox__input { width: 100%; } /* consume .input para el resto de estilos */
1147
+ .combobox__list {
1148
+ position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 40;
1149
+ margin: 0; padding: 6px; list-style: none; max-height: 264px; overflow: auto;
1150
+ background: var(--surface-2); border: 1px solid var(--elevated-border);
1151
+ border-radius: var(--radius-md); box-shadow: var(--shadow-3);
1152
+ }
1153
+ .combobox__list[hidden] { display: none; }
1154
+ .combobox__option {
1155
+ display: flex; align-items: center; gap: 10px; width: 100%;
1156
+ padding: 10px 12px; border-radius: var(--radius-sm);
1157
+ color: var(--text-primary); font-size: var(--text-sm); cursor: pointer;
1158
+ transition: background-color var(--duration-fast) var(--ease-out);
1159
+ }
1160
+ .combobox__option[hidden] { display: none; }
1161
+ /* .is-active = resaltado por teclado; [aria-selected] = la elegida (fondo + icono, no solo color) */
1162
+ .combobox__option.is-active { background: var(--surface-3); }
1163
+ .combobox__option[aria-selected="true"] { background: var(--accent-selected); color: var(--accent-300); }
1164
+ [data-theme="light"] .combobox__option[aria-selected="true"] { color: var(--link); }
1165
+ .combobox__check { display: none; margin-left: auto; width: 16px; height: 16px; color: var(--accent-400); flex: none; }
1166
+ .combobox__option[aria-selected="true"] .combobox__check { display: block; }
1167
+
1168
+ /* Search — wrapper con lupa (aria-hidden) + botón limpiar (×, aria-label="Limpiar") */
1169
+ .search { position: relative; display: flex; align-items: center; }
1170
+ .search__icon {
1171
+ position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
1172
+ width: 18px; height: 18px; color: var(--text-disabled); pointer-events: none;
1173
+ }
1174
+ .search__input { width: 100%; padding-left: 40px; padding-right: 40px; } /* consume .input */
1175
+ /* Oculta el clear nativo de WebKit para no duplicar el affordance del .search__clear */
1176
+ .search__input::-webkit-search-cancel-button,
1177
+ .search__input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
1178
+ .search__clear {
1179
+ position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
1180
+ display: inline-grid; place-content: center; width: 28px; height: 28px; padding: 0;
1181
+ border: 0; border-radius: var(--radius-full); background: transparent;
1182
+ color: var(--text-muted); cursor: pointer;
1183
+ transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
1184
+ }
1185
+ .search__clear:hover { background: var(--surface-3); color: var(--text-primary); }
1186
+ .search__clear svg { width: 16px; height: 16px; }
1187
+ .search__clear[hidden] { display: none; }
1188
+
1189
+ /* ============================================================
1190
+ 26. DRAWER / SHEET — .drawer + __overlay + __panel (focus-trap por JS)
1191
+ Motion: transform translate + fade del overlay (--duration-slow).
1192
+ El overlay reutiliza --overlay del modal; SIN blur nuevo.
1193
+ ============================================================ */
1194
+ .drawer { position: fixed; inset: 0; z-index: 100; }
1195
+ .drawer[hidden] { display: none; }
1196
+ .drawer__overlay {
1197
+ position: absolute; inset: 0; background: var(--overlay);
1198
+ opacity: 0; transition: opacity var(--duration-slow) var(--ease-out);
1199
+ }
1200
+ .drawer.is-open .drawer__overlay { opacity: 1; }
1201
+ .drawer__panel {
1202
+ position: absolute; top: 0; right: 0; bottom: 0;
1203
+ display: flex; flex-direction: column;
1204
+ width: min(400px, 100%); padding: var(--card-padding);
1205
+ background: var(--surface-2); border-left: 1px solid var(--elevated-border);
1206
+ box-shadow: var(--shadow-3);
1207
+ overflow-y: auto; overscroll-behavior: contain;
1208
+ transform: translateX(100%);
1209
+ transition: transform var(--duration-slow) var(--ease-out);
1210
+ }
1211
+ .drawer.is-open .drawer__panel { transform: none; }
1212
+ /* Variante izquierda */
1213
+ .drawer--left .drawer__panel {
1214
+ right: auto; left: 0; border-left: 0; border-right: 1px solid var(--elevated-border);
1215
+ transform: translateX(-100%);
1216
+ }
1217
+ .drawer--left.is-open .drawer__panel { transform: none; }
1218
+ /* Variante inferior (sheet) */
1219
+ .drawer--bottom .drawer__panel {
1220
+ top: auto; left: 0; right: 0; width: 100%; max-height: 80dvh;
1221
+ border-left: 0; border-top: 1px solid var(--elevated-border);
1222
+ border-radius: var(--radius-lg) var(--radius-lg) 0 0;
1223
+ transform: translateY(100%);
1224
+ }
1225
+ .drawer--bottom.is-open .drawer__panel { transform: none; }
1226
+ .drawer__header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-4); }
1227
+ .drawer__title { font-family: var(--font-body); font-size: var(--text-xl); font-weight: 600; letter-spacing: normal; color: var(--text-heading); margin: 0; }
1228
+ .drawer__body { flex: 1; }
1229
+ .drawer__footer { display: flex; justify-content: flex-end; gap: var(--space-3); margin-top: var(--space-6); }
1230
+
1231
+ /* ============================================================
1232
+ 27. POPOVER — .popover + __trigger + __panel (abre por CLICK)
1233
+ Motion: fade + scale(0.96→1) desde el transform-origin del trigger.
1234
+ Panel sobre --surface-2. Se distingue del Tooltip: click, no hover.
1235
+ ============================================================ */
1236
+ .popover { position: relative; display: inline-flex; }
1237
+ .popover__panel {
1238
+ position: absolute; top: calc(100% + 8px); left: 0; z-index: 60;
1239
+ min-width: 240px; max-width: 320px; padding: var(--space-4);
1240
+ background: var(--surface-2); border: 1px solid var(--elevated-border);
1241
+ border-radius: var(--radius-md); box-shadow: var(--shadow-3);
1242
+ transform-origin: top left; /* origin-aware: crece desde el disparador */
1243
+ opacity: 0; transform: scale(.96); visibility: hidden;
1244
+ transition: opacity var(--duration-base) var(--ease-out),
1245
+ transform var(--duration-base) var(--ease-out),
1246
+ visibility 0s linear var(--duration-base);
1247
+ }
1248
+ .popover.is-open .popover__panel {
1249
+ opacity: 1; transform: none; visibility: visible;
1250
+ transition: opacity var(--duration-base) var(--ease-out),
1251
+ transform var(--duration-base) var(--ease-out),
1252
+ visibility 0s;
1253
+ }
1254
+ /* Alineado a la derecha del disparador */
1255
+ .popover--right .popover__panel { left: auto; right: 0; transform-origin: top right; }
1256
+
1257
+ /* ============================================================
1258
+ 28. BUTTON GROUP — .btn-group (bordes compartidos, radios en extremos)
1259
+ ============================================================ */
1260
+ .btn-group { display: inline-flex; }
1261
+ .btn-group > .btn { border-radius: 0; }
1262
+ .btn-group > .btn:not(:first-child) { margin-left: -1px; }
1263
+ .btn-group > .btn:first-child { border-top-left-radius: var(--radius-md); border-bottom-left-radius: var(--radius-md); }
1264
+ .btn-group > .btn:last-child { border-top-right-radius: var(--radius-md); border-bottom-right-radius: var(--radius-md); }
1265
+ /* El botón enfocado/hover sube de z-index para que su borde no lo tape el vecino */
1266
+ .btn-group > .btn:hover, .btn-group > .btn:focus-visible, .btn-group > .btn.is-active { z-index: 1; }
1267
+
1268
+ /* ============================================================
1269
+ 29. DATEPICKER — .datepicker + calendario en popover
1270
+ El panel reutiliza el look de .popover__panel sobre --surface-2.
1271
+ Grid role="grid"; días como <button role="gridcell">.
1272
+ ============================================================ */
1273
+ .datepicker { position: relative; }
1274
+ .datepicker__input { cursor: pointer; } /* consume .input; el icono va con .input-wrap */
1275
+ .datepicker__calendar {
1276
+ position: absolute; top: calc(100% + 8px); left: 0; z-index: 60;
1277
+ padding: var(--space-4);
1278
+ background: var(--surface-2); border: 1px solid var(--elevated-border);
1279
+ border-radius: var(--radius-md); box-shadow: var(--shadow-3);
1280
+ }
1281
+ .datepicker__calendar[hidden] { display: none; }
1282
+
1283
+ .calendar { width: 252px; }
1284
+ .calendar__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-3); }
1285
+ .calendar__title { font-size: var(--text-sm); font-weight: 600; color: var(--text-heading); } /* mes en minúscula (es) */
1286
+ .calendar__weekdays { display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: var(--space-1); }
1287
+ .calendar__weekdays span {
1288
+ display: grid; place-items: center; height: 28px;
1289
+ font-size: var(--text-xs); font-weight: 500; color: var(--text-muted);
1290
+ }
1291
+ .calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
1292
+ .calendar__week { display: contents; } /* fila semántica (role="row") sin romper el grid */
1293
+ .calendar__day {
1294
+ display: grid; place-items: center; width: 100%; height: 36px; padding: 0;
1295
+ border: 1px solid transparent; border-radius: var(--radius-sm); background: transparent;
1296
+ color: var(--text-primary); font-family: var(--font-mono); font-size: var(--text-sm);
1297
+ font-variant-numeric: tabular-nums; cursor: pointer;
1298
+ transition: background-color var(--duration-fast) var(--ease-out),
1299
+ color var(--duration-fast) var(--ease-out);
1300
+ }
1301
+ .calendar__day:hover:not([disabled]):not(.calendar__day--selected) { background: var(--surface-3); }
1302
+ /* Hoy: aro de acento por borde (no glow) + texto de acento */
1303
+ .calendar__day--today { border-color: var(--accent-400); color: var(--accent-300); }
1304
+ /* Seleccionado: fondo de acento + texto oscuro + peso — no depende solo del color */
1305
+ .calendar__day--selected { background: var(--accent-400); border-color: var(--accent-400); color: var(--text-on-accent); font-weight: 600; }
1306
+ .calendar__day--outside { color: var(--text-muted); }
1307
+ .calendar__day[disabled] { color: var(--text-disabled); opacity: .5; cursor: not-allowed; }
1308
+
1309
+ /* ============================================================
1310
+ 30. FILE UPLOAD — .file-upload (dropzone) + __list
1311
+ El input file real va oculto accesiblemente (.visually-hidden).
1312
+ ============================================================ */
1313
+ .file-upload { display: block; }
1314
+ .file-upload__label {
1315
+ display: flex; flex-direction: column; align-items: center; gap: var(--space-2);
1316
+ padding: var(--space-8) var(--space-6); text-align: center;
1317
+ background: var(--surface-1); border: 1px dashed var(--border-strong);
1318
+ border-radius: var(--radius-lg); color: var(--text-muted); cursor: pointer;
1319
+ transition: border-color var(--duration-fast) var(--ease-out),
1320
+ background-color var(--duration-fast) var(--ease-out),
1321
+ color var(--duration-fast) var(--ease-out);
1322
+ }
1323
+ .file-upload__label > svg { width: 28px; height: 28px; color: var(--text-muted); }
1324
+ .file-upload__button { color: var(--link); font-weight: 600; }
1325
+ .file-upload__hint { font-size: var(--text-xs); color: var(--text-muted); }
1326
+ @media (hover: hover) and (pointer: fine) {
1327
+ .file-upload__label:hover { border-color: var(--border-hover); background: var(--surface-2); color: var(--text-secondary); }
1328
+ }
1329
+ /* El ring se pinta sobre el label porque el input está oculto */
1330
+ .file-upload__input:focus-visible + .file-upload__label { outline: var(--focus-ring); outline-offset: var(--focus-ring-offset); }
1331
+ /* Arrastrando encima: acento (icono + borde, no solo color) */
1332
+ .file-upload.is-dragover .file-upload__label { border-color: var(--accent-400); background: var(--accent-ghost-hover); color: var(--text-secondary); }
1333
+
1334
+ .file-upload__list { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
1335
+ .file-upload__item {
1336
+ display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: var(--space-3);
1337
+ padding: var(--space-3); background: var(--surface-2);
1338
+ border: 1px solid var(--border); border-radius: var(--radius-md);
1339
+ }
1340
+ .file-upload__item > svg { width: 20px; height: 20px; color: var(--text-muted); flex: none; }
1341
+ .file-upload__item > div { min-width: 0; } /* permite truncar el nombre */
1342
+ .file-upload__item .progress { margin-top: var(--space-2); }
1343
+ .file-upload__name { display: block; font-size: var(--text-sm); color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1344
+ .file-upload__size { display: block; font-size: var(--text-xs); color: var(--text-muted); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
1345
+ /* Error: borde + icono + texto en danger, nunca solo por color */
1346
+ .file-upload__item--error { border-color: color-mix(in srgb, var(--danger) 45%, transparent); background: var(--danger-bg); }
1347
+ .file-upload__item--error > svg { color: var(--danger); }
1348
+ .file-upload__item--error .file-upload__size { color: var(--danger); }
1349
+ .file-upload__remove {
1350
+ display: inline-grid; place-content: center; width: 28px; height: 28px; padding: 0; flex: none;
1351
+ border: 0; border-radius: var(--radius-full); background: transparent;
1352
+ color: var(--text-muted); cursor: pointer;
1353
+ transition: background-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
1354
+ }
1355
+ .file-upload__remove:hover { background: var(--surface-3); color: var(--text-primary); }
1356
+ .file-upload__remove svg { width: 16px; height: 16px; }
1357
+
1358
+ /* ============================================================
1359
+ 31. STEPPER / WIZARD — .stepper (<ol>) + .step
1360
+ Completo → check (no solo color); actual → acento.
1361
+ ============================================================ */
1362
+ .stepper { display: flex; list-style: none; margin: 0; padding: 0; }
1363
+ .step {
1364
+ position: relative; flex: 1; display: flex; flex-direction: column; align-items: center;
1365
+ gap: var(--space-2); text-align: center; min-width: 0;
1366
+ }
1367
+ .step__marker {
1368
+ position: relative; z-index: 1;
1369
+ display: grid; place-items: center; width: 32px; height: 32px; flex: none;
1370
+ border-radius: var(--radius-full); border: 2px solid var(--border);
1371
+ background: var(--surface-1); color: var(--text-muted);
1372
+ font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 600; font-variant-numeric: tabular-nums;
1373
+ }
1374
+ .step__marker svg { width: 16px; height: 16px; }
1375
+ .step__label { font-size: var(--text-sm); color: var(--text-muted); }
1376
+ .step__connector {
1377
+ position: absolute; top: 15px; left: 50%; z-index: 0;
1378
+ width: 100%; height: 2px; background: var(--border);
1379
+ }
1380
+ /* Completo */
1381
+ .step--complete .step__marker { background: var(--accent-400); border-color: var(--accent-400); color: var(--text-on-accent); }
1382
+ .step--complete .step__label { color: var(--text-secondary); }
1383
+ .step--complete .step__connector { background: var(--accent-400); }
1384
+ /* Actual */
1385
+ .step--current .step__marker { border-color: var(--accent-400); color: var(--accent-300); }
1386
+ .step--current .step__label { color: var(--text-primary); font-weight: 600; }
1387
+ /* Próximo = valores por defecto (atenuado) */
1388
+
1389
+ /* Variante vertical */
1390
+ .stepper--vertical { flex-direction: column; }
1391
+ .stepper--vertical .step { flex-direction: row; align-items: flex-start; gap: var(--space-3); text-align: left; padding-bottom: var(--space-6); }
1392
+ .stepper--vertical .step:last-child { padding-bottom: 0; }
1393
+ .stepper--vertical .step__label { padding-top: var(--space-1); }
1394
+ .stepper--vertical .step__connector { top: 34px; left: 15px; width: 2px; height: calc(100% - 34px); }
1395
+
1396
+ /* ============================================================
1397
+ 32. KBD — .kbd (tecla / atajo de teclado)
1398
+ Combos: varias .kbd con un "+" literal entre ellas.
1399
+ ============================================================ */
1400
+ .kbd {
1401
+ display: inline-flex; align-items: center; justify-content: center;
1402
+ min-width: 20px; height: 20px; padding: 0 6px;
1403
+ font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 500; line-height: 1;
1404
+ color: var(--text-secondary); background: var(--surface-2);
1405
+ border: 1px solid var(--border-strong); border-radius: 6px; box-shadow: var(--shadow-1);
1406
+ }