/* ==========================================================================
   Stackapital — components.css · un bloque por componente del manual
   (docs/06-sistema-diseno.md). Convenciones: clase corta semántica +
   modificadores BEM (--), elementos con __. Los estados usan atributos
   ARIA/data existentes ([aria-invalid], [aria-current], .htmx-request).
   Solo consume tokens.
   ========================================================================== */

@layer components {

  /* =====================================================================
     ICONOS — el SVG inline de {% icon %} (trazo Lucide, currentColor)
     ===================================================================== */
  .icon {
    display: inline-block;
    flex: none;
    vertical-align: -0.125em; /* que case en la línea de texto */
    stroke: currentColor;
  }

  /* =====================================================================
     BOTÓN — una sola acción primaria por vista; el resto secundarias.
     Primario: albero sólido con texto tinta (9,25:1). Peligro: teja con
     blanco (5,63:1). 44 px de alto (objetivo táctil).
     ===================================================================== */
  .button {
    --button-bg: var(--surface);
    --button-fg: var(--fg);
    --button-border: var(--line-2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 44px;
    padding: var(--space-2) var(--space-5);
    border: 1px solid var(--button-border);
    border-radius: var(--radius-pill);
    background: var(--button-bg);
    color: var(--button-fg);
    font: 600 var(--text-md)/1 var(--f-sans);
    text-decoration: none;
    white-space: nowrap;
    transition: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast),
      box-shadow var(--dur-fast), translate var(--dur-fast) var(--ease-out);
  }
  .button:hover { text-decoration: none; border-color: var(--fg-3); box-shadow: var(--shadow-sm); }
  .button:active { translate: 0 1px; }

  .button--primary {
    --button-bg: var(--albero);
    --button-fg: var(--on-accent);
    --button-border: transparent;
    font-weight: 600;
  }
  .button--primary:hover { background: var(--albero-deep); border-color: transparent; color: var(--on-accent); }

  .button--secondary { --button-bg: var(--surface); --button-fg: var(--fg); }

  .button--ghost {
    --button-bg: transparent;
    --button-border: transparent;
    --button-fg: var(--fg-2);
  }
  .button--ghost:hover { background: var(--surface-2); box-shadow: none; color: var(--fg); }

  .button--danger {
    --button-bg: var(--error);
    --button-fg: var(--blanco);
    --button-border: transparent;
  }
  .button--danger:hover { background: color-mix(in srgb, var(--error) 85%, var(--tinta)); border-color: transparent; color: var(--blanco); }

  /* La destructiva que no grita (patrón ficha, docs/06 §10): texto teja
     sin relleno — borrar un borrador, revocar un acceso. Reversible o de
     poca monta: si destruye de verdad, va SÓLIDA. */
  .button--danger.button--ghost {
    --button-bg: transparent;
    --button-border: transparent;
    --button-fg: var(--error-text);
  }
  .button--danger.button--ghost:hover { background: var(--error-bg); box-shadow: none; color: var(--error-text); }

  .button--sm { min-height: 36px; padding: var(--space-1) var(--space-4); font-size: var(--text-sm); }
  .button--lg { min-height: 52px; padding: var(--space-3) var(--space-6); font-size: var(--text-lg); }

  .button:disabled, .button[aria-disabled='true'] {
    opacity: 0.5;
    pointer-events: none;
    box-shadow: none;
  }

  /* El botón del menú del shell solo existe en móvil (#87): fuera de
     ≤900 px la barra lateral ya está a la vista y el botón —único
     disparador del <dialog>— sobra. Su display NO puede vivir en
     layout.css: esa capa precede a components, así que el inline-flex de
     `.button` ganaba siempre (el orden de capa manda, da igual la
     especificidad) y la hamburguesa se veía en escritorio. Misma capa que
     `.button`, más especificidad: gana sin !important; el media query la
     devuelve en móvil. */
  .button.app-header__menu { display: none; }
  @media (max-width: 900px) {
    .button.app-header__menu { display: inline-flex; }
  }

  /* Cargando: htmx 4 marca .htmx-request en el ORIGEN de la petición. En
     enlaces y botones con hx-* el origen ES el .button (aro directo); en
     los forms —hx-post en el <form>, el caso común— la clase cae en el
     form y el aro viaja a su botón submit (#94). Nada de hx-indicator: una
     sola fuente de verdad. El bloqueo del doble envío lo declara app.js
     con hx-disable. */
  .button.htmx-request,
  form.htmx-request button[type='submit'].button {
    pointer-events: none;
    color: transparent !important;
  }
  .button--primary.htmx-request,
  form.htmx-request button[type='submit'].button--primary { background: var(--albero); }
  .button.htmx-request::after,
  form.htmx-request button[type='submit'].button::after {
    content: '';
    position: absolute;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
    border-top-color: var(--fg);
    animation: girar 0.7s linear infinite;
  }
  .button { position: relative; }
  @keyframes girar { to { transform: rotate(360deg); } }

  /* Grupo de botones: la primaria a la derecha, juntas */
  .button-group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-3);
  }

  /* =====================================================================
     CAMPOS — anatomía .field: label + control + ayuda + error. El
     formulario entero es .form (una columna de fields) y los grupos
     lógicos <fieldset class="fieldset">.
     ===================================================================== */
  .field {
    display: grid;
    gap: var(--space-2);
    align-content: start;
  }
  .field__label, .field > label, .field > legend {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--fg-2);
    padding: 0;
  }

  /* La regla por descendencia es un fallback de POCO PESO (:where, issue
     #89): viste los widgets que no tocamos (allauth) sin ganarles a los
     ajustes que llegan después. A (0,4,1) se comía el padding-right y el
     chevron del .select, el font-size de .input--code y el alto del
     textarea; a (0,1,0) empata con las clases y pierde por orden (o por
     peso) con quien afina encima. background-color, no shorthand: un
     background aquí resetearía la background-image del chevron. */
  .input, .select, .textarea,
  .field :where(input, select, textarea):where(:not([type='checkbox']):not([type='radio']):not([type='hidden'])) {
    width: 100%;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    background-color: var(--surface);
    border: 1px solid var(--line-2);
    border-radius: var(--radius-md);
    font-size: var(--text-md);
    color: var(--fg);
    transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
  }
  .textarea, .field textarea { min-height: 120px; resize: vertical; }
  /* El chevron es un SVG autoalojado en /static (static/img/, trazo Lucide
     con la tinta horneada): la CSP de producción es default-src 'self' sin
     img-src, y un data: URI ahí muere (issue #89). Sirviéndolo del mismo
     origen pasa sin tocar la CSP; el color va en el fichero porque un SVG
     como background-image no hereda currentColor. */
  .select, .field select {
    appearance: none;
    padding-right: var(--space-7);
    background-image: url("../img/select-chevron.c21651326aac.svg");
    background-repeat: no-repeat;
    background-position: right var(--space-3) center;
  }
  .input:hover, .select:hover, .textarea:hover,
  .field :is(input, select, textarea):not([type='checkbox']):not([type='radio']):not([type='hidden']):hover { border-color: var(--fg-3); }
  /* El indicador de foco teclado es el outline global de base.css
     (:focus-visible: 2 px --albero-ink + offset 2): 5,01:1 sobre --surface
     y 4,76:1 sobre --cal — siempre ≥ 3:1, WCAG 1.4.11 (issue #92). Aquí NO
     se anula con outline:none: el borde --albero-deep (2,53:1) y el anillo
     albero al 35 % (1,24:1) no llegan a 3:1 y en forced-colors el
     box-shadow no se pinta (el outline sí: lo repinta el alto contraste).
     Borde y anillo quedan como decoración del foco de puntero. */
  .input:focus, .select:focus, .textarea:focus,
  .field :is(input, select, textarea):not([type='checkbox']):not([type='radio']):not([type='hidden']):focus {
    border-color: var(--albero-deep);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--albero) 35%, transparent);
  }
  .input::placeholder, .textarea::placeholder,
  .field :is(input, textarea)::placeholder { color: var(--fg-3); opacity: 1; }
  .input:disabled, .select:disabled, .textarea:disabled,
  .field :is(input, select, textarea):not([type='checkbox']):not([type='radio']):not([type='hidden']):disabled {
    /* color, no shorthand: el chevron del select deshabilitado sigue ahí */
    background-color: var(--bg-2);
    color: var(--fg-3);
    border-color: var(--line);
    cursor: not-allowed;
  }

  /* El error lo marca Django con aria-invalid en el control */
  .input[aria-invalid='true'], .select[aria-invalid='true'], .textarea[aria-invalid='true'],
  .field :is(input, select, textarea)[aria-invalid='true'] {
    border-color: var(--error);
  }
  .input[aria-invalid='true']:focus, .select[aria-invalid='true']:focus, .textarea[aria-invalid='true']:focus,
  .field :is(input, select, textarea)[aria-invalid='true']:focus {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 25%, transparent);
  }
  .field__help { font-size: var(--text-sm); color: var(--fg-3); }
  /* El error llega como <ul> (form renderer) o como <p> (marcado a mano) */
  .field__error {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: var(--space-1);
    font-size: var(--text-sm);
    color: var(--error-text);
    font-weight: 500;
  }
  .field__error li::before { content: '↑ '; } /* sin icono externo también se entiende */
  .field--error > :is(label, legend) { color: var(--error-text); }

  /* Subida de tickets en lote (#143): la tarjeta entera es zona de arrastre.
     app.js marca `data-encima` mientras el fichero pasa por encima; el
     contorno punteado (mismo par foco de .field: albero) dice DÓNDE cae. */
  form[data-soltar-tickets][data-encima] {
    outline: 2px dashed var(--albero-deep);
    outline-offset: var(--space-2);
    border-radius: var(--radius-md);
  }

  /* El código de un solo uso (MFA): mono, grande y centrado */
  .input--code {
    font-family: var(--f-mono);
    font-size: var(--text-xl);
    letter-spacing: 0.35em;
    text-align: center;
  }

  /* El QR de alta de la app autenticadora: blanco SIEMPRE detrás (lectura) */
  .qr {
    padding: var(--space-2);
    background: var(--blanco);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
  }

  /* Checkbox y radio: nativos acentuados en albero legible, 20 px */
  .choice {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    min-height: 44px;
    padding-block: var(--space-1);
    font-size: var(--text-md);
    cursor: pointer;
  }
  .choice input {
    width: 20px;
    height: 20px;
    flex: none;
    translate: 0 4px;
    accent-color: var(--albero-ink);
  }
  .choice__text { display: grid; gap: var(--space-1); }

  /* La variante TARJETA (el asistente de onboarding: una pregunta por
     pantalla y opciones grandes con título y explicación — docs/06 §10).
     El estado marcado lo pinta :has(:checked) al vuelo: nada de clases
     de ida y vuelta desde el servidor. */
  .choice--card {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    min-height: 0;
    padding: var(--space-4);
    border: 1px solid var(--line-2);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: border-color var(--dur-fast), background-color var(--dur-fast);
  }
  .choice--card:hover { border-color: var(--fg-3); }
  .choice--card:focus-within {
    border-color: var(--albero-deep);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--albero) 35%, transparent);
  }
  .choice--card:has(input:checked) {
    border-color: var(--albero-deep);
    background: color-mix(in srgb, var(--albero) 8%, var(--surface));
  }
  .choice--card input { translate: 0 2px; }

  .form {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    max-width: var(--container-narrow);
  }
  .form--wide { max-width: none; }
  .fieldset {
    display: grid;
    gap: var(--space-4);
    border: 0;
    padding: 0;
    margin: 0;
  }
  .fieldset__legend {
    padding: 0;
    font: 500 var(--text-xs)/1 var(--f-mono);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--fg-3);
  }
  /* Los errores de FICHA (non-field) del form */
  .form__errors {
    display: grid;
    gap: var(--space-1);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--error-border);
    border-radius: var(--radius-md);
    background: var(--error-bg);
    color: var(--error-text);
    font-size: var(--text-sm);
  }

  /* =====================================================================
     TABLA — cifras y cabeceras en mono tabular; variante densa; la fila
     que HTMX está refrescando respira (.htmx-request en el <tr>).
     ===================================================================== */
  .table-wrap {
    overflow-x: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
  }
  .table { width: 100%; font-size: var(--text-sm); }
  .table :is(th, td) {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    vertical-align: middle;
  }
  .table thead th {
    border-bottom: 1px solid var(--line-2);
    background: var(--surface);
    font: 500 var(--text-xs)/1.2 var(--f-mono);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--fg-3);
    white-space: nowrap;
  }
  .table tbody tr { border-bottom: 1px solid var(--line); }
  .table tbody tr:last-child { border-bottom: 0; }
  .table tbody tr:hover { background: color-mix(in srgb, var(--surface-2) 45%, transparent); }
  .table .num, .table td.num, .table th.num {
    font-family: var(--f-mono);
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
  }
  .table--dense :is(th, td) { padding: var(--space-2) var(--space-3); }
  .table tbody tr.htmx-request, .table tbody tr[aria-busy='true'] {
    animation: respirar 1.2s var(--ease-io) infinite;
    pointer-events: none;
  }
  @keyframes respirar { 50% { opacity: 0.55; } }

  /* Controles de edición DESNUDOS en tabla (las líneas del editor: la
     cabecera de la columna ES su label, así que no van envueltos en
     .field — docs/06 §Campos). Densos, que la tabla es de trabajo. */
  .table td :is(input, select, textarea):not([type='checkbox']):not([type='radio']):not([type='hidden']) {
    width: 100%;
    min-width: 6ch;
    min-height: 36px;
    padding: var(--space-1) var(--space-2);
    background: var(--surface);
    border: 1px solid var(--line-2);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    color: var(--fg);
  }
  /* Igual que los campos (#92): el outline de base.css es el indicador de
     foco (5,01:1 sobre --surface) y NO se anula — el anillo al 25 %
     (1,17:1) no llega a 3:1 y en forced-colors no se pinta. */
  .table td :is(input, select, textarea):not([type='checkbox']):not([type='radio']):not([type='hidden']):focus {
    border-color: var(--albero-deep);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--albero) 25%, transparent);
  }
  .table td :is(input, select, textarea)[aria-invalid='true'] { border-color: var(--error); }

  /* =====================================================================
     TARJETA — la superficie de trabajo: ficha, resumen, formulario.
     ===================================================================== */
  .card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
  }
  .card__header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-5) var(--space-6) 0;
  }
  .card__title {
    font: 500 var(--text-xl)/var(--leading-tight) var(--f-display);
    letter-spacing: -0.02em;
  }
  .card__body { padding: var(--space-5) var(--space-6) var(--space-6); }
  .card__body > :first-child { margin-top: 0; }
  .card__footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-3);
    padding: 0 var(--space-6) var(--space-6);
  }
  .card__header + .card__body { padding-top: var(--space-4); }
  .card--flat { box-shadow: none; }

  /* =====================================================================
     BADGE — etiqueta corta de estado. Texto pequeño: SIEMPRE fondo suave
     (los ratios del doc), nunca sólido.
     ===================================================================== */
  .badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-3);
    border: 1px solid var(--neutral-border);
    border-radius: var(--radius-pill);
    background: var(--neutral-bg);
    color: var(--neutral-text);
    font: 500 var(--text-xs)/1.4 var(--f-sans);
    white-space: nowrap;
  }
  .badge--success { border-color: var(--ok-border); background: var(--ok-bg); color: var(--ok-text); }
  .badge--warning { border-color: var(--warn-border); background: var(--warn-bg); color: var(--warn-text); }
  .badge--info    { border-color: var(--info-border); background: var(--info-bg); color: var(--info-text); }
  .badge--error   { border-color: var(--error-border); background: var(--error-bg); color: var(--error-text); }
  .badge--neutral { border-color: var(--neutral-border); background: var(--neutral-bg); color: var(--neutral-text); }
  .badge--mono { font-family: var(--f-mono); letter-spacing: var(--tracking-label); text-transform: uppercase; }
  .badge--outline { background: transparent; border-color: var(--line-2); color: var(--fg-3); }

  /* =====================================================================
     MINIATURA — la foto del ticket en la bandeja (y cualquier preview
     cuadrada pequeña): recorta al centro, borde y radio del sistema.
     ===================================================================== */
  .thumb {
    width: 80px;
    height: 80px;
    flex: none;
    object-fit: cover;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
  }

  /* =====================================================================
     ALERTA — mensaje con lugar fijo en la página (no desaparece solo).
     ===================================================================== */
  .alert {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--neutral-border);
    border-radius: var(--radius-md);
    background: var(--neutral-bg);
    color: var(--neutral-text);
    font-size: var(--text-sm);
  }
  .alert--success { border-color: var(--ok-border); background: var(--ok-bg); color: var(--ok-text); }
  .alert--warning { border-color: var(--warn-border); background: var(--warn-bg); color: var(--warn-text); }
  .alert--info    { border-color: var(--info-border); background: var(--info-bg); color: var(--info-text); }
  .alert--error   { border-color: var(--error-border); background: var(--error-bg); color: var(--error-text); }
  .alert--neutral { border-color: var(--neutral-border); background: var(--neutral-bg); color: var(--neutral-text); }

  /* =====================================================================
     TOAST — confirmación effímera; app.js cuelga .toast--{tipo} en
     #toasts cuando el servidor dispara HX-Trigger sm:toast. El resto de
     toasts pausa su cuenta atrás con hover o foco (#103, 2.2.1); el
     toast--error no vuela: role="alert" y el botón .toast__close.
     Las CINCO clases .toast--{tipo} son el contrato ToastTipo de
     apps/common/htmx.py (#107): el servidor aún no emite warning ni
     neutral, pero son vocabulario del contrato, no clases muertas (#137).
     ===================================================================== */
  .toasts {
    position: fixed;
    right: var(--space-5);
    bottom: var(--space-5);
    z-index: 60;
    display: grid;
    gap: var(--space-2);
    width: min(92vw, 380px);
  }
  .toast {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--neutral-border);
    border-left-width: 4px;
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow);
    color: var(--neutral-text);
    font-size: var(--text-sm);
    animation: toast-entra 0.35s var(--ease-out);
  }
  .toast--success { border-color: var(--ok-border); border-left-color: var(--ok); color: var(--ok-text); }
  .toast--warning { border-color: var(--warn-border); border-left-color: var(--warn); color: var(--warn-text); }
  .toast--info    { border-color: var(--info-border); border-left-color: var(--info); color: var(--info-text); }
  .toast--error   { border-color: var(--error-border); border-left-color: var(--error); color: var(--error-text); }
  .toast--neutral { border-color: var(--neutral-border); border-left-color: var(--neutral); color: var(--neutral-text); }
  /* El × del toast de error (#103): la capa base ya resetea el botón (sin
     borde ni fondo, color y fuente heredados) y da el foco visible; aquí
     solo el empuje al borde, una zona de golpe ≥24 px y la affordance. */
  .toast__close {
    margin-left: auto;
    padding: var(--space-1) 0 var(--space-1) var(--space-2);
    font-size: var(--text-md);
    line-height: 1;
  }
  .toast__close:hover,
  .toast__close:focus-visible { text-decoration: underline; text-underline-offset: 0.2em; }
  @keyframes toast-entra {
    from { opacity: 0; transform: translateY(8px); }
  }
  @media (prefers-reduced-motion: reduce) {
    .toast { animation: none; }
  }

  /* =====================================================================
     MODAL — <dialog> nativo. Con JS es modal de verdad: app.js promociona
     el `open` que viaja en el HTML a showModal() (#93) — centrado, fondo
     bloqueado e inerte, foco al primer campo, Escape y clic fuera cierran.
     ===================================================================== */
  .modal {
    width: min(92vw, 560px);
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
  }
  .modal::backdrop { background: var(--scrim); }
  .modal__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-5) var(--space-6) var(--space-3);
  }
  .modal__title { font: 500 var(--text-xl)/var(--leading-tight) var(--f-display); letter-spacing: -0.02em; }
  .modal__body { padding: 0 var(--space-6) var(--space-4); color: var(--fg-2); }
  .modal__footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-3);
    padding: 0 var(--space-6) var(--space-6);
  }
  .modal--wide { width: min(94vw, 880px); }

  /* El diálogo SIN JS (docs/06 §Modal): la vista lo sirve ya con el
     atributo ``open`` — página completa o fragmento HTMX — y nadie llama a
     showModal(), así que el pseudo ``:modal`` no existe todavía. En ese
     estado el propio <dialog> ES el overlay: cubre la pantalla, la tinta
     del fondo (la misma del ::backdrop) es SU background y centra su
     .modal__box (la caja con borde/radio/sombra y las secciones dentro).
     Escape no cierra diálogos NO modales: cancelar es el --ghost del pie;
     con JS, app.js lo promociona a modal y el clic en el fondo también
     (#93). El [popover] de la demo de marca usa ``open`` para otra cosa:
     fuera del overlay. */
  .modal:not(:modal)[open]:not([popover]) {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: grid;
    width: 100%;
    height: 100dvh;
    max-width: none;
    max-height: none;
    margin: 0;
    padding: var(--space-5);
    border: 0;
    border-radius: 0;
    background: var(--scrim); /* el mismo tinte del ::backdrop (#109) */
    box-shadow: none;
    overflow: auto;
  }
  .modal__box {
    position: relative;
    display: flex;
    flex-direction: column;
    margin: auto; /* centrado seguro: con contenido más alto que la pantalla, permite subir a él */
    width: min(92vw, 560px);
    max-height: calc(100dvh - 2 * var(--space-5));
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
  }
  .modal__box .modal__body { overflow-y: auto; }
  .modal--wide .modal__box { width: min(94vw, 880px); }
  /* En estado modal el propio <dialog> ES la tarjeta (.modal arriba), así que
     la caja interior se vuelve passthrough: conserva el flex y el max-height
     (el pie queda fijo y solo el cuerpo hace scroll) pero sin cromo. Sin esto
     el ancho de la caja (560) iguala al del diálogo y sus bordes dejan el
     content-box en 558: desborda 2px y el overflow:auto del diálogo pinta
     scrollbar horizontal. Sin JS el diálogo es el overlay y la caja sigue
     siendo la tarjeta (regla de arriba). */
  .modal:modal .modal__box {
    width: 100%;
    max-width: none;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }

  /* =====================================================================
     DESPLEGABLE — <details> nativo (menú de cuenta, acciones de fila).
     ===================================================================== */
  .dropdown { position: relative; display: inline-block; }
  .dropdown > summary {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    color: var(--fg-2);
    cursor: pointer;
    list-style: none;
  }
  .dropdown > summary::-webkit-details-marker { display: none; }
  .dropdown > summary:hover { background: var(--surface-2); color: var(--fg); }
  .dropdown__menu {
    position: absolute;
    right: 0;
    top: calc(100% + var(--space-1));
    z-index: 30;
    min-width: 220px;
    display: grid;
    padding: var(--space-2);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow);
  }
  .dropdown__menu a, .dropdown__menu button {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: 40px;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--fg-2);
    font-size: var(--text-sm);
    text-align: left;
    width: 100%;
  }
  .dropdown__menu a:hover, .dropdown__menu button:hover {
    background: var(--surface-2);
    color: var(--fg);
    text-decoration: none;
  }
  /* Lo destructivo no se tiñe en el menú (#137): toda baja va con su modal
     de confirmación y su .button--danger (docs/06 §10). */

  /* =====================================================================
     LISTA DE ACCIONES — filas clicables en flujo (los resultados del
     buscador del catálogo del editor de documentos). El dropdown de arriba
     es un menú POSICIONADO que se despliega; esta es la lista a pelo.
     ===================================================================== */
  .lista { display: grid; gap: var(--space-1); }
  .lista button, .lista a {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    width: 100%;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    text-align: left;
    color: var(--fg-2);
  }
  .lista button:hover, .lista a:hover { background: var(--surface-2); color: var(--fg); }
  .lista__meta { font-size: var(--text-xs); color: var(--fg-3); white-space: nowrap; }

  /* =====================================================================
     ACORDEÓN — <details> en columna (ayuda, desgloses, FAQ de ficha).
     ===================================================================== */
  .accordion { display: grid; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); }
  .accordion > details + details { border-top: 1px solid var(--line); }
  .accordion summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    min-height: 48px;
    padding: var(--space-3) var(--space-4);
    font-weight: 500;
    cursor: pointer;
    list-style: none;
  }
  .accordion summary::-webkit-details-marker { display: none; }
  .accordion summary::after {
    content: '';
    width: 12px;
    height: 12px;
    flex: none;
    border-right: 2px solid var(--fg-3);
    border-bottom: 2px solid var(--fg-3);
    transform: rotate(45deg);
    transition: transform var(--dur-fast) var(--ease-out);
  }
  .accordion details[open] summary::after { transform: rotate(225deg); }
  .accordion__body { padding: 0 var(--space-4) var(--space-4); color: var(--fg-2); font-size: var(--text-sm); }

  /* =====================================================================
     PESTAÑAS — vistas paralelas de una misma cosa (gasto: ficha/adjunto).
     ===================================================================== */
  .tabs {
    display: flex;
    gap: var(--space-1);
    border-bottom: 1px solid var(--line);
    overflow-x: auto;
  }
  .tabs a, .tabs button {
    padding: var(--space-3) var(--space-4);
    border-bottom: 2px solid transparent;
    color: var(--fg-2);
    font-weight: 500;
    font-size: var(--text-sm);
    white-space: nowrap;
  }
  .tabs a:hover, .tabs button:hover { color: var(--fg); text-decoration: none; }
  .tabs a[aria-current='page'],
  .tabs a[aria-selected='true'],
  .tabs button[aria-selected='true'] {
    color: var(--accent);
    border-bottom-color: var(--albero-deep);
  }

  /* =====================================================================
     NAVEGACIÓN LATERAL (la del shell vive en layout.css: .app-nav).
     Esta es la secundaria de página (secciones de una ficha).
     ===================================================================== */
  .side-nav { display: grid; gap: var(--space-1); }
  .side-nav a {
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    color: var(--fg-2);
    font-size: var(--text-sm);
  }
  .side-nav a:hover { background: var(--surface-2); color: var(--fg); text-decoration: none; }
  /* Activa (#91): mismo criterio que .app-nav — texto --fg sobre
     --surface-2 (13,47:1) y barra --accent como indicador no textual. */
  .side-nav a[aria-current='page'] {
    background: var(--surface-2);
    color: var(--fg);
    font-weight: 600;
    box-shadow: inset 3px 0 0 var(--accent);
  }

  /* =====================================================================
     MIGAS
     ===================================================================== */
  .breadcrumbs { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--fg-3); }
  .breadcrumbs a { color: var(--fg-3); }
  .breadcrumbs a:hover { color: var(--accent); }
  .breadcrumbs li { display: inline-flex; align-items: center; gap: var(--space-2); }
  .breadcrumbs li + li::before { content: '/'; color: var(--line-2); }
  .breadcrumbs [aria-current='page'] { color: var(--fg-2); }

  /* =====================================================================
     PAGINACIÓN
     ===================================================================== */
  .pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--space-2); }
  .pagination a, .pagination span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 40px;
    padding-inline: var(--space-3);
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    color: var(--fg-2);
    font-size: var(--text-sm);
  }
  .pagination a:hover { background: var(--surface-2); color: var(--fg); text-decoration: none; }
  .pagination [aria-current='page'] {
    background: var(--surface-2);
    color: var(--fg);
    font-weight: 600;
    border-color: var(--accent); /* #91: acento en el anillo; el texto, en tinta (AA) */
  }
  .pagination .is-disabled { opacity: 0.4; pointer-events: none; }

  /* =====================================================================
     STAT / KPI — la cifra grande del panel.
     ===================================================================== */
  .stat { display: grid; gap: var(--space-2); align-content: start; }
  .stat__label {
    font: 500 var(--text-xs)/1 var(--f-mono);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--fg-3);
  }
  .stat__value {
    font: 400 clamp(1.75rem, 1.3rem + 1.5vw, 2.5rem)/1 var(--f-display);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
  }
  .stat__value--mono { font-family: var(--f-mono); }
  .stat__value--pos { color: var(--ok-text); }
  .stat__hint { font-size: var(--text-sm); color: var(--fg-3); }

  /* =====================================================================
     BARRA DE PROGRESO — <progress> nativo (sin style= inline, CSP ok).
     ===================================================================== */
  .progress {
    width: 100%;
    height: 10px;
    appearance: none;
    border: 0;
    border-radius: var(--radius-pill);
    background: var(--surface-2);
    overflow: hidden;
  }
  .progress::-webkit-progress-bar { background: var(--surface-2); border-radius: var(--radius-pill); }
  .progress::-webkit-progress-value { background: var(--albero-deep); border-radius: var(--radius-pill); transition: width var(--dur) var(--ease-out); }
  .progress::-moz-progress-bar { background: var(--albero-deep); border-radius: var(--radius-pill); }
  /* #88: cada pseudoelemento en su regla — un selector desconocido
     (`::-moz-…` en Chrome, `::-webkit-…` en Firefox) invalida la regla
     entera, y las variantes nunca llegaban a pintarse. */
  .progress--ok::-webkit-progress-value { background: var(--ok); }
  .progress--ok::-moz-progress-bar { background: var(--ok); }
  .progress--error::-webkit-progress-value { background: var(--error); }
  .progress--error::-moz-progress-bar { background: var(--error); }

  /* =====================================================================
     ESTADO VACÍO — la primera vez en cada lista.
     ===================================================================== */
  .empty {
    display: grid;
    justify-items: center;
    gap: var(--space-3);
    padding: var(--space-8) var(--space-6);
    text-align: center;
    color: var(--fg-3);
  }
  /* En tabla el estado vacío es un <td class="empty" colspan=N>: un td con
     display:grid deja de ser celda de tabla — el colspan se ignora y el
     mensaje queda confinado a la primera columna. Como celda que sigue
     siéndolo, el apilado lo ponen los márgenes y el centrado text-align +
     margin-inline:auto en los hijos de caja. La especificidad de td.empty
     empata con .table :is(th, td) y gana por orden de fuente: el padding y
     el centrado del componente se repiten aquí a propósito. */
  td.empty {
    display: table-cell;
    vertical-align: middle;
    padding: var(--space-8) var(--space-6);
    text-align: center;
  }
  td.empty > * + * { margin-top: var(--space-3); }
  td.empty .icon,
  td.empty p { margin-inline: auto; }
  .empty .icon { width: 40px; height: 40px; opacity: 0.6; }
  .empty__title { font: 500 var(--text-lg)/var(--leading-tight) var(--f-display); color: var(--fg-2); }
  .empty p { font-size: var(--text-sm); max-width: 44ch; }

  /* =====================================================================
     INDICADOR HTMX — mientras viaja el fragmento. La espera la marca
     htmx con .htmx-request en el ORIGEN (#94): el aro del botón (arriba)
     y la fila que respira (§Tabla). Nada de skeletons ni velos de panel:
     murieron en la #137 — la #94 los dejó sin papel.
     ===================================================================== */
  .htmx-indicator { display: none; }
  .htmx-request .htmx-indicator, .htmx-request.htmx-indicator { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--fg-3); }

  /* =====================================================================
     OBJETIVO TÁCTIL 44 px EN MÓVIL (docs/06 §Accesibilidad): los controles
     DENSOS de escritorio — botones --sm de tablas y barras de herramientas,
     items de paginación y de menú, pestañas, navegación de ficha — crecen
     en pantalla pequeña. Con puntero fino se mantiene la densidad de
     trabajo. (La barra lateral del shell, .app-nav__link, crece en su
     propio bloque de móvil de layout.css.)
     ===================================================================== */
  @media (max-width: 900px) {
    .button--sm { min-height: 44px; }
    .pagination a, .pagination span { min-height: 44px; min-width: 44px; }
    .dropdown__menu a, .dropdown__menu button { min-height: 44px; }
    .tabs a, .tabs button {
      display: inline-flex;
      align-items: center;
      min-height: 44px;
    }
    .side-nav a { min-height: 44px; }
  }
}
