/* ═══════════════════════════════════════════════════════════════════════════
   Embassy — amalgamaco/amalgama-design-system @ f657a24
   GENERADO por build.mjs: variables + base + composition + layout + components,
   con los imports del barrel resueltos. No se edita a mano.
   Se vendoriza a propósito: una pantalla de mostrador no puede depender de que
   la red del cliente alcance un CDN para dibujarse.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ═══════════════════════════════════════
   Amalgama Design System — Tokens
   Source of truth: Embassy UI Figma file
   (H7roIg3WybZXZhse6H7pVg)
═══════════════════════════════════════ */

:root {
  /* ─────────────────────────────────────
     Neutral palette
  ───────────────────────────────────── */
  --neutral-10:  #FAFBFC;
  /* El fondo de una app vive entre 10 y 50, y ese tramo estaba vacío: 10 contra
     blanco da 1.036:1, o sea que fondo y tarjeta eran el mismo color. 30 da 1.10:1,
     que es el escalón mínimo para que un grupo se lea como grupo sin dibujar una
     línea. Ver MOBILE.md §6b·1. */
  --neutral-30:  #F3F4F6;
  --neutral-50:  #EAEBED;
  --neutral-100: #BFC1C8;
  --neutral-200: #9FA3AE;
  --neutral-300: #747989;
  /* El escalón de texto apagado que faltaba. 300 no se puede usar para texto (4.34:1,
     no llega a AA) y 400 no está apagado (8.39:1, casi cuerpo). 350 da 5.54:1 sobre
     tarjeta, 5.03:1 sobre el fondo de página y 4.64:1 sobre surface-variant: pasa AA
     en los tres y por fin se lee como jerarquía. Ver FAILURES.md M13. */
  --neutral-350: #63687A;
  --neutral-400: #474D61;
  --neutral-500: #353A4A;
  --neutral-600: #282C39;
  --neutral-700: #1C202C;
  --neutral-750: #171B26; /* el escalon que faltaba entre 800 y 700 — sin el,
                             --color-surface-container-low en dark resolvia al MISMO
                             #13161F que --color-surface y un panel en ese token era
                             invisible salvo por su borde (medido sep 2026) */
  --neutral-800: #13161F;
  --neutral-900: #0A0C12;
  --neutral-black: #000000;
  --neutral-white: #FFFFFF;

  /* ─────────────────────────────────────
     Primary palette  (navy blues)
  ───────────────────────────────────── */
  --primary-50:  #E4E7EF;
  --primary-60:  #D7DEFB; /* periwinkle-blue tint — Primary Container (light) */
  --primary-75:  #D6DBE8; /* gris-navy claro con cuerpo */
  --primary-100: #BCC2D8;
  --primary-200: #909BBE;
  --primary-300: #6675A4;
  --primary-400: #475993;
  --primary-500: #253E82;
  --primary-600: #1F387A;
  --primary-700: #162F6F;
  --primary-800: #0D2663;
  --primary-900: #01164D;

  /* ─────────────────────────────────────
     Secondary palette  (periwinkle blues)
  ───────────────────────────────────── */
  --secondary-10:  #FBFCFF;
  --secondary-50:  #EDF2FF;
  --secondary-100: #DCE6FF;
  --secondary-200: #CAD9FF;
  --secondary-300: #B9CCFF;
  --secondary-400: #A7C0FF;
  --secondary-500: #95B3FF;
  --secondary-600: #84A6FF;
  --secondary-700: #7299FF;
  --secondary-800: #618DFF;
  --secondary-900: #4F80FF;
  --secondary-925: #3A5BB0; /* mid-dark periwinkle — Secondary Container en dark (tono ~40) */
  --secondary-950: #1E3A8C; /* dark agile-blue — accessible "on" text within the Secondary family */

  /* ─────────────────────────────────────
     Tertiary palette (purple / violet)
  ───────────────────────────────────── */
  --tertiary-50:  #F1EEFE;
  --tertiary-100: #E3DEFD;
  --tertiary-200: #D4CDFC;
  --tertiary-300: #C6BCFB;
  --tertiary-400: #B8ACFA;
  --tertiary-500: #AA9BF8;
  --tertiary-600: #9C8AF7;
  --tertiary-700: #8D79F6;
  --tertiary-800: #7F69F5;
  --tertiary-900: #7158F4;
  --tertiary-950: #4A3BA8;  /* el escalon que faltaba. Todas las otras familias acento lo
                              tienen y por el mismo motivo: sin el, on-tertiary-container
                              se quedaba en el -900 y daba 3.66:1 sobre el container claro,
                              y el container oscuro tenia que usar el -800, un lila brillante
                              con luminancia 0.212 cuando el resto de los containers oscuros
                              viven entre 0.045 y 0.115. Fallaba AA en los dos temas. */

  /* ─────────────────────────────────────
     Success palette
  ───────────────────────────────────── */
  --success-50:  #E4F8EC;
  --success-100: #BFECD1;
  --success-200: #93E1B3;
  --success-300: #60D594;
  --success-400: #28CB7C;
  --success-500: #00C164;
  --success-600: #00B15A;
  --success-700: #009E4D;
  --success-800: #008D40;
  --success-900: #006D2C;
  --success-950: #004D1E;  /* container en oscuro — el -900 queda en 4.24:1 */

  /* ─────────────────────────────────────
     Error palette
  ───────────────────────────────────── */
  --error-50:  #FFEBF0;
  --error-100: #FFCDD7;
  --error-200: #F29AA1;
  --error-300: #EA727D;
  --error-400: #F64F5D;
  --error-500: #FD3A45;
  --error-600: #EE3043;
  --error-700: #DB263C;
  --error-800: #CE1D35;
  --error-900: #BF0B29;
  --error-950: #7A0C1B;    /* container en oscuro — el -700 era demasiado claro */

  /* ─────────────────────────────────────
     Warning palette
  ───────────────────────────────────── */
  --warning-50:  #FFF2DF;
  --warning-100: #FFDEB0;
  --warning-200: #FFC97D;
  --warning-400: #FFA120;
  --warning-500: #FFB249;
  --warning-700: #FC7400;
  --warning-800: #F66200;
  --warning-900: #ED4403;
  --warning-925: #C43800;  /* texto y on-container en claro — el -900 da 3.50:1 */
  --warning-950: #7A3200;  /* container en oscuro — el -900 es naranja saturado */

  /* ─────────────────────────────────────
     Information palette
  ───────────────────────────────────── */
  --info-50:  #E9EBFD;
  --info-100: #C7CCFA;
  --info-200: #9FABF8;
  --info-300: #748AF5;
  --info-400: #4F6EF3;
  --info-500: #1E52EE;
  --info-600: #1749E2;
  --info-700: #003ED6;
  --info-800: #0032CB;
  --info-900: #001ABA;

  /* ─────────────────────────────────────
     Color Roles (semantic) — capa de uso
     Los componentes consumen SOLO estos tokens (o sus alias).
     Cada rol apunta a un primitivo de la paleta Embassy.
  ───────────────────────────────────── */
  /* Primary · Navy */
  --color-primary:                   var(--primary-900);
  --color-on-primary:                var(--neutral-white);
  --color-primary-container:         var(--primary-60);
  --color-on-primary-container:      var(--primary-900);
  --color-primary-hover:             var(--primary-700);
  /* Secondary · Agile Blue */
  --color-secondary:                 var(--secondary-900);
  /* Texto oscuro, no blanco. El acento en claro es un color vivo y saturado: blanco
     encima da 3.58:1 en Embassy y 3.26:1 en un verde de marca, o sea por debajo de AA
     para texto normal. Con primary-900 da 4.79:1 en Embassy y 5.11:1 en Nortia, y
     escala a cualquier marca porque el 900 de la rampa siempre es el paso más oscuro.
     En dark el rol ya usa secondary-950 sobre secondary-300 (6.45:1) y no cambia. */
  --color-on-secondary:              var(--primary-900);
  /* Container = secondary-200 + on = primary navy: el botón/chip Secondary
     (tonal) se diferencia del Primary por inversión (claro + texto navy). */
  --color-secondary-container:       var(--secondary-200);
  --color-on-secondary-container:    var(--primary-900);
  /* Tertiary (Purple) */
  --color-tertiary:                    var(--tertiary-900);
  --color-on-tertiary:                 var(--neutral-white);
  --color-tertiary-container:          var(--tertiary-100);
  --color-on-tertiary-container:       var(--tertiary-950);  /* 6.5:1. El -900 daba 3.66:1 */
  /* Error */
  --color-error:                     var(--error-500);
  --color-on-error:                  var(--neutral-white);
  --color-error-container:           var(--error-50);
  --color-on-error-container:        var(--error-900);
  --color-error-hover:               var(--error-700);
  /* El RELLENO de un botón lleno, separado de la señal.
     --color-error es el rojo de señal: sirve para un borde de campo, un punto de
     estado o un anillo de foco, donde WCAG pide 3:1 porque son objetos gráficos.
     Como superficie con texto encima es otra cosa: blanco sobre #FD3A45 da
     3.58:1 y no pasa AA. Un token, dos trabajos.
     El -900 ya existe en la rampa: 6.37:1 con blanco. En OSCURO no se toca —
     ahí el par ya da 6.78:1 y pisarlo sería empeorarlo. */
  --color-error-fill:                var(--error-900);
  --color-error-fill-hover:          var(--error-950);
  /* Success */
  --color-success:                   var(--success-500);
  --color-on-success:                var(--neutral-white);
  --color-success-container:         var(--success-50);
  --color-on-success-container:      var(--success-900);
  --color-success-hover:             var(--success-700);
  /* Idem verde, y acá el caso era peor: blanco sobre #00C164 da 2.38:1.
     El -900 da 6.51:1. En oscuro tampoco se toca (8.96:1). */
  --color-success-fill:              var(--success-900);
  --color-success-fill-hover:        var(--success-950);
  /* Warning */
  --color-warning:                   var(--warning-500);
  --color-on-warning:                var(--neutral-900);
  --color-warning-container:         var(--warning-50);
  --color-on-warning-container:      var(--warning-925);
  /* Info */
  --color-info:                      var(--info-500);
  --color-on-info:                   var(--neutral-white);
  --color-info-container:            var(--info-50);
  --color-on-info-container:         var(--info-900);
  /* Background & Surface */
  --color-surface:                   var(--neutral-30);
  --color-surface-dim:               var(--neutral-50);
  --color-surface-bright:            var(--neutral-white);
  --color-surface-container-lowest:  var(--neutral-10);
  --color-surface-container-low:     var(--neutral-10);
  --color-surface-container:         var(--neutral-white);
  --color-surface-container-high:    var(--neutral-50);
  --color-surface-container-highest: var(--neutral-100);
  --color-surface-variant:           var(--neutral-50);
  --color-on-surface:                var(--neutral-900);
  --color-on-surface-variant:        var(--neutral-350);  /* texto de énfasis medio (#63687A) — antes neutral-400, que a 8.39:1 no se leía como apagado */
  /* Outline · bordes (outline = más prominente/oscuro; variant = sutil) */
  --color-outline:                   var(--neutral-200);  /* #9FA3AE */
  --color-outline-variant:           var(--neutral-100);  /* #BFC1C8 */
  /* Inverse */
  --color-inverse-surface:           var(--neutral-900);
  --color-inverse-on-surface:        var(--neutral-50);
  --color-inverse-primary:           var(--secondary-900);
  /* Disabled */
  --color-disabled:                  var(--neutral-100);
  --color-on-disabled:               var(--neutral-300);
  /* Focus & Overlay */
  --color-focus:                     var(--secondary-900);
  /* Anillo de foco: derivado de --color-focus (single source of truth) en vez de
     hardcodear rgba(79,128,255,.15). --color-focus no tiene override en dark
     (señal de foco consistente entre temas), así que el valor resuelto es el
     mismo en ambos temas — pero ahora sigue automáticamente cualquier cambio de
     --color-focus, sin duplicar el hex. */
  --color-focus-ring:                color-mix(in srgb, var(--color-focus) 15%, transparent);
  /* Ring de error (inputs .is-error): derivado de --color-error vía color-mix,
     por lo que se recalibra solo en dark. Nunca hardcodear el rgba. */
  --color-error-ring:                color-mix(in srgb, var(--color-error) 12%, transparent);
  --color-scrim:                     rgba(0,0,0,.32);

  /* ─────────────────────────────────────
     Button · Secondary (rol de componente — NEUTRAL/tonal)
     Tratamiento NEUTRO basado en superficie, idéntico en ambos temas:
     una superficie tonal recesiva + borde sutil + texto on-surface.
     NO consume el rol de acento Secondary (agile blue) — así el botón
     Secondary siempre lee como menor jerarquía frente al Primary (filled),
     en Light Y Dark. Los tokens semánticos ya son theme-aware, por eso
     se definen UNA sola vez (sin override por tema). Sin hardcodes.
  ───────────────────────────────────── */
  /* Restaurados en sep-2026. Habían sido eliminados y btn-secondary pasó a consumir
     --color-secondary-container directamente, o sea el mismo token que --color-nav-selected:
     el item seleccionado del menú y el botón secundario salían idénticos. En todo el resto
     del sistema secondary-container significa SELECCIONADO (chip, segmented, pagination,
     toggle, calendar, list); el botón era el único lugar donde significaba "acción". Así que
     el que se movió fue el botón, y vuelve al tratamiento neutro que describe el bloque de
     arriba. Los roles ya son theme-aware: se definen una sola vez, sin override por tema. */
  --button-secondary-bg:        var(--color-surface-container-high);
  --button-secondary-fg:        var(--color-on-surface);
  /* El relleno neutro contrasta 1.19:1 con la tarjeta: el borde ES lo que identifica
     al control, así que tiene que llegar a 3:1 (WCAG 1.4.11). En claro --color-outline
     es neutral-200 y se queda en 2.52:1, por eso acá va neutral-300 → 4.34:1. En dark
     --color-outline YA es neutral-300 y encima viene teñido por la marca: ahí se usa
     el rol (ver el bloque [data-theme="dark"]). */
  --button-secondary-border:    var(--neutral-300);
  --button-secondary-bg-hover:  var(--color-surface-container-highest);


  /* ─────────────────────────────────────
     Semantic aliases
  ───────────────────────────────────── */
  --bg:             var(--color-surface);
  --surface:        var(--color-surface-dim);
  --sidebar-bg:     var(--color-surface-container);
  --card-bg:        var(--color-surface-container);
  /* Borde de CONTENEDORES (cards, tablas, paneles): on-surface al 10% de opacidad.
     Decisión de diseño (jun 2026): el chrome de contenedores se atenúa para que el
     outline de elementos INTERACTIVOS (--color-outline, botones/chips/inputs) se
     distinga con claridad. Theme-aware vía color-mix — sin override en dark. */
  --border:         color-mix(in srgb, var(--color-on-surface) 10%, transparent);
  --divider:        var(--border);

  /* Tipografía de página (decisión de diseño, jun 2026): el texto principal
     —títulos y cuerpo— usa el NAVY BRAND (primary-900), no el casi-negro
     neutral-900. Igual que el sitio de docs. En dark se recalibra a
     neutral-50 (override abajo). --color-on-surface queda para contenido
     interno de componentes (labels de chips, botones, celdas). */
  --text-primary:   var(--primary-900);
  --text-secondary: var(--neutral-500);
  --text-muted:     var(--color-on-surface-variant);  /* NO --color-on-disabled: pinta texto activo, y ahí el mínimo AA sí rige */

  /* Estados como TEXTO sobre una superficie.
     --color-success / --color-error están calibrados para funcionar como FONDO
     con su par -on-* encima; usados como color de texto no llegan a AA en claro
     (2.30:1 y 3.45:1 medidos). Estos roles son el mismo significado con el
     contraste que corresponde. El color de marca y el color legible no tienen
     por qué ser el mismo hex, y forzarlos a serlo es lo que rompía los KPIs. */
  /* El acento cuando hay que LEERLO. --color-secondary es #4F80FF: 3.58:1 sobre
     blanco. Alcanza para un borde, un caret o un icono (WCAG pide 3:1 para
     objetos graficos), no para el label de una pestaña seleccionada. La familia
     *-text ya existia para los cuatro de estado y a secondary le faltaba: por eso
     .tab.active fallaba AA en todos los productos que usan tabs, con el token
     correcto de la familia correcta escrito en el componente.
     No usa --color-on-secondary-container (navy #01164D) como hacen los de estado,
     porque ahi el acento deja de ser azul y la pestaña activa pierde su diferencia
     con las inactivas. El -950 existe justamente para esto: 10.3:1 sobre blanco. */
  --color-secondary-text: var(--secondary-950);
  --color-success-text: var(--color-on-success-container);
  --color-error-text:   var(--color-on-error-container);
  --color-warning-text: var(--color-on-warning-container);
  --color-info-text:    var(--color-on-info-container);
  --text-on-dark:   var(--neutral-white);

  /* Interactive — secondary blue (links, nav, tabs, focus) */
  --interactive:         var(--color-secondary);
  --interactive-hover:   var(--primary-500);
  --interactive-light:   var(--color-secondary-container);

  /* Accent aliases — consumed by docs.css and docs shell */
  --accent:              var(--interactive);
  --accent-light:        var(--interactive-light);

  /* Purple (legacy tertiary aliases) */
  --tertiary-purple:       var(--color-tertiary);
  --tertiary-purple-hover: var(--tertiary-800);
  --tertiary-purple-light: var(--tertiary-50);

  /* Semantic status */
  --green:          var(--color-success);
  --green-light:    var(--color-success-container);
  --red:            var(--color-error);
  --red-light:      var(--color-error-container);
  --yellow:         var(--color-warning);
  --yellow-light:   var(--color-warning-container);
  --blue:           var(--color-info);
  --blue-light:     var(--color-info-container);

  /* ─────────────────────────────────────
     Spacing scale  (4px base grid)
     Consume these tokens — never hardcode px values.
     Usage:  padding: var(--space-4);  gap: var(--space-2);
  ───────────────────────────────────── */
  /* Medios pasos. Existen porque el interior de un componente los necesita:
     6px entre un ícono y su label es correcto y 8px queda flojo. Son parte de
     la escala, no una excepción — y por eso también responden a la densidad. */
  --space-0-5:  2px;  /* separación mínima, compensación de borde  */
  --space-1:    4px;  /* icon gaps, min separators          */
  --space-1-5:  6px;  /* ícono ↔ label, padding de control chico   */
  --space-2:    8px;  /* inline element gaps                */
  --space-2-5: 10px;  /* padding vertical de controles compactos   */
  --space-3:   12px;  /* badge / chip padding               */
  --space-3-5: 14px;  /* padding de editor, celda cómoda           */
  --space-4:   16px;  /* compact card padding               */
  --space-5:   20px;   /* form field gaps                    */
  --space-6:   24px;   /* standard card padding              */
  --space-8:   32px;   /* medium section padding             */
  --space-10: 40px;   /* block separators                   */
  --space-12: 48px;   /* main content padding               */
  --space-16: 64px;   /* section separators                 */
  --space-20: 80px;   /* hero / large section padding       */

  /* ─────────────────────────────────────
     Densidad y superficie táctil

     Los 44px de target vivían sueltos dentro de media queries en
     components.css, así que no existían como decisión: eran un parche por
     archivo. Acá son un token, y en [data-platform="native"] suben a 48.
     --target-min es un PISO: un control puede ser más alto, nunca más bajo.
  ───────────────────────────────────── */
  --target-min:     44px;  /* WCAG 2.5.5 / iOS. En nativo: 48 */
  --row-height:     36px;  /* fila de tabla y de lista en escritorio */
  --control-height: 36px;  /* botón, input y select por defecto */

  /* ─────────────────────────────────────
     La capa de pantalla — la densidad con la que se dibuja la composición

     Las clases de composition.css (.screen-anchor, .screen-group, .screen-row…)
     son el PATRÓN y valen igual en una pantalla de producto y en una app. Estos
     ocho tokens son la DENSIDAD, y por eso son lo único que cambia entre las dos
     — acá los valores de escritorio; [data-platform="native"] los sube.

     Un proyecto los redeclara en su capa de marca y sigue teniendo exactamente
     las mismas reglas. Lo que no se ajusta desde acá: --target-min (es
     accesibilidad) y el peso de la fila (es la falla M13).
     Ver COMPOSICION.md Parte III·d y MOBILE.md §6e.
  ───────────────────────────────────── */
  --screen-block-gap:      24px;  /* entre bloques: secciones, grupos, paneles   */
  --screen-block-padding:  20px;  /* adentro de un bloque de encabezado          */
  --screen-group-padding:  16px;  /* adentro de un grupo: el inset del divisor   */
  --screen-row-height:     44px;  /* fila de etiqueta/dato                       */
  --screen-item-height:    56px;  /* fila con ícono guía                         */
  --screen-item-icon:      32px;  /* el contenedor del ícono guía                */
  --screen-item-gap:       12px;  /* entre el ícono y el texto                   */
  --screen-radius:  var(--radius-lg);  /* el radio del ancla y de los grupos     */

  /* ─────────────────────────────────────
     Iconografía — grosor de trazo

     Un solo valor para todos los íconos del sistema. Antes convivían seis
     grosores distintos (1, 1.5, 2, 2.2, 2.5, 3) sin ninguna regla, así que dos
     íconos vecinos podían tener peso distinto sin que nadie lo hubiera decidido.

     Es también un eje de marca: 1.5 se lee liviano y elegante, 2.5 se lee
     robusto y utilitario. Se overridea en el brand layer, nunca por componente.

     Escape hatch: un ícono que de verdad necesita otro peso lo declara inline
     (style="stroke-width:3"), que gana sobre la hoja de estilos.
  ───────────────────────────────────── */
  --icon-stroke: 2;

  /* ─────────────────────────────────────
     Typography — Font families
  ───────────────────────────────────── */
  --font-heading: 'Epilogue', sans-serif;
  --font-body:    'Inter', sans-serif;
  --font-mono:    'DM Mono', monospace;

  /* ─────────────────────────────────────
     Typography — Named type scale
     Consume these tokens for consistent sizing.
     Format: --font-size-{role}  /  --line-height-{role}
  ───────────────────────────────────── */

  /* Registro editorial — SOLO para el título de apertura de una landing, una portada
     de propuesta o un reporte. No entra a producto: la escala de abajo es la de las
     pantallas y no se toca.
     Existe porque la escala de producto arranca en 28px, y a 28px un hero se lee
     tímido — que es exactamente el default de una página generada: todo un poco
     grande y nada dominante. La identidad no está en agrandar todo, está en el
     CONTRASTE entre un título de 96 y un cuerpo de 13.5.
     Uno solo por página. Dos títulos editoriales son dos aperturas. */
  --font-size-editorial-lg: clamp(52px, 8vw, 96px);  --line-height-editorial-lg: 1.02;
  --font-size-editorial-md: clamp(40px, 6vw, 68px);  --line-height-editorial-md: 1.06;
  --font-size-editorial-sm: clamp(32px, 4.5vw, 46px); --line-height-editorial-sm: 1.1;
  /* A este tamaño el tracking se mide en em, no en px: -0.4px no se nota en 96 y
     desarma un título de 32. */
  --letter-spacing-editorial: -0.02em;

  /* Epilogue — Display & Headings */
  --font-size-display:     28px;   --line-height-display:     1.15;
  --font-size-heading-xl:  24px;   --line-height-heading-xl:  1.2;
  --font-size-heading-lg:  22px;   --line-height-heading-lg:  1.25;
  --font-size-heading-md:  18px;   --line-height-heading-md:  1.3;
  --font-size-heading-sm:  17px;   --line-height-heading-sm:  1.35;
  --font-size-heading-xs:  15px;   --line-height-heading-xs:  1.4;

  /* Inter — Body & UI */
  --font-size-body-lg:     14px;   --line-height-body-lg:     1.5;
  --font-size-body-md:     13.5px; --line-height-body-md:     1.5;  /* base: inputs, buttons, table cells */
  --font-size-body-sm:     12.5px; --line-height-body-sm:     1.6;
  --font-size-label:       13px;   --line-height-label:       1.4;
  --font-size-caption:     12px;   --line-height-caption:     1.4;
  --font-size-badge:       11.5px; --line-height-badge:       1.2;
  --font-size-overline:    12px;   --line-height-overline:    1.2;  /* uppercase table headers */
  /* El registro chico de versalitas. La prosa del sistema ya lo describe —«versalitas
     de componente: badge, header de tabla, label del nav», 10-11px con
     --letter-spacing-label— pero no tenía token, así que el cromo lo escribía a mano
     en 25 lugares. NO es un tamaño de texto: a 10px solo se lee en mayúsculas y con
     tracking. Para texto corrido el piso sigue siendo --font-size-caption. */
  --font-size-overline-sm: 10px;   --line-height-overline-sm: 1.4;

  /* DM Mono — Monospace */
  --font-size-mono-md:     13px;   --line-height-mono-md:     1.5;
  --font-size-mono-sm:     11px;   --line-height-mono-sm:     1.4;

  /* Interlineados que NO vienen del paso, sino del contexto.
     La escala de arriba está pareada: usar --font-size-X implica --line-height-X.
     Estos dos son las únicas dos excepciones con nombre, y no agregan valores
     nuevos — son los que ya estaban, dichos por su motivo en vez de por su paso.
     Ver GOVERNANCE.md §8.3. */

  /* Una sola línea adentro de un control: botón, label, segmented button. El
     interlineado de prosa ahí no separa nada (no hay segunda línea), infla la
     altura del control y pelea con su padding. */
  --line-height-control: 1.2;

  /* Prosa adentro de un bloque de lectura: cuerpo de accordion, editor, bajada
     de empty state. Se lee en párrafo, no en renglón de UI, así que el
     interlineado lo manda el bloque y no el paso. */
  --line-height-prose:   1.6;

  /* Titular corto puesto a un tamaño de CUERPO (alert, popover, toast): el peso
     lo hace titular, pero el interlineado de cuerpo lo devolvería a párrafo. */
  --line-height-title:   1.3;

  /* Texto centrado en una caja de alto fijo: inicial de avatar, número de día
     del calendario. El interlineado no participa del centrado — participa la
     caja. Cualquier valor > 1 descentra el glifo. */
  --line-height-none:    1;

  /* ── Inicial de avatar ──────────────────────────────────────────────
     Escala PROPORCIONAL al diámetro del disco, no la escala de texto: una
     inicial no es tipografía de lectura, es una marca centrada en una caja.
     Por eso no hay --font-size-* que le sirva, y mapearla por valor es el
     error clásico (10px "es" overline-sm, 15px "es" heading-xs — y ninguno
     de los dos lo es por rol).
     La razón inicial/diámetro NO es constante y eso es deliberado: baja de
     .42 a .32 a medida que el disco crece, porque en un disco chico la
     inicial necesita proporcionalmente más cuerpo para seguir leyéndose.
     Cuatro componentes se inventaron un escalón cada uno antes de que esto
     estuviera escrito (avatar-group-count, kanban, person-card, vacancy).
     Si necesitás un disco que no está acá, agregá el escalón ACÁ. */
  --avatar-size-xs: 24px;  --avatar-initial-xs: 10px;
  --avatar-size-sm: 28px;  --avatar-initial-sm: 11px;
  --avatar-size-md: 36px;  --avatar-initial-md: 13px;
  --avatar-size-lg: 44px;  --avatar-initial-lg: 15px;
  --avatar-size-xl: 56px;  --avatar-initial-xl: 18px;

  /* Font weights */
  --font-weight-regular:   400;
  --font-weight-medium:    500;
  --font-weight-semibold:  600;
  --font-weight-bold:      700;
  --font-weight-extrabold: 800;

  /* Letter spacing (heading only) */
  /* Escala cerrada. No hay tracking libre: si un texto necesita uno que no está
     acá, no lo necesita. Los dos positivos son los ÚNICOS, y no son
     intercambiables — ver COMPOSICION.md, eje A. */
  --letter-spacing-display:    -0.5px;
  --letter-spacing-heading-xl: -0.4px;
  --letter-spacing-heading-lg: -0.3px;
  --letter-spacing-heading-md: -0.2px;
  /* Overline de página, a 12px. Apretado a propósito: el default de una página
     generada es 0.12–0.16em y ese tracking amplio es lo que la vuelve reconocible
     a diez metros. Lo usan el rótulo de página (`.overline`, en Epilogue) y el
     índice de sección y los atajos de teclado (en mono): comparten el VALOR, no la
     familia. */
  --letter-spacing-overline:    0.04em;
  /* Versalitas de componente (badge, header de tabla, label del nav, acción del
     toast): 10–11px. Acá el tracking apretado se lee PEOR — a ese tamaño las
     mayúsculas necesitan aire para separarse. No unificar con el de arriba:
     resuelven problemas distintos. */
  --letter-spacing-label:       0.08em;
  --letter-spacing-normal:      0;

  /* ─────────────────────────────────────
     Layout
  ───────────────────────────────────── */
  /* El riel siempre visible del app shell. 112px y no 72: los labels mas largos
     —Foundations 68.9px, Components 71.4px— no ENTRABAN en los 55px utiles que
     dejaban 72px menos el padding, y como el label va con white-space:nowrap se
     salian del padding y el riel los recortaba. El aire no era poco, era negativo.
     A 112 quedan 95 utiles: 12px a cada lado del mas largo. */
  /* ── Contexto de superficie ──────────────────────────────────────────────
     Todo componente asumia que estaba apoyado sobre --color-surface. Metido en
     un panel con fondo propio, su relleno quedaba mal y habia que pisarlo a
     mano: medido en una consola de check-in (sep 2026), ocho de catorce
     overrides de clases publicas eran esto.

     Dos tokens, y los dos los declara el CONTENEDOR, no el componente:
       --ctx-surface         el fondo de lo que me contiene. Un componente que
                             tiene que fundirse con su contenedor lo usa.
       --ctx-surface-raised  el escalon que se despega de ese fondo. Un campo,
                             un input, un track: algo que tiene que verse
                             ENCIMA del contenedor.
     Un contenedor del DS los setea solo. Un panel propio del proyecto declara
     `data-surface="container"` y listo — sin pisar ninguna clase publica. */
  --ctx-surface:        var(--color-surface);
  --ctx-surface-raised: var(--color-surface-container);

  /* ── La pista y lo que corre sobre ella ──────────────────────────────────
     Un escalon de superficie da 1.10:1 y nada mas, en los dos temas: un
     elemento "levantado" sobre el fondo de la pagina no se despega solo, y por
     eso terminaba pidiendo un aro o una sombra que lo dibujara. Una pista
     HUNDIDA usa dos escalones en vez de uno y llega a 1.19:1 en claro y 1.24:1
     en oscuro, sin ningun borde.

     Va aparte de --ctx-surface/-raised porque la rampa CAMBIA DE DIRECCION
     entre temas: en claro container-high esta por debajo de surface, en oscuro
     por encima. Ese override vive aca una sola vez, no en cada componente que
     necesite una pista (segmented, slider, progress, toggle-group).
       --ctx-track        el canal hundido
       --ctx-track-thumb  lo que corre sobre el: el segmento activo, el pulgar */
  --ctx-track:       var(--color-surface-container-high);
  --ctx-track-thumb: var(--color-surface-container);

  --rail-width:    112px;
  --sidebar-width: 220px;
  --topbar-height: 56px;

  /* Breakpoints — referencia canónica del DS.
     Limitación CSS: las media queries NO pueden consumir var(); usá estos
     valores literales en @media y mantenelos sincronizados con estos tokens.
       md  768px  — debajo: layout de columna única; grids colapsan
       lg 1024px  — debajo: contenido compacto; sidebar puede colapsar
     Shell en mobile (resuelto jul-2026): debajo de 768px el sidebar es un
     modal navigation drawer (off-canvas + scrim + hamburguesa .shell-menu-btn)
     — implementado en layout.css, spec en GOVERNANCE.md §14.3. */
  --breakpoint-md: 768px;
  --breakpoint-lg: 1024px;

  /* ─────────────────────────────────────
     Border radius  (DS scale)
  ───────────────────────────────────── */
  --radius-sm:   4px;
  --radius-md:   8px;
  --radius-lg:   12px;
  --radius-xl:   16px;
  --radius-full: 9999px;
  /* --radius-button: NO se define acá a propósito. Los botones usan
     var(--radius-button, <el token que le toca a ese tamaño>), así que mientras nadie
     lo declare el radio sigue saliendo de la escala por tamaño (GOVERNANCE §4.3) y
     nada cambia. El tema de marca con forma `pildora` lo declara en 9999px y los
     convierte a todos en píldora de una, sin tocar tarjetas, modales ni campos.
     Es el único token de forma pensado para que una marca lo prenda o lo apague. */
  --radius:      var(--radius-lg);

  /* ─────────────────────────────────────
     Shadows
  ───────────────────────────────────── */
  --shadow-sm: 0 1px 3px rgba(28,36,56,.06);
  --shadow-md: 0 4px 16px rgba(28,36,56,.08);
  --shadow-lg: 0 8px 32px rgba(28,36,56,.12);

  /* Elevated Button elevation (MD3 elevated button). A runtime var so it can flip in
     dark — Tailwind inlines --shadow-* into utility classes (they can't recalibrate),
     so the elevated button consumes THIS via shadow-[var(--btn-elevation)]. Slightly
     stronger than the card --shadow-* so the lift reads clearly; black in dark. */
  --btn-elevation:       0 1px 2px rgba(28,36,56,.18), 0 2px 6px rgba(28,36,56,.12);
  --btn-elevation-hover: 0 2px 4px rgba(28,36,56,.20), 0 6px 16px rgba(28,36,56,.16);

  /* Nav/menu item interaction states — the "blue hover" unified across sidebar
     nav and dropdown/context menus. Derived from --color-secondary-container so
     they recalibrate automatically in dark mode with no separate override. */
  --color-nav-hover:            color-mix(in srgb, var(--color-secondary-container) 45%, transparent);
  --color-nav-hover-content:    var(--color-secondary);
  --color-nav-press:            color-mix(in srgb, var(--color-secondary-container) 70%, transparent);
  --color-nav-selected:         var(--color-secondary-container);
  --color-nav-selected-content: var(--color-on-secondary-container);

  /* ─────────────────────────────────────
     Degradés — set cerrado, construido con las primitivas de la marca.
     Salen de var(--primary-*) y var(--secondary-*) a propósito: en un proyecto de
     cliente toman SU paleta, y por eso un degradé sí se puede usar en producto ajeno
     mientras que la capa espacial no. Lo que no cambia es la regla: el degradé es una
     SUPERFICIE (una banda, una tarjeta, un titular grande), nunca una mancha flotando
     ni un vidrio esmerilado — eso sigue siendo la falla H2.
     Tres, y no hay un cuarto:
  ───────────────────────────────────── */
  /* Fuerte: una banda o una tarjeta que tiene que pesar. Texto claro encima. */
  --gradient-brand:   linear-gradient(135deg, var(--primary-900) 0%, var(--secondary-925) 100%);
  /* Sutil: separa una sección del fondo sin dibujar una caja. Texto normal encima. */
  --gradient-surface: linear-gradient(180deg,
                        color-mix(in srgb, var(--color-secondary) 10%, var(--color-surface)) 0%,
                        var(--color-surface) 100%);
  /* Halo: el foco de luz detrás de una apertura. Es el mismo gesto que --space-glow,
     pero con el color de la marca que corresponda. */
  --gradient-glow:    radial-gradient(60% 100% at 50% 0%,
                        color-mix(in srgb, var(--color-secondary) 35%, transparent) 0%,
                        transparent 70%);

  /* Medida de línea. El límite de legibilidad no depende del ancho del layout:
     una página borde a borde sigue necesitando que el TEXTO corte a tiempo. Por eso
     es un token propio y no el ancho del contenedor (COMPOSICION.md, estructura). */
  --measure:      68ch;   /* texto corrido */
  --measure-lead: 52ch;   /* bajadas y párrafos de apertura, más cortos */

  /* ─────────────────────────────────────
     Transitions
  ───────────────────────────────────── */
  /* Standard scheme — functional, minimal bounce (MD3 fallback for Web) */
  --ease-default:    cubic-bezier(.4,0,.2,1);
  --ease-enter:      cubic-bezier(0,0,0,1);
  --ease-exit:       cubic-bezier(.3,0,1,1);
  /* Expressive scheme — CSS-engineered overshoot approximating MD3's spring
     physics (which MD3 itself says has no native Web/CSS implementation).
     Spatial use only (movement/scale/rotate) — MD3 explicitly excludes
     effects (opacity/color) from overshoot, so never pair these with a
     color/opacity transition. */
  --ease-expressive:        cubic-bezier(.34,1.56,.64,1);
  --ease-expressive-enter:  cubic-bezier(.175,.885,.32,1.4);
  --ease-expressive-exit:   cubic-bezier(.3,0,.8,.15);
  /* Emphasized decelerate — the shared Sheet curve. Smooth, no overshoot;
     the shared curve for every edge-anchored Sheet (Side + Bottom Sheet)
     enter/exit as one motion system. Use for large edge-anchored surfaces. */
  --ease-emphasized: cubic-bezier(.32,.72,0,1);
  --duration-fast:   120ms;
  --duration-normal: 200ms;
  --duration-medium: 300ms;
  --duration-slow:   450ms;
  /* Shared Sheet slide duration (500ms) for all sides. */
  --duration-sheet: 500ms;
}

/* ═══════════════════════════════════════
   Dark Mode — Color Roles (semantic)
   Recalibra SOLO la capa semántica --color-*.
   Los componentes consumen estos roles, nunca primitivos.

   Nota: en dark, el shell (style inline de index.html) reasigna
   --neutral-50 / -100 / -900 a valores de superficie/texto, por lo
   que aquí los roles se escriben con el valor literal del primitivo
   correspondiente para evitar resolución cruzada.

   Superficies derivadas de la rampa Neutral profunda:
     surface-dim   neutral-900  #0A0C12   (la más apagada)
     surface       neutral-800  #13161F   (base)
     surface-bright neutral-700 #1C202C   (la más luminosa)
═══════════════════════════════════════ */
[data-theme="dark"] {
  /* Las familias de marca y estado (primary/secondary/tertiary/error/success/
     warning/info) se referencian con var(--primitiva) para que un tema de marca
     white-label llegue también a dark (ver WHITE-LABEL.md). Los tokens derivados
     de la rampa neutral se escriben con valor literal a propósito, por la misma
     razón documentada en el bloque [data-theme="light"] de abajo: el shell de la
     doc reasigna esos primitivos a valores de superficie y se produce resolución
     cruzada. El hex del comentario es el valor de Embassy, como referencia. */

  /* Primary · Navy → en dark mode el rol Primary se invierte a BLANCO.
     #01164D sobre #FFFFFF = 18.4:1 (WCAG AAA). Es la "Dark Mode definition"
     oficial de Embassy: máximo énfasis, máximo contraste. Single source of
     truth — todo Primary (botón relleno, FAB, CTA) lo consume desde aquí. */
  --color-primary:                   #FFFFFF; /* neutral-white */
  --color-on-primary:                var(--primary-900);          /* #01164D */
  --color-primary-container:         var(--primary-400);          /* #475993 */
  --color-on-primary-container:      var(--primary-50);           /* #E4E7EF */
  --color-primary-hover:             var(--primary-50);           /* #E4E7EF */
  /* Secondary · Agile Blue — 4 valores distintos (sin duplicación) */
  --color-secondary:                 var(--secondary-300);        /* #B9CCFF */
  /* En oscuro el acento YA es claro sobre fondo oscuro (11.31:1): el -950 de
     claro seria azul oscuro sobre casi negro. Recalibra como cualquier rol. */
  --color-secondary-text:            var(--secondary-300);        /* #B9CCFF */
  --color-on-secondary:              var(--secondary-950);        /* #1E3A8C */
  --color-secondary-container:       var(--secondary-925);        /* #3A5BB0 */
  --color-on-secondary-container:    var(--secondary-100);        /* #DCE6FF */
  /* Tertiary (Purple) */
  --color-tertiary:                  var(--tertiary-300);         /* #C6BCFB */
  --color-on-tertiary:                 #0A0C12; /* neutral-900 — máximo contraste sobre tertiary claro */
  --color-tertiary-container:        var(--tertiary-950);         /* #4A3BA8 — 7.41:1 con el -50
                                                                    encima. El -800 daba 3.51:1 y
                                                                    ademas era el unico container
                                                                    oscuro claro de la familia. */
  --color-on-tertiary-container:     var(--tertiary-50);          /* #F1EEFE */
  /* Estados · mains en dark — se ACLARAN (tono ~80) como los acentos, criterio MD3 */
  --color-error:                     var(--error-200);            /* #F29AA1 */
  --color-on-error:                  #5C0010; /* rojo profundo */
  --color-success:                   var(--success-200);          /* #93E1B3 */
  --color-on-success:                #00351A; /* verde profundo */
  /* En oscuro el relleno y la señal vuelven a ser lo mismo: el par ya da 6.78:1
     y 8.96:1 sin ayuda. Se redeclaran para que el token no arrastre el valor de
     claro. */
  --color-error-fill:                var(--error-200);            /* #F29AA1 */
  --color-error-fill-hover:          var(--error-300);
  --color-success-fill:              var(--success-200);          /* #93E1B3 */
  --color-success-fill-hover:        var(--success-300);
  --color-warning:                   var(--warning-200);          /* #FFC97D */
  --color-on-warning:                #5A2A00; /* ámbar profundo */
  --color-info:                      var(--info-200);             /* #9FABF8 */
  --color-on-info:                   #00177A; /* azul profundo */
  /* Estados · containers en dark (flip MD3: container oscuro + on-container claro) */
  --color-error-container:           var(--error-950);            /* #7A0C1B */
  --color-on-error-container:        var(--error-100);            /* #FFCDD7 */
  --color-success-container:         var(--success-950);          /* #004D1E */
  --color-on-success-container:      var(--success-200);          /* #93E1B3 */
  --color-warning-container:         var(--warning-950);          /* #7A3200 */
  --color-on-warning-container:      var(--warning-100);          /* #FFDEB0 */
  --color-info-container:            var(--info-800);             /* #0032CB */
  --color-on-info-container:         var(--info-100);             /* #C7CCFA */
  /* Surfaces — rampa Neutral profunda (separación pareja y clara) */
  --color-surface:                   #13161F; /* neutral-800 */
  --color-surface-dim:               #0A0C12; /* neutral-900 */
  --color-surface-bright:            #1C202C; /* neutral-700 */
  --color-surface-container-lowest:  #0A0C12; /* neutral-900 */
  --color-surface-container-low:     #171B26; /* neutral-750 — el escalon sobre surface */
  --color-surface-container:         #1C202C; /* neutral-700 */
  --color-surface-container-high:    #282C39; /* neutral-600 */
  --color-surface-container-highest: #353A4A; /* neutral-500 */
  --color-surface-variant:           #282C39; /* neutral-600 */
  /* La pista se invierte: en claro container-high esta DEBAJO de surface, en
     oscuro esta encima. Literales por la misma razon que el resto del bloque. */
  --ctx-track:                       #171B26; /* neutral-750 — el canal */
  --ctx-track-thumb:                 #282C39; /* neutral-600 — lo que corre sobre el */
  --color-on-surface:                #EAEBED; /* neutral-50  */
  --color-on-surface-variant:        #BFC1C8; /* neutral-100 */
  /* Outline · bordes */
  --color-outline:                   #747989; /* neutral-300 */
  --color-outline-variant:           #474D61; /* neutral-400 */
  /* Inverse */
  --color-inverse-surface:           #EAEBED; /* neutral-50  */
  --color-inverse-on-surface:        #13161F; /* neutral-800 */
  --color-inverse-primary:           var(--primary-700);          /* #162F6F */
  /* Disabled */
  --color-disabled:                  #282C39; /* neutral-600 */
  --color-on-disabled:               #747989; /* neutral-300 */

  /* Button · Secondary — el relleno y el texto ya son theme-aware por rol. El borde sí
     se reasigna: en dark --color-outline es neutral-300 (el mismo peso que en claro pide
     neutral-300) y además lo tiñe el tema de marca. 3.79:1 sobre la tarjeta. */
  --button-secondary-border:    var(--color-outline);

  /* Aliases semánticos que dependen de la rampa */
  --text-primary:   #EAEBED; /* neutral-50 — en dark el navy no contrasta; vuelve a on-surface */
  --text-secondary: #BFC1C8; /* neutral-100 */
  --text-muted:     #9FA3AE; /* neutral-200 — 6.44:1 sobre card-bg. No neutral-100: sería igual que --text-secondary */
  --interactive-hover:               var(--primary-200);          /* #909BBE */

  /* Elevated Button elevation — black shadows so the lift is visible on dark surfaces
     (the navy light shadows disappear against dark backgrounds). */
  --btn-elevation:       0 1px 2px rgba(0,0,0,.55), 0 2px 6px rgba(0,0,0,.42);
  --btn-elevation-hover: 0 2px 4px rgba(0,0,0,.60), 0 8px 20px rgba(0,0,0,.50);
}

/* Light-mode scoped override — permite usar data-theme="light" en un contenedor
   dentro de una página en modo oscuro (eg. demos de estados en la doc site).
   Restaura todos los tokens que [data-theme="dark"] sobreescribe.
   IMPORTANTE: los tokens derivados de --neutral-50/-100/-900 se escriben con
   valor literal (igual que el bloque dark) porque el shell de la doc reasigna
   esos primitivos a valores de superficie, causando resolución cruzada. */
[data-theme="light"] {
  --color-primary:                   var(--primary-900);         /* #01164D */
  --color-on-primary:                #FFFFFF;                    /* neutral-white */
  --color-primary-container:         var(--primary-60);          /* #D7DEFB */
  --color-on-primary-container:      var(--primary-900);         /* #01164D */
  --color-primary-hover:             var(--primary-700);         /* #162F6F */
  --color-secondary:                 var(--secondary-900);       /* #4F80FF */
  --color-on-secondary:              var(--primary-900);        /* texto oscuro: blanco sobre el acento no llega a AA */
  --color-secondary-container:       var(--secondary-200);       /* #CAD9FF */
  --color-on-secondary-container:    var(--primary-900);         /* #01164D */
  --color-tertiary:                  var(--tertiary-900);        /* #7158F4 */
  --color-on-tertiary:               #FFFFFF;                    /* neutral-white */
  --color-tertiary-container:        var(--tertiary-100);        /* #E3DEFD */
  --color-on-tertiary-container:     var(--tertiary-950);        /* #4A3BA8 */
  --color-error:                     var(--error-500);           /* #FD3A45 */
  --color-on-error:                  #FFFFFF;                    /* neutral-white */
  --color-error-fill:                var(--error-900);           /* #BF0B29 */
  --color-error-fill-hover:          var(--error-950);
  --color-success:                   var(--success-500);         /* #00C164 */
  --color-on-success:                #FFFFFF;                    /* neutral-white */
  --color-success-fill:              var(--success-900);         /* #006D2C */
  --color-success-fill-hover:        var(--success-950);
  --color-warning:                   var(--warning-500);         /* #FFB249 */
  --color-on-warning:                #0A0C12;                    /* neutral-900 */
  --color-info:                      var(--info-500);            /* #1E52EE */
  --color-on-info:                   #FFFFFF;                    /* neutral-white */
  --color-error-container:           var(--error-50);            /* #FFEBF0 */
  --color-on-error-container:        var(--error-900);           /* #BF0B29 */
  --color-success-container:         var(--success-50);          /* #E4F8EC */
  --color-on-success-container:      var(--success-900);         /* #006D2C */
  --color-warning-container:         var(--warning-50);          /* #FFF2DF */
  --color-on-warning-container:      var(--warning-925);         /* #C43800 — el -900 da 3.50:1.
                                                                   Este bloque es un espejo de :root
                                                                   y este token se habia quedado atras. */
  --color-info-container:            var(--info-50);             /* #E9EBFD */
  --color-on-info-container:         var(--info-900);            /* #001ABA */
  /* Surfaces — literal hex para neutrals hijackeados por el doc shell */
  --color-surface:                   #F3F4F6;                    /* neutral-30 */
  --color-surface-dim:               #EAEBED;                    /* neutral-50 */
  --color-surface-bright:            #FFFFFF;                    /* neutral-white */
  --color-surface-container-lowest:  #FAFBFC;                    /* neutral-10 */
  --color-surface-container-low:     #FAFBFC;                    /* neutral-10 */
  --color-surface-container:         #FFFFFF;                    /* neutral-white */
  --color-surface-container-high:    #EAEBED;                    /* neutral-50 */
  --color-surface-container-highest: #BFC1C8;                    /* neutral-100 */
  --color-surface-variant:           #EAEBED;                    /* neutral-50 */
  --color-on-surface:                #0A0C12;                    /* neutral-900 */
  --color-on-surface-variant:        #63687A;                    /* neutral-350 */
  /* Outline — literal hex */
  --color-outline:                   #9FA3AE;                    /* neutral-200 */
  --color-outline-variant:           #BFC1C8;                    /* neutral-100 */
  /* Restaura el borde del botón secundario: en claro necesita neutral-300 para llegar
     a 3:1, porque el rol outline es más liviano acá (ver :root §Button · Secondary). */
  --button-secondary-border:         #747989;                    /* neutral-300 */
  /* Inverse */
  --color-inverse-surface:           #0A0C12;                    /* neutral-900 */
  --color-inverse-on-surface:        #EAEBED;                    /* neutral-50 */
  --color-inverse-primary:           var(--secondary-900);       /* #4F80FF */
  /* Disabled — literal hex */
  --color-disabled:                  #BFC1C8;                    /* neutral-100 */
  --color-on-disabled:               #747989;                    /* neutral-300 */
  /* Aliases */
  --text-primary:   var(--primary-900);                          /* #01164D */
  --text-secondary: #353A4A;                                     /* neutral-500 */
  --text-muted:     #63687A;                                     /* neutral-350 — 5.54:1 sobre card-bg */
  --interactive-hover: var(--primary-500);                       /* #253E82 */
  /* Elevated Button — restore light (navy) elevation inside a light-scoped island */
  --btn-elevation:       0 1px 2px rgba(28,36,56,.18), 0 2px 6px rgba(28,36,56,.12);
  --btn-elevation-hover: 0 2px 4px rgba(28,36,56,.20), 0 6px 16px rgba(28,36,56,.16);
}

/* ═══════════════════════════════════════
   PLATAFORMA NATIVA  ·  el eje que no es claro/oscuro
   ═══════════════════════════════════════

   Embassy nació para pantallas de escritorio y su escala lo delata: el cuerpo
   base son 13,5px, que es el tamaño del *caption* de iOS. Una app nativa con
   esos valores sale chica y fuera de norma en las dos plataformas.

   Esto NO es un tema. Claro/oscuro es un eje de color; escritorio/nativo es un
   eje de tamaño y densidad. Son independientes: una app nativa en dark usa los
   dos bloques a la vez.

   Los NOMBRES no cambian — un componente lee --font-size-body-md en las dos
   plataformas y no sabe dónde está corriendo. Cambian los valores.

   Referencias: iOS body 17pt, secundario 15pt, caption 13pt · Material
   body-large 16sp, title-large 22sp · targets 44pt (iOS) y 48dp (Android).

   En web este bloque no hace nada salvo que alguien ponga data-platform="native"
   a mano. Existe acá, y no en un archivo aparte, para que scripts/build-tokens.mjs
   lo exporte con una sola fuente: tokens/embassy.tokens.ts sale con `native`
   además de `light` y `dark`.
   ═══════════════════════════════════════ */

[data-platform="native"] {
  /* ─────────────────────────────────────
     Escala tipográfica nativa
  ───────────────────────────────────── */
  --font-size-display:     32px;   --line-height-display:     1.1;
  --font-size-heading-xl:  24px;   --line-height-heading-xl:  1.2;
  --font-size-heading-lg:  22px;   --line-height-heading-lg:  1.25;
  --font-size-heading-md:  20px;   --line-height-heading-md:  1.3;
  --font-size-heading-sm:  17px;   --line-height-heading-sm:  1.35;
  --font-size-heading-xs:  15px;   --line-height-heading-xs:  1.4;

  --font-size-body-lg:     17px;   --line-height-body-lg:     1.45;
  --font-size-body-md:     16px;   --line-height-body-md:     1.5;  /* la base: inputs, botones, filas */
  --font-size-body-sm:     14px;   --line-height-body-sm:     1.45;
  --font-size-label:       15px;   --line-height-label:       1.4;
  --font-size-caption:     13px;   --line-height-caption:     1.45;
  --font-size-badge:       11px;   --line-height-badge:       1.2;

  /* El registro editorial no es fluido en nativo: no hay viewport que crezca.
     Se congela en el extremo de teléfono; en tablet se sube un escalón a mano. */
  --font-size-editorial-lg: 44px;  --line-height-editorial-lg: 1.05;
  --font-size-editorial-md: 34px;  --line-height-editorial-md: 1.1;
  --font-size-editorial-sm: 28px;  --line-height-editorial-sm: 1.15;

  /* ─────────────────────────────────────
     Densidad y superficie táctil
  ───────────────────────────────────── */
  /* 48 cubre iOS (44) y Android (48) de una. Es un piso, no un tamaño:
     un control puede ser más alto, nunca más bajo. */
  --target-min:      48px;
  --row-height:      48px;
  --control-height:  48px;

  /* En nativo no hay columna: hay pantalla. El gutter no es fluido —no hay vw
     que valga en un teléfono— y no existe max-width que acote nada.
     Por eso el token se llama distinto que el --column-gutter de composition.css:
     no es el mismo concepto y no queremos que alguien los cruce. */
  --screen-gutter:   20px;

  /* ─────────────────────────────────────
     Forma del botón

     `--radius-button` es el único token de forma que el sistema deja declarar para
     mover todos los botones de una (GOVERNANCE §4.3); en :root queda sin declarar a
     propósito para que cada tamaño use el de su escala. Acá sí se declara: en un
     teléfono el botón primario es de ancho completo, y a 8px contra grupos de 16 se
     lee como un botón de escritorio pegado adentro de una app.

     **14 es una excepción deliberada a la escala** (4 · 8 · 12 · 16) y es el único
     valor de forma fuera de escala del sistema. Sale de la pantalla que usábamos de
     referencia, y el motivo es la relación con el contenedor, no el número: los
     grupos van a `--screen-radius` (16), y el botón a 14 queda *apenas* por dentro,
     que es lo que lo hace leer anidado. A 16 sería idéntico a la tarjeta y esa
     jerarquía se pierde; a 12 el salto se nota demasiado.

     Si alguna vez se mueve `--screen-radius`, este valor se mueve con él: la regla
     es «un escalón por dentro del grupo», no «14».
     Un tema de marca con forma píldora sigue ganando: carga después. */
  --radius-button:   14px;

  /* ─────────────────────────────────────
     La capa de pantalla — lo que un proyecto SÍ puede mover

     Las convenciones de MOBILE.md §6b son el patrón y no se negocian: el dato
     pesa más que su etiqueta, el grupo va en tarjeta, el header afuera, el
     divisor insetado. Pero la DENSIDAD con la que ese patrón se dibuja es una
     decisión de cada app —una de consumo respira más que una de back-office— y
     hasta acá estaba escrita a mano adentro del CSS, así que ajustarla obligaba
     a abandonar el patrón.

     Estos ocho tokens son la superficie de ajuste. Un proyecto los redeclara en
     su capa de marca y sigue teniendo exactamente las mismas reglas.
     Lo que NO se mueve desde acá: --target-min (es accesibilidad, no densidad)
     y el peso de la fila (es la falla M13). Ver MOBILE.md §6e.
  ───────────────────────────────────── */
  --screen-block-gap:      20px;  /* entre bloques: ancla, secciones, grupos      */
  --screen-block-padding:  18px;  /* adentro del ancla                            */
  --screen-group-padding:  16px;  /* adentro de un grupo: el inset del divisor    */
  --screen-row-height:     52px;  /* fila de etiqueta/dato — piso --target-min    */
  --screen-item-height:    60px;  /* fila con ícono guía                          */
  --screen-item-icon:      36px;  /* el contenedor del ícono guía                 */
  --screen-item-gap:       13px;  /* entre el ícono y el texto                    */
  --screen-radius:  var(--radius-xl);  /* el radio del ancla y de los grupos      */
}

/* ═══════════════════════════════════════
   Base — Reset, Typography, Animations
═══════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--text-primary);
  font-size: var(--font-size-body-lg);
  line-height: var(--line-height-body-lg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings — ligados al type scale de variables.css (h1 → display … h6 → heading-xs).
   Regla del DS: todo font-size fluye por tokens --font-size-*; nunca px sueltos. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: 600;
  letter-spacing: var(--letter-spacing-heading-lg);
}

h1 { font-size: var(--font-size-display);    line-height: var(--line-height-display);    letter-spacing: var(--letter-spacing-display); }
h2 { font-size: var(--font-size-heading-xl); line-height: var(--line-height-heading-xl); letter-spacing: var(--letter-spacing-heading-xl); }
h3 { font-size: var(--font-size-heading-lg); line-height: var(--line-height-heading-lg); letter-spacing: var(--letter-spacing-heading-lg); }
h4 { font-size: var(--font-size-heading-md); line-height: var(--line-height-heading-md); letter-spacing: var(--letter-spacing-heading-md); }
h5 { font-size: var(--font-size-heading-sm); line-height: var(--line-height-heading-sm); }
h6 { font-size: var(--font-size-heading-xs); line-height: var(--line-height-heading-xs); }

a { color: var(--interactive); text-decoration: none; }
a:hover { text-decoration: underline; }

img, svg { display: block; max-width: 100%; }

/* Un solo grosor de trazo para todos los íconos. Gana sobre el atributo
   stroke-width del SVG (CSS > atributo de presentación), así que no hace falta
   tocar cada ícono. Los logos quedan afuera: su trazo es parte de la marca. */
svg:not(.logo):not(.logo *) { stroke-width: var(--icon-stroke); }

button { font-family: inherit; cursor: pointer; }

input, select, textarea { font-family: inherit; }

/* ── Scrollbar ── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--color-outline-variant); border-radius: 4px; }

/* ── Animations ── */
@keyframes fadeSlideIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes slideInRight {
  from { opacity: 0; transform: translateX(18px); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes slideInLeft {
  from { opacity: 0; transform: translateX(-18px); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes slideInUp {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* ── Utility classes ── */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); border: 0;
}

.text-muted { color: var(--text-muted); }
.text-secondary { color: var(--text-secondary); }
/* Los alias legacy --interactive / --green / --red apuntan a los tonos de SEÑAL
   (--color-secondary, --color-success, --color-error). Como fondo o como borde
   estan bien; como TEXTO chico ninguno llega a AA: daban 3.58:1, 2.38:1 y 3.58:1.
   La familia *-text existe justamente para el caso de leerlos. */
.text-accent { color: var(--color-secondary-text); }
.text-success { color: var(--color-success-text); }
.text-error { color: var(--color-error-text); }

.font-mono { font-family: var(--font-mono); }
.font-heading { font-family: var(--font-heading); }

/* ============================================================================
   Reduced motion — shared, DS-wide (a11y).
   Honors prefers-reduced-motion: reduce by neutralizing transitions and
   entrance/exit animations across EVERY Embassy component at once, so
   product screens (and generated screens) get accessible motion for free —
   no per-component opt-in needed. Near-zero (0.01ms) rather than `none` so
   JS that waits for transitionend/animationend still fires. Essential loaders
   (spinner, indeterminate progress, skeleton) become static; their role/
   aria-label still convey status. Any component that MUST keep a reduced
   looping motion opts back in with its own `@media (prefers-reduced-motion:
   reduce) { … !important }` rule (see component-rules <id>.md → motion.reducedMotion).
   ============================================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}

/* ═══════════════════════════════════════
   Composition — la estructura de la página

   Cuándo usar: SIEMPRE. Toda página que publicamos, con app shell o sin él. Define la
     estructura (columna, borde a borde, riel, dividida), la medida de línea, el ritmo entre
     secciones, el overline y el índice de sección. Es la contraparte construida de
     COMPOSICION.md: ese archivo dice qué forma tiene una página nuestra, éste la hace.
   Cuándo no: nunca se saltea. Una landing no carga layout.css (no tiene shell) pero sí carga
     esto — es justamente donde más falta hace.
   Reemplaza a: los `max-width: 1200px; margin: 0 auto` escritos a mano en cada página, y los
     eyebrow con `text-transform: uppercase; letter-spacing: .14em` inventados por proyecto.

   Dependencia: variables.css.
   Orden de carga: variables.css → marca → base.css → ESTE ARCHIVO → components.css

   Uso (una estructura por página, elegida a propósito):
   <div class="column">…</div>                      <!-- default: una columna de 1200 -->
   <div class="column column-1440">…</div>          <!-- el mismo esquema, más ancho -->
   <div class="column column-bleed">…</div>         <!-- sin ancho máximo, con márgenes igual -->
   <div class="grid-12"><div class="span-7">…</div><div class="span-5">…</div></div>
   <div class="column column-rail">…<aside>…</aside></div>
   <section class="section section-lead">…</section>
   <span class="section-index">02 / 05</span>
   <p class="overline">Informe trimestral</p>
   <h1 class="editorial-lg">Cuánto tardan las altas</h1>   <!-- uno por página, nunca en el shell -->
   <p class="measure">Texto corrido que corta a tiempo aunque la página sea ancha.</p>
   <hr class="rule">                                 <!-- fuera de .column: cruza la pantalla -->
   <p class="figure"><span class="figure-value">4,2</span><span class="figure-unit">min</span>
      <span class="figure-base">mediana · 240 altas · jul–sep 2026</span></p>
═══════════════════════════════════════ */

/* ── Estructura de página ──────────────────────────────────────────────────
   Cuatro, y se elige UNA por página. Es la decisión que más diferencia una página de
   otra — más que el color, más que el ícono. Ver COMPOSICION.md § Estructura. */

.column {
  --column-max:    1200px;
  --column-gutter: clamp(20px, 5vw, 56px);
  width: 100%;
  max-width: var(--column-max);
  margin-inline: auto;
  padding-inline: var(--column-gutter);
}

/* ── El ancho, elegido a propósito ───────────────────────────────────────
   Escala cerrada. El default de 1200 es cómodo y es el que usa todo el mundo; un
   proyecto puede querer más aire lateral o aprovechar mejor un monitor grande, y esa
   es una decisión de composición, no un número que se escribe a mano en cada página.
   Los márgenes laterales NUNCA desaparecen: el ancho crece, el aire se mantiene. */
.column-1280 { --column-max: 1280px; }
.column-1440 { --column-max: 1440px; }
.column-1600 { --column-max: 1600px; }
.column-1920 { --column-max: 1920px; }

/* Lectura y detalle: la columna se angosta, el texto manda. */
.column-read { --column-max: 800px; }
/* Un formulario o un panel de auth. */
.column-form { --column-max: 680px; }

/* Borde a borde: sin ancho máximo, con márgenes generosos que crecen con la pantalla.
   En un monitor ancho la página usa todo el ancho en vez de dejar dos vacíos a los
   costados, que es la silueta de cualquier landing generada.
   ATENCIÓN: esto libera el LAYOUT, no el texto. Los párrafos siguen cortando en
   `.measure`; sin eso son líneas de 200 caracteres y es una falla (D3). */
.column-bleed {
  --column-max: none;
  --column-gutter: clamp(24px, 6vw, 96px);
}

/* Riel: contenido ancho + una columna angosta al costado para metadatos, notas al
   margen, índice o datos de apoyo. Es la estructura más "estudio" de las cuatro y la
   que peor imita un generador, porque exige decidir qué es principal y qué es nota. */
.column-rail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(180px, 260px);
  gap: clamp(24px, 4vw, 64px);
  align-items: start;
}

/* Dividida: dos tracks asimétricos. Para heros con una herramienta o una imagen a un
   lado. 60/40 a propósito — 50/50 no dice cuál de los dos manda. */
.column-split {
  display: grid;
  grid-template-columns: 6fr 4fr;
  gap: clamp(24px, 4vw, 64px);
  align-items: center;
}

@media (max-width: 900px) {
  .column-rail,
  .column-split { grid-template-columns: minmax(0, 1fr); }
}

/* ── Grilla de 12 ──────────────────────────────────────────────────────────
   Adentro de la columna. Es lo que permite que una sección no sea una fila de bloques
   iguales: 7+5, 8+4, 5+4+3 son composiciones; 4+4+4 tres veces seguidas es la grilla
   de tres tarjetas con otro nombre (COMPOSICION.md regla 4).

   Debajo de 900px todo pasa a una columna: en un celular no hay doce de nada. */
.grid-12 {
  --grid-gap: clamp(16px, 2vw, 32px);
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--grid-gap);
  align-items: start;
}
.span-1  { grid-column: span 1; }
.span-2  { grid-column: span 2; }
.span-3  { grid-column: span 3; }
.span-4  { grid-column: span 4; }
.span-5  { grid-column: span 5; }
.span-6  { grid-column: span 6; }
.span-7  { grid-column: span 7; }
.span-8  { grid-column: span 8; }
.span-9  { grid-column: span 9; }
.span-10 { grid-column: span 10; }
.span-11 { grid-column: span 11; }
.span-12 { grid-column: span 12; }

@media (max-width: 900px) {
  .grid-12 > * { grid-column: 1 / -1; }
}

/* ── Medida de línea ───────────────────────────────────────────────────────
   El límite de legibilidad no depende del ancho del contenedor. Va en el texto. */
.measure      { max-width: var(--measure); }
.measure-lead { max-width: var(--measure-lead); }

/* ── Ritmo ─────────────────────────────────────────────────────────────────
   Una página no es una pila de bandas iguales. Tres pesos, y la diferencia entre
   `lead` y `tight` es real (2.5×), no del 10%: si todas las secciones miden lo mismo
   el ojo no encuentra dónde parar. COMPOSICION.md regla 6. */
.section       { padding-block: var(--space-12); }
.section-lead  { padding-block: var(--space-20); }
.section-tight { padding-block: var(--space-8); }

/* ── Separación entre secciones ────────────────────────────────────────────
   Una línea de 1px que cruza toda la pantalla mientras el contenido se queda en la
   columna. Es la alternativa a la banda de fondo alternado, que es lo que hace toda
   página generada, y es coherente con el eje D: borde antes que sombra.
   Va FUERA de `.column`, como hermana de la sección — si va adentro, se corta al
   ancho del contenido y pierde la mitad del efecto. */
.rule {
  border: 0;
  border-top: 1px solid var(--color-outline-variant);
  margin: 0;
}

/* ── El dato destacado ─────────────────────────────────────────────────────
   Una cifra sola en una página. El default es número gigante de color con flechita
   verde; el nuestro es sobrio: la cifra en color de texto, la unidad más chica pegada,
   y la base al lado — porque un número sin base no se publica (regla 8). */
.figure {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.figure-value {
  font-family: var(--font-heading);
  font-size: var(--font-size-editorial-sm);
  line-height: 1;
  font-weight: 600;
  letter-spacing: var(--letter-spacing-editorial);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.figure-unit {
  font-size: var(--font-size-body-lg);
  font-weight: 500;
  color: var(--text-secondary);
}
/* La base: de qué universo sale el número, en qué período. Al lado, no debajo. */
.figure-base {
  font-family: var(--font-mono);
  font-size: var(--font-size-mono-sm);
  color: var(--text-muted);
}

/* ── Aparición ─────────────────────────────────────────────────────────────
   UNA vez, en el bloque de apertura, y al cargar — no al scrollear. El fade-up de
   cada sección al entrar en pantalla es el gesto más repetido de las landings
   generadas, cuesta rendimiento y molesta a quien navega con teclado.
   Sin desplazamiento vertical: solo opacidad. El bloque ya está donde va.
   `base.css` neutraliza esto solo con prefers-reduced-motion. */
@keyframes amalgama-fade-in { from { opacity: 0; } to { opacity: 1; } }
.reveal {
  animation: amalgama-fade-in var(--duration-medium) var(--ease-enter) both;
}

/* ── Degradés ──────────────────────────────────────────────────────────────
   Tres, y salen de las primitivas de la marca: en un proyecto de cliente toman su
   paleta. Por eso un degradé sí viaja a producto ajeno y la capa espacial no.

   La regla que los separa de la mancha de IA: **un degradé es una superficie**. Pinta
   una banda, una tarjeta o un titular. No flota, no es un blob, no es un vidrio
   esmerilado y no va detrás de texto corrido. Uno por sección; dos seguidos se anulan. */
.gradient-brand   { background: var(--gradient-brand); color: var(--color-on-primary); }
.gradient-surface { background: var(--gradient-surface); }
.gradient-glow    { background: var(--gradient-glow); pointer-events: none; }

/* El titular que se desvanece. Solo en el registro editorial —texto grande—, nunca en
   cuerpo: el degradé baja el contraste en su extremo débil y a 13.5px eso es ilegible.
   El `color` va SIEMPRE como fallback: si el navegador no soporta el recorte, queda el
   color sólido en vez de un titular invisible. */
.text-gradient {
  color: var(--text-primary);
  background: linear-gradient(180deg,
    currentColor 0%,
    color-mix(in srgb, currentColor 55%, transparent) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* ── Imágenes ──────────────────────────────────────────────────────────────
   La política está en COMPOSICION.md regla 10: solo material real —capturas del
   producto, fotos del trabajo o del equipo—, nunca stock, ilustración genérica ni 3D.
   Acá está el marco, que es la parte que se puede hacer cumplir: todas las imágenes
   de una página nuestra se ven igual. */
.media {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-md);
  background: var(--color-surface-container);
}
.media-caption {
  font-family: var(--font-mono);
  font-size: var(--font-size-mono-sm);
  color: var(--text-muted);
  margin-top: var(--space-2);
}

/* ── Overline ──────────────────────────────────────────────────────────────
   El reemplazo del eyebrow. No está prohibido clasificar algo arriba del título: está
   prohibido hacerlo con el TRACKING AMPLIO (0.12–0.16em) que usa todo el mundo.
   Lo que diferencia no es la familia: es el interletrado apretado, en la tipografía
   de titulares. Va en Epilogue —la misma cara que el título que encabeza— a 0.04em,
   en gris, y con --space-5 de aire abajo: suficiente para que el título arranque
   solo, no tanto como para que dejen de leerse como una unidad.
   (Hasta sep 2026 iba en DM Mono. Se cambió porque el registro de etiqueta de
   terminal no es el de la marca; lo que la regla 1 prohíbe sigue siendo el tracking.)
   Uno por página: repetido deja de clasificar y es textura (COMPOSICION.md regla 1). */
.overline {
  font-family: var(--font-heading);
  font-size: var(--font-size-overline);
  line-height: var(--line-height-overline);
  letter-spacing: var(--letter-spacing-overline);
  font-weight: 600;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 var(--space-5);
}

/* ── Registro editorial ────────────────────────────────────────────────────
   El título de apertura de una landing, una portada o un reporte. Va en el h1, una
   sola vez por página, y NUNCA adentro del app shell: una pantalla de producto abre
   con `page-title`, no con esto.
   El cuerpo de la página sigue en la escala de producto a propósito. Lo que se
   reconoce no es el tamaño del título: es la distancia entre el título y el resto. */
.editorial-lg,
.editorial-md,
.editorial-sm {
  font-family: var(--font-heading);
  font-weight: 600;
  letter-spacing: var(--letter-spacing-editorial);
  text-wrap: balance;          /* que no quede una palabra huérfana en la última línea */
  margin: 0 0 var(--space-4);
  max-width: 18ch;             /* un titular largo se parte en columnas, no en una línea eterna */
}
.editorial-lg { font-size: var(--font-size-editorial-lg); line-height: var(--line-height-editorial-lg); }
.editorial-md { font-size: var(--font-size-editorial-md); line-height: var(--line-height-editorial-md); }
.editorial-sm { font-size: var(--font-size-editorial-sm); line-height: var(--line-height-editorial-sm); max-width: 24ch; }

/* ── Índice de sección — el gesto de Amalgama ──────────────────────────────
   Va en todo lo que publicamos. Es el eje E de COMPOSICION.md: el marcador formal
   repetido que hace reconocible al estudio más allá del color y la tipografía.
   Numera el total real de secciones de la página («02 / 05»), no una secuencia
   inventada, y nunca se reinicia ni se saltea una. */
.section-index {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--font-size-overline);
  line-height: var(--line-height-overline);
  letter-spacing: var(--letter-spacing-overline);
  color: var(--color-secondary);
  margin-bottom: var(--space-2-5);
  font-variant-numeric: tabular-nums;
}
/* La barra separadora se apaga para que los números manden. */
.section-index .sep { color: var(--color-outline); margin-inline: var(--space-1); }


/* ═══════════════════════════════════════════════════════════════════════════
   LA CAPA DE PANTALLA

   La anatomía de una pantalla —de producto o nativa— como clases: el ancla, el
   header de sección, el grupo, la fila, la acción. Las reglas están en
   COMPOSICION.md Parte III (producto) y MOBILE.md §6b (nativa), y son las mismas:
   la jerarquía la hace la superficie, el dato pesa más que su etiqueta, el header
   va afuera del grupo, el divisor insetado, la acción primaria anclada.

   **Las clases son el patrón; los tokens son la densidad.** Por eso esto NO está
   scopeado a [data-platform="native"]: una pantalla de producto en desktop y una
   pantalla de app son la misma composición en dos densidades, y escribirlas dos
   veces garantiza que diverjan. Lo que cambia son los ocho tokens --screen-*:
   acá están los valores de escritorio, y el bloque nativo de variables.css los
   sube. Un proyecto los redeclara y sigue teniendo el mismo patrón.

   Existe porque describir no alcanza: mientras esto vivió sólo en prosa, cada
   pantalla la volvía a derivar a mano y cada derivación traía sus propios errores
   —la etiqueta pesando más que el dato, el chip un escalón grande, un borde
   encima del escalón de superficie—. Ninguno lo agarra un script.
   ═══════════════════════════════════════════════════════════════════════════ */


/* La pantalla: una columna, gutter a los lados, la acción primaria afuera del
   scroll. El fondo es --color-surface y los bloques --color-surface-container:
   ese escalón es lo que agrupa (M10). */
.screen {
  display: flex;
  flex-direction: column;
  gap: var(--screen-block-gap);
  padding: 8px var(--screen-gutter) 0;
  background: var(--color-surface);
}

/* §6b·5 — El ancla. Lo único invertido de la pantalla: concentra identidad y
   estado en un punto de entrada, antes del contenido. Una sola por pantalla. */
.screen-anchor {
  background: var(--color-primary);
  color: var(--color-on-primary);
  border-radius: var(--screen-radius);
  padding: var(--screen-block-padding);
  display: flex;
  flex-direction: column;
  gap: 16px;

  /* El ancla REDEFINE los roles para lo que lleve adentro, en vez de pedir
     variantes nuevas de cada componente.

     Los componentes de Embassy leen roles (`var(--text-muted)`,
     `var(--color-surface-variant)`), nunca literales. Redefinir esos roles acá
     hace que un `.badge`, un `.progress` o un `.avatar` puestos adentro se
     retiñan solos sobre la superficie invertida — sin `.badge-on-dark`, sin
     overrides por componente, y sin que nadie tenga que acordarse.

     Funciona porque la sustitución de custom properties resuelve en el elemento
     que LEE la variable: el hijo hereda estos valores y su propio
     `color: var(--text-muted)` resuelve contra ellos. */
  --text-primary:          var(--color-on-primary);
  --text-secondary:        color-mix(in srgb, var(--color-on-primary) 80%, transparent);
  --text-muted:            color-mix(in srgb, var(--color-on-primary) 66%, transparent);
  --color-surface:         transparent;
  --color-surface-container: color-mix(in srgb, var(--color-primary-container) 16%, transparent);
  --color-surface-variant: color-mix(in srgb, var(--color-on-primary) 16%, transparent);
  --color-outline-variant: color-mix(in srgb, var(--color-on-primary) 24%, transparent);
}
.screen-anchor-top { display: flex; align-items: center; gap: var(--screen-item-gap); }
.screen-anchor-id { flex: 1 1 auto; min-width: 0; }
.screen-anchor-name {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: var(--font-size-heading-lg);
  line-height: var(--line-height-heading-lg);
  letter-spacing: -.015em;
}
.screen-anchor-meta {
  font-size: var(--font-size-caption);
  line-height: var(--line-height-caption);
  color: color-mix(in srgb, var(--color-on-primary) 66%, transparent);
  margin-top: 3px;
}
/* La línea del número: la cifra a la izquierda, un dato de apoyo a la derecha,
   alineados por línea base y no por caja — con align-items:center la cifra
   grande y el texto chico quedan visiblemente desfasados. */
.screen-anchor-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  margin-bottom: 9px;
}
.screen-anchor-aside {
  font-size: var(--font-size-caption);
  line-height: var(--line-height-caption);
  color: var(--text-muted);
}
.screen-anchor-figure {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--font-size-heading-xl);
  line-height: 1;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.screen-anchor-figure span {
  font-family: var(--font-body);
  font-size: var(--font-size-label);
  font-weight: 500;
  color: color-mix(in srgb, var(--color-on-primary) 66%, transparent);
}

/* §6b·3 — El header de sección va AFUERA del grupo, en caption apagado. Adentro
   y en escala de heading compite con los datos y gana la etiqueta (M12). */
.screen-section { display: block; }
.screen-section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 0 4px 8px;
  font-size: var(--font-size-caption);
  line-height: var(--line-height-caption);
  font-weight: 600;
  color: var(--text-muted);
}

/* §6b·2 — El grupo. Sin borde: el escalón de superficie ya dibuja el límite, y
   un borde encima lo dibuja dos veces (M10, M11). */
.screen-group {
  background: var(--color-surface-container);
  border-radius: var(--screen-radius);
  overflow: hidden;
}
/* El divisor va SOLO entre filas —nunca antes de la primera ni después de la
   última— y arranca donde arranca el TEXTO, no en el borde de la tarjeta. A
   sangre corta la tarjeta en dos; insetado la lee como lista (M11).

   Va como pseudo-elemento y no como border-top para poder insetarlo sin tocar
   el layout de la fila: con márgenes habría que compensarlos adentro, y esa
   compensación se rompe apenas alguien cambia el padding del grupo. */
.screen-group > .screen-row + .screen-row,
.screen-group > .screen-item + .screen-item { position: relative; }
.screen-group > .screen-row + .screen-row::before,
.screen-group > .screen-item + .screen-item::before {
  content: "";
  position: absolute;
  top: 0; right: 0;
  height: 1px;
  background: var(--color-surface-variant);
}
/* Fila simple: el divisor arranca en el padding del grupo. */
.screen-group > .screen-row + .screen-row::before { left: var(--screen-group-padding); }
/* Fila con ícono guía: arranca después del ícono. Se calcula, no se escribe:
   así sigue alineado si el proyecto cambia el padding o el tamaño del ícono. */
.screen-group > .screen-item + .screen-item::before { left: calc(var(--screen-group-padding) + var(--screen-item-icon) + var(--screen-item-gap)); }

/* §6b·4 — La fila. Etiqueta y dato al MISMO cuerpo; los separa el peso, no el
   tamaño. La inversión —etiqueta pesando más que el dato— es M13. */
.screen-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  min-height: var(--screen-row-height);
  padding: 0 var(--screen-group-padding);
  margin-left: 0;
}
.screen-row-label {
  font-size: var(--font-size-label);
  line-height: var(--line-height-label);
  font-weight: 400;
  color: var(--text-muted);
  flex: 0 1 auto;
}
.screen-row-value {
  font-size: var(--font-size-label);
  line-height: var(--line-height-label);
  font-weight: 600;
  color: var(--text-primary);
  text-align: right;
  flex: 0 1 auto;
  min-width: 0;
}

/* §6b·8 — Fila con ícono guía: el ícono ayuda a encontrarla en una lista larga.
   Acá el divisor arranca después del ícono, no del padding. */
.screen-item {
  display: flex;
  align-items: center;
  gap: var(--screen-item-gap);
  min-height: var(--screen-item-height);
  padding: 0 var(--screen-group-padding);
}
.screen-item-icon {
  width: var(--screen-item-icon); height: var(--screen-item-icon);
  border-radius: var(--radius-full);
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  background: var(--color-surface-variant);
}
.screen-item-body { flex: 1 1 auto; min-width: 0; }
.screen-item-title {
  font-size: var(--font-size-label);
  line-height: var(--line-height-label);
  font-weight: 600;
  color: var(--text-primary);
}
/* El rol «monto en tarjeta» de la tabla de MOBILE.md §6b·4b: heading-md a 700.
   Existe como clase para que no se escriba a mano cada vez — que es como el
   chip terminó un escalón arriba de donde iba. */
.screen-item-figure {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--font-size-heading-md);
  line-height: var(--line-height-heading-md);
  letter-spacing: -.015em;
  color: var(--text-primary);
  display: block;
}
.screen-item-sub { display: block; }
.screen-item-sub,
.screen-item-aside {
  font-size: var(--font-size-caption);
  line-height: var(--line-height-caption);
  color: var(--text-muted);
}
.screen-item-aside { text-align: right; font-variant-numeric: tabular-nums; flex: 0 0 auto; }


/* §6b·6 — La acción primaria, anclada abajo y fuera del scroll. Al final del
   contenido obliga a scrollear para actuar, y en una lista larga desaparece (M14). */
.screen-action {
  flex: 0 0 auto;
  padding: 12px var(--screen-gutter) 24px;
  background: var(--color-surface);
}

/* La acción secundaria en línea. Misma clase visual que un badge de estado, así
   que va al MISMO cuerpo que él —caption— y no al del dato de una fila. Se dibuja
   a 36 y llega a 48 por superficie táctil: a 48 dibujada se vuelve un botón y le
   compite a la primaria; a 36 y sin ::after es M2, que es bloqueante. */
.screen-inline-action {
  position: relative;
  display: inline-flex;
  align-items: center;
  height: 36px;
  padding: 0 13px;
  border-radius: var(--radius-full);
  background: var(--color-surface-variant);
  color: var(--text-primary);
  font-size: var(--font-size-caption);
  font-weight: 600;
  border: 0;
  cursor: pointer;
}
.screen-inline-action::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: var(--target-min);
  transform: translateY(-50%);
}

/* ═══════════════════════════════════════
   Layout — App Shell, Sidebar, Topbar

   Cuándo usar: shell de aplicación completo — sidebar + topbar + avatar. Solo para apps con navegación persistente.
   Cuándo no: páginas standalone (auth, landing) — no cargar layout.css.
   Reemplaza a: nav bars/sidebars legacy.
═══════════════════════════════════════ */

/* ── App Shell ── */
.app {
  display: flex;
  min-height: 100vh;
}

.main {
  margin-left: var(--sidebar-width);
  flex: 1;
  /* min-width:0 anula el min-width:auto que traen los ítems flex. Sin esto el
     shell no puede encogerse por debajo del min-content de su contenido, y
     cualquier tabla ancha lo empuja más allá del viewport: en 375px medimos
     589px de desborde. Es el arreglo de fondo — sin él cada pantalla
     redescubre el problema la primera vez que mete una tabla en el shell. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.content {
  padding: 32px 32px 48px;
  flex: 1;
}

/* ── SIDEBAR ── */
.sidebar {
  width: var(--sidebar-width);
  background: var(--sidebar-bg);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 0 0 16px;
  position: fixed;
  top: 0; left: 0; bottom: 0;
  z-index: 10;
  overflow-y: auto;
}

.sidebar-logo {
  height: var(--topbar-height); padding: 0 20px;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-heading);
  font-weight: 600; font-size: var(--font-size-heading-xs); color: var(--text-primary);
}
.sidebar-logo svg { color: var(--interactive); }

.sidebar-search {
  margin: 12px 12px 8px;
  display: flex; align-items: center; gap: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  color: var(--text-muted);
  font-size: var(--font-size-label);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-default);
}
.sidebar-search:hover { border-color: var(--neutral-200); }
.sidebar-search .shortcut {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--font-size-mono-sm);
  background: var(--neutral-50);
  padding: 2px 5px;
  border-radius: var(--radius-sm);
}

.sidebar-nav { padding: 4px 8px; flex: 1; }

.nav-section-label {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-label);
  color: var(--text-muted);
  padding: 16px 12px 6px;
}

.nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px;
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  font-size: var(--font-size-body-md);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-default), color var(--duration-fast) var(--ease-default);
  position: relative;
  user-select: none;
}
.nav-item:hover { background: var(--color-nav-hover); color: var(--color-nav-hover-content); }
.nav-item.active {
  background: var(--color-nav-selected);
  color: var(--color-nav-selected-content);
  font-weight: 500;
}
.nav-item.active svg { color: var(--color-nav-selected-content); }
.nav-item svg { width: 16px; height: 16px; flex-shrink: 0; }
.nav-item .chevron { margin-left: auto; opacity: .5; }
.nav-item .nav-badge {
  margin-left: auto;
  font-size: 11px;
  font-weight: 600;
  /* El par CONTAINER, no "acento sobre acento claro". Empezó en --interactive
     sobre --interactive-light (2.54:1); lo pasé a --color-secondary-text y arregló
     el claro pero en oscuro seguía en 3.98:1, porque ahí el acento ya es claro y
     el fondo también. Un contador sobre superficie tintada es exactamente para lo
     que existe el par container, y [15] lo certifica en los DOS temas:
     12.16:1 en claro y 5.09:1 en oscuro. */
  background: var(--color-secondary-container);
  color: var(--color-on-secondary-container);
  padding: 1px 7px;
  border-radius: var(--radius-full);
}

.nav-sub-items {
  padding-left: 20px;
}
.nav-sub-items .nav-item {
  font-size: var(--font-size-label);
  padding: 6px 12px;
}

.sidebar-footer {
  padding: 12px 12px 0;
  border-top: 1px solid var(--border);
}
.sidebar-footer .nav-item { margin-top: 4px; }

/* ── TOPBAR ── */
.topbar {
  background: var(--sidebar-bg);
  border-bottom: 1px solid var(--border);
  padding: 0 28px;
  height: var(--topbar-height);
  display: flex; align-items: center; justify-content: flex-end;
  gap: 12px;
  position: sticky; top: 0; z-index: 5;
}

.topbar-breadcrumb {
  margin-right: auto;
  display: flex; align-items: center; gap: 6px;
  font-size: var(--font-size-label); color: var(--text-muted);
}
.topbar-breadcrumb a { color: var(--text-muted); text-decoration: none; }
.topbar-breadcrumb a:hover { color: var(--text-primary); }
.topbar-breadcrumb .separator { color: var(--neutral-200); }

.topbar-btn {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 16px;
  border-radius: var(--radius-md);
  font-size: var(--font-size-label); font-weight: 500;
  cursor: pointer; border: none;
  transition: background var(--duration-fast) var(--ease-default);
}
.topbar-btn.secondary {
  background: var(--bg);
  color: var(--text-secondary);
  border: 1px solid var(--border);
}
.topbar-btn.secondary:hover { background: var(--interactive-light); }

.topbar-notif {
  position: relative;
  width: 36px; height: 36px;
  border-radius: var(--radius-md);
  background: var(--bg);
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.notif-badge {
  position: absolute; top: -5px; right: -5px;
  background: var(--interactive);
  color: var(--color-on-primary);
  font-size: 10px; font-weight: 600;
  width: 17px; height: 17px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--color-surface);
}

/* Shell avatar. Flat tonal fill (matches the Avatar component in
   css/components/avatar.css; the color audit removed gradient/primitives).
   NOTE: this .avatar shares the class name with avatar.css's component base;
   components.css loads after layout.css so the component rules win where both
   are present — kept token-identical here so the shell renders the same. */
.avatar {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--color-primary-container);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-on-primary-container); font-size: var(--font-size-caption); font-weight: 600;
  cursor: pointer;
}
.avatar-label {
  font-size: var(--font-size-label); font-weight: 500; cursor: pointer;
  display: flex; align-items: center; gap: 4px;
}

/* ═══════════════════════════════════════
   MOBILE SHELL — canonical modal navigation drawer (resolved 2026-07)

   ≥768px (--breakpoint-md): the sidebar is persistent/fixed (the rules above).
   <768px: it becomes a MODAL NAVIGATION DRAWER — off-canvas by default, slides
   in over a scrim when the shell root gets `.nav-open`, toggled by the topbar
   hamburger (.shell-menu-btn). This is MD3's modal-vs-standard drawer split,
   reusing Embassy's Navigation Drawer language. Media queries use the literal
   768px because @media can't read var(--breakpoint-md).

   Consuming app wires ~10 lines of JS: the hamburger toggles `.nav-open` on
   `.app`; Esc and a scrim click remove it; on open move focus into the sidebar,
   on close return it to the hamburger. See GOVERNANCE §14.3.
═══════════════════════════════════════ */

/* Hamburger — only shown on compact. Place first in .topbar. El target de 44px
   lo aplica el bloque de punteros gruesos en base.css, no esta regla. */
.shell-menu-btn {
  display: none;
  width: 40px; height: 40px; margin-right: 4px;
  align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--bg); color: var(--text-secondary);
  cursor: pointer;
}
.shell-menu-btn:hover { background: var(--interactive-light); }
.shell-menu-btn:focus-visible {
  outline: 2px solid var(--color-focus); outline-offset: 2px;
}

/* Scrim behind the modal drawer — sits below the sidebar (z-index 10), above content. */
.sidebar-scrim {
  display: none;
  position: fixed; inset: 0; z-index: 9;
  background: var(--color-scrim);
  opacity: 0; transition: opacity var(--duration-normal) var(--ease-default);
}

@media (max-width: 768px) {
  /* Sidebar off-canvas by default; slides in as a modal drawer when open. */
  .sidebar {
    transform: translateX(-100%);
    transition: transform var(--duration-medium) var(--ease-default);
    box-shadow: var(--shadow-lg);
  }
  .app.nav-open .sidebar { transform: translateX(0); }

  /* Content reclaims full width; the hamburger appears in the topbar. */
  .main { margin-left: 0; }
  .shell-menu-btn { display: inline-flex; }

  /* El drawer resolvía el sidebar y dejaba el topbar sin tocar: 28px de padding
     lateral y un breadcrumb sin truncar alcanzan para desbordar solos. */
  .topbar { padding: 0 var(--space-4); gap: var(--space-2); }
  .topbar-breadcrumb {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .avatar-label { display: none; }

  /* Scrim shown while the drawer is open. */
  .app.nav-open .sidebar-scrim { display: block; opacity: 1; }
}

/* Respect reduced-motion — no slide, just show/hide. */
@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
  .sidebar-scrim { transition: none; }
}

/* ═══════════════════════════════════════
   Amalgama DS — Library Components
   Importa este archivo para cargar todos los componentes de la libreria.
   Para uso individual, copia el archivo del componente que necesitas
   desde la carpeta css/components/.

   Dependencia: variables.css, base.css
═══════════════════════════════════════ */

/* ── components/page-header.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Page Header
   Encabezado de pagina con titulo y acciones.

   Cuándo usar: título + acciones al tope de cada vista.
   Cuándo no: dentro de cards o modales (tienen su propio header).
   Reemplaza a: headers de página ad-hoc.

   Dependencia: variables.css
   Requiere: (ninguno)

   Uso:
   <div class="page-header">
     <h1 class="page-title">Vacantes</h1>
     <div class="header-actions">
       <button class="btn-primary">+ Nueva vacante</button>
     </div>
   </div>
═══════════════════════════════════════ */

.page-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 24px;
}
.page-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-heading-lg); font-weight: 600; letter-spacing: var(--letter-spacing-heading-lg);
}
.header-actions { display: flex; align-items: center; gap: var(--space-2-5); }

/* ── components/button.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Button
   Jerarquía de énfasis (alto → bajo), inspirada en Material 3
   pero con identidad Embassy:

     Primary    · Filled         · acción de mayor jerarquía
     Elevated   · Tonal + shadow · MD3 elevated button — primary-container con
                                    elevación real que crece en hover; distinto
                                    de Tertiary (outlined, sin sombra)
     Secondary  · Neutral tonal  · jerarquía media — superficie neutra (NO acento)
     Tertiary   · Outlined       · acción secundaria / alternativa
     Text       · Text only     · acción de baja jerarquía / inline
     Icon       · Icon          · acción compacta solo-ícono

   Cuándo usar: acciones. Jerarquía obligatoria: UN solo btn-primary por contexto; btn-secondary para alternativas; btn-tertiary menor peso; btn-text acciones inline.
   Cuándo no: navegación (usar links/nav). NUNCA estirar a full-width ni alinear contenido a la izquierda — el botón tiene ancho intrínseco y contenido centrado; si el layout legacy lo pide, se cambia el layout, no el botón.
   Reemplaza a: cualquier botón/CTA legacy; un "action row" plano se mapea a grupo con jerarquía (un primary + alternativas).

   Dependencia: variables.css (Color Roles)
   Consume EXCLUSIVAMENTE tokens semánticos --color-* → funciona en
   light y dark sin overrides por tema (los roles se recalibran en
   variables.css → [data-theme="dark"]).

   Tamaños: .btn-xs · .btn-sm · .btn-lg · .btn-xl (el radio escala con el tamaño,
   nunca con la variante — ver GOVERNANCE.md §4.3).
   Modificadores: .btn-danger · .btn-success · .btn-compact
   Compat: .btn-ghost y .btn-next se mantienen como alias.

   Uso:
   <button class="btn-primary">Crear vacante</button>
   <button class="btn-elevated">Ver detalle</button>
   <button class="btn-secondary">Guardar borrador</button>
   <button class="btn-tertiary">Cancelar</button>
   <button class="btn-text">Ver más</button>
   <button class="icon-btn" aria-label="Ajustes"><svg>…</svg></button>
═══════════════════════════════════════ */

/* ── Base compartida ── */
.btn-primary,
.btn-elevated,
.btn-secondary,
.btn-tertiary,
.btn-text,
.btn-link,
.btn-ghost,
.btn-next {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius-button, var(--radius-md));
  font-family: inherit;
  font-size: var(--font-size-body-md); font-weight: 600;
  line-height: var(--line-height-control);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default),
              color var(--duration-fast) var(--ease-default),
              box-shadow var(--duration-fast) var(--ease-default),
              transform var(--duration-fast) var(--ease-default);
}

/* Foco visible accesible — común a todas las variantes (WCAG 2.4.7) */
.btn-primary:focus-visible,
.btn-secondary:focus-visible,
.btn-tertiary:focus-visible,
.btn-text:focus-visible,
.btn-link:focus-visible,
.btn-ghost:focus-visible,
.btn-next:focus-visible,
.icon-btn:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

/* ─────────────────────────────────────
   Primary · Filled (rol Primary)
───────────────────────────────────── */
.btn-primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
}
.btn-primary:hover { background: var(--color-primary-hover); transform: translateY(-1px); }
.btn-primary:active { transform: translateY(0); }
.btn-primary:disabled {
  background: var(--color-disabled);
  color: var(--color-on-disabled);
  cursor: not-allowed; transform: none; box-shadow: none;
}

/* Modificadores semánticos de Primary */
/* El RELLENO, no la señal: --color-error es el rojo del borde de un campo y del
   punto de estado, y como superficie con texto encima daba 3.58:1. El par
   -fill / -on es el que pasa AA en los dos temas (6.37:1 y 6.78:1). */
.btn-primary.btn-danger  { background: var(--color-error-fill);   color: var(--color-on-error);   }
.btn-primary.btn-danger:hover  { background: var(--color-error-fill-hover);   }
/* Anillo de foco teñido de error (par shadcn destructive focus-visible:ring-destructive/20). */
.btn-primary.btn-danger:focus-visible {
  outline-color: var(--color-error);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-error) 25%, transparent);
}
.btn-primary.btn-success { background: var(--color-success-fill); color: var(--color-on-success); }
.btn-primary.btn-success:hover { background: var(--color-success-fill-hover); }

/* ─────────────────────────────────────
   Secondary · fill NEUTRO (superficie tonal + borde sutil).
   No consume el acento: en todo el sistema --color-secondary-container significa
   SELECCIONADO (chip, segmented-button, pagination, toggle, calendar, list, y el
   item activo del menú vía --color-nav-selected). Un botón no es un estado
   seleccionado, y cuando compartían token el botón secundario salía igual que el
   item de menú activo. Además, en neutro el Secondary lee como menor jerarquía
   frente al Primary en los dos temas, que es lo que pide variables.css §Button.
───────────────────────────────────── */
.btn-secondary {
  background: var(--button-secondary-bg);
  color: var(--button-secondary-fg);
  border-color: var(--button-secondary-border);
}
.btn-secondary:hover {
  background: var(--button-secondary-bg-hover);
  transform: translateY(-1px);
}
.btn-secondary:active { transform: translateY(0); }
.btn-secondary:disabled {
  background: var(--color-disabled);
  color: var(--color-on-disabled);
  border-color: transparent;
  cursor: not-allowed; transform: none;
}

/* ─────────────────────────────────────
   Elevated · Tonal (Primary Container) + sombra real que crece en hover
   (MD3 elevated button). Se diferencia de Filled (opaco, sin sombra) y de
   Outlined/Tertiary (transparente + borde, sin sombra) — la sombra ES el punto
   de esta variante. --btn-elevation(-hover) tiene override en dark mode
   (variables.css) para que la elevación se vea también sobre superficies oscuras.
───────────────────────────────────── */
.btn-elevated {
  background: var(--color-primary-container);
  color: var(--color-on-primary-container);
  border-color: transparent;
  box-shadow: var(--btn-elevation);
}
.btn-elevated:hover {
  background: color-mix(in srgb, var(--color-on-primary-container) 8%, var(--color-primary-container));
  box-shadow: var(--btn-elevation-hover);
  transform: translateY(-1px);
}
.btn-elevated:active {
  background: color-mix(in srgb, var(--color-on-primary-container) 12%, var(--color-primary-container));
  box-shadow: var(--btn-elevation);
  transform: translateY(0);
}
.btn-elevated:disabled {
  background: var(--color-disabled);
  color: var(--color-on-disabled);
  border-color: transparent;
  box-shadow: none;
  cursor: not-allowed;
  transform: none;
}

/* ─────────────────────────────────────
   Tertiary · Outlined (rol Outline + Primary)
   (alias histórico: .btn-ghost)
───────────────────────────────────── */
.btn-tertiary,
.btn-ghost {
  background: transparent;
  border-color: var(--color-outline);
  color: var(--color-primary);
  font-weight: 500;
}
.btn-tertiary:hover,
.btn-ghost:hover {
  background: var(--color-surface-variant);
  border-color: var(--color-primary);
}
.btn-tertiary:disabled,
.btn-ghost:disabled {
  background: transparent;
  border-color: var(--color-outline-variant);
  color: var(--color-on-disabled);
  cursor: not-allowed;
}

/* ─────────────────────────────────────
   Text · Text button (rol Primary, sin contenedor)
───────────────────────────────────── */
.btn-text {
  background: transparent;
  color: var(--color-primary);
  padding: var(--space-2) var(--space-3);
}
.btn-text:hover {
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}
.btn-text:disabled {
  background: transparent;
  color: var(--color-on-disabled);
  cursor: not-allowed;
}

/* ─────────────────────────────────────
   Link · texto subrayado en hover, sin contenedor ni bloque de fondo
   (par shadcn variant="link"). Distinto de .btn-text (que pinta un bloque de
   fondo en hover): el Link es un enlace de acción inline.
───────────────────────────────────── */
.btn-link {
  background: transparent;
  color: var(--color-primary);
  padding: var(--space-2) var(--space-1);
  font-weight: 500;
  text-underline-offset: 4px;
}
.btn-link:hover { text-decoration: underline; }
.btn-link:active:not(:disabled) { color: var(--color-primary-hover); }
.btn-link:disabled {
  background: transparent;
  color: var(--color-on-disabled);
  cursor: not-allowed;
  text-decoration: none;
}

/* ─────────────────────────────────────
   Tamaños (aplica a cualquier variante)

   La escalera del botón es PROPORCIONAL al alto del control, no la escala de
   texto: el label de un botón de 24px es chico porque el botón es chico, no
   porque sea texto secundario. Que SM y MD coincidan con --font-size-body-sm y
   --font-size-body-md es una coincidencia de VALOR, no de rol — mapearlos por
   ahí es el error que este repo ya cometió dos veces (11px "es"
   --font-size-mono-sm, y el label de un botón no es mono).

   Vive acá y no en variables.css a propósito: GOVERNANCE §21 dice que un tier de
   custom properties para UN componente se define en el archivo del componente,
   como hace Segmented Button con --seg-btn-*. Si algún día un segundo componente
   necesita esta escalera, se promueve.
───────────────────────────────────── */
.btn-xs, .btn-sm, .btn-lg, .btn-xl,
.btn-primary, .btn-secondary, .btn-tertiary, .btn-text, .btn-link, .btn-ghost, .btn-next {
  --btn-h-xs: 24px;   --btn-label-xs: 11px;    /* dense lists, tags               */
  --btn-h-sm: 32px;   --btn-label-sm: 12.5px;  /* compact toolbars                */
  --btn-h-md: 36px;   --btn-label-md: 13.5px;  /* default — base styles de arriba */
  --btn-h-lg: 44px;   --btn-label-lg: 15px;    /* CTAs, piso táctil               */
  --btn-h-xl: 52px;   --btn-label-xl: 16px;    /* hero / marketing                */
}
.btn-xs { padding: 3px var(--space-2);   font-size: var(--btn-label-xs); font-weight: 600; min-height: var(--btn-h-xs); border-radius: var(--radius-button, var(--radius-sm)); }
.btn-sm { padding: var(--space-1-5) var(--space-3);  font-size: var(--btn-label-sm); min-height: var(--btn-h-sm); border-radius: var(--radius-button, var(--radius-sm)); }
.btn-lg { padding: var(--space-2-5) 22px; font-size: var(--btn-label-lg); min-height: var(--btn-h-lg); border-radius: var(--radius-button, var(--radius-md)); }
.btn-xl { padding: var(--space-3-5) 30px; font-size: var(--btn-label-xl); font-weight: 700; min-height: var(--btn-h-xl); border-radius: var(--radius-button, var(--radius-lg)); }

.btn-text.btn-xs { padding: 3px var(--space-1-5); }
.btn-text.btn-sm { padding: var(--space-1-5) var(--space-2-5); }
.btn-text.btn-lg { padding: var(--space-2-5) var(--space-4); }
.btn-text.btn-xl { padding: var(--space-3-5) var(--space-5); }

/* Forma: el radio escala con el tamaño del botón.
   XS/SM → --radius-sm (4px)  ·  MD/LG → --radius-md (8px)  ·  XL → --radius-lg (12px)
   La píldora (--radius-full) está reservada a chips y badges —
   un botón píldora se confunde con un chip seleccionado. */

/* Variante compacta (MD3): 16px izquierda y derecha.
   Usar en contextos de mayor densidad (toolbars, formularios, tablas).
   <button class="btn-primary btn-compact">Guardar</button> */
.btn-compact { padding-left: var(--space-4); padding-right: var(--space-4); }

/* ─────────────────────────────────────
   Next · avance de wizard (alias de Primary)
───────────────────────────────────── */
.btn-next {
  background: var(--color-primary);
  color: var(--color-on-primary);
}
.btn-next:hover { background: var(--color-primary-hover); }

/* ─────────────────────────────────────
   Icon button · superficie + outline
───────────────────────────────────── */
.icon-btn {
  position: relative;
  width: 36px; height: 36px;
  padding: 0;   /* fixed square box: reset any padding inherited from a size class
                   (.btn-sm/.btn-xs/.btn-lg/.btn-xl) so the icon isn't squished */
  border-radius: var(--radius-button, var(--radius-md));
  background: var(--color-surface-container);
  border: 1px solid var(--color-outline);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  color: var(--color-on-surface-variant);
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default),
              color var(--duration-fast) var(--ease-default);
}
.icon-btn:hover { background: var(--color-surface-variant); color: var(--color-on-surface); }
.icon-btn:disabled {
  background: transparent;
  border-color: var(--color-outline-variant);
  color: var(--color-on-disabled);
  cursor: not-allowed;
}
.icon-btn.btn-xs { width: 24px; height: 24px; border-radius: var(--radius-button, var(--radius-sm)); }
.icon-btn.btn-sm { width: 30px; height: 30px; border-radius: var(--radius-button, var(--radius-sm)); }
.icon-btn.btn-lg { width: 44px; height: 44px; border-radius: var(--radius-button, var(--radius-md)); }
.icon-btn.btn-xl { width: 52px; height: 52px; border-radius: var(--radius-button, var(--radius-lg)); }

/* ─────────────────────────────────────
   Pressed state — 12% state layer (WCAG 2.5.3)
   Implemented via filter so it works across all variants
   without a pseudo-element overlay.
───────────────────────────────────── */
.btn-primary:active:not(:disabled)              { filter: brightness(0.88); transform: translateY(0); }
.btn-secondary:active:not(:disabled)            { filter: brightness(0.92); transform: translateY(0); }
.btn-tertiary:active:not(:disabled),
.btn-ghost:active:not(:disabled)                { background: var(--color-surface-container-high); }
.btn-text:active:not(:disabled)                 { background: color-mix(in srgb, var(--color-primary) 16%, transparent); }
.icon-btn:active:not(:disabled)                 { background: var(--color-surface-container-high); }

/* ── components/segmented-button.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Segmented Button
   Selector de opción única dentro de un conjunto discreto de opciones
   mutuamente excluyentes, con forma de píldora.

   Cuándo usar: alternar entre vistas (Lista / Cuadrícula), filtros de
     categoría o modo dentro del mismo contexto (Día / Semana / Mes).
   Cuándo no: acciones que disparan efectos (usar Button), selección
     múltiple (usar Chips), navegación entre páginas (usar Tabs), más
     de 5 opciones o etiquetas muy dispares (usar Select).
   Reemplaza a: toggle-group custom, radio-button pill legacy.

   ───────────────────────────────────────────────────────────────────
   ARQUITECTURA DE TOKENS — estructura "Tokens and specs" de Material 3
   ───────────────────────────────────────────────────────────────────
   Tres capas, igual que MD3:
       paleta Embassy  →  rol de sistema  →  token de componente  →  CSS
       (--secondary-200)  (--md-sys-color-…)  (--seg-btn-…)          rule

   Cada fila de la tabla de tokens de MD3 = UNA custom property declarada
   en .seg-btn-group. Las reglas consumen SOLO tokens de componente.
   Tematizar/recolorear una instancia = sobreescribir un token, no editar
   selectores. Los defaults usan el nombre de sistema MD3 nativo con
   fallback al rol Embassy:  var(--md-sys-color-X, var(--color-X)).
   → Cargá css/md-sys-bridge.css para resolver los nombres --md-sys-color-*
     contra la paleta Embassy. Sin el bridge, el componente cae al rol
     Embassy automáticamente (sigue funcionando).

   Ojo, una diferencia con MD3 a proposito: MD3 pinta el segmento activo con
   secondaryContainer, y Embassy no. En una pantalla real el filtro activo ya
   usa ese azul, y si el modo de vista usa el mismo ninguno de los dos pesa mas
   que el otro (COMPOSICION.md §4c). Aca el grupo es una pista hundida y el
   segmento activo se levanta, en neutro.

   ┌─ MD3 component token ───────────── token de componente ── default ──┐
   │ (Embassy) container.color          container-color        ctx-track   │
   │ outline.color                      outline-color          transparent │
   │ label-text.color (unselected)      label-color            on-surface  │
   │ icon.color (unselected)            icon-color             on-surface  │
   │ selected.container.color           selected-container     ctx-track-thumb           │
   │ selected.label-text.color          selected-label         onSurface                 │
   │ selected.icon.color                selected-icon          onSurface                 │
   │ hover.state-layer  (8%)            hover-layer-*          onSurface 8%              │
   │ focus.state-layer  (12%)           focus-layer-*          onSurface 12%             │
   │ pressed.state-layer (12%)          pressed-layer-*        onSurface 12%             │
   │ selected.hover.state-layer (8%)    selected-hover-*       onSurface 8%              │
   │ selected.focus.state-layer (12%)   selected-focus-*       onSurface 12%             │
   │ selected.pressed.state-layer(12%)  selected-pressed-*     onSurface 12%             │
   │ disabled.container.color (12%)     disabled-container-*   onSurface 12%             │
   │ disabled.label-text.color (38%)    disabled-label-*       onSurface 38%             │
   │ disabled.outline.color (12%)       disabled-outline-*     onSurface 12%             │
   └──────────────────────────────────────────────────────────────────────┘

   FOCO: MD3 combina el focus.state-layer (12%) CON un anillo de foco
   visible (md-focus-ring). Embassy implementa ambos: state layer + anillo
   (outline 2px + ring) por WCAG 2.4.7 (GOVERNANCE §6.2). El anillo NO se
   elimina — es parte del patrón MD3 y un requisito de accesibilidad.

   Dependencia: variables.css  ·  recomendado: md-sys-bridge.css
   Uso:
   <div class="seg-btn-group" role="group" aria-label="Vista">
     <!-- El estado lo declara aria-selected (única) o aria-pressed (múltiple); .selected es un alias. -->
     <button class="seg-btn" aria-selected="true" onclick="segSwitch(this)">Lista</button>
     <button class="seg-btn" aria-selected="false" onclick="segSwitch(this)">Cuadrícula</button>
   </div>
═══════════════════════════════════════ */

/* ── Tokens de componente (capa MD3) ── */
.seg-btn-group {
  /* Estructura */
  --seg-btn-container-shape:                var(--radius-full);
  /* Pista del grupo — COMPOSICION.md §4c: «como estas mirando» es el mas callado
     de los tres seleccionados, y no lleva acento. En vez de un aro alrededor del
     segmento activo, el grupo se HUNDE un escalon y el segmento se queda al
     nivel de una tarjeta: 1.19:1 en claro, 1.24:1 en oscuro, sin ningun borde.
     Un aro adentro del borde del grupo son dos anillos concentricos a 3px. */
  --seg-btn-container-color:                var(--ctx-track);
  --seg-btn-outline-color:                  transparent;
  /* Segmento no seleccionado */
  --seg-btn-label-color:                    var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-icon-color:                     var(--md-sys-color-on-surface, var(--color-on-surface));
  /* Segmento seleccionado */
  /* Neutro a proposito: el token de seleccion (secondary-container) queda para el
     filtro activo. Si los dos usan el mismo azul, ninguno pesa mas que el otro. */
  --seg-btn-selected-container-color:       var(--ctx-track-thumb);
  --seg-btn-selected-label-color:           var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-selected-icon-color:            var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-selected-elevation:             var(--shadow-sm);
  /* State layers — no seleccionado */
  --seg-btn-hover-layer-color:              var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-hover-layer-opacity:            8%;
  --seg-btn-focus-layer-color:              var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-focus-layer-opacity:            12%;
  --seg-btn-pressed-layer-color:            var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-pressed-layer-opacity:          12%;
  /* State layers — seleccionado */
  --seg-btn-selected-hover-layer-color:     var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-selected-hover-layer-opacity:   8%;
  --seg-btn-selected-focus-layer-color:     var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-selected-focus-layer-opacity:   12%;
  --seg-btn-selected-pressed-layer-color:   var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-selected-pressed-layer-opacity: 12%;
  /* Deshabilitado — alineado a MD3 (onSurface @ opacidad) */
  --seg-btn-disabled-group-color:           var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-disabled-group-opacity:         4%;
  --seg-btn-disabled-container-color:       var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-disabled-container-opacity:     12%;
  --seg-btn-disabled-label-color:           var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-disabled-label-opacity:         38%;
  --seg-btn-disabled-outline-color:         var(--md-sys-color-on-surface, var(--color-on-surface));
  --seg-btn-disabled-outline-opacity:       12%;
  /* Foco — anillo visible (WCAG 2.4.7 · GOVERNANCE §6.2) */
  --seg-btn-focus-outline-color:            var(--color-focus);
  --seg-btn-focus-ring-color:               var(--color-focus-ring);

  display: inline-flex;
  align-items: center;
  background: var(--seg-btn-container-color);
  border: 1px solid var(--seg-btn-outline-color);
  border-radius: var(--seg-btn-container-shape);
  padding: 3px;
  gap: var(--space-0-5);
}

/* ── Segmento individual ── */
.seg-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-1-5) var(--space-4);
  border: none;
  border-radius: var(--seg-btn-container-shape);
  background: transparent;
  color: var(--seg-btn-label-color);
  font-family: var(--font-body);
  font-size: var(--font-size-body-sm);
  font-weight: 500;
  line-height: var(--line-height-control);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background var(--duration-fast) var(--ease-default),
    color var(--duration-fast) var(--ease-default),
    box-shadow var(--duration-fast) var(--ease-default);
}
.seg-btn svg,
.seg-btn i { color: var(--seg-btn-icon-color); }

/* ── Hover (unselected) — state layer ── */
.seg-btn:hover:not(.selected):not([aria-selected="true"]):not([aria-pressed="true"]):not(:disabled) {
  background: color-mix(in srgb, var(--seg-btn-hover-layer-color) var(--seg-btn-hover-layer-opacity), transparent);
}

/* ── Estado seleccionado ── */
.seg-btn.selected,
.seg-btn[aria-selected="true"],
.seg-btn[aria-pressed="true"] {
  background: var(--seg-btn-selected-container-color);
  color: var(--seg-btn-selected-label-color);
  box-shadow: var(--seg-btn-selected-elevation);
  font-weight: 600;
}
.seg-btn.selected svg, .seg-btn.selected i,
.seg-btn[aria-selected="true"] svg, .seg-btn[aria-selected="true"] i,
.seg-btn[aria-pressed="true"] svg, .seg-btn[aria-pressed="true"] i {
  color: var(--seg-btn-selected-icon-color);
}

/* ── Hover (selected) — state layer sobre el contenedor ── */
.seg-btn.selected:hover:not(:disabled),
.seg-btn[aria-selected="true"]:hover:not(:disabled),
.seg-btn[aria-pressed="true"]:hover:not(:disabled) {
  background: color-mix(in srgb, var(--seg-btn-selected-hover-layer-color) var(--seg-btn-selected-hover-layer-opacity), var(--seg-btn-selected-container-color));
}

/* ── Foco — anillo visible (siempre) ── */
.seg-btn:focus-visible {
  outline: 2px solid var(--seg-btn-focus-outline-color);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--seg-btn-focus-ring-color);
  z-index: 1;
}
/* ── Foco — state layer (unselected) ── */
.seg-btn:focus-visible:not(.selected):not([aria-selected="true"]):not([aria-pressed="true"]):not(:disabled) {
  background: color-mix(in srgb, var(--seg-btn-focus-layer-color) var(--seg-btn-focus-layer-opacity), transparent);
}
/* ── Foco — state layer (selected) sobre el contenedor ── */
.seg-btn.selected:focus-visible:not(:disabled),
.seg-btn[aria-selected="true"]:focus-visible:not(:disabled),
.seg-btn[aria-pressed="true"]:focus-visible:not(:disabled) {
  background: color-mix(in srgb, var(--seg-btn-selected-focus-layer-color) var(--seg-btn-selected-focus-layer-opacity), var(--seg-btn-selected-container-color));
}

/* ── Presionado (unselected) — state layer ── */
.seg-btn:active:not(:disabled):not(.selected):not([aria-selected="true"]):not([aria-pressed="true"]) {
  background: color-mix(in srgb, var(--seg-btn-pressed-layer-color) var(--seg-btn-pressed-layer-opacity), transparent);
}

/* ── Presionado (selected) — state layer sobre el contenedor ── */
.seg-btn.selected:active:not(:disabled),
.seg-btn[aria-selected="true"]:active:not(:disabled),
.seg-btn[aria-pressed="true"]:active:not(:disabled) {
  background: color-mix(in srgb, var(--seg-btn-selected-pressed-layer-color) var(--seg-btn-selected-pressed-layer-opacity), var(--seg-btn-selected-container-color));
}

/* ── Deshabilitado — grupo ── */
.seg-btn-group:has(.seg-btn:disabled),
.seg-btn-group[aria-disabled="true"] {
  background: color-mix(in srgb, var(--seg-btn-disabled-group-color) var(--seg-btn-disabled-group-opacity), transparent);
  border-color: color-mix(in srgb, var(--seg-btn-disabled-outline-color) var(--seg-btn-disabled-outline-opacity), transparent);
}

/* ── Deshabilitado — segmento ── */
.seg-btn:disabled,
.seg-btn[aria-disabled="true"] {
  color: color-mix(in srgb, var(--seg-btn-disabled-label-color) var(--seg-btn-disabled-label-opacity), transparent);
  cursor: not-allowed;
  pointer-events: none;
}

/* ── Deshabilitado seleccionado — contenedor ── */
.seg-btn.selected:disabled,
.seg-btn[aria-selected="true"]:disabled,
.seg-btn[aria-pressed="true"]:disabled {
  background: color-mix(in srgb, var(--seg-btn-disabled-container-color) var(--seg-btn-disabled-container-opacity), transparent);
}

/* ── Tamaño SM — toolbars compactas, filtros inline ── */
.seg-btn-group-sm .seg-btn {
  padding: var(--space-1) var(--space-2-5);
  font-size: var(--font-size-caption);
}

/* ── Tamaño LG — touch targets, CTAs de cambio de modo ── */
.seg-btn-group-lg .seg-btn {
  padding: var(--space-2) var(--space-5);
  font-size: var(--font-size-body-md);
}

/* ── components/toolbar.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Toolbar
   La fila de controles que encabeza una lista, tabla o dashboard: búsqueda,
   filtros, orden, cambio de vista/período y las acciones de la vista. Un solo
   componente con VARIANTES (compuestas de componentes Embassy existentes):

     · Actions only              .toolbar                 (solo acciones a la derecha)
     · Search + actions          .toolbar + .search-field (búsqueda que crece + acción)
     · Filters                   .toolbar-filters         (Selects + toolbar-btn, campo unificado)
     · Filters + segmented       .toolbar-filters         (+ Segmented Button)
     · Filters + date/calendar   .toolbar-filters         (+ Date Picker / Calendar)
     · Bulk actions (selección)  .toolbar-selection       (aparece al seleccionar ítems)
     · Overflow                  .toolbar-overflow-btn    (menú "más" cuando falta espacio)
     · Sticky                    .toolbar-sticky          (queda fija al hacer scroll)
     · Responsive / mobile       (flex-wrap por defecto; colapso a Sheet/Popover)

   Cuándo usar: barra de controles sobre UNA lista/tabla/dashboard — búsqueda,
     filtros, orden y las acciones de esa vista.
   Cuándo no: búsqueda que encabeza una pantalla completa → search-bar (search.css).
     Contexto y acciones globales de la app → Top Bar. Navegación entre sub-vistas → Tabs.
   Reemplaza a: filter bars legacy, "action rows" planos, y el ex-"Filter Toolbar"
     (ahora es la variante .toolbar-filters de este componente).

   ── JERARQUÍA IZQUIERDA / DERECHA ──
   Izquierda: lo que FILTRA o define la vista (search-field, Selects, Segmented Button,
     Date Picker, Checkbox de "seleccionar todo"). Derecha (.toolbar-actions, empujada con
     margin-left:auto): las ACCIONES de la vista, con UNA sola acción principal.
   Jerarquía de la acción (GOVERNANCE §20.5): Filled/Primary (btn-primary) sólo si es la
     acción principal de la vista; Tonal (btn-secondary) si es secundaria/de apoyo;
     Outlined (btn-tertiary) / icon-btn para neutrales (Filtros, Ordenar, Restablecer).

   ── TRATAMIENTO DE CAMPO COMPARTIDO (.toolbar-filters) ──
   Los controles de filtro tienen, sueltos, formas distintas (Select = campo relleno ·
   Segmented Button = píldora transparente · Date Picker = btn-tertiary outlined). Cuando
   tienen la MISMA jerarquía funcional deben verse iguales: .toolbar-filters expone una
   capa de tokens --tb-* y REESTILA a sus hijos SÓLO dentro de su alcance para unificar
   superficie, borde, radio, altura, tipografía e ícono. El Select es el campo de
   referencia (sin cambios); el Segmented Button conserva su segmento seleccionado primary;
   el Date Picker adopta el mismo campo. NO cambia los componentes standalone.
   No mezcles contenedores rellenos y transparentes sin una diferencia de jerarquía
   deliberada (anti-patrón, GOVERNANCE §19.5 / §20.5).

   Dependencia: variables.css. Para .toolbar-filters: select.css, segmented-button.css,
     date-picker.css (+ calendar.css), button.css (reestila a esos hijos sólo si están
     presentes). search-field es la variante compacta de .search-bar (search.css).

   Uso (búsqueda + filtros + acción):
   <div class="toolbar toolbar-filters" role="toolbar" aria-label="Controles de la lista">
     <div class="search-field"><i data-lucide="search"></i><input type="search" aria-label="Buscar"></div>
     <div class="select" data-select>…</div>
     <div class="seg-btn-group" role="group" aria-label="Vista">…</div>
     <div class="toolbar-actions"><button class="btn-primary">Nueva vacante</button></div>
   </div>
   <div class="result-count" aria-live="polite">Mostrando <strong>24</strong> vacantes</div>
═══════════════════════════════════════ */

/* ── Base: fila de controles ── */
.toolbar {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

/* Agrupador opcional para clusters de controles a un lado. */
.toolbar-group { display: inline-flex; align-items: center; gap: var(--space-2); }

/* Ranura de acciones — empujada a la derecha. */
.toolbar-actions {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: var(--space-2);
}
/* Empuje manual cuando la acción no se envuelve en .toolbar-actions. */
.toolbar > .toolbar-spacer { margin-left: auto; }

/* ── Search-field — variante compacta del Search component para toolbar ──
   Fondo --color-surface (blanco en light), NUNCA transparente — a diferencia de la
   .search-bar mobile, que es gris (--color-surface-container-high). Comparte borde y
   tokens de texto/estado. Forma --radius-md (8px) y padding compacto para convivir
   con toolbar-btn en la misma fila. .search-bar usa --radius-lg (12px) y 56px de alto. */
.search-field {
  flex: 1; min-width: 180px;
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface);   /* desktop: superficie (blanco en light) — par shadcn. NO igual que .search-bar mobile, que es gris (surface-container-high) */
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--color-on-surface-variant);
  cursor: text;
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default);
}
.search-field:hover {
  background: color-mix(in srgb, var(--color-on-surface) 8%, var(--color-surface));
  border-color: color-mix(in srgb, var(--color-on-surface) 20%, transparent);
}
.search-field:focus-within {
  background: color-mix(in srgb, var(--color-on-surface) 12%, var(--color-surface));
  border-color: var(--color-secondary);
}
.search-field > svg,
.search-field > i { width: 18px; height: 18px; flex-shrink: 0; color: inherit; }
.search-field input {
  flex: 1; min-width: 0;
  border: none; outline: none; background: transparent;
  font-family: var(--font-body); font-size: var(--font-size-body-md);
  color: var(--color-on-surface);
  caret-color: var(--color-secondary);
}
.search-field input::placeholder { color: var(--color-on-surface-variant); }
.search-field input::-webkit-search-cancel-button { display: none; }

/* ── Toolbar-btn — acción secundaria outlined (análoga a btn-secondary) ── */
.toolbar-btn {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface-container);
  border: 1px solid var(--color-outline);
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: var(--font-size-body-md); font-weight: 500;
  color: var(--color-on-surface-variant);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default),
              color var(--duration-fast) var(--ease-default);
}
.toolbar-btn:hover {
  background: var(--color-surface-variant);
  border-color: var(--color-outline-variant);
  color: var(--color-on-surface);
}
.toolbar-btn:active { opacity: .85; }
.toolbar-btn:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.toolbar-btn:disabled,
.toolbar-btn[aria-disabled="true"] {
  background: var(--color-surface-variant);
  color: var(--color-on-disabled);
  border-color: var(--color-outline);
  cursor: not-allowed; pointer-events: none;
}
.toolbar-btn > svg,
.toolbar-btn > i { width: 18px; height: 18px; }
/* Chip de conteo dentro de un toolbar-btn (p. ej. "Filtros · 3"). */
.toolbar-btn .toolbar-btn-count {
  min-width: 18px; height: 18px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-full);
  background: var(--color-secondary-container); color: var(--color-on-secondary-container);
  font-size: var(--font-size-caption); font-weight: 600;
}

/* ── Result count — resumen debajo de la barra ── */
.result-count {
  font-size: var(--font-size-label);
  color: var(--color-on-surface-variant);
  margin-bottom: 14px;
  padding: 0 var(--space-0-5);
}
.result-count strong { color: var(--color-on-surface); font-weight: 500; }

/* ═══════════════════════════════════════
   VARIANTE · Filters — tratamiento de campo unificado
   (ex "Filter Toolbar" — ahora .toolbar-filters). Reestila SÓLO dentro de .toolbar-filters:
   NO toca el Segmented Button, Date Picker ni Select globales.
═══════════════════════════════════════ */
.toolbar-filters {
  /* Capa de tokens de composición (una sola verdad para todos los controles). */
  --tb-height:            40px;                         /* altura de control compartida (= Select trigger) */
  --tb-radius:            var(--radius-md);             /* radio de campo (nunca píldora aquí) */
  --tb-pad-x:             14px;                          /* padding horizontal interno del campo */
  --tb-gap:               var(--space-3);               /* separación entre controles */
  --tb-surface:           var(--card-bg);               /* superficie de campo (relleno, NO transparente) */
  --tb-surface-hover:     color-mix(in srgb, var(--color-on-surface) 6%, var(--card-bg));
  --tb-border:            var(--border);
  --tb-border-hover:      color-mix(in srgb, var(--color-on-surface) 22%, transparent);
  --tb-border-focus:      var(--interactive);           /* = --color-secondary */
  --tb-focus-ring:        var(--color-focus-ring);
  --tb-text:              var(--text-primary);
  --tb-text-muted:        var(--text-muted);
  --tb-icon:              var(--text-muted);
  --tb-font-size:         var(--font-size-body-md);
  --tb-selected-bg:       var(--color-secondary-container);      /* estado primary del Segmented Button */
  --tb-selected-text:     var(--color-on-secondary-container);
  --tb-disabled-surface:  var(--color-surface-variant);
  --tb-disabled-text:     var(--color-on-disabled);
  --tb-disabled-border:   var(--color-outline);
  --tb-motion-duration:   var(--duration-fast);
  --tb-motion-ease:       var(--ease-default);

  gap: var(--tb-gap);
}
/* Alineación vertical de los controles directos al alto del campo. */
.toolbar-filters > .select,
.toolbar-filters > .seg-btn-group,
.toolbar-filters > .date-picker,
.toolbar-filters > .toolbar-actions { align-self: center; }

/* ── Select — campo de referencia; re-apuntamos superficie/borde/radio a --tb-*. ── */
.toolbar-filters .select-trigger {
  min-height: var(--tb-height);
  padding: var(--space-2) var(--tb-pad-x);
  background: var(--tb-surface);
  border-color: var(--tb-border);
  border-radius: var(--tb-radius);
  font-size: var(--tb-font-size);
  transition: background var(--tb-motion-duration) var(--tb-motion-ease),
              border-color var(--tb-motion-duration) var(--tb-motion-ease),
              box-shadow var(--tb-motion-duration) var(--tb-motion-ease);
}
.toolbar-filters .select-trigger:hover:not(:disabled):not([aria-disabled="true"]) {
  background: var(--tb-surface-hover);
  border-color: var(--tb-border-hover);
}
.toolbar-filters .select-trigger:focus-visible,
.toolbar-filters .select-trigger[aria-expanded="true"] {
  border-color: var(--tb-border-focus);
  box-shadow: 0 0 0 4px var(--tb-focus-ring);
}

/* ── Date Picker trigger — adopta el mismo campo que los Selects. ── */
.toolbar-filters .date-picker-trigger {
  min-height: var(--tb-height);
  width: auto;                                          /* base fija 240px → intrínseco en la barra */
  gap: var(--space-2);
  padding: var(--space-2) var(--tb-pad-x);
  background: var(--tb-surface);
  border: 1px solid var(--tb-border);
  border-radius: var(--tb-radius);
  color: var(--tb-text);
  font-family: var(--font-body);
  font-size: var(--tb-font-size);
  font-weight: 400;                                     /* base btn = 600 */
  transition: background var(--tb-motion-duration) var(--tb-motion-ease),
              border-color var(--tb-motion-duration) var(--tb-motion-ease),
              box-shadow var(--tb-motion-duration) var(--tb-motion-ease);
}
.toolbar-filters .date-picker-trigger:hover:not(:disabled) {
  background: var(--tb-surface-hover);
  border-color: var(--tb-border-hover);
  transform: none;                                      /* neutraliza cualquier lift del btn base */
}
.toolbar-filters .date-picker-trigger:focus-visible {
  outline: none;                                        /* reemplazado por el anillo de campo (Select) */
  border-color: var(--tb-border-focus);
  box-shadow: 0 0 0 4px var(--tb-focus-ring);
}
.toolbar-filters .date-picker-trigger:active { transform: none; }
.toolbar-filters .date-picker-trigger:disabled {
  background: var(--tb-disabled-surface);
  color: var(--tb-disabled-text);
  border-color: var(--tb-disabled-border);
  cursor: not-allowed;
}
.toolbar-filters .date-picker-trigger .date-picker-icon,
.toolbar-filters .date-picker-trigger > i,
.toolbar-filters .date-picker-trigger > svg { color: var(--tb-icon); }
.toolbar-filters .date-picker-trigger.date-picker-trigger-empty { color: var(--tb-text-muted); }

/* ── Segmented Button — sólo el CONTENEDOR adopta el campo. ──
   El segmento seleccionado conserva su estado primary (secondary-container) y cada
   segmento conserva SU anillo de foco (accesibilidad — no se toca). */
.toolbar-filters .seg-btn-group {
  min-height: var(--tb-height);
  background: var(--tb-surface);
  border-radius: var(--tb-radius);                      /* campo cuadrado, no píldora, dentro de la barra */
  padding: var(--space-1);
  --seg-btn-outline-color: var(--tb-border);            /* re-apunta el borde del componente al campo */
  /* Suelto, el segmented es un modo de vista y va neutro: pista hundida + pulgar
     levantado (COMPOSICION.md §4c). Acá no: adentro de .toolbar-filters está al
     lado de un Select y un Date picker, o sea que está eligiendo un FILTRO, y el
     filtro activo sí lleva el tonal. Además la barra ya pintó el grupo con la
     superficie de campo, así que un pulgar levantado quedaría del mismo color que
     su contenedor en claro (los dos son --card-bg) y no se vería. */
  --seg-btn-container-color:          var(--tb-surface);
  --seg-btn-selected-container-color: var(--color-secondary-container);
  --seg-btn-selected-label-color:     var(--color-on-secondary-container);
  --seg-btn-selected-icon-color:      var(--color-on-secondary-container);
  --seg-btn-selected-elevation:       none;
}
.toolbar-filters .seg-btn { border-radius: var(--radius-sm); }   /* segmentos anidados en el campo cuadrado */
.toolbar-filters .seg-btn-group:hover { border-color: var(--tb-border-hover); }

/* ── Search-field dentro de .toolbar-filters: misma superficie de campo (card-bg). ── */
.toolbar-filters .search-field {
  min-height: var(--tb-height);
  background: var(--tb-surface);
  border-color: var(--tb-border);
}
.toolbar-filters .search-field:hover { background: var(--tb-surface-hover); border-color: var(--tb-border-hover); }
.toolbar-filters .search-field:focus-within { border-color: var(--tb-border-focus); }

/* ═══════════════════════════════════════
   VARIANTE · Dos líneas (desktop) — .toolbar-stack
   Apila dos filas .toolbar cuando hay demasiados controles para una sola línea
   (p. ej. fila 1 = título/búsqueda + acción principal; fila 2 = filtros). Es una
   decisión de layout DESKTOP; en mobile la barra ya hace wrap por sí sola.
═══════════════════════════════════════ */
.toolbar-stack {
  display: flex; flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.toolbar-stack > .toolbar { margin-bottom: 0; }

/* ═══════════════════════════════════════
   VARIANTE · Bulk actions (selección)
   Aparece / se transforma cuando hay ítems seleccionados (p. ej. sobre una tabla o
   Data Table). Superficie teñida secondary-container para señalar "modo selección".
   Lo togglea el producto agregando .toolbar-selection + el conteo.
═══════════════════════════════════════ */
.toolbar-selection {
  background: color-mix(in srgb, var(--color-secondary-container) 55%, var(--card-bg));
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  color: var(--color-on-surface);
  animation: toolbar-selection-in var(--duration-normal) var(--ease-default) both;
}
@keyframes toolbar-selection-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.toolbar-selection-count {
  font-size: var(--font-size-body-md); font-weight: 600;
  color: var(--color-on-surface);
}
.toolbar-selection .toolbar-selection-clear {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border: none; border-radius: var(--radius-full);
  background: transparent; color: var(--color-on-surface-variant); cursor: pointer;
  transition: background var(--duration-fast) var(--ease-default);
}
.toolbar-selection .toolbar-selection-clear:hover { background: color-mix(in srgb, var(--color-on-surface) 8%, transparent); }
.toolbar-selection .toolbar-selection-clear:focus-visible {
  outline: 2px solid var(--color-focus); outline-offset: 2px; box-shadow: 0 0 0 4px var(--color-focus-ring);
}

/* ═══════════════════════════════════════
   VARIANTE · Overflow (espacio limitado)
   Los controles que no entran se mueven a un menú "más" (icon-btn + Dropdown/Popover).
   El botón de overflow es un icon-btn estándar; el menú es una composición con
   dropdown-menu.css / popover.css (use-flyout). Aquí sólo el layout del disparador.
═══════════════════════════════════════ */
.toolbar-overflow-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--tb-height, 40px); height: var(--tb-height, 40px);
  flex-shrink: 0;
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-md);
  color: var(--color-on-surface-variant); cursor: pointer;
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default);
}
.toolbar-overflow-btn:hover { background: var(--color-surface-variant); color: var(--color-on-surface); }
.toolbar-overflow-btn:focus-visible {
  outline: 2px solid var(--color-focus); outline-offset: 2px; box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.toolbar-overflow-btn > svg, .toolbar-overflow-btn > i { width: 20px; height: 20px; }

/* ═══════════════════════════════════════
   VARIANTE · Sticky
   La barra queda fija al hacer scroll (encabezado de tabla/lista larga).
═══════════════════════════════════════ */
.toolbar-sticky {
  position: sticky; top: 0; z-index: 20;
  background: var(--color-surface);
  /* respiro para que el contenido no “toque” la barra al scrollear */
  padding-top: var(--space-2); padding-bottom: var(--space-2);
  margin-bottom: var(--space-2);
  border-bottom: 1px solid transparent;
  transition: border-color var(--duration-fast) var(--ease-default),
              box-shadow var(--duration-fast) var(--ease-default);
}
/* El producto agrega .is-stuck (vía IntersectionObserver) al despegarse del top. */
.toolbar-sticky.is-stuck {
  border-bottom-color: var(--border);
  box-shadow: var(--shadow-sm);
}

/* ═══════════════════════════════════════
   Responsive — la barra hace wrap por defecto (flex-wrap arriba). En viewports
   angostos: el search-field pasa a ancho completo y las acciones dejan de empujar.
═══════════════════════════════════════ */
@media (max-width: 768px) {
  .toolbar .search-field { flex-basis: 100%; }
  .toolbar-actions { margin-left: 0; }           /* deja de empujar a la derecha al apilarse */
}

/* ═══════════════════════════════════════
   Reduced motion — además del bloque global de base.css (GOVERNANCE §13.3).
═══════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .toolbar-filters .select-trigger,
  .toolbar-filters .date-picker-trigger,
  .toolbar-filters .seg-btn,
  .toolbar-filters .search-field,
  .toolbar-selection,
  .toolbar-sticky { transition: none; animation: none; }
}

/* ── components/badge.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Badge
   Indicadores de estado y etiquetas de categoría.

   Cuándo usar: estado o categoría de SOLO LECTURA de un ítem (tablas, cards, listas). Todo label categórico debe ser badge — nunca texto plano coloreado.
   Cuándo no: si es interactivo/filtrable usar Chip; si es una acción usar Button.
   Reemplaza a: labels de texto plano con color, pills custom, tags legacy.

   Dependencia: variables.css
   Requiere: (ninguno)

   Variantes semánticas de estado:
   <span class="badge badge-open">Abierta</span>
   <span class="badge badge-active">Activa</span>
   <span class="badge badge-closed">Cerrada</span>
   <span class="badge badge-draft">Borrador</span>
   <span class="badge badge-archived">Archivada</span>
   <span class="badge badge-warning">Urgente</span>
   <span class="badge badge-tertiary">Nuevo</span>
   <span class="badge badge-info">Informativo</span>

   Modificador Label (monoespaciado / categoría):
   <span class="badge badge-info badge-label">Styles · Color</span>

   Uso:
   <span class="badge badge-open">Abierta</span>
═══════════════════════════════════════ */

/* ── Base ── */
.badge {
  display: inline-flex; align-items: center;
  font-size: var(--font-size-badge); font-weight: 600;
  padding: var(--space-1) var(--space-2-5); border-radius: var(--radius-full);
  white-space: nowrap;
}

/* ── Variantes de color ── */
/* La variacion de una Stat Card es el mismo objeto que un badge: la misma
   pildora, el mismo par container/on-container y el mismo tratamiento de
   oscuro. Los modificadores de tendencia se listan ACA y no en stat-card.css
   para que la forma y el tinte de dark tengan una sola fuente (GOVERNANCE
   §22): una copia parafraseada del color-mix de abajo no fallaria cuando el
   porcentaje cambie — mentiria. El markup compone `badge stat-change
   stat-change-positive`. */
.badge-open, .stat-change-positive   { background: var(--color-success-container); color: var(--color-on-success-container); }
/* Activa = proceso en curso: color distinto de open (secondary-container), para que
   "abierta" (verde) y "activa" (en curso) no se lean idénticas. */
/* «En proceso» comparte registro con «informativo»: los dos son el azul neutro que
   no afirma ni exito ni error. Hasta sep 2026 `active` usaba --color-secondary-container,
   que es el token de SELECCION (chip, segmented, calendar, pagination, menu activo):
   un badge de estado y un estado de seleccion quedaban indistinguibles en la misma
   pantalla. La seleccion es transversal a toda la UI y el badge es de una familia,
   asi que cede el badge. Queda como alias de la variante info — se mantiene la clase
   para no romper los usos existentes. */
.badge-active { background: var(--color-info-container); color: var(--color-on-info-container); }
.badge-closed, .stat-change-negative { background: var(--color-error-container);   color: var(--color-on-error-container); }
/* Tendencia plana: la misma superficie tonal que .badge-draft, sin el borde
   (draft usa el borde para decir "todavia no publicado"; una variacion de cero
   no dice nada de eso). En oscuro el par surface-variant/on-surface-variant ya
   es tonal, asi que no necesita la excepcion de abajo. */
.stat-change-neutral { background: var(--color-surface-variant); color: var(--color-on-surface-variant); }
.badge-draft  { background: var(--color-surface-variant);   color: var(--color-on-surface-variant); border: 1px solid var(--color-outline); }
/* Archivada = la misma superficie tonal que draft, SIN su borde. Hasta sep 2026
   llevaba `opacity: .75` para verse "apagada": medido, eso bajaba el contraste real
   de 4.64:1 a 2.91:1 en claro —por debajo de AA— porque la opacidad mezcla el texto
   Y el fondo contra la pagina. El chequeo [15] no lo veia: compara tokens, y los
   tokens estaban bien; lo que fallaba era el pixel. Lo apagado ahora lo dice la
   ausencia de borde, que no toca el contraste. */
.badge-archived { background: var(--color-surface-variant); color: var(--color-on-surface-variant); }
.badge-warning  { background: var(--color-warning-container); color: var(--color-on-warning-container); }
.badge-tertiary { background: var(--color-tertiary-container); color: var(--color-on-tertiary-container); }
.badge-info     { background: var(--color-info-container);    color: var(--color-on-info-container); }

/* ── Modificador Label: etiqueta de categoría/sección (tipografía monoespaciada) ── */
.badge-label {
  font-family: var(--font-mono);
  font-size: var(--font-size-overline-sm); font-weight: 500;
  text-transform: uppercase; letter-spacing: var(--letter-spacing-label);
}


/* ── Ícono / spinner líder ── el badge admite un SVG o un spinner al inicio
   (par shadcn "with icon" / "with spinner"). El gap no afecta a los badges de
   solo texto. */
.badge { gap: var(--space-1); }
.badge > svg { width: 12px; height: 12px; flex-shrink: 0; }
/* El spinner dentro de un badge hereda el color del texto para contrastar sobre
   cualquier variante (en vez de los tokens fijos primary/outline). */
.badge .spinner {
  width: 12px; height: 12px; border-width: 2px;
  border-color: color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
  vertical-align: baseline;
}

/* ── Link ── badge navegable. Como <a class="badge …"> (asChild) hereda su
   variante y agrega hover; la variante .badge-link es el estilo de enlace de
   texto de shadcn (sin relleno, color de acento, subrayado en hover). */
a.badge { text-decoration: none; cursor: pointer; transition: opacity var(--duration-fast) var(--ease-default); }
a.badge:hover { opacity: .85; }
a.badge:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.badge-link { background: transparent; color: var(--color-primary); padding-left: var(--space-0-5); padding-right: var(--space-0-5); }
a.badge-link:hover { opacity: 1; text-decoration: underline; text-underline-offset: 3px; }

/* ── Dark: tratamiento tonal suave ── en dark, en vez del relleno saturado
   profundo (que además dejaba texto claro sobre color medio por debajo de AA),
   el badge usa un TINTE sutil del color sobre la superficie + el texto claro
   on-container. Es el mismo lenguaje que light, invertido: pill tenue + texto
   vívido → contraste AA holgado y vividez pareja entre variantes.
   Excepción documentada al "sin bloque por-tema" (aprobada; scope = badge). */
[data-theme="dark"] .badge-open,
[data-theme="dark"] .stat-change-positive { background: color-mix(in srgb, var(--color-success-container)   42%, var(--color-surface)); }
[data-theme="dark"] .badge-active  { background: color-mix(in srgb, var(--color-secondary-container) 55%, var(--color-surface)); }
[data-theme="dark"] .badge-closed,
[data-theme="dark"] .stat-change-negative { background: color-mix(in srgb, var(--color-error-container)     42%, var(--color-surface)); }
[data-theme="dark"] .badge-warning { background: color-mix(in srgb, var(--color-warning-container)   42%, var(--color-surface)); }
[data-theme="dark"] .badge-tertiary{ background: color-mix(in srgb, var(--color-tertiary-container)  42%, var(--color-surface)); }
[data-theme="dark"] .badge-info    { background: color-mix(in srgb, var(--color-info-container)      42%, var(--color-surface)); }

/* ── components/card.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Card
   Contenedor basico con borde y fondo blanco.

   Cuándo usar: contenedor genérico de contenido agrupado sobre superficie.
   Cuándo no: métricas (stat-card), personas (person-card), vacantes (vacancy-card).
   Reemplaza a: paneles/boxes legacy con borde o sombra.

   MD3 tiene tres variantes, todas expresadas con roles Embassy:
     .card           (outlined, predeterminado) · superficie + borde 1px, sin elevación
     .card-elevated  · surface-container-low + sombra sm, sin borde
     .card-filled    · surface-container-highest, sin borde ni sombra

   Dependencia: variables.css
   Requiere: (ninguno)

   Uso:
   <div class="card">
     <div class="card-header">
       <div class="card-title">Título</div>
       <p class="card-desc">Descripción breve.</p>
     </div>
     <div class="card-content">Contenido de la tarjeta</div>
     <div class="card-footer"><button class="btn-text">Acción</button></div>
   </div>
═══════════════════════════════════════ */

.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* Panel = escalon estandar. COMPOSICION.md §4b + guidelines/visual-hierarchy.md.
     Hasta sep 2026 eran 16/20: el escalon compacto en vertical y un valor que no
     era escalon en horizontal, sin que nadie lo hubiera decidido. */
  padding: var(--space-6);
}

/* ── Variantes MD3 ── */
.card-elevated {
  background: var(--color-surface-container-low);
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: var(--space-6);
  box-shadow: var(--shadow-sm);
}
.card-filled {
  background: var(--color-surface-container-highest);
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: var(--space-6);
}

/* ── Sub-partes (Basic Card) ── */
/* Header como grid (par shadcn CardHeader): título + descripción apilados en la
   columna izquierda; si hay una .card-action, reserva una columna derecha para
   ella (col-start-2, abarca ambas filas), p.ej. un icon-btn "⋮" o un Switch. */
.card-header {
  margin-bottom: var(--space-3);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-1);
}
.card-header:has(.card-action) {
  grid-template-columns: 1fr auto;
}
.card-action {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: start;
  justify-self: end;
}
.card-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-heading-xs);
  line-height: var(--line-height-heading-xs);
  letter-spacing: var(--letter-spacing-heading-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
}
.card-desc {
  font-size: var(--font-size-body-sm);
  color: var(--text-secondary);
  line-height: var(--line-height-body-sm);
}

/* El titular de la tarjeta que ABRE una sección — la que
   `guidelines/visual-hierarchy.md` manda a --space-8 porque lleva título, bajada
   y lista. El padding ya tenía tres pasos y el titular uno solo: una tarjeta con
   32px de aire y un titular de 15px queda desbalanceada, el aire la agranda y el
   título no la acompaña.
   Va un escalón arriba en la MISMA escala, con su par de interlineado y su
   tracking. No es "más grande": es el paso que le corresponde a esa tarjeta.
   Nota: GOVERNANCE §8.2 decía que --font-size-heading-md era «titular de tarjeta»
   mientras .card-title usaba heading-xs. Las dos cosas eran ciertas para tarjetas
   distintas y ninguna lo aclaraba; ahora la tabla distingue las dos clases. */
.card-title-lg {
  font-family: var(--font-heading);
  font-size: var(--font-size-heading-md);
  line-height: var(--line-height-heading-md);
  letter-spacing: var(--letter-spacing-heading-md);
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
}
.card-content {
  font-size: var(--font-size-body-md);
  color: var(--text-primary);
}
.card-footer {
  margin-top: var(--space-4);
  display: flex; align-items: center; gap: var(--space-2);
}

/* ── Contexto de superficie ──────────────────────────────────────────────────
   Una tarjeta pinta su propio fondo, asi que declara el contexto para lo que
   vaya adentro. Sin esto, un `list` adentro de una card pintaba el gris de la
   pagina sobre el blanco de la card y habia que escribirle `background:
   transparent` a mano — un override de clase publica por cada uso. */
.card                { --ctx-surface: var(--card-bg);                            --ctx-surface-raised: var(--color-surface-container-high); }
.card-elevated       { --ctx-surface: var(--color-surface-container-low);        --ctx-surface-raised: var(--color-surface-container-high); }
.card-filled         { --ctx-surface: var(--color-surface-container-highest);    --ctx-surface-raised: var(--color-surface-container); }

/* La valvula para un panel que NO es una card del DS — un rail, un sheet, una
   region propia del proyecto. Se declara en el contenedor y los componentes de
   adentro se acomodan solos. */
[data-surface="surface"]           { --ctx-surface: var(--color-surface);                        --ctx-surface-raised: var(--color-surface-container); }
[data-surface="container-low"]     { --ctx-surface: var(--color-surface-container-low);          --ctx-surface-raised: var(--color-surface-container); }
[data-surface="container"]         { --ctx-surface: var(--color-surface-container);              --ctx-surface-raised: var(--color-surface-container-high); }
[data-surface="container-high"]    { --ctx-surface: var(--color-surface-container-high);         --ctx-surface-raised: var(--color-surface-container-highest); }

/* ── components/form.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Form
   Elementos de formulario: campos, labels, selects, textareas,
   inputs numericos, inputs de fecha, y tarjeta de formulario.

   Cuándo usar: todo campo de entrada — input, select, textarea, number, date — con label, hint y error.
   Cuándo no: búsqueda (usar search-field de toolbar.css o search-bar de search.css).
   Reemplaza a: cualquier form control legacy; nunca inventar campos custom.

   Dependencia: variables.css
   Requiere: (ninguno)

   Uso:
   <div class="form-card">
     <div class="form-card-title">Informacion basica</div>
     <p class="form-card-desc">Completa los datos del puesto.</p>
     <div class="fields-grid">
       <div class="field-group">
         <label class="field-label">Titulo <span class="field-required">*</span></label>
         <input class="field-input" placeholder="Ej: Desarrollador Frontend">
       </div>
       <div class="field-group">
         <label class="field-label">Ubicacion</label>
         <div class="select-wrapper">
           <select><option>Remoto</option></select>
           <span class="select-caret">▾</span>
         </div>
       </div>
     </div>
   </div>
═══════════════════════════════════════ */

/* Panel que abre seccion (COMPOSICION.md §4b): escalon holgado, y su titulo
   sube con el — los dos pasos son un par, no dos decisiones sueltas. */
.form-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-8);
  margin-bottom: var(--space-4);
}
.form-card-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-heading-md);
  line-height: var(--line-height-heading-md);
  letter-spacing: var(--letter-spacing-heading-md);
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
  margin-bottom: var(--space-1-5);
}
.form-card-desc {
  font-size: var(--font-size-body-sm); color: var(--text-secondary);
  line-height: var(--line-height-body-sm);
  margin-bottom: var(--space-6);
  max-width: 680px;
}

.field-group { margin-bottom: 20px; }
.field-group:last-child { margin-bottom: 0; }

.field-label {
  display: block;
  font-size: var(--font-size-label); font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 8px;
}
.field-required { color: var(--red); margin-left: 2px; }

.field-input {
  width: 100%;
  padding: var(--space-2-5) var(--space-3-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: var(--font-size-body-md);
  font-family: inherit;
  color: var(--text-primary);
  background: var(--ctx-surface-raised);
  outline: none;
  transition: border-color var(--duration-fast) var(--ease-default), box-shadow var(--duration-fast) var(--ease-default);
  appearance: none;
}
.field-input:focus-visible {
  border-color: var(--color-focus);
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.field-input::placeholder { color: var(--text-muted); }

/* ── File input (par shadcn: botón de selección tonal + texto) ── */
.field-input[type="file"] {
  padding: 7px var(--space-3-5);
  cursor: pointer;
  color: var(--text-secondary);
}
.field-input[type="file"]::file-selector-button {
  margin: 0 12px 0 0;
  padding: var(--space-1) var(--space-3);
  border: none;
  border-radius: var(--radius-sm);
  background: var(--color-secondary-container);
  color: var(--color-on-secondary-container);
  font: inherit;
  font-weight: 500;
  cursor: pointer;
}
.field-input[type="file"]:hover::file-selector-button {
  background: color-mix(in srgb, var(--color-secondary-container) 88%, #000);
}

/* ── Error a nivel de elemento (par shadcn aria-invalid) ──
   Además del wrapper .field-group.is-error, un control con aria-invalid="true"
   se pinta en rojo por sí solo — robusto cuando no hay wrapper y consistente
   con input.tsx (que setea aria-invalid automáticamente). */
.field-input[aria-invalid="true"],
.select-wrapper select[aria-invalid="true"],
.field-textarea[aria-invalid="true"] {
  border-color: var(--color-error);
}
.field-input[aria-invalid="true"]:focus-visible,
.select-wrapper select[aria-invalid="true"]:focus-visible,
.field-textarea[aria-invalid="true"]:focus-visible {
  border-color: var(--color-error);
  box-shadow: 0 0 0 4px var(--color-error-ring);
}

input:disabled, select:disabled, textarea:disabled {
  background: var(--color-disabled);
  color: var(--color-on-disabled);
  border-color: var(--color-outline);
  cursor: not-allowed;
}

.select-wrapper { position: relative; }
.select-wrapper select {
  width: 100%;
  padding: var(--space-2-5) 38px var(--space-2-5) var(--space-3-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: var(--font-size-body-md);
  font-family: inherit;
  color: var(--text-primary);
  background: var(--card-bg);
  outline: none;
  appearance: none;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-default), box-shadow var(--duration-fast) var(--ease-default);
}
.select-wrapper select:focus-visible {
  border-color: var(--interactive);
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
/* Compact size (par shadcn Select size="sm" / select-trigger[data-size="sm"]). */
.select-wrapper select[data-size="sm"] {
  min-height: 32px;
  padding: 5px 34px 5px var(--space-3);
  font-size: var(--font-size-body-sm);
}
.select-caret {
  position: absolute; right: 13px; top: 50%; transform: translateY(-50%);
  pointer-events: none; color: var(--text-muted);
}

.fields-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4) var(--space-5);
}

.field-textarea {
  resize: vertical;
  min-height: 80px;
  line-height: var(--line-height-body-md);
  /* Auto-crece con el contenido donde el navegador lo soporte (par shadcn
     field-sizing:content); progresivo — se ignora sin soporte, queda el resize. */
  field-sizing: content;
}

.number-input-wrapper {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-2-5) var(--space-3-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  transition: border-color var(--duration-fast) var(--ease-default);
}
.number-input-wrapper:focus-within { border-color: var(--interactive); }
.number-value { font-size: var(--font-size-body-md); color: var(--text-primary); }
.number-controls { display: flex; align-items: center; gap: var(--space-1); }
.number-btn {
  width: 32px; height: 32px;
  border-radius: var(--radius-md);
  background: var(--bg);
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--text-secondary);
  transition: background var(--duration-fast) var(--ease-default);
  font-family: inherit;
}
.number-btn:hover { background: var(--color-surface-container-highest); color: var(--text-primary); }

.date-input-wrapper { position: relative; }
.date-input-wrapper input {
  width: 100%;
  padding: var(--space-2-5) 42px var(--space-2-5) var(--space-3-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: var(--font-size-body-md);
  font-family: inherit;
  color: var(--text-muted);
  background: var(--card-bg);
  outline: none;
  transition: border-color var(--duration-fast) var(--ease-default);
  cursor: pointer;
}
.date-input-wrapper input:focus-visible { border-color: var(--interactive); }
.date-icon {
  position: absolute; right: 13px; top: 50%; transform: translateY(-50%);
  color: var(--text-muted); pointer-events: none;
}

/* ═══════════════════════════════════════
   Hover state
   Distingue visualmente el hover del estado habilitado sin focus.
   Usa --color-outline (más prominente) vs --color-outline-variant (defecto).
═══════════════════════════════════════ */
.field-input:hover:not(:focus):not(:disabled) {
  border-color: var(--color-outline);
}

/* ═══════════════════════════════════════
   Label — color en focus
   La etiqueta cambia al color interactivo cuando el campo está activo,
   reforzando el foco visualmente.
═══════════════════════════════════════ */
.field-group:focus-within .field-label {
  color: var(--interactive);
}

/* ═══════════════════════════════════════
   Texto de soporte (Supporting text)
   Área debajo del campo para mensajes de ayuda, errores o contadores.

   Uso:
   <div class="field-supporting">
     <span class="field-hint">Máximo 100 caracteres</span>
     <span class="field-char-count">0/100</span>
   </div>
═══════════════════════════════════════ */
.field-supporting {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: 6px;
  padding: 0 var(--space-0-5);
}
.field-hint {
  font-size: var(--font-size-caption);
  line-height: var(--line-height-caption);
  color: var(--color-on-surface-variant);
}
.field-error-msg {
  font-size: var(--font-size-caption);
  line-height: var(--line-height-caption);
  color: var(--color-error-text);
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
}
.field-error-msg svg {
  flex-shrink: 0;
  margin-top: 1px;
}
.field-char-count {
  font-family: var(--font-mono);
  font-size: var(--font-size-badge);
  color: var(--color-on-surface-variant);
  white-space: nowrap;
  flex-shrink: 0;
  margin-left: auto;
}
.field-char-count.is-over {
  color: var(--color-error-text);
}

/* ═══════════════════════════════════════
   Estado de error
   Se aplica .is-error al .field-group contenedor.
   El mensaje de error reemplaza al texto de soporte normal.

   Uso:
   <div class="field-group is-error">
     <label class="field-label">Email</label>
     <input class="field-input" value="texto inválido">
     <div class="field-supporting">
       <span class="field-error-msg">
         <svg>...</svg> Ingresa un correo válido.
       </span>
     </div>
   </div>
═══════════════════════════════════════ */
.field-group.is-error .field-label {
  color: var(--color-error-text);
}
.field-group.is-error:focus-within .field-label {
  color: var(--color-error-text); /* el error sobreescribe el color interactivo */
}
.field-group.is-error .field-input {
  border-color: var(--color-error);
}
.field-group.is-error .field-input:hover:not(:focus):not(:disabled) {
  border-color: var(--color-error); /* el error sobreescribe el hover */
}
.field-group.is-error .field-input:focus-visible {
  border-color: var(--color-error);
  box-shadow: 0 0 0 4px var(--color-error-ring);
}
.field-group.is-error .field-hint {
  display: none; /* el mensaje de error reemplaza el texto de soporte */
}
.field-group.is-error .field-trailing-icon {
  color: var(--color-error-text);
}

/* ═══════════════════════════════════════
   Íconos iniciales y finales
   Usa .field-input-wrapper como contenedor relativo.
   Agrega .has-leading o .has-trailing al .field-input para ajustar padding.

   Uso:
   <div class="field-input-wrapper">
     <span class="field-leading-icon"><svg>...</svg></span>
     <input class="field-input has-leading has-trailing">
     <span class="field-trailing-icon"><svg>...</svg></span>
   </div>
═══════════════════════════════════════ */
.field-input-wrapper {
  position: relative;
}
.field-leading-icon,
.field-trailing-icon {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-on-surface-variant);
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
}
.field-leading-icon  { left: 14px; }
.field-trailing-icon { right: 14px; }

.field-input-wrapper .field-input.has-leading  { padding-left: 42px; }
.field-input-wrapper .field-input.has-trailing { padding-right: 42px; }

/* ── components/tabs.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Tabs
   Pestanas para organizar contenido en secciones.

   Cuándo usar: alternar vistas dentro del MISMO contexto/página.
   Cuándo no: navegación entre páginas (usar sidebar de layout.css).
   Reemplaza a: tab bars legacy.

   Dependencia: variables.css
   Requiere: (ninguno) — la barra indicadora animada usa JS mínimo (ver js/components.js).

   Selected-state token: usa --color-secondary, NUNCA --color-primary — Primary
   se invierte a blanco en dark mode, lo que en un indicador de selección pasivo
   se lee como "sin color" y rompe la consistencia visual con el resto de los
   estados seleccionados de la librería (Chip, Segmented Button — familia
   secondary-container). Secondary se mantiene azul en ambos temas.

   Uso:
   <div class="tabs" role="tablist">
     <!-- El estado lo declara aria-selected; .active es un alias que el JS agrega y el CSS también pinta. -->
     <button class="tab" role="tab" aria-selected="true" id="tab-general" aria-controls="panel-general">General</button>
     <button class="tab" role="tab" aria-selected="false" id="tab-requisitos" aria-controls="panel-requisitos" tabindex="-1">Requisitos</button>
     <span class="tab-indicator"></span>
   </div>
   <div class="tab-panel active" id="panel-general" role="tabpanel" aria-labelledby="tab-general">…</div>
   <div class="tab-panel" id="panel-requisitos" role="tabpanel" aria-labelledby="tab-requisitos" hidden>…</div>
═══════════════════════════════════════ */

.tabs {
  position: relative;
  display: flex;
  border-bottom: 1px solid var(--border);
  gap: 0;
  margin-bottom: 28px;
}
.tab {
  padding: 11px var(--space-5);
  font-size: var(--font-size-body-md);
  font-family: inherit;
  color: var(--color-on-surface-variant);
  background: transparent;
  border: 0;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--duration-fast) var(--ease-default);
  white-space: nowrap;
  user-select: none;
}
.tab:hover { color: var(--color-on-surface); }
.tab:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.tab:active { color: var(--color-secondary-text); }
.tab.active,
.tab[aria-selected="true"] {
  color: var(--color-secondary-text);
  font-weight: 600;
}
.tab:disabled {
  opacity: .4;
  cursor: not-allowed;
  pointer-events: none;
}

/* Barra indicadora deslizante — una sola vez por .tabs, posicionada por JS
   sobre el tab activo (left/width vía offsetLeft/offsetWidth). El salto entre
   tabs usa la curva Expressive (transform/width), igual que el resto de los
   indicadores "seleccionados" con overshoot espacial. */
.tab-indicator {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 2px;
  background: var(--color-secondary);
  transition: transform var(--duration-normal, 220ms) var(--ease-expressive, ease-out),
              width var(--duration-normal, 220ms) var(--ease-expressive, ease-out);
  pointer-events: none;
}

.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* ═══ Variante pill / filled ═══ par shadcn default TabsList (contenedor tenue,
   la pestaña activa como superficie elevada). Nota de diseño: para un switch de
   vistas tipo toggle, preferí Segmented Button — esta variante existe para dar
   paridad con la API de Tabs de shadcn cuando el patrón pill es el correcto. */
.tabs-pill {
  display: inline-flex;
  gap: var(--space-1);
  padding: var(--space-1);
  border-bottom: none;
  border-radius: var(--radius-md);
  background: var(--color-surface-variant);
}
.tabs-pill .tab {
  border-bottom: none;
  margin-bottom: 0;
  padding: var(--space-1-5) var(--space-3-5);
  border-radius: var(--radius-sm);
}
.tabs-pill .tab.active,
.tabs-pill .tab[aria-selected="true"] {
  background: var(--card-bg);
  color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}
.tabs-pill .tab-indicator { display: none; }

/* ═══ Orientación vertical ═══ par shadcn orientation="vertical": lista en
   columna con el borde/indicador a la derecha. Usa un borde activo estático
   (no la barra deslizante, que es horizontal), así no requiere JS extra. */
.tabs-vertical {
  flex-direction: column;
  align-items: stretch;
  border-bottom: none;
  border-right: 1px solid var(--border);
  margin-bottom: 0;
}
.tabs-vertical .tab {
  border-bottom: none;
  border-right: 2px solid transparent;
  margin-bottom: 0;
  margin-right: -1px;
  text-align: left;
}
.tabs-vertical .tab.active,
.tabs-vertical .tab[aria-selected="true"] { border-right-color: var(--color-secondary); }
.tabs-vertical .tab-indicator { display: none; }

/* ── components/table.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Table
   Tabla de datos con encabezados, filas hover y variante clickable.

   Cuándo usar: datos tabulares; filas navegables con tr.clickable.
   Cuándo no: layouts (usar grid); tableros de flujo (usar Kanban).
   Reemplaza a: tablas legacy.

   Dependencia: variables.css
   Requiere: (ninguno)

   Wrapper: envolvé la tabla en .table-scroll para que las tablas anchas hagan
   scroll horizontal DENTRO de su propio contenedor en vez de estirar la página
   o recortar contenido — el radio/borde vive en el wrapper (así las esquinas
   siguen recortando el header), no en <table>.

   Uso:
   <div class="table-scroll">
     <table class="data-table">
       <thead>
         <tr><th>Nombre</th><th>Estado</th><th>Fecha</th></tr>
       </thead>
       <tbody>
         <tr class="clickable"><td>Vacante 1</td><td>Abierta</td><td>15 Mar</td></tr>
       </tbody>
     </table>
   </div>
═══════════════════════════════════════ */

.table-scroll {
  position: relative;
  width: 100%;
  overflow-x: auto;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.data-table {
  width: 100%;
  border-collapse: collapse;
}
.data-table thead {
  background: var(--color-surface-variant);
  border-bottom: 1px solid var(--border);
}
.data-table th {
  padding: var(--space-2-5) var(--space-4);
  font-size: var(--font-size-overline);
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-label);
  text-align: left;
}
.data-table td {
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-size-body-md);
  color: var(--text-primary);
  border-top: 1px solid var(--border);
}
.data-table tbody tr {
  transition: background var(--duration-fast) var(--ease-default);
}
.data-table tbody tr:hover {
  background: var(--color-surface-variant);
}
.data-table tbody tr.clickable {
  cursor: pointer;
}
/* Selected row (e.g. Data Table's row-selection feature) */
.data-table tbody tr.selected,
.data-table tbody tr[aria-selected="true"] {
  background: var(--color-surface-container-high);
}
/* Las filas clickeables necesitan tabindex="0" para recibir foco de teclado */
.data-table tbody tr.clickable:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

/* ── Footer (tfoot) ── fila de totales/resumen: borde superior, fondo tenue y
   peso medio, par shadcn TableFooter (border-t, bg-muted/50, font-medium). */
.data-table tfoot {
  border-top: 1px solid var(--border);
  background: color-mix(in srgb, var(--color-surface-variant) 50%, transparent);
  font-weight: 600;
}
.data-table tfoot td {
  border-top: none;
  color: var(--text-primary);
}

/* ── Caption ── título o nota de la tabla, al pie (par shadcn TableCaption). */
.data-table caption {
  caption-side: bottom;
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-size-body-sm);
  color: var(--text-secondary);
  text-align: left;
}


/* ── Alineación numérica ──
   guidelines/tables-and-data.md §32 exige columnas numéricas a la derecha con
   tabular-nums, y el camino React lo resuelve con `text-right`. El CSS forzaba
   text-align:left en th y td sin ningún modificador, así que toda tabla
   buildless lo peleaba con especificidad propia. Estos dos lo resuelven. */
.data-table th.numeric,
.data-table td.numeric,
.table th.numeric,
.table td.numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ── components/stat-card.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Stat Card
   Tarjetas KPI con etiqueta, valor grande y cambio porcentual.

   Cuándo usar: métricas numéricas con label y variación (KPIs en dashboards).
   Cuándo no: contenido no numérico (usar Card).
   Reemplaza a: widgets de métricas legacy.

   Dependencia: variables.css
   Requiere: (ninguno)

   Uso:
   <div class="stats-grid">
     <div class="stat-card">
       <div class="stat-label">Vacantes abiertas</div>
       <div class="stat-value">24</div>
       <div class="badge stat-change stat-change-positive">
         <i data-lucide="trending-up" aria-hidden="true"></i>12%
       </div>
     </div>
   </div>
═══════════════════════════════════════ */

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}
/* Dato (§4b): escalon compacto — la cifra es lo que pesa, no el aire. */
.stat-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
}
.stat-card .stat-label {
  font-size: var(--font-size-body-sm);
  line-height: var(--line-height-body-sm);
  color: var(--text-muted);
  margin-bottom: var(--space-2);
}
.stat-card .stat-value {
  font-family: var(--font-heading);
  font-size: var(--font-size-display);
  line-height: var(--line-height-display);
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: var(--letter-spacing-display);
}
/* La variacion va DENTRO de una pildora: se compone con `.badge`, que aporta la
   forma (--radius-full), el padding, --font-size-badge y el SVG a 12px. Este
   archivo no vuelve a declarar nada de eso.
   Los tres modificadores de tendencia (-positive / -negative / -neutral) viven
   en badge.css, junto al resto de las variantes de color y a su excepcion de
   oscuro. */
.stat-card .stat-change {
  margin-top: var(--space-2);
  /* Guarda, no estilo. El contenido esta limitado a 12 caracteres (ver
     component-rules/stat-card.md): el ancho del chip es ~20px de padding mas
     ~6.1px por caracter, y la card mas angosta que `stats-grid` puede producir
     es 200px = 160px utiles, o sea ~23 caracteres antes de romper. El maximo
     de 12 deja la mitad de ese margen para traducciones mas largas y para un
     proyecto con una escala mayor.
     Si aun asi alguien se pasa, la pildora ENVUELVE en vez de desbordar la
     card: queda fea —que es el punto, se ve el error— pero no se pierde
     ningun digito. Un `text-overflow: ellipsis` aca ocultaria parte de un
     numero, y un dato a medias miente. Es la unica propiedad de `.badge` que
     este archivo pisa, y es a proposito. */
  max-width: 100%;
  white-space: normal;
}

/* ── components/back-link.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Back Link
   Enlace de retorno con icono para navegacion.

   Cuándo usar: volver a la vista padre desde una página de detalle.
   Cuándo no: navegación principal (usar sidebar/tabs) ni acciones (usar btn-text).
   Reemplaza a: links "‹ Volver" ad-hoc.

   Dependencia: variables.css
   Requiere: (ninguno)

   Uso:
   <button class="back-link">
     <svg width="16" height="16">…</svg>
     Volver a vacantes
   </button>
═══════════════════════════════════════ */

.back-link {
  display: inline-flex; align-items: center; gap: var(--space-1-5);
  font-size: var(--font-size-body-md); color: var(--text-secondary);
  cursor: pointer;
  transition: color var(--duration-fast);
  margin-bottom: 6px;
  background: none; border: none; font-family: inherit; padding: 0;
}
.back-link:hover { color: var(--text-primary); text-decoration: none; }
.back-link:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

/* ── components/description.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Description Section
   Secciones colapsables con editor de texto y boton eliminar.

   Cuándo usar: secciones de descripción editables (título + editor rich-text).
   Cuándo no: campos simples de formulario (usar form.css).
   Reemplaza a: editores de descripción ad-hoc.

   Dependencia: variables.css
   Requiere: (ninguno)

   Uso:
   <div class="desc-section">
     <div class="desc-section-header">
       <input class="desc-title-input" value="Responsabilidades">
       <button class="desc-delete-btn">✕</button>
     </div>
     <div class="desc-editor" contenteditable="true"
          data-placeholder="Escribe aqui...">
     </div>
   </div>
═══════════════════════════════════════ */

.desc-section {
  margin-bottom: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.desc-section-header {
  padding: var(--space-2-5) var(--space-3) var(--space-2-5) var(--space-4);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: var(--space-2);
}
.desc-title-input {
  flex: 1;
  border: none;
  background: transparent;
  font-size: var(--font-size-body-md);
  font-weight: 600;
  font-family: inherit;
  color: var(--text-primary);
  outline: none;
  padding: 0;
  min-width: 0;
}
.desc-title-input:focus-visible { color: var(--interactive); }
.desc-delete-btn {
  width: 28px; height: 28px;
  border-radius: var(--radius-md);
  border: none;
  background: transparent;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--duration-fast), color var(--duration-fast);
  flex-shrink: 0;
  opacity: 0;
}
.desc-section:hover .desc-delete-btn { opacity: 1; }
.desc-delete-btn:hover { background: var(--color-error-container); color: var(--color-on-error-container); }
.desc-editor {
  min-height: 100px;
  padding: var(--space-3-5) var(--space-4);
  font-size: var(--font-size-body-md);
  font-family: inherit;
  color: var(--text-primary);
  line-height: var(--line-height-prose);
  outline: none;
  background: var(--card-bg);
  transition: box-shadow var(--duration-fast);
}
.desc-editor:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: -2px;
  box-shadow: inset 0 0 0 4px var(--color-focus-ring);
}
.desc-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--text-muted);
  pointer-events: none;
}

/* ── components/skeleton.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Skeleton
   Animacion de carga shimmer para placeholders.

   Cuándo usar: carga de contenido con estructura conocida (mientras llega la data).
   Cuándo no: estados vacíos (usar Empty State). No usar spinners ad-hoc.
   Reemplaza a: spinners/loaders custom.

   Dependencia: variables.css
   Requiere: (ninguno)

   Uso:
   <div class="skeleton skeleton-title"></div>
   <div class="skeleton skeleton-text"></div>
   <div class="skeleton skeleton-text" style="width:80%"></div>
   <div class="skeleton skeleton-card"></div>
═══════════════════════════════════════ */

.skeleton {
  background: linear-gradient(90deg, var(--color-surface-variant) 25%, var(--color-surface-container-highest) 50%, var(--color-surface-variant) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: var(--radius-md);
}
@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.skeleton-text { height: 14px; margin-bottom: 8px; }
.skeleton-title { height: 22px; width: 60%; margin-bottom: 12px; }
.skeleton-card { height: 80px; margin-bottom: 8px; }
/* Círculo — placeholder para avatares/íconos (par shadcn <Skeleton className="rounded-full">). */
.skeleton-circle,
.skeleton-avatar { width: 40px; height: 40px; border-radius: var(--radius-full); }

/* ── components/empty-state.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Empty State
   Placeholder visual cuando no hay datos para mostrar.

   Cuándo usar: listas/tablas/búsquedas sin resultados o sin datos aún.
   Cuándo no: features en construcción (usar Placeholder); carga en progreso (usar Skeleton).
   Reemplaza a: textos sueltos "no hay datos".

   Dependencia: variables.css
   Requiere: button.css (para CTA opcional)

   Uso:
   <div class="empty-state">
     <i data-lucide="inbox" class="empty-state-icon" aria-hidden="true"></i>
     <div class="empty-state-title">No hay vacantes</div>
     <p class="empty-state-desc">Crea tu primera vacante para empezar.</p>
     <button class="btn-primary">+ Nueva vacante</button>
   </div>
═══════════════════════════════════════ */

.empty-state {
  text-align: center;
  padding: var(--space-12) var(--space-6);
  color: var(--text-muted);
}
/* El icono de un estado vacio es el caso donde el icono ES el contenido
   (COMPOSICION.md regla 5, ubicacion 4): grande, con aire, sin superficie
   detras. Va en Lucide. Un emoji no es un icono — falla `H4`.
   font-size se conserva solo por compatibilidad con markup viejo que mete un
   glifo; el markup correcto es un <svg>. */
.empty-state-icon {
  width: 48px; height: 48px;
  margin: 0 auto 16px;
  display: flex; align-items: center; justify-content: center;
  color: var(--color-on-surface-variant);
  font-size: 48px;  /* ds-allow: es la caja del glifo heredado, no tipografia — el SVG manda abajo */
}
.empty-state-icon svg { width: 48px; height: 48px; stroke-width: var(--icon-stroke, 2); }
.empty-state-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-heading-md); font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 8px;
}
.empty-state-desc {
  font-size: var(--font-size-body-lg); color: var(--text-muted);
  max-width: 400px; margin: 0 auto 20px;
  line-height: var(--line-height-prose);
}

/* ── components/modal.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Dialog (alias histórico: Modal)
   Diálogo centrado con scrim, header/body/footer y un único nivel de anidado.
   Estructura canónica shadcn (2026-07): borderless — sin dividers internos, un
   solo padding de 24px (p-6) en el contenedor y 16px (gap-4) de ritmo vertical
   entre header/body/footer en lugar de padding por sección.

   Cuándo usar: confirmaciones y tareas cortas que bloquean el contexto actual.
   Cuándo no: formularios largos (página con create-form); feedback pasivo (usar Snackbar).
   Reemplaza a: dialogs/popups/lightboxes legacy.

   Jerarquía de acciones del footer (obligatoria, máx. 2 botones):
     Cancelar/Descartar → btn-tertiary (Outlined) — siempre habilitado, nunca compite.
     Confirmar          → btn-primary (Filled), verbo claro, al extremo trailing.
     Confirmar destructivo → btn-primary btn-danger (Error), en vez de Filled normal.

   Alert Dialog (confirmación bloqueante, sin componente aparte — es este mismo
   Dialog con data-alert="true"): no se cierra con click afuera ni Escape, exige
   elegir Cancelar/Confirmar. El wrapper React lo maneja vía la prop `alertMode`.

   Dependencia: variables.css, base.css (keyframes fadeIn, slideInUp)
   Requiere: button.css (para botones en el footer)

   Uso:
   <div class="modal-overlay">
     <div class="modal">
       <button class="modal-close">✕</button>
       <div class="modal-header">
         <div class="modal-title">¿Eliminar la vacante?</div>
         <div class="modal-description">Esta acción no se puede deshacer.</div>
       </div>
       <div class="modal-body">
         <p>Se eliminarán también sus postulaciones asociadas.</p>
       </div>
       <div class="modal-footer">
         <button class="btn-tertiary">Cancelar</button>
         <button class="btn-primary btn-danger">Eliminar</button>
       </div>
     </div>
   </div>
═══════════════════════════════════════ */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--color-scrim);
  display: flex; align-items: center; justify-content: center;
  z-index: 100;
  animation: fadeIn var(--duration-fast) var(--ease-enter);
}
.modal {
  position: relative;
  background: var(--card-bg);
  border-radius: var(--radius-lg);
  width: min(560px, calc(100vw - 48px));
  max-height: calc(100vh - 96px);
  overflow-y: auto;
  box-shadow: var(--shadow-lg);
  animation: slideInUp var(--duration-normal) var(--ease-enter);
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.modal-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-1-5);
  padding-right: var(--space-8); /* deja lugar al close button absoluto */
}
.modal-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-heading-sm); font-weight: 600;
}
.modal-description {
  font-size: var(--font-size-body-sm);
  color: var(--color-on-surface-variant);
  line-height: var(--line-height-body-sm);
}
.modal-close {
  position: absolute;
  top: 16px; right: 16px;
  width: 32px; height: 32px;
  border-radius: var(--radius-md);
  border: none;
  background: transparent;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--color-on-surface-variant);
  transition: background var(--duration-fast) var(--ease-default),
              color var(--duration-fast) var(--ease-default);
}
.modal-close:hover { background: var(--color-surface-variant); color: var(--color-on-surface); }
.modal-close:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.modal-body { color: var(--color-on-surface-variant); }
.modal-footer {
  display: flex;
  flex-direction: column-reverse;
  gap: var(--space-2);
}
@media (min-width: 640px) {
  .modal-footer {
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;
  }
}

/* ═══ Alert Dialog: tamaño sm + slot de media ═══
   Par shadcn AlertDialog size="sm" + AlertDialogMedia. Se usan sobre el mismo
   Dialog con data-alert="true".

   .modal-sm        panel angosto (≈max-w-xs); footer en grid de 2 columnas de
                    igual peso (para dos acciones equivalentes de confirmación).
   .modal-media     ícono líder (típico en confirmaciones destructivas), 48px.
   .modal-centered  centra header + media (patrón alert centrado de shadcn). */
.modal-sm {
  width: min(360px, calc(100vw - 48px));
}
@media (min-width: 640px) {
  .modal-sm .modal-footer {
    display: grid;
    grid-template-columns: 1fr 1fr;
    justify-content: stretch;
  }
  .modal-sm .modal-footer > * { width: 100%; }
}

.modal-media {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-full);
  background: var(--color-surface-variant);
  color: var(--color-on-surface-variant);
}
.modal-media > svg,
.modal-media > i { width: 24px; height: 24px; }
.modal-media-error {
  background: var(--color-error-container);
  color: var(--color-on-error-container);
}

.modal-centered .modal-header {
  align-items: center;
  text-align: center;
  padding-right: 0;
}
.modal-centered .modal-media { margin: 0 auto; }

/* ── Tamaño lg ── panel más ancho para contenido con más densidad (par shadcn
   sm:max-w-2xl). Combinable con las demás clases. */
.modal-lg { width: min(720px, calc(100vw - 48px)); }

/* ── Contenido scrolleable + footer fijo ── par shadcn "scrollable content" +
   sticky footer: sólo el .modal-body scrollea; header y footer quedan fijos.
   El .modal ya es flex-column con max-height, así que basta con contener el
   overflow y dejar que el body crezca/scrollee. */
.modal-scrollable { overflow: hidden; }
.modal-scrollable .modal-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* ── components/toast.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Toast + Snackbar
   Dos componentes de retroalimentación temporal:

   Toast   · .toast-container / .toast     · panel flotante top-right con borde coloreado
                                              (success / error / info). Acompaña acciones
                                              internas del sistema (guardado, error de red, etc.)
   Snackbar · .snackbar                    · barra bottom-centered con superficie inversa.
                                              Feedback breve post-acción del usuario.
                                              Auto-dismiss 4 s. Máximo un botón de acción.

   Cuándo usar Snackbar: confirmación de acciones reversibles ("Archivo eliminado — DESHACER"),
                         avisos no críticos que no requieren respuesta.
   Cuándo usar Toast: notificaciones del sistema (éxito/error) con ícono y clasificación semántica.
   Cuándo no: confirmaciones que requieren decisión → usar Modal.

   Tokens Snackbar — familia inverse (auto-contraste en light y dark sin overrides):
     Contenedor       --color-inverse-surface       #0A0C12 claro / #EAEBED oscuro
     Texto mensaje    --color-inverse-on-surface     #EAEBED claro / #13161F oscuro
     Botón de acción  --color-inverse-primary        #4F80FF claro / #162F6F oscuro
     Presionado acc.  color-mix(inverse-primary 12%, inverse-surface)

   Specs Snackbar:
     Alto: 48dp (una línea) · ~68dp (apilado con mensaje largo)
     Radio: --radius-sm (4dp) · Padding-x: 16dp · Gap: 8dp
     Ancho: min 288dp / max 568dp · Posición: bottom 16dp, centrado horizontal
     Animación entrada: slide-up 250ms ease-out
     Animación salida:  fade-out 200ms ease-in (via clase .snackbar--exit)

   Uso Snackbar:
   <!-- Solo mensaje -->
   <div class="snackbar" role="status" aria-live="polite">
     <span class="snackbar-message">Archivo eliminado correctamente</span>
   </div>

   <!-- Mensaje + botón de acción -->
   <div class="snackbar" role="status" aria-live="polite">
     <span class="snackbar-message">Archivo eliminado</span>
     <button class="snackbar-action">Deshacer</button>
   </div>

   <!-- Mensaje + acción + cierre -->
   <div class="snackbar" role="status" aria-live="polite">
     <span class="snackbar-message">Invitación enviada</span>
     <button class="snackbar-action">Ver</button>
     <button class="snackbar-close" aria-label="Cerrar"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
   </div>

   <!-- Mensaje largo apilado -->
   <div class="snackbar snackbar--multiline" role="status" aria-live="polite">
     <span class="snackbar-message">No se pudo completar la operación en el servidor remoto</span>
     <button class="snackbar-action">Reintentar</button>
   </div>

   Dependencia: variables.css (Color Roles inverse)

   Cola de apilado (Fase 3): .snackbar-viewport envuelve N .snackbar como
   hijos flex en column-reverse — el más nuevo entra abajo (pegado al borde)
   y los anteriores se acomodan arriba solos, sin recalcular offsets por
   índice. Dentro del viewport cada .snackbar pasa a position:relative (el
   viewport es el único position:fixed) y usa una animación de entrada/salida
   sin el translateX(-50%) del modo standalone (el centrado horizontal ya lo
   resuelve align-items:center del viewport). Ver components/lib/use-toast.ts
   + components/ui/toaster.tsx para la cola en sí (swipe-to-dismiss fuera de
   alcance — solo botón de cierre / auto-dismiss por timeout).
═══════════════════════════════════════ */

.toast-container {
  position: fixed;
  top: 24px;
  right: 24px;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.toast {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-5);
  box-shadow: var(--shadow-lg);
  display: flex; align-items: center; gap: var(--space-2-5);
  font-size: var(--font-size-body-md);
  animation: slideInRight .25s ease;
  min-width: 280px;
  max-width: 420px;
}
.toast-success { border-left: 3px solid var(--green); }
.toast-error { border-left: 3px solid var(--red); }
.toast-info { border-left: 3px solid var(--interactive); }
.toast-warning { border-left: 3px solid var(--color-warning); }
.toast-loading { border-left: 3px solid var(--color-outline); }
/* Spinner para el toast de carga (par sonner loading). Usar como .toast-icon. */
.toast-spinner {
  width: 16px; height: 16px;
  border-radius: 50%;
  border: 2px solid var(--color-outline-variant);
  border-top-color: var(--color-primary);
  animation: toast-spin .7s linear infinite;
}
@keyframes toast-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .toast-spinner { animation-duration: 1.6s; } }
.toast .toast-icon { flex-shrink: 0; align-self: flex-start; margin-top: 1px; }
.toast .toast-message { flex: 1; }
/* Título + descripción (par sonner: dos líneas). Usar .toast-content como slot
   flexible cuando el toast lleva encabezado y detalle. */
.toast .toast-content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-0-5); }
.toast .toast-title { font-weight: 600; color: var(--text-primary); }
.toast .toast-desc { font-size: var(--font-size-body-sm); color: var(--text-secondary); line-height: var(--line-height-body-sm); }
.toast .toast-close {
  width: 24px; height: 24px;
  border: none; background: none;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--color-on-surface-variant);
  border-radius: var(--radius-sm);
  transition: background var(--duration-fast) var(--ease-default),
              color var(--duration-fast) var(--ease-default);
}
.toast .toast-close:hover { background: var(--color-surface-variant); color: var(--color-on-surface); }
.toast .toast-close:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

/* ══════════════════════════════════════
   Snackbar — barra de feedback bottom-centered
   Tokens: familia inverse (auto-contraste light/dark sin overrides)
   ══════════════════════════════════════ */
.snackbar {
  position: fixed;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%) translateY(0);
  z-index: 300;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);                   /* 8px */
  min-width: 288px;
  max-width: 568px;
  min-height: 48px;
  padding: 0 var(--space-2) 0 var(--space-4);  /* 0 8 0 16 */
  border-radius: var(--radius-sm);       /* 4dp */
  background: var(--color-inverse-surface);
  box-shadow: var(--shadow-lg);
  animation: snackbar-in 250ms var(--ease-default) both;
}
.snackbar--exit {
  animation: snackbar-out 200ms ease-in forwards;
}

@keyframes snackbar-in {
  from { transform: translateX(-50%) translateY(16px); opacity: 0; }
  to   { transform: translateX(-50%) translateY(0);    opacity: 1; }
}
@keyframes snackbar-out {
  to { transform: translateX(-50%) translateY(16px); opacity: 0; }
}

/* ── Mensaje ── */
.snackbar-message {
  flex: 1;
  min-width: 0;
  color: var(--color-inverse-on-surface);
  font: var(--font-weight-regular) var(--font-size-body-md)/1.4 var(--font-body);
  padding: var(--space-3-5) 0;
}

/* ── Botón de acción — texto puro, inverse-primary
   Pressed: 12% de inverse-primary sobre inverse-surface (mismo patrón
   que btn-text:active pero usando los tokens del contexto inverso) */
.snackbar-action {
  flex-shrink: 0;
  border: 0;
  background: transparent;
  padding: var(--space-2) var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--color-inverse-primary);
  font: var(--font-weight-bold) var(--font-size-label)/1 var(--font-body);
  letter-spacing: var(--letter-spacing-label);
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-default);
}
.snackbar-action:hover {
  background: color-mix(in srgb, var(--color-inverse-primary) 8%, transparent);
}
.snackbar-action:active {
  background: color-mix(in srgb, var(--color-inverse-primary) 12%, transparent);
}
.snackbar-action:focus-visible {
  outline: 2px solid var(--color-inverse-primary);
  outline-offset: 2px;
}

/* ── Botón de cierre ── */
.snackbar-close {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin: 0;
  border: 0;
  background: transparent;
  border-radius: 50%;
  color: var(--color-inverse-on-surface);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-default);
}
.snackbar-close:hover {
  background: color-mix(in srgb, var(--color-inverse-on-surface) 8%, transparent);
}
.snackbar-close:active {
  background: color-mix(in srgb, var(--color-inverse-on-surface) 12%, transparent);
}
.snackbar-close:focus-visible {
  outline: 2px solid var(--color-inverse-primary);
  outline-offset: 2px;
}
.snackbar-close > svg,
.snackbar-close > i { width: 18px; height: 18px; flex-shrink: 0; }

/* ── Variante estática — para comparativas lado a lado (Specs), sin
   position:fixed ni animación de entrada/salida ── */
.snackbar--static {
  position: static;
  bottom: auto;
  left: auto;
  transform: none;
  animation: none;
  width: 100%;
}

/* ── Variante apilada — mensaje largo + acción debajo ── */
.snackbar--multiline {
  flex-direction: column;
  align-items: flex-start;
  padding: var(--space-3) var(--space-2) var(--space-2) var(--space-4);
  min-height: 68px;
}
.snackbar--multiline .snackbar-message { padding: 0 0 var(--space-1); }
.snackbar--multiline .snackbar-action  { align-self: flex-end; }

/* Reducción de movimiento */
@media (prefers-reduced-motion: reduce) {
  .snackbar { animation: none; }
  .snackbar--exit { animation: none; transition: opacity 50ms ease-in; }
}

/* ── Cola de apilado — ver nota arriba ── */
.snackbar-viewport {
  position: fixed;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 300;
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: var(--space-2);
  pointer-events: none;
}
.snackbar-viewport .snackbar {
  position: relative;
  bottom: auto;
  left: auto;
  transform: none;
  pointer-events: auto;
  animation: snackbar-stack-in 250ms var(--ease-default) both;
}
.snackbar-viewport .snackbar.snackbar--exit {
  animation: snackbar-stack-out 200ms ease-in forwards;
}
@keyframes snackbar-stack-in {
  from { transform: translateY(16px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}
@keyframes snackbar-stack-out {
  to { transform: translateY(16px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .snackbar-viewport .snackbar { animation: none; }
  .snackbar-viewport .snackbar.snackbar--exit { animation: none; transition: opacity 50ms ease-in; }
}

/* ── components/chip.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Chip
   Elementos compactos que representan una acción, filtro,
   entrada o sugerencia. Cuatro variantes funcionales (MD3):

     Assist     · .chip (+ .chip-icon opcional)        · acción contextual
     Filter     · .chip + aria-pressed="true"          · refinar contenido
                  (.chip-selected es un alias aceptado; el estado lo declara el atributo)
     Input      · .chip + .chip-remove                 · información ingresada
     Suggestion · .chip                                · sugerencia dinámica

   Cuándo usar: filtros, selección múltiple, sugerencias — siempre interactivo. Filter chips van debajo de la search bar para refinar resultados.
   Cuándo no: estado de solo lectura (usar Badge); acción principal (usar Button).
   Reemplaza a: toggles de filtro ad-hoc, tags clickeables legacy.

   Specs (Specs → Chips en la documentación):
     Alto 32px · radio full (píldora) · ícono 18px
     Padding 16px (sin ícono) · 8px del lado del ícono · gap 8px
     Tipografía Label Large · Inter 500 · 14px

   Dependencia: variables.css (Color Roles)
   Consume EXCLUSIVAMENTE tokens semánticos --color-* → funciona en
   light y dark sin overrides por tema.

   Border token: el Chip sin seleccionar usa --color-outline-variant, NO
   --color-outline. --color-outline queda reservado para controles interactivos
   primarios (Button tertiary/ghost/icon, Input, Checkbox, Radio, Switch) — el
   Chip es un control de filtro/metadata y necesita un trazo visiblemente más
   claro para no leerse como otro botón al lado de ellos. Mismo criterio que el
   frame de Segmented Button (ver segmented-button.css).

   Uso:
   <button class="chip">Sugerencia</button>
   <button class="chip"><span class="chip-icon">…</span> Asistencia</button>
   <button class="chip" aria-pressed="false">Filtro</button>
   <button class="chip" aria-pressed="true">Filtro activo</button>
   <span class="chip">Entrada <button class="chip-remove" aria-label="Quitar">✕</button></span>
   <button class="chip chip-elevated">Elevado</button>
═══════════════════════════════════════ */

/* ── Base (outlined / plano) ── */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);                  /* 8px ícono–etiqueta */
  height: 32px;
  padding: 0 var(--space-4);            /* 16px sin ícono */
  border-radius: var(--radius-full);
  border: 1px solid var(--color-outline-variant);
  background: transparent;
  color: var(--color-on-surface);
  font: var(--font-weight-medium) var(--font-size-body-lg)/1 var(--font-body);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default),
              box-shadow var(--duration-fast) var(--ease-default);
}

/* ── Slots ── */
.chip-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  color: var(--color-on-surface);
  flex-shrink: 0;
}
.chip-icon svg, .chip-icon i { width: 18px; height: 18px; }

.chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  margin: 0; padding: 0;
  border: 0; background: transparent;
  border-radius: 50%;
  color: var(--color-on-surface-variant);
  cursor: pointer;
  flex-shrink: 0;
}
.chip-remove:hover { background: color-mix(in srgb, var(--color-on-surface) 12%, transparent); }
.chip-remove svg, .chip-remove i { width: 14px; height: 14px; }

/* Padding asimétrico cuando hay ícono inicial o de eliminación (8px del lado del ícono) */
.chip:has(.chip-icon)   { padding-left: var(--space-2); }
.chip:has(.chip-remove) { padding-right: var(--space-2); }

/* ── Variante: elevado ── */
.chip-elevated {
  border-color: transparent;
  background: var(--color-surface-container-low);
  box-shadow: var(--shadow-sm);
}

/* ── Variante: seleccionado (filter / input) ── */
.chip-selected,
.chip[aria-pressed="true"] {
  background: var(--color-secondary-container);
  color: var(--color-on-secondary-container);
  border-color: transparent;
}
.chip-selected .chip-icon,
.chip[aria-pressed="true"] .chip-icon   { color: var(--color-on-secondary-container); }
.chip-selected .chip-remove,
.chip[aria-pressed="true"] .chip-remove { color: var(--color-on-secondary-container); }

/* ── Estados (capas sobre on-surface, según tabla de tokens) ── */
.chip:hover {
  background: color-mix(in srgb, var(--color-on-surface) 8%, transparent);
}
.chip:active {
  background: color-mix(in srgb, var(--color-on-surface) 12%, transparent);
}
.chip-elevated:hover {
  background: color-mix(in srgb, var(--color-on-surface) 8%, var(--color-surface-container-low));
}
.chip-elevated:active {
  background: color-mix(in srgb, var(--color-on-surface) 12%, var(--color-surface-container-low));
}
.chip-selected:hover,
.chip[aria-pressed="true"]:hover {
  background: color-mix(in srgb, var(--color-on-secondary-container) 8%, var(--color-secondary-container));
}
.chip-selected:active,
.chip[aria-pressed="true"]:active {
  background: color-mix(in srgb, var(--color-on-secondary-container) 12%, var(--color-secondary-container));
}

.chip:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

/* Arrastrado (input chips durante drag & drop) */
.chip-dragged {
  box-shadow: var(--shadow-md);
  background: var(--color-surface-container-low);
}

/* Deshabilitado: tokens dedicados de Embassy (--color-disabled / --color-on-disabled),
   no el modelo de opacidad MD3 (38%/12% de on-surface). Alinea al patrón canónico
   §5.3 de GOVERNANCE — reconciliación pendiente resuelta jul-2026. */
.chip:disabled,
.chip[aria-disabled="true"] {
  border-color: var(--color-disabled);
  background: transparent;
  color: var(--color-on-disabled);
  cursor: not-allowed;
  box-shadow: none;
}
.chip:disabled .chip-icon,
.chip[aria-disabled="true"] .chip-icon,
.chip:disabled .chip-remove,
.chip[aria-disabled="true"] .chip-remove {
  color: var(--color-on-disabled);
}
.chip-elevated:disabled,
.chip-elevated[aria-disabled="true"] {
  border-color: transparent;
  background: var(--color-disabled);
}

/* ── Conjunto de chips (layout helper) ── */
.chip-set {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

/* ── components/search.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Search
   Búsqueda de contenido. Dos variantes:

     Search bar  · .search-bar   · campo píldora (--radius-full); 40px en .search-row · 56px standalone
     Search view · .search-view  · vista expandida con resultados
                                   (modos docked y full-screen)

   Specs:
     Bar:  alto 40px en .search-row · 56px standalone · radio --radius-full (píldora) · ícono 24px · avatar 30px
           padding horizontal 16px · ancho 360–720px · borde sutil 1px
     View: encabezado 72px · divisor 1px · radio 28px (docked) ·
           íconos 24px · padding horizontal 16px
     Icon btn: 40×40px · border-radius 50% · ícono 20px

   Cuándo usar: search-bar standalone para vistas de búsqueda (desktop y mobile).
                Acompañar con .search-icon-btn para acciones contextuales (Filtros, Ordenar, Vista).
   Cuándo no: reemplazar search-bar con un input de formulario (usar .form-input).

   Search con acciones — patrón canónico Embassy:
     Cuando la búsqueda convive con acciones, usar .search-row + .search-icon-btn.
     Desktop: search-bar + icon buttons en la misma fila.
     Mobile:  search-bar ocupa el ancho completo; acciones en overflow o colapsadas.

   Reemplaza a: inputs de búsqueda legacy (inputs de formulario usados como buscador).
                NO reemplaza a .search-field (toolbar.css): ésa es la variante compacta
                canónica de Search para toolbars de desktop — es un hermano, no un legacy.

   Dependencia: variables.css (Color Roles)
   Consume EXCLUSIVAMENTE tokens semánticos --color-* → funciona en
   light y dark sin overrides por tema.

   Uso:
   <!-- Search bar standalone -->
   <div class="search-bar" role="search">
     <span class="search-bar-icon"><i data-lucide="search"></i></span>
     <input class="search-bar-input" type="search" placeholder="Buscar" aria-label="Buscar">
     <span class="search-bar-avatar">AV</span>   <!-- opcional -->
   </div>

   <!-- Search + icon buttons (patrón canónico para toolbar-like) -->
   <div class="search-row">
     <div class="search-bar" role="search">
       <span class="search-bar-icon"><i data-lucide="search"></i></span>
       <input class="search-bar-input" type="search" placeholder="Buscar" aria-label="Buscar">
     </div>
     <button class="search-icon-btn" aria-label="Filtros"><svg>…</svg></button>
     <button class="search-icon-btn" aria-label="Ordenar"><svg>…</svg></button>
   </div>

   <!-- Search view (docked). Full-screen: + .search-view-fullscreen -->
   <div class="search-view" role="search">
     <div class="search-view-header">
       <button class="search-view-back" aria-label="Volver">…</button>
       <input class="search-bar-input" type="search" placeholder="Buscar">
     </div>
     <ul class="search-view-results">…</ul>
   </div>
═══════════════════════════════════════ */

/* ── Search row — wrapper para search-bar + search-icon-btn ── */
.search-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
}
.search-row .search-bar {
  flex: 1;
  min-width: 0;
  max-width: none;
  height: 40px; /* alinea con .search-icon-btn en contexto desktop */
}

/* ── Search bar ── */
.search-bar {
  display: flex;
  align-items: center;
  gap: var(--space-4);                /* 16px entre elementos */
  height: 56px;
  padding: 0 var(--space-4);          /* 16px horizontal */
  border-radius: var(--radius-full);  /* píldora — forma canónica del Search */
  background: var(--color-surface-container-high);  /* mobile: fondo gris — tratamiento correcto para mobile (NO usar surface acá) */
  border: 1px solid var(--border);    /* borde sutil semitransparente */
  min-width: min(360px, 100%);
  max-width: 720px;
  width: 100%;
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default);
}
.search-bar:hover {
  background: color-mix(in srgb, var(--color-on-surface) 8%, var(--color-surface-container-high));
  border-color: color-mix(in srgb, var(--color-on-surface) 20%, transparent);
}
.search-bar:focus-within {
  background: color-mix(in srgb, var(--color-on-surface) 12%, var(--color-surface-container-high));
  border-color: var(--color-secondary);
}

.search-bar-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  color: var(--color-on-surface);
  flex-shrink: 0;
}
.search-bar-icon svg, .search-bar-icon i { width: 24px; height: 24px; }

.search-bar-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--color-on-surface);
  font: var(--font-weight-regular) var(--font-size-body-lg)/1.4 var(--font-body);
  caret-color: var(--color-secondary);
  outline: none;
}
.search-bar-input::placeholder { color: var(--color-on-surface-variant); }
.search-bar-input::-webkit-search-cancel-button { display: none; }

/* Ícono final opcional (micrófono, limpiar, etc.) */
.search-bar-trailing {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  margin: 0; padding: 0;
  border: 0; background: transparent;
  color: var(--color-on-surface-variant);
  cursor: pointer;
  flex-shrink: 0;
}
.search-bar-trailing svg, .search-bar-trailing i { width: 24px; height: 24px; }

/* Avatar opcional (cuenta del usuario) */
.search-bar-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--color-primary-container);
  color: var(--color-on-primary-container);
  font: var(--font-weight-bold) 11px/1 var(--font-body);
  flex-shrink: 0;
  overflow: hidden;
}
.search-bar-avatar img { width: 100%; height: 100%; object-fit: cover; }

.search-bar:focus-within .search-bar-icon { color: var(--color-on-surface); }

/* Deshabilitado: contenedor al 12%, contenido al 38% de on-surface */
.search-bar[aria-disabled="true"],
.search-bar:has(.search-bar-input:disabled) {
  background: color-mix(in srgb, var(--color-on-surface) 12%, transparent);
  pointer-events: none;
}
.search-bar[aria-disabled="true"] *,
.search-bar:has(.search-bar-input:disabled) * {
  color: color-mix(in srgb, var(--color-on-surface) 38%, transparent);
}

/* ── Search icon button — acción circular adyacente al Search ──
   Tamaño: 40×40px. Tratamiento outlined (igual que btn-tertiary):
   fondo transparente + --color-outline. El fondo se activa sólo en
   hover/pressed/selected — nunca en el estado default.
   Estado activo (filtro aplicado): surface-container-high fill — mismo tratamiento que btn-tertiary:active. */
.search-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--color-outline);
  color: var(--color-on-surface-variant);
  cursor: pointer;
  padding: 0;
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default),
              color var(--duration-fast) var(--ease-default);
}
.search-icon-btn:hover {
  background: var(--color-surface-variant);
  border-color: var(--color-primary);
  color: var(--color-on-surface);
}
.search-icon-btn:active:not(:disabled) {
  background: var(--color-surface-container-high);
  border-color: var(--color-outline);
}
.search-icon-btn:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.search-icon-btn[aria-pressed="true"],
.search-icon-btn.active {
  background: var(--color-surface-container-high);
  border-color: var(--color-outline);
  color: var(--color-on-surface);
}
.search-icon-btn[aria-pressed="true"]:hover,
.search-icon-btn.active:hover {
  background: var(--color-surface-container-high);
  border-color: var(--color-primary);
  color: var(--color-on-surface);
}
.search-icon-btn[aria-pressed="true"]:active:not(:disabled),
.search-icon-btn.active:active:not(:disabled) {
  background: var(--color-surface-container-high);
  border-color: var(--color-outline);
}
.search-icon-btn:disabled,
.search-icon-btn[aria-disabled="true"] {
  background: transparent;
  border-color: var(--color-outline-variant);
  color: var(--color-on-disabled);
  cursor: not-allowed;
  pointer-events: none;
}
.search-icon-btn > svg,
.search-icon-btn > i { width: 20px; height: 20px; flex-shrink: 0; }

/* ── Search view ── */
.search-view {
  display: flex;
  flex-direction: column;
  background: var(--color-surface-container-high);
  border-radius: 28px;                /* spec: radio 28px en modo docked */
  overflow: hidden;
  min-width: min(360px, 100%);
  max-width: 720px;
  width: 100%;
}
/* Modo full-screen: ocupa toda la pantalla, sin radio */
.search-view-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 100;
  max-width: none;
  border-radius: 0;
}

.search-view-header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  height: 72px;                       /* spec: alto del encabezado */
  padding: 0 var(--space-4);
  flex-shrink: 0;
  border-bottom: 1px solid var(--color-outline);  /* spec: divisor 1px */
}

.search-view-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  margin: 0; padding: 0;
  border: 0; background: transparent;
  color: var(--color-on-surface);
  cursor: pointer;
  flex-shrink: 0;
}
.search-view-back svg, .search-view-back i { width: 24px; height: 24px; }

.search-view-results {
  margin: 0;
  padding: var(--space-2) 0;
  list-style: none;
  overflow-y: auto;
  flex: 1;
}
.search-view-result {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  color: var(--color-on-surface);
  font: var(--font-weight-regular) var(--font-size-body-lg)/1.4 var(--font-body);
  cursor: pointer;
}
.search-view-result:hover {
  background: color-mix(in srgb, var(--color-on-surface) 8%, transparent);
}
.search-view-result-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  color: var(--color-on-surface-variant);
  flex-shrink: 0;
}
.search-view-result-icon svg, .search-view-result-icon i { width: 24px; height: 24px; }

/* ── components/select.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Select (listbox)
   Select con dropdown ESTILADO (par shadcn/Radix Select): un trigger + un panel
   flotante role="listbox" con ítems role="option", indicador de check a la
   derecha del ítem seleccionado, grupos, labels, separadores y botones de scroll.
   A diferencia del <select> nativo de form.css (cuyo desplegable lo dibuja el SO
   y no se puede tematizar), este componente reimplementa el listbox para replicar
   el SelectContent de shadcn con tokens Embassy.

   Cuándo usar: elegir UNA opción de una lista corta/media cuando el desplegable
   debe verse igual que el resto del DS (tokens, dark mode, foco).
   Cuándo no: búsqueda/filtrado dentro de la lista → Combobox/Command; campo de
   formulario denso donde alcanza el nativo → <select> de form.css.

   Estructura (slots par shadcn):
     .select            → Root (position:relative)
     .select-trigger    → SelectTrigger (button, aria-haspopup="listbox")
       .select-value    → SelectValue (texto elegido / placeholder)
       .select-icon     → ChevronDown
     .select-content    → SelectContent (role="listbox")
       .select-scroll-up   → SelectScrollUpButton
       .select-viewport    → SelectViewport (área scrolleable, p-1)
         .select-group     → SelectGroup (role="group")
           .select-label   → SelectLabel
           .select-item    → SelectItem (role="option")
             .select-item-text  → SelectItemText
             .select-item-check → SelectItemIndicator (CheckIcon)
         .select-separator → SelectSeparator
       .select-scroll-down → SelectScrollDownButton

   Uso:
   <div class="select">
     <button class="select-trigger" aria-haspopup="listbox" aria-expanded="false">
       <span class="select-value">Últimos 90 días</span>
       <span class="select-icon">▾</span>
     </button>
   </div>

   Comportamiento: initSelect() en index.html (abrir/cerrar, teclado ↑↓/Home/End/
   type-ahead/Enter/Esc, foco al panel, retorno al trigger, check en seleccionado).
   Posicionamiento: absolute bajo el trigger (sin detección de colisión de viewport
   — misma simplificación aceptada que Date Picker / Popover buildless).

   Dependencia: variables.css. Consume sólo tokens semánticos → light/dark sin overrides.
═══════════════════════════════════════ */

.select { position: relative; display: inline-block; }

/* ── Trigger — replica la anatomía de campo del <select> nativo (form.css) ── */
.select-trigger {
  display: inline-flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  width: 100%;
  min-height: 40px;                                  /* alineado con los field selects */
  padding: var(--space-2) var(--space-3) var(--space-2) var(--space-3-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  color: var(--text-primary);
  font-family: inherit; font-size: var(--font-size-body-md);
  text-align: left; white-space: nowrap;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-default),
              box-shadow var(--duration-fast) var(--ease-default);
}
.select-trigger:focus-visible,
.select-trigger[aria-expanded="true"] {
  outline: none;
  border-color: var(--interactive);
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.select-trigger[aria-invalid="true"] { border-color: var(--color-error); }
.select-trigger[aria-invalid="true"]:focus-visible { box-shadow: 0 0 0 4px var(--color-error-ring); }
.select-trigger:disabled,
.select-trigger[aria-disabled="true"] {
  background: var(--color-surface-variant);
  color: var(--color-on-disabled);
  border-color: var(--color-outline);
  cursor: not-allowed;
}
.select-trigger[data-size="sm"] { min-height: 32px; padding: var(--space-1) var(--space-2-5) var(--space-1) var(--space-3); }
.select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.select-value[data-placeholder] { color: var(--text-muted); }
.select-icon { width: 16px; height: 16px; flex-shrink: 0; color: var(--text-muted); opacity: .5; }   /* shadcn: ChevronDown estático (no rota) */

/* ── Content (SelectContent, role="listbox") ── */
.select-content[hidden] { display: none; }
.select-content {
  position: absolute;
  z-index: 100;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;                                   /* = ancho del trigger (crece con el contenido) */
  max-height: min(320px, 60vh);                      /* max-h disponible → scroll */
  overflow-y: auto; overflow-x: hidden;
  background: var(--color-surface-container);
  color: var(--color-on-surface);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  animation: selectContentIn var(--duration-fast) var(--ease-default);
}
@keyframes selectContentIn {
  from { opacity: 0; transform: scale(.96) translateY(-2px); }
  to   { opacity: 1; transform: none; }
}
.select-viewport { padding: var(--space-1); }                   /* p-1 */

/* ── Item (SelectItem, role="option") ── */
.select-item {
  position: relative;
  display: flex; align-items: center; gap: var(--space-2);
  width: 100%;
  padding: var(--space-1-5) var(--space-8) var(--space-1-5) var(--space-2);                         /* py-1.5 pr-8 pl-2 (check a la derecha) */
  border-radius: var(--radius-sm);
  font-size: var(--font-size-body-md);
  color: var(--color-on-surface);
  cursor: default; user-select: none; outline: none;
}
/* highlight (hover + navegación por teclado) = accent, igual que Dropdown Menu */
.select-item.highlighted,
.select-item:hover:not([aria-disabled="true"]) { background: var(--color-surface-variant); }
.select-item[aria-disabled="true"] { color: var(--color-on-disabled); opacity: .5; pointer-events: none; }
.select-item-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.select-item-check {
  position: absolute; right: 8px;                    /* right-2 */
  width: 14px; height: 14px;                         /* size-3.5 */
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0;
}
.select-item-check svg { width: 16px; height: 16px; }
.select-item[aria-selected="true"] .select-item-check { opacity: 1; }

/* ── Label / Group / Separator ── */
.select-label { padding: var(--space-1-5) var(--space-2); font-size: var(--font-size-caption); font-weight: 600; color: var(--color-on-surface-variant); }
.select-group { display: block; }
.select-separator { height: 1px; margin: 4px -4px; background: var(--color-outline-variant); }

/* ── Scroll buttons (SelectScrollUp/DownButton) ── se muestran vía JS al desbordar ── */
.select-scroll-up, .select-scroll-down {
  position: sticky; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  height: 22px; cursor: default;
  color: var(--color-on-surface-variant);
  background: var(--color-surface-container);
}
.select-scroll-up { top: 0; }
.select-scroll-down { bottom: 0; }
.select-scroll-up[hidden], .select-scroll-down[hidden] { display: none; }
.select-scroll-up svg, .select-scroll-down svg { width: 16px; height: 16px; }


/* ── Extended components (domain-oriented) ── */
/* ── components/vacancy-card.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Vacancy Card
   Tarjeta de vacante con icono, titulo, metadata, stats y asignados.

   Cuándo usar: ítem de vacante en listas de recruiting (ícono + nombre + meta + stats).
   Cuándo no: personas (person-card); otros dominios (usar Card genérica).
   Reemplaza a: rows/cards de vacante custom.

   Dependencia: variables.css
   Requiere: badge.css (para badges de status en el HTML)

   Uso:
   <div class="vacancies-list">
     <!-- Interactiva: agregá tabindex="0" role="button" (o usá <a>/<button>) —
          la card es focusable y trae :focus-visible. -->
     <div class="vacancy-card" tabindex="0" role="button">
       <i data-lucide="briefcase" class="vacancy-icon" aria-hidden="true"></i>
       <div class="vacancy-info">
         <div class="vacancy-name">
           Desarrollador Frontend
           <span class="badge badge-open">Abierta</span>
         </div>
         <div class="vacancy-meta">
           Tecnologia <span class="meta-dot"></span> Remoto
         </div>
       </div>
       <div class="vacancy-right">
         <div class="vacancy-age">3d</div>
       </div>
     </div>
   </div>
═══════════════════════════════════════ */

.vacancies-list { display: flex; flex-direction: column; gap: var(--space-2); }

.vacancy-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  display: flex; align-items: center; gap: var(--space-4);
  cursor: pointer;
  transition: border-color var(--duration-fast), box-shadow var(--duration-fast), transform var(--duration-fast);
  position: relative;
  animation: fadeSlideIn var(--duration-medium) var(--ease-enter) both;
}
.vacancy-card:hover {
  border-color: var(--color-outline-variant);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
.vacancy-card:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.vacancy-card:nth-child(1) { animation-delay: .05s; }
.vacancy-card:nth-child(2) { animation-delay: .12s; }
.vacancy-card:nth-child(3) { animation-delay: .19s; }

/* Icono guia de una fila que se escanea: COMPOSICION.md regla 5, ubicacion 2
   —en linea, antes del texto, al color del texto—. SIN superficie cuadrada
   con radio detras: eso es `H8`, y con el icono adentro era ademas un emoji,
   que es `H4`. Lucide, y el ancho se mantiene para que la columna no salte. */
.vacancy-icon {
  width: 40px; height: 40px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--color-secondary);
}
.vacancy-icon svg { width: 20px; height: 20px; stroke-width: var(--icon-stroke, 2); }
.vacancy-info { flex: 1; min-width: 0; }
.vacancy-name {
  font-family: var(--font-heading);
  font-size: var(--font-size-heading-xs); font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 4px;
  display: flex; align-items: center; gap: var(--space-2);
}
.vacancy-meta {
  display: flex; align-items: center; gap: var(--space-1-5);
  font-size: var(--font-size-body-sm); color: var(--text-muted);
  flex-wrap: wrap;
}
.meta-dot { width: 3px; height: 3px; border-radius: 50%; background: var(--color-outline-variant); flex-shrink: 0; }

.vacancy-stats {
  display: flex; align-items: center; gap: var(--space-4);
  margin-top: 10px;
}
.stat-pill {
  display: flex; align-items: center; gap: var(--space-1-5);
  font-size: var(--font-size-label); color: var(--text-secondary);
}
.stat-pill .count { font-weight: 600; color: var(--text-primary); }

.assignee {
  display: flex; align-items: center; gap: var(--space-1-5);
  font-size: var(--font-size-label); color: var(--text-secondary);
}
/* Avatares tonales: rol semántico container + on-container (par al Avatar canónico),
   nunca gradientes con primitivos. Cada variante usa su propio par → contraste
   correcto en light Y dark (antes: --color-on-primary, que se invierte a navy en
   dark sobre color saturado). */
.assignee-avatar {
  /* Escalón xs de la escala de avatar (variables.css) — antes 22px/9px, un
     escalón propio a dos píxeles del que ya existía. */
  width: var(--avatar-size-xs); height: var(--avatar-size-xs); border-radius: 50%;
  background: var(--color-error-container);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-on-error-container); font-size: var(--avatar-initial-xs); font-weight: 700;
}
.assignee-avatar.blue   { background: var(--color-secondary-container); color: var(--color-on-secondary-container); }
.assignee-avatar.green  { background: var(--color-success-container);   color: var(--color-on-success-container); }
.assignee-avatar.purple { background: var(--color-tertiary-container);  color: var(--color-on-tertiary-container); }

.vacancy-right {
  display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-2);
  flex-shrink: 0;
}
.vacancy-age {
  font-size: var(--font-size-mono-md); font-weight: 600; color: var(--text-muted);
  font-family: var(--font-mono);
}

.more-btn {
  width: 28px; height: 28px;
  border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-on-surface-variant);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-default),
              color var(--duration-fast) var(--ease-default);
  background: none; border: none;
}
.more-btn:hover { background: var(--color-surface-variant); color: var(--color-on-surface); }

/* ── components/person-card.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Person (variante de Basic Card)
   NO es un componente aparte: es la variante de dominio "Person" de la Basic Card
   (el primitivo .item — ver item.css), documentada dentro de Basic Card. Solo
   aporta el avatar circular tonal (Secondary container) + fondo de superficie; el
   layout, padding, gap, hover, foco, título y descripción vienen de
   .item / .item-content / .item-title / .item-description.

   Cuándo usar: persona en grillas (avatar + nombre + rol).
   Cuándo no: vacantes (vacancy-card); datos tabulares de personas (Table);
     una fila genérica sin avatar de marca (usá .item directamente).
   Reemplaza a: cards de perfil custom.

   Dependencia: variables.css, item.css (primitivo base / Basic Card)

   Uso:
   <div class="people-grid">
     <!-- Interactiva: .item-clickable trae cursor:pointer + hover; agregá
          tabindex="0" role="button" (o usá <a class="item person-card">) para
          que sea focusable y se anuncie como control. -->
     <div class="item item-outline item-clickable person-card" tabindex="0" role="button">
       <div class="item-media person-avatar">MG</div>
       <div class="item-content">
         <div class="item-title">María García</div>
         <div class="item-description">Desarrolladora Frontend</div>
       </div>
     </div>
   </div>
═══════════════════════════════════════ */

.people-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--space-3);
}

/* Fondo de superficie para que la card se despegue de la página (el primitivo
   .item es transparente; Person Card es una tarjeta, no una fila embebida). */
.person-card { background: var(--card-bg); }

/* Avatar circular tonal — usa el par semántico Secondary container/on-container,
   igual que el Avatar canónico (item.css/avatar.css), no un gradiente con
   primitivos. Se coloca en el slot .item-media; centrado vertical (el default
   del media es align-self:flex-start). */
.person-card .person-avatar {
  /* Escalón lg de la escala de avatar (variables.css) */
  width: var(--avatar-size-lg); height: var(--avatar-size-lg);
  border-radius: 50%;
  background: var(--color-secondary-container);
  color: var(--color-on-secondary-container);
  font-size: var(--avatar-initial-lg); font-weight: 600;
  align-self: center;
}

/* Al ser interactiva eleva su sombra en hover (además del fondo que aporta
   .item-clickable), reforzando el patrón de tarjeta de la grilla. */
.person-card.item-clickable:hover,
a.person-card.item:hover { box-shadow: var(--shadow-md); }

/* ── components/kanban.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Kanban
   Tablero kanban con columnas y tarjetas arrastrables.

   Cuándo usar: tableros de flujo por columnas (pipelines de recruiting/HR).
   Cuándo no: datos tabulares (usar Table); listas simples (usar cards).
   Reemplaza a: boards custom.

   Dependencia: variables.css
   Requiere: badge.css (para badges en columnas, opcional)

   Uso:
   <div class="kanban-board">
     <div class="kanban-column">
       <div class="kanban-column-header">
         Aplicados <span class="count">5</span>
       </div>
       <div class="kanban-column-body">
         <!-- Interactiva/arrastrable: agregá tabindex="0" role="button" — la card
              es focusable y trae :focus-visible + estado dragging. -->
         <div class="kanban-card" tabindex="0" role="button">
           <div class="kanban-card-title">Juan Perez</div>
           <div class="kanban-card-meta">Frontend · 3 dias</div>
         </div>
       </div>
     </div>
   </div>
═══════════════════════════════════════ */

.kanban-board {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  padding-bottom: var(--space-4);
}
.kanban-column {
  min-width: 280px;
  max-width: 320px;
  flex-shrink: 0;
  background: var(--color-surface-variant);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
}
.kanban-column-header {
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-size-label);
  font-weight: 600;
  color: var(--text-primary);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
}
.kanban-column-header .count {
  font-size: var(--font-size-caption);
  font-weight: 600;
  color: var(--text-muted);
  background: var(--card-bg);
  border: 1px solid var(--border);
  padding: var(--space-0-5) var(--space-2);
  border-radius: var(--radius-full);
}
.kanban-column-body {
  padding: var(--space-3);
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-height: 100px;
}
/* Entidad (COMPOSICION.md §4b): escalon compacto. */
.kanban-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  cursor: grab;
  transition: box-shadow var(--duration-fast), transform var(--duration-fast);
}
.kanban-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
.kanban-card.dragging {
  opacity: .5;
  transform: rotate(2deg);
}
.kanban-card:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.kanban-card-title {
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-title);
  font-weight: var(--font-weight-medium);
  color: var(--text-primary);
  margin-bottom: var(--space-1-5);
}
.kanban-card-meta {
  font-size: var(--font-size-caption);
  color: var(--text-muted);
  display: flex; align-items: center; gap: var(--space-1-5);
}

/* ── Header row: título + badge de estado opcional ── */
.kanban-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.kanban-card-header .kanban-card-title { margin-bottom: 0; }

/* ── Footer row: meta + avatar de asignado opcional ── */
.kanban-card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.kanban-card-avatar {
  /* Escalón xs de la escala de avatar (variables.css) */
  width: var(--avatar-size-xs); height: var(--avatar-size-xs);
  border-radius: 50%;
  background: var(--color-primary-container);
  color: var(--color-on-primary-container);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--avatar-initial-xs); font-weight: 600;
  flex-shrink: 0;
  margin-left: auto;
}

/* ── components/create-form.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Create Form
   Encabezado y footer sticky para paginas de creacion.

   Cuándo usar: header + footer pegajoso de formularios de creación full-page.
   Cuándo no: tareas cortas que caben en un Modal.
   Reemplaza a: encabezados/footers de formulario custom.

   Dependencia: variables.css
   Requiere: button.css (para botones en el footer)

   Uso:
   <div class="create-header">
     <h1 class="create-title">Crear vacante</h1>
   </div>
   <!-- formulario -->
   <div class="create-footer">
     <button class="btn-tertiary">Cancelar</button>
     <button class="btn-primary">Publicar</button>
   </div>
═══════════════════════════════════════ */

.create-header { margin-bottom: 0; }
.create-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-heading-xl); font-weight: 600; letter-spacing: var(--letter-spacing-heading-xl);
  margin-bottom: 20px;
}
.create-footer {
  background: var(--card-bg);
  border-top: 1px solid var(--border);
  padding: var(--space-4) var(--space-8);
  display: flex; align-items: center; justify-content: space-between;
  position: sticky; bottom: 0;
  margin-top: auto;
}

/* ── components/placeholder.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Placeholder Panel
   Panel centrado para mensajes o contenido vacio.

   Cuándo usar: panel para secciones/features en construcción.
   Cuándo no: listas sin datos (usar Empty State).
   Reemplaza a: textos "próximamente" sueltos.

   Dependencia: variables.css
   Requiere: (ninguno)

   Uso:
   <div class="placeholder-panel">
     <i data-lucide="mouse-pointer-click" class="placeholder-icon" aria-hidden="true"></i>
     <p>Selecciona una vacante para ver los detalles.</p>
   </div>
═══════════════════════════════════════ */

.placeholder-panel {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-12) var(--space-8);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--font-size-body-lg);
}
.placeholder-panel .placeholder-icon { font-size: 32px; margin-bottom: 12px; }  /* ds-allow: caja del glifo heredado, no tipografia */
.placeholder-panel p { line-height: var(--line-height-prose); }


/* ── shadcn-parity primitives (added post-Tailwind-revert, 2026-07) ── */
/* ── components/accordion.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Accordion
   Secciones apiladas que se expanden y colapsan para organizar contenido extenso.

   Cuándo usar: FAQs, filtros avanzados o detalles opcionales donde mostrar todo a
     la vez abrumaría.
   Cuándo no: navegar entre vistas (usar Tabs); un único bloque mostrar/ocultar
     (usar Collapsible); contenido crítico que siempre debe verse.
   Reemplaza a: bloques "ver más" custom apilados sin patrón común.

   Dependencia: variables.css (Color Roles)
   Consume EXCLUSIVAMENTE tokens semánticos --color-* y --text-* → funciona en
   light y dark sin overrides por tema.

   Animación: grid-template-rows 0fr → 1fr (sin medir scrollHeight por JS) —
   técnica CSS pura, funciona con contenido de alto variable.

   Uso:
   <div class="accordion">
     <div class="accordion-item">
       <h3>
         <button class="accordion-trigger" aria-expanded="false" aria-controls="acc-1" onclick="accToggle(this)">
           ¿Cómo aplico el DS?
           <svg class="accordion-chevron">…</svg>
         </button>
       </h3>
       <div class="accordion-content" data-state="closed" id="acc-1" role="region">
         <div class="accordion-content-inner">Importá el theme y copiá el componente.</div>
       </div>
     </div>
   </div>
═══════════════════════════════════════ */

.accordion {
  width: 100%;
}

.accordion-item {
  border-bottom: 1px solid var(--border);
}
.accordion-item:last-child {
  border-bottom: none;
}

.accordion-item > h3 {
  margin: 0;
}

.accordion-trigger {
  display: flex;
  width: 100%;
  /* shadcn: items-start — el chevron queda arriba y el título puede tener 2 líneas */
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4); /* shadcn gap-4 */
  padding: var(--space-4) 0;
  background: transparent;
  border: none;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--font-size-body-md);
  font-weight: 500;
  color: var(--text-primary);
  transition: color var(--duration-fast) var(--ease-default);
}
/* shadcn: hover:underline — la interacción canónica es subrayado, no cambio de color */
.accordion-trigger:hover {
  text-decoration: underline;
}
.accordion-trigger:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
/* Disabled item — trigger inerte con opacidad reducida (mismo patrón que shadcn). */
.accordion-trigger:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.accordion-chevron {
  width: 16px;  /* shadcn size-4 */
  height: 16px;
  flex-shrink: 0;
  margin-top: 3px; /* shadcn translate-y-0.5 — alinea con la primera línea del título */
  color: var(--text-secondary);
  transition: transform var(--duration-normal) var(--ease-expressive);
}
.accordion-trigger[aria-expanded="true"] .accordion-chevron {
  transform: rotate(180deg);
}

/* ── Contenido: grid-rows 0fr → 1fr, sin medir altura por JS ── */
.accordion-content {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows var(--duration-normal) var(--ease-default);
}
.accordion-content[data-state="open"] {
  grid-template-rows: 1fr;
}
.accordion-content-inner {
  min-height: 0;
  overflow: hidden;
  font-size: var(--font-size-body-md);
  color: var(--text-secondary);
  line-height: var(--line-height-prose);
}
/* Bottom spacing as CLIPPABLE in-flow content (not padding): padding on the
   overflow:hidden grid item is not clipped by min-height:0, so it would leave a
   ~16px sliver of every closed panel visible. A zero-height-when-collapsed
   spacer keeps the open spacing (shadcn pb-4) without the residual. */
.accordion-content-inner::after {
  content: "";
  display: block;
  height: 16px;
}

/* ═══ Variantes de estilo (solo clases — misma estructura y comportamiento) ═══ */

/* Bordered — todo el accordion en un contenedor con borde y radio; filas divididas. */
.accordion-bordered {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding-inline: 16px;
}

/* Card — el accordion COMPLETO va dentro de una única Card (con card-header opcional),
   igual que el ejemplo canónico de shadcn (Card + CardHeader + CardContent + Accordion).
   No hay clase propia: se compone con el componente Card del DS. El accordion adentro
   pierde su primer borde superior visual porque el card-content ya lo separa del header. */
.card > .card-content > .accordion:first-child .accordion-item:first-child .accordion-trigger {
  padding-top: 0;
}

/* ── components/alert.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Alert
   Mensaje estático en contexto que destaca información importante
   dentro de la página.

   Cuándo usar: mensajes persistentes en la página — un aviso de sistema,
   la validación completa de un formulario, una nota informativa que
   debe seguir visible hasta que la persona actúe.
   Cuándo no: feedback efímero de una acción (usar Snackbar); confirmación
   que interrumpe (usar Alert Dialog); como único indicador por color
   (acompañá siempre con título/ícono).
   Reemplaza a: banners de aviso ad-hoc.

   Alert vs. Snackbar (NO son duplicados):
     Alert     → INLINE, PERSISTENTE. Convive con el contenido, varios a la
                 vez, hasta que se resuelve o se descarta manualmente.
     Snackbar  → FLOTANTE, EFÍMERO. Uno por vez en cola, se auto-descarta
                 (~4–10s), confirma que algo ya pasó.
   Regla rápida: si debe seguir visible hasta que la persona actúe → Alert;
   si solo confirma un hecho → Snackbar.

   Dependencia: variables.css (Color Roles). Reusa exactamente los mismos
   roles container/on-container que Badge (badge.css) para que ambos
   hablen el mismo lenguaje de color semántico.

   Uso:
   <div class="alert alert-info" role="alert">
     <svg class="alert-icon">…</svg>
     <div class="alert-body">
       <div class="alert-title">Mantenimiento programado</div>
       <div class="alert-description">El sistema estará en modo lectura el sábado de 2 a 4am.</div>
     </div>
   </div>
═══════════════════════════════════════ */

.alert {
  position: relative;
  width: 100%;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-0-5) var(--space-3);
  align-items: start;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-container);
  color: var(--color-on-surface);
}
.alert:has(.alert-icon) { grid-template-columns: auto 1fr; }

.alert-icon {
  width: 18px;
  height: 18px;
  margin-top: 2px;
  color: var(--color-on-surface-variant);
  flex-shrink: 0;
}

.alert-body { grid-column: 2; }
.alert:not(:has(.alert-icon)) .alert-body { grid-column: 1; }

.alert-title {
  font-size: var(--font-size-body-md);
  font-weight: 600;
  line-height: var(--line-height-title);
}

.alert-description {
  font-size: var(--font-size-body-sm);
  line-height: var(--line-height-body-sm);
  opacity: .9;
  margin-top: 2px;
}

/* ── Variantes semánticas — mismos roles que badge-{info,open,warning,error} ── */
.alert-info {
  background: var(--color-info-container);
  border-color: transparent;
  color: var(--color-on-info-container);
}
.alert-info .alert-icon { color: var(--color-on-info-container); }

.alert-success {
  background: var(--color-success-container);
  border-color: transparent;
  color: var(--color-on-success-container);
}
.alert-success .alert-icon { color: var(--color-on-success-container); }

.alert-warning {
  background: var(--color-warning-container);
  border-color: transparent;
  color: var(--color-on-warning-container);
}
.alert-warning .alert-icon { color: var(--color-on-warning-container); }

.alert-error {
  background: var(--color-error-container);
  border-color: transparent;
  color: var(--color-on-error-container);
}
.alert-error .alert-icon { color: var(--color-on-error-container); }

/* ── Action ── botones de acción bajo la descripción + cierre opcional
   (par shadcn Alert "Action"). Los botones son <button>/<a> reales de Embassy. */
.alert-actions {
  grid-column: 2;
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: 10px;
}
.alert:not(:has(.alert-icon)) .alert-actions { grid-column: 1; }

/* Botón de cierre — descarta el alert; anclado arriba a la derecha. */
.alert-close {
  position: absolute;
  top: 10px;
  right: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  border-radius: var(--radius-sm);
  cursor: pointer;
  opacity: .7;
  transition: opacity var(--duration-fast) var(--ease-default),
              background-color var(--duration-fast) var(--ease-default);
}
.alert-close:hover { opacity: 1; background: color-mix(in srgb, currentColor 12%, transparent); }
.alert-close:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; opacity: 1; }
.alert-close svg { width: 16px; height: 16px; }
.alert:has(.alert-close) { padding-right: var(--space-10); }

/* ── components/avatar.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Avatar
   Círculo con iniciales o imagen que representa un usuario.

   Cuándo usar: identificar a una persona (topbar, listas, cards de vacante/persona, asignados).
   Cuándo no: para íconos genéricos de sistema (usar Icon Button) ni como decoración sin significado.
   Reemplaza a: divs con gradientes hardcodeados / iniciales sin token — todo Avatar usa el fallback
   tokenizado --color-primary-container / --color-on-primary-container, nunca hex.

   Dependencia: variables.css (Color Roles)
   Tamaños: .avatar-sm (28px) · .avatar-md (36px, default) · .avatar-lg (44px) · .avatar-xl (56px)

   Uso:
   <span class="avatar avatar-md"><span class="avatar-fallback">MG</span></span>
   <span class="avatar avatar-lg"><img class="avatar-image" src="…" alt="María González"></span>
═══════════════════════════════════════ */

.avatar {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* Sin overflow:hidden en el contenedor — recortaría el .avatar-badge a media
     luna. La imagen se recorta sola (border-radius:inherit). */
  border-radius: var(--radius-full);
  background: var(--color-primary-container);
  color: var(--color-on-primary-container);
  font-family: var(--font-body);
  font-weight: 600;
  user-select: none;
}

/* Los cinco escalones viven en variables.css (ver "Inicial de avatar"): los
   consumen también kanban-card, person-card y vacancy-card, así que el número
   no puede vivir acá. */
.avatar-xs { width: var(--avatar-size-xs); height: var(--avatar-size-xs); font-size: var(--avatar-initial-xs); }
.avatar-sm { width: var(--avatar-size-sm); height: var(--avatar-size-sm); font-size: var(--avatar-initial-sm); }
.avatar-md { width: var(--avatar-size-md); height: var(--avatar-size-md); font-size: var(--avatar-initial-md); }
.avatar-lg { width: var(--avatar-size-lg); height: var(--avatar-size-lg); font-size: var(--avatar-initial-lg); }
.avatar-xl { width: var(--avatar-size-xl); height: var(--avatar-size-xl); font-size: var(--avatar-initial-xl); }

.avatar-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  line-height: var(--line-height-none);
}

.avatar-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit; /* recorta la imagen al círculo sin overflow en el padre */
}

/* ── Status badge ── punto de estado anclado abajo-derecha, con anillo de la
   superficie para separarlo del avatar (par shadcn AvatarBadge). Default = online.
   Modificadores: -online/-busy/-away/-offline. Escala con el tamaño del avatar. */
.avatar-badge {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 30%;
  height: 30%;
  min-width: 8px;
  min-height: 8px;
  border-radius: var(--radius-full);
  background: var(--color-success);
  box-shadow: 0 0 0 2px var(--color-surface);
  /* Puede alojar un ícono de estado (par shadcn AvatarBadge con SVG). */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-surface);
}
.avatar-badge svg { width: 64%; height: 64%; }
.avatar-badge-online  { background: var(--color-success); }
.avatar-badge-busy    { background: var(--color-error); }
.avatar-badge-away    { background: var(--color-warning); }
.avatar-badge-offline { background: var(--color-outline); }

/* Grupo apilado — usado en listas de asignados */
.avatar-group { display: flex; align-items: center; }
.avatar-group .avatar {
  margin-left: -8px;
  box-shadow: 0 0 0 2px var(--color-surface);
}
.avatar-group .avatar:first-child { margin-left: 0; }

/* Overflow "+N" — chip con forma de avatar que cierra el grupo cuando hay más
   asignados de los mostrados (par shadcn AvatarGroupCount). Usar junto con un
   tamaño: <span class="avatar avatar-md avatar-group-count">+3</span>. */
.avatar-group-count {
  background: var(--color-surface-variant);
  color: var(--color-on-surface-variant);
  /* El tamaño lo pone el escalón (.avatar-md en el uso canónico). Este bloque
     solo cambia el color: un +N es un avatar, no un tamaño nuevo. */
}

/* ── components/breadcrumb.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Breadcrumb
   Rastro de jerarquía: dónde estoy, cómo vuelvo a un ancestro.

   Cuándo usar: mostrar la ubicación dentro de una jerarquía de varios
   niveles y permitir volver a un ancestro.
   Cuándo no: una sola acción "volver" (usar Back Link); navegación
   primaria entre secciones (usar Navigation).
   Reemplaza a: cualquier "ruta" de texto plano con separadores ad-hoc.

   Dependencia: variables.css (Color Roles)
   Consume EXCLUSIVAMENTE tokens semánticos --color-* / --font-size-* →
   funciona en light y dark sin overrides por tema.

   Estructura (nav > ol > li):
   <nav class="breadcrumb" aria-label="breadcrumb">
     <ol class="breadcrumb-list">
       <li class="breadcrumb-item"><a class="breadcrumb-link" href="/">Inicio</a></li>
       <li class="breadcrumb-separator" aria-hidden="true">/</li>
       <li class="breadcrumb-item"><a class="breadcrumb-link" href="/vacantes">Vacantes</a></li>
       <li class="breadcrumb-separator" aria-hidden="true">/</li>
       <li class="breadcrumb-item"><span class="breadcrumb-page" aria-current="page">Product Designer</span></li>
     </ol>
   </nav>

   Para rutas largas, colapsar los ítems del medio:
   <li class="breadcrumb-item"><span class="breadcrumb-ellipsis" aria-hidden="true">…</span></li>
═══════════════════════════════════════ */

.breadcrumb-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1-5);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--font-size-body-sm);
  color: var(--text-secondary);
  word-break: break-word;
}

.breadcrumb-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1-5);
}

.breadcrumb-link {
  color: inherit;
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: color var(--duration-fast) var(--ease-default);
}
.breadcrumb-link:hover { color: var(--color-primary); text-decoration: underline; }
.breadcrumb-link:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

.breadcrumb-page {
  font-weight: 600;
  color: var(--text-primary);
}

.breadcrumb-separator {
  display: inline-flex;
  align-items: center;
  color: var(--color-outline);
}
.breadcrumb-separator svg { width: 14px; height: 14px; }

.breadcrumb-ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  color: var(--text-secondary);
  /* Sirve como <span> decorativo o como <button> que abre el menú de ancestros */
  background: none;
  border: 0;
  padding: 0;
  border-radius: var(--radius-sm);
  font: inherit;
}
button.breadcrumb-ellipsis { cursor: pointer; }
button.breadcrumb-ellipsis:hover { background: var(--color-surface-variant); color: var(--text-primary); }
button.breadcrumb-ellipsis:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.breadcrumb-ellipsis svg { width: 16px; height: 16px; }

/* ── components/calendar.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Calendar
   Grilla de mes para elegir un día o un rango de fechas. Bloque interno del
   Date Picker (date-picker.css) — también usable en línea por su cuenta.

   Cuándo usar: como base del Date Picker, o en línea cuando el calendario debe
   estar siempre visible (no como popover).
   Cuándo no: para elegir solo mes/año (usar Select); para texto libre de fecha
   sin calendario (usar Input nativo type="date").
   Reemplaza a: cualquier selector de fecha ad-hoc sin grilla accesible.

   Dependencia: variables.css (Color Roles). Consume EXCLUSIVAMENTE tokens
   semánticos --color-* → funciona en light y dark sin overrides por tema.

   Nota de alcance (revert a vanilla): implementación simplificada respecto a la
   versión Tailwind (react-day-picker + date-fns) — matemática de fechas con
   Date nativo, sin soporte de locale (los nombres de mes/día usan
   Intl.DateTimeFormat del navegador), sin edge cases avanzados de rango
   (selección cruzando años se resuelve visualmente pero sin lógica extra).

   Uso:
   <div class="calendar">
     <div class="calendar-header">
       <button class="calendar-nav-btn" aria-label="Mes anterior">‹</button>
       <span class="calendar-caption">Julio 2026</span>
       <button class="calendar-nav-btn" aria-label="Mes siguiente">›</button>
     </div>
     <div class="calendar-weekdays">
       <span class="calendar-weekday">L</span><span class="calendar-weekday">M</span>
       <span class="calendar-weekday">M</span><span class="calendar-weekday">J</span>
       <span class="calendar-weekday">V</span><span class="calendar-weekday">S</span>
       <span class="calendar-weekday">D</span>
     </div>
     <div class="calendar-grid">
       <button class="calendar-day calendar-day-outside">29</button>
       <button class="calendar-day calendar-day-outside">30</button>
       <button class="calendar-day">1</button>
       <button class="calendar-day">2</button>
       <button class="calendar-day">3</button>
       <button class="calendar-day">4</button>
       <button class="calendar-day">5</button>
       <button class="calendar-day">6</button>
       <button class="calendar-day">7</button>
       <button class="calendar-day calendar-day-today">8</button>
       <button class="calendar-day">9</button>
       <button class="calendar-day">10</button>
       <button class="calendar-day calendar-day-selected" aria-selected="true">11</button>
       <button class="calendar-day">12</button>
     </div>
   </div>
═══════════════════════════════════════ */

.calendar {
  --cal-cell: 32px;              /* shadcn --cell-size: 2rem (32px); overridable por .calendar-sm/-lg */
  display: inline-block;         /* w-fit */
  padding: var(--space-3);                 /* p-3 */
  background: var(--color-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
/* Tamaño de celda custom (par shadcn --cell-size) */
.calendar-sm { --cal-cell: 28px; }
.calendar-lg { --cal-cell: 44px; }

/* ── Header: mes centrado, flechas a los extremos ── */
.calendar-header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--cal-cell);       /* month_caption: h-(--cell-size) */
  margin-bottom: 16px;           /* month: gap-4 */
  padding: 0 var(--cal-cell);    /* px-(--cell-size) — deja lugar a las flechas absolutas */
}
.calendar-caption {
  font-family: var(--font-body);         /* shadcn caption_label: sans */
  font-size: var(--font-size-body-md);   /* text-sm */
  font-weight: 600;                      /* font-medium */
  color: var(--color-on-surface);
  text-transform: capitalize;
}

/* ── Caption con dropdowns de mes/año (par shadcn captionLayout="dropdown") ──
   para saltar rápido de mes/año sin clickear las flechas repetidamente. */
.calendar-caption-dropdowns { display: inline-flex; gap: var(--space-1-5); }
.calendar-caption-select {
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--color-input, var(--border));   /* dropdown_root: border-input */
  border-radius: var(--radius-md);                         /* rounded-md */
  background: var(--color-surface);
  color: var(--color-on-surface);
  font-family: var(--font-body);
  font-size: var(--font-size-body-md);                     /* text-sm */
  font-weight: 600;                                        /* font-medium */
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
  text-transform: capitalize;
}
.calendar-caption-select:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.calendar-nav-btn {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--cal-cell);        /* button_previous/next: size-(--cell-size) */
  height: var(--cal-cell);
  border: none;
  border-radius: var(--radius-md);   /* ghost button: rounded-md */
  background: transparent;
  color: var(--color-on-surface);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-default);
}
.calendar-nav-btn svg { width: 16px; height: 16px; pointer-events: none; }   /* Chevron: size-4 */
.calendar-nav-btn:hover:not(:disabled) { background: var(--color-surface-variant); }
.calendar-nav-btn:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.calendar-nav-btn:disabled { opacity: .5; cursor: not-allowed; }
.calendar-nav-btn.prev { left: 0; }
.calendar-nav-btn.next { right: 0; }

/* ── Días de la semana ── */
.calendar-weekdays {
  display: flex;
}
.calendar-weekday {
  width: var(--cal-cell);
  padding-bottom: var(--space-2);                 /* week: mt-2 (separa del primer renglón) */
  text-align: center;
  font-size: var(--font-size-label);   /* weekday: text-[0.8rem] */
  font-weight: 400;
  color: var(--text-muted);
}

/* ── Grilla (semanas × 7 días) ── */
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, var(--cal-cell));
  grid-auto-rows: var(--cal-cell);
  row-gap: var(--space-2);                        /* week: mt-2 */
}
.calendar-day {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--cal-cell);
  height: var(--cal-cell);
  padding: 0;
  border: none;
  border-radius: var(--radius-md);         /* day_button: rounded-md */
  background: transparent;
  color: var(--color-on-surface);
  font-size: var(--font-size-body-md);     /* day_button: text-sm */
  font-weight: 400;                        /* font-normal */
  line-height: var(--line-height-none);    /* leading-none */
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-default);
}
/* hover = ghost button hover (bg-accent) */
.calendar-day:hover:not(:disabled):not(.calendar-day-selected):not(.calendar-day-range-middle) { background: var(--color-surface-variant); }
.calendar-day:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

/* outside = text-muted-foreground (sin opacidad extra, par shadcn) */
.calendar-day-outside { color: var(--text-muted); }
/* today = bg-accent text-accent-foreground rounded-md (no negrita) */
.calendar-day-today {
  background: var(--color-surface-variant);
  color: var(--color-on-surface);
}
/* selected single = bg-primary text-primary-foreground */
.calendar-day-selected,
.calendar-day-selected:hover {
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-weight: 500;
}
.calendar-day-disabled { opacity: .5; cursor: not-allowed; }   /* disabled: opacity-50 */

/* ── Rango (mode="range") ── medio = bg-accent, extremos = bg-primary ── */
.calendar-day-range-middle {
  background: var(--color-surface-variant);
  color: var(--color-on-surface);
  border-radius: 0;
}
.calendar-day-range-start { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.calendar-day-range-end   { border-top-left-radius: 0;  border-bottom-left-radius: 0; }

/* ── Booked (día reservado / no disponible) ── par shadcn "booked". No
   seleccionable; se marca con línea tachada y color atenuado. */
.calendar-day-booked {
  color: var(--text-muted);
  text-decoration: line-through;
  opacity: .6;
  cursor: not-allowed;
}
.calendar-day-booked:hover:not(.calendar-day-selected) { background: transparent; }

/* ── Week numbers (par shadcn showWeekNumber) ── columna líder con la semana ISO. */
.calendar-has-weeknums .calendar-grid { grid-template-columns: var(--cal-cell) repeat(7, var(--cal-cell)); }
.calendar-has-weeknums .calendar-weekdays { padding-left: var(--cal-cell); }
.calendar-weeknum {
  display: flex; align-items: center; justify-content: center;
  width: var(--cal-cell); height: var(--cal-cell);
  font-size: var(--font-size-label);
  color: var(--text-muted);
  opacity: .7;
}

/* ── Superficie compuesta ── cuando el Calendar convive con presets o con la fila
   de hora dentro de un panel, la superficie (borde + fondo + radio + padding) la
   provee el contenedor —  par shadcn (Calendar dentro de un Card/Popover único).
   El propio .calendar queda sin marco para no dibujar un borde doble. */
.calendar-with-presets > .calendar,
.date-picker-panel > .calendar {
  border: 0;
  padding: 0;
  border-radius: 0;
  background: transparent;
}

/* ── Presets ── atajos de fecha DEBAJO de la grilla, como el bloque shadcn
   "calendar con presets": botones outline que se reparten el ancho y hacen wrap. */
.calendar-with-presets {
  display: inline-flex;
  flex-direction: column;          /* calendario arriba, presets abajo */
  padding: var(--space-3);
  /* la card se ancla al ancho del calendario (7×32 celdas + 24 padding = 248px)
     → los presets hacen wrap (3+2) en vez de estirar la card, igual que el
     bloque shadcn calendar-32. (--cal-cell vive en .calendar, no acá.) */
  max-width: min(100%, 248px);
  background: var(--color-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.calendar-presets {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin: 12px -12px 0;            /* divisor a ancho completo de la card (border-t + px-4 !pt-4) */
  padding: var(--space-3) var(--space-3) 0;
  border-top: 1px solid var(--border);
}
.calendar-preset-btn {
  flex: 1 1 auto;                  /* se reparten el ancho; wrap cuando no entran */
  white-space: nowrap;
  text-align: center;
  padding: var(--space-2) var(--space-1-5);
  border: 1px solid var(--color-outline-variant);   /* Button variant="outline" size="sm" */
  border-radius: var(--radius-md);
  background: transparent; color: var(--color-on-surface);
  font: inherit; font-size: var(--font-size-body-sm); font-weight: 500;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-default), border-color var(--duration-fast) var(--ease-default);
}
.calendar-preset-btn:hover { background: var(--color-surface-variant); border-color: var(--color-outline); }
.calendar-preset-btn:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.calendar-preset-btn[aria-current="true"] { background: var(--color-secondary-container); border-color: var(--color-secondary); color: var(--color-on-secondary-container); }

/* ── Date + time ── fila de hora bajo la grilla (par shadcn date-and-time picker). */
.calendar-time-row {
  display: flex; align-items: center; gap: var(--space-2);
  margin-top: 8px; padding-top: var(--space-2-5);
  border-top: 1px solid var(--border);
  font-size: var(--font-size-body-sm);
  color: var(--color-on-surface-variant);
}
.calendar-time-row input[type="time"] {
  flex: 1;
  padding: var(--space-1-5) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-bg);
  color: var(--color-on-surface);
  font: inherit;
}
.calendar-time-row input[type="time"]:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

/* ── components/carousel.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Carousel
   Colección de ítems desplazable horizontalmente.

   Cuándo usar: colecciones de tarjetas/imágenes que no caben en una fila.
   Cuándo no: listas verticales largas (usar Table/List); un solo ítem destacado.
   Reemplaza a: carruseles custom con transform + drag handlers a mano.

   Dependencia: variables.css
   Implementación: scroll nativo con scroll-snap-type (sin librería de drag).
   Simplificado a propósito: sin física de swipe más allá del scroll táctil
   nativo del navegador, sin autoplay, sin loop infinito — los botones
   Prev/Next avanzan un ítem por click via scrollBy().

   Uso:
   <div class="carousel">
     <div class="carousel-content">
       <div class="carousel-item">Primera lámina</div>
       <div class="carousel-item">Segunda lámina</div>
     </div>
     <button class="carousel-btn carousel-btn-prev">‹</button>
     <button class="carousel-btn carousel-btn-next">›</button>
   </div>
═══════════════════════════════════════ */

.carousel {
  position: relative;
}

.carousel-content {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.carousel-content::-webkit-scrollbar {
  display: none;
}

.carousel-item {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: start;
}

/* Modifier: 2 ítems visibles por vista (descontando el gap de 16px) */
.carousel-item-half {
  flex: 0 0 calc(50% - 8px);
}

.carousel-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--card-bg);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  color: var(--text-primary);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-default);
}
.carousel-btn:hover:not(:disabled) {
  background: var(--color-surface-variant);
}
.carousel-btn:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.carousel-btn:disabled {
  opacity: .3;
  cursor: not-allowed;
}
.carousel-btn-prev { left: 8px; }
.carousel-btn-next { right: 8px; }

/* ── Orientación vertical ── par shadcn orientation="vertical": el contenido
   apila y scrollea en Y (scroll-snap vertical); los botones pasan arriba/abajo.
   El contenedor necesita una altura acotada (la fija el consumidor). */
.carousel-vertical .carousel-content {
  flex-direction: column;
  overflow-x: hidden;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  height: 100%;
}
.carousel-vertical .carousel-item { flex: 0 0 auto; }
.carousel-vertical .carousel-btn-prev {
  top: 8px;
  bottom: auto;
  left: 50%;
  right: auto;
  transform: translateX(-50%);
}
.carousel-vertical .carousel-btn-next {
  top: auto;
  bottom: 8px;
  left: 50%;
  right: auto;
  transform: translateX(-50%);
}

/* ── components/chart.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Chart
   Gráficos de línea, barra y torta para dashboards y analytics.
   Implementación simplificada: SVG a mano (línea/barra) + conic-gradient
   CSS (torta) — sin librería de charting. Cubre los casos comunes de un
   sitio de documentación; no tiene brush/zoom. El tooltip es rico y
   posicionado por JS (indicador de color + etiqueta + valor, initChartTooltips)
   sobre los puntos/barras; el <title> nativo queda como fallback accesible.

   Cuándo usar: series temporales o categóricas en dashboards y analytics
   (línea, barra, torta).
   Cuándo no: una sola métrica puntual (usar Stat Card); tablas de datos
   exactos (usar Table).
   Reemplaza a: gráficos armados con imágenes estáticas o librerías ad-hoc
   sin theming.

   Dependencia: variables.css (Color Roles)
   Paleta categórica (--chart-1..5) definida localmente acá — no existe
   en variables.css, se mapea 1:1 a roles semánticos existentes
   (Secondary/Tertiary/Success/Warning/Info), nunca a --color-primary
   (invierte a blanco en dark mode, ver Tabs/GOVERNANCE.md §5.6).

   Uso:
   <div class="chart-container">
     <svg class="chart-svg" viewBox="0 0 600 300">…</svg>
   </div>
═══════════════════════════════════════ */

.chart-container {
  --chart-1: var(--color-secondary);
  --chart-2: var(--color-tertiary);
  --chart-3: var(--color-success);
  --chart-4: var(--color-warning);
  --chart-5: var(--color-info);

  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  font-size: var(--font-size-caption);
  color: var(--text-muted);
}

/* SVG scales responsively via viewBox — no JS measurement needed */
.chart-svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.chart-grid line {
  stroke: var(--border);
  stroke-width: 1;
}

.chart-axis-label {
  fill: var(--text-muted);
  /* 11px en font-body no tenía token: el único 11px de la escala es
     --font-size-mono-sm, y un rótulo de eje no es mono. Por ROL es texto de
     apoyo de un dato — el mismo registro que un timestamp — así que va a
     caption. Un píxel más grande, y deja de ser un valor suelto. */
  font-size: var(--font-size-caption);
  font-family: var(--font-body);
}

/* ── Line chart ── */
.chart-line {
  fill: none;
  stroke-width: 2;
}
.chart-area {
  opacity: .14;
}
.chart-dot {
  transition: r var(--duration-fast);
  cursor: default;
}
.chart-dot:hover {
  r: 5;
}

/* ── Bar chart ── */
.chart-bar {
  transition: opacity var(--duration-fast);
  cursor: default;
}
.chart-bar:hover {
  opacity: .82;
}

/* ── Pie chart ── */
.chart-pie {
  width: 180px;
  height: 180px;
  border-radius: var(--radius-full);
  margin: 0 auto;
  flex-shrink: 0;
}

/* ── Legend ── */
.chart-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  padding-top: var(--space-3);
  font-size: var(--font-size-caption);
  color: var(--text-muted);
}
.chart-legend-item {
  display: flex;
  align-items: center;
  gap: var(--space-1-5);
}
.chart-legend-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex-shrink: 0;
}

/* ── Pie layout (chart + legend side by side on wider screens) ── */
.chart-pie-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}

/* ── Rich tooltip (posicionado por JS) ── par shadcn ChartTooltipContent:
   indicador de color + etiqueta + valor, sobre superficie inversa. Lo posiciona
   initChartTooltips() al hacer hover en un .chart-dot / .chart-bar. */
.chart-tooltip {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  transform: translate(-50%, calc(-100% - 8px));
  padding: var(--space-1-5) var(--space-2-5);
  border-radius: var(--radius-sm);
  background: var(--color-inverse-surface);
  color: var(--color-inverse-on-surface);
  box-shadow: var(--shadow-md);
  font-size: var(--font-size-caption);
  white-space: nowrap;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-default);
}
.chart-tooltip.is-visible { opacity: 1; }
.chart-tooltip-row { display: flex; align-items: center; gap: var(--space-2); }
.chart-tooltip-indicator { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
.chart-tooltip-label { color: color-mix(in srgb, var(--color-inverse-on-surface) 68%, transparent); }
.chart-tooltip-value { margin-left: auto; font-family: var(--font-mono); font-weight: 600; }
/* Punto resaltado en hover para un objetivo más visible. */
.chart-dot { transition: r var(--duration-fast) var(--ease-default); }
.chart-dot:hover { r: 5.5; }

/* ── components/checkbox.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Checkbox
   Selección múltiple dentro de una lista de opciones independientes.
   Tres estados: unchecked · checked · indeterminate.

   Cuándo usar: selección múltiple independiente (cada opción no afecta a las demás);
   aceptar términos; seleccionar filas de una tabla (con estado indeterminate en el
   checkbox de cabecera cuando la selección es parcial).
   Cuándo no: selección única excluyente (usar Radio Group); on/off inmediato sin
   lista de opciones (usar Switch).
   Reemplaza a: checkbox HTML sin estilizar, custom div+ARIA re-implementations.

   Dependencia: variables.css (Color Roles)
   Consume EXCLUSIVAMENTE tokens semánticos --color-* → funciona en light y dark
   sin overrides por tema.

   Implementación: un <input type="checkbox"> real, visualmente reemplazado (no oculto)
   vía appearance:none + un ::after para el ícono de check/dash. Mantiene el semántico
   nativo (focus, keyboard Space, aria-checked/indeterminate vía la propiedad DOM
   .indeterminate) sin reimplementar ARIA a mano.

   Uso:
   <label class="checkbox-label">
     <input type="checkbox" class="checkbox">
     Acepto los términos
   </label>
   <input type="checkbox" class="checkbox" id="row-1">
   <!-- indeterminate se setea vía JS: el.indeterminate = true -->
═══════════════════════════════════════ */

.checkbox {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  border-radius: 4px;
  border: 2px solid var(--color-outline);
  background: transparent;
  cursor: pointer;
  position: relative;
  vertical-align: middle;
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default);
}

.checkbox:hover {
  border-color: var(--color-on-surface);
}

.checkbox:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

.checkbox:checked,
.checkbox:indeterminate {
  border-color: transparent;
  background: var(--color-primary);
}

/* Error/invalid — red border via the shared error token (same signal as Input/Select) */
.checkbox[aria-invalid="true"] {
  border-color: var(--color-error);
}

/* Check icon (rendered via a rotated-box CSS checkmark — no external SVG needed) */
.checkbox:checked::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 1px;
  width: 5px;
  height: 9px;
  border: solid var(--color-on-primary);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* Indeterminate icon: a single dash */
.checkbox:indeterminate::after {
  content: "";
  position: absolute;
  left: 3px;
  top: 7px;
  width: 8px;
  height: 2px;
  background: var(--color-on-primary);
  border-radius: 1px;
}

.checkbox:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

/* ── Label wrapper (checkbox + texto, click target completo) ── */
.checkbox-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-on-surface);
  font: var(--font-weight-regular) var(--font-size-body-md)/1.4 var(--font-body);
  cursor: pointer;
}
.checkbox-label:has(.checkbox:disabled) {
  cursor: not-allowed;
  color: var(--color-on-disabled);
}

/* ── Card composition ── tarjeta seleccionable con borde (par shadcn checkbox card).
   El estado marcado se resuelve con :has(input:checked) — sin JS. */
/* Entidad seleccionable (COMPOSICION.md §4b): misma familia que Entidad —
   el checked es un estado, no otra forma de tarjeta. */
.checkbox-card {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-default),
              background-color var(--duration-fast) var(--ease-default);
}
.checkbox-card:hover { border-color: var(--color-outline); }
.checkbox-card:has(input:checked) {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 8%, transparent);
}
.checkbox-card:has(input:disabled) { opacity: .5; cursor: not-allowed; }
.checkbox-card-content { display: flex; flex-direction: column; gap: var(--space-0-5); min-width: 0; }
.checkbox-card-title {
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-title);
  font-weight: var(--font-weight-medium);
  color: var(--text-primary);
}
.checkbox-card-desc { font-size: var(--font-size-body-sm); color: var(--text-secondary); line-height: var(--line-height-body-sm); }

/* ── components/collapsible.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Collapsible
   Un único bloque de contenido que se muestra u oculta bajo un disparador.

   Cuándo usar: mostrar/ocultar UN bloque ("ver más", detalles opcionales de un
     formulario, una fila expandible en una tabla).
   Cuándo no: varias secciones expandibles agrupadas con el mismo patrón (usar
     Accordion, que además garantiza que solo una quede abierta si se necesita).
   Reemplaza a: `display:none` toggle ad-hoc sin transición ni estado accesible.

   Dependencia: variables.css (Color Roles)

   Nota: a diferencia de Accordion, Collapsible no impone un trigger estilizado
   (chevron, tipografía) — es intencionalmente mínimo, para poder envolver
   cualquier elemento disparador (un link de texto, un ícono, un botón).
   El único contrato de estilo es la animación de altura del contenido.

   Uso:
   <div class="collapsible">
     <button class="collapsible-trigger" aria-expanded="false" aria-controls="col-1" onclick="collapsibleToggle(this)">
       Ver más
     </button>
     <div class="collapsible-content" data-state="closed" id="col-1">
       <div class="collapsible-content-inner">Contenido adicional…</div>
     </div>
   </div>
═══════════════════════════════════════ */

.collapsible-trigger {
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-body);
  color: var(--color-primary);
  font-size: var(--font-size-body-md);
}
.collapsible-trigger:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

.collapsible-content {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows var(--duration-normal) var(--ease-default);
}
.collapsible-content[data-state="open"] {
  grid-template-rows: 1fr;
}
.collapsible-content-inner {
  min-height: 0;
  overflow: hidden;
}

/* ── components/combobox.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Combobox
   Select filtrable: un botón trigger que abre una lista Command buscable.

   Cuándo usar: elegir 1 opción de una lista larga donde hace falta buscar/filtrar
   (país, usuario, repo). Cuándo no: listas cortas sin búsqueda (usar .form-select);
   multi-select con tags (usar Chips + Command).
   Reemplaza a: selects con búsqueda hechos a mano.

   Dependencia: variables.css (Color Roles), button.css (.btn-tertiary), command.css
   Consume EXCLUSIVAMENTE tokens semánticos --color-* → funciona en
   light y dark sin overrides por tema.

   Uso:
   <div class="combobox">
     <button class="btn-tertiary combobox-trigger" aria-expanded="false" aria-haspopup="listbox">
       Seleccionar… <span class="combobox-chevron">…</span>
     </button>
     <div class="combobox-panel">
       <div class="command">…</div>
     </div>
   </div>
═══════════════════════════════════════ */

.combobox {
  position: relative;
  display: inline-block;
}

.combobox-trigger {
  width: 240px;
  justify-content: space-between;
  font-weight: 400;
}
.combobox-trigger.is-placeholder { color: var(--text-muted); }

.combobox-chevron {
  display: inline-flex;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  opacity: .5;
}

.combobox-panel[hidden] { display: none; }
.combobox-panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  width: 240px;
  z-index: 40;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--color-surface-container);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

.combobox-check {
  width: 16px;
  height: 16px;
  margin-right: 4px;
  flex-shrink: 0;
  opacity: 0;
}
.combobox-check.is-selected,
[aria-selected="true"] > .combobox-check { opacity: 1; }

/* ── components/command.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Command
   Paleta de comandos / lista filtrable rápida (búsqueda por substring).

   Cuándo usar: búsqueda-y-ejecución rápida de comandos o navegación, pickers
   filtrables, y es la base del Combobox.
   Cuándo no: un select de formulario corto (usar .form-select); búsqueda de
   contenido de la página (usar Search).
   Reemplaza a: menús filtrables hechos a mano.

   Nota de implementación: el filtrado es por coincidencia de substring
   (`.toLowerCase().includes()`), no fuzzy-search — ver components/ui/command.tsx.

   Dependencia: variables.css (Color Roles)
   Consume EXCLUSIVAMENTE tokens semánticos --color-* → funciona en
   light y dark sin overrides por tema.

   Uso:
   <div class="command">
     <div class="command-input-wrapper">
       <span class="command-input-icon"><i data-lucide="search"></i></span>
       <input class="command-input" placeholder="Buscar…">
     </div>
     <div class="command-list">
       <div class="command-empty">Sin resultados.</div>
       <div class="command-group">
         <div class="command-group-heading">Sugerencias</div>
         <div class="command-item" data-active="true">Item</div>
       </div>
     </div>
   </div>
═══════════════════════════════════════ */

.command {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--color-surface-container);
  color: var(--color-on-surface);
}

.command-input-wrapper {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-3);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.command-input-icon {
  display: inline-flex;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--color-on-surface-variant);
  opacity: .7;
}

.command-input {
  flex: 1;
  height: 44px;
  border: none;
  background: transparent;
  outline: none;
  font-family: var(--font-body);
  font-size: var(--font-size-body-sm);
  color: var(--color-on-surface);
}
.command-input::placeholder { color: var(--text-muted); }
.command-input:disabled { cursor: not-allowed; opacity: .5; }

.command-list {
  max-height: 300px;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--space-1-5);
}

.command-empty {
  padding: var(--space-6) 0;
  text-align: center;
  font-size: var(--font-size-body-sm);
  color: var(--color-on-surface-variant);
}

.command-group {
  overflow: hidden;
  padding: var(--space-1);
}
.command-group[hidden] { display: none; }

.command-group-heading {
  padding: var(--space-1-5) var(--space-2);
  font-size: var(--font-size-caption);
  font-weight: var(--font-weight-medium);
  color: var(--color-on-surface-variant);
}

.command-separator {
  margin: 4px -4px;
  height: 1px;
  background: var(--border);
}

.command-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-body-sm);
  color: var(--color-on-surface);
  cursor: pointer;
  user-select: none;
  outline: none;
}
.command-item[hidden] { display: none; }
.command-item svg { width: 18px; height: 18px; flex-shrink: 0; opacity: .7; }
.command-item[data-active="true"] {
  background: var(--color-surface-variant);
}
.command-item[aria-disabled="true"] {
  pointer-events: none;
  opacity: .4;
}

.command-shortcut {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--font-size-mono-sm);
  letter-spacing: var(--letter-spacing-overline);
  color: var(--color-on-surface-variant);
  opacity: .7;
}

/* ── Command Dialog (⌘K) ── el Command dentro de un overlay modal, anclado
   arriba-centro (par shadcn CommandDialog). Se abre con ⌘K/Ctrl+K o un trigger;
   cierra con Escape o click en el scrim. El [hidden] combinado le gana al
   display:flex de abajo. */
.command-dialog[hidden] { display: none; }
.command-dialog {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 12vh var(--space-4) var(--space-4);
  background: var(--color-scrim);
  animation: fadeIn var(--duration-fast) var(--ease-enter);
}
.command-dialog .command {
  width: min(640px, 100%);
  height: auto;
  max-height: 60vh;
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  animation: slideInUp var(--duration-normal) var(--ease-enter);
}

/* ── components/context-menu.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Context Menu
   Menú de acciones anclado al cursor, disparado por click derecho sobre un
   área. Visualmente es EL MISMO panel que Dropdown Menu (reutiliza sus clases
   `.dropdown-content`/`.dropdown-item`/etc. de dropdown-menu.css) — lo único
   que cambia es el disparador (contextmenu event + posición del cursor en vez
   de un trigger-button con click).

   Cuándo usar: acciones contextuales sobre un elemento de una lista/tabla/canvas.
   Cuándo no: acción siempre visible sobre un botón → Dropdown Menu.
   Reemplaza a: menús contextuales custom con position:fixed + z-index ad hoc.

   Dependencia: variables.css, dropdown-menu.css (clases .dropdown-*)

   Uso:
   <div class="context-menu-trigger" onContextMenu="…">Click derecho acá</div>
   <div class="dropdown-content" role="menu" style="top:200px;left:120px">…</div>
═══════════════════════════════════════ */

.context-menu-trigger {
  /* No estilos propios — es el área que escucha el evento contextmenu.
     El panel resultante usa las clases .dropdown-* compartidas. */
}

/* ── components/data-table.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Data Table
   Tabla con columnas ordenables + paginación. Extiende .data-table
   (css/components/table.css) — no duplica sus estilos base.

   Cuándo usar: tablas con datos reales que necesitan ordenar/paginar por
   columna.
   Cuándo no: una tabla estática de layout (usar Table directamente).
   Reemplaza a: tablas con lógica de sort/paginado cableada a mano.

   Paridad shadcn/TanStack (features de alto nivel, sin librería): orden por
   columna (con aria-sort), filtro de texto, selección de filas (checkbox de
   header + por fila, con conteo "N de M"), visibilidad de columnas y paginación
   por slice — todo vanilla vía initDataTable(). Fuera de alcance a propósito:
   resize/pin/drag de columnas, filtros facetados, virtualización de filas.

   Dependencia: variables.css, table.css, pagination.css, button.css,
                checkbox.css (selección), dropdown-menu.css (visibilidad de col.)

   Uso:
   <DataTable columns={[{ key:"name", header:"Nombre", sortable:true }]} data={rows} />
═══════════════════════════════════════ */

.data-table-sort-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1-5);
  margin: -6px -8px;
  padding: var(--space-1-5) var(--space-2);
  border-radius: var(--radius-sm);
  background: transparent;
  border: none;
  font: inherit;
  font-weight: 600;
  color: inherit;
  cursor: pointer;
}
.data-table-sort-btn:hover {
  background: var(--color-surface-variant);
}
.data-table-sort-btn:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.data-table-sort-icon {
  width: 14px;
  height: 14px;
  opacity: .5;
  flex-shrink: 0;
}
.data-table-sort-icon.is-active {
  opacity: 1;
}

/* aria-sort en el th refleja la dirección en el caret (paridad shadcn/TanStack). */
.data-table th[aria-sort="ascending"] .data-table-sort-icon,
.data-table th[aria-sort="descending"] .data-table-sort-icon { opacity: 1; }
.data-table th[aria-sort="descending"] .data-table-sort-icon { transform: rotate(180deg); }

/* ── Toolbar (filtro + visibilidad de columnas) ── par shadcn DataTableToolbar:
   filtro a la izquierda, menú de columnas a la derecha. */
.data-table-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.data-table-filter { max-width: 280px; flex: 1; min-width: 180px; }

/* ── Columna de selección ── checkbox nativo en th/td (par shadcn row selection);
   la fila seleccionada usa tr.selected (definido en table.css). */
.data-table th.data-table-select,
.data-table td.data-table-select {
  width: 1%;
  white-space: nowrap;
}
.data-table .data-table-select .checkbox { vertical-align: middle; }

/* Fila oculta por filtro o fuera de la página actual. */
.data-table tbody tr[hidden] { display: none; }
/* Celda de columna oculta por el menú de visibilidad. */
.data-table [data-col-hidden] { display: none; }

.data-table-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: 12px;
  flex-wrap: wrap;
}
.data-table-count {
  font-size: var(--font-size-caption);
  color: var(--text-muted);
}
/* Estado vacío cuando el filtro no deja filas visibles. */
.data-table-empty {
  padding: var(--space-8) var(--space-4);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--font-size-body-sm);
}

/* ── components/date-picker.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Date Picker
   Campo anclado (docked): un botón trigger que muestra la fecha elegida y abre
   un Calendar (calendar.css) en un panel flotante debajo.

   Cuándo usar: elegir una fecha (o rango) desde un campo de formulario o toolbar.
   Cuándo no: entrada de texto libre de fecha (usar Input nativo type="date" si
   no hace falta calendario); elegir mes/año sueltos (usar Select).
   Reemplaza a: inputs de fecha ad-hoc sin calendario accesible.

   Dependencia: variables.css (Color Roles), button.css (.btn-tertiary), calendar.css.

   Nota de alcance (revert a vanilla): panel posicionado con
   position:absolute; top:100% simple, sin detección de colisión con el borde
   del viewport (aceptado como limitación — ver tooltip.css/sheet.css para el
   mismo criterio en otros componentes).

   Uso:
   <div class="date-picker">
     <button class="date-picker-trigger btn-tertiary">
       <svg class="date-picker-icon">…</svg> 17 de julio, 2026
     </button>
     <div class="date-picker-panel"><div class="calendar">…</div></div>
   </div>
═══════════════════════════════════════ */

.date-picker {
  position: relative;
  display: inline-block;
}

.date-picker-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  justify-content: flex-start;
  width: 240px;
  font-weight: 400;
}
.date-picker-trigger.date-picker-trigger-empty {
  color: var(--text-muted);
}
.date-picker-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.date-picker-panel {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  animation: date-picker-in var(--duration-fast) var(--ease-default);
}
@keyframes date-picker-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── components/divider.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Divider
   Línea de separación entre secciones o ítems de contenido.

   Cuándo usar: separar grupos lógicos dentro de una lista, card o layout.
   Cuándo no: para crear espaciado (usar --space-* + margin/gap) ni como borde decorativo de contenedor.
   Reemplaza a: <hr> sin estilo / bordes CSS ad-hoc entre secciones.

   Dependencia: variables.css (Color Roles)

   Accesibilidad: un divisor puramente decorativo puede ser un <div> (default).
   Para un separador SEMÁNTICO entre grupos de contenido, usá un <hr class="divider">
   (rol "separator" implícito) o agregá role="separator" + aria-orientation al div
   — igual que el Separator de shadcn con decorative={false}. El reset border:none
   de abajo ya deja el <hr> con el mismo aspecto.

   Uso:
   <hr class="divider">                            <!-- separador semántico -->
   <div class="divider" role="separator"></div>    <!-- equivalente explícito -->
   <div class="divider divider-inset"></div>
   <div class="divider divider-vertical" role="separator" aria-orientation="vertical"></div>
═══════════════════════════════════════ */

.divider {
  border: none;
  background: var(--color-outline-variant);
  height: 1px;
  width: 100%;
  margin: 0;
}

.divider-inset { margin-left: 16px; width: calc(100% - 16px); }

.divider-vertical {
  height: auto;
  width: 1px;
  align-self: stretch;
}

/* ── components/dropdown-menu.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Dropdown Menu
   Lista de acciones flotante anclada a un trigger (click), con navegación por
   teclado (↑↓, Home/End, Enter/Espacio) — a diferencia de Popover, que es
   contenido libre sin semántica de lista.

   Cuándo usar: menú de acciones contextual sobre un botón/ícono ("⋮").
   Cuándo no: click derecho sobre un área → Context Menu; navegación de nivel
   superior siempre visible → Menubar / Navigation Menu.
   Reemplaza a: cualquier "menú" custom con `position:absolute` + z-index ad hoc.

   Posicionamiento: JS (components/lib/use-flyout.ts), ver popover.css para el
   detalle del mecanismo compartido.

   Dependencia: variables.css

   Uso:
   <button class="dropdown-trigger">Opciones</button>
   <div class="dropdown-content" role="menu" style="top:140px;left:60px">
     <div class="dropdown-label">Cuenta</div>
     <div class="dropdown-separator"></div>
     <button class="dropdown-item" role="menuitem">Editar perfil</button>
     <button class="dropdown-item" role="menuitem">Cerrar sesión</button>
   </div>
═══════════════════════════════════════ */

/* El display:flex de .dropdown-content pisa el [hidden] del UA stylesheet; este
   selector combinado (más específico) restaura hidden para poder alternar el
   panel con el atributo hidden desde JS. */
.dropdown-content[hidden] { display: none; }

.dropdown-content {
  position: fixed;
  z-index: 100;
  min-width: 180px;
  max-width: 280px;
  background: var(--color-surface-container);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--space-1-5);
  display: flex;
  flex-direction: column;
  gap: 1px;
  animation: dropdownIn var(--duration-normal) var(--ease-default);
}

@keyframes dropdownIn {
  from { opacity: 0; transform: scale(.96); }
  to   { opacity: 1; transform: scale(1); }
}

.dropdown-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-2-5);
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  font-size: var(--font-size-body-sm);
  color: var(--color-on-surface);
  text-align: left;
  cursor: pointer;
}
.dropdown-item:hover,
.dropdown-item:focus-visible {
  background: var(--color-surface-variant);
  outline: none;
}
.dropdown-item[data-disabled="true"] {
  color: var(--color-on-disabled);
  cursor: not-allowed;
  pointer-events: none;
}
.dropdown-item[data-variant="danger"] {
  color: var(--color-error-text);
}
.dropdown-item[data-variant="danger"]:hover,
.dropdown-item[data-variant="danger"]:focus-visible {
  background: var(--color-error-container);
}

.dropdown-item-indicator {
  width: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.dropdown-label {
  padding: var(--space-1-5) var(--space-2-5) var(--space-1);
  font-size: var(--font-size-caption);
  font-weight: 600;
  color: var(--color-on-surface-variant);
}

.dropdown-separator {
  height: 1px;
  margin: 4px -2px;
  background: var(--color-outline-variant);
}

.dropdown-shortcut {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--font-size-caption);
  color: var(--color-on-surface-variant);
  letter-spacing: var(--letter-spacing-overline);
}

/* ── Group (role="group") ── agrupa ítems relacionados; par shadcn DropdownMenuGroup. */
.dropdown-group {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* ── Inset ── ítems/labels sin ícono líder se alinean con los que sí tienen uno
   (shadcn data-inset → pl-8 = 32px = 10px padding + 16px slot + 6px gap). */
.dropdown-item[data-inset="true"],
.dropdown-label[data-inset="true"] {
  padding-left: var(--space-8);
}

/* ── Checkbox / Radio items ──
   role="menuitemcheckbox" | role="menuitemradio" + aria-checked manejan el
   indicador líder: check (SVG en el markup) para checkbox, punto CSS para radio.
   El slot .dropdown-item-indicator queda siempre reservado (16px) para que las
   etiquetas se alineen estén marcadas o no — igual que el pl-8 de shadcn. */
.dropdown-checkbox-item .dropdown-item-indicator > svg,
.dropdown-radio-item .dropdown-item-indicator::before {
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-default);
}
.dropdown-radio-item .dropdown-item-indicator::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.dropdown-checkbox-item[aria-checked="true"] .dropdown-item-indicator > svg,
.dropdown-radio-item[aria-checked="true"] .dropdown-item-indicator::before {
  opacity: 1;
}

/* ── Submenús (Sub/SubTrigger/SubContent) ──
   NO soportados en la arquitectura buildless (use-flyout no hace portal/anidado).
   Decisión de diseño documentada — para ramificar, usar acciones planas o un
   segundo Dropdown. No improvisar un motor de submenús. */

/* ── components/input-otp.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Input OTP
   Entrada de código de un solo uso / PIN, en casillas individuales.

   Cuándo usar: códigos de verificación (2FA, SMS, email), PIN.
   Cuándo no: texto libre (usar Input); un solo dígito (usar Input).
   Reemplaza a: inputs de código hechos con N campos <input> sueltos sin
   auto-avance ni paste-splitting.

   Dependencia: variables.css (Color Roles) + form.css (mismo lenguaje de foco)
   Cada casilla es un <input maxlength="1"> real (no un <div> + JS de caret) —
   el navegador resuelve selección/borrado/IME gratis. El wrapper agrega:
   auto-avance al siguiente casillero al tipear, retroceso que vuelve y limpia
   el casillero anterior, y paste-splitting (pegar un código completo lo
   reparte una posición por casillero desde el foco actual).

   Uso:
   <div class="otp-group" role="group" aria-label="Código de verificación">
     <input class="otp-slot" maxlength="1" inputmode="numeric">
     <input class="otp-slot" maxlength="1" inputmode="numeric">
     …
   </div>
═══════════════════════════════════════ */

.otp-group {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.otp-group.is-disabled {
  opacity: .5;
}

.otp-slot {
  width: 40px;
  height: 40px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  color: var(--text-primary);
  font-size: var(--font-size-heading-xs);
  font-family: var(--font-mono);
  font-weight: var(--font-weight-medium);
  text-align: center;
  padding: 0;
  outline: none;
  transition: border-color var(--duration-fast) var(--ease-default),
              box-shadow var(--duration-fast) var(--ease-default);
}
.otp-slot:hover:not(:disabled):not(:focus) {
  border-color: var(--color-outline);
}
.otp-slot:focus-visible {
  border-color: var(--interactive);
  box-shadow: 0 0 0 4px var(--color-focus-ring);
  z-index: 1;
  position: relative;
}
.otp-slot:disabled {
  cursor: not-allowed;
  background: var(--color-disabled);
  color: var(--color-on-disabled);
}

/* Error / inválido — borde rojo por casillero (par shadcn aria-invalid);
   .otp-group.is-error pinta todo el grupo (ej. código incorrecto). */
.otp-slot[aria-invalid="true"],
.otp-group.is-error .otp-slot {
  border-color: var(--color-error);
}
.otp-slot[aria-invalid="true"]:focus-visible,
.otp-group.is-error .otp-slot:focus-visible {
  box-shadow: 0 0 0 4px var(--color-error-ring);
}

/* Separador opcional entre grupos (ej. 3 + 3) */
.otp-separator {
  display: flex;
  align-items: center;
  color: var(--color-on-surface-variant);
  width: 12px;
  justify-content: center;
}

/* ── components/label.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Label
   Etiqueta accesible y reutilizable para cualquier control de formulario
   (input, select, checkbox, switch, radio, slider). Es el mismo lenguaje
   tipográfico que `.field-label` (form.css) pero desacoplado del `.field-group`
   de un input de texto — usalo cuando el control NO es un input de texto o
   cuando querés un label suelto (checkbox + label en línea, switch + label).

   Par shadcn/Radix Label: cuando el control asociado está deshabilitado, el
   label se atenúa y deja de responder al puntero. Acá se resuelve sin JS con
   `:has(+ :disabled)` (label inmediatamente antes del control) o con el estado
   explícito `data-disabled="true"`.

   Cuándo usar: nombrar un control de formulario, sobre todo checkbox/switch/
     radio/slider, o un label suelto asociado por `for`/`id`.
   Cuándo no: el label integrado de un input de texto con hint/error → usá
     `.field-label` dentro de `.field-group` (form.css). Título de sección →
     tipografía de heading, no un label.
   Reemplaza a: `<label>` sin estilo o `<span>` haciendo de label.

   Dependencia: variables.css

   Uso:
   <label class="label" for="email">Correo <span class="label-required">*</span></label>
   <input class="field-input" id="email" type="email">

   <!-- En línea con un control no-texto -->
   <div style="display:flex;align-items:center;gap:8px">
     <input type="checkbox" id="tos">
     <label class="label" for="tos">Acepto los términos</label>
   </div>
═══════════════════════════════════════ */

.label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-body);
  font-size: var(--font-size-label);
  font-weight: 500;
  line-height: var(--line-height-control);
  color: var(--text-primary);
  user-select: none;
  cursor: default;
}

/* Cuando el label nombra un control clickeable en línea (checkbox/radio/switch),
   compartir el cursor de puntero mejora el target táctil. */
.label[for] { cursor: pointer; }

/* Marcador de campo obligatorio — mismo tratamiento que form.css .field-required */
.label-required { color: var(--red); margin-left: -4px; }

/* Estado deshabilitado — derivado del control asociado, en cualquiera de los dos
   órdenes (label antes del control, o control antes del label como en el patrón
   checkbox + label), o explícito vía data-disabled. Sin necesidad de JS. */
.label:has(+ :disabled),
:disabled + .label,
.label[data-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* ── components/list.css ── */
/* ═══════════════════════════════════════
   Embassy DS — List
   Filas de una sola o múltiples líneas con headline + supporting text opcional.

   Cuándo usar: colecciones verticales de ítems homogéneos (bandeja de entrada, resultados, menús simples).
   Cuándo no: datos tabulares con columnas → usar Table; navegación jerárquica → usar Navigation.
   Reemplaza a: <ul>/<li> sin estilo ad-hoc.

   Dependencia: variables.css (Color Roles)

   Uso:
   <ul class="list">
     <li class="list-item">
       <div class="list-item-headline">Bandeja de entrada</div>
       <div class="list-item-supporting">128 mensajes sin leer</div>
     </li>
   </ul>
═══════════════════════════════════════ */

.list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  background: var(--ctx-surface);
}

.list-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  min-height: 56px;
}
/* El texto (headline + supporting) se apila; los slots leading/trailing quedan a
   los lados. Sin leading/trailing, .list-item-text puede omitirse y el headline/
   supporting apilan directo (retrocompatibilidad). */
.list-item-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-0-5);
  flex: 1;
  min-width: 0;
}
.list-item > .list-item-headline,
.list-item > .list-item-supporting { flex: 1; }

.list-item-headline {
  font: 500 var(--font-size-body-md)/1.4 var(--font-body);
  color: var(--color-on-surface);
}

.list-item-supporting {
  font: 400 var(--font-size-body-sm)/1.4 var(--font-body);
  color: var(--color-on-surface-variant);
}

/* ── Slots leading / trailing ── ícono, avatar, checkbox, badge o meta-texto. */
.list-item-leading,
.list-item-trailing {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  color: var(--color-on-surface-variant);
}
.list-item-trailing { margin-left: auto; }
.list-item-leading svg,
.list-item-trailing svg { width: 20px; height: 20px; }

/* ── Divisor entre ítems ── */
.list-item-divider,
.list--divided .list-item:not(:last-child) {
  border-bottom: 1px solid var(--border);
}

button.list-item {
  width: 100%;
  border: none;
  background: transparent;
  text-align: left;
  cursor: pointer;
  font: inherit;
}
button.list-item:hover { background: var(--color-surface-variant); }
/* Foco canónico de dos partes (GOVERNANCE §6.2) — consistente con el resto del DS. */
button.list-item:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: -2px;
  box-shadow: inset 0 0 0 4px var(--color-focus-ring);
}
/* Seleccionado — mismo lenguaje de selección que Nav/Menu (secondary-container). */
/* Sin `button.` adelante: el atributo declara el estado en cualquier etiqueta —
   un <li> con aria-selected se veía sin seleccionar. */
.list-item[aria-selected="true"],
.list-item.is-selected {
  background: var(--color-secondary-container);
  color: var(--color-on-secondary-container);
}
.list-item[aria-selected="true"] .list-item-supporting,
.list-item.is-selected .list-item-supporting { color: inherit; }
/* Deshabilitado */
button.list-item:disabled,
.list-item.is-disabled {
  opacity: .5;
  cursor: not-allowed;
  pointer-events: none;
}

/* ── components/menubar.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Menubar
   Fila horizontal de triggers de nivel superior (ej. "Archivo", "Editar"),
   cada uno abre un panel de acciones — reutiliza `.dropdown-content`/
   `.dropdown-item`/etc. de dropdown-menu.css para los paneles; este archivo
   solo define la barra y sus triggers.

   Comportamiento: una vez que un menú está abierto, pasar el mouse sobre otro
   trigger cambia el menú abierto (sin necesidad de volver a hacer click) —
   convención estándar de menubars de escritorio. Flechas ←/→ mueven el foco
   entre triggers de nivel superior; ↑/↓ dentro de un panel abierto navegan
   sus items (ver components/lib/use-flyout.ts).

   Cuándo usar: barra de menú de aplicación tipo desktop, siempre visible.
   Cuándo no: menú de acciones puntual sobre un botón → Dropdown Menu.
   Reemplaza a: barras de menú custom.

   Dependencia: variables.css, dropdown-menu.css (clases .dropdown-*)

   Uso:
   <div class="menubar">
     <button class="menubar-trigger">Archivo</button>
     <button class="menubar-trigger">Editar</button>
   </div>
   <div class="dropdown-content" role="menu" style="top:40px;left:8px">…</div>
═══════════════════════════════════════ */

.menubar {
  display: flex;
  align-items: center;
  gap: var(--space-0-5);
  padding: var(--space-1);
  background: var(--color-surface-container);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-md);
  width: fit-content;
}

.menubar-trigger {
  padding: var(--space-1-5) var(--space-3);
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  font-size: var(--font-size-body-sm);
  font-weight: 500;
  color: var(--color-on-surface);
  cursor: pointer;
}
.menubar-trigger:hover,
.menubar-trigger[data-state="open"] {
  background: var(--color-surface-variant);
}
.menubar-trigger:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: -2px;
  box-shadow: inset 0 0 0 4px var(--color-focus-ring);
}

/* ── components/navigation-menu.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Navigation Menu
   Fila horizontal de triggers de navegación (no acciones) que revelan un panel
   de contenido rico (enlaces agrupados, no una lista simple de items como
   Dropdown Menu/Menubar). Simplificación vs. Radix: sin animación de tamaño
   de viewport entre paneles de distinto alto (el viewport se re-mide al
   instante, sin easing de altura); hover-intent con un pequeño delay antes de
   abrir/cerrar, igual que un navbar real.

   Cuándo usar: navegación principal con sub-secciones agrupadas (mega-menu).
   Cuándo no: lista de acciones → Dropdown Menu; barra de menú tipo desktop →
   Menubar.
   Reemplaza a: mega-menus custom con hover + position:absolute ad hoc.

   El indicador deslizante reutiliza la misma técnica que Tabs (tabs.css):
   una barra posicionada vía offsetLeft/offsetWidth del trigger activo.

   Dependencia: variables.css

   Uso:
   <nav class="nav-menu">
     <div class="nav-menu-list">
       <button class="nav-menu-trigger">Productos</button>
       <a class="nav-menu-trigger" href="/precios">Precios</a>
       <span class="nav-menu-indicator"></span>
     </div>
     <div class="nav-menu-viewport">
       <div class="nav-menu-content">…</div>
     </div>
   </nav>
═══════════════════════════════════════ */

.nav-menu {
  position: relative;
  display: flex;
  justify-content: center;
}

.nav-menu-list {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav-menu-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3-5);
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  font-size: var(--font-size-body-sm);
  font-weight: 500;
  color: var(--color-on-surface);
  text-decoration: none;
  cursor: pointer;
}
.nav-menu-trigger:hover,
.nav-menu-trigger[data-state="open"] {
  background: var(--color-surface-variant);
}
.nav-menu-trigger:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: -2px;
  box-shadow: inset 0 0 0 4px var(--color-focus-ring);
}
.nav-menu-trigger-icon {
  width: 12px; height: 12px;
  transition: transform var(--duration-fast) var(--ease-default);
}
.nav-menu-trigger[data-state="open"] .nav-menu-trigger-icon {
  transform: rotate(180deg);
}

.nav-menu-indicator {
  position: absolute;
  bottom: -1px;
  left: 0;
  height: 2px;
  background: var(--color-secondary);
  transition: transform var(--duration-normal) var(--ease-expressive, ease-out),
              width var(--duration-normal) var(--ease-expressive, ease-out),
              opacity var(--duration-fast) var(--ease-default);
  pointer-events: none;
}
.nav-menu-indicator[data-visible="false"] { opacity: 0; }

.nav-menu-viewport-wrap {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  justify-content: center;
}
.nav-menu-viewport {
  position: relative;
  background: var(--color-surface-container);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  animation: navMenuIn var(--duration-normal) var(--ease-default);
}
@keyframes navMenuIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.nav-menu-content {
  padding: var(--space-4);
  min-width: 320px;
  display: grid;
  gap: var(--space-1);
}

.nav-menu-link {
  display: block;
  padding: var(--space-2) var(--space-2-5);
  border-radius: var(--radius-sm);
  color: var(--color-on-surface);
  text-decoration: none;
  font-size: var(--font-size-body-sm);
}
.nav-menu-link:hover,
.nav-menu-link:focus-visible {
  background: var(--color-surface-variant);
  outline: none;
}
/* Enlace actual (par shadcn NavigationMenuLink data-active) — marca la sección en
   la que está la persona; mismo lenguaje de selección que Nav/Menu/List. */
.nav-menu-link[aria-current="page"],
.nav-menu-link.is-active {
  background: var(--color-secondary-container);
  color: var(--color-on-secondary-container);
  font-weight: 600;
}
.nav-menu-link-title { font-weight: 600; display: block; }
.nav-menu-link-desc {
  display: block;
  margin-top: 2px;
  /* --color-on-surface-variant da 5.03:1 sobre --color-surface, pero el panel del
     menú NO está sobre surface: medido en el render daba 3.92:1 en claro y 3.54:1
     en oscuro. Es el caso que [15] no puede ver — certifica el par contra SU
     superficie, y el componente lo usa sobre otra. --text-secondary es el mismo
     rol (la bajada de un ítem) y aguanta las dos: 10.28:1 y 10.05:1. */
  color: var(--text-secondary);
  font-size: var(--font-size-caption);
}

/* ── components/pagination.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Pagination
   Navegación entre páginas de un conjunto de resultados.

   Cuándo usar: dividir listas/tablas largas en páginas navegables.
   Cuándo no: scroll infinito, o conjuntos chicos que caben en una vista.
   Reemplaza a: controles de "cargar más" cuando el total es conocido y acotado.

   Dependencia: variables.css (Color Roles) + button.css (Prev/Next reusan
   .btn-text). La página activa usa el mismo lenguaje de selección que
   Chip/Nav: --color-secondary-container / --color-on-secondary-container.

   Estructura (nav > ul > li):
   <nav class="pagination" aria-label="Paginación">
     <ul class="pagination-list">
       <li><a class="btn-text pagination-prev" href="#">‹ Anterior</a></li>
       <li><a class="pagination-link" href="#">1</a></li>
       <li><a class="pagination-link is-active" aria-current="page" href="#">2</a></li>
       <li><span class="pagination-ellipsis" aria-hidden="true">…</span></li>
       <li><a class="pagination-link" href="#">9</a></li>
       <li><a class="btn-text pagination-next" href="#">Siguiente ›</a></li>
     </ul>
   </nav>
═══════════════════════════════════════ */

.pagination {
  display: flex;
  width: 100%;
  justify-content: center;
}

.pagination-list {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}

.pagination-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 36px;
  min-width: 36px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-md);
  font-size: var(--font-size-body-md);
  font-weight: 500;
  color: var(--color-on-surface);
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-default);
}
.pagination-link:hover { background: var(--color-surface-variant); }
.pagination-link:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.pagination-link.is-active {
  background: var(--color-secondary-container);
  color: var(--color-on-secondary-container);
}
.pagination-link.is-active:hover { background: var(--color-secondary-container); }

.pagination-prev,
.pagination-next {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  cursor: pointer;
}
.pagination-prev svg,
.pagination-next svg { width: 18px; height: 18px; }
.pagination-prev { padding-left: var(--space-2-5); }
.pagination-next { padding-right: var(--space-2-5); }

.pagination-ellipsis {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 36px;
  width: 36px;
  color: var(--text-secondary);
}
.pagination-ellipsis svg { width: 16px; height: 16px; }

.pagination-link[aria-disabled="true"],
.pagination-prev[aria-disabled="true"],
.pagination-next[aria-disabled="true"] {
  pointer-events: none;
  opacity: .45;
}

/* ── components/popover.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Popover
   Panel flotante anclado a un trigger, sin flecha, para contenido rico
   arbitrario (no una lista de acciones — eso es Dropdown Menu).

   Cuándo usar: contenido contextual breve junto a un elemento (info extra,
   mini-formulario, selector custom).
   Cuándo no: lista de acciones → Dropdown Menu; confirmación bloqueante →
   Dialog; texto de una línea al hover → Tooltip.
   Reemplaza a: cualquier "dropdown" custom que solo muestre contenido, sin
   navegación por teclado tipo lista.

   Posicionamiento: JS (components/lib/use-flyout.ts) calcula top/left vía
   getBoundingClientRect con flip de borde de viewport; este archivo solo
   define look & feel, no vue lógica de posición (se aplica inline desde React
   como top/left en px sobre `.popover-content`).

   Dependencia: variables.css

   Uso:
   <button class="popover-trigger">Abrir</button>
   <div class="popover-content" style="top:120px;left:80px">…</div>
═══════════════════════════════════════ */

.popover-content {
  position: fixed;
  z-index: 100;
  min-width: 220px;
  max-width: 320px;
  background: var(--color-surface-container);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--space-4);
  color: var(--color-on-surface);
  font-size: var(--font-size-body-sm);
  animation: popoverIn var(--duration-normal) var(--ease-default);
}

@keyframes popoverIn {
  from { opacity: 0; transform: scale(.96); }
  to   { opacity: 1; transform: scale(1); }
}

/* ── Sub-partes opcionales (par shadcn PopoverHeader/Title/Description) ──
   para el patrón popover-con-título. El cuerpo sigue siendo contenido libre. */
.popover-header { margin-bottom: 8px; }
.popover-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-body-md);
  font-weight: 600;
  line-height: var(--line-height-title);
  color: var(--text-primary);
}
.popover-description {
  font-size: var(--font-size-body-sm);
  color: var(--text-secondary);
  line-height: var(--line-height-body-sm);
}

/* Alineación (align=start|center|end de shadcn) — la resuelve el posicionador JS
   (use-flyout) vía left/top; no hay clase de estilo. El flip de lado/edge ya es
   automático. */

/* ── components/progress.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Progress
   Indicadores de progreso lineal y circular, determinados e indeterminados.

   Cuándo usar: comunicar que una operación está en curso (carga, subida, procesamiento).
   Cuándo no: una acción instantánea no necesita indicador — usá un spinner solo si tarda
   más de ~300ms; para estados vacíos usá Empty State, no un progress infinito.
   Reemplaza a: spinners/loaders custom hechos a mano por pantalla.

   Dependencia: variables.css (Color Roles)
   Pista en --color-surface-variant, relleno en --color-primary. Funciona en
   light y dark sin overrides por tema.

   Uso:
   <div class="progress" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100">
     <div class="progress-fill" style="width: 60%"></div>
   </div>
   <div class="progress progress-indeterminate" role="progressbar"><div class="progress-fill"></div></div>
   <svg class="progress-circular" viewBox="0 0 44 44"><circle class="progress-circular-track" cx="22" cy="22" r="18"/><circle class="progress-circular-fill" cx="22" cy="22" r="18" style="stroke-dashoffset: calc(113 - (113 * 60 / 100))"/></svg>
   <svg class="progress-circular progress-circular-indeterminate" viewBox="0 0 44 44"><circle class="progress-circular-track" cx="22" cy="22" r="18"/><circle class="progress-circular-fill" cx="22" cy="22" r="18"/></svg>
═══════════════════════════════════════ */

/* ── Linear ── */
.progress {
  position: relative;
  width: 100%;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--color-surface-variant);
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  transition: width var(--duration-fast) var(--ease-default);
}
.progress-indeterminate .progress-fill {
  position: absolute;
  width: 40%;
  animation: progress-indeterminate-slide 1.4s var(--ease-default) infinite;
}
@keyframes progress-indeterminate-slide {
  0%   { left: -40%; }
  100% { left: 100%; }
}

/* ── Circular ── */
.progress-circular {
  width: 44px;
  height: 44px;
  transform: rotate(-90deg);
}
.progress-circular-track {
  fill: none;
  stroke: var(--color-surface-variant);
  stroke-width: 4;
}
.progress-circular-fill {
  fill: none;
  stroke: var(--color-primary);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-dasharray: 113; /* 2 * PI * r(18) ≈ 113 */
  transition: stroke-dashoffset var(--duration-fast) var(--ease-default);
}
.progress-circular-indeterminate .progress-circular-fill {
  stroke-dasharray: 85 28;
  animation: progress-circular-spin 1.2s linear infinite;
}
.progress-circular-indeterminate {
  animation: progress-circular-rotate 1.6s linear infinite;
}
@keyframes progress-circular-spin {
  0%   { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 113; }
}
@keyframes progress-circular-rotate {
  0%   { transform: rotate(-90deg); }
  100% { transform: rotate(270deg); }
}

/* ── components/radio-group.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Radio Group
   Selección única entre un conjunto de opciones mutuamente excluyentes,
   todas visibles al mismo tiempo.

   Cuándo usar: 2-6 opciones mutuamente excluyentes, todas visibles a la vez.
   Cuándo no: selección múltiple independiente (usar Checkbox); muchas opciones
   o espacio limitado (usar Select); on/off inmediato (usar Switch).
   Reemplaza a: radio buttons HTML sin estilizar, pill-buttons custom usados
   como selección única (ese patrón es Segmented Button, no Radio Group).

   Dependencia: variables.css (Color Roles)
   Consume EXCLUSIVAMENTE tokens semánticos --color-* → funciona en light y dark
   sin overrides por tema.

   Implementación: <input type="radio" name="..."> real agrupado por `name` —
   el navegador provee roving-tabindex (Flechas mueven la selección dentro del
   grupo, Tab sale del grupo) sin reimplementar ARIA a mano.

   Uso:
   <div class="radio-group" role="radiogroup" aria-label="Tipo de contrato">
     <label class="radio-label"><input type="radio" name="contract" class="radio" checked> Full-time</label>
     <label class="radio-label"><input type="radio" name="contract" class="radio"> Part-time</label>
   </div>
═══════════════════════════════════════ */

.radio-group {
  display: grid;
  gap: var(--space-2-5);
}

.radio {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid var(--color-outline);
  background: transparent;
  cursor: pointer;
  position: relative;
  vertical-align: middle;
  transition: border-color var(--duration-fast) var(--ease-default);
}

.radio:hover {
  border-color: var(--color-on-surface);
}

.radio:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

.radio:checked {
  border-color: var(--color-primary);
}
.radio:checked::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--color-primary);
}

/* Error/invalid — red border via the shared error token (set aria-invalid on the item) */
.radio[aria-invalid="true"] {
  border-color: var(--color-error);
}

.radio:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

/* ── Label wrapper ── */
.radio-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-on-surface);
  font: var(--font-weight-regular) var(--font-size-body-md)/1.4 var(--font-body);
  cursor: pointer;
}
.radio-label:has(.radio:disabled) {
  cursor: not-allowed;
  color: var(--color-on-disabled);
}

/* ── Card composition ── tarjeta seleccionable con borde (par shadcn radio card).
   El estado seleccionado se resuelve con :has(input:checked) — sin JS. */
/* Entidad seleccionable (§4b). */
.radio-card {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-default),
              background-color var(--duration-fast) var(--ease-default);
}
.radio-card:hover { border-color: var(--color-outline); }
.radio-card:has(input:checked) {
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 8%, transparent);
}
.radio-card:has(input:disabled) { opacity: .5; cursor: not-allowed; }
.radio-card-content { display: flex; flex-direction: column; gap: var(--space-0-5); min-width: 0; }
.radio-card-title {
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-title);
  font-weight: var(--font-weight-medium);
  color: var(--text-primary);
}
.radio-card-desc { font-size: var(--font-size-body-sm); color: var(--text-secondary); line-height: var(--line-height-body-sm); }

/* ── components/rich-tooltip.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Rich Tooltip
   Superficie persistente y descartable con título, texto de soporte y una fila de
   acciones opcional. A diferencia del Tooltip plano (hover), este se dispara por
   click/focus — el puntero puede entrar a leerlo o interactuar con sus acciones.
   Esta es la ÚNICA superficie "rich content overlay" del DS — no se envía un Hover
   Card separado (evaluado 2026-07 y descartado por duplicar esto).

   Cuándo usar: preview con título + texto + acción sobre un trigger (ícono, chip, avatar).
   Cuándo no: un hint de una sola línea → Tooltip. Contenido arbitrario grande → un
   panel propio (Popover/Sheet).
   Reemplaza a: Hover Card.

   Extiende la base posicional de tooltip.css (mismo concepto de offset por lado)
   pero es JS-driven (clase .open toggled por click), no :hover.

   Dependencia: variables.css, tooltip.css (posicionamiento por lado)

   Uso:
   <span class="rich-tooltip-wrap">
     <button class="icon-btn">…</button>
     <div class="rich-tooltip-content rich-tooltip-top open" role="dialog">
       <div class="rich-tooltip-row">
         <div class="rich-tooltip-body">
           <p class="rich-tooltip-title">María García</p>
           <p class="rich-tooltip-subtitle">UX Designer · Disponible</p>
         </div>
         <button class="rich-tooltip-close">✕</button>
       </div>
       <div class="rich-tooltip-actions">
         <button class="btn-text btn-sm">Ver perfil</button>
       </div>
     </div>
   </span>
═══════════════════════════════════════ */

.rich-tooltip-wrap {
  position: relative;
  display: inline-flex;
}

.rich-tooltip-content {
  position: absolute;
  z-index: 60;
  width: max-content;
  max-width: 280px;
  background: var(--color-surface-container);
  color: var(--color-on-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--space-4);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-default),
              transform var(--duration-fast) var(--ease-default);
}
.rich-tooltip-content.open {
  opacity: 1;
  pointer-events: auto;
}

.rich-tooltip-top    { bottom: calc(100% + 6px); left: 50%; transform: translate(-50%, 4px) scale(.96); }
.rich-tooltip-bottom { top: calc(100% + 6px);    left: 50%; transform: translate(-50%, -4px) scale(.96); }
.rich-tooltip-left   { right: calc(100% + 6px);  top: 50%;  transform: translate(4px, -50%) scale(.96); }
.rich-tooltip-right  { left: calc(100% + 6px);   top: 50%;  transform: translate(-4px, -50%) scale(.96); }

.rich-tooltip-top.open    { transform: translate(-50%, 0) scale(1); }
.rich-tooltip-bottom.open { transform: translate(-50%, 0) scale(1); }
.rich-tooltip-left.open   { transform: translate(0, -50%) scale(1); }
.rich-tooltip-right.open  { transform: translate(0, -50%) scale(1); }

.rich-tooltip-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}
.rich-tooltip-body { flex: 1; min-width: 0; }
.rich-tooltip-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-body-md);
  font-weight: 600;
}
.rich-tooltip-subtitle {
  margin-top: 4px;
  font-size: var(--font-size-body-sm);
  color: var(--color-on-surface-variant);
}
.rich-tooltip-close {
  flex-shrink: 0;
  width: 28px; height: 28px;
  margin: -4px -4px 0 0;
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-on-surface-variant);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-default),
              color var(--duration-fast) var(--ease-default);
}
.rich-tooltip-close:hover { background: var(--color-surface-variant); color: var(--color-on-surface); }
.rich-tooltip-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: 4px;
}

/* ── components/scroll-area.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Scroll Area
   Contenedor con scroll y una barra estilizada, consistente entre navegadores.
   Puro CSS — sin JS: `overflow: auto` nativo + reglas de scrollbar por motor.

   Cuándo usar: regiones con scroll donde querés una barra consistente y discreta
   (listas largas dentro de un panel, contenido de un menú/sheet).
   Cuándo no: el scroll natural de la página completa (no lo envuelvas).
   Reemplaza a: el scrollbar default del navegador dentro de paneles/menús.

   Dependencia: variables.css (thumb = --color-outline)

   Uso:
   <div class="scroll-area" style="max-height: 320px">
     …
   </div>
═══════════════════════════════════════ */

.scroll-area {
  position: relative;
  overflow: auto;
  overscroll-behavior: contain;

  /* Firefox */
  scrollbar-width: thin;
  scrollbar-color: var(--color-outline) transparent;
}

/* Chromium / WebKit */
.scroll-area::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
.scroll-area::-webkit-scrollbar-track {
  background: transparent;
}
.scroll-area::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--color-outline) 60%, transparent);
  border-radius: var(--radius-full);
  border: 2px solid transparent;
  background-clip: content-box;
}
.scroll-area::-webkit-scrollbar-thumb:hover {
  background: var(--color-outline);
  background-clip: content-box;
}
.scroll-area::-webkit-scrollbar-corner {
  background: transparent;
}

/* ── components/sheet.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Sheet
   EL único panel anclado a un borde del DS. `side-left`/`side-right` = Side Sheet ·
   `side-bottom` = Bottom Sheet · `side-top`. Bottom Sheet y Side Sheet no son
   componentes separados — son valores de `side` de este mismo panel.

   Cuándo usar: cualquier panel anclado a un borde — filtros, detalle, formularios
   secundarios, bottom sheet en mobile.
   Cuándo no: navegación principal (usar el app-shell / Navigation Drawer); diálogo
   modal centrado (usar Dialog/Modal).
   Reemplaza a: Drawer / bottom-sheet / side-sheet legacy — el DS estandarizó en UN
   solo panel edge-anchored (un Drawer con gestos fue evaluado y removido: su único
   diferencial era swipe/snap points, que el producto no usa).

   Motion: --ease-emphasized · --duration-sheet (500ms, sin overshoot) — compartido
   por los 4 lados. Sin animación de salida (mismo approach que Modal: se desmonta
   al cerrar, no hay estado "closing").

   Dependencia: variables.css, button.css (para el close button / footer actions)

   Uso:
   <div class="sheet-overlay">
     <div class="sheet-content sheet-content-right open">
       <button class="sheet-close">✕</button>
       <div class="sheet-header">
         <div class="sheet-title">Editar perfil</div>
         <div class="sheet-description">Actualizá tu información pública.</div>
       </div>
       <div class="sheet-body">…</div>
       <div class="sheet-footer">
         <button class="btn-tertiary">Cancelar</button>
         <button class="btn-primary">Guardar</button>
       </div>
     </div>
   </div>
═══════════════════════════════════════ */

.sheet-overlay {
  position: fixed;
  inset: 0;
  background: var(--color-scrim);
  backdrop-filter: blur(4px);
  z-index: 100;
  animation: fadeIn var(--duration-fast) var(--ease-enter);
}

.sheet-content {
  position: fixed;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  background: var(--color-surface);
  border-color: var(--color-outline-variant);
  border-style: solid;
  border-width: 0;
  box-shadow: var(--shadow-lg);
  transition: transform var(--duration-sheet) var(--ease-emphasized);
}

/* ── Side Sheet (left/right) — full height, responsive width ── */
.sheet-content-right,
.sheet-content-left {
  top: 0;
  height: 100vh;
  width: 75vw;
  max-width: 384px; /* sm:max-w-sm */
}
.sheet-content-right { right: 0; border-left-width: 1px; transform: translateX(100%); }
.sheet-content-left  { left: 0;  border-right-width: 1px; transform: translateX(-100%); }
.sheet-content-right.open { transform: translateX(0); }
.sheet-content-left.open  { transform: translateX(0); }

/* ── Bottom / Top Sheet — content-sized, centered on wider viewports ── */
.sheet-content-top,
.sheet-content-bottom {
  left: 0; right: 0;
  max-height: 80vh;
}
.sheet-content-top {
  top: 0;
  border-bottom-width: 1px;
  border-radius: 0 0 16px 16px;
  transform: translateY(-100%);
}
.sheet-content-bottom {
  bottom: 0;
  border-top-width: 1px;
  border-radius: 16px 16px 0 0;
  transform: translateY(100%);
}
.sheet-content-top.open,
.sheet-content-bottom.open { transform: translateY(0); }

@media (min-width: 640px) {
  .sheet-content-top,
  .sheet-content-bottom {
    margin: 0 auto;
    max-width: 512px; /* sm:max-w-lg */
  }
}

.sheet-header {
  display: flex;
  flex-direction: column;
  gap: var(--space-1-5);
  padding: var(--space-6) var(--space-6) 0;
  padding-right: 56px; /* deja lugar al close button absoluto */
}
.sheet-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-heading-sm);
  font-weight: 600;
}
.sheet-description {
  font-size: var(--font-size-body-sm);
  color: var(--color-on-surface-variant);
  line-height: var(--line-height-body-sm);
}
.sheet-close {
  position: absolute;
  top: 16px; right: 16px;
  width: 32px; height: 32px;
  border-radius: var(--radius-md);
  border: none;
  background: transparent;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--color-on-surface-variant);
  transition: background var(--duration-fast) var(--ease-default),
              color var(--duration-fast) var(--ease-default);
}
.sheet-close:hover { background: var(--color-surface-variant); color: var(--color-on-surface); }
.sheet-close:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.sheet-body {
  flex: 1;
  overflow-y: auto;
  padding: 0 var(--space-6);
  color: var(--color-on-surface-variant);
}
/* Side sheets: footer stacks full-width (narrow panel convention).
   Top/Bottom sheets: footer follows the same trailing-aligned row as Dialog. */
.sheet-footer {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0 var(--space-6) var(--space-6);
}
.sheet-content-top .sheet-footer,
.sheet-content-bottom .sheet-footer {
  flex-direction: row-reverse;
  justify-content: flex-start;
}

/* ── components/slider.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Slider
   Selección de un valor (o rango) dentro de un continuo — volumen, brillo, precio.

   Cuándo usar: valores donde el número exacto importa poco, con feedback inmediato.
   Cuándo no: valores que requieren precisión exacta (usar Input numérico).
   Reemplaza a: sliders custom hechos con <div> + drag handlers a mano.

   Dependencia: variables.css (Color Roles)
   Pista en --color-surface-variant, rango activo y thumb en --color-primary.

   Implementación: <input type="range"> nativo — drag físico, teclado
   (flechas, Home/End, Page Up/Down) y semántica de foco vienen gratis del
   navegador. Modo rango (dos thumbs): dos <input type="range"> apilados con
   pointer-events:none en el input y pointer-events:auto reactivado solo en
   el thumb (::-webkit-slider-thumb / ::-moz-range-thumb) — técnica estándar
   para simular un slider de dos manijas sin una librería.

   Uso:
   <div class="slider">
     <div class="slider-track"></div>
     <div class="slider-range" style="width: 50%"></div>
     <input type="range" class="slider-input" min="0" max="100" value="50">
   </div>
═══════════════════════════════════════ */

.slider {
  position: relative;
  width: 100%;
  height: 20px;
  display: flex;
  align-items: center;
  touch-action: none;
}
.slider[data-disabled] {
  opacity: .4;
}

.slider-track {
  position: absolute;
  left: 0;
  right: 0;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--color-surface-variant);
  pointer-events: none;
}
.slider-range {
  position: absolute;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  pointer-events: none;
}

.slider-input {
  position: absolute;
  left: 0;
  width: 100%;
  margin: 0;
  appearance: none;
  background: transparent;
  pointer-events: none;
}
.slider-input:focus-visible {
  outline: none;
}
.slider-input::-webkit-slider-runnable-track {
  background: transparent;
  height: 4px;
}
.slider-input::-moz-range-track {
  background: transparent;
  height: 4px;
  border: none;
}

.slider-input::-webkit-slider-thumb {
  appearance: none;
  pointer-events: auto;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--color-primary);
  border: 2px solid var(--card-bg);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: box-shadow var(--duration-fast) var(--ease-default);
}
.slider-input::-moz-range-thumb {
  pointer-events: auto;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--color-primary);
  border: 2px solid var(--card-bg);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: box-shadow var(--duration-fast) var(--ease-default);
}
.slider-input:focus-visible::-webkit-slider-thumb,
.slider-input:hover:not(:disabled)::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.slider-input:focus-visible::-moz-range-thumb,
.slider-input:hover:not(:disabled)::-moz-range-thumb {
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.slider-input:disabled::-webkit-slider-thumb,
.slider-input:disabled::-moz-range-thumb {
  cursor: not-allowed;
}

/* ═══ Orientación vertical ═══ par shadcn orientation="vertical".
   El contenedor se vuelve alto y angosto; pista y rango van en el eje Y y el
   <input type=range> nativo se orienta con writing-mode vertical (Chrome 111+,
   Firefox 121+). El rango (.slider-range) recibe su alto por estado (inline/JS),
   igual que el ancho en horizontal. Modo rango vertical = misma técnica de dos
   inputs apilados. */
.slider-vertical {
  width: 20px;
  height: 160px;
  flex-direction: column;
}
.slider-vertical .slider-track {
  left: 50%;
  right: auto;
  top: 0;
  bottom: 0;
  width: 4px;
  height: auto;
  transform: translateX(-50%);
}
.slider-vertical .slider-range {
  left: 50%;
  bottom: 0;
  width: 4px;
  transform: translateX(-50%);
}
.slider-vertical .slider-input {
  writing-mode: vertical-lr;
  direction: rtl; /* mínimo abajo, máximo arriba */
  top: 0;
  left: 50%;
  width: 20px;
  height: 100%;
  transform: translateX(-50%);
}

/* ── components/switch.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Switch
   Control binario on/off con efecto inmediato (no requiere un botón de
   confirmación aparte).

   Cuándo usar: ajustes que se aplican al instante (modo oscuro, notificaciones).
   Cuándo no: selección dentro de una lista de opciones (usar Checkbox); elegir
   una entre varias alternativas (usar Radio Group).
   Reemplaza a: toggle switches ad-hoc, checkbox estilizado como switch sin
   la semántica role="switch".

   Dependencia: variables.css (Color Roles)
   Consume EXCLUSIVAMENTE tokens semánticos --color-* → funciona en light y dark
   sin overrides por tema.

   Implementación: <input type="checkbox" role="switch"> real, estilizado como
   track + thumb vía appearance:none. role="switch" hace que los lectores de
   pantalla anuncien "activado/desactivado" en lugar de "marcado/no marcado".

   Uso:
   <label class="switch-label">
     <input type="checkbox" role="switch" class="switch">
     Modo oscuro
   </label>
═══════════════════════════════════════ */

.switch {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  position: relative;
  flex-shrink: 0;
  width: 44px;
  height: 24px;
  border-radius: var(--radius-full);
  border: 2px solid var(--color-outline);
  background: var(--color-surface-variant);
  cursor: pointer;
  vertical-align: middle;
  transition: background var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default);
}

/* Thumb */
.switch::after {
  content: "";
  position: absolute;
  top: 1px;
  left: 1px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--color-outline);
  transition: transform var(--duration-normal) var(--ease-expressive),
              background var(--duration-fast) var(--ease-default);
}

.switch:hover {
  border-color: var(--color-on-surface);
}

.switch:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}

.switch:checked {
  background: var(--color-primary);
  border-color: transparent;
}
.switch:checked::after {
  transform: translateX(20px);
  background: var(--color-on-primary);
}

/* Error/invalid — red track border via the shared error token. Only the unchecked
   track shows a visible border at rest, so that's the state this needs to override. */
.switch[aria-invalid="true"]:not(:checked) {
  border-color: var(--color-error);
}

.switch:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

/* ── Tamaño small ── par shadcn size="sm" (track más compacto para filas densas
   o dentro de una card-action). Track 36×20, thumb 14, recorrido 16px. */
.switch-sm { width: 36px; height: 20px; }
.switch-sm::after { width: 14px; height: 14px; }
.switch-sm:checked::after { transform: translateX(16px); }

/* ── Label wrapper ── */
.switch-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-on-surface);
  font: var(--font-weight-regular) var(--font-size-body-md)/1.4 var(--font-body);
  cursor: pointer;
}
.switch-label:has(.switch:disabled) {
  cursor: not-allowed;
  color: var(--color-on-disabled);
}

/* ── components/toggle-group.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Toggle Group
   Un conjunto de Toggles relacionados que comparten selección única o múltiple.

   Cuándo usar: agrupar varios Toggle relacionados (formato de texto:
     negrita/cursiva/subrayado; alineación). type="single" = elegir 1;
     type="multiple" = varios on/off a la vez.
   Cuándo no: elegir 1 de N mutuamente excluyentes con etiqueta de vista y
     forma de píldora (usar Segmented Button — visualmente distinto: outline
     de grupo + contenedor selected, ver segmented-button.css); filtros tipo
     tag (usar Chip).
   Reemplaza a: grupos de botones on/off ad-hoc.

   Nota de diseño: Toggle Group NO reutiliza los tokens de píldora de
   Segmented Button (--seg-btn-*) — son visualmente distintos a propósito
   (Toggle Group = fila de .toggle individuales sin contenedor de grupo;
   Segmented Button = contenedor con borde + segmentos). Ambos comparten el
   mismo color de selección (--color-secondary-container) por consistencia
   de "estado seleccionado" en todo el sistema, pero la forma difiere.

   Dependencia: variables.css, toggle.css (cada item es un .toggle)

   Uso:
   <div class="toggle-group" role="group" aria-label="Formato de texto">
     <button class="toggle" aria-pressed="false">B</button>
     <button class="toggle" aria-pressed="true">I</button>
     <button class="toggle" aria-pressed="false">U</button>
   </div>
═══════════════════════════════════════ */

.toggle-group {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

/* ── components/toggle.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Toggle
   Botón de dos estados (presionado / no presionado) para alternar un ajuste.

   Cuándo usar: alternar un solo estado on/off que se aplica de inmediato
     (negrita en un editor, "mostrar solo favoritos").
   Cuándo no: elegir 1 de N (usar Segmented Button); filtros tipo tag (usar
     Chip); acción sin estado (usar Button).
   Reemplaza a: botones on/off ad-hoc con aria-pressed manejado a mano.

   Dependencia: variables.css (Color Roles)
   El estado presionado usa el mismo lenguaje de selección que Chip/nav-active
   (--color-secondary-container), NUNCA --color-primary (que se invierte a
   blanco en dark mode y se leería como "sin color").

   Uso:
   <button class="toggle" aria-pressed="false" onclick="togglePress(this)">
     <svg>…</svg>
   </button>
   <button class="toggle toggle-outline toggle-lg" aria-pressed="true">B</button>
═══════════════════════════════════════ */

.toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 36px;
  min-width: 36px;
  padding: 0 var(--space-2-5);
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-on-surface);
  font-family: var(--font-body);
  font-size: var(--font-size-body-md);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--duration-fast) var(--ease-default), color var(--duration-fast) var(--ease-default);
}
.toggle svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}
.toggle:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-on-surface) 8%, transparent);
}
.toggle:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.toggle[aria-pressed="true"] {
  background: var(--color-secondary-container);
  color: var(--color-on-secondary-container);
}
.toggle[aria-pressed="true"]:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-on-secondary-container) 8%, var(--color-secondary-container));
}
.toggle:disabled {
  opacity: 0.38;
  cursor: not-allowed;
  pointer-events: none;
}
/* Error / inválido (par shadcn aria-invalid) — poco frecuente en Toggle, pero
   disponible para consistencia con el resto de los controles. */
.toggle[aria-invalid="true"] {
  border: 1px solid var(--color-error);
}

/* ── Variante Outline ── */
.toggle-outline {
  border: 1px solid var(--color-outline-variant);
  background: transparent;
}
.toggle-outline[aria-pressed="true"] {
  border-color: transparent;
}

/* ── Tamaños ── */
.toggle-sm { height: 32px; min-width: 32px; padding: 0 var(--space-2); }
.toggle-lg { height: 40px; min-width: 40px; padding: 0 var(--space-3); }

/* ── components/tooltip.css ── */
/* ═══════════════════════════════════════
   Amalgama DS — Tooltip
   Etiqueta de texto breve, disparada por hover/focus sobre un trigger.
   CSS-only: sin JS de posicionamiento. El trigger es el ancla (position: relative)
   y el tooltip es absolute dentro de él — no hace collision-detection de viewport
   (no "flipea" solo si se corta contra el borde de la pantalla). Si un caso de uso
   necesita eso, es una limitación conocida y aceptada de esta variante buildless.

   Cuándo usar: un hint de texto simple sobre un ícono/control ambiguo.
   Cuándo no: contenido con título + acción → usar Rich Tooltip (rich-tooltip.css).
   Reemplaza a: `title="…"` nativo (sin estilo, sin delay configurable).

   Dependencia: variables.css

   Uso:
   <span class="tooltip-wrap">
     <button class="icon-btn" aria-describedby="tt-1"><svg>…</svg></button>
     <span class="tooltip-content tooltip-top" role="tooltip" id="tt-1">Ajustes</span>
   </span>
═══════════════════════════════════════ */

.tooltip-wrap {
  position: relative;
  display: inline-flex;
}

.tooltip-content {
  position: absolute;
  z-index: 60;
  white-space: nowrap;
  background: var(--color-inverse-surface);
  color: var(--color-inverse-on-surface);
  padding: var(--space-1-5) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-label);
  box-shadow: var(--shadow-md);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-default),
              transform var(--duration-fast) var(--ease-default);
  transition-delay: 0s;
}

.tooltip-wrap:hover .tooltip-content,
.tooltip-wrap:focus-within .tooltip-content {
  opacity: 1;
  transition-delay: .4s; /* hover-intent delay before showing; hides instantly */
}

/* ── Side placement (default offset 6px, matches Radix sideOffset) ── */
.tooltip-top {
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translate(-50%, 4px);
}
.tooltip-wrap:hover .tooltip-top,
.tooltip-wrap:focus-within .tooltip-top { transform: translate(-50%, 0); }

.tooltip-bottom {
  top: calc(100% + 6px);
  left: 50%;
  transform: translate(-50%, -4px);
}
.tooltip-wrap:hover .tooltip-bottom,
.tooltip-wrap:focus-within .tooltip-bottom { transform: translate(-50%, 0); }

.tooltip-left {
  right: calc(100% + 6px);
  top: 50%;
  transform: translate(4px, -50%);
}
.tooltip-wrap:hover .tooltip-left,
.tooltip-wrap:focus-within .tooltip-left { transform: translate(0, -50%); }

.tooltip-right {
  left: calc(100% + 6px);
  top: 50%;
  transform: translate(-4px, -50%);
}
.tooltip-wrap:hover .tooltip-right,
.tooltip-wrap:focus-within .tooltip-right { transform: translate(0, -50%); }

/* ── Flecha (par shadcn TooltipContent Arrow: rombo rotado del color del panel) ── */
.tooltip-content::after {
  content: "";
  position: absolute;
  width: 8px;
  height: 8px;
  background: var(--color-inverse-surface);
  transform: rotate(45deg);
}
.tooltip-top::after    { bottom: -3px; left: 50%; margin-left: -4px; }
.tooltip-bottom::after { top: -3px;    left: 50%; margin-left: -4px; }
.tooltip-left::after   { right: -3px;  top: 50%;  margin-top: -4px; }
.tooltip-right::after  { left: -3px;   top: 50%;  margin-top: -4px; }

/* ── components/spinner.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Spinner
   Indicador de carga rotatorio (par shadcn Spinner).

   Cuándo usar: carga breve/indeterminada dentro de botones, inputs, celdas,
     o junto a un texto ("Guardando…").
   Cuándo no: carga con estructura conocida (usar Skeleton); progreso medible
     (usar Progress).
   Reemplaza a: spinners custom ad-hoc.

   Dependencia: variables.css
   Hereda currentColor: por defecto el aro activo usa --color-primary; dentro de
   un botón filled poné .spinner-on-primary para que contraste.

   Uso:
   <span class="spinner" role="status" aria-label="Cargando"></span>
   <button class="btn-primary"><span class="spinner spinner-sm spinner-on-primary"></span> Guardando…</button>
═══════════════════════════════════════ */

.spinner {
  display: inline-block;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  border: 2px solid var(--color-outline-variant);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  vertical-align: -3px;
  animation: spinner-rotate .7s linear infinite;
}
.spinner-sm { width: 12px; height: 12px; }
.spinner-lg { width: 24px; height: 24px; border-width: 3px; }

/* Sobre superficies de color (botón filled, snackbar) */
.spinner-on-primary { border-color: color-mix(in srgb, var(--color-on-primary) 35%, transparent); border-top-color: var(--color-on-primary); }

@keyframes spinner-rotate { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 1.6s; } }

/* ── components/button-group.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Button Group
   Botones contiguos con radios y bordes compartidos (par shadcn ButtonGroup).

   Cuándo usar: agrupar acciones relacionadas del mismo peso (paginador de
     acciones, split button, input + botón), o un addon de texto junto a un input.
   Cuándo no: elegir 1 de N con estado (usar Segmented Button / Toggle Group);
     una sola acción (usar Button).
   Reemplaza a: filas de botones pegados con overrides de radio a mano.

   Dependencia: variables.css, button.css
   Los hijos (btn-*, icon-btn, button-group-text) pierden el radio y el borde
   adyacentes para leerse como una sola unidad.

   Uso:
   <div class="button-group">
     <button class="btn-tertiary">Anterior</button>
     <button class="btn-tertiary">Siguiente</button>
   </div>
   <div class="button-group"><span class="button-group-text">https://</span>
     <input class="field-input" placeholder="tu-sitio.com"></div>
═══════════════════════════════════════ */

.button-group {
  display: inline-flex;
  width: fit-content;
  align-items: stretch;
}
.button-group > *:focus-visible { position: relative; z-index: 1; }

/* Horizontal (default) */
.button-group:not(.button-group-vertical) > *:not(:first-child) {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  border-left-width: 0;
}
.button-group:not(.button-group-vertical) > *:not(:last-child) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

/* Vertical */
.button-group-vertical { flex-direction: column; }
.button-group-vertical > *:not(:first-child) {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  border-top-width: 0;
}
.button-group-vertical > *:not(:last-child) {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

/* Addon de texto (par ButtonGroupText): superficie neutra + borde. */
.button-group-text {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--color-surface-variant);
  color: var(--color-on-surface);
  font-family: var(--font-body);
  font-size: var(--font-size-body-sm);
  font-weight: 500;
  white-space: nowrap;
}
.button-group-text svg { width: 16px; height: 16px; flex-shrink: 0; }

/* Separador entre sub-grupos. */
.button-group-separator {
  align-self: stretch;
  width: 1px;
  background: var(--color-outline-variant);
}

/* ── components/input-group.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Input Group
   Input con addons (íconos, texto, botones) dentro de un contenedor con borde
   único (par shadcn InputGroup). El foco/error viven en el contenedor, no en el
   control interno.

   Cuándo usar: input con prefijo/sufijo (íconos, unidades, botón de acción),
     o un textarea con toolbar arriba/abajo.
   Cuándo no: input simple (usar .field-input); búsqueda filtrable (Combobox).
   Reemplaza a: wrappers ad-hoc con position:absolute para íconos.

   Dependencia: variables.css, form.css, button.css

   Uso:
   <div class="input-group">
     <span class="input-group-addon"><svg>…</svg></span>
     <input class="input-group-control" placeholder="Buscar…">
     <span class="input-group-addon input-group-addon-end"><kbd class="kbd">⌘K</kbd></span>
   </div>
═══════════════════════════════════════ */

.input-group {
  display: flex;
  align-items: center;
  width: 100%;
  min-width: 0;
  height: 40px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  transition: border-color var(--duration-fast) var(--ease-default), box-shadow var(--duration-fast) var(--ease-default);
}
.input-group:focus-within {
  border-color: var(--interactive);
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.input-group[aria-invalid="true"],
.input-group.is-error {
  border-color: var(--color-error);
}
.input-group.is-error:focus-within {
  box-shadow: 0 0 0 4px var(--color-error-ring);
}

/* Control interno — sin borde ni fondo; hereda el foco del contenedor. */
.input-group-control,
.input-group .field-input {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: 0;
  background: transparent;
  outline: none;
  box-shadow: none;
  padding: 0 var(--space-3);
  font-family: inherit;
  font-size: var(--font-size-body-md);
  color: var(--text-primary);
}
.input-group-control::placeholder { color: var(--text-muted); }

/* Addon: ícono, texto o botón a los extremos. */
.input-group-addon {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding-left: var(--space-3);
  color: var(--color-on-surface-variant);
  font-size: var(--font-size-body-sm);
  white-space: nowrap;
}
.input-group-addon-end { order: 99; padding-left: 0; padding-right: var(--space-3); }
.input-group-addon svg { width: 16px; height: 16px; flex-shrink: 0; }
.input-group-addon .btn-text,
.input-group-addon .icon-btn { margin: 0 -4px; }
.input-group-text { color: var(--color-on-surface-variant); font-size: var(--font-size-body-sm); }

/* Addons apilados (block): textarea con barra arriba/abajo. */
.input-group-block { flex-direction: column; align-items: stretch; height: auto; }
.input-group-block .input-group-control { padding: var(--space-2-5) var(--space-3); }
.input-group-block .input-group-addon { width: 100%; padding: var(--space-2) var(--space-3); }
.input-group:disabled,
.input-group.is-disabled { opacity: .5; pointer-events: none; }

/* ── components/item.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Basic Card (primitivo de fila)
   Nombre en la doc: "Basic Card". Clases con prefijo .item* (paridad shadcn Item).
   Primitiva de fila de contenido flexible: media + contenido (título/descripción)
   + acciones. Es el BLOQUE BASE reutilizable de todas las cards compactas de
   dominio (Person, etc. — ver person-card.css). Más genérica que List (MD3) y que
   el panel Full Card (card.css).

   Cuándo usar: filas ricas y reutilizables (settings rows, resultados, notif con
     ícono/avatar + texto + acción), personas en grilla, listas con separadores.
   Cuándo no: tabla de datos (Table); panel con header/footer (Full Card → card.css).
   Reemplaza a: divs flex ad-hoc con ícono + texto + botón.

   Dependencia: variables.css

   Uso:
   <div class="item item-outline">
     <div class="item-media item-media-icon"><i data-lucide="bell"></i></div>
     <div class="item-content">
       <div class="item-title">Notificaciones</div>
       <div class="item-description">Recibí avisos por email.</div>
     </div>
     <div class="item-actions"><button class="btn-tertiary btn-sm">Config</button></div>
   </div>
═══════════════════════════════════════ */

.item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-size: var(--font-size-body-md);
  color: var(--text-primary);
  transition: background-color var(--duration-fast) var(--ease-default),
              border-color var(--duration-fast) var(--ease-default);
}
/* Variantes */
.item-outline { border-color: var(--border); }
.item-muted { background: color-mix(in srgb, var(--color-surface-variant) 50%, transparent); }
/* Tamaño sm */
.item-sm { gap: var(--space-2-5); padding: var(--space-3) var(--space-4); }

/* Interactiva (a.item o .item-clickable) */
a.item, .item-clickable { cursor: pointer; text-decoration: none; color: inherit; }
a.item:hover, .item-clickable:hover { background: var(--color-surface-variant); }
.item:focus-visible {
  outline: none;
  border-color: var(--color-focus);
  box-shadow: 0 0 0 4px var(--color-focus-ring);
}
.item.is-disabled { opacity: .5; pointer-events: none; }

/* Media: default (nada), icon (chip), image */
.item-media { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); flex-shrink: 0; align-self: flex-start; }
.item-media-icon {
  width: 32px; height: 32px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--color-surface-variant);
  color: var(--color-on-surface-variant);
}
.item-media-icon svg { width: 16px; height: 16px; }
.item-media-image { width: 40px; height: 40px; border-radius: var(--radius-sm); overflow: hidden; }
.item-media-image img { width: 100%; height: 100%; object-fit: cover; }

.item-content { display: flex; flex-direction: column; gap: var(--space-1); flex: 1; min-width: 0; }
/* Un segundo .item-content contiguo no crece (par shadcn [&+item-content]:flex-none) */
.item-content + .item-content { flex: none; }
.item-title { display: flex; width: fit-content; align-items: center; gap: var(--space-2); font-weight: 500; color: var(--text-primary); line-height: var(--line-height-title); }
.item-description {
  color: var(--text-secondary);
  font-size: var(--font-size-body-sm);
  line-height: var(--line-height-body-sm);
  /* clamp a 2 líneas, como shadcn */
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.item-description > a { text-decoration: underline; text-underline-offset: 4px; }
.item-description > a:hover { color: var(--color-primary); }
.item-actions { display: inline-flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }
.item-header, .item-footer { display: flex; flex-basis: 100%; align-items: center; justify-content: space-between; gap: var(--space-2); }

/* Grupo + separador */
.item-group { display: flex; flex-direction: column; }
.item-separator { height: 1px; border: 0; margin: 0; background: var(--border); }

/* ── components/attachment.css ── */
/* ═══════════════════════════════════════
   Embassy DS — Attachment
   Fila de archivo adjunto: ícono/miniatura + nombre + metadata + acción/estado
   (par shadcn Attachment). Se apilan en una .attachment-list.

   Cuándo usar: listar archivos subidos o por subir (CV de candidatos, adjuntos
     de un mensaje), con progreso o botón de quitar.
   Cuándo no: subir el archivo (usar <input type="file">, ver Input); galería de
     imágenes (Carousel).
   Reemplaza a: filas de archivo custom ad-hoc.

   Dependencia: variables.css, button.css (para el icon-btn de quitar)

   Uso:
   <ul class="attachment-list">
     <li class="attachment">
       <span class="attachment-icon"><i data-lucide="file-text"></i></span>
       <div class="attachment-body">
         <div class="attachment-name">CV_Ana_Torres.pdf</div>
         <div class="attachment-meta">240 KB · PDF</div>
       </div>
       <button class="icon-btn btn-sm attachment-remove" aria-label="Quitar">✕</button>
     </li>
   </ul>
═══════════════════════════════════════ */

.attachment-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}

.attachment {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2-5) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card-bg);
}

.attachment-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--color-surface-variant);
  color: var(--color-on-surface-variant);
}
.attachment-icon svg { width: 18px; height: 18px; }
/* Miniatura de imagen en vez de ícono */
.attachment-icon-image { overflow: hidden; }
.attachment-icon-image img { width: 100%; height: 100%; object-fit: cover; }

.attachment-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-0-5); }
.attachment-name {
  font-size: var(--font-size-body-sm);
  font-weight: 500;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.attachment-meta { font-size: var(--font-size-caption); color: var(--text-muted); }
.attachment-remove { flex-shrink: 0; }

/* Estado subiendo — barra de progreso bajo el nombre. */
.attachment-progress {
  height: 4px;
  margin-top: 6px;
  border-radius: var(--radius-full);
  background: var(--color-surface-variant);
  overflow: hidden;
}
.attachment-progress-bar {
  height: 100%;
  border-radius: inherit;
  background: var(--color-primary);
  transition: width var(--duration-normal) var(--ease-default);
}

/* Estado error */
.attachment-error { border-color: var(--color-error); }
.attachment-error .attachment-icon { background: var(--color-error-container); color: var(--color-on-error-container); }


/* El bloque de punteros gruesos va acá y no en base.css: el orden de carga es
   variables → brand → base → layout → components, así que una regla en base
   queda pisada por layout.css, que es donde viven .nav-item y .shell-menu-btn.
   Lo descubrimos midiendo: con el bloque en base, .topbar-btn llegaba a 44px y
   .nav-item se quedaba en 36. */
/* ─────────────────────────────────────────────────────────────
   Punteros gruesos — WCAG 2.5.5 / guidelines/accessibility.md / GOVERNANCE §7.2

   Los controles miden 36px y nada los llevaba a 44px: no existía ningún bloque
   que lo aplicara, así que la regla estaba escrita en las guidelines y en
   ninguna hoja de estilos.

   (pointer: coarse) además del ancho, porque una tablet o una pantalla táctil
   grande necesitan el target igual y el ancho solo no las detecta.
   ───────────────────────────────────────────────────────────── */
@media (max-width: 768px), (pointer: coarse) {
  /* min-height y no padding: el padding calculado depende del line-height y
     quedaba en 42px. */
  .nav-item { min-height: 44px; }
  .icon-btn,
  .topbar-notif,
  .topbar-btn,
  .shell-menu-btn { width: 44px; height: 44px; }
  .btn-primary, .btn-secondary, .btn-tertiary, .btn-elevated, .btn-text,
  .toolbar-btn, .seg-btn { min-height: 44px; }
}
