/* ==========================================================================
   BASE — reset + tokens del sistema de diseño
   Implementa DISENO.md §2 (color), §3 (tokens) y §4 (componentes comunes).

   Se carga SIEMPRE y primero. panel.css, showroom.css y marketing.css
   asumen que todo lo de acá ya existe y no vuelven a declarar tokens.

   Reglas que este archivo hace cumplir:
   - Cero @import y cero fuentes externas (§3.1): pila del sistema.
   - Ningún texto por debajo de 13px (.8125rem) en ningún tramo.
   - Área táctil 44x44 en todo control, vía ::after invisible (§4.2).
   - Nunca `outline: none` sin reemplazo (§4.1).
   - El body no hace scroll horizontal jamás (§7 de la auditoría).
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Reset mínimo
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  tab-size: 4;
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd, ul, ol {
  margin: 0;
}

ul[class], ol[class] { list-style: none; padding: 0; }

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

img { height: auto; }

input, button, textarea, select {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
}

button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  /* La mayoría de los controles llevan un ::after de 44x44 (§4.2) y eso
     necesita un contenedor posicionado. Se pone acá y no en .btn para que
     también sirva a los botones sueltos de las barras del showroom. */
  position: relative;
}

button:disabled,
[aria-disabled="true"] { cursor: not-allowed; }

a { color: inherit; }

table { border-collapse: collapse; border-spacing: 0; }

[hidden] { display: none !important; }

/* Sin scroll horizontal, nunca. `clip` en vez de `hidden` donde exista:
   `hidden` convierte al elemento en contenedor de scroll y rompe
   position: sticky del encabezado pegajoso de las tablas del panel (§4.7). */
html, body { overflow-x: hidden; }
@supports (overflow: clip) {
  html, body { overflow-x: clip; }
}

/* --------------------------------------------------------------------------
   2. Tokens — tipografía, espacio, radio, elevación, movimiento (§3)
   -------------------------------------------------------------------------- */

:root {
  /* Breakpoints. Documentales: las @media usan el literal porque las
     custom properties no se pueden evaluar dentro de una consulta. */
  --bp-compacto: 380px;
  --bp-movil:    640px;
  --bp-tablet:   1024px;

  /* 3.1 Tipografía — pila del sistema, cero fuentes externas */
  --fuente: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
            "Helvetica Neue", Arial, sans-serif;
  --fuente-num: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;

  --t-display: clamp(2rem, 1.3rem + 3.2vw, 3.5rem);
  --t-h1:      clamp(1.5rem, 1.2rem + 1.4vw, 2rem);
  --t-h2:      clamp(1.25rem, 1.1rem + .7vw, 1.5rem);
  --t-h3:      1.125rem;
  --t-base:    1rem;
  --t-sm:      .875rem;
  --t-xs:      .8125rem;   /* piso absoluto del sistema: 13px */

  --lh-display: 1.05;
  --lh-h1: 1.15;
  --lh-h2: 1.2;
  --lh-h3: 1.3;
  --lh-base: 1.55;
  --lh-sm: 1.5;
  --lh-xs: 1.45;

  /* Pesos: 400 cuerpo · 500 énfasis · 600 títulos y botones · 700 cifras.
     Nada de 300: a 13px desaparece. */
  --peso-cuerpo: 400;
  --peso-enfasis: 500;
  --peso-titulo: 600;
  --peso-cifra: 700;

  /* 3.2 Espacio — base 4px, solo estos valores */
  --e-1: 4px;   --e-2: 8px;   --e-3: 12px;  --e-4: 16px;
  --e-5: 24px;  --e-6: 32px;  --e-7: 48px;  --e-8: 64px;

  /* Separación desde el borde de pantalla. La ajustan los tramos en
     showroom.css; SIEMPRE se suma env(safe-area-inset-*), nunca la reemplaza. */
  --borde-pantalla: var(--e-5);

  /* 3.3 Radio y borde */
  --r-sm: 6px;  --r-md: 10px;  --r-lg: 16px;  --r-xl: 24px;  --r-full: 9999px;
  --b-fino: 1px;
  --b-grueso: 2px;

  /* 3.4 Elevación — SOLO panel y marketing. El showroom no usa sombras. */
  --sombra-1: 0 1px 2px rgb(0 0 0 / .06), 0 1px 3px rgb(0 0 0 / .10);
  --sombra-2: 0 2px 8px rgb(0 0 0 / .08), 0 4px 16px rgb(0 0 0 / .06);
  --sombra-3: 0 8px 24px rgb(0 0 0 / .12), 0 16px 48px rgb(0 0 0 / .10);

  /* 3.5 Movimiento */
  --ms-instantaneo: 80ms;
  --ms-rapido: 160ms;
  --ms-normal: 240ms;
  --ms-lento: 450ms;

  --ease-salida:  cubic-bezier(.4, 0, 1, 1);
  --ease-entrada: cubic-bezier(0, 0, .2, 1);
  --ease-ambos:   cubic-bezier(.4, 0, .2, 1);
  --ease-suave:   cubic-bezier(.32, .72, 0, 1);

  /* Área táctil mínima. No se toca. */
  --tactil: 44px;
}

/* --------------------------------------------------------------------------
   3. Paleta del panel (§2.4) — fija, no la toca el cliente.
   Neutrales en oklch: claro y oscuro son el mismo tono con otra luminosidad.
   -------------------------------------------------------------------------- */

:root {
  --n-0:   oklch(99%  0.002 250);
  --n-50:  oklch(97%  0.004 250);
  --n-100: oklch(94%  0.006 250);
  --n-200: oklch(89%  0.008 250);
  --n-300: oklch(80%  0.010 250);
  --n-400: oklch(66%  0.012 250);
  --n-500: oklch(54%  0.014 250);
  --n-600: oklch(44%  0.014 250);
  --n-700: oklch(35%  0.014 250);
  --n-800: oklch(26%  0.012 250);
  --n-900: oklch(18%  0.010 250);
  --n-950: oklch(13%  0.008 250);
}

/* Tema claro (default) */
:root {
  color-scheme: light;

  --sup-0: var(--n-0);      /* lienzo */
  --sup-1: var(--n-50);     /* tarjeta */
  --sup-2: var(--n-100);    /* fila alterna, input */
  --sup-3: var(--n-200);    /* hover de fila */
  --borde:        var(--n-200);
  --borde-fuerte: var(--n-300);
  --txt:   var(--n-900);
  --txt-2: var(--n-600);
  --txt-3: var(--n-500);

  --ok:    oklch(52% .14 152);
  --aviso: oklch(58% .13 75);
  --error: oklch(52% .19 26);
  --info:  oklch(52% .13 245);

  /* Fondo suave del mismo semántico, para insignias y avisos */
  --ok-suave:    color-mix(in oklab, var(--ok) 12%, transparent);
  --aviso-suave: color-mix(in oklab, var(--aviso) 14%, transparent);
  --error-suave: color-mix(in oklab, var(--error) 12%, transparent);
  --info-suave:  color-mix(in oklab, var(--info) 12%, transparent);
}

/* Tema oscuro por preferencia del sistema, salvo que el usuario haya
   forzado claro con data-tema. */
@media (prefers-color-scheme: dark) {
  :root:not([data-tema="claro"]) {
    color-scheme: dark;
    --sup-0: var(--n-950);
    --sup-1: var(--n-900);
    --sup-2: var(--n-800);
    --sup-3: var(--n-700);
    --borde:        var(--n-800);
    --borde-fuerte: var(--n-700);
    --txt:   var(--n-50);
    --txt-2: var(--n-300);
    --txt-3: var(--n-400);

    --ok:    oklch(72% .16 152);
    --aviso: oklch(78% .15 85);
    --error: oklch(70% .18 26);
    --info:  oklch(72% .14 245);

    /* En oscuro una sombra negra no eleva, ensucia: se suavizan. */
    --sombra-1: 0 1px 2px rgb(0 0 0 / .30);
    --sombra-2: 0 2px 8px rgb(0 0 0 / .34), 0 4px 16px rgb(0 0 0 / .22);
    --sombra-3: 0 8px 24px rgb(0 0 0 / .40), 0 16px 48px rgb(0 0 0 / .28);
  }
}

/* Oscuro forzado por el usuario (mismo bloque; CSS no permite reutilizar) */
:root[data-tema="oscuro"] {
  color-scheme: dark;
  --sup-0: var(--n-950);
  --sup-1: var(--n-900);
  --sup-2: var(--n-800);
  --sup-3: var(--n-700);
  --borde:        var(--n-800);
  --borde-fuerte: var(--n-700);
  --txt:   var(--n-50);
  --txt-2: var(--n-300);
  --txt-3: var(--n-400);

  --ok:    oklch(72% .16 152);
  --aviso: oklch(78% .15 85);
  --error: oklch(70% .18 26);
  --info:  oklch(72% .14 245);

  --sombra-1: 0 1px 2px rgb(0 0 0 / .30);
  --sombra-2: 0 2px 8px rgb(0 0 0 / .34), 0 4px 16px rgb(0 0 0 / .22);
  --sombra-3: 0 8px 24px rgb(0 0 0 / .40), 0 16px 48px rgb(0 0 0 / .28);
}

/* --------------------------------------------------------------------------
   4. Color configurable del cliente (§2.1 y §2.2)

   El panel escribe SOLO estas cuatro variables, en un <style> del layout
   público o en el atributo style del <html>:

       --showroom-primary        --showroom-primary-text
       --showroom-foreground     --showroom-foreground-text

   El par texto/fondo ya viene validado desde PHP (§2.2 capa 1 y 2): acá se
   confía en él y no se recalcula nada. Los defaults son los del proyecto demo
   y solo se ven si el panel no escribió nada.
   -------------------------------------------------------------------------- */

:root {
  --showroom-primary:         #4ade80;
  --showroom-primary-text:    #07210f;
  --showroom-foreground:      #14161a;
  --showroom-foreground-text: #f6f6f6;

  --p:      var(--showroom-primary);
  --p-txt:  var(--showroom-primary-text);
  --fg:     var(--showroom-foreground);
  --fg-txt: var(--showroom-foreground-text);

  /* Derivadas en oklab: en sRGB los pasos intermedios salen embarrados y el
     hover se ve como otro color en vez del mismo más oscuro (§8.2). */
  --p-hover: color-mix(in oklab, var(--p) 88%, black);
  --p-press: color-mix(in oklab, var(--p) 76%, black);
  --p-suave: color-mix(in oklab, var(--p) 12%, transparent);
  --p-borde: color-mix(in oklab, var(--p) 40%, transparent);
  --p-halo:  color-mix(in oklab, var(--p) 30%, transparent);

  --fg-2:     color-mix(in oklab, var(--fg) 94%, var(--fg-txt));
  --fg-borde: color-mix(in oklab, var(--fg) 86%, var(--fg-txt));
  --fg-txt-2: color-mix(in oklab, var(--fg-txt) 68%, var(--fg));
  --fg-txt-3: color-mix(in oklab, var(--fg-txt) 46%, var(--fg));
}

/* Navegador sin color-mix(): las derivadas quedarían inválidas en tiempo de
   cómputo y el color se perdería del todo. Degradan a algo legible. */
@supports not (color: color-mix(in oklab, white 50%, black)) {
  :root {
    --p-hover: var(--p);
    --p-press: var(--p);
    --p-suave: rgb(255 255 255 / .10);
    --p-borde: rgb(255 255 255 / .32);
    --p-halo:  rgb(255 255 255 / .28);
    --fg-2:     var(--fg);
    --fg-borde: rgb(255 255 255 / .16);
    --fg-txt-2: var(--fg-txt);
    --fg-txt-3: var(--fg-txt);
    --ok-suave:    transparent;
    --aviso-suave: transparent;
    --error-suave: transparent;
    --info-suave:  transparent;
  }
}

/* --------------------------------------------------------------------------
   5. Velos y placas (§2.3)
   Ningún texto del showroom se apoya directamente sobre la imagen: siempre
   hay velo de gradiente, placa desenfocada, o los dos.
   -------------------------------------------------------------------------- */

:root {
  --velo-abajo: linear-gradient(to top,
                  rgb(0 0 0 / .78) 0%, rgb(0 0 0 / .45) 26%, transparent 62%);
  --velo-arriba: linear-gradient(to bottom,
                  rgb(0 0 0 / .62) 0%, transparent 48%);

  --placa:        rgb(16 18 22 / .58);
  --placa-fuerte: rgb(16 18 22 / .78);
  --placa-blur:   saturate(1.4) blur(20px);
  --placa-borde:  rgb(255 255 255 / .14);

  /* Refuerzo, nunca única defensa. */
  --txt-sombra: 0 1px 3px rgb(0 0 0 / .55);

  /* Texto casi-negro de §2.1 (el que devuelve textoSobre() cuando el fondo es
     claro). Se declara como token para que ningún componente lo escriba a mano. */
  --txt-sobre-claro: #0B0E11;

  /* ESTADOS SOBRE FOTO. --ok/--aviso/--error/--info cambian de luminosidad con
     el tema del sistema, y eso es correcto en el panel: ahí viven sobre --sup-*.
     Sobre una fotografía no hay tema que seguir, y la variante clara del modo
     oscuro (oklch 70-78%) con texto blanco encima da ~2.5:1, ilegible. Estos
     cuatro NO cambian nunca y están calculados para el texto que llevan al lado:
     blanco sobre ok/error/neutro, --txt-sobre-claro sobre aviso. */
  --est-ok:     oklch(48% .15 152);
  --est-aviso:  oklch(80% .15 85);
  --est-error:  oklch(50% .20 26);
  --est-neutro: oklch(42% .014 250);
}

/* Fallback obligatorio: sin backdrop-filter la placa queda transparente e
   ilegible. Sube a .82 y el desenfoque se anula para no reservar capa. */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  :root {
    --placa:        rgb(16 18 22 / .82);
    --placa-fuerte: rgb(16 18 22 / .93);
    --placa-blur:   none;
  }
}

/* --------------------------------------------------------------------------
   6. Elementos base
   -------------------------------------------------------------------------- */

body {
  font-family: var(--fuente);
  font-size: var(--t-base);
  line-height: var(--lh-base);
  font-weight: var(--peso-cuerpo);
  color: var(--txt);
  background: var(--sup-0);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100vh;
  min-height: 100dvh;   /* dvh y no vh: la barra del navegador móvil corta */
}

h1 { font-size: var(--t-h1); line-height: var(--lh-h1); font-weight: var(--peso-titulo); }
h2 { font-size: var(--t-h2); line-height: var(--lh-h2); font-weight: var(--peso-titulo); }
h3 { font-size: var(--t-h3); line-height: var(--lh-h3); font-weight: var(--peso-titulo); }
h4, h5, h6 { font-size: var(--t-base); line-height: var(--lh-base); font-weight: var(--peso-titulo); }

small { font-size: var(--t-sm); }

code, kbd, samp, pre { font-family: var(--fuente-num); font-size: var(--t-sm); }

/* Cifras, superficies y códigos de unidad: tabulares y monoespaciadas para
   que se comparen de un vistazo en columna (§3.1). */
.num,
.cifra {
  font-family: var(--fuente-num);
  font-variant-numeric: tabular-nums;
}

hr {
  border: 0;
  border-top: var(--b-fino) solid var(--borde);
  margin: var(--e-5) 0;
}

::selection { background: var(--p-suave); }

/* --------------------------------------------------------------------------
   7. Foco (§4.1) — la regla que casi siempre se rompe
   -------------------------------------------------------------------------- */

/* `border-radius: inherit` es de DISENO §4.1 y solo debe alcanzar a los
   elementos que no tienen radio propio (un <a> suelto, una celda). Por eso
   este bloque va ANTES que .btn, .campo y los componentes del showroom: a
   igual especificidad gana el que se declara después, así ningún control
   cambia de forma al recibir el foco. No mover este bloque más abajo. */
:focus-visible {
  outline: var(--b-grueso) solid var(--p);
  outline-offset: 2px;
  border-radius: inherit;
}

/* Sobre imagen el color del cliente puede desaparecer: doble anillo. */
.s-sobre-imagen :focus-visible,
.sobre-imagen :focus-visible {
  outline: var(--b-grueso) solid #fff;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgb(0 0 0 / .6);
}

/* --------------------------------------------------------------------------
   8. Utilidades
   -------------------------------------------------------------------------- */

/* Texto para lectores de pantalla: visible al foco si es un salto de enlace */
.oculto-visual {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.saltar-al-contenido {
  position: absolute;
  top: var(--e-2);
  left: var(--e-2);
  z-index: 999;
  transform: translateY(-200%);
  background: var(--sup-0);
  color: var(--txt);
  border: var(--b-fino) solid var(--borde-fuerte);
  border-radius: var(--r-sm);
  padding: var(--e-2) var(--e-3);
  transition: transform var(--ms-rapido) var(--ease-entrada);
}
.saltar-al-contenido:focus-visible { transform: translateY(0); }

/* Contenido ancho: scrollea en SU contenedor, nunca en el body. */
.scroll-x {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
}
.scroll-y {
  overflow-y: auto;
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

.contenedor {
  width: 100%;
  max-width: 1140px;
  margin-inline: auto;
  padding-inline: var(--e-4);
}
@media (min-width: 640px) {
  .contenedor { padding-inline: var(--e-5); }
}

.pila { display: flex; flex-direction: column; gap: var(--e-3); }
.fila { display: flex; align-items: center; gap: var(--e-2); flex-wrap: wrap; }
.crece { flex: 1 1 auto; min-width: 0; }
.txt-2 { color: var(--txt-2); }
.txt-3 { color: var(--txt-3); }
.txt-sm { font-size: var(--t-sm); line-height: var(--lh-sm); }
.txt-xs { font-size: var(--t-xs); line-height: var(--lh-xs); }
.txt-centro { text-align: center; }

/* Una sola línea con puntos suspensivos, sin romper el ancho del padre. */
.una-linea {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   9. Botón (§4.2)
   Estados obligatorios: default · hover · focus-visible · active · disabled ·
   loading. Si falta uno, el componente está incompleto.
   -------------------------------------------------------------------------- */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--e-2);
  height: 40px;
  padding: 0 var(--e-4);
  border: var(--b-fino) solid transparent;
  border-radius: var(--r-md);
  background: var(--sup-2);
  color: var(--txt);
  font-size: var(--t-base);
  font-weight: var(--peso-titulo);
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--ms-instantaneo) var(--ease-ambos),
              border-color var(--ms-instantaneo) var(--ease-ambos),
              color var(--ms-instantaneo) var(--ease-ambos),
              transform var(--ms-instantaneo) var(--ease-ambos),
              opacity var(--ms-instantaneo) var(--ease-ambos);
}

.btn > svg { width: 20px; height: 20px; flex: none; }

/* Área táctil 44x44 sin agrandar el dibujo (§4.2).
   Ojo: se extiende hacia afuera, así que en barras de herramientas los
   controles van con gap >= --e-2 para que no se roben el click entre sí. */
.btn::after,
.tactil::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  min-width: var(--tactil);
  min-height: var(--tactil);
  transform: translate(-50%, -50%);
  border-radius: inherit;
}

.btn:hover { background: var(--sup-3); }
.btn:active { transform: scale(.97); }
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
  transform: none;
}

/* Variantes (§4.2). `primario`: UNA sola por vista.
   Cada variante se declara con DOS nombres dentro de :is():
   `.btn-x` (panel, admin, avance) y `.btn--x` (vistas públicas y de marketing).
   Los dos conviven en el repositorio y con un solo nombre la mitad de los
   botones del sitio se quedaba con el relleno neutro del `.btn` base. :is() no
   sube la especificidad (las dos ramas son una clase), así que el orden de
   estas reglas sigue mandando igual que antes. */
:is(.btn-primario, .btn--primario) {
  background: var(--p);
  color: var(--p-txt);
  border-color: transparent;
}
:is(.btn-primario, .btn--primario):hover { background: var(--p-hover); }
:is(.btn-primario, .btn--primario):active { background: var(--p-press); }

:is(.btn-secundario, .btn--secundario) {
  background: var(--p-suave);
  color: var(--p);
  border-color: var(--p-borde);
}
:is(.btn-secundario, .btn--secundario):hover { background: color-mix(in oklab, var(--p) 20%, transparent); }

:is(.btn-fantasma, .btn--fantasma) {
  background: transparent;
  color: var(--txt-2);
  border-color: transparent;
}
:is(.btn-fantasma, .btn--fantasma):hover { background: var(--sup-2); color: var(--txt); }

:is(.btn-destructivo, .btn--destructivo) {
  background: var(--error);
  color: #fff;
  border-color: transparent;
}
:is(.btn-destructivo, .btn--destructivo):hover { background: color-mix(in oklab, var(--error) 88%, black); }

/* `flotante` es el botón sobre imagen: placa + blur, sin sombra (§3.4). */
:is(.btn-flotante, .btn--flotante) {
  background: var(--placa);
  color: #fff;
  border-color: var(--placa-borde);
  backdrop-filter: var(--placa-blur);
  -webkit-backdrop-filter: var(--placa-blur);
  box-shadow: none;
}
:is(.btn-flotante, .btn--flotante):hover { background: var(--placa-fuerte); }

/* Tamaños */
:is(.btn-sm, .btn--sm) { height: 32px; padding: 0 var(--e-3); font-size: var(--t-sm); }
:is(.btn-md, .btn--md) { height: 40px; padding: 0 var(--e-4); font-size: var(--t-base); }
:is(.btn-lg, .btn--lg) { height: 48px; padding: 0 var(--e-5); font-size: var(--t-base); }
:is(.btn-icono, .btn--icono) { width: 40px; padding: 0; }
:is(.btn-lg, .btn--lg):is(.btn-icono, .btn--icono) { width: 48px; }
:is(.btn-sm, .btn--sm):is(.btn-icono, .btn--icono) { width: 32px; }
:is(.btn-bloque, .btn--bloque) { display: flex; width: 100%; }

/* Loading: el botón CONSERVA su ancho. Si se encoge, el layout salta y el
   usuario pierde el puntero justo cuando confirmó algo. */
.btn[aria-busy="true"] { color: transparent; pointer-events: none; }
.btn[aria-busy="true"] > * { opacity: 0; }
.btn[aria-busy="true"]::before {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 18px; height: 18px;
  margin: -9px 0 0 -9px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  color: inherit;
  opacity: .9;
  animation: girar .9s linear infinite;
}
:is(.btn-primario, .btn--primario)[aria-busy="true"]::before { border-color: var(--p-txt); border-top-color: transparent; }
:is(.btn-flotante, .btn--flotante)[aria-busy="true"]::before,
:is(.btn-destructivo, .btn--destructivo)[aria-busy="true"]::before { border-color: #fff; border-top-color: transparent; }

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

/* --------------------------------------------------------------------------
   10. Campo de formulario (§4.6)
   Etiqueta SIEMPRE visible arriba. El placeholder es un ejemplo, no una
   etiqueta: al escribir desaparece y el usuario ya no sabe qué campo es.
   -------------------------------------------------------------------------- */

.campo { display: flex; flex-direction: column; gap: var(--e-1); }

/* Mismo doble nombre que en el botón: las vistas públicas escriben
   `campo__etiqueta` / `campo__input` / `campo__error` y el panel escribe
   `campo-etiqueta` / `campo-control` / `campo-error`. */
:is(.campo-etiqueta, .campo__etiqueta) {
  font-size: var(--t-sm);
  font-weight: var(--peso-enfasis);
  color: var(--txt);
}
.campo__opcional { font-weight: var(--peso-cuerpo); color: var(--txt-3); }
:is(.campo-etiqueta, .campo__etiqueta)[data-requerido]::after {
  content: " *";
  color: var(--error);
}

.campo-control,
.campo__input,
.campo__area,
.campo input[type="text"],
.campo input[type="email"],
.campo input[type="password"],
.campo input[type="tel"],
.campo input[type="url"],
.campo input[type="number"],
.campo input[type="date"],
.campo input[type="search"],
.campo select,
.campo textarea {
  width: 100%;
  min-height: var(--tactil);
  padding: var(--e-2) var(--e-3);
  background: var(--sup-0);
  color: var(--txt);
  border: var(--b-fino) solid var(--borde-fuerte);
  border-radius: var(--r-sm);
  font-size: var(--t-base);
  transition: border-color var(--ms-instantaneo) var(--ease-ambos),
              background var(--ms-instantaneo) var(--ease-ambos);
}
.campo textarea { min-height: 120px; resize: vertical; line-height: var(--lh-base); }
.campo input::placeholder,
.campo textarea::placeholder { color: var(--txt-3); }
.campo input:hover,
.campo select:hover,
.campo textarea:hover { border-color: var(--n-400); }

.campo input:disabled,
.campo select:disabled,
.campo textarea:disabled {
  background: var(--sup-2);
  color: var(--txt-3);
  cursor: not-allowed;
}

:is(.campo-ayuda, .campo__ayuda) { font-size: var(--t-xs); line-height: var(--lh-xs); color: var(--txt-2); }

/* Error: borde, ícono y el mensaje DEBAJO, con aria-describedby.
   El mensaje dice cómo arreglarlo, no qué se hizo mal. */
.campo [aria-invalid="true"] {
  border-color: var(--error);
  background: var(--error-suave);
}
:is(.campo-error, .campo__error) {
  display: flex;
  align-items: flex-start;
  gap: var(--e-1);
  font-size: var(--t-xs);
  line-height: var(--lh-xs);
  color: var(--error);
  font-weight: var(--peso-enfasis);
}
:is(.campo-error, .campo__error)::before {
  content: "!";
  flex: none;
  /* 18px de círculo para que el signo entre a 13px: el piso de 13 vale para
     todo lo que sea un carácter, aunque haga las veces de ícono. */
  width: 18px; height: 18px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--error);
  color: var(--sup-0);
  font-size: var(--t-xs);
  font-weight: var(--peso-cifra);
  line-height: 1;
}

/* Casilla y radio: el control se ve chico pero el objetivo mide 44. */
.campo-check {
  display: flex;
  align-items: center;
  gap: var(--e-2);
  min-height: var(--tactil);
  font-size: var(--t-sm);
}
.campo-check input { width: 20px; height: 20px; accent-color: var(--p); flex: none; }

/* Interruptor de secciones opcionales (unidades apagado por defecto) */
.interruptor {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--e-3);
  min-height: var(--tactil);
  cursor: pointer;
}
.interruptor input { position: absolute; opacity: 0; width: 44px; height: 44px; margin: 0; cursor: pointer; }
.interruptor-pista {
  width: 44px; height: 26px;
  flex: none;
  border-radius: var(--r-full);
  background: var(--n-400);
  transition: background var(--ms-rapido) var(--ease-ambos);
}
.interruptor-pista::after {
  content: "";
  display: block;
  width: 20px; height: 20px;
  margin: 3px;
  border-radius: 50%;
  background: var(--n-0);
  transition: transform var(--ms-rapido) var(--ease-ambos);
}
.interruptor input:checked + .interruptor-pista { background: var(--p); }
.interruptor input:checked + .interruptor-pista::after { transform: translateX(18px); }
.interruptor input:focus-visible + .interruptor-pista {
  outline: var(--b-grueso) solid var(--p);
  outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   11. Insignias de estado
   Ningún estado se comunica solo con color (§2.4): color + forma + texto.
   -------------------------------------------------------------------------- */

.insignia {
  display: inline-flex;
  align-items: center;
  gap: var(--e-1);
  padding: 2px var(--e-2);
  border-radius: var(--r-full);
  border: var(--b-fino) solid transparent;
  font-size: var(--t-xs);
  line-height: var(--lh-xs);
  font-weight: var(--peso-enfasis);
  white-space: nowrap;
}
.insignia::before {
  content: "";
  flex: none;
  width: 10px; height: 10px;
  border-radius: 50%;
  border: 2px solid currentColor;
}

.insignia-ok    { color: var(--ok);    background: var(--ok-suave);    border-color: var(--ok); }
.insignia-aviso { color: var(--aviso); background: var(--aviso-suave); border-color: var(--aviso); }
.insignia-error { color: var(--error); background: var(--error-suave); border-color: var(--error); }
.insignia-info  { color: var(--info);  background: var(--info-suave);  border-color: var(--info); }
.insignia-neutra{ color: var(--txt-2); background: var(--sup-2);       border-color: var(--borde-fuerte); }

/* Formas por estado de unidad: relleno / anillo punteado / diagonal / hueco */
.insignia-ok::before     { background: currentColor; }
.insignia-aviso::before  { border-style: dotted; background: transparent; }
.insignia-error::before  { background: linear-gradient(to bottom right,
                             transparent 44%, currentColor 44%, currentColor 56%, transparent 56%); }
.insignia-info::before,
.insignia-neutra::before { background: transparent; }

/* --------------------------------------------------------------------------
   12. Vacío, carga y error (§4.8)
   Son el 80% de las primeras impresiones. Acá no se improvisan.
   -------------------------------------------------------------------------- */

.estado-vacio,
.estado-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--e-3);
  text-align: center;
  padding: var(--e-7) var(--e-4);
  border: var(--b-fino) dashed var(--borde-fuerte);
  border-radius: var(--r-md);
  color: var(--txt-2);
}
.estado-vacio-figura,
.estado-error-figura {
  width: 56px; height: 56px;
  display: grid; place-items: center;
  border-radius: var(--r-full);
  background: var(--sup-2);
  color: var(--txt-3);
}
.estado-error { border-style: solid; border-color: color-mix(in oklab, var(--error) 40%, transparent); }
.estado-error-figura { background: var(--error-suave); color: var(--error); }
.estado-vacio h3, .estado-error h3 { color: var(--txt); }
.estado-vacio p, .estado-error p { max-width: 46ch; font-size: var(--t-sm); }

/* Esqueletos con la forma del contenido real, nunca un spinner a página
   completa. Retardo de 120ms: si viene de caché el parpadeo se ve peor que
   la espera. */
.esqueleto {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-sm);
  background: var(--sup-2);
  opacity: 0;
  animation: esqueleto-aparecer 1ms linear 120ms forwards;
}
.esqueleto::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg,
    transparent, color-mix(in oklab, var(--txt) 8%, transparent), transparent);
  animation: esqueleto-brillo 1.4s infinite;
}
.esqueleto-linea { height: 12px; margin-bottom: var(--e-2); }
.esqueleto-linea:nth-child(3n) { width: 72%; }
.esqueleto-bloque { height: 120px; }
.esqueleto-circulo { width: 40px; height: 40px; border-radius: 50%; }

@keyframes esqueleto-aparecer { to { opacity: 1; } }
@keyframes esqueleto-brillo { to { transform: translateX(100%); } }

/* Cargador genérico (formularios, diálogos). Nunca a página completa. */
.cargador {
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 3px solid var(--borde);
  border-top-color: var(--p);
  animation: girar .9s linear infinite;
}

/* --------------------------------------------------------------------------
   13. Notificaciones (§5)
   Abajo a la derecha en escritorio, arriba en móvil: abajo choca con el
   pulgar y con la barra de gestos. Máximo 3 apiladas.
   -------------------------------------------------------------------------- */

.notificaciones {
  position: fixed;
  z-index: 900;
  display: flex;
  flex-direction: column;
  gap: var(--e-2);
  width: min(380px, calc(100vw - var(--e-4) * 2));
  pointer-events: none;
  inset-block-start: calc(var(--e-4) + env(safe-area-inset-top));
  inset-inline: var(--e-4);
  margin-inline: auto;
}
@media (min-width: 640px) {
  .notificaciones {
    inset-block-start: auto;
    inset-block-end: calc(var(--e-5) + env(safe-area-inset-bottom));
    inset-inline-start: auto;
    inset-inline-end: calc(var(--e-5) + env(safe-area-inset-right));
    margin-inline: 0;
  }
}
.notificacion {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--e-2);
  padding: var(--e-3);
  border-radius: var(--r-md);
  border: var(--b-fino) solid var(--borde);
  background: var(--sup-1);
  color: var(--txt);
  box-shadow: var(--sombra-2);
  font-size: var(--t-sm);
  animation: notif-entrar var(--ms-normal) var(--ease-entrada);
}
.notificacion-ok    { border-inline-start: 3px solid var(--ok); }
.notificacion-error { border-inline-start: 3px solid var(--error); }
.notificacion-aviso { border-inline-start: 3px solid var(--aviso); }
.notificacion .btn { margin-inline-start: auto; }

@keyframes notif-entrar {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* Indicador discreto de guardado automático (§5) */
.guardado {
  display: inline-flex;
  align-items: center;
  gap: var(--e-1);
  font-size: var(--t-xs);
  color: var(--txt-2);
}
.guardado::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--txt-3);
}
.guardado[data-estado="guardando"]::before { background: var(--aviso); animation: latir 1s infinite; }
.guardado[data-estado="guardado"]::before  { background: var(--ok); }
.guardado[data-estado="error"]::before     { background: var(--error); }

@keyframes latir { 50% { opacity: .3; } }

/* --------------------------------------------------------------------------
   14. Movimiento reducido
   Bloque obligatorio de DISENO §3.5. Se excluye .anim-motion-libre, que es
   el proyecto con respeta_reduce_motion=false: ahí igual se cancela todo
   MOVIMIENTO y solo se conservan los fundidos de opacidad. Nunca se ignora
   del todo la preferencia del visitante (CONTRATO §5).
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html:not(.anim-reduce-suave),
  html:not(.anim-reduce-suave) *,
  html:not(.anim-reduce-suave) *::before,
  html:not(.anim-reduce-suave) *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }

  /* .anim-reduce-suave la pone animaciones.js cuando el proyecto tiene
     respeta_reduce_motion:false. Ahí NO se aplica el bloque de arriba con
     !important, porque ese archivo ya cancela todo el movimiento y deja solo
     los fundidos de opacidad, que es justo lo que pide el contrato §5.
     Lo único que se fuerza acá es que ninguna animación quede infinita ni
     larga: nadie necesita un pulso eterno cuando pidió menos movimiento. */
  html.anim-reduce-suave *,
  html.anim-reduce-suave *::before,
  html.anim-reduce-suave *::after {
    scroll-behavior: auto !important;
  }
  html.anim-reduce-suave { --anim-ms: 160ms; }
}
