/* SEINGELEC · paquete generado por tools/build-assets.php · 2026-09-25 10:29
   NO editar acá: los cambios van en el archivo original y se vuelve a correr el script. */
/* ═══ css/tokens.css ═══ */
/* ============================================================
   SEINGELEC Gestión v2 · Design Tokens
   Basado en Design System v12 (Claude Design · 2026-05-26)
   Cálido · Crema dominante · Esquinas suaves
   ============================================================ */

:root {
  /* ---------- Marca ---------- */
  --brand-yellow:        #FFCF00;
  --brand-yellow-dark:   #E6BB00;
  /* Tinta de marca · para TEXTO amarillo sobre fondo claro.
     `--brand-yellow-dark` no es un amarillo oscuro: es amarillo, y da 1,76:1
     sobre el crema de las tarjetas — cuando el mínimo legible es 4,5. Medido
     el 8-sep-2026 en producción, donde se estaba usando para la línea
     «cobrado $21.668.536 · pagado $20.116.481» del panel de Finanzas y para
     el ítem activo de la barra inferior en el teléfono. Es el mismo error que
     `--brand-navy` sobre fondos oscuros, del otro lado de la rueda.
     Éste da 5,77:1 sobre crema y sigue siendo de la familia del amarillo. */
  --brand-ink:           #7A6000;
  --brand-yellow-soft:   #FFFBE6;
  --brand-yellow-300:    #FFE062;
  --brand-yellow-100:    #FFF4B8;

  --brand-navy:          #1A1A18;
  --brand-navy-medium:   #2A2C24;
  --brand-navy-soft:     #3A3B33;

  /* ---------- Neutros (light) ---------- */
  --cream-base:          #F5F5F0;
  --cream-50:            #FAFAF6;
  --cream-100:           #ECEAE5;
  --cream-200:           #E0DED7;
  --border-default:      #D4D4CF;
  --border-subtle:       #E2E0DA;
  /* ---------- Jerarquía de texto · TRES niveles reales ----------
     Problema resuelto: secondary #6B6B68 (4.83 sobre cream) y disabled
     #6F6F6C (4.61) estaban a 0,22 de contraste — el tercer nivel no
     existía en la práctica. Ahora hay separación perceptible y los tres
     niveles siguen pasando AA sobre crema (#F5F5F0):
       primary   16.1 : 1   · el dato, el título, la cifra
       secondary  6.2 : 1   · etiqueta, subtítulo, meta
       tertiary   4.5 : 1   · placeholder, deshabilitado, marca de agua
     Debajo de tertiary no hay nivel: si algo necesita ser más tenue,
     el problema es de tamaño o de posición, no de color. */
  --text-primary:        #1A1A18;
  --text-secondary:      #5A5A56;
  --text-tertiary:       #6F6F67;
  --text-disabled:       #6F6F67;
  --surface-base:        #FFFFFF;
  --surface-elevated:    #FFFFFF;
  --surface-sunken:      #ECEAE5;
  /* Cuarto nivel · lo que flota sobre una tarjeta: modal, drawer, popover,
     menú. En claro es el mismo blanco y lo que lo separa es la sombra; en
     oscuro tiene que ser más claro que la tarjeta, porque ahí la sombra no
     dice nada. */
  --surface-raised:      #FFFFFF;
  /* Borde de luz · la línea de 1 px arriba que hace que algo se vea
     levantado. En claro es casi invisible; en oscuro es LO que separa una
     tarjeta del fondo, más que la luminancia. */
  --edge-light:          inset 0 1px 0 rgba(255, 255, 255, 0.9);

  /* ---------- Semánticos ---------- */
  --success:             #276749;
  --success-soft:        #DCEFE3;
  /* Antes #B45309 contraste 4.29 sobre warning-soft — falla AA body.
     Subido a #9D470A → 5.37 PASS. */
  --warning:             #9D470A;
  --warning-soft:        #FCEBD1;
  /* Antes #C53030 contraste 4.39 — falla AA body. #A52424 → 5.85 PASS. */
  --error:               #A52424;
  --error-soft:          #FBE0E0;
  /* 18-sep-2026 · era #1565C0, el azul de Material Design: el único
     semántico que no se retocó en la v38 y el que más saltaba contra el
     crema. Los otros tres ya eran tonos de tierra (verde bosque, naranja
     quemado, ladrillo); éste era de otra paleta y se notaba en 34 lugares
     —los chips «Por iniciar», «AFP Modelo», «Fonasa»—. Ahora es un azul
     de acero profundo, del lado frío de la misma familia. */
  --info:                #27567F;
  --info-soft:           #E4EBF1;
  /* Neutro semántico · "Borrador", "Sin control", "Pendiente de emitir".
     Antes compartía el gris de badge por defecto con "Anulada", que
     significa lo contrario. Ahora borrador = tinta viva pero neutra. */
  --draft:               #4A4E58;
  --draft-soft:          #E7E9EE;
  /* Anulado / cancelado · gris ceniza con tachado semántico. Nunca rojo:
     rojo es "esto exige acción", anulado es "esto ya no existe".
     #6E6C64 daba 4.41 sobre su propio soft — falla AA. #6B6961 → 4.61 PASS. */
  --void:                #6B6961;
  --void-soft:           #EDEBE4;

  /* ---------- Velos y lavados (18-sep-2026) ----------
     Nacen de contar el codigo: habia 30 `rgba(0,0,0,.x)` sueltos repartidos
     en 20 archivos. Un negro translucido sobre fondo claro oscurece, que es
     lo que se queria; sobre fondo oscuro tambien oscurece, o sea que no
     hace NADA. Por eso el menu que se levanta y la fila que se ilumina al
     pasar el mouse desaparecian en modo oscuro.

     --velo   el fondo detras de un modal o un panel: siempre oscurece,
              en los dos temas, porque lo de atras tiene que retroceder.
     --lavado el toque que levanta una fila o una celda: aclara en oscuro
              y oscurece en claro. Es la unica forma de que "levantar" se
              vea levantado en los dos lados. */
  --velo:                rgba(20, 20, 15, .55);
  --velo-suave:          rgba(20, 20, 15, .28);
  --lavado:              rgba(0, 0, 0, .06);
  --lavado-fuerte:       rgba(0, 0, 0, .12);

  /* La tinta que va ENCIMA de un relleno de color (el navy, el verde de
     "listo", el amarillo de marca). No es `--text-primary`: ese sigue al
     tema y sobre un relleno saturado quedaria ilegible la mitad de las
     veces. Este es fijo a proposito, porque el relleno tambien lo es. */
  --tinta-sobre-color:   #FFFFFF;

  /* ---------- Paleta de datos ----------
     Croma y luminosidad emparejadas: las series se distinguen por tono,
     no porque una grite más que otra. Derivadas del amarillo de marca
     girando hacia frío, todas conviven con el crema. Uso EXCLUSIVO en
     gráficos, anillos, barras de avance y sparklines.

     Van primero en hexadecimal y recién después en OKLCH, dentro de un
     @supports. Motivo: si el navegador no entiende oklch() o color-mix(),
     la declaración que las consume queda inválida y la propiedad cae al
     valor inicial — o sea `background: transparent`. La barra no se ve
     "menos linda": DESAPARECE. Safari necesita ~16.4 para color-mix y
     ~15.4 para oklch, y en terreno hay iPads que no llegan. */
  --viz-1:    #FFC53D;                 /* marca · oro */
  --viz-2:    #FF7A45;                 /* coral */
  --viz-3:    #2AD8C6;                 /* turquesa */
  --viz-4:    #4C6FFF;                 /* azul */
  --viz-5:    #A56BFF;                 /* violeta */
  --viz-6:    #21C97A;                 /* verde */

  /* Fondo del degradado · cada serie va de su tono al de abajo. Un relleno
     plano se ve barato; el degradado corto es lo que separa un gráfico de
     tablero de uno de planilla. */
  --viz-1-deep: #F0A000;
  --viz-2-deep: #ED4E1F;
  --viz-3-deep: #00AFA3;
  --viz-4-deep: #2E4BDB;
  --viz-5-deep: #7C3FE4;
  --viz-6-deep: #0FA25F;

  /* TINTA de cada serie · para lo que es FINO: la línea de 2 px, el punto,
     el texto de la leyenda, el número sobre la barra.
     La regla que hace que esto funcione: el relleno puede brillar porque
     es una masa grande y lo lee el ojo de lejos; la tinta es la que tiene
     que pasar 4,5:1, porque es la que se lee de cerca. Medir el relleno
     contra el fondo con la vara del texto es lo que dejó la paleta
     anterior apagada — y apagada tampoco se leía mejor: viz-1 daba 1,75. */
  --viz-ink-1: #8A5A00;
  --viz-ink-2: #B23A17;
  --viz-ink-3: #00706A;
  --viz-ink-4: #2A46C4;
  --viz-ink-5: #6034C9;
  --viz-ink-6: #0C7A4A;
  --viz-grid: rgba(26, 26, 24, 0.08);
  --viz-axis: rgba(26, 26, 24, 0.42);

  /* ---------- Foco ----------
     Un solo anillo para toda la app. Doble capa: halo cálido de marca +
     contorno oscuro, para que se vea tanto sobre crema como sobre navy
     y sobre el amarillo del CTA (donde un ring amarillo desaparecería). */
  --focus-ring:
    0 0 0 2px var(--surface-elevated),
    0 0 0 4px var(--brand-navy),
    0 0 0 7px rgba(255, 207, 0, 0.45);
  --focus-ring-dark:
    0 0 0 2px var(--brand-navy),
    0 0 0 4px var(--brand-yellow),
    0 0 0 7px rgba(255, 207, 0, 0.22);

  /* ---------- Vidrio ----------
     Solo rinde donde hay contenido pasando por debajo: barras fijas,
     overlays, drawers. En filas de tabla y listas largas está prohibido
     (mata el scroll en Safari iOS y baja la legibilidad). */
  --glass-bg:      rgba(255, 255, 255, 0.72);
  --glass-blur:    saturate(180%) blur(20px);          /* @kind other */
  --glass-border:  rgba(255, 255, 255, 0.55);
  --glass-line:    inset 0 0.5px 0 rgba(255, 255, 255, 0.85);  /* @kind shadow */
  --glass-navy-bg: rgba(22, 22, 20, 0.78);

  /* ---------- Fondo del lienzo ----------
     El crema plano no le daba nada a las sombras ni al vidrio para
     apoyarse. Dos halos amplísimos (uno cálido de marca arriba a la
     izquierda, uno frío abajo a la derecha) sobre el crema base: no se
     lee como gradiente, se lee como luz de ambiente. */
  --app-bg:
    radial-gradient(1200px 680px at 8% -12%,  rgba(255, 207, 0, 0.085), transparent 62%),
    radial-gradient(1000px 700px at 104% 108%, rgba(90, 110, 140, 0.070), transparent 60%),
    var(--cream-base);

  /* ---------- Tipografía ---------- */
  --font-display: "Space Grotesk", system-ui, sans-serif;
  --font-body:    "Inter", system-ui, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, monospace;

  /* ---------- Escala tipográfica del producto ----------
     Los nombres siguen siendo el tamaño BASE en px (--text-13 = 13 de base),
     pero el valor real se multiplica por --text-scale. Un solo número mueve
     toda la tipografía del sistema sin tocar ninguna regla.

     11-ago-2026 · se subió de 1 a 1.12: en monitor grande el cuerpo de 14px
     y las etiquetas de 10-11px se leían diminutas. 1.12 deja el cuerpo en
     ~15.7px y las etiquetas en ~12, que es donde las quiere Apple en desktop.
     Para volver atrás, poner 1 acá y listo. */
  --text-scale: 1.12;

  --text-10:  calc(10px * var(--text-scale));
  --text-11:  calc(11px * var(--text-scale));
  --text-12:  calc(12px * var(--text-scale));
  --text-13:  calc(13px * var(--text-scale));
  --text-14:  calc(14px * var(--text-scale));
  --text-15:  calc(15px * var(--text-scale));
  --text-16:  calc(16px * var(--text-scale));
  --text-18:  calc(18px * var(--text-scale));
  --text-20:  calc(20px * var(--text-scale));
  --text-22:  calc(22px * var(--text-scale));
  --text-24:  calc(24px * var(--text-scale));
  --text-26:  calc(26px * var(--text-scale));
  --text-28:  calc(28px * var(--text-scale));
  --text-32:  calc(32px * var(--text-scale));
  --text-36:  calc(36px * var(--text-scale));

  /* ---------- Espaciado (base 4px) ---------- */
  --space-1:   4px;
  --space-2:   8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  /* ---------- Border radius ---------- */
  /* ---------- Iconos · grilla óptica ----------
     Tres tamaños y nada más. Un icono a 17 px se dibuja entre líneas de
     píxel y sale borroso; a 20 px cae en la grilla y queda nítido.
       sm 16 · dentro de un distintivo, de un chip, de una celda
       md 20 · el tamaño por defecto: barra lateral, botones, listas
       lg 24 · encabezados de sección y estados vacíos */
  --ico-sm: 16px;
  --ico-md: 20px;
  --ico-lg: 24px;

  --r-xs:   4px;
  --r-sm:   8px;
  --r-md:  12px;
  --r-lg:  16px;
  --r-xl:  20px;
  --r-pill: 9999px;

  /* ---------- Touch (iPad/mobile) · Apple HIG ---------- */
  --touch-min:           44px;      /* target mínimo Apple/W3C */
  --touch-comfortable:   48px;      /* recomendado uso terreno guante */
  --font-input-mobile:   16px;      /* anti-zoom Safari iOS */
  --tap-hl:              transparent; /* @kind color */

  /* ---------- Safe-area iOS (notch iPad Pro M) ---------- */
  --safe-top:    env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left:   env(safe-area-inset-left, 0px);
  --safe-right:  env(safe-area-inset-right, 0px);

  /* ---------- Breakpoints (referencia · no usables en CSS vars) ----------
     mobile:   <= 640px  · phone portrait, bottom nav
     tablet:   641-1024  · iPad Pro 11 portrait, sidebar drawer
     desktop:  >= 1025   · Mac / iPad Pro landscape, sidebar fija

     U-7 · Los cortes que EXISTEN, y no se inventan otros:
       380 · 480          teléfono chico / teléfono
       640                mobile (el de arriba)
       767 / 768          tablet chica · el corte del menú lateral
       1024 / 1025        tablet · el de arriba
       1100 · 1280 · 1440 anchos de escritorio (tablas pegajosas, padding)
     Medido el 8-sep-2026: había diez cortes distintos, y 600, 680, 720 y 820
     hacían lo mismo que 640 y 767 con veinte píxeles de diferencia. Cada
     arreglo responsive había que probarlo en diez anchos en vez de tres.
     Se plegaron a los de arriba y `cli/pruebas.php` no deja entrar uno nuevo. */
  --bp-mobile:  640px;   /* @kind other */
  --bp-tablet:  1024px;  /* @kind other */

  /* ---------- Sombras · Apple-tier multi-layer diffused ----------
     Múltiples capas con tint hacia el brand (cálido). Cada sombra suma
     una capa cercana de definición + una capa lejana ambient/diffused.
     Inset highlight emula "luz desde arriba" físico como hardware
     pulido (Apple Glass / Linear) en lugar de drop shadow plano CSS. */
  --shadow-card:
    0 1px 2px rgba(26, 26, 24, 0.04),
    0 4px 12px rgba(26, 26, 24, 0.06),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.6);
  --shadow-hover:
    0 2px 4px rgba(26, 26, 24, 0.06),
    0 12px 32px rgba(26, 26, 24, 0.10),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.7);
  --shadow-overlay:
    0 8px 16px rgba(26, 26, 24, 0.08),
    0 32px 80px rgba(26, 26, 24, 0.20),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.5);
  /* CTA primario · amarillo con halo cálido (tinted brand, no negro plano) */
  --shadow-yellow:
    0 1px 2px rgba(230, 187, 0, 0.20),
    0 4px 14px rgba(255, 207, 0, 0.32),
    0 12px 28px rgba(255, 207, 0, 0.16),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.55);
  /* Sombra "hero" para KPIs principales (Double-Bezel aware) */
  --shadow-hero:
    0 1px 2px rgba(26, 26, 24, 0.04),
    0 6px 20px rgba(26, 26, 24, 0.08),
    0 24px 56px rgba(26, 26, 24, 0.05),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.7);
  --shadow-inset:   inset 0 0 0 1px var(--border-default);
  /* Sombra mínima · chips, badges elevados, thead pegajoso */
  --shadow-sm:
    0 1px 1px rgba(26, 26, 24, 0.04),
    0 2px 6px rgba(26, 26, 24, 0.05);
  /* Vidrio · sombra difusa larga + borde de luz. Sin el inset el blur
     parece suciedad, no material. */
  --shadow-glass:
    0 1px 0 rgba(26, 26, 24, 0.05),
    0 12px 40px rgba(26, 26, 24, 0.10),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.85);

  /* ---------- Layout ---------- */
  --sidebar-width:      200px;
  --topbar-height:       48px;
  --m-header-height:     52px;
  --m-bottomnav-height:  68px;

  /* ---------- Z-index ----------
     Escala semántica completa. NO usar valores arbitrarios tipo 9999 fuera
     de esta tabla — todo elemento sticky/fixed referencia un token. */
  --z-sidebar:        100;  /* @kind other */
  --z-topbar:         100;  /* @kind other */
  /* El aviso de «datos parciales» va ENCIMA del contenido y DEBAJO de los
     overlays. Estaba en 1800 escrito a mano —por encima de todo, incluida la
     firma— y el resultado, medido el 8-sep-2026: con el aviso visible, el
     botón de cerrar de cualquier modal quedaba tapado por la barra y el clic
     lo recibía el aviso. No se podía cerrar un modal con la X.
     Un aviso informativo no tapa una interacción activa. */
  --z-banner-datos:   130;  /* @kind other */
  --z-overlay:        150;  /* @kind other */
  --z-modal:          200;  /* @kind other */
  --z-toast:          300;  /* @kind other */
  --z-search:         400;  /* @kind other */
  --z-banner-offline: 500;  /* @kind other */
  --z-pwa-install:    520;  /* @kind other */
  --z-splash:         600;  /* @kind other */
  --z-pwa-modal:      700;  /* @kind other */
  --z-confirm:        800;  /* @kind other */
  /* El salto al contenido tiene que verse por encima de la barra superior
     y de la lateral cuando toma el foco. No compite con los modales: si
     hay uno abierto, el foco está atrapado adentro y el salto no aplica. */
  --z-salto:          900;  /* @kind other */
  /* 400 búsqueda global Cmd+K · 500 banner sin conexión · 520 banner
     "Instalá SEINGELEC" · 600 boot-loader · 700 instructivo iOS ·
     800 piso de los modales de confirmación. */

  /* ⚠ De 1000 para arriba NO se escriben números a mano.
     Drawers y modales piden su capa con `Sei.ui.modals.nextZ()`, que mira lo
     que ya hay abierto y devuelve una por encima (arranca en Z_BASE = 1000).
     Un `.modal-overlay` sin z-index propio lo eleva solo un observador; si se
     le fija uno, ese mecanismo queda desactivado y el modal aparece DETRÁS del
     panel que lo abrió — que es exactamente lo que pasaba con --z-confirm: 800
     contra un drawer en 1010. `--z-confirm` marca el piso, no el techo. */

  /* ---------- Motion · Easings ----------
     Las curvas built-in de CSS (ease, ease-out) son débiles — les falta
     el "punch" que hace que las animaciones se sientan intencionales.
     Estas curvas son las que usan Apple, Linear, Vercel y Sonner. */
  /* ease-out: UI, entrada de elementos · in-out: movimiento en pantalla
     drawer: iOS-like, drawers y sheets · spring: overshoot sutil, badges */
  --ease-out:     cubic-bezier(0.23, 1, 0.32, 1);     /* @kind other */
  --ease-in-out:  cubic-bezier(0.77, 0, 0.175, 1);    /* @kind other */
  --ease-drawer:  cubic-bezier(0.32, 0.72, 0, 1);     /* @kind other */
  --ease-spring:  cubic-bezier(0.34, 1.56, 0.64, 1);  /* @kind other */

  /* ---------- Motion · Duraciones ----------
     instant: feedback de press · fast: hover y tap · base: dropdowns,
     toasts · slow: modales y drawers · page: transiciones de página. */
  --duration-instant: 100ms;   /* @kind other */
  --duration-fast:    160ms;   /* @kind other */
  --duration-base:    200ms;   /* @kind other */
  --duration-slow:    280ms;   /* @kind other */
  --duration-page:    400ms;   /* @kind other */
}

/* ============================================================
   ACCESIBILIDAD · Reduced motion
   Para usuarios con vértigo o sensibilidad vestibular: anular
   movimientos pero mantener fades/colores (esos AYUDAN a comprender).
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    /* Reducir animaciones a casi-cero pero NO a 0 (algunas dependen del
       evento animationend que sí queremos que dispare) */
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  /* Skeleton wave queda sin shimmer */
  .skeleton { animation: none !important; background: var(--cream-100) !important; }
}

/* ---------- Dark mode ---------- */
[data-theme="dark"] {
  --cream-base:        #14140F;
  --cream-50:          #1B1B16;
  --cream-100:         #1F1F1A;
  --cream-200:         #26261F;
  /* Subidos junto con las superficies: #2A2B25 contra la tarjeta nueva
     (#282820) es el mismo color, y el borde de las tarjetas desaparecía. */
  --border-default:    #47483E;
  --border-subtle:     #36372E;
  --text-primary:      #F5F5F0;
  --text-secondary:    #ADADA6;
  /* Tertiary se midió contra el fondo de página (#14140F), pero casi siempre
     vive DENTRO de una tarjeta (--surface-elevated #232319), que es más claro.
     #85857E daba 4.26 ahí — falla AA. #8A8A83 → 4.56 sobre tarjeta y 5.32
     sobre el fondo: pasa en los dos sitios. */
  /* Con la tarjeta en #282820, #8A8A83 cae a 4,16 — falla AA.
     #97968D → 4,63 sobre tarjeta y 5,93 sobre el fondo: pasa en los dos. */
  --text-tertiary:     #97968D;
  --text-disabled:     #97968D;

  --draft:             #C4CAD6;
  --draft-soft:        #2F333D;
  /* Con el soft nuevo, #8F8D84 ya no llega: #9D9B91 → 4,55 PASS. */
  /* En oscuro el velo tiene que ser MAS negro (lo de atras ya es oscuro y
     si no, no retrocede) y el lavado tiene que ACLARAR, no oscurecer. */
  --velo:              rgba(0, 0, 0, .66);
  --velo-suave:        rgba(0, 0, 0, .40);
  --lavado:            rgba(255, 255, 255, .07);
  --lavado-fuerte:     rgba(255, 255, 255, .13);

  --void:              #9D9B91;
  --void-soft:         #33332B;

  /* Los rellenos casi no cambian — un tono vivo sobre negro se ve igual de
     bien. Lo que SÍ se da vuelta es la tinta: sobre fondo claro es la
     versión profunda del tono, sobre fondo oscuro tiene que ser la clara,
     si no el trazo fino desaparece. */
  --viz-1:             #FFCE5C;
  --viz-2:             #FF8F63;
  --viz-3:             #3EE4D1;
  --viz-4:             #6C88FF;
  --viz-5:             #B98AFF;
  --viz-6:             #3ED98F;

  --viz-1-deep:        #F5B100;
  --viz-2-deep:        #F06437;
  --viz-3-deep:        #12C7B8;
  --viz-4-deep:        #4867E8;
  --viz-5-deep:        #9257F0;
  --viz-6-deep:        #17B36D;

  --viz-ink-1:         #FFDC8E;
  --viz-ink-2:         #FFB098;
  --viz-ink-3:         #6FE3D5;
  --viz-ink-4:         #A8B8FF;
  --viz-ink-5:         #D0B0FF;
  --viz-ink-6:         #7FE6B5;

  --viz-grid:          rgba(255, 255, 255, 0.10);
  --viz-axis:          rgba(255, 255, 255, 0.40);

  --focus-ring:        var(--focus-ring-dark);

  /* Vidrio en oscuro: menos opacidad no alcanza — hay que BAJAR el brillo
     del borde de luz, si no el marco blanco flota sobre el fondo negro. */
  --glass-bg:          rgba(35, 35, 25, 0.72);
  --glass-border:      rgba(255, 255, 255, 0.10);
  --glass-line:        inset 0 0.5px 0 rgba(255, 255, 255, 0.12);  /* @kind shadow */
  --glass-navy-bg:     rgba(12, 12, 10, 0.80);

  --app-bg:
    radial-gradient(1200px 680px at 8% -12%,  rgba(255, 207, 0, 0.055), transparent 62%),
    radial-gradient(1000px 700px at 104% 108%, rgba(90, 130, 190, 0.055), transparent 60%),
    var(--cream-base);
  /* Medido el 18-sep-2026 con `php tools/contraste.php`: la tarjeta
     (#232319) contra el fondo de página (#14140F) daba 1,17 — o sea que
     en oscuro las tarjetas no existían, eran manchas del mismo tono. Y
     como en oscuro la sombra no se ve, no había nada más que las separara.
     Se sube cada nivel y se agrega `--edge-light`, que es lo que de verdad
     dibuja el canto de una tarjeta sobre negro. */
  --surface-base:      #1E1E18;
  --surface-elevated:  #282820;
  --surface-sunken:    #16160F;
  --surface-raised:    #34342B;
  --edge-light:        inset 0 1px 0 rgba(255, 255, 255, 0.055);

  --brand-yellow-soft: #2A2410;
  /* Sobre fondo oscuro el amarillo pleno SÍ se lee: 11:1. */
  --brand-ink:         #FFCF00;
  --brand-yellow-100:  #36300F;

  /* Los semánticos NO se heredában en oscuro: #276749 sobre #163524 da
     1.9:1 y "Sin pendientes" o "Saldo +$249.000" quedaban ilegibles.
     Se suben a versiones claras del mismo tono: pasan AA tanto sobre el
     -soft oscuro como sobre surface-elevated. Los usos donde el color
     es FONDO con texto blanco (toasts, .btn--danger, embudo ganado)
     llevan su propio override en components.css / dashboards.css, y el
     documento imprimible se re-ancla a los valores de papel en
     informes.css — la hoja siempre es blanca. */
  --success:           #5FCB8E;
  --warning:           #EFAF56;
  --error:             #F28A8A;
  --info:              #74B0F0;
  /* Medidos contra la tarjeta nueva, no contra el fondo de página: es
     donde viven. Antes daban 1,18 / 1,15 / 1,00 / 1,09 — o sea que la
     pastilla de "vencido" y la de "informativo" no se veían. */
  --success-soft:      #1C4530;
  --warning-soft:      #46330F;
  --error-soft:        #562828;
  --info-soft:         #17385C;

  /* Dark mode · sombras más profundas, inset highlight más sutil */
  --shadow-card:
    0 1px 2px rgba(0, 0, 0, 0.40),
    0 4px 12px rgba(0, 0, 0, 0.50),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.08);
  --shadow-hover:
    0 2px 4px rgba(0, 0, 0, 0.45),
    0 12px 32px rgba(0, 0, 0, 0.55),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.10);
  --shadow-overlay:
    0 8px 16px rgba(0, 0, 0, 0.50),
    0 32px 80px rgba(0, 0, 0, 0.70),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.06);
  --shadow-yellow:
    0 1px 2px rgba(230, 187, 0, 0.15),
    0 4px 14px rgba(255, 207, 0, 0.22),
    0 12px 28px rgba(255, 207, 0, 0.12),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.10);
  --shadow-hero:
    0 1px 2px rgba(0, 0, 0, 0.40),
    0 6px 20px rgba(0, 0, 0, 0.55),
    0 24px 56px rgba(0, 0, 0, 0.45),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.08);
  --shadow-sm:
    0 1px 1px rgba(0, 0, 0, 0.30),
    0 2px 6px rgba(0, 0, 0, 0.35);
  --shadow-glass:
    0 1px 0 rgba(0, 0, 0, 0.40),
    0 12px 40px rgba(0, 0, 0, 0.50),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.10);
}

/* ============================================================
   PALETA DE DATOS · versión de gama amplia
   Los hexadecimales de arriba son el piso: se ven en cualquier
   navegador. Acá se pisan con los OKLCH originales, que tienen
   croma y luminosidad emparejadas de verdad, solo donde el
   navegador los entiende. Un navegador viejo no queda sin color,
   queda con el color aproximado.
   ============================================================ */
@supports (color: oklch(0.5 0.1 200)) {
  :root {
    --viz-1: oklch(0.84 0.158 86);
    --viz-2: oklch(0.71 0.183 42);
    --viz-3: oklch(0.73 0.132 185);
    --viz-4: oklch(0.58 0.222 267);
    --viz-5: oklch(0.63 0.212 300);
    --viz-6: oklch(0.74 0.164 158);
  }
  [data-theme="dark"] {
    --viz-1: oklch(0.86 0.152 88);
    --viz-2: oklch(0.75 0.158 44);
    --viz-3: oklch(0.79 0.136 184);
    --viz-4: oklch(0.67 0.181 268);
    --viz-5: oklch(0.70 0.175 299);
    --viz-6: oklch(0.80 0.152 159);
  }
}
@supports (color: color-mix(in oklab, red 50%, transparent)) {
  :root {
    --viz-grid: color-mix(in oklab, var(--text-primary) 8%, transparent);
    --viz-axis: color-mix(in oklab, var(--text-primary) 42%, transparent);
  }
  [data-theme="dark"] {
    --viz-grid: color-mix(in oklab, #fff 10%, transparent);
    --viz-axis: color-mix(in oklab, #fff 40%, transparent);
  }
}

/* ═══ css/reset.css ═══ */
/* ═══════════════════════════════════════════════════════════
   Reset moderno — basado en Josh Comeau + ajustes Sei
   ═══════════════════════════════════════════════════════════ */

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

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

html, body {
  height: 100%;
  font-family: var(--font-body);
  font-size: var(--text-14);
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--cream-base);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: var(--tap-hl, transparent);
  /* Momentum scroll iOS · scroll fluido */
  -webkit-overflow-scrolling: touch;
  /* No bounce overscroll · evita rebote raro al hacer pull */
  overscroll-behavior-y: contain;
  /* Bloquea zoom doble-tap manteniendo pinch-zoom */
  touch-action: manipulation;
}

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

input, button, textarea, select {
  font: inherit;
  color: inherit;
  -webkit-tap-highlight-color: var(--tap-hl, transparent);
}

/* ═══ iPad / iOS · anti-zoom Safari y touch targets ═══
   Safari hace zoom automático en focus si font-size < 16px.
   Forzamos 16px en mobile/tablet pero respetamos tamaño visual
   con font-size diferente via inline si es necesario. */
@media (max-width: 1024px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  textarea,
  select {
    font-size: var(--font-input-mobile, 16px) !important;
  }
  /* Touch targets mínimos para botones e interactivos */
  button,
  [role="button"],
  a.btn,
  .btn,
  .icon-btn,
  .icon-btn-mini {
    min-height: var(--touch-min, 44px);
    min-width: var(--touch-min, 44px);
  }
  /* Excepción: botones dentro de tablas pueden ser un poco menores
     (40px) si el contexto está apretado. Lo manejamos via clase explícita. */
  .btn--sm {
    min-height: 40px;
    min-width: 40px;
  }
}

p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }

button { cursor: pointer; background: none; border: none; padding: 0; }

a { color: inherit; text-decoration: none; }

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

table { border-collapse: collapse; width: 100%; }

#root, #app { isolation: isolate; }

/* Focus visible accesible siempre · WCAG 2.4.7 (Level AA)
   Mapeado a tokens reales (antes referenciaba --brand-primary fantasma) */
:focus-visible {
  outline: 2px solid var(--brand-yellow-dark);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* Scrollbar elegante · tokens reales */
::-webkit-scrollbar         { width: 8px; height: 8px; }
::-webkit-scrollbar-track   { background: transparent; }
::-webkit-scrollbar-thumb   { background: var(--cream-200); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--border-default); }

/* Selection */
/* 16-sep-2026 · antes era --brand-yellow-soft: #FFFBE6 sobre blanco (invisible) y en
   oscuro #2A2410 con texto #1A1A18 (oscuro sobre oscuro). Amarillo pleno en los dos. */
::selection { background: var(--brand-yellow); color: #1A1A18; }
input::selection, textarea::selection { background: var(--brand-yellow); color: #1A1A18; }

/* ═══ css/components.css ═══ */
/* ============================================================
   SEINGELEC Gestión v2 · Componentes del producto
   Escala real de la app (no escala de presentación).
   Basado en Design System v12 · screens.css (clases m- y d-)
   ============================================================ */

/* ============================================================
   BOTONES
   ============================================================ */
.btn {
  font-family: var(--font-body);
  font-size: var(--text-15);
  font-weight: 600;
  border: none;
  cursor: pointer;
  padding: 10px 16px;
  border-radius: var(--r-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* Propiedades explícitas + ease-out fuerte (antes era `120ms ease` =
     transition:all con curva débil). */
  transition:
    transform var(--duration-instant) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    opacity var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
  white-space: nowrap;
  text-decoration: none;
  line-height: 1;
}
.btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }

/* Feedback de PRESIÓN universal — la regla #1 de Emil para PWA mobile.
   Sin esto los botones no se "sienten" cuando los tocás en obra. */
.btn:active {
  transform: scale(0.97);
  transition-duration: var(--duration-instant);
}

.btn--primary  { background: var(--brand-yellow); color: var(--brand-navy); box-shadow: var(--shadow-yellow); }
.btn--secondary { background: var(--surface-elevated); color: var(--text-primary); border: 1.5px solid var(--border-default); }
.btn--ghost { background: transparent; color: var(--text-primary); }
.btn--danger { background: var(--error); color: #fff; }

/* Hover states aislados de touch devices: en iPhone el `:hover` se queda
   pegado tras tap. Esta media query lo limita a mouse/trackpad reales. */
@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover   { background: var(--brand-yellow-dark); transform: translateY(-1px); }
  .btn--secondary:hover { background: var(--cream-100); }
  .btn--ghost:hover     { background: var(--cream-100); }
  .btn--danger:hover    { opacity: .9; }
}

.btn--sm  { font-size: var(--text-12); padding: 6px 10px; border-radius: var(--r-sm); }
.btn--lg  { font-size: var(--text-16); padding: 13px 22px; }
.btn--xl  { font-size: var(--text-16); padding: 16px 28px; border-radius: var(--r-lg); }
.btn--full { width: 100%; }

.btn[data-loading="true"] { opacity: .8; cursor: progress; pointer-events: none; }
.btn[data-loading="true"]::after {
  content: "";
  width: 14px; height: 14px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: var(--r-pill);
  animation: sei-spin .8s linear infinite;
}
@keyframes sei-spin { to { transform: rotate(360deg); } }

/* ============================================================
   INPUTS Y FORMULARIOS
   ============================================================ */
.field { display: flex; flex-direction: column; gap: 6px; }
.field-label { font-size: var(--text-13); font-weight: 600; color: var(--text-primary); }
.field-helper { font-size: var(--text-12); color: var(--text-secondary); }
.field-error  { font-size: var(--text-12); color: var(--error); font-weight: 500; }

.input {
  background: var(--surface-elevated);
  border: 1.5px solid var(--border-default);
  border-radius: var(--r-md);
  padding: 10px 13px;
  font-family: var(--font-body);
  font-size: var(--text-15);
  line-height: 1.4;
  color: var(--text-primary);
  width: 100%;
  outline: none;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
  min-height: 42px;
}
/* Textareas con la clase .input heredaban min-height:42px (≈1,5 líneas) y sin
   line-height propia → el texto multilínea se cortaba/ocultaba. Les damos alto
   y altura de línea cómodos. */
textarea.input {
  min-height: 88px;
  line-height: 1.5;
  resize: vertical;
}
.input::placeholder { color: var(--text-disabled); }
.input:focus {
  border-color: var(--brand-yellow-dark);
  box-shadow: 0 0 0 3px rgba(255,207,0,.18);
}
.input.error { border-color: var(--error); background: var(--error-soft); }
.input:disabled { background: var(--surface-sunken); color: var(--text-disabled); cursor: not-allowed; }

.input-group {
  position: relative;
  display: flex;
  align-items: center;
}
.input-group .input { padding-left: 38px; }
.input-group .input-icon {
  position: absolute;
  left: 12px;
  color: var(--text-secondary);
  width: 16px; height: 16px;
  stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  pointer-events: none;
}
.input-group .input-suffix {
  position: absolute;
  right: 12px;
  color: var(--text-secondary);
  cursor: pointer;
}
.input-group .input-suffix svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.stack { display: flex; flex-direction: column; gap: 14px; }

/* Select
   ------------------------------------------------------------------
   Va sobre `select` a secas, no sobre `select.input`.

   Antes vivia en `select.input` y los 19 desplegables que usan otra clase
   -`.sel` y `.inp` de los informes, `.mat-cascade__select` de Materiales,
   el de Asistencia- se dibujaban con el control de Windows: otra flecha,
   otra caja y otra letra en medio de un formulario que si sigue el
   sistema. Es de las cosas que hacian que la app "se viera vieja" sin que
   se pudiera senalar que.

   La flecha se declara dos veces -claro y oscuro/obra- porque va dentro de
   un data URI y el color no puede salir de una variable. Es feo, y es la
   unica forma de que un `<select>` tenga una flecha que siga al tema. */
select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236B6B68' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 36px;
}
[data-theme="dark"] select,
body.modo-obra select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23A8A69C' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}
/* Un select de varias filas ya muestra sus opciones: la flecha sobra. */
select[multiple], select[size]:not([size="1"]) {
  background-image: none;
  padding-right: 10px;
}

/* ============================================================
   CARDS
   ============================================================ */
.card {
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-shadow: var(--shadow-card);
  transition: box-shadow var(--duration-base) var(--ease-out);
}
.card--accent { border-top: 3px solid var(--brand-yellow); }
/* Antes border-left 3px (side-stripe = AI cliché web 2010, prohibido
   por impeccable). Reemplazado por background tint completo + borde
   tenue del color semántico. Mantiene la identidad de estado sin la
   estética de "alert box bootstrap". */
.card--warn    { background: var(--warning-soft); border-color: color-mix(in oklab, var(--warning)    22%, var(--border-default)); }
.card--error   { background: var(--error-soft);   border-color: color-mix(in oklab, var(--error)      22%, var(--border-default)); }
.card--success { background: var(--success-soft); border-color: color-mix(in oklab, var(--success)    22%, var(--border-default)); }
/* Hover solo en dispositivos con mouse real (evita estado pegado en touch) */
@media (hover: hover) and (pointer: fine) {
  .card:hover { box-shadow: var(--shadow-hover); }
}

.card__title {
  font-family: var(--font-display);
  font-size: var(--text-16);
  font-weight: 700;
  margin: 0;
  text-wrap: balance;
}
.card__meta {
  font-family: var(--font-mono);
  font-size: var(--text-11);
  color: var(--text-secondary);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ============================================================
   KPI CARDS
   ============================================================ */
.kpi {
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  box-shadow: var(--shadow-card);
}
.kpi__label { font-size: var(--text-11); color: var(--text-secondary); font-weight: 500; }
.kpi__value {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-22);
  line-height: 1;
  letter-spacing: -0.02em;
}
.kpi__delta {
  font-family: var(--font-mono);
  font-size: var(--text-11);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 2px;
}
.kpi__delta svg { width: 11px; height: 11px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* KPI--accent · upgrade visual hero: radius más generoso + shadow profunda */
.kpi--accent  {
  border-top: 3px solid var(--brand-yellow);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-hero);
}
.kpi--success { background: var(--success-soft); border-color: transparent; }
.kpi--warning { background: var(--warning-soft); border-color: transparent; }
.kpi--error   { background: var(--error-soft);   border-color: transparent; }
.kpi--info    { background: var(--info-soft);     border-color: transparent; }

/* Card hero · variante para cards destacadas del dashboard (Atención + Hoy).
   Squircle radius generoso + shadow profundo difuso · Apple-tier */
.card--hero {
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-hero);
  padding: 18px 20px;
}

/* ============================================================
   STAGGER · entrada en cascada para listas decorativas
   ============================================================
   Uso: agregar la clase .sei-stagger al CONTAINER. Los hijos
   directos van entrando con 40ms de delay entre cada uno.
   Stagger SOLO los primeros 10 ítems — del 11 en adelante
   entran sin delay (para no demorar listas largas).

   NO usar en listas con cientos de items (materiales 3700+):
   el delay acumulado sería molesto. Mantener para grids
   decorativas: KPIs, cards del dashboard, hitos de proyecto. */
.sei-stagger > * {
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out);
}
.sei-stagger > *:nth-child(1)  { transition-delay: 0ms;   }
.sei-stagger > *:nth-child(2)  { transition-delay: 40ms;  }
.sei-stagger > *:nth-child(3)  { transition-delay: 80ms;  }
.sei-stagger > *:nth-child(4)  { transition-delay: 120ms; }
.sei-stagger > *:nth-child(5)  { transition-delay: 160ms; }
.sei-stagger > *:nth-child(6)  { transition-delay: 200ms; }
.sei-stagger > *:nth-child(7)  { transition-delay: 240ms; }
.sei-stagger > *:nth-child(8)  { transition-delay: 280ms; }
.sei-stagger > *:nth-child(9)  { transition-delay: 320ms; }
.sei-stagger > *:nth-child(10) { transition-delay: 360ms; }
.sei-stagger > *:nth-child(n+11) { transition-delay: 380ms; }
@starting-style {
  .sei-stagger > * { opacity: 0; transform: translateY(6px); }
}

/* Fade-in suave sin stagger · para listas LARGAS (tabla materiales,
   filas presupuestos). Solo el wave inicial fade-in coordinado, sin
   cascada por item. */
.sei-fade-in > * {
  opacity: 1;
  transition: opacity var(--duration-base) var(--ease-out);
}
@starting-style {
  .sei-fade-in > * { opacity: 0; }
}

/* Stagger en KPIs: cuando el dashboard se monta, los 4 KPIs entran en
   cascada (50ms entre uno y otro) en vez de aparecer todos juntos.
   Sensación de "construcción" del panel. */
.kpi {
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out);
}
.kpi:nth-child(1) { transition-delay: 0ms; }
.kpi:nth-child(2) { transition-delay: 50ms; }
.kpi:nth-child(3) { transition-delay: 100ms; }
.kpi:nth-child(4) { transition-delay: 150ms; }
@starting-style {
  .kpi { opacity: 0; transform: translateY(6px); }
}

/* ============================================================
   BADGES / PILLS
   ============================================================ */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-11);
  font-weight: 600;
  padding: 3px 7px;
  border-radius: var(--r-xs);
  background: var(--cream-100);
  color: var(--text-secondary);
  white-space: nowrap;
}
.badge--success { background: var(--success-soft); color: var(--success); }
.badge--warning { background: var(--warning-soft); color: var(--warning); }
.badge--error   { background: var(--error-soft);   color: var(--error);   }
.badge--info    { background: var(--info-soft);     color: var(--info);    }
.badge--brand   { background: var(--brand-yellow-soft); color: var(--brand-navy); border: 1px solid var(--brand-yellow); }
.badge--dark    { background: var(--brand-navy); color: #fff; }
.badge--pill    { border-radius: var(--r-pill); }

.badge--dot::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: var(--r-pill);
  background: currentColor;
  flex-shrink: 0;
}

/* ============================================================
   AVATAR
   ============================================================ */
.avatar {
  border-radius: var(--r-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 700;
  color: #fff;
  text-transform: uppercase;
  flex-shrink: 0;
}
.avatar--sm  { width: 22px; height: 22px; font-size: 10px; }
.avatar--md  { width: 28px; height: 28px; font-size: 11px; }
.avatar--lg  { width: 36px; height: 36px; font-size: 13px; }
.avatar--xl  { width: 44px; height: 44px; font-size: 16px; }
.avatar--2xl { width: 56px; height: 56px; font-size: 20px; }

/* ============================================================
   TOASTS
   ============================================================ */
.toast {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--brand-navy);
  color: #fff;
  padding: 12px 16px;
  border-radius: var(--r-md);
  font-size: var(--text-13);
  max-width: 360px;
  box-shadow: var(--shadow-overlay);
  pointer-events: auto;
  /* Transition en vez de keyframes para que múltiples toasts (post-sync
     offline con N pendientes) se retargeten suavemente al interrumpirse */
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity var(--duration-slow) var(--ease-out),
    transform var(--duration-slow) var(--ease-out);
}
@starting-style {
  .toast {
    opacity: 0;
    /* Entrada desde abajo · spatial consistency con bottom-nav */
    transform: translateY(100%);
  }
}
.toast--success { background: var(--success); }
.toast--error   { background: var(--error);   }
.toast--warning { background: var(--warning); }
.toast--info    { background: var(--info);    }

/* ============================================================
   IMÁGENES — respetar EXIF orientation
   En iPhone las fotos verticales vienen con EXIF=6/8 (rotar 90/270).
   Por default los navegadores ignoran EXIF en CSS background-image,
   pero respetan image-orientation en <img>. Esta regla garantiza que
   las fotos subidas se muestren con la orientación correcta sin
   tener que pre-rotar con canvas antes del upload.
   ============================================================ */
img {
  image-orientation: from-image;
}

/* ============================================================
   MODALES
   Roles ARIA: cuando JS inserta un overlay debería agregar
   `role="dialog" aria-modal="true" aria-labelledby="modal-title-id"`.
   El componente Sei.ui.modals.formModal ya los aplica;
   los modales custom hechos a mano deben replicar el patrón.
   ============================================================ */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.4);
  z-index: var(--z-overlay);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* Padding inferior con safe-area: en iPhone el modal no queda
     pegado al home indicator */
  /* v61 · arriba también: la app instalada dibuja bajo la barra de estado. */
  padding: calc(var(--safe-top) + 8px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
  /* Backdrop fade-in suave en vez de aparecer instantáneo */
  opacity: 1;
  transition: opacity var(--duration-base) var(--ease-out);
}
@starting-style {
  .modal-overlay { opacity: 0; }
}
@media (min-width: 768px) {
  .modal-overlay { align-items: center; padding: calc(24px + var(--safe-top)) 24px calc(24px + var(--safe-bottom)); }
}
/* v61 · En pantallas táctiles (iPhone, iPad) el modal nunca es más alto que
   el hueco que deja el overlay: muchos traen `max-height:94dvh` en línea y,
   alineados abajo, se subían por encima de la barra de estado con la X. */
@media (pointer: coarse) {
  .modal-overlay > .modal { max-height: 100% !important; }
}
.modal {
  background: var(--surface-elevated);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: var(--shadow-overlay);
  border: 1px solid var(--border-subtle);
  width: 100%;
  /* Resto la safe-area-top para que el modal no se meta bajo el notch
     cuando es full-height en iPhone */
  max-height: calc(92dvh - env(safe-area-inset-top, 0px));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Transition + @starting-style en vez de keyframes:
     · Interruptible si el usuario abre/cierra rápido
     · Permite spatial correcto: en mobile entra desde abajo (bottom sheet),
       en desktop solo un sutil scale+fade desde el centro */
  opacity: 1;
  transform: translateY(0) scale(1);
  transform-origin: bottom;
  transition:
    opacity var(--duration-slow) var(--ease-out),
    transform var(--duration-slow) var(--ease-drawer);
}
@starting-style {
  .modal {
    opacity: 0;
    /* Mobile · bottom sheet · sale desde abajo de verdad */
    transform: translateY(100%);
  }
}
@media (min-width: 768px) {
  .modal {
    border-radius: var(--r-lg);
    max-width: 560px;
    max-height: 80dvh;
    transform-origin: center;
  }
  @starting-style {
    .modal {
      opacity: 0;
      /* Desktop · sutil scale + fade desde el centro */
      transform: translateY(8px) scale(0.96);
    }
  }
}
.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
/* h2 elemento dentro de modales · vía clase para que JS pueda usar
   .modal__title o <h2 class="modal__title">, ambos heredan el estilo. */
.modal__title,
h1.modal__title, h2.modal__title, h3.modal__title {
  font-family: var(--font-display);
  font-size: var(--text-18);
  font-weight: 700;
  margin: 0;
  text-wrap: balance;
}
.modal__close {
  width: 32px; height: 32px;
  border-radius: var(--r-pill);
  background: var(--cream-100);
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  flex-shrink: 0;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    transform var(--duration-instant) var(--ease-out);
}
.modal__close:active { transform: scale(0.92); }
@media (hover: hover) and (pointer: fine) {
  .modal__close:hover { background: var(--cream-200); color: var(--text-primary); }
}
.modal__close svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.modal__body {
  padding: 20px;
  overflow-y: auto;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.modal__foot {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 14px 20px;
  border-top: 1px solid var(--border-subtle);
  background: var(--surface-sunken);
  flex-shrink: 0;
}

/* Modales de dos columnas en pantalla angosta.
   `1fr` es `minmax(auto, 1fr)`, y ese `auto` no baja del ancho mínimo del
   contenido: un <select> con "PPTO-2026-005 · Instalación residencial Los
   Andes" adentro pedía 388px de columna dentro de un cuerpo de 374px, así que
   la mitad derecha del formulario —incluido el selector de Estado— quedaba
   fuera de la pantalla en el celular. `minmax(0, 1fr)` deja que la columna se
   achique por debajo de su contenido.
   Va con !important porque cada modal trae su grilla en un atributo `style`
   en línea, y una regla de clase no le gana a eso. */
@media (max-width: 640px) {
  .modal__body[style*="grid"],
  .modal__body [style*="grid-template-columns"] {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
  }
  /* Debajo de 480px dos columnas de campos ya no se pueden ni tocar ni leer:
     pasa a una sola. Los pares Fecha/Vencimiento se apilan, que en terreno con
     una mano es mejor que dos cajas de 150px. */
  @media (max-width: 480px) {
    .modal__body[style*="grid"],
    .modal__body [style*="grid-template-columns"] {
      grid-template-columns: minmax(0, 1fr) !important;
    }
  }
}

/* ============================================================
   DRAWER (panel lateral móvil)
   ============================================================ */
.drawer-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.4);
  z-index: var(--z-overlay);
  /* Backdrop fade-in al abrir */
  opacity: 1;
  transition: opacity var(--duration-base) var(--ease-out);
}
@starting-style {
  .drawer-overlay { opacity: 0; }
}
.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(100vw, 400px);
  background: var(--surface-elevated);
  box-shadow: var(--shadow-overlay);
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Curva iOS-like (Ionic) para que se sienta como una app nativa:
     acelera rápido y desacelera con peso. */
  transform: translateX(0);
  transition: transform var(--duration-slow) var(--ease-drawer);
  /* Respeta safe-area en iPhone: el header del drawer queda visible y
     el contenido scrolleable no se mete bajo el home indicator. */
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  padding-right: env(safe-area-inset-right, 0px);
}
@starting-style {
  .drawer { transform: translateX(100%); }
}
.drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.drawer__title {
  font-family: var(--font-display);
  font-size: var(--text-18);
  font-weight: 700;
  margin: 0;
  text-wrap: balance;
}
.drawer__body {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
}

/* ============================================================
   TABS
   ============================================================ */
.tabs {
  display: flex;
  /* U-7 · El patrón "pestaña con el texto cortado" apareció 5 veces y se
     parchó 3 veces localmente. Sin wrap, agregar una pestaña más a
     cualquier vista vuelve a romperlo. Se cierra acá, de una vez. */
  flex-wrap: wrap;
  gap: 2px;
  background: var(--cream-100);
  border-radius: var(--r-sm);
  padding: 3px;
}
.tab {
  /* Evita que una pestaña se comprima hasta cortar su propio texto. */
  flex: 0 0 auto;
  white-space: nowrap;
}
.tab {
  padding: 5px 12px;
  font-size: var(--text-12);
  font-weight: 500;
  border-radius: var(--r-xs);
  cursor: pointer;
  color: var(--text-secondary);
  /* Propiedades específicas + ease-out (antes era `transition: 120ms` =
     transition:all sin easing). */
  transition:
    color var(--duration-fast) var(--ease-out),
    background-color var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    transform var(--duration-instant) var(--ease-out);
  border: none;
  background: transparent;
  white-space: nowrap;
}
.tab:active { transform: scale(0.97); }
.tab--active {
  background: var(--surface-elevated);
  color: var(--text-primary);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}

/* ============================================================
   PROGRESS BAR
   ============================================================ */
.progress-bar {
  height: 4px;
  background: var(--cream-100);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.progress-bar__fill {
  /* v45 · `display:block`. Sin esto, un `<span>` de relleno es inline y
     `height:100%` no aplica: la barra sale vacía. Pasó en la tabla de
     Obras, donde una obra al 92% mostraba el riel entero en blanco. */
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--success);
  /* Antes era 300ms ease (sluggish). Ahora 250ms con ease-out fuerte =
     se siente snappier sin perder claridad */
  transition: width var(--duration-slow) var(--ease-out);
}
.progress-bar__fill--warning { background: var(--warning); }
.progress-bar__fill--error   { background: var(--error);   }
.progress-bar__fill--brand   { background: var(--brand-yellow-dark); }

/* ============================================================
   SKELETON (loading placeholder)
   ============================================================ */
.skeleton {
  background: linear-gradient(90deg, var(--cream-100) 25%, var(--cream-200) 50%, var(--cream-100) 75%);
  background-size: 200% 100%;
  /* Linear infinite es lo correcto para shimmer: con `ease` el loop se
     "acelera/desacelera" raro porque vuelve a 0% cada iteración */
  animation: skeleton-wave 1.5s linear infinite;
  border-radius: var(--r-sm);
  color: transparent !important;
  pointer-events: none;
}
@keyframes skeleton-wave {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ============================================================
   STEPPER / WIZARD
   ============================================================ */
.stepper {
  display: flex;
  align-items: center;
  gap: 0;
}
.stepper__step {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
}
.stepper__step:last-child { flex: 0; }
.stepper__dot {
  width: 28px; height: 28px;
  border-radius: var(--r-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-12);
  flex-shrink: 0;
  border: 2px solid var(--border-default);
  background: var(--surface-elevated);
  color: var(--text-secondary);
  transition:
    background-color var(--duration-base) var(--ease-out),
    border-color var(--duration-base) var(--ease-out),
    color var(--duration-base) var(--ease-out);
}
.stepper__line {
  /* También animar el fill del line cuando un paso se completa */
  transition: background-color var(--duration-base) var(--ease-out);
}
.stepper__dot--active {
  background: var(--brand-yellow);
  border-color: var(--brand-yellow);
  color: var(--brand-navy);
}
.stepper__dot--done {
  background: var(--success-soft);
  border-color: var(--success);
  color: var(--success);
}
.stepper__line {
  flex: 1;
  height: 2px;
  background: var(--border-default);
  margin: 0 4px;
}
.stepper__line--done { background: var(--success); }
.stepper__label {
  font-size: var(--text-11);
  color: var(--text-secondary);
  white-space: nowrap;
}
.stepper__label--active { color: var(--text-primary); font-weight: 600; }

/* ============================================================
   EMPTY STATE
   ============================================================ */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 48px 24px;
  gap: 12px;
  color: var(--text-secondary);
}
.empty__icon {
  width: 48px; height: 48px;
  border-radius: var(--r-lg);
  background: var(--cream-100);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-disabled);
}
.empty__icon svg { width: 24px; height: 24px; stroke: currentColor; fill: none; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.empty__title { font-family: var(--font-display); font-size: var(--text-16); font-weight: 700; color: var(--text-primary); margin: 0; text-wrap: balance; }
.empty__desc  { font-size: var(--text-13); margin: 0; max-width: 320px; text-wrap: pretty; }

/* ============================================================
   DIVIDER
   ============================================================ */
.divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--text-disabled);
  font-size: var(--text-11);
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}

/* ============================================================
   UTILIDADES DE TEXTO
   ============================================================ */
.text-mono   { font-family: var(--font-mono); }
.text-display{ font-family: var(--font-display); }
.text-muted  { color: var(--text-secondary); }
.text-disabled { color: var(--text-disabled); }
.text-success { color: var(--success); }
.text-warning { color: var(--warning); }
.text-error   { color: var(--error); }
.text-brand   { color: var(--brand-navy); }
.text-sm  { font-size: var(--text-12); }
.text-xs  { font-size: var(--text-11); }
.text-xxs { font-size: var(--text-10); }

/* Lucide SVG defaults */
svg.lu {
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ── Feedback de interacción para clickables que NO son .btn ──────────
   (cards de proyecto/OT/material, filas). El press se siente igual con
   dedo (celular en obra) y con click (notebook/escritorio); el hover-lift
   es solo desktop-mouse (gated). Emil: todo elemento presionable debe
   "responder" al toque.
   · Cards/divs → se hunden al presionar (transform scale).
   · Filas <tr> → transform no aplica a table-row, feedback por fondo.
   Va al FINAL del archivo a propósito: así su `transition` gana en orden
   sobre .card (que solo transiciona box-shadow) e incluye transform.
   Orden interno: :active DESPUÉS de :hover para que gane al presionar. */
.pressable {
  transition: transform var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-base) var(--ease-out);
}
.pressable-row {
  transition: background-color var(--duration-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .pressable:hover     { transform: translateY(-1px); }
  .pressable-row:hover { background-color: var(--cream-50); }
}
.pressable:active {
  transform: scale(0.98);
  transition-duration: var(--duration-instant);
}
.pressable-row:active {
  background-color: var(--cream-200);
}

/* ════════════════════════════════════════════════════════════
   REFINAMIENTO v3 · 2026-08-10
   Bloque aditivo: no renombra ni elimina ninguna clase anterior.
   Va al final a propósito — su cascada gana sobre las reglas de
   arriba sin necesidad de !important.
   ════════════════════════════════════════════════════════════ */

/* ────────────────────────────────────────────────────────────
   1 · FOCO VISIBLE — accesibilidad, no decoración
   Un solo anillo para toda la app. `:focus-visible` (no `:focus`)
   para que el mouse no lo dispare: solo teclado y navegación
   asistida. `:focus:not(:focus-visible)` limpia el outline que
   Safari deja en botones al tocarlos.
   ──────────────────────────────────────────────────────────── */
:where(a, button, input, select, textarea, summary, [tabindex], [role="button"],
       [role="tab"], [role="switch"], [role="checkbox"], [role="menuitem"]):focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-sm);
  position: relative;
  z-index: 1;
}
:where(a, button, input, select, textarea, summary, [tabindex]):focus:not(:focus-visible) {
  outline: none;
}
/* Elementos que ya traen su propio radio lo conservan */
.btn:focus-visible        { border-radius: var(--r-md); }
.btn--sm:focus-visible    { border-radius: var(--r-sm); }
.btn--xl:focus-visible    { border-radius: var(--r-lg); }
.input:focus-visible      { border-radius: var(--r-md); border-color: var(--brand-yellow-dark); }
.modal__close:focus-visible,
.topbar-avatar:focus-visible,
.m-bottomnav__fab:focus-visible { border-radius: var(--r-pill); }
/* Sobre el navy del menú el anillo claro no se ve: se invierte */
.app-sidebar :where(a, button, [tabindex]):focus-visible { box-shadow: var(--focus-ring-dark); }
/* El input ya tenía halo propio en :focus; se mantiene para mouse
   y se refuerza con el anillo del sistema en teclado. */
.input:focus { box-shadow: 0 0 0 3px rgba(255,207,0,.18); }
.input:focus-visible { box-shadow: 0 0 0 3px rgba(255,207,0,.18), var(--focus-ring); }

/* Salto al contenido — existe en el marcado o se agrega en un <a> suelto */
.skip-link {
  position: absolute; left: 8px; top: -60px; z-index: var(--z-toast);
  background: var(--brand-navy); color: #fff; font-weight: 600;
  padding: 10px 16px; border-radius: var(--r-md); text-decoration: none;
  transition: top var(--duration-base) var(--ease-out);
}
.skip-link:focus { top: calc(8px + var(--safe-top)); }

/* ────────────────────────────────────────────────────────────
   2 · DESHABILITADO — un estado, no un `style="opacity"` por vista
   Regla: lo deshabilitado pierde el material (sombra y relieve),
   no solo la opacidad. Bajar opacity apaga también el texto y lo
   saca de AA; esto lo deja plano pero legible.
   ──────────────────────────────────────────────────────────── */
.btn:disabled,
.btn[disabled],
.btn[aria-disabled="true"] {
  background: var(--surface-sunken);
  color: var(--text-tertiary);
  border-color: var(--border-subtle);
  box-shadow: none;
  cursor: not-allowed;
  transform: none;
  opacity: 1;
}
.btn:disabled:active, .btn[disabled]:active { transform: none; }
.btn--ghost:disabled, .btn--ghost[disabled] { background: transparent; }
@media (hover: hover) and (pointer: fine) {
  .btn:disabled:hover, .btn[disabled]:hover, .btn[aria-disabled="true"]:hover {
    background: var(--surface-sunken); transform: none;
  }
  .btn--ghost:disabled:hover { background: transparent; }
}
/* Los .btn del footer de informes (informes.css) heredan lo mismo */
select:disabled { background-image: none; }
fieldset[disabled] { border: none; margin: 0; padding: 0; }
fieldset[disabled] :where(.input, .btn, .short, .acc-rapida) {
  pointer-events: none; color: var(--text-tertiary); box-shadow: none;
}

/* ────────────────────────────────────────────────────────────
   3 · CONTROLES DE FORMULARIO
   Checkbox, radio, switch y archivo salían con el aspecto crudo
   del navegador y rompían la línea del resto. Se estilan por
   selector de atributo: no hace falta tocar el marcado.
   El objetivo táctil se consigue con un ::after invisible de 44px
   en vez de agrandar la caja (que rompería las tablas densas).
   ──────────────────────────────────────────────────────────── */
input[type="checkbox"]:not([role="switch"]),
input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  width: 18px; height: 18px;
  margin: 0; flex-shrink: 0;
  border: 1.5px solid var(--border-default);
  background: var(--surface-elevated);
  cursor: pointer;
  position: relative;
  display: inline-grid;
  place-content: center;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    transform var(--duration-instant) var(--ease-out);
  vertical-align: middle;
}
input[type="checkbox"]:not([role="switch"]) { border-radius: var(--r-xs); }
input[type="radio"] { border-radius: var(--r-pill); }
/* Objetivo táctil de 44px sin agrandar el dibujo */
@media (pointer: coarse) {
  input[type="checkbox"]:not([role="switch"])::after,
  input[type="radio"]::after {
    content: ""; position: absolute;
    left: 50%; top: 50%; transform: translate(-50%, -50%);
    width: var(--touch-min); height: var(--touch-min);
  }
}
input[type="checkbox"]:not([role="switch"]):active,
input[type="radio"]:active { transform: scale(0.92); }
input[type="checkbox"]:not([role="switch"]):checked,
input[type="radio"]:checked {
  background: var(--brand-yellow);
  border-color: var(--brand-yellow-dark);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.55), 0 1px 3px rgba(230,187,0,.35);
}
/* La marca se dibuja con clip-path: escala nítida a cualquier DPI y
   hereda el navy sin depender de un SVG embebido en base64. */
input[type="checkbox"]:not([role="switch"])::before {
  content: ""; width: 10px; height: 10px;
  background: var(--brand-navy);
  clip-path: polygon(14% 46%, 0 60%, 39% 100%, 100% 22%, 86% 8%, 38% 71%);
  transform: scale(0);
  transition: transform var(--duration-base) var(--ease-spring);
}
input[type="checkbox"]:not([role="switch"]):checked::before { transform: scale(1); }
input[type="checkbox"]:not([role="switch"]):indeterminate {
  background: var(--brand-yellow); border-color: var(--brand-yellow-dark);
}
input[type="checkbox"]:not([role="switch"]):indeterminate::before {
  clip-path: inset(42% 8% 42% 8% round 1px); transform: scale(1);
}
input[type="radio"]::before {
  content: ""; width: 8px; height: 8px; border-radius: var(--r-pill);
  background: var(--brand-navy);
  transform: scale(0);
  transition: transform var(--duration-base) var(--ease-spring);
}
input[type="radio"]:checked::before { transform: scale(1); }
input[type="checkbox"]:disabled, input[type="radio"]:disabled {
  background: var(--surface-sunken); border-color: var(--border-subtle);
  cursor: not-allowed;
}
input[type="checkbox"]:disabled::before, input[type="radio"]:disabled::before {
  background: var(--text-tertiary);
}
@media (hover: hover) and (pointer: fine) {
  input[type="checkbox"]:not(:disabled):hover,
  input[type="radio"]:not(:disabled):hover { border-color: var(--brand-yellow-dark); }
}

/* Switch · para <input type="checkbox" role="switch"> y para .switch */
input[type="checkbox"][role="switch"], .switch {
  appearance: none; -webkit-appearance: none;
  width: 44px; height: 26px; border-radius: var(--r-pill);
  background: var(--cream-200);
  border: none; margin: 0; padding: 0; flex-shrink: 0;
  position: relative; cursor: pointer;
  box-shadow: inset 0 1px 3px rgba(26,26,24,.12);
  transition: background-color var(--duration-base) var(--ease-out);
}
input[type="checkbox"][role="switch"]::before, .switch::before {
  content: ""; position: absolute;
  top: 3px; left: 3px; width: 20px; height: 20px;
  border-radius: var(--r-pill); background: #fff;
  box-shadow: var(--shadow-sm);
  /* La curva drawer (iOS) hace que el pulgar se sienta con peso */
  transition: transform var(--duration-base) var(--ease-drawer),
              width var(--duration-instant) var(--ease-out);
}
input[type="checkbox"][role="switch"]:checked, .switch[aria-checked="true"] {
  background: var(--brand-yellow); box-shadow: inset 0 1px 3px rgba(160,128,0,.22);
}
input[type="checkbox"][role="switch"]:checked::before,
.switch[aria-checked="true"]::before { transform: translateX(18px); }
/* El pulgar se estira al mantener presionado — detalle físico de iOS */
input[type="checkbox"][role="switch"]:active::before, .switch:active::before { width: 24px; }
input[type="checkbox"][role="switch"]:checked:active::before,
.switch[aria-checked="true"]:active::before { transform: translateX(14px); }
input[type="checkbox"][role="switch"]:disabled, .switch:disabled {
  background: var(--surface-sunken); cursor: not-allowed;
}

/* Etiqueta clicable que envuelve un control */
.check-row {
  display: flex; align-items: center; gap: 10px;
  min-height: var(--touch-min);
  font-size: var(--text-14); cursor: pointer;
  -webkit-tap-highlight-color: var(--tap-hl);
}
.check-row__hint { font-size: var(--text-12); color: var(--text-secondary); }

/* Campo de archivo · el botón nativo es distinto en cada navegador */
input[type="file"] {
  font-family: var(--font-body); font-size: var(--text-13);
  color: var(--text-secondary); max-width: 100%;
}
input[type="file"]::file-selector-button {
  font-family: var(--font-body); font-size: var(--text-13); font-weight: 600;
  color: var(--text-primary);
  background: var(--surface-elevated);
  border: 1.5px solid var(--border-default);
  border-radius: var(--r-sm);
  padding: 8px 14px; margin-right: 12px;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  input[type="file"]::file-selector-button:hover {
    background: var(--cream-100); border-color: var(--brand-yellow-dark);
  }
}
/* Zona de arrastre */
.dropzone {
  border: 1.5px dashed var(--border-default); border-radius: var(--r-lg);
  background: var(--cream-50);
  padding: 24px; text-align: center;
  color: var(--text-secondary); font-size: var(--text-13);
  transition: border-color var(--duration-fast) var(--ease-out),
              background-color var(--duration-fast) var(--ease-out);
}
.dropzone.is-over { border-color: var(--brand-yellow); background: var(--brand-yellow-soft); }

/* Todo control táctil llega a 44px en dedo, sin engordar el escritorio */
@media (pointer: coarse) {
  .btn--sm { min-height: var(--touch-min); padding: 8px 14px; }
  .tab, .area-tabs button, .subtabs button, .paper-seg button { min-height: 38px; }
  .icon-btn, .modal__close { min-width: var(--touch-min); min-height: var(--touch-min); }
}

/* ────────────────────────────────────────────────────────────
   4 · TABLA DEL SISTEMA
   Reglas de mesa de datos, no de tarjeta: sin vidrio, sin sombra
   por fila, sin cebra. La fila se separa por una línea de 1px y
   se distingue por peso de texto. Los números en mono y a la
   derecha, con `tabular-nums` para que las columnas se alineen
   dígito con dígito.
   NOTA: las tablas actuales traen padding/color en atributos
   `style` en línea, que ganan sobre cualquier clase — acá se
   ajusta todo lo que NO está en línea (cabecera, líneas, hover,
   alineación numérica) y la clase .table queda lista para cuando
   el marcado se limpie.
   ──────────────────────────────────────────────────────────── */
.table, table { border-collapse: collapse; width: 100%; }
.table thead th, table thead th {
  font-family: var(--font-mono);
  font-size: var(--text-10);
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-secondary);
  text-align: left;
  background: var(--cream-50);
  border-bottom: 1px solid var(--border-default);
  white-space: nowrap;
}
.table tbody td, table tbody td {
  border-bottom: 1px solid var(--border-subtle);
  font-variant-numeric: tabular-nums;
  vertical-align: middle;
}
.table tbody tr:last-child td, table tbody tr:last-child td { border-bottom: none; }
/* Columnas numéricas: la clase, o cualquier celda ya alineada a la derecha */
.table .num, .table td[align="right"],
table td[style*="text-align:right"], table th[style*="text-align:right"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.table .num { text-align: right; }
/* Fila: hover y selección. Sin transform — no aplica a table-row. */
.table tbody tr, table tbody tr.mat-table-row, table tbody tr[data-item-row] {
  transition: background-color var(--duration-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .table tbody tr:hover,
  table tbody tr.mat-table-row:hover { background: var(--cream-50); }
}
.table tbody tr[aria-selected="true"],
.table tbody tr.is-selected,
table tbody tr.is-selected {
  background: var(--brand-yellow-soft);
  box-shadow: inset 3px 0 0 var(--brand-yellow);
}
.table tbody tr td:first-child, .table thead th:first-child { padding-left: 16px; }
.table tbody tr td:last-child,  .table thead th:last-child  { padding-right: 16px; }
.table td, .table th { padding: 10px 12px; font-size: var(--text-13); }
.table--compact td, .table--compact th { padding: 7px 10px; font-size: var(--text-12); }
/* Tabla dentro de una tarjeta: la tarjeta ya pone el borde exterior */
.card > .table, .card > * > .table { border-radius: inherit; }

/* ────────────────────────────────────────────────────────────
   4.b · TABLAS · Fase 4 · Tanda A — el marcado limpio
   Las vistas dejaron de traer padding/tipografía en `style` en
   línea, así que acá viven las tres cosas que faltaban para que
   .table gobierne de verdad: alineación por clase, contenedor de
   scroll y cabecera pegajosa.
   ──────────────────────────────────────────────────────────── */
/* Alineación por clase (antes iba `style="text-align:…"` en cada celda).
   `.num` (arriba) es para cifras: alinea a la derecha Y pasa a mono.
   `.rgt` es para lo que va a la derecha sin ser número (botones de fila). */
/* ── v45 · Cotizaciones ──────────────────────────────────────
   Las celdas traían su tipografía y su color en `style=""`, una por
   una. Acá viven, y así la tabla se ve igual en oscuro y en obra. */
.pres-chips { margin-bottom: 14px; }
.pres-chips .tab { padding: 7px 13px; font-size: var(--text-12); min-height: 36px; }
.pres-chips .tab b { font-family: var(--font-mono); margin-left: 4px; }
.pres-chips .tab[disabled] { opacity: .45; cursor: default; }

.pres-tabla { min-width: 720px; }
.pres-tabla tbody tr { cursor: pointer; }
.pres-cod { white-space: nowrap; }
.pres-cod .mono { font-size: var(--text-11); color: var(--text-secondary); }
.pres-cod__fecha { display: block; font-size: var(--text-10); color: var(--text-disabled); margin-top: 1px; }
.pres-v {
  display: inline-block; margin-left: 5px; padding: 1px 5px;
  border-radius: var(--r-pill); font-family: var(--font-mono); font-size: var(--text-10);
  background: var(--brand-yellow-soft); color: var(--brand-ink); font-weight: 600;
}
.pres-cli { font-weight: 600; }
/* La obra es lo más largo de la fila y lo que más se corta. Se le da
   ancho de sobra y se corta con puntos suspensivos, no a la mitad. */
.pres-obra { color: var(--text-secondary); max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pres-vence { white-space: nowrap; }
.pres-vence .mono { font-size: var(--text-11); color: var(--text-secondary); }
.pres-vence__d { display: block; font-size: var(--text-10); color: var(--text-disabled); margin-top: 1px; }
.pres-vence--ya .mono, .pres-vence--ya .pres-vence__d { color: var(--error); font-weight: 600; }
.pres-monto { font-weight: 700; }
.pres-acc { display: inline-flex; gap: 4px; }

/* ── v45 · Obras · la tabla que deja comparar ────────────────
   Contratado y gastado uno al lado del otro: la pregunta «¿esta obra
   va bien?» se contesta leyendo dos columnas, no entrando a cinco
   fichas. El punto de la izquierda es el semáforo de la v40. */
.obras-tabla { min-width: 760px; }
.obras-tabla tbody tr { cursor: pointer; }
.obras-nom { font-weight: 600; max-width: 300px; }
.obras-nom > span:not(.obras-sem) { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.obras-cod { font-family: var(--font-mono); font-size: var(--text-10); color: var(--text-disabled); font-weight: 400; }
.obras-cli { color: var(--text-secondary); max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.obras-sem {
  float: left; width: 8px; height: 8px; border-radius: var(--r-pill);
  margin: 6px 8px 0 0; flex: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 18%, transparent);
}
.obras-sem--ok        { background: var(--success); color: var(--success); }
.obras-sem--atencion  { background: var(--warning); color: var(--warning); }
.obras-sem--riesgo    { background: var(--error);   color: var(--error); }
.obras-sem--sin_datos { background: var(--text-disabled); color: var(--text-disabled); }
.obras-av { min-width: 130px; white-space: nowrap; }
.obras-av .progress-bar { display: inline-block; width: 74px; height: 5px; vertical-align: middle; }
/* El avance no es un juicio: es cuánto se hizo. El verde de «va bien» lo
   pone el punto del semáforo, que sí mira el gasto. */
.obras-av .progress-bar__fill { background: var(--viz-4); }
.obras-av .mono { font-size: var(--text-10); color: var(--text-secondary); margin-left: 8px; }
.obras-gasto__pct { display: block; font-family: var(--font-mono); font-size: var(--text-10); color: var(--text-disabled); font-weight: 400; }
/* Gastando más rápido de lo que se avanza: la cifra lo dice sola. */
.obras-gasto--ojo, .obras-gasto--ojo .obras-gasto__pct { color: var(--error); }
.obras-sindato { color: var(--text-disabled); }
/* El chip de «Con alerta» arrastra su color aunque no esté elegido. */
.tab--mal { color: var(--error); }


.table .ctr { text-align: center; }
.table .lft { text-align: left; }
.table .rgt { text-align: right; }

/* Fila de totales: cierra la tabla con una línea fuerte y fondo propio.
   Antes cada vista la dibujaba con `border-top:2px` en línea. */
.table tfoot td {
  border-top: 2px solid var(--border-default);
  border-bottom: none;
  background: var(--cream-50);
  font-weight: 700;
}
/* Contenedor de scroll horizontal.
   En pantalla angosta scrollea; desde 1100px deja de ser contenedor de
   scroll para que la cabecera pegajosa se ancle a la página y no a un
   recorte invisible. Un `overflow` distinto de visible rompe `sticky`. */
/* `min-width: 0` es obligatorio: la tarjeta es flex y un hijo flex no baja
   de su ancho mínimo de contenido — sin esto el `min-width` de la tabla
   estira la tarjeta en vez de scrollear adentro. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
@media (min-width: 1100px) { .table-wrap { overflow: visible; } }

/* Cabecera pegajosa: se apoya justo bajo la barra superior */
@media (min-width: 1100px) {
  .table--sticky thead th {
    position: sticky;
    top: var(--topbar-height);
    z-index: 2;
    box-shadow: 0 1px 0 var(--border-default);
  }
  /* v64.1 · Dentro de una ficha lateral o una ventana el que scrollea es el
     panel, no la página: bajo la barra superior la cabecera quedaba flotando
     en medio de la tabla (Materiales de la obra 019). Ahí se pega arriba. */
  .pdet-drawer .table--sticky thead th,
  .sei-drawer .table--sticky thead th,
  .modal .table--sticky thead th { top: 0; }
}

/* Tarjeta que contiene una tabla a sangre.
   Reemplaza al viejo `style="padding:0;overflow:hidden"`: el overflow
   hidden era solo para redondear las esquinas, y de paso mataba el
   sticky. Acá las esquinas las redondea la propia cabecera. */
.card--table { padding: 0; gap: 0; overflow: visible; }
.card--table > .table:first-child > thead > tr > th:first-child,
.card--table > .table-wrap:first-child > .table > thead > tr > th:first-child { border-top-left-radius: var(--r-lg); }
.card--table > .table:first-child > thead > tr > th:last-child,
.card--table > .table-wrap:first-child > .table > thead > tr > th:last-child  { border-top-right-radius: var(--r-lg); }

/* ────────────────────────────────────────────────────────────
   5 · PESTAÑAS — un solo lenguaje visual
   Conviven .tabs/.tab, .area-tabs, .subtabs y .paper-seg haciendo
   lo mismo con cuatro aspectos distintos. Sin renombrar nada, se
   igualan al mismo material: riel hundido + pastilla elevada.
   El riel tiene alto fijo, así la pestaña activa no hace saltar
   el bloque al cambiar de vista.
   ──────────────────────────────────────────────────────────── */
.tabs, .area-tabs, .subtabs, .paper-seg {
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-pill);
  padding: 3px;
  gap: 2px;
  box-shadow: inset 0 1px 2px rgba(26,26,24,.05);
  /* Nunca envuelve a dos líneas: se desliza. Resuelve el salto de alto.
     La barra queda VISIBLE y fina, no oculta: esconderla es el patrón
     D-2/U-2 que este repo ya arregló dos veces (ver .scroll-x-thin en
     layout.css). Sin barra, en el teléfono la mitad de las pestañas
     simplemente no existe para el usuario — medido: 171px de contenido
     dentro de 88px visibles en la vista de finanzas a 390px. */
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(20, 30, 55, .28) transparent;
  -webkit-overflow-scrolling: touch;
}
/* v50 · El campo de archivo, con la letra de la app.
   Visto en Contabilidad, que es donde Hebert sube los CSV del SII todos
   los meses: decia «Seleccionar archivo · Sin archivos seleccionados»
   con el boton gris de Chrome y su tipografia, al lado de botones de la
   app. Nueve lugares de la app ya lo hacen bien —esconden el input y
   ponen su propio boton—; siete mostraban el crudo, incluido el campo
   generico de los modales, que sirve a cualquier formulario futuro.
   Por eso va en el CSS y no en los siete: se arregla el componente. */
.input[type="file"] {
  padding: 6px 8px;
  font-size: var(--text-13);
  color: var(--text-secondary);
  cursor: pointer;
}
.input[type="file"]::file-selector-button {
  font-family: var(--font-body);
  font-size: var(--text-13);
  font-weight: 600;
  color: var(--text-primary);
  background: var(--surface-elevated);
  border: 1.5px solid var(--border-default);
  border-radius: var(--r-sm);
  padding: 6px 12px;
  margin-right: 10px;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background-color var(--duration-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .input[type="file"]::file-selector-button:hover { background: var(--cream-100); }
}

/* v49 · Solo para el lector de pantalla.
   La app no tenia ninguna utilidad de este tipo — cero en todo el CSS —,
   y hace falta cada vez que algo se dibuja pero no se lee: el esqueleto
   de carga es `aria-hidden` (son rayitas), asi que sin esto, cambiar la
   palabra «Cargando...» por el esqueleto dejaba mudo al lector.
   No se usa `display:none` ni `visibility:hidden`: las dos lo sacan
   tambien del arbol de accesibilidad, que es justo lo contrario. */
.solo-lector {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* v49 · El riel y la acción que lo acompaña.
   En Contabilidad el botón «Respaldo del período» vivía DENTRO del riel
   con un margin-left automático, y a11y.js marca como pestaña a todo
   hijo botón del grupo: lo anunciaba «pestaña 7 de 7» y las flechas
   ← → caían ahí. Una acción no es una pestaña — va al lado, no adentro. */
.sii-riel {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.sii-riel > .tabs { flex: 1 1 auto; min-width: 0; }
.sii-riel > .btn  { flex: 0 0 auto; }

.tabs::-webkit-scrollbar, .area-tabs::-webkit-scrollbar,
.subtabs::-webkit-scrollbar, .paper-seg::-webkit-scrollbar { height: 5px; }
.tabs::-webkit-scrollbar-track, .area-tabs::-webkit-scrollbar-track,
.subtabs::-webkit-scrollbar-track, .paper-seg::-webkit-scrollbar-track { background: transparent; }
.tabs::-webkit-scrollbar-thumb, .area-tabs::-webkit-scrollbar-thumb,
.subtabs::-webkit-scrollbar-thumb, .paper-seg::-webkit-scrollbar-thumb {
  background: rgba(20, 30, 55, .22);
  border-radius: var(--r-xs);
}
.tab, .area-tabs button, .subtabs button, .paper-seg button {
  border-radius: var(--r-pill);
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--text-secondary);
  background: transparent;
  border: none;
  cursor: pointer;
  white-space: nowrap;
  flex: 0 0 auto;
  transition:
    color var(--duration-fast) var(--ease-out),
    background-color var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    transform var(--duration-instant) var(--ease-out);
}
.tab:active, .area-tabs button:active,
.subtabs button:active, .paper-seg button:active { transform: scale(0.96); }
.tab--active,
/* v45 · `.tab` con `aria-pressed` también se ve elegida. Sin esta línea,
   los filtros de Mi día, Cotizaciones y Obras quedaban todos planos: la
   pastilla sólo se pintaba en `.area-tabs`, `.subtabs` y `.paper-seg`, y
   el que estaba activo no se distinguía de los otros seis. */
.tab[aria-pressed="true"],
.area-tabs button[aria-pressed="true"],
.subtabs button[aria-pressed="true"],
.paper-seg button[aria-pressed="true"],
[role="tab"][aria-selected="true"] {
  background: var(--surface-elevated);
  color: var(--text-primary);
  font-weight: 600;
  /* Pastilla con luz arriba: parece una tecla, no un rectángulo pintado */
  box-shadow:
    var(--shadow-sm)
    0 2px 6px rgba(26,26,24,.06),
    inset 0 .5px 0 rgba(255,255,255,.85);
}
@media (hover: hover) and (pointer: fine) {
  .tab:not(.tab--active):hover,
  .area-tabs button:not([aria-pressed="true"]):hover,
  .subtabs button:not([aria-pressed="true"]):hover { color: var(--text-primary); }
}

/* ────────────────────────────────────────────────────────────
   6 · KPI — que la cifra gane de lejos
   Se mira de reojo desde un celular en obra: la etiqueta pasa a
   11px con más tracking (texto chico quiere aire entre letras),
   la cifra sube a 28px con -0.03em y tabular-nums, y el fondo
   tintado completo se retira. El color del estado se conserva en
   un punto de 6px junto a la etiqueta y en un velo del 8% que
   apenas se insinúa: la tinta fuerte vuelve a ser el número.
   ──────────────────────────────────────────────────────────── */
.kpi { padding: 14px 16px; gap: 3px; border-radius: var(--r-lg); }
.kpi__label {
  font-size: var(--text-11);
  font-weight: 600;
  letter-spacing: .015em;
  color: var(--text-secondary);
  display: flex; align-items: center; gap: 6px;
}
.kpi__value {
  font-size: var(--text-28);
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.kpi__delta { font-variant-numeric: tabular-nums; color: var(--text-secondary); }
.kpi--success, .kpi--warning, .kpi--error, .kpi--info {
  background: var(--surface-elevated);
  border-color: var(--border-subtle);
  position: relative;
  overflow: hidden;
}
/* Velo diagonal del color de estado — presencia sin gritar */
.kpi--success::after, .kpi--warning::after,
.kpi--error::after, .kpi--info::after {
  content: ""; position: absolute; inset: 0;
  pointer-events: none;
}
.kpi--success::after { background: linear-gradient(158deg, var(--success-soft), transparent 58%); }
.kpi--warning::after { background: linear-gradient(158deg, var(--warning-soft), transparent 58%); }
.kpi--error::after   { background: linear-gradient(158deg, var(--error-soft),   transparent 58%); }
.kpi--info::after    { background: linear-gradient(158deg, var(--info-soft),    transparent 58%); }
/* Punto de color en la etiqueta: identifica el estado sin teñir la tarjeta */
:where(.kpi--success, .kpi--warning, .kpi--error, .kpi--info, .kpi--accent) .kpi__label::before {
  content: ""; width: 6px; height: 6px; border-radius: 2px; flex-shrink: 0;
  background: var(--text-secondary);
}
.kpi--success .kpi__label::before { background: var(--success); }
.kpi--warning .kpi__label::before { background: var(--warning); }
.kpi--error   .kpi__label::before { background: var(--error); }
.kpi--info    .kpi__label::before { background: var(--info); }
.kpi--accent  .kpi__label::before { background: var(--brand-yellow-dark); }
:where(.kpi--success, .kpi--warning, .kpi--error, .kpi--info) .kpi__label,
:where(.kpi--success, .kpi--warning, .kpi--error, .kpi--info) .kpi__value,
:where(.kpi--success, .kpi--warning, .kpi--error, .kpi--info) .kpi__delta { position: relative; z-index: 1; }
.kpi--accent {
  background: linear-gradient(158deg, var(--brand-yellow-soft), var(--surface-elevated) 62%);
  border-top-width: 3px;
}
@media (hover: hover) and (pointer: fine) {
  .kpi:hover { box-shadow: var(--shadow-hover); }
}
.kpi { transition: box-shadow var(--duration-base) var(--ease-out); }

/* ────────────────────────────────────────────────────────────
   7 · BADGES — cinco familias de estado que ya no se pisan
   "Borrador" y "Anulada" se veían idénticas y significan lo
   contrario. Borrador toma tinta azulada viva (es un documento
   vivo); anulada toma ceniza con el texto tachado (ya no existe).
   ──────────────────────────────────────────────────────────── */
.badge {
  padding: 3px 8px;
  letter-spacing: .01em;
  border-radius: var(--r-sm);
  line-height: 1.45;
}
.badge--pill { border-radius: var(--r-pill); }
.badge--draft { background: var(--draft-soft); color: var(--draft); }
.badge--void  { background: var(--void-soft);  color: var(--void); text-decoration: line-through; text-decoration-thickness: 1px; }
.badge--dot::before { box-shadow: 0 0 0 2.5px color-mix(in srgb, currentColor 20%, transparent); }
/* Badge "contador" — número dentro de un encabezado de panel */
.badge--count {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  min-width: 20px; justify-content: center; border-radius: var(--r-pill);
}

/* ────────────────────────────────────────────────────────────
   8 · BOTÓN PRIMARIO — material, no rectángulo pintado
   Gradiente vertical corto + luz interior arriba + línea de
   sombra abajo. Al presionar la luz se invierte: el botón se
   hunde de verdad en vez de solo achicarse.
   ──────────────────────────────────────────────────────────── */
.btn--primary {
  background: linear-gradient(180deg, var(--brand-yellow-300), var(--brand-yellow) 52%, var(--brand-yellow-dark));
  box-shadow:
    var(--shadow-yellow),
    inset 0 1px 0 rgba(255,255,255,.60),
    inset 0 -1px 0 rgba(120,95,0,.16);
}
.btn--primary:active {
  box-shadow:
    var(--shadow-sm)
    inset 0 1px 3px rgba(120,95,0,.26);
}
@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover {
    background: linear-gradient(180deg, var(--brand-yellow), var(--brand-yellow-dark) 60%, #D9AF00);
  }
}
.btn--secondary {
  box-shadow: 0 1px 2px rgba(26,26,24,.05), inset 0 1px 0 rgba(255,255,255,.7);
}
.btn--danger {
  background: linear-gradient(180deg, color-mix(in oklab, var(--error) 88%, #fff), var(--error));
  box-shadow: 0 1px 2px rgba(165,36,36,.22), 0 4px 12px rgba(165,36,36,.18), inset 0 1px 0 rgba(255,255,255,.22);
}

/* ────────────────────────────────────────────────────────────
   9 · VIDRIO — solo donde hay algo detrás que desenfocar
   Overlays y drawers: el fondo oscurecido y el contenido de la
   página pasan por debajo, así que el material se lee de verdad.
   Prohibido en filas de tabla y listas largas.
   ──────────────────────────────────────────────────────────── */
.modal-overlay, .drawer-overlay, .app-sidebar-overlay {
  background: rgba(20, 20, 15, .38);
  -webkit-backdrop-filter: blur(6px) saturate(120%);
  backdrop-filter: blur(6px) saturate(120%);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .modal-overlay, .drawer-overlay, .app-sidebar-overlay { background: rgba(20, 20, 15, .55); }
}
.modal, .drawer {
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-overlay);
}
.modal__head, .drawer__head {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-line);
  position: sticky; top: 0; z-index: 2;
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .modal__head, .drawer__head { background: var(--surface-elevated); }
}
.modal__foot {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .modal__foot { background: var(--surface-sunken); }
}
/* Agarradera del bottom-sheet: señal de que se puede arrastrar */
@media (max-width: 767px) {
  .modal { border-radius: var(--r-xl) var(--r-xl) 0 0; }
  .modal__head::before {
    content: ""; position: absolute; top: 7px; left: 50%;
    transform: translateX(-50%);
    width: 36px; height: 4px; border-radius: var(--r-pill);
    background: var(--border-default);
  }
  .modal__head { position: relative; padding-top: 20px; }
}

/* ────────────────────────────────────────────────────────────
   10 · TARJETAS — radio anidado y sombra consistente
   Regla del radio anidado: hijo = padre − padding. Un radio 16
   dentro de otro 16 delata el CSS; 16 → 10 se lee como material.
   ──────────────────────────────────────────────────────────── */
.card { border-radius: var(--r-lg); }
.card--hero { border-radius: var(--r-xl); }
.card > .card, .card .kpi, .card .tile, .card .panel { border-radius: var(--r-md); }
.card--hero > .card, .card--hero .kpi { border-radius: var(--r-lg); }
.card--accent { border-top-width: 3px; }

/* Barra de progreso: sube a 6px (4px se pierde en pantalla al sol),
   con relieve interior y punta redondeada. */
.progress-bar { height: 6px; background: var(--surface-sunken); box-shadow: inset 0 1px 2px rgba(26,26,24,.08); }
.progress-bar__fill {
  background: linear-gradient(90deg, color-mix(in oklab, var(--success) 78%, #fff), var(--success));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.35);
}
.progress-bar__fill--warning { background: linear-gradient(90deg, color-mix(in oklab, var(--warning) 78%, #fff), var(--warning)); }
.progress-bar__fill--error   { background: linear-gradient(90deg, color-mix(in oklab, var(--error)   78%, #fff), var(--error));   }
.progress-bar__fill--brand   { background: linear-gradient(90deg, var(--brand-yellow-300), var(--brand-yellow-dark)); }

/* Cifras: en toda la app los números se alinean dígito con dígito */
.text-mono, .kpi__value, .kpiv__v, .tile__v, .area__figure,
.fin-hero__v, .fin-box__v, .obra__pct, .ot-stat .n, .aging__n {
  font-variant-numeric: tabular-nums;
}
/* `.mono` se usa en 142 lugares del marcado (folios, RUT, horas, montos,
   inputs numéricos) y NUNCA tuvo regla: pedía monoespaciada y salía en la
   fuente de texto. Acá se le da lo que siempre dijo tener. */
.mono, .text-mono { font-family: var(--font-mono); }
.mono { font-variant-numeric: tabular-nums; }

/* ────────────────────────────────────────────────────────────
   11 · COMPENSACIONES DE MODO OSCURO
   Donde el color semántico es FONDO con texto blanco encima, el
   token claro de oscuro no sirve: se usa la versión profunda.
   ──────────────────────────────────────────────────────────── */
[data-theme="dark"] .toast--success { background: #1F6142; }
[data-theme="dark"] .toast--warning { background: #7A4A12; }
[data-theme="dark"] .toast--error   { background: #8C2C2C; }
[data-theme="dark"] .toast--info    { background: #17538F; }
[data-theme="dark"] .btn--danger {
  background: linear-gradient(180deg, #B03A3A, #8C2C2C);
  color: #fff;
  box-shadow: 0 1px 2px rgba(0,0,0,.35), 0 4px 12px rgba(0,0,0,.30), inset 0 1px 0 rgba(255,255,255,.14);
}
[data-theme="dark"] .badge--dark { background: var(--cream-200); color: var(--text-primary); }
[data-theme="dark"] .card--warn,
[data-theme="dark"] .card--error,
[data-theme="dark"] .card--success { color: var(--text-primary); }
/* El vidrio en oscuro necesita el borde de luz más tenue, si no el
   marco blanco flota sobre el fondo negro. */
[data-theme="dark"] :where(.modal__head, .modal__foot, .drawer__head) {
  box-shadow: inset 0 .5px 0 rgba(255,255,255,.10);
}
[data-theme="dark"] .btn--secondary { box-shadow: 0 1px 2px rgba(0,0,0,.30), inset 0 1px 0 rgba(255,255,255,.06); }
[data-theme="dark"] input[type="checkbox"]:not([role="switch"]),
[data-theme="dark"] input[type="radio"] { background: var(--surface-sunken); }
[data-theme="dark"] input[type="checkbox"][role="switch"],
[data-theme="dark"] .switch { background: var(--cream-200); box-shadow: inset 0 1px 3px rgba(0,0,0,.45); }
[data-theme="dark"] input[type="checkbox"][role="switch"]::before,
[data-theme="dark"] .switch::before { background: #E8E6DF; }
[data-theme="dark"] .progress-bar { box-shadow: inset 0 1px 2px rgba(0,0,0,.40); }

/* Divisor y estado vacío en el tercer nivel de texto, no en el segundo */
.divider { color: var(--text-tertiary); }
.empty__icon { color: var(--text-tertiary); }
.input::placeholder { color: var(--text-tertiary); }

/* ────────────────────────────────────────────────────────────
   12 · FOCO — cierre de cascada
   La regla general del bloque 1 usa `:where()`, que tiene
   especificidad CERO a propósito (para no ganarle a nada por
   accidente). El costo es que cualquier `.clase { box-shadow }`
   declarada después la pisa — que es justo lo que pasaba con
   .btn--primary, .btn--secondary y .short.
   Este bloque va último y repite el anillo con especificidad de
   clase sobre los elementos que traen sombra propia. No es
   duplicación decorativa: sin esto no se ve dónde estás parado.
   ──────────────────────────────────────────────────────────── */
.btn:focus-visible,
.short:focus-visible,
.tab:focus-visible,
.topbar-nuevo:focus-visible,
.topbar-avatar:focus-visible,
.m-bottomnav__item:focus-visible,
.m-bottomnav__fab:focus-visible,
.modal__close:focus-visible,
.acc-rapida:focus-visible,
.rep-card:focus-visible,
.tpl-card:focus-visible,
.icon-btn:focus-visible,
.area-tabs button:focus-visible,
.subtabs button:focus-visible,
.paper-seg button:focus-visible,
.sidebar-item:focus-visible,
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible,
input[type="checkbox"][role="switch"]:focus-visible,
.switch:focus-visible,
input[type="file"]:focus-visible,
.photo-slot:focus-visible,
.sub-check:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  z-index: 1;
  position: relative;
}
.sidebar-item:focus-visible { box-shadow: var(--focus-ring-dark); }
/* Los campos conservan su halo cálido y suman el anillo */
.input:focus-visible, .inp:focus-visible, .ta:focus-visible, .sel:focus-visible {
  box-shadow: 0 0 0 3px rgba(255,207,0,.18), var(--focus-ring);
}
/* Fila de tabla enfocada por teclado: barra lateral, no anillo
   (un box-shadow difuso no se dibuja bien sobre table-row). */
.table tbody tr:focus-visible,
table tbody tr:focus-visible {
  outline: 2px solid var(--brand-navy);
  outline-offset: -2px;
  background: var(--brand-yellow-soft);
}

/* ────────────────────────────────────────────────────────────
   PARCHE v3.1 · badge de marca en modo oscuro
   Medido en la app: navy #1A1A18 sobre brand-yellow-soft #2A2410
   daba 1,13:1 — ilegible. Es el mismo tipo de bug que v3 corrigió
   en los badges semánticos, pero `--brand` se había quedado afuera.
   El amarillo de marca sobre su propio soft da 10,4:1.
   ──────────────────────────────────────────────────────────── */
[data-theme="dark"] .badge--brand { color: var(--brand-yellow); }


/* ════════════════════════════════════════════════════════════
   14 · VISTAS QUE TRAÍAN SU PROPIO CSS · Fase 4 · Tanda B
   Cuatro vistas se inyectaban ~190 líneas de estilos desde
   JavaScript, en un <style> que se colgaba del documento la
   primera vez que se abrían. Eso significaba: estilos que llegan
   después de todo (ganan sin querer), no se cachean con el resto,
   y nadie los encuentra buscando en css/. Acá quedan, tal cual,
   en el orden en que se aplicaban.
   ════════════════════════════════════════════════════════════ */

/* ── OT · tarjetas, chips de filtro y buscador ── */
.ot-card {
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  padding: 12px 14px;
  display: flex; flex-direction: column; gap: 6px;
  cursor: pointer;
  transition: box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.ot-card:hover { box-shadow: var(--shadow-card); transform: translateY(-1px); }
.ot-card--urgente { border-left: 4px solid var(--error); }
.ot-card--alta    { border-left: 4px solid var(--warning); }
.ot-card--media   { border-left: 4px solid var(--info); }
.ot-card--baja    { border-left: 4px solid var(--text-disabled); }
.ot-card__head    { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.ot-card__num     { font-family: var(--font-mono); font-size: var(--text-11); color: var(--text-secondary); letter-spacing: .04em; }
.ot-card__chips   { display: flex; gap: 4px; flex-wrap: wrap; }
.ot-card__titulo  { font-family: var(--font-display); font-weight: 700; font-size: var(--text-14); line-height: 1.25; margin: 0; }
.ot-card__meta    { display: flex; align-items: center; gap: 6px; font-size: var(--text-11); color: var(--text-secondary); }
.ot-card__meta svg { width: 12px; height: 12px; flex-shrink: 0; }
.ot-card__foot    { display: flex; justify-content: space-between; align-items: center; margin-top: 4px; }
.ot-card__tecnico { display: flex; align-items: center; gap: 8px; font-size: var(--text-11); color: var(--text-secondary); }
.ot-card__close-btn {
  margin-top: 4px;
  background: var(--brand-yellow); color: var(--brand-navy);
  border: none; border-radius: var(--r-sm); padding: 9px 12px;
  font-weight: 600; font-size: var(--text-12); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
}
.ot-card__close-btn svg { width: 14px; height: 14px; }
.ot-card__close-btn:hover { filter: brightness(0.97); }

.ot-grid {
  display: grid; gap: 10px;
  grid-template-columns: 1fr;
}
@media (min-width: 768px) { .ot-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px){ .ot-grid { grid-template-columns: repeat(3, 1fr); } }

/* v46 · `.ot-chip` se fue: Órdenes de trabajo usa el riel del sistema
   como las otras ocho pantallas que filtran. Eran 28 líneas de CSS para
   que el filtro elegido de esta pantalla se viera distinto al de todas
   las demás. La nota que vivía acá —el filtro elegido es ESTADO y no
   acción, así que no se marca con el amarillo del botón «Nueva OT»—
   sigue siendo cierta y ahora la cumple el riel: pastilla clara sobre
   fondo hundido, sin robarle el acento a nada. */

.ot-search {
  position: relative; flex: 1; max-width: 360px;
}
.ot-search input {
  width: 100%;
  padding: 8px 12px 8px 34px;
  border: 1px solid var(--border-default);
  border-radius: var(--r-sm);
  font-size: var(--text-13);
  background: var(--surface-elevated);
}
.ot-search svg {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  width: 14px; height: 14px; color: var(--text-secondary);
  pointer-events: none;
}
.ot-toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }


/* ── Comercial · seguimiento de cotizaciones ── */
#comercial-tabs { flex-wrap: wrap; }
.cot-kpis { display:grid; grid-template-columns:repeat(3,1fr); gap:10px; margin-bottom:14px; }
.cot-kpi { background:var(--cream-50); border:1px solid var(--border-subtle);
  border-radius:var(--r-md); padding:10px 12px; }
.cot-kpi--alerta { border-color:var(--warning); }
.cot-kpi__k { font-family:var(--font-mono); font-size:var(--text-10); letter-spacing:.06em;
  text-transform:uppercase; color:var(--text-secondary); }
.cot-kpi__v { font-family:var(--font-display); font-weight:700; font-size:var(--text-20); }
.cot-fila { display:grid; align-items:center; gap:10px; padding:10px 12px;
  grid-template-columns:1fr 110px 190px auto; }
.cot-fila + .cot-fila { border-top:1px solid var(--border-subtle); }
.cot-fila__main { min-width:0; }
.cot-fila__main b { display:block; font-size:var(--text-13); font-weight:600;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cot-fila__main span { font-size:var(--text-11); color:var(--text-secondary); }
.cot-monto { font-family:var(--font-mono); font-size:var(--text-13); text-align:right; }
.cot-estado { font-size:var(--text-11); color:var(--text-secondary); display:flex;
  align-items:center; gap:5px; flex-wrap:wrap; }
.cot-estado em { display:block; width:100%; font-size:var(--text-10); font-style:normal;
  color:var(--text-disabled); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cot-punto { width:7px; height:7px; border-radius:var(--r-pill); background:var(--border-default); flex:none; }
.cot-fila--tibia   .cot-punto { background:var(--warning); }
.cot-fila--urgente .cot-punto { background:var(--error); }
.cot-fila--urgente { background:var(--error-soft, rgba(197,48,48,.05)); }
.cot-fila--vencida .cot-punto { background:var(--error); }
.cot-fila--vencida { background:var(--error-soft, rgba(197,48,48,.08)); }
.cot-fila--vencida .cot-fila__main b { text-decoration:line-through solid var(--text-disabled); }
/* U-8 · Los 3 botones salían con el texto cortado: la columna auto
   los comprimía y .btn no impedía el recorte. Nowrap + no encoger. */
.cot-acciones { display:flex; gap:5px; flex-wrap:wrap; justify-content:flex-end; }
.cot-acciones .btn { white-space:nowrap; flex:0 0 auto; }
.cot-barra { height:4px; background:var(--cream-100); border-radius:var(--r-pill);
  margin-top:5px; overflow:hidden; max-width:220px; }
.cot-barra span { display:block; height:100%; background:var(--brand-yellow); }
/* La grilla de 4 columnas no cabe en celular: se apila. */
@media (max-width: 767px) {
  .cot-kpis { grid-template-columns:1fr; }
  .cot-fila { grid-template-columns:1fr auto; }
  .cot-estado { grid-column:1 / -1; }
  .cot-acciones { grid-column:1 / -1; justify-content:flex-start; }
  .cot-acciones .btn { flex:1 1 calc(33% - 4px); justify-content:center; }
}


/* ── Personas · tabla y filas de trabajadores ── */
#rrhh-tabs { flex-wrap: wrap; }
.rrhh-tabla { border:1px solid var(--border-subtle); border-radius:var(--r-md); overflow:hidden; }
.rrhh-fila { display:flex; align-items:center; gap:10px; padding:9px 12px; }
.rrhh-fila + .rrhh-fila { border-top:1px solid var(--border-subtle); }
.rrhh-fila__nom { flex:1; min-width:0; }
.rrhh-fila__nom b { display:block; font-size:var(--text-13); font-weight:600;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rrhh-fila__nom span { font-size:var(--text-10); color:var(--text-secondary); }
.rrhh-num { font-family:var(--font-mono); font-size:var(--text-13); text-align:right; min-width:52px; }
.rrhh-num small { display:block; font-family:var(--font-body); font-size:var(--text-10);
  color:var(--text-secondary); text-transform:uppercase; letter-spacing:.05em; }
@media (max-width: 640px) {
  .rrhh-fila { flex-wrap: wrap; }
  .rrhh-fila__nom { flex:1 1 100%; }
  .rrhh-num { flex:1 1 auto; text-align:left; }
}


/* ── Personas · asistencia del día ── */
.asis-tot { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; margin-bottom:12px; }
.asis-tot__c { background:var(--cream-50); border:1px solid var(--border-subtle);
  border-radius:var(--r-sm); padding:8px 10px; }
.asis-tot__k { font-family:var(--font-mono); font-size:var(--text-10); letter-spacing:.06em;
  text-transform:uppercase; color:var(--text-secondary); }
.asis-tot__v { font-family:var(--font-display); font-weight:700; font-size:var(--text-18); }
.asis-tabla { border:1px solid var(--border-subtle); border-radius:var(--r-sm); overflow:hidden; }
.asis-fila { display:grid; align-items:center; gap:6px; padding:5px 8px;
  grid-template-columns:44px 1fr 92px 92px 42px 64px 28px; }
.asis-fila + .asis-fila { border-top:1px solid var(--border-subtle); }
.asis-fila--finde { background:var(--cream-50); }
.asis-fila__dia { display:flex; flex-direction:column; line-height:1.1; }
.asis-fila__dia b { font-size:var(--text-13); }
.asis-fila__dia span { font-size:var(--text-10); color:var(--text-secondary);
  text-transform:uppercase; font-family:var(--font-mono); }
.asis-fila__h { font-family:var(--font-mono); font-size:var(--text-12); text-align:right;
  color:var(--text-secondary); }
.asis-inp { padding:5px 7px; font-size:var(--text-12); height:30px; }
.asis-inp--num { text-align:right; }
.asis-del { background:transparent; border:none; cursor:pointer; padding:3px;
  color:var(--text-disabled); display:inline-flex; }
.asis-del:hover { color:var(--error); }
/* En celular la grilla de 7 columnas no cabe. Se reordena en 3 filas
   con áreas explícitas: dejarlo al auto-flow daba 6 filas por día, o
   sea 31 días × 6 = una lista interminable. */
@media (max-width: 640px) {
  .asis-tot { grid-template-columns:repeat(2,1fr); }
  .asis-fila {
    grid-template-columns:40px 1fr 1fr;
    grid-template-areas:
      "dia tipo del"
      "dia ent  sal"
      "dia hrs  ext";
    row-gap:4px; padding:7px 8px;
  }
  .asis-fila__dia { grid-area:dia; align-self:start; }
  .asis-fila [data-campo="tipo"]         { grid-area:tipo; }
  .asis-fila [data-campo="hora_entrada"] { grid-area:ent; }
  .asis-fila [data-campo="hora_salida"]  { grid-area:sal; }
  .asis-fila__h { grid-area:hrs; text-align:left; align-self:center; }
  .asis-fila__h::before { content:"total "; font-family:var(--font-body); font-size:var(--text-10);
    text-transform:uppercase; letter-spacing:.05em; }
  .asis-fila [data-campo="horas_extra"]  { grid-area:ext; }
  .asis-del { grid-area:del; justify-self:end; align-self:center; }
}

/* TERRENO · controles que sólo tienen sentido con cámara en la mano. En
   escritorio «Sacar foto» abriría el selector de archivos igual que el botón
   de al lado: se esconde. */
[data-solo-tactil] { display: none; }
@media (pointer: coarse) { [data-solo-tactil] { display: inline-flex; } }

/* ══════════════════════════════════════════════════════════════
   v51 · Dos barritas que la maqueta pedía y no existían
   ══════════════════════════════════════════════════════════════ */

/* ── Bodega · «Hay» contra el mínimo ──
   La maqueta: «se ve de un vistazo qué está por caerse». Con dos
   columnas de números hay que comparar fila por fila, y con veinte
   materiales nadie lo hace.
   El carril vale DOS veces el mínimo, así que la marca del medio ES el
   mínimo: a la izquierda estás corto, a la derecha estás bien. */
.bod-min {
  display: block; position: relative;
  height: 5px; margin-top: 5px;
  border-radius: var(--r-pill);
  background: var(--surface-sunken);
  overflow: hidden;
}
.bod-min i {
  display: block; height: 100%; border-radius: var(--r-pill);
  transform-origin: left center;
  animation: bfila-crece var(--duration-slow, .6s) var(--ease-out, cubic-bezier(.22,1,.36,1)) both;
}
/* La marca del mínimo. Va por encima del relleno para que se vea
   también cuando estás por encima del mínimo. */
.bod-min__marca {
  position: absolute; left: 50%; top: -1px; bottom: -1px;
  width: 2px; background: var(--text-secondary);
  opacity: .55; text-decoration: none;
}
@media (prefers-reduced-motion: reduce) { .bod-min i { animation: none; } }

/* ── Proveedores · el rating en barras ──
   La maqueta: «barras de color en vez de estrellas, ocupa menos y se
   compara mejor». Con los datos reales se nota por qué: de once
   proveedores, seis están en 0,0 — seis filas de cinco estrellas grises
   para decir «todavía no lo calificaste». */
.rat { display: inline-flex; align-items: center; gap: 2px; }
.rat i { width: 11px; height: 6px; border-radius: var(--r-xs); display: block; }
.rat--vacio {
  font-size: var(--text-10);
  color: var(--text-disabled);
  font-family: var(--font-mono);
}

/* ═══ css/layout.css ═══ */
/* ============================================================
   SEINGELEC Gestión v2 · Layout
   Shell de la app: sidebar, topbar, main, bottom-nav móvil.
   Basado en Design System v12 · Bloque A pantallas.
   ============================================================ */

/* ---- Reset base ---- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }

body {
  font-family: var(--font-body);
  font-size: var(--text-14);
  color: var(--text-primary);
  background: var(--cream-base);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overscroll-behavior: none;
}

#app { min-height: 100dvh; display: flex; flex-direction: column; }

/* ============================================================
   SHELL DE LA APP (logueado)
   Desktop: sidebar fijo + topbar + main scrollable
   Mobile:  header fijo + main + bottom nav fijo
   ============================================================ */

.app-shell {
  display: flex;
  min-height: 100dvh;
  background: var(--cream-base);
}

/* ---- Sidebar (desktop) ---- */
.app-sidebar {
  width: var(--sidebar-width);
  flex-shrink: 0;
  background: var(--brand-navy);
  color: #C9C7C2;
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: var(--z-sidebar);
  overflow-y: auto;
  overflow-x: hidden;
  /* Respeta safe-area en iPhone: el sidebar mobile se abre encima de todo */
  /* v61 · el `padding` de abajo pisaba estos dos: en el iPad horizontal
     (app instalada) el logo quedaba bajo la barra de estado. Van juntos. */
  padding: calc(14px + env(safe-area-inset-top, 0px)) 10px calc(20px + env(safe-area-inset-bottom, 0px));
  gap: 2px;
  /* El menú tiene 20 ítems: en un notebook de 768 px de alto NO cabe y hay
     que hacer scroll. La barra estaba oculta (`scrollbar-width: none`), así
     que no había ninguna señal de que quedaba menú más abajo — parecía que
     los últimos ítems simplemente no existían.
     Ahora se muestra, discreta y en tono oscuro para no romper el navy. */
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.22) transparent;
}
.app-sidebar::-webkit-scrollbar        { width: 8px; }
.app-sidebar::-webkit-scrollbar-track  { background: transparent; }
.app-sidebar::-webkit-scrollbar-thumb  {
  background: rgba(255,255,255,.18);
  border-radius: var(--r-xs);
}
.app-sidebar::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.34); }

/* Utilidad · scroll horizontal con barra fina VISIBLE.
   D-2 (U-2): pestañas y menús con `overflow-x:auto` pero barra oculta —
   en móvil no había señal de que había más contenido al costado. Es la 4ª/5ª
   aparición del patrón (mismo origen que el fix del sidebar). Pulgar oscuro
   porque va sobre superficies claras (cream), no sobre el navy. */
.scroll-x-thin {
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(20,30,55,.28) transparent;
  -webkit-overflow-scrolling: touch;
}
.scroll-x-thin::-webkit-scrollbar        { height: 6px; }
.scroll-x-thin::-webkit-scrollbar-track  { background: transparent; }
.scroll-x-thin::-webkit-scrollbar-thumb  { background: rgba(20,30,55,.22); border-radius: var(--r-xs); }
.scroll-x-thin::-webkit-scrollbar-thumb:hover { background: rgba(20,30,55,.38); }

/* CRÍTICO · el menú no se podía scrollear en el PC.
   El sidebar es un flex column y sus hijos traen flex-shrink: 1 de fábrica:
   cuando el menú no cabe NO desborda, se APLASTA. Medido en 1366×700 antes
   del arreglo: ítems de 19-22 px en vez de 25, `scrollHeight === clientHeight`
   y por lo tanto ni scroll ni barra. El menú parecía completo pero apretado.
   Vale para TODOS los hijos (marca, secciones, ítems y el bloque de usuario),
   por eso el selector es directo y no una lista de clases: si mañana se
   agrega un elemento nuevo al sidebar, queda cubierto solo. */
.app-sidebar > * { flex-shrink: 0; }

.app-sidebar__brand {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px 14px;
  border-bottom: 1px solid rgba(255,255,255,.08);
  margin-bottom: 8px;
  flex-shrink: 0;
}
.app-sidebar__brand img { height: 22px; }
.app-sidebar__brand .sei-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-14);
  color: #fff;
  letter-spacing: 0.08em;
}

.sidebar-section {
  font-family: var(--font-mono);
  font-size: var(--text-10);
  letter-spacing: 0.12em;
  color: #6B6B68;
  margin: 10px 8px 2px;
  text-transform: uppercase;
  flex-shrink: 0;   /* mismo motivo que .sidebar-item: si no, se aplastan */
}

.sidebar-item {
  display: flex;
  align-items: center;
  gap: 10px;
  /* U-9 · Los ítems medían 24-25 px: muy chicos para el dedo. Subirlos a
     32 px en TODAS partes apretó el menú en notebooks, así que el objetivo
     táctil queda sólo bajo `pointer: coarse` (más abajo). */
  padding: 7px 10px;
  /* CRÍTICO · sin esto el menú NO se puede scrollear en el PC.
     El sidebar es un flex column: por defecto los hijos tienen
     flex-shrink: 1, así que cuando el menú no cabe NO desborda —
     se APLASTA. Medido en 1366×700: los ítems quedaban en 19-22 px
     en vez de 25, `scrollHeight === clientHeight` y por lo tanto no
     había nada que scrollear ni barra que mostrar. El menú se veía
     "completo" pero apretado, y los ítems de abajo quedaban ilegibles. */
  flex-shrink: 0;
  border-radius: var(--r-sm);
  font-size: var(--text-12);
  color: #C9C7C2;
  cursor: pointer;
  text-decoration: none;
  position: relative;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    transform var(--duration-instant) var(--ease-out);
  white-space: nowrap;
  overflow: hidden;
}
.sidebar-item svg { width: 14px; height: 14px; flex-shrink: 0; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.sidebar-item:active { transform: scale(0.98); }
@media (hover: hover) and (pointer: fine) {
  .sidebar-item:hover { background: rgba(255,255,255,.06); color: #fff; }
}

/* Áreas del menú · nombre + qué contiene, en dos líneas.
   "Operaciones" y "Proyectos" son indistinguibles por el nombre solo
   (una es el día a día, la otra las obras vendidas); el subtítulo lo
   resuelve sin obligar a entrar a ver. */
.sidebar-item__lbl { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.sidebar-item__lbl em {
  font-style: normal;
  font-size: var(--text-10);
  color: #7A7873;
  letter-spacing: .01em;
}
.sidebar-item--active .sidebar-item__lbl em { color: #A8A29A; }
@media (hover: hover) and (pointer: fine) {
  .sidebar-item:hover .sidebar-item__lbl em { color: #A8A29A; }
}

.sidebar-sep {
  height: 1px;
  background: rgba(255,255,255,.08);
  margin: 12px 10px;
  flex-shrink: 0;
}

.sidebar-item--active {
  background: rgba(255,207,0,.10);
  color: var(--brand-yellow);
}
.sidebar-item--active::before {
  content: "";
  position: absolute;
  left: -10px;
  top: 6px;
  bottom: 6px;
  width: 2px;
  background: var(--brand-yellow);
  border-radius: var(--r-xs);
}

.sidebar-item--coming {
  color: #6B6B68;
  cursor: default;
  pointer-events: none;
}
.sidebar-item .soon {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: calc(9px * var(--text-scale));   /* I4 · era 8px */
  padding: 2px 5px;
  border-radius: var(--r-xs);
  background: rgba(255,255,255,.06);
  color: #A0A09C;
  letter-spacing: 0.04em;
}

/* U-9 · Objetivo táctil sólo donde hay dedo: celular, iPad y notebooks
   táctiles. En esos casos el menú es un drawer a pantalla completa, así que
   los 32 px no le quitan espacio a nada. */
@media (pointer: coarse) {
  .sidebar-item {
    padding: 9px 10px;
    min-height: 32px;
  }
}

.sidebar-item__user {
  margin-top: auto;
  padding-top: 12px;
  border-top: 1px solid rgba(255,255,255,.08);
}

/* ---- Área principal ---- */
.app-main-wrapper {
  flex: 1;
  display: flex;
  flex-direction: column;
  margin-left: var(--sidebar-width);
  min-height: 100dvh;
  /* Fase 4 · Tanda A: sin esto, un hijo flex nunca baja de su ancho mínimo
     de contenido y UNA tabla ancha estira el shell entero — el título, las
     tarjetas y hasta el fondo salían del viewport en el teléfono. */
  min-width: 0;
}

/* ---- Topbar (desktop) ---- */
.app-topbar {
  height: var(--topbar-height);
  background: var(--surface-elevated);
  border-bottom: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 18px;
  position: sticky;
  top: 0;
  z-index: var(--z-topbar);
  flex-shrink: 0;
}

.topbar-crumbs {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-12);
  color: var(--text-secondary);
}
.topbar-crumbs .crumb-current {
  color: var(--text-primary);
  font-weight: 600;
}
.topbar-crumbs svg { width: 11px; height: 11px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.topbar-search {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--cream-100);
  border-radius: var(--r-pill);
  padding: 5px 12px;
  width: 260px;
  font-size: var(--text-11);
  color: var(--text-secondary);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .topbar-search:hover { background: var(--cream-200); }
}
.topbar-search svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.topbar-search .kbd {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--text-10);
  background: var(--surface-elevated);
  padding: 1px 5px;
  border-radius: var(--r-xs);
  border: 1px solid var(--border-default);
}

/* Botón "Crear" de la barra superior · gemelo del + del teléfono */
.topbar-nuevo {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--brand-navy);
  color: var(--cream-base);
  border: none;
  border-radius: var(--r-pill);
  padding: 6px 14px;
  font-family: var(--font-body);
  font-size: var(--text-12);
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
  transition: background-color var(--duration-fast) var(--ease-out),
              transform var(--duration-instant) var(--ease-out);
}
.topbar-nuevo svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.topbar-nuevo:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .topbar-nuevo:hover { background: #000; }
}

/* Lista del menú Crear · orden por frecuencia de uso real, no por módulo */
.acc-rapidas { display: flex; flex-direction: column; gap: 8px; }
.acc-rapida {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px 14px;
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  font-family: var(--font-body);
  font-size: var(--text-14);
  font-weight: 600;
  color: var(--text-primary);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out),
              transform var(--duration-instant) var(--ease-out);
}
.acc-rapida__txt { flex: 1; min-width: 0; }
.acc-rapida__ico {
  width: 38px; height: 38px; border-radius: var(--r-md);
  background: var(--cream-100);
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.acc-rapida__ico svg { width: 18px; height: 18px; }
.acc-rapida__chev { width: 15px; height: 15px; color: var(--text-disabled); flex-shrink: 0; }
.acc-rapida:active { transform: scale(0.99); }
@media (hover: hover) and (pointer: fine) {
  .acc-rapida:hover { border-color: var(--brand-navy); }
}
/* La primera es la más frecuente: se marca para poder apuntarle sin leer */
.acc-rapida:first-child .acc-rapida__ico { background: var(--brand-yellow-soft); color: var(--brand-ink); }

.topbar-bell {
  position: relative;
  color: var(--text-primary);
  cursor: pointer;
}
.topbar-bell svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.topbar-bell .bell-badge {
  position: absolute;
  top: -3px;
  right: -5px;
  background: var(--error);
  color: #fff;
  font-family: var(--font-mono);
  font-size: calc(9px * var(--text-scale));   /* I4 · era 8px */
  padding: 1px 4px;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--surface-elevated);
  line-height: 1.2;
  /* Pop con curva spring cuando aparece (nueva alerta) — usa @starting-style
     en navegadores modernos (Safari 17.4+, Chrome 117+). En viejos aparece
     instantáneo sin romper nada. */
  opacity: 1;
  transform: scale(1);
  transition:
    opacity var(--duration-base) var(--ease-spring),
    transform var(--duration-base) var(--ease-spring);
}
@starting-style {
  .topbar-bell .bell-badge { opacity: 0; transform: scale(0.3); }
}

.topbar-avatar {
  width: 28px;
  height: 28px;
  border-radius: var(--r-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-11);
  background: linear-gradient(135deg, var(--brand-yellow), var(--brand-yellow-dark));
  color: var(--brand-navy);
  cursor: pointer;
  flex-shrink: 0;
  transition: transform var(--duration-instant) var(--ease-out);
}
.topbar-avatar:active { transform: scale(0.93); }

/* ---- Contenido principal ---- */
.app-content {
  flex: 1;
  padding: 18px 22px;
  /* Fase 4 · Tanda A. Antes: `overflow-y: auto`.
     `.app-main-wrapper` es min-height, no height, así que este bloque nunca
     llegó a scrollear: quien scrollea es la página. Pero bastaba con que el
     overflow fuera distinto de `visible` para que TODO `position: sticky` de
     adentro quedara anclado a un recorte que jamás se mueve — cabeceras de
     tabla y `.action-bar` incluidas. `clip` no crea contenedor de scroll
     (sticky vuelve a funcionar) y sigue evitando que algo ancho estire la
     página a lo largo. */
  overflow-x: clip;
}

/* ============================================================
   MOBILE SHELL
   ============================================================ */

/* Sidebar drawer · tablet portrait (iPad Pro 11) + mobile */
@media (max-width: 1024px) {
  .app-sidebar { transform: translateX(-100%); transition: transform var(--duration-slow) var(--ease-drawer); }
  .app-sidebar.open { transform: translateX(0); }
  .app-sidebar-overlay {
    display: none;
    position: fixed; inset: 0;
    background: rgba(0,0,0,.45);
    z-index: calc(var(--z-sidebar) - 1);
  }
  .app-sidebar-overlay.open { display: block; }
  .app-main-wrapper { margin-left: 0; }
  .app-topbar { display: none; }
}

/* Mobile only · padding bottom para no tapar con bottom-nav */
@media (max-width: 767px) {
  .app-content {
    padding: 12px max(16px, env(safe-area-inset-left)) calc(80px + env(safe-area-inset-bottom, 0px)) max(16px, env(safe-area-inset-right));
  }
}

/* Tablet (768-1024) · sin bottom-nav · padding normal */
@media (min-width: 768px) and (max-width: 1024px) {
  .app-content {
    padding: 18px max(20px, env(safe-area-inset-left)) 32px max(20px, env(safe-area-inset-right));
  }
  /* Ocultar bottom-nav en tablet (iPad usa solo sidebar drawer) */
  .m-bottomnav { display: none !important; }
  /* m-header sí visible en tablet */
  .m-header { display: flex !important; }
}

/* ---- Mobile header ---- */
/* Respeta el safe-area-inset-top del iPhone (notch / Dynamic Island).
   El viewport-fit=cover del index.php deja el contenido ir bajo la barra
   de estado, y este padding compensa para que los íconos queden tocables. */
.m-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: env(safe-area-inset-top, 0) 16px 0;
  /* Padding-left/right también respetan safe-area (iPhone en landscape) */
  padding-left:  max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
  min-height: calc(var(--m-header-height) + env(safe-area-inset-top, 0px));
  box-sizing: border-box;
  background: var(--surface-elevated);
  border-bottom: 1px solid var(--border-subtle);
  position: sticky;
  top: 0;
  z-index: var(--z-topbar);
  flex-shrink: 0;
}
.m-header__ham {
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--brand-navy);
  cursor: pointer;
  border-radius: var(--r-sm);
  flex-shrink: 0;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    transform var(--duration-instant) var(--ease-out);
}
.m-header__ham:active {
  transform: scale(0.92);
  background: var(--cream-100);
}
.m-header__ham svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.m-header__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-18);
  flex: 1;
}
.m-header__icons {
  display: flex;
  align-items: center;
  gap: 2px;
  color: var(--brand-navy);
}
/* v61 · cada ícono de la cabecera es un blanco de 40 px (el dibujo sigue en
   20): con el dedo en terreno, 20 px al lado de la batería no se acertaban. */
.m-header__icons > * {
  min-width: 40px;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.m-header__icon {
  background: transparent;
  border: none;
  padding: 0;
  color: inherit;
  cursor: pointer;
  border-radius: var(--r-sm);
}
.m-header__icons svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.m-header__bell { position: relative; }
.m-header__bell-dot {
  position: absolute;
  top: -3px;
  right: -3px;
  background: var(--error);
  color: #fff;
  font-family: var(--font-mono);
  font-size: var(--text-10);
  font-weight: 700;
  padding: 1px 4px;
  border-radius: var(--r-pill);
  border: 2px solid var(--surface-elevated);
  line-height: 1;
  /* Pop spring · idéntico al bell-badge del topbar */
  opacity: 1;
  transform: scale(1);
  transition:
    opacity var(--duration-base) var(--ease-spring),
    transform var(--duration-base) var(--ease-spring);
}
@starting-style {
  .m-header__bell-dot { opacity: 0; transform: scale(0.3); }
}

/* ---- Bottom nav (mobile) ---- */
/* Respeta safe-area-inset-bottom (home indicator del iPhone) y los lados. */
.m-bottomnav {
  background: var(--surface-elevated);
  border-top: 1px solid var(--border-subtle);
  padding: 8px max(16px, env(safe-area-inset-right)) max(18px, calc(8px + env(safe-area-inset-bottom))) max(16px, env(safe-area-inset-left));
  display: flex;
  justify-content: space-around;
  align-items: center;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: var(--z-topbar);
  min-height: calc(var(--m-bottomnav-height) + env(safe-area-inset-bottom, 0px));
  box-sizing: border-box;
}
.m-bottomnav__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-size: var(--text-10);
  color: var(--text-secondary);
  cursor: pointer;
  text-decoration: none;
  padding: 4px 8px;
  transition:
    color var(--duration-fast) var(--ease-out),
    transform var(--duration-instant) var(--ease-out);
}
.m-bottomnav__item:active { transform: scale(0.93); }
.m-bottomnav__item svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.m-bottomnav__item--active {
  color: var(--brand-ink);
  font-weight: 600;
}
.m-bottomnav__fab {
  background: var(--brand-yellow);
  width: 52px;
  height: 52px;
  border-radius: var(--r-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: -28px;
  box-shadow: var(--shadow-yellow);
  color: var(--brand-navy);
  cursor: pointer;
  border: none;
  flex-shrink: 0;
  /* El botón más tocado de toda la app (nuevo presupuesto / nuevo gasto).
     Debe responder con punch claro. */
  transition:
    transform var(--duration-instant) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out);
}
.m-bottomnav__fab:active {
  transform: scale(0.92);
  /* Sombra cercana + sutil al presionar (simula "hundir el botón en el material") */
  box-shadow:
    var(--shadow-sm)
    0 2px 6px rgba(255, 207, 0, 0.25),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.40);
}
.m-bottomnav__fab svg { width: 24px; height: 24px; stroke: currentColor; fill: none; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }

/* ============================================================
   UTILIDADES DE LAYOUT
   ============================================================ */
.row { display: flex; align-items: center; }
.col { display: flex; flex-direction: column; }
.spacer { flex: 1; }
.grid { display: grid; gap: var(--space-3); }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }
.grid--4 { grid-template-columns: repeat(4, 1fr); }
/* Grids fluidas sin breakpoints — siempre llenan el contenedor con
   tantas columnas como entren con minimum width. Preferido para listas
   de cards de tamaño homogéneo (proyectos, clientes, materiales). */
.grid--auto       { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid--auto-sm    { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.grid--auto-lg    { grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); }

@media (max-width: 767px) {
  .grid--4 { grid-template-columns: repeat(2, 1fr); }
  .grid--3 { grid-template-columns: repeat(1, 1fr); }
}

/* ---- Toast container ---- */
.toast-container {
  position: fixed;
  /* En mobile el bottom-nav mide ~70px + safe-area-inset-bottom.
     En desktop no hay bottom-nav, pero env() devuelve 0, así que
     la fórmula sigue funcionando. */
  bottom: calc(80px + env(safe-area-inset-bottom, 0px));
  right: max(16px, env(safe-area-inset-right));
  left: max(16px, env(safe-area-inset-left));
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
  align-items: flex-end;
}
@media (min-width: 768px) {
  .toast-container { bottom: 24px; right: 24px; }
}

/* ---- Page header helper ---- */
.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: var(--space-4);
  gap: var(--space-3);
}
.page-header__title {
  font-family: var(--font-display);
  font-weight: 700;
  /* clamp fluid responsivo: 22px en mobile (375px) → 28px en desktop */
  font-size: clamp(22px, 2.8vw + 14px, 28px);
  margin: 0 0 2px;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.page-header__sub {
  font-size: var(--text-12);
  color: var(--text-secondary);
  margin: 0;
  text-wrap: pretty;
}
.page-header__actions {
  display: flex;
  gap: var(--space-2);
  flex-shrink: 0;
  flex-wrap: wrap;
}
@media (max-width: 767px) {
  .page-header { flex-direction: column; align-items: flex-start; gap: 10px; }
  .page-header__actions { width: 100%; }
  .page-header__actions .tabs { flex: 1; }
  .page-header__actions .btn { font-size: var(--text-12); padding: 6px 10px; }
}

/* ============================================================
   OCULTAR ELEMENTOS MÓVILES EN DESKTOP (≥1025)
   - Mobile (≤767): m-header + m-bottomnav visibles
   - Tablet (768-1024): m-header visible, m-bottomnav oculto (iPad usa sidebar drawer)
   - Desktop (≥1025): m-header + m-bottomnav ocultos, sidebar fija + topbar
   ============================================================ */
@media (min-width: 1025px) {
  .m-header    { display: none !important; }
  .m-bottomnav { display: none !important; }
}

/* ════════════════════════════════════════════════════════════
   REFINAMIENTO v3 · 2026-08-10 · shell
   Bloque aditivo. No renombra ni elimina clases.
   ════════════════════════════════════════════════════════════ */

/* ────────────────────────────────────────────────────────────
   1 · EL LIENZO
   El crema plano no le daba nada al vidrio ni a las sombras para
   apoyarse: una tarjeta blanca sobre crema plano con blur se ve
   idéntica sin blur. Dos halos amplísimos —cálido de marca arriba
   a la izquierda, frío abajo a la derecha— dan profundidad sin
   leerse como gradiente. Sigue siendo crema cálido.
   ──────────────────────────────────────────────────────────── */
body, .app-shell { background: var(--app-bg); background-attachment: fixed; }
.app-shell { min-height: 100dvh; }

/* ────────────────────────────────────────────────────────────
   2 · BARRA LATERAL
   ──────────────────────────────────────────────────────────── */
.app-sidebar {
  /* Navy con una veta de luz al tope: el panel deja de ser un
     rectángulo negro y pasa a tener orientación física. */
  background:
    linear-gradient(180deg, #212120 0%, var(--brand-navy) 26%, #151513 100%);
  border-right: 1px solid rgba(255,255,255,.06);
}
/* Los rótulos de sección estaban en #6B6B68 sobre navy: 3.2:1, falla AA
   en texto de 10px. #8F8F88 da 5.4:1 y sigue leyéndose como rótulo. */
.sidebar-section { color: #8F8F88; }
.sidebar-item .soon { color: #B3B0A8; }
.sidebar-item--coming { color: #83817B; }
/* U-4 · El subtítulo del menú («la plata», «las obras»). #8B8880 daba 4,82
   sobre el fondo del menú —pasable— pero **3,06 sobre el ítem ACTIVO**, que
   lleva un velo amarillo encima. O sea que el único que fallaba era justo el
   de la sección donde estás parado. #ADAAA2 da 7,35 y 4,67: pasa en los dos. */
.sidebar-item__lbl em { color: #ADAAA2; }

.sidebar-item { border-radius: var(--r-md); }
/* El ítem activo pasa de "fondo amarillo al 10%" a material: velo
   horizontal que se apaga hacia la derecha + barra de luz a la
   izquierda con halo. Se ve de reojo cuál sección está abierta. */
.sidebar-item--active {
  background: linear-gradient(90deg, rgba(255,207,0,.16), rgba(255,207,0,.04) 70%, transparent);
  color: var(--brand-yellow);
  box-shadow: inset 0 .5px 0 rgba(255,255,255,.06);
}
.sidebar-item--active::before {
  left: 0; top: 5px; bottom: 5px; width: 3px;
  background: var(--brand-yellow);
  border-radius: 0 3px 3px 0;
  box-shadow: var(--shadow-card);
}
@media (hover: hover) and (pointer: fine) {
  .sidebar-item:hover { background: rgba(255,255,255,.07); }
  .sidebar-item--active:hover {
    background: linear-gradient(90deg, rgba(255,207,0,.22), rgba(255,207,0,.06) 70%, transparent);
  }
}
.app-sidebar__brand .sei-name { letter-spacing: .10em; }

/* Modo riel · barra lateral colapsada a iconos.
   El CSS está completo; falta que la app agregue/quite la clase
   `.app-sidebar--rail` y persista la preferencia (ver lista de
   pendientes de código). El tooltip usa el atributo `title`. */
/* El botón que enciende el riel · vive en la fila de la marca */
.sidebar-rail-btn {
  margin-left: auto;
  width: 26px; height: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  border: none;
  border-radius: var(--r-sm, 6px);
  background: transparent;
  color: rgba(255,255,255,.55);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}
.sidebar-rail-btn svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
@media (hover: hover) and (pointer: fine) {
  .sidebar-rail-btn:hover { background: rgba(255,255,255,.10); color: #fff; }
}
/* En pantalla angosta el menú es un cajón que se abre y se cierra entero:
   contraerlo a iconos ahí no significa nada. */
@media (max-width: 1024px) { .sidebar-rail-btn { display: none; } }

.app-sidebar--rail {
  width: 60px;
  padding-left: 8px; padding-right: 8px;
  overflow: visible;
}
/* Con el menú contraído la marca queda sola y el botón, centrado debajo */
.app-sidebar--rail .app-sidebar__brand { flex-direction: column; gap: 6px; padding-bottom: 10px; }
.app-sidebar--rail .sidebar-rail-btn { margin-left: 0; }
/* Al fondo queda el avatar y el botón de salir; el nombre y el correo no
   entran en 60px y se cortarían a la mitad. */
.app-sidebar--rail .sidebar-user { flex-direction: column; gap: 10px; padding-left: 0; padding-right: 0; }
.app-sidebar--rail .sidebar-user__id { display: none; }
.app-sidebar--rail ~ .app-main-wrapper,
.app-shell:has(.app-sidebar--rail) .app-main-wrapper { margin-left: 60px; }
.app-sidebar--rail .sidebar-item { justify-content: center; padding: 9px 0; }
.app-sidebar--rail .sidebar-item__lbl,
.app-sidebar--rail .sidebar-item .soon,
.app-sidebar--rail .sidebar-section,
.app-sidebar--rail .app-sidebar__brand .sei-name { display: none; }
.app-sidebar--rail .sidebar-sep { margin: 10px 6px; }
.app-sidebar--rail .sidebar-item svg { width: 17px; height: 17px; }
.app-sidebar--rail .sidebar-item--active::before { top: 3px; bottom: 3px; }
.app-sidebar,
.app-sidebar .sidebar-item__lbl { transition: width var(--duration-slow) var(--ease-drawer); }

/* ────────────────────────────────────────────────────────────
   3 · BARRA SUPERIOR — vidrio de verdad
   Es fija y el contenido pasa por debajo al hacer scroll: es
   exactamente el caso donde `backdrop-filter` rinde. Con borde de
   luz arriba, si no parece blur sucio en vez de vidrio.
   ──────────────────────────────────────────────────────────── */
.app-topbar, .m-header {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--border-subtle);
  box-shadow: var(--glass-line);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .app-topbar, .m-header { background: var(--surface-elevated); }
}
.topbar-search {
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  box-shadow: inset 0 1px 2px rgba(26,26,24,.05);
  min-height: 30px;
}
.topbar-search .kbd { color: var(--text-tertiary); box-shadow: 0 1px 0 rgba(26,26,24,.06); }
.topbar-nuevo {
  background: linear-gradient(180deg, #2E2E2B, var(--brand-navy));
  box-shadow: var(--shadow-sm)
              inset 0 1px 0 rgba(255,255,255,.10);
}
.topbar-avatar {
  box-shadow: 0 1px 2px rgba(230,187,0,.30), inset 0 1px 0 rgba(255,255,255,.55);
}
.topbar-crumbs { color: var(--text-secondary); }

/* ────────────────────────────────────────────────────────────
   4 · BARRA INFERIOR — vidrio + separación del botón principal
   Problema en terreno: con guante el pulgar apuntaba al FAB y
   tocaba el ítem de al lado. El FAB sube 4px más, gana un aro de
   separación del color de la barra y los ítems vecinos reservan
   un carril propio de 44px.
   ──────────────────────────────────────────────────────────── */
.m-bottomnav {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-top: 1px solid var(--border-subtle);
  box-shadow: 0 -1px 0 rgba(255,255,255,.7), 0 -8px 28px rgba(26,26,24,.07);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .m-bottomnav { background: var(--surface-elevated); }
}
.m-bottomnav__item {
  min-width: var(--touch-min);
  min-height: var(--touch-min);
  justify-content: center;
  border-radius: var(--r-md);
}
/* El item activo de la barra inferior EN EL TELEFONO. Daba 1,8:1 — o sea
   que en terreno no se veia cual seccion estaba abierta. */
.m-bottomnav__item--active { color: var(--brand-ink); }
.m-bottomnav__fab {
  width: 56px; height: 56px;
  margin-top: -32px;
  background: linear-gradient(180deg, var(--brand-yellow-300), var(--brand-yellow) 50%, var(--brand-yellow-dark));
  /* Aro del color de la barra: separa el FAB de los ítems vecinos
     para que el dedo con guante no toque el destino equivocado. */
  box-shadow:
    0 0 0 5px var(--cream-base),
    var(--shadow-yellow),
    inset 0 1px 0 rgba(255,255,255,.55);
}
.m-bottomnav__fab:active {
  box-shadow:
    0 0 0 5px var(--cream-base),
    0 1px 2px rgba(230,187,0,.30),
    inset 0 1px 3px rgba(120,95,0,.24);
}
/* Cualquier acción fija de página deja libre la barra inferior y el
   home indicator: nunca queda pegada al borde. */
.action-bar, .sticky-actions {
  position: sticky; bottom: 0; z-index: 2;
  display: flex; gap: 10px;
  padding: 12px max(16px, env(safe-area-inset-left))
           calc(12px + env(safe-area-inset-bottom, 0px)) max(16px, env(safe-area-inset-right));
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-top: 1px solid var(--border-subtle);
  margin-inline: calc(-1 * max(16px, env(safe-area-inset-left)));
}
/* Sin blur el fondo translúcido deja leer el contenido por debajo: se
   opaca, igual que las otras superficies de vidrio del sistema. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .action-bar, .sticky-actions { background: var(--surface-elevated); }
}
@media (max-width: 767px) {
  /* 68px de barra + 56px de FAB que sobresale + aire */
  .app-content {
    padding-bottom: calc(104px + env(safe-area-inset-bottom, 0px));
  }
  .action-bar, .sticky-actions { bottom: calc(var(--m-bottomnav-height) + env(safe-area-inset-bottom, 0px)); }
}

/* ────────────────────────────────────────────────────────────
   5 · CABECERA DE PÁGINA — jerarquía por contraste, no por líneas
   ──────────────────────────────────────────────────────────── */
.page-header { margin-bottom: var(--space-5); align-items: flex-start; }
.page-header__title { letter-spacing: -0.028em; line-height: 1.12; }
.page-header__sub { font-size: var(--text-12); color: var(--text-secondary); }
.page-header__actions { align-items: center; gap: 10px; }

/* El contenido respira más entre bloques y menos dentro de ellos:
   el aire es lo que agrupa. */
.app-content { padding: 22px 24px 40px; }
@media (min-width: 1440px) { .app-content { padding: 26px 32px 48px; } }

/* ────────────────────────────────────────────────────────────
   6 · ENLACES
   ──────────────────────────────────────────────────────────── */
a { color: var(--text-primary); text-underline-offset: 3px; text-decoration-thickness: 1px; }
a:hover { color: var(--brand-navy-medium); }
[data-theme="dark"] a:hover { color: var(--brand-yellow); }
.app-sidebar a { color: inherit; }

/* ────────────────────────────────────────────────────────────
   7 · COMPENSACIONES DE MODO OSCURO
   El botón "Crear" era navy sobre navy: desaparecía. En oscuro
   invierte a crema con tinta navy — sigue siendo el gemelo del
   FAB del teléfono, y sigue sin competir con el amarillo.
   ──────────────────────────────────────────────────────────── */
/* ARREGLADO 8-sep-2026 · el botón principal de la app era invisible.
   La regla de acá abajo ya existía y la intención era la correcta —"crema
   con tinta navy"—, pero usaba `--cream-100` y `--cream-200`, que EN MODO
   OSCURO ya están invertidos: valen #1F1F1A y #26261F, o sea casi negro.
   Resultado: tinta navy #1A1A18 sobre fondo #1F1F1A. Medido en producción:
   **1,10 : 1** de contraste, cuando el mínimo legible es 4,5. El botón
   "Crear" —el único punto de entrada a crear cualquier cosa— se veía como un
   rectángulo vacío. El parche estaba escrito y nunca llegó a la pantalla.
   Van valores claros fijos: el botón tiene que ser claro SIEMPRE, y por eso
   no puede salir de un token que se da vuelta con el tema. */
[data-theme="dark"] .topbar-nuevo {
  background: linear-gradient(180deg, #F5F5F0, #E4E4DC);
  color: #1A1A18;
  box-shadow: 0 1px 2px rgba(0,0,0,.40), inset 0 1px 0 rgba(255,255,255,.35);
}
@media (hover: hover) and (pointer: fine) {
  [data-theme="dark"] .topbar-nuevo:hover { background: #F0EEE7; }
}
[data-theme="dark"] .app-sidebar {
  background: linear-gradient(180deg, #1C1C1A 0%, #131311 60%, #0E0E0C 100%);
  border-right-color: rgba(255,255,255,.05);
}
[data-theme="dark"] .topbar-search { box-shadow: inset 0 1px 2px rgba(0,0,0,.35); }
[data-theme="dark"] .topbar-search .kbd { background: var(--cream-200); border-color: var(--border-default); }
[data-theme="dark"] .m-bottomnav {
  box-shadow: 0 -1px 0 rgba(255,255,255,.06), 0 -8px 28px rgba(0,0,0,.45);
}
[data-theme="dark"] .m-bottomnav__fab { box-shadow: 0 0 0 5px var(--cream-base), var(--shadow-yellow); }
[data-theme="dark"] .topbar-avatar { box-shadow: 0 1px 3px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.45); }

/* PARCHE v3.1 · la tecla ⌘K daba 4,38:1 en oscuro sobre cream-200.
   Sube a secondary: 6,75:1. */
[data-theme="dark"] .topbar-search .kbd { color: var(--text-secondary); }


/* ────────────────────────────────────────────────────────────
   U-1 · PARCHE 8-sep-2026 · blancos de toque en pantalla táctil

   Medido en las 30 pantallas: seis controles por debajo de 28 px.
   Contraer el menú 26×26, el globo de notificaciones 16×16, cerrar
   aviso 24×24, los enlaces «Ver…» de 20 px de alto.

   Con mouse eso se acierta. Con el dedo, en obra, con guante o con la
   mano sucia, no — y son justo los controles que se usan en terreno.

   No se agrandan los íconos: se agranda el ÁREA que responde al toque,
   con un pseudo-elemento que no ocupa espacio ni corre el diseño. Sólo
   donde el puntero es grueso (`pointer: coarse`), así en escritorio la
   densidad queda igual.
   ──────────────────────────────────────────────────────────── */
@media (pointer: coarse) {
  .sidebar-rail-btn,
  .icon-btn-mini,
  .alerta-archivar,
  .topbar-badge,
  .fin-hero__eye,
  .modal__x {
    position: relative;
  }
  .sidebar-rail-btn::after,
  .icon-btn-mini::after,
  .alerta-archivar::after,
  .topbar-badge::after,
  .fin-hero__eye::after,
  .modal__x::after {
    content: "";
    position: absolute;
    /* Centrado sobre el control, 44 px de lado: la medida que usan iOS y
       Android como mínimo para un blanco que se toca con el dedo. */
    top: 50%; left: 50%;
    width: 44px; height: 44px;
    transform: translate(-50%, -50%);
  }
  /* Los enlaces de acción medían 20 px de alto. El padding vertical los
     sube sin moverlos de lugar: el texto queda donde estaba. */
  .area__act, .panel__act { padding: 12px 0; margin: -12px 0; }
}

/* ═══ css/dashboards.css ═══ */
/* ============================================================
   SEINGELEC · Dashboards por área
   Patrones de contenido diseñados en Claude Design (proyecto v12).
   Se adaptó: se quitó el "chrome" del mockup (.frame, .d-sidebar, .d-topbar,
   .m-app…) porque la app ya tiene su shell; se quitaron los overrides de
   :root y body. Usa los tokens de tokens.css.
   ============================================================ */

/* ── animaciones de entrada (stagger controlado por .dash-stagger) ── */
@keyframes dashRiseIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@keyframes dashGrowX  { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.dash-stagger > * { animation: dashRiseIn .5s var(--ease-out) both; }
.dash-stagger > *:nth-child(1) { animation-delay: .02s; }
.dash-stagger > *:nth-child(2) { animation-delay: .08s; }
.dash-stagger > *:nth-child(3) { animation-delay: .14s; }
.dash-stagger > *:nth-child(4) { animation-delay: .20s; }
.dash-stagger > *:nth-child(5) { animation-delay: .26s; }
.dash-stagger > *:nth-child(6) { animation-delay: .32s; }

.pcard__fill, .obra__fill, .funnel__bar, .gbar__f, .aging__col {
  will-change: transform;
}
.obra__fill, .gbar__f { transform-origin: left; animation: dashGrowX .8s var(--ease-out) both; animation-delay: .25s; }
.funnel__bar { transform-origin: left; animation: dashGrowX .7s var(--ease-out) both; }
.funnel__row:nth-child(1) .funnel__bar { animation-delay: .15s; }
.funnel__row:nth-child(2) .funnel__bar { animation-delay: .23s; }
.funnel__row:nth-child(3) .funnel__bar { animation-delay: .31s; }
.funnel__row:nth-child(4) .funnel__bar { animation-delay: .39s; }
.funnel__row:nth-child(5) .funnel__bar { animation-delay: .47s; }

/* hover premium en superficies */
.panel, .kpiv, .fin-box, .ot-stat, .tile {
  transition: transform var(--duration-base) var(--ease-out),
              box-shadow var(--duration-base) var(--ease-out),
              border-color var(--duration-base) var(--ease-out);
}
.panel:hover, .kpiv:hover, .fin-box:hover, .tile:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-hover);
}
.att, .ot, .per-row, .obra {
  transition: background var(--duration-fast) var(--ease-out);
  border-radius: var(--r-sm); padding-left: 8px; padding-right: 8px;
  margin-left: -8px; margin-right: -8px;
}
.att:hover, .ot:hover, .per-row:hover, .obra:hover { background: var(--cream-50); }

/* ── saludo / encabezado ── */
.home-head { display: flex; align-items: flex-end; gap: 14px; margin-bottom: 16px; }
.home-hello { font-family: var(--font-display); font-weight: 700; font-size: var(--text-24); letter-spacing: -.02em; margin: 0; }
.home-date { font-size: var(--text-12); color: var(--text-secondary); margin: 0 0 2px; }
.home-head .spacer { flex: 1; }

/* ── pestañas de portada de área ── */
.area-tabs { display: inline-flex; gap: 2px; background: var(--cream-100); border-radius: var(--r-sm); padding: 3px; margin-bottom: 16px; }
.area-tabs button { font-family: var(--font-body); font-size: var(--text-12); font-weight: 600; color: var(--text-secondary); background: transparent; border: none; border-radius: var(--r-sm); padding: 6px 14px; cursor: pointer; }
.area-tabs button[aria-pressed="true"] { background: var(--surface-elevated); color: var(--text-primary); box-shadow: var(--shadow-sm); }

/* ── rejilla de áreas (Inicio) ── */
.area-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 18px; }
.area { background: var(--surface-elevated); border: 1px solid var(--border-subtle); border-radius: var(--r-md); padding: 14px 15px; display: flex; flex-direction: column; gap: 9px; position: relative; min-height: 132px; transition: box-shadow var(--duration-base) var(--ease-out), transform var(--duration-base) var(--ease-out); }
.area:hover { box-shadow: var(--shadow-hover); transform: translateY(-1px); }
.area--alert { border-color: var(--error); border-left: 3px solid var(--error); background: linear-gradient(180deg, var(--error-soft), var(--surface-elevated) 60%); }
.area--warn  { border-color: var(--warning); border-left: 3px solid var(--warning); background: linear-gradient(180deg, var(--warning-soft), var(--surface-elevated) 60%); }
.area--ok    { border-left: 3px solid var(--success); }
.area--empty { border-style: dashed; border-color: var(--border-default); }
.area--focus { grid-column: span 2; }
.area__top { display: flex; align-items: center; gap: 9px; }
.area__ico { width: 30px; height: 30px; border-radius: var(--r-sm); display: inline-flex; align-items: center; justify-content: center; background: var(--cream-100); color: var(--brand-navy); flex-shrink: 0; }
.area__ico svg { width: 16px; height: 16px; }
.area__ico--alert { background: var(--error-soft); color: var(--error); }
.area__ico--warn  { background: var(--warning-soft); color: var(--warning); }
.area__ico--ok    { background: var(--success-soft); color: var(--success); }
.area__ico--brand { background: var(--brand-yellow-soft); color: var(--brand-ink); }
.area__ico--info  { background: var(--info-soft); color: var(--info); }
.area__name { font-weight: 600; font-size: var(--text-13); }
.area__pill { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; font-size: var(--text-10); font-weight: 600; font-family: var(--font-mono); letter-spacing: .03em; text-transform: uppercase; padding: 3px 8px; border-radius: var(--r-pill); background: var(--cream-100); color: var(--text-secondary); }
.area__pill::before { content: ""; width: 6px; height: 6px; border-radius: var(--r-pill); background: currentColor; box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 18%, transparent); }
.pill--ok    { background: var(--success-soft); color: var(--success); }
.pill--warn  { background: var(--warning-soft); color: var(--warning); }
.pill--alert { background: var(--error-soft); color: var(--error); }
.pill--empty { background: var(--cream-100); color: var(--text-disabled); }
.pill--empty::before { box-shadow: none; background: var(--text-disabled); }
.area__figure { font-family: var(--font-display); font-weight: 700; font-size: var(--text-26); line-height: 1; letter-spacing: -.02em; }
.area--focus .area__figure { font-size: var(--text-32); }
.area__figure .u { font-size: var(--text-14); color: var(--text-secondary); font-weight: 600; margin-left: 3px; }
.area__ctx { font-size: var(--text-12); color: var(--text-secondary); line-height: 1.35; margin: 0; }
.area__ctx b { color: var(--text-primary); font-weight: 600; }
.area__foot { margin-top: auto; display: flex; align-items: center; gap: 5px; }
.area__act { display: inline-flex; align-items: center; gap: 4px; font-size: var(--text-12); font-weight: 600; color: var(--brand-navy); text-decoration: none; background: none; border: none; cursor: pointer; padding: 0; }
.area__act svg { width: 12px; height: 12px; }
.area__act:hover { color: var(--brand-ink); }
.area__empty { display: flex; flex-direction: column; gap: 6px; justify-content: center; flex: 1; }
.area__empty p { font-size: var(--text-12); color: var(--text-disabled); margin: 0; line-height: 1.35; }
.area--wide-figure { display: flex; flex-direction: row; align-items: center; gap: 18px; }

/* ── paneles genéricos ── */
.home-cols { display: grid; grid-template-columns: 1.4fr 1fr; gap: 14px; }
.panel { background: var(--surface-elevated); border: 1px solid var(--border-subtle); border-radius: var(--r-md); padding: 14px 16px; }
.panel--accent { border-top: 3px solid var(--brand-yellow); }
.panel__h { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; }
.panel__h .t { font-family: var(--font-display); font-weight: 700; font-size: var(--text-15); }
.panel__h .n { margin-left: auto; font-family: var(--font-mono); font-size: var(--text-11); color: var(--error); background: var(--error-soft); padding: 2px 8px; border-radius: var(--r-pill); font-weight: 600; }
.panel__h .n--muted { color: var(--text-secondary); background: var(--cream-100); }
.panel__act { margin-left: auto; font-size: var(--text-12); font-weight: 600; color: var(--brand-navy); text-decoration: none; display: inline-flex; align-items: center; gap: 4px; background: none; border: none; cursor: pointer; }
.panel__act svg { width: 12px; height: 12px; }

/* ── "Atención hoy" ── */
.att { display: flex; align-items: center; gap: 11px; padding: 9px 0; border-top: 1px solid var(--border-subtle); }
.att:first-of-type { border-top: none; }
.att__ico { width: 26px; height: 26px; border-radius: var(--r-sm); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.att__ico svg { width: 14px; height: 14px; }
.att--cobro .att__ico { background: var(--error-soft); color: var(--error); }
.att--pago  .att__ico { background: var(--warning-soft); color: var(--warning); }
.att--ot    .att__ico { background: var(--error-soft); color: var(--error); }
.att--info  .att__ico { background: var(--info-soft); color: var(--info); }
.att--ok    .att__ico { background: var(--success-soft); color: var(--success); }
.att__body { flex: 1; min-width: 0; }
.att__t { font-size: var(--text-13); font-weight: 600; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.att__s { font-size: var(--text-11); color: var(--text-secondary); }
.att__amt { font-family: var(--font-mono); font-size: var(--text-13); font-weight: 600; text-align: right; white-space: nowrap; }
.att__amt.neg { color: var(--error); }

/* ════════════════════════════════════════════════════════════
   v45 · INICIO · la composición de la maqueta
   ────────────────────────────────────────────────────────────
   `.area` sigue existiendo: la usan las portadas de área. Lo que cambia
   es el Inicio, que pasa de cinco tarjetas con degradado y borde de color
   a cuatro cifras planas más un gráfico y una lista.

   El porqué del cambio: con cinco tarjetas gritando (cada una con su
   gradiente, su borde de 3 px y su pastilla de estado) ninguna resalta.
   El color vuelve a significar algo cuando lo usa UNA sola cosa —acá, la
   barrita de arriba, y sólo cuando hay algo atrasado.
   ════════════════════════════════════════════════════════════ */
.hk-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(186px, 1fr)); gap: 12px; margin-bottom: 14px; }
.hk {
  background: var(--surface-elevated); border: 1px solid var(--border-subtle);
  border-radius: var(--r-md); padding: 13px 15px 14px;
  display: flex; flex-direction: column; gap: 3px;
  position: relative; overflow: hidden; min-height: 126px;
  text-decoration: none; color: inherit;
  transition: box-shadow var(--duration-base) var(--ease-out), transform var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out);
}
.hk:hover { box-shadow: var(--shadow-hover); transform: translateY(-1px); border-color: var(--border-default); }
.hk:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: 2px; }

/* La cinta de estado. Es lo ÚNICO con color fuerte en la fila, así que
   cuando aparece se ve sin buscarla. Al día no lleva nada: "todo bien"
   no necesita un adorno verde para entenderse. */
.hk__cinta { position: absolute; inset: 0 0 auto 0; height: 3px; }
.hk--alert .hk__cinta { background: var(--error); }
.hk--warn  .hk__cinta { background: var(--warning); }

.hk__rot {
  font-size: var(--text-10); letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-secondary); font-weight: 600; font-family: var(--font-mono);
  display: flex; align-items: center; gap: 6px;
}
.hk__rot svg { width: 12px; height: 12px; opacity: .75; }
.hk__num {
  font-family: var(--font-display); font-weight: 800; font-size: var(--text-24);
  line-height: 1.12; letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.hk--alert .hk__num { color: var(--error); }
.hk__pie { font-size: var(--text-11); color: var(--text-secondary); line-height: 1.35; }
.hk__pie b { color: var(--text-primary); font-weight: 600; }
.hk__pie .mal { color: var(--error); font-weight: 600; }
.hk__pie .ojo { color: var(--warning); font-weight: 600; }
.hk__spark { display: block; margin-top: auto; padding-top: 8px; }
.hk__spark .spark { width: 100%; height: 26px; display: block; }
.hk__bar { margin-top: auto; padding-top: 10px; }
.hk__bar i { display: block; height: 6px; background: var(--cream-200); border-radius: var(--r-pill); overflow: hidden; }
.hk__bar i > i { display: block; height: 100%; border-radius: var(--r-pill); }

/* Vacía / no cargó: el borde punteado dice "acá falta algo" antes de leer. */
.hk--empty { border-style: dashed; border-color: var(--border-default); }
.hk--empty .hk__num { font-size: var(--text-13); font-family: var(--font-body); font-weight: 600; color: var(--text-disabled); line-height: 1.35; }
.hk__act { margin-top: auto; padding-top: 10px; display: inline-flex; align-items: center; gap: 4px; font-size: var(--text-12); font-weight: 600; color: var(--brand-navy); background: none; border: none; padding-left: 0; cursor: pointer; font-family: var(--font-body); }
.hk__act svg { width: 12px; height: 12px; }

/* ── el gráfico de área ── */
.sviz { width: 100%; height: auto; display: block; }
.sviz__grilla { stroke: var(--border-subtle); stroke-width: 1; }
.sviz__eje { font-family: var(--font-mono); font-size: var(--text-10); fill: var(--text-secondary); }
.sviz-leg { display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 4px; }
.sviz-leg__it { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-12); }
.sviz-leg__it i { width: 9px; height: 9px; border-radius: var(--r-xs); flex: none; }
.sviz-leg__n { color: var(--text-secondary); }
.sviz-leg__it b { font-family: var(--font-mono); font-weight: 700; font-variant-numeric: tabular-nums; }

/* ── Atención hoy · la acción a la derecha ── */
.att__act {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-10); font-weight: 700; padding: 3px 9px;
  border-radius: var(--r-pill); font-family: var(--font-mono);
  letter-spacing: .02em; text-transform: uppercase; white-space: nowrap;
}
.att__act::before { content: ""; width: 6px; height: 6px; border-radius: var(--r-pill); background: currentColor; flex: none; }
.att__act--ma { background: var(--error-soft);   color: var(--error); }
.att__act--av { background: var(--warning-soft); color: var(--warning); }
.att__act--in { background: var(--info-soft);    color: var(--info); }
.att--cot .att__ico { background: var(--info-soft); color: var(--info); }
/* La fila entera es el boton: un link envolviendo todo se pincha donde
   sea, que es lo que la mano intenta hacer igual. */
a.att { text-decoration: none; color: inherit; }
a.att:hover .att__t { text-decoration: underline; text-underline-offset: 2px; }
.att--mas { padding: 9px 0 0; font-size: var(--text-11); color: var(--text-secondary); border-top: 1px solid var(--border-subtle); }

/* ── atajos a lo ancho ── */
.shorts--fila { grid-template-columns: repeat(6, 1fr); }
.shorts--fila .short--primary { grid-column: auto; }

/* ── atajos ── */
.shorts { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.short { display: flex; align-items: center; gap: 9px; padding: 11px 12px; border-radius: var(--r-sm); border: 1.5px solid var(--border-default); background: var(--surface-elevated); cursor: pointer; font-size: var(--text-13); font-weight: 600; color: var(--text-primary); transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), transform var(--duration-instant) var(--ease-out); font-family: var(--font-body); text-align: left; }
.short:hover { border-color: var(--brand-yellow); background: var(--brand-yellow-soft); }
.short:active { transform: scale(.97); }
.short__ico { width: 26px; height: 26px; border-radius: var(--r-sm); background: var(--cream-100); display: inline-flex; align-items: center; justify-content: center; color: var(--brand-navy); flex-shrink: 0; }
.short__ico svg { width: 15px; height: 15px; }
.short--primary { grid-column: span 2; background: var(--brand-yellow); border-color: var(--brand-yellow); color: var(--brand-navy); }
.short--primary .short__ico { background: rgba(0,0,0,.08); }
.short--primary:hover { background: var(--brand-yellow-dark); }

/* ── COMERCIAL · embudo ── */
.funnel { display: flex; flex-direction: column; gap: 6px; }
.funnel__row { display: flex; align-items: center; gap: 10px; }
.funnel__label { width: 96px; font-size: var(--text-12); font-weight: 600; flex-shrink: 0; }
.funnel__track { flex: 1; height: 26px; background: var(--cream-100); border-radius: var(--r-sm); overflow: hidden; position: relative; }
.funnel__bar { height: 100%; border-radius: var(--r-sm); display: flex; align-items: center; padding: 0 9px; color: #fff; font-size: var(--text-11); font-weight: 700; font-family: var(--font-mono); }
/* v46 · la barra de «ventas por área» iba en `--brand-navy` con tinta
   blanca: un bloque casi negro al lado de cinco barras de color, que se
   leía como «esto es otra cosa» cuando es el mismo tipo de dato. Ahora
   usa la paleta de datos, que existe justo para series sin juicio. */
.funnel__bar--dato { background: var(--viz-f, var(--viz-1)); color: var(--viz-t, var(--text-primary)); }
.funnel__bar--1 { background: #7C8A9E; } .funnel__bar--2 { background: #5E7186; } .funnel__bar--3 { background: var(--info); } .funnel__bar--4 { background: var(--brand-yellow-dark); color: var(--brand-navy); } .funnel__bar--5 { background: var(--success); }
.funnel__n { width: 34px; text-align: right; font-family: var(--font-mono); font-size: var(--text-12); font-weight: 600; }

/* ── OPERACIONES · OT ── */
.ot { display: flex; align-items: center; gap: 11px; padding: 10px 0; border-top: 1px solid var(--border-subtle); }
.ot:first-of-type { border-top: none; }
.ot__time { width: 52px; flex-shrink: 0; text-align: center; }
.ot__time .h { font-family: var(--font-display); font-weight: 700; font-size: var(--text-15); line-height: 1; }
.ot__time .d { font-size: var(--text-10); color: var(--text-secondary); font-family: var(--font-mono); text-transform: uppercase; }
.ot__prio { width: 3px; align-self: stretch; border-radius: 2px; background: var(--border-default); }
.ot__prio--alta { background: var(--error); } .ot__prio--media { background: var(--warning); } .ot__prio--baja { background: var(--success); }
.ot__body { flex: 1; min-width: 0; }
.ot__t { font-size: var(--text-13); font-weight: 600; }
.ot__s { font-size: var(--text-11); color: var(--text-secondary); display: flex; align-items: center; gap: 6px; }
.ot-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.ot-stat { background: var(--cream-50); border: 1px solid var(--border-subtle); border-radius: var(--r-md); padding: 11px 13px; text-align: center; }
.ot-stat .n { font-family: var(--font-display); font-weight: 700; font-size: var(--text-24); line-height: 1; }
.ot-stat .l { font-size: var(--text-11); color: var(--text-secondary); margin-top: 3px; }

/* ── PROYECTOS · lista de obras ── */
.obra { display: flex; align-items: center; gap: 14px; padding: 13px 0; border-top: 1px solid var(--border-subtle); }
.obra:first-of-type { border-top: none; }
.obra__main { flex: 1; min-width: 0; }
.obra__t { font-size: var(--text-13); font-weight: 600; }
.obra__s { font-size: var(--text-11); color: var(--text-secondary); }
/* v51 · La fila de obra: anillo a la izquierda, plata a la derecha.
   Antes el avance iba en una barra de 230px que al 0% no se veía, y el
   porcentaje estaba escrito DOS veces —chiquito dentro de la barra y
   grande al costado—. El anillo lo dice una sola vez y se ve antes de
   leerse, que es el argumento de la maqueta.
   `.obra__prog`, `.obra__bar` y `.obra__pct` quedan porque las usa el
   cronograma de la pestaña Obras. */
.obra__ring { flex: 0 0 auto; width: 44px; height: 44px; display: flex; align-items: center; }
.obra__ring .viz-anillo { width: 44px; height: 44px; }
.obra__monto {
  flex: 0 0 auto; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; font-size: var(--text-12);
  font-weight: 600; color: var(--text-primary); white-space: nowrap;
}
.obra__prog { width: 230px; flex-shrink: 0; }
.obra__bar { height: 8px; border-radius: var(--r-pill); background: var(--cream-100); overflow: hidden; }
.obra__fill { height: 100%; border-radius: var(--r-pill); background: var(--brand-navy); }
.obra__fill--late { background: var(--error); }
.obra__meta { display: flex; justify-content: space-between; font-size: var(--text-11); color: var(--text-secondary); margin-top: 4px; font-family: var(--font-mono); }
.obra__pct { width: 48px; text-align: right; font-family: var(--font-display); font-weight: 700; font-size: var(--text-18); }

/* ── FINANZAS ── */
.fin-hero { background: linear-gradient(160deg, var(--brand-yellow-soft), var(--surface-elevated) 70%); border: 1px solid var(--border-subtle); border-top: 3px solid var(--brand-yellow); color: var(--text-primary); border-radius: var(--r-md); padding: 18px 20px; display: flex; flex-direction: column; gap: 4px; position: relative; overflow: hidden; box-shadow: var(--shadow-card); }
.fin-hero__l { font-size: var(--text-12); color: var(--text-secondary); display: flex; align-items: center; gap: 7px; font-weight: 600; }
.fin-hero__eye { margin-left: auto; color: var(--text-secondary); cursor: pointer; background: none; border: none; display: inline-flex; }
.fin-hero__eye svg { width: 15px; height: 15px; }
.fin-hero__v { font-family: var(--font-display); font-weight: 700; font-size: var(--text-32); letter-spacing: -.02em; }
/* La linea de «cobrado X · pagado Y» daba 1,76:1. Es una cifra de plata. */
.fin-hero__d { font-family: var(--font-mono); font-size: var(--text-12); color: var(--brand-ink); font-weight: 600; }
/* v51 · auto-fit y no `1fr 1fr`: desde que entró «Utilidad del mes» son
   TRES cajas, y con dos columnas la tercera caía sola a una fila propia.
   Con auto-fit se acomodan solas a lo que haya. */
.fin-split { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
.fin-box { background: var(--surface-elevated); border: 1px solid var(--border-subtle); border-radius: var(--r-md); padding: 14px 15px; }
.fin-box__l { font-size: var(--text-11); color: var(--text-secondary); margin-bottom: 3px; }
.fin-box__v { font-family: var(--font-display); font-weight: 700; font-size: var(--text-22); }
.fin-box__venc { font-size: var(--text-11); font-weight: 600; color: var(--error); margin-top: 4px; display: inline-flex; align-items: center; gap: 4px; }
.aging { display: flex; gap: 8px; }
.aging__col { flex: 1; background: var(--cream-50); border: 1px solid var(--border-subtle); border-radius: var(--r-sm); padding: 9px 10px; text-align: center; }
.aging__n { font-family: var(--font-display); font-weight: 700; font-size: var(--text-16); }
.aging__l { font-size: var(--text-10); color: var(--text-secondary); font-family: var(--font-mono); margin-top: 2px; }
.aging__col--warn .aging__n { color: var(--warning); } .aging__col--alert .aging__n { color: var(--error); }
.health { display: flex; align-items: center; gap: 12px; }
.health__ring { width: 52px; height: 52px; flex-shrink: 0; }
.health__txt .s { font-family: var(--font-display); font-weight: 700; font-size: var(--text-15); }
.health__txt .d { font-size: var(--text-11); color: var(--text-secondary); }

/* ── PERSONAS ── */
.att-donut { display: flex; align-items: center; gap: 16px; }
.att-donut svg { width: 88px; height: 88px; flex-shrink: 0; }
.att-legend { display: flex; flex-direction: column; gap: 7px; }
.att-legend .li { display: flex; align-items: center; gap: 8px; font-size: var(--text-12); }
.att-legend .dot { width: 9px; height: 9px; border-radius: var(--r-xs); flex-shrink: 0; }
.att-legend .n { font-family: var(--font-mono); font-weight: 600; margin-left: auto; }
.per-row { display: flex; align-items: center; gap: 11px; padding: 9px 0; border-top: 1px solid var(--border-subtle); }
.per-row:first-of-type { border-top: none; }
.per-row__body { flex: 1; min-width: 0; }
.per-row__t { font-size: var(--text-13); font-weight: 600; }
.per-row__s { font-size: var(--text-11); color: var(--text-secondary); }

/* ── estados: skeleton, offline ── */
.sk { background: linear-gradient(90deg, var(--cream-100) 25%, var(--cream-200) 37%, var(--cream-100) 63%); background-size: 400% 100%; animation: dashSkl 1.4s ease infinite; border-radius: var(--r-sm); }
@keyframes dashSkl { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }
.offline-bar { display: flex; align-items: center; gap: 8px; background: var(--warning-soft); color: var(--warning); border: 1px solid var(--warning); border-radius: var(--r-sm); padding: 8px 12px; font-size: var(--text-12); font-weight: 600; margin-bottom: 14px; }
.offline-bar svg { width: 15px; height: 15px; }

/* ── utilidades compartidas ── */
.tile { border-radius: var(--r-md); padding: 14px 16px; border: 1px solid var(--border-subtle); box-shadow: var(--shadow-card); position: relative; overflow: hidden; }
.tile__l { font-size: var(--text-11); color: var(--text-secondary); display: flex; align-items: center; gap: 6px; font-weight: 600; }
.tile__l svg { width: 13px; height: 13px; }
.tile__v { font-family: var(--font-display); font-weight: 700; font-size: var(--text-26); letter-spacing: -.02em; margin-top: 5px; line-height: 1; }
.tile__d { font-size: var(--text-11); color: var(--text-secondary); margin-top: 5px; }
.tile--brand { background: linear-gradient(155deg, var(--brand-yellow-soft), var(--surface-elevated) 72%); border-top: 3px solid var(--brand-yellow); }
.tile--ok    { background: linear-gradient(155deg, var(--success-soft), var(--surface-elevated) 72%); border-top: 3px solid var(--success); }
.tile--warn  { background: linear-gradient(155deg, var(--warning-soft), var(--surface-elevated) 72%); border-top: 3px solid var(--warning); }
.tile--alert { background: linear-gradient(155deg, var(--error-soft), var(--surface-elevated) 72%); border-top: 3px solid var(--error); }
.tile--info  { background: linear-gradient(155deg, var(--info-soft), var(--surface-elevated) 72%); border-top: 3px solid var(--info); }

/* v46 · `.ring` se fue. Era el segundo componente de anillo del sistema
   (el otro es `.viz-anillo`, en viz.css) y lo usaban las tres portadas
   de área, cada una dibujando su SVG a mano. Ahora todas pasan por
   `Sei.ui.anillo`, que emite `.viz-anillo`. */

/* v46 · la fila de KPI de las portadas. Estaba escrita en línea en las
   tres (`display:grid;grid-template-columns:repeat(3,1fr);gap:12px`), y
   con `repeat(3,1fr)` fijo la del medio se partía en el teléfono. */
.kpiv-fila { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; margin-bottom: 14px; }
/* El pie de la cifra: 14 copias del mismo `style=""` repartidas en seis
   archivos. Un solo lugar, y así se puede cambiar de una. */
.kpiv__d { font-size: var(--text-11); color: var(--text-secondary); margin-top: 4px; }
.kpiv__d b { color: var(--text-primary); font-weight: 600; }
.kpiv { background: var(--surface-elevated); border: 1px solid var(--border-subtle); border-radius: var(--r-md); padding: 13px 15px; box-shadow: var(--shadow-card); position: relative; overflow: hidden; }
.kpiv::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; }
.kpiv--brand::before { background: var(--brand-yellow); } .kpiv--ok::before { background: var(--success); } .kpiv--warn::before { background: var(--warning); } .kpiv--alert::before { background: var(--error); } .kpiv--info::before { background: var(--info); }
.kpiv__l { font-size: var(--text-11); color: var(--text-secondary); margin-bottom: 3px; }
.kpiv__v { font-family: var(--font-display); font-weight: 700; font-size: var(--text-24); line-height: 1; letter-spacing: -.02em; }

.gbar { height: 8px; border-radius: var(--r-pill); background: var(--cream-100); overflow: hidden; }
.gbar__f { height: 100%; border-radius: var(--r-pill); background: linear-gradient(90deg, var(--brand-yellow-dark), var(--brand-yellow)); }
.gbar__f--ok { background: linear-gradient(90deg, #1F5A3D, var(--success)); }
.gbar__f--late { background: linear-gradient(90deg, #7E1B1B, var(--error)); }

.trend { display: inline-flex; align-items: center; gap: 3px; font-family: var(--font-mono); font-size: var(--text-11); font-weight: 600; }
.trend svg { width: 12px; height: 12px; }
.trend--up { color: var(--success); } .trend--down { color: var(--error); }

.tavs { display: flex; }
.tav { width: 24px; height: 24px; border-radius: var(--r-pill); display: inline-flex; align-items: center; justify-content: center; font-family: var(--font-display); font-weight: 700; font-size: var(--text-10); color: #fff; border: 2px solid var(--surface-elevated); margin-left: -6px; }
.tav:first-child { margin-left: 0; }
.mini-av { width: 26px; height: 26px; border-radius: var(--r-pill); display: inline-flex; align-items: center; justify-content: center; font-family: var(--font-display); font-weight: 700; font-size: var(--text-10); color: #fff; flex-shrink: 0; }
.sec-label { font-family: var(--font-mono); font-size: var(--text-10); letter-spacing: .1em; text-transform: uppercase; color: var(--text-secondary); margin: 2px 0; }

/* ── responsive ── */
@media (max-width: 1024px) {
  .area-grid { grid-template-columns: repeat(2, 1fr); }
  .home-cols { grid-template-columns: 1fr; }
  .shorts--fila { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 640px) {
  .area-grid { grid-template-columns: 1fr; }
  .hk-grid   { grid-template-columns: 1fr 1fr; }
  .hk { min-height: 112px; }
  /* En 375 px el saludo y el botón no caben en la misma línea:
     sin esto, la fecha queda partida en dos y el botón encima. */
  .home-head { flex-wrap: wrap; gap: 8px 14px; }
  .shorts--fila { grid-template-columns: 1fr 1fr; }
  .area--focus { grid-column: auto; }
  .area--wide-figure { flex-direction: column; align-items: flex-start; gap: 10px; }
  .obra { flex-wrap: wrap; }
  .obra__prog { width: 100%; }
  .fin-split, .ot-cols { grid-template-columns: 1fr 1fr; }
}

/* ════════════════════════════════════════════════════════════
   REFINAMIENTO v3 · 2026-08-10 · datos y paneles
   Acá vive la libertad de color: gráficos, anillos, barras de
   avance, embudos, KPI. Todo con la paleta --viz-* de tokens.css,
   emparejada en croma para que ninguna serie grite más que otra
   y todas convivan con el crema.
   Bloque aditivo: no renombra ni elimina clases.
   ════════════════════════════════════════════════════════════ */

/* ────────────────────────────────────────────────────────────
   1 · SUPERFICIES — radios generosos, sombras físicas
   Antes: paneles de radio 12 con borde y sin sombra sobre crema
   plano; todo al mismo plano. Ahora hay dos niveles claros:
   panel (apoyado) y hero (elevado).
   ──────────────────────────────────────────────────────────── */
.panel, .area, .fin-box, .kpiv, .tile, .fin-hero, .ot-stat, .aging__col {
  border-radius: var(--r-lg);
}
.panel, .area, .fin-box, .kpiv, .tile {
  box-shadow: var(--shadow-card);
  position: relative;
}
.ot-stat, .aging__col { border-radius: var(--r-md); }
/* Media línea de luz al tope: hace que la superficie parezca
   material iluminado desde arriba y no un rectángulo pintado. */
.panel::before, .fin-hero::before, .tile::before, .area::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.9) 20%, rgba(255,255,255,.9) 80%, transparent);
  pointer-events: none; border-radius: inherit;
}
[data-theme="dark"] .panel::before, [data-theme="dark"] .fin-hero::before,
[data-theme="dark"] .tile::before,  [data-theme="dark"] .area::before {
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.10) 20%, rgba(255,255,255,.10) 80%, transparent);
}
.panel { padding: 16px 18px; }
.panel--accent { border-top-width: 3px; }
.panel__h { margin-bottom: 12px; }
.panel__h .t { letter-spacing: -.012em; }
.panel__h .n { font-variant-numeric: tabular-nums; }
.home-cols { gap: 16px; }
.area-grid { gap: 14px; margin-bottom: 22px; }
.home-head { margin-bottom: 20px; }
.home-hello { letter-spacing: -.028em; }
@media (hover: hover) and (pointer: fine) {
  .panel:hover, .kpiv:hover, .fin-box:hover, .tile:hover { transform: translateY(-2px); }
}

/* Tarjetas de área: el borde de 3px a la izquierda es el cliché
   "alert box" de 2010. Se reemplaza por velo diagonal del color
   de estado + regla superior fina, que es lo que hace Apple en
   los widgets: el color informa, no enmarca. */
.area--alert, .area--warn {
  border-left-width: 1px;
  border-color: var(--border-subtle);
}
.area--alert { background: linear-gradient(158deg, var(--error-soft), var(--surface-elevated) 56%);   border-top: 2px solid var(--error); }
.area--warn  { background: linear-gradient(158deg, var(--warning-soft), var(--surface-elevated) 56%); border-top: 2px solid var(--warning); }
.area--ok    { border-left-width: 1px; border-top: 2px solid var(--success); }
.area__figure { letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.area__ico { border-radius: var(--r-md); box-shadow: inset 0 .5px 0 rgba(255,255,255,.5); }
.area__pill { font-variant-numeric: tabular-nums; }
.area__ctx { color: var(--text-secondary); }
.area__empty p { color: var(--text-tertiary); }

/* Tiles: mismo criterio, velo en vez de marco */
.tile { padding: 15px 17px; }
.tile__v { font-size: var(--text-28); letter-spacing: -.03em; }
.tile--brand, .tile--ok, .tile--warn, .tile--alert, .tile--info { border-top-width: 2px; }

/* ────────────────────────────────────────────────────────────
   2 · KPI VERTICAL (.kpiv) — la cifra manda
   La barra de 3px a la izquierda se afina a 2px y se recorta a la
   altura del bloque de texto: deja de leerse como marco.
   ──────────────────────────────────────────────────────────── */
.kpiv { padding: 15px 17px; }
.kpiv::before { left: 0; top: 15px; bottom: 15px; width: 2px; border-radius: 0 2px 2px 0; }
.kpiv__l { font-size: var(--text-11); font-weight: 600; color: var(--text-secondary); letter-spacing: .015em; }
.kpiv__v { font-size: var(--text-26); letter-spacing: -.03em; }
.fin-hero { padding: 20px 22px; box-shadow: var(--shadow-hero); border-top-width: 3px; }
.fin-hero__v { font-size: var(--text-36); letter-spacing: -.035em; }
.fin-hero__l { color: var(--text-secondary); }
.fin-box { padding: 15px 17px; }
.fin-box__v { font-size: var(--text-24); letter-spacing: -.028em; }
.ot-stat .n, .aging__n { letter-spacing: -.02em; }

/* ────────────────────────────────────────────────────────────
   3 · BARRAS DE AVANCE Y EMBUDO
   Fin de los rellenos planos: cada barra tiene gradiente corto
   dentro de su propio tono (claro→saturado en el sentido del
   avance) y una línea de luz interior. La punta redondeada del
   relleno queda por encima del riel, así el avance se lee incluso
   al 3%.
   ──────────────────────────────────────────────────────────── */
.gbar, .obra__bar, .av-bar {
  height: 8px;
  background: var(--surface-sunken);
  box-shadow: inset 0 1px 2px rgba(26,26,24,.08);
}
.gbar__f, .obra__fill {
  box-shadow: inset 0 1px 0 rgba(255,255,255,.35);
  min-width: 6px;
}
.gbar__f        { background: linear-gradient(90deg, var(--brand-yellow-300), var(--brand-yellow-dark)); }
.gbar__f--ok    { background: linear-gradient(90deg, color-mix(in oklab, var(--success) 62%, #fff), var(--success)); }
.gbar__f--late  { background: linear-gradient(90deg, color-mix(in oklab, var(--error) 62%, #fff), var(--error)); }
.obra__fill     { background: linear-gradient(90deg, var(--viz-3), color-mix(in oklab, var(--viz-3) 70%, var(--brand-navy))); }
.obra__fill--late { background: linear-gradient(90deg, color-mix(in oklab, var(--error) 62%, #fff), var(--error)); }
.obra__pct { letter-spacing: -.02em; }
.obra__meta { color: var(--text-tertiary); }

/* Embudo: la escala de grises azulados hecha a ojo (#7C8A9E, #5E7186)
   se reemplaza por la rampa --viz-*, que sí tiene método: mismo croma,
   el tono gira de frío a cálido a medida que la oportunidad avanza
   hacia el cierre. La etapa ganada es la única en verde. */
.funnel__track { background: var(--surface-sunken); border-radius: var(--r-sm); box-shadow: inset 0 1px 2px rgba(26,26,24,.07); height: 28px; }
.funnel__bar {
  border-radius: var(--r-sm);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.28);
  font-variant-numeric: tabular-nums;
}
.funnel__bar--1 { background: linear-gradient(90deg, color-mix(in oklab, var(--viz-4) 72%, #fff), var(--viz-4)); }
.funnel__bar--2 { background: linear-gradient(90deg, color-mix(in oklab, var(--viz-3) 72%, #fff), var(--viz-3)); }
.funnel__bar--3 { background: linear-gradient(90deg, color-mix(in oklab, var(--viz-2) 76%, #fff), var(--viz-2)); }
.funnel__bar--4 { background: linear-gradient(90deg, var(--brand-yellow-300), var(--brand-yellow-dark)); color: var(--brand-navy); }
.funnel__bar--5 { background: linear-gradient(90deg, color-mix(in oklab, var(--success) 62%, #fff), var(--success)); }
.funnel__n { font-variant-numeric: tabular-nums; }

/* ────────────────────────────────────────────────────────────
   4 · ANILLOS Y DONUTS
   El anillo lleva un riel de fondo (sin él no se lee cuánto FALTA,
   solo cuánto hay) y punta redondeada. La cifra del centro sube y
   se aprieta: es el dato, no una etiqueta.
   ──────────────────────────────────────────────────────────── */
.att-donut svg circle { stroke-linecap: butt; }
.att-legend .n { font-variant-numeric: tabular-nums; }
.att-legend .dot { box-shadow: inset 0 .5px 0 rgba(255,255,255,.5); }

/* Donut de composición sin SVG: conic-gradient con hueco.
   API por instancia (se pasan en el atributo style del elemento, no
   se declaran acá para no confundirlas con tokens del sistema):
     --donut-size   tamaño, por defecto 148px
     --a1 --a2 --a3 porcentajes ACUMULADOS de cada tramo */
.donut {
  width: var(--donut-size, 148px); height: var(--donut-size, 148px);
  border-radius: var(--r-pill);
  position: relative; flex-shrink: 0;
  background: conic-gradient(
    var(--viz-1) 0 var(--a1, 46%),
    var(--viz-3) 0 var(--a2, 74%),
    var(--viz-4) 0 var(--a3, 92%),
    var(--cream-200) 0 100%);
  box-shadow: var(--shadow-hover);
}
.donut::after {
  content: ""; position: absolute; inset: 22%; border-radius: var(--r-pill);
  background: var(--surface-elevated);
  box-shadow: inset 0 1px 3px rgba(26,26,24,.08);
}
.donut__center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 1; }
.donut__num { font-family: var(--font-display); font-weight: 700; font-size: var(--text-24); line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.donut__cap { font-size: var(--text-10); color: var(--text-tertiary); text-transform: uppercase; letter-spacing: .07em; margin-top: 2px; }

/* Barra apilada 100% · composición en 12px de alto
   OJO con el nombre: esto se llamaba `.stack` y chocaba con la utilidad
   `.stack` de components.css (columna flex para formularios). Como
   dashboards.css carga después, TODOS los formularios en modal quedaban
   aplastados a 12px con el contenido recortado por el `overflow:hidden`.
   Se renombró a `.stackbar`. No usar `.stack` para nada visual. */
.stackbar { display: flex; height: 12px; border-radius: var(--r-pill); overflow: hidden; background: var(--surface-sunken); gap: 2px; }
.stackbar__seg { background: var(--c, var(--viz-1)); width: calc(var(--v, 25) * 1%); transition: width var(--duration-slow) var(--ease-out); }

/* ────────────────────────────────────────────────────────────
   5 · GRÁFICOS SVG
   Sin marco, sin grilla vertical, sin ejes gruesos: el trazo del
   dato es la única tinta fuerte del bloque. Clases para el SVG
   que la app dibuja (curva S de informes incluida).
   ──────────────────────────────────────────────────────────── */
.chart { width: 100%; height: auto; display: block; overflow: visible; }
.chart .grid-line { stroke: var(--viz-grid); stroke-width: 1; }
.chart .goal-line { stroke: var(--viz-axis); stroke-width: 1; stroke-dasharray: 2 5; opacity: .7; }
.chart .series     { fill: none; stroke: var(--viz-1); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.chart .series--2  { stroke: var(--viz-3); }
.chart .series--ghost { stroke: var(--viz-5); stroke-width: 1.5; stroke-dasharray: 4 5; opacity: .85; }
.chart .marker { fill: var(--surface-elevated); stroke: var(--viz-1); stroke-width: 2.5; }
.chart .tick   { font-family: var(--font-mono); font-size: var(--text-10); fill: var(--text-secondary); }
.chart-x { display: grid; font-family: var(--font-mono); font-size: var(--text-10); color: var(--text-secondary); text-align: center; margin-top: 4px; }
/* Último dato: respira una vez cada 3s. No parpadea —parpadear es
   una alarma, y esto es solo "acá estás parado hoy". */
.chart .pulse { fill: var(--viz-1); transform-origin: center; transform-box: fill-box; animation: vizPulse 3s var(--ease-out) infinite; }
@keyframes vizPulse { 0%, 70%, 100% { opacity: .28; transform: scale(1); } 35% { opacity: 0; transform: scale(2.6); } }
@media (prefers-reduced-motion: reduce) { .chart .pulse { animation: none; opacity: .28; } }

/* Barras verticales en CSS puro · alto por --v (0–100) */
.bars { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; align-items: end; gap: 8px; height: var(--h, 148px); padding-top: 6px; }
.bars__col { display: flex; flex-direction: column; justify-content: flex-end; align-items: center; gap: 7px; height: 100%; position: relative; }
.bars__bar {
  width: 100%; max-width: 38px;
  height: calc(var(--v, 50) * 1%);
  min-height: 3px;
  border-radius: var(--r-sm) var(--r-sm) 3px 3px;
  background: linear-gradient(180deg, var(--c, var(--viz-1)), color-mix(in oklab, var(--c, var(--viz-1)) 74%, var(--surface-elevated)));
  box-shadow: inset 0 .5px 0 rgba(255,255,255,.55);
  transition: height var(--duration-slow) var(--ease-out), filter var(--duration-fast) var(--ease-out);
}
.bars__lbl { font-family: var(--font-mono); font-size: var(--text-10); color: var(--text-secondary); white-space: nowrap; }
.bars__top { position: absolute; top: 0; font-family: var(--font-mono); font-size: var(--text-10); color: var(--text-secondary); opacity: 0; transition: opacity var(--duration-fast) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .bars__col:hover .bars__bar { filter: brightness(1.06) saturate(1.05); }
  .bars__col:hover .bars__top { opacity: 1; }
}
/* Ranking horizontal */
.hbar { display: grid; grid-template-columns: minmax(88px, 116px) 1fr auto; align-items: center; gap: 10px; font-size: var(--text-12); }
.hbar__track { height: 8px; border-radius: var(--r-pill); background: var(--surface-sunken); overflow: hidden; box-shadow: inset 0 1px 2px rgba(26,26,24,.07); }
.hbar__fill { display: block; height: 100%; border-radius: var(--r-pill); width: calc(var(--v, 50) * 1%); background: linear-gradient(90deg, color-mix(in oklab, var(--c, var(--viz-1)) 74%, var(--surface-elevated)), var(--c, var(--viz-1))); transition: width var(--duration-slow) var(--ease-out); }
.hbar__val { font-family: var(--font-mono); font-size: var(--text-11); color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* Leyenda compartida por todos los gráficos */
.legend { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; }
.legend__item { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-11); color: var(--text-secondary); white-space: nowrap; }
.legend__dot { width: 8px; height: 8px; border-radius: var(--r-xs); flex-shrink: 0; background: var(--c, var(--viz-1)); box-shadow: inset 0 .5px 0 rgba(255,255,255,.5); }
.legend__val { font-family: var(--font-mono); color: var(--text-primary); font-weight: 500; font-variant-numeric: tabular-nums; }

/* Sparkline dentro de un KPI: sangra hasta el borde de la tarjeta */
.spark { display: block; width: 100%; height: 40px; margin-top: 6px; }
/* Variante que sangra a los tres bordes de una .kpi (padding 14/16):
   la tendencia se apoya en el piso de la tarjeta en vez de flotar. */
.kpi { padding-bottom: 0; }
.kpi__spark { display: block; width: calc(100% + 32px); height: 44px; margin: 8px -16px 0; }

/* ────────────────────────────────────────────────────────────
   FLUJO DE CAJA · barras bidireccionales sobre eje cero
   Entra plata arriba, sale abajo. El eje cero es la única línea
   fuerte: se ve de inmediato en qué semana el saldo se da vuelta.
   --in / --out en 0–100 (proporción del alto de cada mitad).
   ──────────────────────────────────────────────────────────── */
.flow { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px; position: relative; }
.flow__col { display: grid; grid-template-rows: 1fr 1px 1fr auto; gap: 0; align-items: end; justify-items: center; height: var(--h, 170px); }
.flow__in {
  width: 100%; max-width: 34px; align-self: end;
  height: calc(var(--in, 0) * 1%); min-height: 2px;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  background: linear-gradient(180deg, var(--viz-1), color-mix(in oklab, var(--viz-1) 72%, var(--surface-elevated)));
  box-shadow: inset 0 .5px 0 rgba(255,255,255,.55);
}
.flow__zero { width: 100%; height: 1px; background: var(--viz-axis); grid-column: 1 / -1; }
.flow__out {
  width: 100%; max-width: 34px; align-self: start;
  height: calc(var(--out, 0) * 1%); min-height: 2px;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  background: linear-gradient(0deg, color-mix(in oklab, var(--viz-4) 62%, var(--surface-elevated)), var(--viz-4));
  box-shadow: inset 0 -.5px 0 rgba(255,255,255,.3);
}
.flow__lbl { font-family: var(--font-mono); font-size: var(--text-10); color: var(--text-secondary); padding-top: 7px; }
/* Semana en que el saldo queda negativo: se marca, no se decora */
.flow__col--alert .flow__out { background: linear-gradient(0deg, var(--error-soft), var(--error)); }
.flow__col--alert .flow__lbl { color: var(--error); font-weight: 600; }

/* ────────────────────────────────────────────────────────────
   PRESUPUESTADO vs REAL · barra con marca de meta
   La barra es lo gastado; la marca vertical es el presupuesto.
   Pasarse se lee al instante porque la barra cruza la marca.
   ──────────────────────────────────────────────────────────── */
.vs { display: grid; grid-template-columns: 120px 1fr 78px; align-items: center; gap: 12px; font-size: var(--text-12); }
.vs__track { position: relative; height: 22px; border-radius: var(--r-sm); background: var(--surface-sunken); box-shadow: inset 0 1px 2px rgba(26,26,24,.07); overflow: visible; }
.vs__fill {
  position: absolute; inset: 0 auto 0 0; width: calc(var(--v, 50) * 1%);
  border-radius: var(--r-sm);
  background: linear-gradient(90deg, color-mix(in oklab, var(--c, var(--viz-1)) 74%, var(--surface-elevated)), var(--c, var(--viz-1)));
  box-shadow: inset 0 .5px 0 rgba(255,255,255,.5);
  transition: width var(--duration-slow) var(--ease-out);
}
.vs__mark { position: absolute; top: -3px; bottom: -3px; left: calc(var(--g, 100) * 1%); width: 2px; background: var(--brand-navy); border-radius: 1px; }
.vs__mark::after { content: ""; position: absolute; top: -3px; left: -2px; border: 3px solid transparent; border-top-color: var(--brand-navy); }
[data-theme="dark"] .vs__mark, [data-theme="dark"] .vs__mark::after { background: var(--text-primary); border-top-color: var(--text-primary); }
.vs__over { background: linear-gradient(90deg, color-mix(in oklab, var(--error) 70%, var(--surface-elevated)), var(--error)); }
.vs__n { font-family: var(--font-mono); font-size: var(--text-11); text-align: right; font-variant-numeric: tabular-nums; }

/* ────────────────────────────────────────────────────────────
   MAPA DE CARGA · la semana en un vistazo
   Concepto nuevo: una grilla de celdas donde la intensidad del
   relleno es la ocupación (--v 0–100). Responde de un vistazo la
   pregunta que hoy exige abrir la agenda día por día: ¿dónde
   puedo meter una visita esta semana?
   ──────────────────────────────────────────────────────────── */
.heat { display: grid; gap: 4px; }
.heat__row { display: grid; grid-template-columns: 62px repeat(var(--cols, 5), 1fr); gap: 4px; align-items: center; }
.heat__lbl { font-size: var(--text-11); color: var(--text-secondary); white-space: nowrap; }
.heat__col { font-family: var(--font-mono); font-size: var(--text-10); color: var(--text-tertiary); text-align: center; text-transform: uppercase; letter-spacing: .06em; }
.heat__cell {
  height: 30px; border-radius: var(--r-sm);
  background: color-mix(in oklab, var(--viz-1) calc(var(--v, 0) * 1%), var(--surface-sunken));
  box-shadow: inset 0 .5px 0 rgba(255,255,255,.4);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: var(--text-10);
  color: var(--text-secondary);
  transition: transform var(--duration-fast) var(--ease-out);
}
/* Sobrecarga: el amarillo ya no alcanza, cambia de familia de color */
.heat__cell--over { background: color-mix(in oklab, var(--error) calc(var(--v, 0) * 0.6%), var(--error-soft)); color: var(--error); font-weight: 600; }
.heat__cell--free { border: 1px dashed var(--border-default); box-shadow: none; color: var(--text-tertiary); }
@media (hover: hover) and (pointer: fine) { .heat__cell:hover { transform: scale(1.04); } }

/* ────────────────────────────────────────────────────────────
   6 · ATAJOS Y FILAS
   ──────────────────────────────────────────────────────────── */
.short {
  border-width: 1px;
  border-radius: var(--r-md);
  min-height: var(--touch-comfortable);
  box-shadow: 0 1px 2px rgba(26,26,24,.04), inset 0 1px 0 rgba(255,255,255,.6);
}
.short__ico { border-radius: var(--r-sm); }
.short--primary {
  background: linear-gradient(180deg, var(--brand-yellow-300), var(--brand-yellow) 52%, var(--brand-yellow-dark));
  box-shadow: var(--shadow-yellow), inset 0 1px 0 rgba(255,255,255,.55);
}
@media (hover: hover) and (pointer: fine) {
  .short--primary:hover { background: linear-gradient(180deg, var(--brand-yellow), var(--brand-yellow-dark)); }
}
.att__ico, .ot-stat, .short__ico { box-shadow: inset 0 .5px 0 rgba(255,255,255,.5); }
.att__amt, .ot__time .h { font-variant-numeric: tabular-nums; }
.att__s, .ot__s, .per-row__s, .obra__s, .tile__d, .fin-box__l, .aging__l { color: var(--text-secondary); }
.sec-label { color: var(--text-tertiary); }
.att, .ot, .per-row, .obra { border-radius: var(--r-md); }
.ot__prio { border-radius: var(--r-pill); }

/* Compensaciones de modo oscuro: donde el semántico es FONDO con
   texto encima, el token claro no sirve. */
[data-theme="dark"] .funnel__bar--5,
[data-theme="dark"] .funnel__bar--1,
[data-theme="dark"] .funnel__bar--2,
[data-theme="dark"] .funnel__bar--3 { color: var(--brand-navy); }
[data-theme="dark"] .short--primary .short__ico { background: rgba(0,0,0,.12); }
[data-theme="dark"] .m-bottomnav__fab,
[data-theme="dark"] .short--primary { color: var(--brand-navy); }
[data-theme="dark"] .tav, [data-theme="dark"] .mini-av { border-color: var(--surface-elevated); }
[data-theme="dark"] .donut::after { box-shadow: inset 0 1px 3px rgba(0,0,0,.45); }
[data-theme="dark"] :where(.gbar, .obra__bar, .av-bar, .hbar__track, .funnel__track) {
  box-shadow: inset 0 1px 2px rgba(0,0,0,.40);
}

/* Sin conexión: el banner deja de ser un cartel de alerta y pasa a
   ser una barra de estado —el modo offline es normal en terreno. */
.offline-bar {
  border-radius: var(--r-md);
  border-color: color-mix(in oklab, var(--warning) 30%, var(--border-default));
  background: var(--warning-soft);
}

/* ────────────────────────────────────────────────────────────
   7 · MOVIMIENTO
   La entrada en cascada ya existía; se le agrega el crecimiento
   de los anillos y se respeta reduced-motion (tokens.css ya lo
   cubre globalmente, esto es el cinturón de seguridad).
   ──────────────────────────────────────────────────────────── */
.donut { animation: dashRiseIn .5s var(--ease-out) both; }
@media (prefers-reduced-motion: reduce) {
  .dash-stagger > *, .obra__fill, .gbar__f, .funnel__bar, .donut { animation: none !important; transform: none !important; opacity: 1 !important; }
}

/* PARCHE v3.1 · la píldora "Sin datos" daba 4,21:1 sobre cream-100.
   Sube a secondary: 5,76:1. */
.pill--empty { color: var(--text-secondary); }
.pill--empty::before { background: var(--text-secondary); }

/* ────────────────────────────────────────────────────────────
   PARCHE 8-sep-2026 · lo que quedó ilegible en modo oscuro
   Encontrado mirando producción en el navegador, midiendo el
   contraste real de cada enlace y botón contra su fondo.

   La causa es siempre la misma: `--brand-navy` (#1A1A18) NO cambia
   con el tema, y no tiene por qué — es el negro de la marca, y está
   bien donde va sobre un fondo claro FIJO (el amarillo de
   `.btn--primary`, el crema de `.badge--brand`). El error es usarlo
   sobre un fondo que sí cambia: ahí queda negro sobre negro.

   Medido antes del parche, con la app en oscuro:
     "+ Crear"            1,10 : 1
     "Ver cotizaciones"   1,06 : 1
     "Ver órdenes/obras/finanzas/equipo"   1,10 : 1
   El mínimo para texto chico es 4,5:1. Eran los seis enlaces de
   acción de la pantalla de Inicio, más el botón principal de toda
   la app. Se veían como huecos.
   ──────────────────────────────────────────────────────────── */
[data-theme="dark"] .area__act,
[data-theme="dark"] .panel__act {
  color: var(--brand-yellow);
}
[data-theme="dark"] .area__act:hover,
[data-theme="dark"] .panel__act:hover {
  color: var(--brand-yellow-300, var(--brand-yellow));
}

/* Los íconos de "Atajos rápidos": tinta navy sobre un fondo que en
   oscuro es negro al 12%. Se veían como cuadrados vacíos. El ícono
   hermano `.area__ico` ya se había arreglado; éste quedó afuera. */
[data-theme="dark"] .short__ico {
  background: color-mix(in oklab, var(--brand-yellow) 16%, transparent);
  color: var(--brand-yellow);
}

/* ══════════════════════════════════════════════════════════
   MI DÍA · la pantalla del teléfono en obra (js/views/mi-dia.js)
   Tarjetas grandes, un botón principal por OT, y los blancos de toque a
   44 px sin depender del @media (pointer: coarse): acá se supone el dedo.
   ══════════════════════════════════════════════════════════ */
.midia { max-width: 640px; margin: 0 auto; }
.midia__cargando, .midia__vacio { padding: 40px 16px; text-align: center; color: var(--text-secondary); }
.midia__vacio { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.midia__vacio .lu { width: 36px; height: 36px; color: var(--success); margin-bottom: 6px; }
.midia__vacio b { color: var(--text-primary); font-size: var(--text-15); }
.midia__top { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
/* Sin `capitalize`: en español ponía mayúscula en cada palabra
   «19 De Septiembre». La primera letra la pone `fechaLarga()`. */
.midia__fecha { font-family: var(--font-display); font-weight: 700; font-size: var(--text-18); }
.midia__sub { font-size: var(--text-12); color: var(--text-secondary); }
.midia__sec { margin-top: 14px; }
.midia__h { font-family: var(--font-mono); font-size: var(--text-10); letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-secondary); margin: 0 0 8px; display: flex; align-items: baseline; gap: 8px; }
.midia__h span { font-weight: 700; color: var(--text-primary); }
.midia__h--mal, .midia__h--mal span { color: var(--error); }
.midia__ot { background: var(--surface-elevated); border: 1px solid var(--border-subtle); border-left: 4px solid var(--border-default);
  border-radius: var(--r-lg); padding: 14px 14px 12px; margin-bottom: 12px; }
.midia__ot--curso    { border-left-color: var(--warning); }
.midia__ot--atrasada { border-left-color: var(--error); }
.midia__ot--hoy      { border-left-color: var(--brand-yellow); }
.midia__ot--urgente .midia__hora { color: var(--error); }
.midia__ot-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.midia__hora { font-family: var(--font-display); font-weight: 700; font-size: var(--text-16); }
.midia__num { font-family: var(--font-mono); font-size: var(--text-11); color: var(--text-secondary); }
.midia__offline { display:flex; align-items:center; gap:8px; margin-top:14px; padding:10px 12px; border-radius:var(--r-md); background:var(--cream-100); color:var(--text-secondary); font-size:var(--text-12); }
.midia__offline .lu { width:16px; height:16px; flex:0 0 16px; }
.midia__dur { font-size: var(--text-11); color: var(--text-secondary); padding: 1px 7px; border: 1px solid var(--border-subtle); border-radius: var(--r-pill); }
.midia__t { font-size: var(--text-16); font-weight: 700; margin: 0; line-height: 1.25; }
.midia__cli { font-size: var(--text-12); color: var(--text-secondary); margin-top: 2px; }
.midia__desc { font-size: var(--text-13); margin: 8px 0 0; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.midia__dir { display: flex; gap: 6px; align-items: flex-start; font-size: var(--text-12); color: var(--text-secondary); margin-top: 8px; }
.midia__dir .lu { width: 14px; height: 14px; flex: none; margin-top: 2px; }
.midia__acciones { display: flex; gap: 8px; margin-top: 10px; }
.midia__acciones .btn { flex: 1; justify-content: center; min-height: 44px; font-size: var(--text-13); }
.midia__acciones--2 .btn--ghost { flex: 0 0 auto; }
.midia__links { display: flex; gap: 4px; margin-top: 8px; border-top: 1px solid var(--border-subtle); padding-top: 6px; }

/* ── v45 · la OT destacada y las filas ──────────────────────────────
   Siete tarjetas grandes iguales son siete pantallas de scroll. La que
   se está haciendo lleva el peso; el resto se mira de reojo. */
.midia__filtros { margin-bottom: 12px; }
.midia__filtros .tab { padding: 7px 13px; font-size: var(--text-12); min-height: 38px; }
.midia__filtros .tab b { font-family: var(--font-mono); margin-left: 4px; }
.midia__filtros .tab[disabled] { opacity: .45; cursor: default; }
.midia__nada { font-size: var(--text-13); color: var(--text-secondary); text-align: center; padding: 26px 12px; margin: 0; }

.midia__cuerpo { display: flex; align-items: center; gap: 12px; }
.midia__cuerpo-tx { min-width: 0; flex: 1; }
.midia__anillo { flex: none; }

.mifila {
  display: flex; align-items: stretch; gap: 4px;
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-left: 4px solid var(--border-default);
  border-radius: var(--r-md);
  margin-bottom: 8px; overflow: hidden;
  transition: box-shadow var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out);
}
.mifila:hover { box-shadow: var(--shadow-card); }
.mifila--curso    { border-left-color: var(--warning); }
.mifila--atrasada { border-left-color: var(--error); }
.mifila--hoy      { border-left-color: var(--brand-yellow); }

/* El cuerpo es un botón y no un div con onclick: así se llega con el
   teclado y el lector de pantalla lo anuncia como algo que se toca. */
.mifila__cuerpo {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 11px;
  padding: 10px 12px; min-height: 56px;
  background: none; border: none; cursor: pointer; text-align: left;
  font-family: var(--font-body); color: inherit;
}
.mifila__cuerpo:hover { background: var(--cream-50); }
.mifila__hora { flex: none; width: 56px; display: flex; flex-direction: column; gap: 1px; align-items: flex-start; }
.mifila__hora b { font-family: var(--font-display); font-weight: 700; font-size: var(--text-15); line-height: 1.15; white-space: nowrap; }
.mifila__hora > span { font-family: var(--font-mono); font-size: var(--text-10); color: var(--text-secondary); white-space: nowrap; }
.mifila__nro { flex: none; }
.mifila__tx { flex: 1; min-width: 0; }
.mifila__tx b { display: block; font-size: var(--text-13); font-weight: 600; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mifila__tx > span { display: block; font-size: var(--text-11); color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mifila__dur { flex: none; font-family: var(--font-mono); font-size: var(--text-11); color: var(--text-secondary); }
.mifila__acc { flex: none; display: flex; border-left: 1px solid var(--border-subtle); }
.mifila__ico {
  width: 46px; display: inline-flex; align-items: center; justify-content: center;
  color: var(--brand-navy); text-decoration: none;
}
.mifila__ico + .mifila__ico { border-left: 1px solid var(--border-subtle); }
.mifila__ico:hover { background: var(--brand-yellow-soft); }
.mifila__ico .lu { width: 17px; height: 17px; }

.midia__link { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 5px; min-height: 40px;
  background: none; border: 0; border-radius: var(--r-sm); color: var(--text-secondary); font: inherit; font-size: var(--text-11); cursor: pointer; }
.midia__link .lu { width: 14px; height: 14px; }
.midia__link:hover { background: var(--cream-100); color: var(--text-primary); }

/* ══════════════════════════════════════════════════════════════
   v51 · Finanzas · resumen, lo que pedía la maqueta y no estaba
   ══════════════════════════════════════════════════════════════ */

/* La tendencia bajo el saldo. El número solo no dice si viene subiendo
   o cayendo — es el mismo argumento con el que la maqueta justifica el
   Inicio, y acá valía igual. */
.fin-hero__spark { margin-top: 10px; height: 34px; }
.fin-hero__spark svg { width: 100%; height: 34px; display: block; }

/* El gráfico grande ocupa las dos columnas: es la forma de la empresa y
   partido al medio no se lee. */
/* v52 · TOPE DE ALTO para los gráficos de área.
   `serie.js` usa `width:100%; height:auto` sobre un viewBox de 480×168,
   así que el alto CRECE con el ancho: medido en producción, puesto a
   todo el ancho (1397 px) daba 489 px de alto — más que la pantalla
   visible. En el Inicio nunca se notó porque va en una columna de 690.
   El tope evita que el próximo que lo ponga ancho repita el problema.
   La maqueta, de hecho, lo dibuja a media columna: 548 × 190. */
.sviz { max-height: 260px; }

.panel--ancho { grid-column: 1 / -1; }

/* La antigüedad en UNA barra. Cuatro cifras sueltas obligan a hacer la
   división en la cabeza; la proporción se ve sola. */
.aging-bar {
  display: flex; height: 12px; border-radius: var(--r-pill);
  overflow: hidden; background: var(--surface-sunken);
}
.aging-bar i {
  display: block; height: 100%;
  transform-origin: left center;
  animation: bfila-crece var(--duration-slow, .6s) var(--ease-out, cubic-bezier(.22,1,.36,1)) both;
}
@media (prefers-reduced-motion: reduce) { .aging-bar i { animation: none; } }
.aging-leg {
  display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 10px;
  font-size: var(--text-11); color: var(--text-secondary);
}
.aging-leg__it { display: inline-flex; align-items: center; gap: 6px; }
.aging-leg__it i { width: 9px; height: 9px; border-radius: var(--r-xs); }
.aging-leg__it b {
  color: var(--text-primary); font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ═══ css/viz.css ═══ */
/* ============================================================
   VIZ · las piezas de dato que Chart.js no cubre
   v38 · 18-sep-2026

   Chart.js dibuja las líneas, las barras y el anillo de los cuatro
   gráficos grandes (Finanzas ×3, Flujo de caja, Material, Reportes).
   Todo lo demás que muestra una proporción en esta app está hecho a
   mano y cada vista lo resolvía a su manera. Acá viven esas piezas,
   una sola vez:

     .viz-anillo    avance de una obra, de un checklist
     .viz-apilada   un total repartido en tramos (antigüedad de cobranza)
     .viz-rank      ranking horizontal (quién te debe, qué material sale)
     .viz-dona      reparto con hueco, sin librería
     .viz-leyenda   la leyenda, igual para todas

   LA REGLA DE COLOR de la v38, que es lo que hace que se vean vivas y
   se lean igual: el RELLENO usa `--viz-N` (puede brillar: es una masa
   grande que se lee de lejos); el TRAZO FINO, el texto y el punto de
   leyenda usan `--viz-ink-N` (tiene que pasar 4,5:1, porque se lee de
   cerca). `php tools/contraste.php` lo verifica en los dos temas.
   ============================================================ */

/* ---------- degradado corto, para todos los rellenos ----------
   Un relleno plano se ve barato; el degradado de dos paradas, del tono
   a su versión profunda, es lo que separa un tablero de una planilla.
   Se define una vez y lo usan todas las piezas. */
.viz-c1 { --viz-f: var(--viz-1); --viz-d: var(--viz-1-deep); --viz-i: var(--viz-ink-1); }
.viz-c2 { --viz-f: var(--viz-2); --viz-d: var(--viz-2-deep); --viz-i: var(--viz-ink-2); }
.viz-c3 { --viz-f: var(--viz-3); --viz-d: var(--viz-3-deep); --viz-i: var(--viz-ink-3); }
.viz-c4 { --viz-f: var(--viz-4); --viz-d: var(--viz-4-deep); --viz-i: var(--viz-ink-4); }
.viz-c5 { --viz-f: var(--viz-5); --viz-d: var(--viz-5-deep); --viz-i: var(--viz-ink-5); }
.viz-c6 { --viz-f: var(--viz-6); --viz-d: var(--viz-6-deep); --viz-i: var(--viz-ink-6); }

/* ============================================================
   ANILLO DE AVANCE
   Un porcentaje que se ve antes de leerse. Se usa en la ficha de obra,
   en el panel de proyectos y en el checklist de una OT.
   ============================================================ */
/* Ojo con la cascada: si acá se declarara `--viz-f`, pisaría al de
   `.viz-c3` — las dos son de una sola clase, así que gana la que va
   después en el archivo, que es ésta. Por eso el color por defecto va
   como respaldo del `var()` donde se usa, no como declaración. */
.viz-anillo {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: var(--anillo-tam, 52px);
  height: var(--anillo-tam, 52px);
  flex: none;
}
.viz-anillo svg { width: 100%; height: 100%; display: block; }
.viz-anillo__pista { fill: none; stroke: var(--cream-100); stroke-width: 6; }
.viz-anillo__arco {
  fill: none;
  stroke: var(--viz-f, var(--viz-1));
  stroke-width: 6;
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: center;
  /* El arco se dibuja creciendo. Se anima el LARGO del trazo, nunca el
     desfase: el desfase es la POSICIÓN del segmento, y animarlo deja
     todos los arcos superpuestos y del mismo color al terminar. */
  transition: stroke-dasharray var(--duration-slow) var(--ease-out);
}
.viz-anillo__cifra {
  position: absolute;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-14);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
/* v46 · el rótulo de una palabra debajo de la cifra («cierre», «hoy»).
   Va adentro de la cifra y no como hermano para que el par quede
   centrado como un bloque: con dos hijos absolutos, uno pisaba al otro. */
.viz-anillo__cifra { display: flex; flex-direction: column; align-items: center; line-height: 1.05; }
.viz-anillo__cifra small {
  font-family: var(--font-mono); font-weight: 600; font-size: var(--text-10);
  color: var(--text-secondary); text-transform: uppercase; letter-spacing: .06em;
}
.viz-anillo--sm { --anillo-tam: 40px; }
.viz-anillo--sm .viz-anillo__cifra { font-size: var(--text-11); }
.viz-anillo--lg { --anillo-tam: 72px; }
.viz-anillo--lg .viz-anillo__cifra { font-size: var(--text-18); }

/* ============================================================
   BARRA APILADA · un total repartido en tramos
   ============================================================ */
.viz-apilada {
  display: flex;
  gap: 2px;
  height: 12px;
  border-radius: var(--r-pill);
}
.viz-apilada__tramo {
  display: block;
  height: 100%;
  min-width: 3px;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--viz-f, var(--viz-1)), var(--viz-d, var(--viz-1-deep)));
  transition: flex-basis var(--duration-slow) var(--ease-out);
}
/* Sin tramos: una pista vacía, no un hueco. */
.viz-apilada:empty {
  background: var(--cream-100);
  border-radius: var(--r-pill);
}

/* ============================================================
   RANKING HORIZONTAL
   Para "quién te debe", "qué material sale más", "por dónde llegó".
   El nombre arriba, el número a la derecha, la barra abajo cruzando
   las dos columnas: así el número no compite con la barra por el
   espacio y los nombres largos no rompen la fila.
   ============================================================ */
.viz-rank { display: flex; flex-direction: column; gap: var(--space-3); }
.viz-rank__li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-1) var(--space-3);
  align-items: baseline;
}
.viz-rank__nom { font-size: var(--text-13); font-weight: 600; color: var(--text-primary); min-width: 0; }
.viz-rank__val {
  font-family: var(--font-mono);
  font-size: var(--text-13);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--text-primary);
}
.viz-rank__pista {
  grid-column: 1 / -1;
  height: 8px;
  background: var(--cream-100);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.viz-rank__barra {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--viz-f, var(--viz-1)), var(--viz-d, var(--viz-1-deep)));
  transition: width var(--duration-slow) var(--ease-out);
}

/* ============================================================
   DONA · reparto con hueco, sin librería
   Segmentos separados por un hueco real: pegados se leen como un solo
   anillo de colores y no como partes.
   ============================================================ */
.viz-dona { position: relative; display: inline-grid; place-items: center; }
.viz-dona svg { display: block; width: var(--dona-tam, 152px); height: var(--dona-tam, 152px); }
.viz-dona__pista { fill: none; stroke: var(--cream-100); stroke-width: 15; }
.viz-dona__arco {
  fill: none;
  stroke-width: 15;
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: center;
}
.viz-dona__centro {
  position: absolute;
  text-align: center;
  line-height: 1.2;
  pointer-events: none;
}
.viz-dona__centro b {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-20);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.viz-dona__centro span { display: block; font-size: var(--text-11); color: var(--text-secondary); }

/* ============================================================
   LEYENDA · una sola para todas las piezas
   El punto lleva el degradado del relleno y el número va en mono, que
   es como se compara una columna de cifras.
   ============================================================ */
.viz-leyenda {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: center;
  margin-top: var(--space-3);
  font-size: var(--text-12);
  color: var(--text-secondary);
}
.viz-leyenda__it { display: inline-flex; align-items: center; gap: var(--space-2); }
.viz-leyenda__punto {
  width: 9px; height: 9px;
  border-radius: var(--r-xs);
  flex: none;
  background: linear-gradient(135deg, var(--viz-f, var(--viz-1)), var(--viz-d, var(--viz-1-deep)));
}
.viz-leyenda__val {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  font-weight: 600;
}

/* ============================================================
   ENTRADA
   Todo lo que muestra una proporción crece desde cero al aparecer: el
   número deja de ser un dato suelto y pasa a ser una proporción. Se
   dispara agregando `is-viva` (lo hace `Sei.ui.viz.animar`).
   ============================================================ */
.viz-rank__barra,
.viz-apilada__tramo { --viz-w: 0%; width: var(--viz-w); }
.viz-apilada__tramo { width: auto; flex: 0 0 var(--viz-w); }

@media (prefers-reduced-motion: reduce) {
  .viz-anillo__arco,
  .viz-rank__barra,
  .viz-apilada__tramo { transition: none; }
}

/* ============================================================
   MODO OSCURO
   Las pistas vacías con `--cream-100` quedan casi negras sobre la
   tarjeta nueva. Se suben al nivel hundido, que es lo que una pista
   es: un canal por debajo de la superficie.
   ============================================================ */
[data-theme="dark"] .viz-anillo__pista,
[data-theme="dark"] .viz-dona__pista { stroke: var(--surface-sunken); }
[data-theme="dark"] .viz-rank__pista,
[data-theme="dark"] .viz-apilada:empty { background: var(--surface-sunken); }

/* ══════════════════════════════════════════════════════════════
   v52 · BARRAS DE ALTO FIJO  (js/ui/barras.js)

   La v51 las dibujó con `height:auto` sobre un viewBox de 480×168, así
   que el alto crecía con el ancho: medido en producción, una tarjeta de
   1397 px daba un gráfico de **489 px de alto**, un 69 % más que la
   pantalla visible. La maqueta no hace eso — su gráfico ancho mide
   939 × 172, con el alto FIJO.

   Para tener alto fijo y ancho libre el dibujo se estira sólo en
   horizontal, y por eso el SVG lleva sólo geometría: los rótulos son
   HTML alrededor. Un rectángulo estirado no se nota; una letra sí.
   ══════════════════════════════════════════════════════════════ */
.bviz { --bviz-alto: 150px; display: block; }

/* El lienzo: alto fijo, ancho libre. Los rótulos del eje de valores van
   encima, posicionados sobre las mismas tres líneas de la grilla. */
.bviz__lienzo {
  position: relative;
  height: var(--bviz-alto);
  padding-left: 52px;
}
.bviz__svg {
  width: 100%; height: 100%; display: block; overflow: visible;
}
.bviz__grilla { stroke: var(--border-subtle); stroke-width: 1; }

.bviz__y {
  position: absolute; left: 0; width: 46px;
  transform: translateY(-50%);
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--text-10);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
}

.bviz__x {
  display: flex;
  margin-left: 52px;
  padding-top: 6px;
}
.bviz__x span {
  flex: 1 1 0; min-width: 0;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-10);
  color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Crece desde el piso. Se anima el ALTO, que es la magnitud — nunca la
   posición, que es el error que viz.css documenta desde la v38 para el
   anillo y vale igual acá. */
.bviz__b {
  transform-origin: center bottom;
  transform-box: fill-box;
  animation: bviz-sube var(--duration-slow, .6s) var(--ease-out, cubic-bezier(.22,1,.36,1)) both;
}
@keyframes bviz-sube { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@media (prefers-reduced-motion: reduce) {
  .bviz__b { animation: none; }
}

/* ── En el teléfono ──
   Dos cosas, las dos aprendidas midiendo a 375 px.

   1. Menos alto. Y NO se puede hacer con `--bviz-alto` en la media
      query: el valor viene en un `style=` en línea, que le gana a
      cualquier regla del archivo. Por eso el tope va en el `height`
      con `min()`, que sí manda.

   2. Doce meses en 263 px dan 22 px por mes: las barras salen de 6 px y
      los rótulos se cortan en «e…». Entonces el gráfico SE DESLIZA, que
      es la misma decisión que el riel de pestañas tomó en la v44 —
      apretar hasta que no se lee no es adaptarse. La barra de scroll
      queda fina pero VISIBLE: esconderla es el patrón que este repo ya
      arregló dos veces. */
@media (max-width: 640px) {
  .bviz {
    overflow-x: auto;
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
  }
  .bviz__lienzo, .bviz__x { min-width: 420px; }
  .bviz__lienzo {
    height: min(var(--bviz-alto), 118px);
    padding-left: 42px;
  }
  .bviz__y { width: 36px; }
  .bviz__x { margin-left: 42px; }
}

/* ── Barras acostadas (HTML, no SVG: el nombre usa la tipografía y el
      recorte del navegador, y la fila puede ser un enlace) ── */
.bfila { display: flex; flex-direction: column; gap: 7px; }
.bfila__f { display: grid; grid-template-columns: minmax(0, 1fr) 1.15fr auto; align-items: center; gap: 10px; }
.bfila__n {
  font-size: var(--text-12);
  color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}
.bfila__p {
  display: block; height: 9px; border-radius: var(--r-pill);
  background: var(--surface-sunken);
  overflow: hidden;
}
.bfila__p i {
  display: block; height: 100%; border-radius: var(--r-pill);
  animation: bfila-crece var(--duration-slow, .6s) var(--ease-out, cubic-bezier(.22,1,.36,1)) both;
}
@keyframes bfila-crece { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.bfila__p i { transform-origin: left center; }
@media (prefers-reduced-motion: reduce) { .bfila__p i { animation: none; } }
.bfila__v {
  font-family: var(--font-mono);
  font-size: var(--text-11);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ══════════════════════════════════════════════════════════════
   v51 · DONA DE REPARTO  (js/ui/dona.js)
   El anillo mide UNA proporción; esto reparte un total entre varios.
   Cada segmento necesita su desfase para saber dónde empieza —eso es
   fijo—, y lo que se ANIMA es el largo, igual que el anillo.
   ══════════════════════════════════════════════════════════════ */
.dviz { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
/* 172 px, que es a lo que se dibuja en la maqueta. La v51 la mostraba
   a 140 y por eso se veía más chica que la propuesta. */
.dviz__svg { width: 172px; height: 172px; flex: 0 0 auto; }
.dviz__pista { stroke: var(--surface-sunken); }
.dviz__arco {
  /* Punta REDONDA, como la maqueta. Es lo que le da el aspecto de la
     propuesta: con `butt` los segmentos se cortan en escuadra y la dona
     se ve como un gráfico de planilla. Los extremos se pisan un pelo
     entre segmentos vecinos y está bien: es a propósito. */
  stroke-linecap: round;
  animation: dviz-crece var(--duration-slow, .6s) var(--ease-out, cubic-bezier(.22,1,.36,1)) both;
}
@keyframes dviz-crece {
  from { stroke-dasharray: 0 9999; }
  to   { stroke-dasharray: var(--dviz-l) 9999; }
}
@media (prefers-reduced-motion: reduce) { .dviz__arco { animation: none; } }
/* El tamaño va como ATRIBUTO del <text>, en unidades del viewBox, no
   acá en px: el SVG escala 140 → 172, así que un «15.5» del dibujo son
   19 px en pantalla, que es lo que muestra la maqueta. Ponerlo en px
   acá sería mentir sobre lo que se ve y además romper la escala
   tipográfica del sistema. */
.dviz__c { font-family: var(--font-display); font-weight: 700; fill: var(--text-primary); }
.dviz__r { font-family: var(--font-mono); fill: var(--text-secondary); letter-spacing: .06em; }
/* Acotada: sin tope, `flex:1` estira la leyenda hasta el borde de la
   tarjeta y en una pantalla ancha la cifra queda a 1.100 px de su
   color. El ojo tiene que poder saltar del punto al número sin
   recorrer la pantalla. */
.dviz-leg {
  display: flex; flex-direction: column; gap: 5px;
  min-width: 0; flex: 0 1 300px; max-width: 320px;
}
.dviz-leg__it { display: flex; align-items: center; gap: 7px; font-size: var(--text-11); min-width: 0; }
.dviz-leg__it i { width: 9px; height: 9px; border-radius: var(--r-xs); flex: 0 0 auto; }
.dviz-leg__n {
  color: var(--text-secondary); min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dviz-leg__it b {
  margin-left: auto; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; color: var(--text-primary);
}

/* ── v51 · El encabezado de un gráfico ──
   Título a la izquierda, qué mide a la derecha. Estaba escrito a mano
   en cada tarjeta con gráfico, con tamaños distintos cada vez. */
.sviz__cab {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 10px; flex-wrap: wrap;
}
.sviz__t { font-weight: 700; font-size: var(--text-14); color: var(--text-primary); }
.sviz__s {
  font-size: var(--text-11); color: var(--text-secondary);
  font-family: var(--font-mono);
}

/* ── v51 · Los KPI de Contabilidad ──
   La maqueta los pide con una barra de proporción debajo: débito y
   crédito se comparan ENTRE SÍ, y un número solo no dice si el mes fue
   de vender o de comprar. */
.sii-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 14px;
}
.sii-kpi { min-width: 0; }
.sii-kpi--fuerte { border-left: 3px solid var(--border-default); padding-left: 12px; }
.sii-kpi__l { font-size: var(--text-11); color: var(--text-secondary); }
.sii-kpi__v {
  font-size: var(--text-18); font-weight: 600;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  margin-top: 2px;
}
.sii-kpi__v--g { font-size: var(--text-24); font-weight: 800; }
.sii-kpi__s { font-size: var(--text-11); color: var(--text-secondary); margin-top: 2px; }
.sii-kpi__b {
  display: block; height: 6px; margin-top: 8px;
  border-radius: var(--r-pill); background: var(--surface-sunken); overflow: hidden;
}
.sii-kpi__b i {
  display: block; height: 100%; border-radius: var(--r-pill);
  transform-origin: left center;
  animation: bfila-crece var(--duration-slow, .6s) var(--ease-out, cubic-bezier(.22,1,.36,1)) both;
}
@media (prefers-reduced-motion: reduce) { .sii-kpi__b i { animation: none; } }

/* ═══ css/informes.css ═══ */
/* ============================================================
   SEINGELEC · Informes Técnicos
   Estudio de informe (ventana) + documento con membrete
   Hereda los tokens del design system (styles.css)
   ============================================================ */

/* ---------- App frame mínimo (contexto del producto) ---------- */
.lu { width: 16px; height: 16px; flex-shrink: 0; }
.app {
  min-height: 100vh;
  background: var(--cream-base);
  display: flex;
  flex-direction: column;
}

/* topbar producto */
.topbar {
  height: 56px;
  background: var(--surface-elevated);
  border-bottom: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 22px;
  position: sticky;
  top: 0;
  z-index: 10;
}
.topbar__brand { display: flex; align-items: center; gap: 9px; }
.topbar__brand img { width: 26px; height: 26px; }
.topbar__brand .n {
  font-family: var(--font-display);
  font-weight: 700; font-size: var(--text-16); letter-spacing: .02em;
}
.crumbs {
  display: flex; align-items: center; gap: 7px;
  font-size: var(--text-13); color: var(--text-secondary);
}
.crumbs svg { width: 12px; height: 12px; }
.crumbs .now { color: var(--text-primary); font-weight: 600; }
.topbar__spacer { flex: 1; }
.avatar {
  width: 30px; height: 30px; border-radius: var(--r-pill);
  background: var(--brand-navy); color: #fff;
  font-size: var(--text-11); font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}

/* ---------- Cabecera de proyecto ---------- */
.proj {
  max-width: 1080px;
  width: 100%;
  margin: 0 auto;
  padding: 26px 24px 60px;
}
.proj__head {
  display: flex; align-items: flex-start; gap: 16px;
  margin-bottom: 18px;
}
.proj__id {
  font-family: var(--font-mono);
  font-size: var(--text-13); letter-spacing: .04em;
  color: var(--brand-ink);
  font-weight: 600;
}
.proj__name { font-family: var(--font-display); font-size: var(--text-24); font-weight: 700; line-height: 1.18; margin: 3px 0 0; }
.proj__client { font-size: var(--text-14); color: var(--text-secondary); margin-top: 1px; }
.proj__tags { display: flex; gap: 8px; margin-top: 9px; }
.tag-pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--text-12); font-weight: 600;
  padding: 4px 11px; border-radius: var(--r-pill);
  background: var(--surface-sunken); color: var(--text-secondary);
}
.tag-pill--ok { background: var(--success-soft); color: var(--success); }
.tag-pill--ok::before { content:""; width: 7px; height: 7px; border-radius: var(--r-pill); background: currentColor; }
.tag-pill--done { background: var(--cream-100); color: var(--text-primary); }
.proj__actions { margin-left: auto; display: flex; gap: 8px; }
.icon-btn {
  width: 34px; height: 34px; border-radius: var(--r-sm);
  border: 1.5px solid var(--border-default);
  background: var(--surface-elevated);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--text-secondary);
  transition: 120ms ease;
}
.icon-btn:hover { border-color: var(--brand-yellow); color: var(--text-primary); }
.icon-btn svg { width: 16px; height: 16px; }
.icon-btn--danger:hover { border-color: var(--error); color: var(--error); }

/* tabs */
.subtabs {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--cream-100); border-radius: var(--r-sm);
  margin-bottom: 4px;
}
.subtabs button {
  font-family: var(--font-body); font-size: var(--text-13); font-weight: 600;
  color: var(--text-secondary); background: transparent;
  border: none; border-radius: var(--r-sm); padding: 7px 18px; cursor: pointer;
}
.subtabs button[aria-pressed="true"] {
  background: var(--surface-elevated); color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}
.tabrule { border: none; border-top: 1px solid var(--border-subtle); margin: 14px 0 22px; }

/* ---------- Sección Generar informe ---------- */
.gen__title { font-family: var(--font-display); font-size: var(--text-22); font-weight: 700; margin: 0; }
.gen__sub { font-size: var(--text-13); color: var(--text-secondary); margin: 3px 0 18px; max-width: 60ch; }

.gen__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}
@media (max-width: 767px) { .gen__grid { grid-template-columns: 1fr; } }

.rep-card {
  display: flex; align-items: center; gap: 14px;
  background: var(--surface-elevated);
  border: 1.5px solid var(--border-default);
  border-radius: var(--r-md);
  padding: 15px 16px;
  text-align: left; width: 100%;
  cursor: pointer;
  transition: 140ms ease;
  position: relative;
}
.rep-card:hover {
  border-color: var(--brand-yellow);
  box-shadow: var(--shadow-hover);
  transform: translateY(-1px);
}
.rep-card__ic {
  width: 46px; height: 46px; border-radius: var(--r-md);
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.rep-card__ic svg { width: 22px; height: 22px; }
.rep-card__phase {
  font-family: var(--font-mono); font-size: var(--text-10); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-disabled);
}
.rep-card__name { font-size: var(--text-15); font-weight: 600; line-height: 1.2; margin-top: 1px; }
.rep-card__desc { font-size: var(--text-12); color: var(--text-secondary); margin-top: 2px; }
.rep-card__chev { margin-left: auto; color: var(--text-disabled); flex-shrink: 0; }
.rep-card__chev svg { width: 18px; height: 18px; }
.rep-card__soon {
  position: absolute; top: 10px; right: 12px;
  font-family: var(--font-mono); font-size: var(--text-10); font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-disabled);
  background: var(--cream-100);
  padding: 2px 7px; border-radius: var(--r-pill);
}
.rep-card--soon { opacity: .72; }
.rep-card--soon:hover { transform: none; border-color: var(--border-default); box-shadow: none; }

/* paletas de íconos por informe */
.ic--info    { background: var(--info-soft);          color: var(--info); }
.ic--success { background: var(--success-soft);       color: var(--success); }
.ic--yellow  { background: var(--brand-yellow-soft);  color: var(--brand-ink); }
.ic--amber   { background: var(--warning-soft);       color: var(--warning); }
.ic--neutral { background: var(--cream-100);          color: var(--text-secondary); }
.ic--error   { background: var(--error-soft);         color: var(--error); }
.ic--navy    { background: #E7E7E3;                   color: var(--brand-navy); }

/* generados recientes */
.recent {
  margin-top: 22px;
  background: var(--cream-50);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  padding: 14px 16px;
}
.recent__h {
  font-family: var(--font-mono); font-size: var(--text-10); letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-secondary); margin: 0 0 10px;
}
.recent__row {
  display: flex; align-items: center; gap: 10px;
  font-size: var(--text-13); padding: 7px 0;
  border-top: 1px solid var(--border-subtle);
}
.recent__row:first-of-type { border-top: none; }
.recent__row .fic { color: var(--error); width: 16px; height: 16px; flex-shrink: 0; }
.recent__row .nm { flex: 1; font-weight: 500; }
.recent__row .dt { color: var(--text-secondary); font-size: var(--text-12); }
.recent__row .dl { color: var(--text-secondary); cursor: pointer; }
.recent__row .dl svg { width: 15px; height: 15px; }

/* ============================================================
   ESTUDIO DE INFORME (ventana modal)
   ============================================================ */
.studio-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(20,20,15,.55);
  /* Sin el prefijo, en Safari iOS nunca desenfocó: el estudio de informes
     se usa sobre todo en iPad. */
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center;
  padding: 26px;
  opacity: 0; pointer-events: none;
  transition: opacity 180ms ease;
}
.studio-overlay.open { opacity: 1; pointer-events: auto; }

.studio {
  width: min(1240px, 100%);
  height: min(860px, 100%);
  background: var(--surface-base);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-overlay);
  display: flex; flex-direction: column;
  overflow: hidden;
  transform: translateY(8px) scale(.99);
  transition: transform 200ms ease;
}
.studio-overlay.open .studio { transform: none; }

.studio__head {
  display: flex; align-items: center; gap: 13px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.studio__ic {
  width: 40px; height: 40px; border-radius: var(--r-md);
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.studio__ic svg { width: 20px; height: 20px; }
.studio__titles h2 { font-family: var(--font-display); font-size: var(--text-18); font-weight: 700; margin: 0; }
.studio__titles p { font-size: var(--text-12); color: var(--text-secondary); margin: 1px 0 0; }
.studio__head-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }

/* selector de papel */
.paper-seg {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--cream-100); border-radius: var(--r-sm);
}
.paper-seg button {
  font-family: var(--font-body); font-size: var(--text-12); font-weight: 600;
  color: var(--text-secondary); background: transparent;
  border: none; border-radius: var(--r-xs); padding: 5px 11px; cursor: pointer;
}
.paper-seg button[aria-pressed="true"] {
  background: var(--surface-elevated); color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}

.studio__body {
  flex: 1; min-height: 0;
  display: grid;
  grid-template-columns: 1.05fr 1.2fr;
}

/* --- panel formulario --- */
.form-pane {
  overflow-y: auto;
  padding: 20px 22px 28px;
  border-right: 1px solid var(--border-subtle);
  background: var(--cream-50);
}
.form-pane::-webkit-scrollbar { width: 9px; }
.form-pane::-webkit-scrollbar-thumb { background: var(--border-default); border-radius: var(--r-sm); }

/* barra de plantillas (arriba del formulario del studio) */
.tpl-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: -4px 0 16px;
  padding: 8px 10px;
  background: var(--surface-elevated);
  border: 1px dashed var(--border-default);
  border-radius: var(--r-md);
}
.tpl-bar[hidden] { display: none; }
.tpl-bar > .lu { color: var(--text-secondary); flex: none; }
.tpl-bar .sel { flex: 1 1 160px; width: auto; padding: 6px 9px; font-size: var(--text-13); }
.tpl-bar .btn { flex: none; }
@media (max-width: 640px) {
  .tpl-bar .sel { flex: 1 1 100%; }
  .tpl-bar .btn { flex: 1 1 calc(50% - 4px); justify-content: center; }
}

.fgroup { margin-bottom: 18px; }
.fgroup__h {
  font-family: var(--font-mono); font-size: var(--text-10); letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-secondary);
  margin: 0 0 10px; display: flex; align-items: center; gap: 7px;
}
.fgroup__h::after { content: ""; flex: 1; height: 1px; background: var(--border-subtle); }

.field { margin-bottom: 11px; }
.field > label {
  display: block; font-size: var(--text-12); font-weight: 600;
  color: var(--text-primary); margin-bottom: 5px;
}
.field__row { display: flex; gap: 10px; }
.field__row > .field { flex: 1; margin-bottom: 0; }

.inp, .ta, .sel {
  width: 100%; font-family: var(--font-body); font-size: var(--text-13);
  color: var(--text-primary);
  background: var(--surface-elevated);
  border: 1.5px solid var(--border-default);
  border-radius: var(--r-sm);
  padding: 9px 11px;
  transition: 120ms ease;
}
.inp:focus, .ta:focus, .sel:focus {
  outline: none; border-color: var(--brand-yellow);
  box-shadow: 0 0 0 3px var(--brand-yellow-soft);
}
.ta { resize: vertical; min-height: 70px; line-height: 1.5; }
.inp::placeholder, .ta::placeholder { color: var(--text-disabled); }
.field__hint { font-size: var(--text-11); color: var(--text-secondary); margin-top: 4px; }

/* tabla editable de mediciones */
.meas-tbl { width: 100%; border-collapse: collapse; }
.meas-tbl th {
  font-family: var(--font-mono); font-size: var(--text-10); letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-secondary);
  text-align: left; padding: 0 6px 6px; font-weight: 600;
}
.meas-tbl td { padding: 0 4px 6px 0; }
.meas-tbl td:last-child { padding-right: 0; }
.meas-tbl .inp { padding: 7px 8px; font-size: var(--text-12); border-radius: var(--r-sm); }
.meas-del {
  border: none; background: transparent; color: var(--text-disabled);
  cursor: pointer; padding: 4px; display: inline-flex;
}
.meas-del:hover { color: var(--error); }
.meas-del svg { width: 14px; height: 14px; }
.add-row {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-body); font-size: var(--text-12); font-weight: 600;
  color: var(--text-secondary); background: transparent;
  border: 1.5px dashed var(--border-default); border-radius: var(--r-sm);
  padding: 7px 12px; cursor: pointer; margin-top: 2px;
}
.add-row:hover { border-color: var(--brand-yellow); color: var(--text-primary); }
.add-row svg { width: 13px; height: 13px; }

/* tarjeta de hito (form avance) */
.hito-card {
  border: 1.5px solid var(--border-default);
  border-radius: var(--r-md);
  padding: 11px 12px 12px;
  margin-bottom: 10px;
  background: var(--surface-elevated);
}
.hito-card .field { margin-bottom: 8px; }
.sub-check {
  width: 22px; height: 22px; flex-shrink: 0;
  border: 1.5px solid var(--border-default); border-radius: var(--r-sm);
  background: var(--surface-elevated); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--brand-navy);
}
.sub-check.on { background: var(--brand-yellow); border-color: var(--brand-yellow-dark); }
.sub-check svg { width: 13px; height: 13px; }

/* selector de fotos · grid responsivo (auto-fill desktop, 3 cols celular) */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 8px;
}
.photo-slot {
  aspect-ratio: 4 / 3;
  border-radius: var(--r-sm);
  background: repeating-linear-gradient(135deg, #E2E0DA, #E2E0DA 8px, #D4D4CF 8px, #D4D4CF 16px);
  position: relative; cursor: pointer; border: 2px solid transparent;
  transition: 120ms ease;
  -webkit-tap-highlight-color: transparent;
}
.photo-slot.sel { border-color: var(--brand-yellow); }
.photo-slot.sel::after {
  content: ""; position: absolute; top: 4px; right: 4px;
  width: 20px; height: 20px; border-radius: var(--r-pill);
  background: var(--brand-yellow);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%231A1A18' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: center;
  box-shadow: var(--shadow-sm);
}
.photo-slot:not(.sel) { opacity: .55; }
@media (max-width: 640px) {
  .photo-grid { grid-template-columns: repeat(3, 1fr); gap: 6px; }
  .photo-slot.sel::after { width: 22px; height: 22px; top: 5px; right: 5px; }
}
@media (max-width: 380px) {
  .photo-grid { grid-template-columns: repeat(2, 1fr); }
}

/* --- panel preview --- */
.preview-pane {
  background: #54544e;
  overflow-y: auto;
  display: flex; flex-direction: column; align-items: center;
  padding: 24px 24px 32px;
  gap: 12px;
}
.preview-pane::-webkit-scrollbar { width: 9px; }
.preview-pane::-webkit-scrollbar-thumb { background: #3a3a36; border-radius: var(--r-sm); }
.preview-tag {
  font-family: var(--font-mono); font-size: var(--text-10); letter-spacing: .12em;
  text-transform: uppercase; color: #c9c9c2; align-self: flex-start;
}
/* escala la hoja para que quepa a lo ancho del panel */
/* escala la hoja para que quepa a lo ancho del panel */
#doc-mount {
  width: 100%;
  display: flex; flex-direction: column; align-items: center; gap: 18px;
}
.doc-wrap {
  overflow: hidden;
  box-shadow: var(--shadow-overlay);
  flex-shrink: 0;
}
#doc-mount .doc {
  transform: scale(var(--doc-scale, 1));
  transform-origin: top left;
  box-shadow: none;
}

/* --- footer --- */
.studio__foot {
  display: flex; align-items: center; gap: 14px;
  padding: 13px 18px;
  border-top: 1px solid var(--border-subtle);
  flex-shrink: 0;
  background: var(--surface-base);
}
.foot-note { font-size: var(--text-12); color: var(--text-secondary); display: flex; align-items: center; gap: 7px; }
.foot-note svg { width: 14px; height: 14px; color: var(--success); }
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-body); font-size: var(--text-13); font-weight: 600;
  border-radius: var(--r-sm); padding: 9px 16px; cursor: pointer; border: none;
  transition: 120ms ease;
}
.btn svg { width: 15px; height: 15px; }
.btn--ghost { background: transparent; color: var(--text-secondary); border: 1.5px solid var(--border-default); }
.btn--ghost:hover { color: var(--text-primary); border-color: var(--text-secondary); }
.btn--primary { background: var(--brand-yellow); color: var(--brand-navy); box-shadow: var(--shadow-yellow); }
.btn--primary:hover { background: var(--brand-yellow-dark); }
.foot-spacer { flex: 1; }

/* Footer del creador de informes en móvil: en una sola fila los 3 botones +
   la nota se encimaban ("Impri…Guardar borra…Generar PDF"). Ahora la nota va
   arriba en su propia línea y los botones a 2 por fila, legibles. */
@media (max-width: 640px) {
  .studio__foot {
    flex-wrap: wrap;
    gap: 8px;
    padding: 10px 12px;
  }
  .studio__foot .foot-note { flex-basis: 100%; order: -1; font-size: var(--text-11); }
  .studio__foot .foot-spacer { display: none; }
  .studio__foot .btn {
    flex: 1 1 calc(50% - 8px);
    justify-content: center;
    white-space: nowrap;
    padding: 10px 12px;
  }
}

/* ============================================================
   DOCUMENTO · membrete SEINGELEC (A4 / Carta)
   Usa <table> con <thead>/<tfoot> para repetir membrete y pie
   automáticamente en cada página al imprimir/PDF.
   ============================================================ */
.doc {
  background: #fff; color: var(--brand-navy);
  box-shadow: var(--shadow-overlay);
  font-family: var(--font-body);
  line-height: 1.45;
  width: 210mm;
  border-collapse: collapse;
  table-layout: fixed;
}
.doc[data-paper="carta"] { width: 216mm; }

/* celdas del wrapper table */
.doc-thead-cell,
.doc-tfoot-cell,
.doc-body-cell {
  padding: 0 14mm;
  vertical-align: top;
}
.doc-thead-cell { padding-top: 10mm; padding-bottom: 0; }
.doc-tfoot-cell { padding-bottom: 8mm; padding-top: 4mm; }
.doc-body-cell  { padding-top: 2mm; padding-bottom: 4mm; }

/* compat: .doc__inner sigue funcionando como fallback para informes no migrados */
.doc__inner {
  padding: 14mm 14mm 12mm;
  display: block;
  min-height: inherit;
}

/* secciones no se parten a mitad al imprimir */
.doc-sec { break-inside: avoid; margin-bottom: 12px; }

/* ---------- DOCUMENTO SOBRIO · header claro, tinta navy ---------- */
:root { --doc-ink: #1F1F1D; --doc-body: #3A3A36; --doc-soft: #6E6E69; --doc-rule: #D7D5CE; --doc-rule-strong: #1F1F1D; }

/* acento amarillo fino a sangre, arriba de la hoja */
.doc-accent {
  height: 3px; background: var(--brand-yellow);
  margin: -14mm -14mm 0;
}

/* membrete claro */
.doc-head {
  display: grid; grid-template-columns: auto 1fr auto;
  align-items: flex-start; gap: 18px;
  padding: 7mm 0 0;
}
.doc-head__brand { display: flex; align-items: center; gap: 10px; }
.doc-head__brand img { height: 34px; width: auto; }
.doc-head__wm { display: flex; flex-direction: column; line-height: 1; }
.doc-head__wm .nm { font-family: var(--font-display); font-weight: 700; font-size: 16pt; letter-spacing: .01em; color: var(--doc-ink); }
.doc-head__wm .tg { font-size: 6pt; letter-spacing: .18em; text-transform: uppercase; color: var(--doc-soft); margin-top: 4px; }
.doc-head__doc { text-align: right; }
.doc-head__type { font-weight: 700; font-size: 9.5pt; letter-spacing: .14em; text-transform: uppercase; color: var(--doc-ink); }
.doc-head__num { font-family: var(--font-mono); font-size: 8pt; color: var(--doc-soft); margin-top: 4px; }
.doc-head__num b { color: var(--doc-ink); font-weight: 600; }

/* línea de contacto + regla principal */
.doc-org {
  font-size: 7pt; color: var(--doc-soft); letter-spacing: .01em;
  margin: 7px 0 0;
}
.doc-org .rut { color: var(--doc-ink); font-weight: 600; }
.doc-rule {
  height: 0; border: none; border-top: 1.5px solid var(--doc-rule-strong);
  margin: 6px 0 14px;
}

/* secciones */
.doc-sec { margin-bottom: 11px; }
.doc-sec__h {
  display: flex; align-items: center; gap: 7px;
  font-size: 7.4pt; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--doc-ink);
  border-bottom: 1px solid var(--doc-rule);
  padding-bottom: 4px; margin: 0 0 9px;
}
.doc-sec__h::before {
  content: ""; width: 3px; height: 9px; border-radius: 1px;
  background: var(--brand-yellow); flex-shrink: 0;
}

/* grid antecedentes */
.doc-ant {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 10px 16px;
}
.doc-ant .k { font-size: 6.4pt; text-transform: uppercase; letter-spacing: .1em; color: var(--doc-soft); font-weight: 600; }
.doc-ant .v { font-size: 9.4pt; font-weight: 600; color: var(--doc-ink); margin-top: 3px; line-height: 1.25; }

/* texto · cuerpo legible, tono intermedio */
.doc-p { font-size: 9pt; margin: 0; color: var(--doc-body); line-height: 1.55; }
.doc-p--lead { font-size: 9.2pt; color: var(--doc-ink); }

/* tabla mediciones · sobria */
.doc-meas { width: 100%; border-collapse: collapse; }
.doc-meas thead th {
  background: transparent; color: var(--doc-soft);
  font-size: 6.8pt; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  text-align: left; padding: 0 9px 5px;
  border-bottom: 1.5px solid var(--doc-rule-strong);
}
.doc-meas thead th.r { text-align: right; }
.doc-meas tbody td {
  font-size: 8.6pt; padding: 6px 9px; color: var(--doc-body);
  border-bottom: 1px solid var(--doc-rule);
  font-variant-numeric: tabular-nums;
}
.doc-meas tbody td.r { text-align: right; font-family: var(--font-mono); color: var(--doc-ink); }
.doc-meas .pt { font-weight: 600; color: var(--doc-ink); }

/* recomendaciones */
.doc-list { margin: 0; padding-left: 16px; }
.doc-list li { font-size: 8.8pt; margin-bottom: 5px; color: var(--doc-body); line-height: 1.45; padding-left: 3px; }
.doc-list li::marker { color: var(--brand-ink); }

/* diagnóstico · línea sobria con punto de color */
.doc-diag {
  display: inline-flex; align-items: center; gap: 8px;
  border: 1px solid var(--doc-rule); border-radius: var(--r-xs);
  padding: 6px 12px; font-size: 8.8pt; font-weight: 600; color: var(--doc-ink);
}
.doc-diag .dot { width: 8px; height: 8px; border-radius: var(--r-pill); flex-shrink: 0; }
.doc-diag .lbl { font-size: 6.6pt; text-transform: uppercase; letter-spacing: .08em; color: var(--doc-soft); font-weight: 700; }
.doc-diag--ok   .dot { background: var(--success); }
.doc-diag--warn .dot { background: var(--warning); }
.doc-diag--err  .dot { background: var(--error); }

/* fotos */
.doc-photos { display: grid; grid-template-columns: repeat(2, 1fr); gap: 9px; }
.doc-photo {
  height: 26mm; border-radius: var(--r-xs); overflow: hidden;
  background: repeating-linear-gradient(135deg, #EEE, #EEE 7px, #E2E2E2 7px, #E2E2E2 14px);
  border: 1px solid var(--doc-rule);
  position: relative;
}
.doc-photo .cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: rgba(31,31,29,.78); color: #fff;
  font-size: 6.4pt; padding: 3px 7px; letter-spacing: .02em;
}

/* firmas · separación garantizada respecto al contenido anterior */
.doc-signs {
  display: grid; grid-template-columns: 1fr 1fr; gap: 36px;
  margin-top: 12mm; padding-top: 0;
  break-inside: avoid;
}
.doc-sign { text-align: center; }
.doc-sign .line { border-top: 1px solid var(--doc-ink); margin-bottom: 5px; }
.doc-sign .nm { font-size: 8.6pt; font-weight: 700; color: var(--doc-ink); }
.doc-sign .ro { font-size: 7.2pt; color: var(--doc-soft); margin-top: 1px; }

/* pie compromiso */
.doc-foot { margin-top: 11mm; break-inside: avoid; }
.doc-foot__rule { border-top: 1px solid var(--doc-rule); margin-bottom: 7px; }
.doc-foot__h {
  font-weight: 700; font-size: 6.8pt;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--doc-soft); margin: 0 0 3px;
}
.doc-foot__txt {
  font-size: 6.6pt; color: var(--doc-soft); line-height: 1.45; margin: 0;
}
.doc-foot__bar {
  display: flex; justify-content: space-between;
  margin-top: 8px; padding-top: 5px;
  border-top: 1px solid var(--doc-rule);
  font-size: 6.4pt; color: var(--text-disabled);
}

/* ============================================================
   FASE 2 · componentes del Informe de Avance
   ============================================================ */
/* KPIs */
.doc-kpis {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border: 1px solid var(--doc-rule); border-radius: var(--r-xs); overflow: hidden;
}
.doc-kpi { padding: 8px 11px 9px; border-left: 1px solid var(--doc-rule); }
.doc-kpi:first-child { border-left: none; }
.doc-kpi .k { font-size: 6.2pt; text-transform: uppercase; letter-spacing: .1em; color: var(--doc-soft); font-weight: 700; }
.doc-kpi .v { font-family: var(--font-display); font-size: 15pt; font-weight: 700; color: var(--doc-ink); line-height: 1.05; margin-top: 4px; font-variant-numeric: tabular-nums; }
.doc-kpi .sub { font-size: 6.8pt; margin-top: 3px; color: var(--doc-soft); }
.doc-kpi .sub.up { color: var(--success); font-weight: 600; }
.doc-kpi .sub.down { color: var(--error); font-weight: 600; }

/* curva S */
.av-chart { width: 100%; }
.av-chart svg { width: 100%; height: auto; display: block; }
.av-legend { display: flex; gap: 16px; margin-top: 6px; }
.av-leg { display: inline-flex; align-items: center; gap: 6px; font-size: 7pt; color: var(--doc-soft); }
.av-leg .ln { width: 16px; height: 0; border-top: 2px solid var(--doc-ink); }
.av-leg .ln--prog { border-top: 2px dashed var(--doc-soft); }
.av-leg .ln--real { border-top: 2px solid var(--doc-ink); }

/* avance por hito */
.av-hito { margin-bottom: 10px; }
.av-hito__top { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.av-hito__name { font-size: 8.8pt; font-weight: 700; color: var(--doc-ink); }
.av-hito__peso { font-size: 6.4pt; color: var(--doc-soft); font-family: var(--font-mono); }
.av-hito__pct { margin-left: auto; font-family: var(--font-mono); font-size: 8.6pt; font-weight: 600; color: var(--doc-ink); }
.av-estado { font-size: 6.2pt; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; padding: 2px 7px; border-radius: var(--r-pill); white-space: nowrap; }
.av-estado--done  { background: #E5EEE8; color: var(--success); }
.av-estado--curso { background: var(--brand-yellow-100); color: #876B00; }
.av-estado--pend  { background: var(--cream-100); color: var(--doc-soft); }
.av-bar { height: 6px; border-radius: var(--r-pill); background: var(--cream-100); overflow: hidden; }
.av-bar__fill { height: 100%; background: var(--doc-ink); border-radius: var(--r-pill); }
.av-subs { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 6px; }
.av-sub { display: inline-flex; align-items: center; gap: 5px; font-size: 7.6pt; color: var(--doc-body); }
.av-sub .mk {
  width: 9px; height: 9px; border-radius: var(--r-pill); flex-shrink: 0;
  border: 1.2px solid var(--doc-soft);
}
.av-sub--done .mk {
  background: var(--doc-ink); border-color: var(--doc-ink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='7' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: center;
}
.av-sub--done { color: var(--doc-soft); }

/* dos columnas (actividades / próximas) */
.av-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }

/* riesgos */
.risk { display: flex; gap: 8px; align-items: flex-start; font-size: 8.4pt; color: var(--doc-body); margin-bottom: 5px; line-height: 1.4; }
.risk .dot { width: 7px; height: 7px; border-radius: var(--r-pill); margin-top: 3px; flex-shrink: 0; }
.risk--warn .dot { background: var(--warning); }
.risk--ok .dot   { background: var(--success); }
.risk--err .dot  { background: var(--error); }

/* firma única (responsable) */
.doc-signs--one { grid-template-columns: 1fr; justify-items: end; }
.doc-signs--one .doc-sign { width: 64mm; }

/* ============================================================
   FASE 3 · Registro Fotográfico
   ============================================================ */
.rf-intro {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 8px 16px; margin-bottom: 13px;
  padding-bottom: 11px; border-bottom: 1px solid var(--doc-rule);
}
.rf-intro .k { display: block; font-size: 6.4pt; text-transform: uppercase; letter-spacing: .1em; color: var(--doc-soft); font-weight: 600; }
.rf-intro .v { display: block; font-size: 9pt; font-weight: 600; color: var(--doc-ink); margin-top: 2px; }
.rf-count { font-weight: 400; color: var(--doc-soft); letter-spacing: 0; }

.rf-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 11px 12px; }
.rf-photo { break-inside: avoid; }
.rf-photo .img {
  height: 44mm; border-radius: var(--r-xs); overflow: hidden; position: relative;
  background: repeating-linear-gradient(135deg, #EDEDEA, #EDEDEA 9px, #E2E2DE 9px, #E2E2DE 18px);
  border: 1px solid var(--doc-rule);
}
.rf-photo .img .badge {
  position: absolute; top: 6px; left: 6px;
  font-size: 6pt; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  background: rgba(31,31,29,.82); color: #fff; padding: 2px 7px; border-radius: var(--r-pill);
}
.rf-photo .meta { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-top: 5px; }
.rf-photo .cap { font-size: 8pt; font-weight: 600; color: var(--doc-ink); line-height: 1.3; }
.rf-photo .date { font-family: var(--font-mono); font-size: 6.6pt; color: var(--doc-soft); white-space: nowrap; flex-shrink: 0; }

/* form: fila de foto */
.rf-row { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 11px; }
.rf-thumb {
  width: 48px; height: 48px; border-radius: var(--r-sm); flex-shrink: 0; margin-top: 1px;
  background: repeating-linear-gradient(135deg, #E2E0DA, #E2E0DA 6px, #D4D4CF 6px, #D4D4CF 12px);
  border: 1px solid var(--border-default);
}

/* ============================================================
   FASE 4 · Informe Final · Acta de Recepción
   ============================================================ */
.ent-tbl { width: 100%; border-collapse: collapse; }
.ent-tbl thead th {
  background: transparent; color: var(--doc-soft);
  font-size: 6.8pt; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: 0 9px 5px; border-bottom: 1.5px solid var(--doc-rule-strong);
}
.ent-st {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 6.6pt; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 2px 8px; border-radius: var(--r-pill);
}
.ent-st--ok   { background: #E5EEE8; color: var(--success); }
.ent-st--pend { background: var(--warning-soft); color: var(--warning); }
.ent-st--ok::before, .ent-st--pend::before {
  content: ""; width: 6px; height: 6px; border-radius: var(--r-pill); background: currentColor;
}

.gar-grid {
  display: grid; grid-template-columns: auto 1fr 1fr; gap: 8px 16px;
}
.gar-grid .k { font-size: 6.4pt; text-transform: uppercase; letter-spacing: .1em; color: var(--doc-soft); font-weight: 600; }
.gar-grid .v { font-size: 9.2pt; font-weight: 600; color: var(--doc-ink); margin-top: 2px; }

.acta-decl {
  border: 1px solid var(--doc-rule);
  border-left: 3px solid var(--brand-yellow);
  border-radius: var(--r-xs);
  padding: 10px 14px 12px;
}
.acta-decl .doc-sec__h { border-bottom: none; padding-bottom: 0; margin-bottom: 6px; }

/* ============================================================
   FASE 5 · Cierre de Orden de Trabajo
   ============================================================ */
.ck-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px 20px;
}
.ck-item {
  display: flex; align-items: center; gap: 7px;
  font-size: 8.4pt; color: var(--doc-body); padding: 4px 0;
}
.ck-box {
  width: 13px; height: 13px; border-radius: var(--r-xs); flex-shrink: 0;
  border: 1.3px solid var(--doc-soft);
  display: inline-flex; align-items: center; justify-content: center;
}
.ck-box svg { width: 9px; height: 9px; }
.ck--ok .ck-box {
  background: var(--doc-ink); border-color: var(--doc-ink); color: #fff;
}
.ck--no .ck-box { border-color: var(--warning); }
.ck--no { color: var(--warning); font-weight: 600; }

/* ============================================================
   FASE 6 · Check-list de Instalación (sistema de plantillas)
   ============================================================ */
/* selector de plantilla */
.tpl-grid { display: grid; grid-template-columns: 1fr; gap: 9px; }
.tpl-card {
  display: block; text-align: left; width: 100%; cursor: pointer;
  background: var(--surface-elevated); border: 1.5px solid var(--border-default);
  border-radius: var(--r-md); padding: 13px 15px; position: relative;
  transition: 140ms ease;
}
.tpl-card:hover { border-color: var(--brand-yellow); box-shadow: var(--shadow-hover); }
.tpl-card--prox { opacity: .6; cursor: default; }
.tpl-card--prox:hover { border-color: var(--border-default); box-shadow: none; }
.tpl-soon {
  position: absolute; top: 9px; right: 11px;
  font-family: var(--font-mono); font-size: var(--text-10); font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-disabled); background: var(--cream-100);
  padding: 2px 7px; border-radius: var(--r-pill);
}
.tpl-name { font-size: var(--text-14); font-weight: 700; }
.tpl-ref { font-family: var(--font-mono); font-size: var(--text-10); color: var(--text-secondary); letter-spacing: .04em; margin-top: 2px; }
.tpl-desc { font-size: var(--text-12); color: var(--text-secondary); margin-top: 4px; }

/* meta strip doc */
.ck-meta {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px 16px;
  margin-bottom: 13px; padding-bottom: 11px; border-bottom: 1px solid var(--doc-rule);
}
.ck-meta .k { display: block; font-size: 6.4pt; text-transform: uppercase; letter-spacing: .1em; color: var(--doc-soft); font-weight: 600; }
.ck-meta .v { display: block; font-size: 9pt; font-weight: 600; color: var(--doc-ink); margin-top: 2px; }
.ck-meta .v.up { color: var(--success); }
.ck-meta .v.down { color: var(--error); }

/* tabla de items del checklist */
.ck-tbl { width: 100%; border-collapse: collapse; }
.ck-td { font-size: 8.4pt; padding: 5px 9px; border-bottom: 1px solid var(--doc-rule); vertical-align: top; color: var(--doc-body); }
.ck-td--val { text-align: center; width: 100px; white-space: nowrap; }
.ck-td--ref { text-align: right; width: 70px; font-family: var(--font-mono); font-size: 7.2pt; color: var(--doc-soft); }
.ck-ok { color: var(--success); font-weight: 600; font-size: 7.8pt; display: inline-flex; align-items: center; gap: 4px; }
.ck-ok svg { width: 11px; height: 11px; }
.ck-no { color: var(--error); font-weight: 600; font-size: 7.8pt; }
.ck-val { font-family: var(--font-mono); font-weight: 600; color: var(--doc-ink); }
.ck-empty { color: var(--doc-soft); }
.ck-obs { font-size: 7pt; color: var(--doc-soft); font-style: italic; margin-top: 2px; }

/* observaciones del técnico por sección */
.ck-sec-obs {
  margin-top: 7px; padding: 6px 10px;
  background: var(--cream-50, #FAFAF6);
  border-left: 3px solid var(--brand-yellow);
  border-radius: 0 3px 3px 0;
  font-size: 8pt; color: var(--doc-body); line-height: 1.45;
}
.ck-sec-obs__h {
  font-weight: 700; color: var(--doc-ink); font-size: 7pt;
  text-transform: uppercase; letter-spacing: .06em;
}

/* dictamen final */
.ck-dictamen { border: 1px solid var(--doc-rule); border-radius: var(--r-xs); padding: 10px 14px 12px; }
.ck-dictamen .doc-sec__h { border-bottom: none; padding-bottom: 0; margin-bottom: 8px; }
.ck-dict-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.ck-dict-result {
  font-family: var(--font-display); font-weight: 700;
  font-size: 12pt; color: var(--doc-ink);
}
.ck-dict-result.up { color: var(--success); }
.ck-dict-result.down { color: var(--error); }
.ck-dict-pct { font-size: 8.4pt; color: var(--doc-soft); }
.ck-dict-pct b { font-family: var(--font-display); font-size: 11pt; }
.ck-dict-pct b.up { color: var(--success); }
.ck-dict-pct b.down { color: var(--error); }
.ck-dict-pct span { font-size: 7.4pt; }

/* foto de respaldo por sección (SEC) */
.ck-sec-photo {
  display: flex; align-items: center; gap: 10px;
  margin-top: 7px; padding: 6px 10px;
  background: #FAFAF6;
  border: 1px solid var(--doc-rule);
  border-radius: var(--r-xs);
}
.ck-sec-photo__img {
  width: 44px; height: 32px; flex-shrink: 0;
  border-radius: var(--r-xs); overflow: hidden;
  background: repeating-linear-gradient(135deg, #EDEDEA, #EDEDEA 6px, #E2E2DE 6px, #E2E2DE 12px);
  border: 1px solid var(--doc-rule);
}
.ck-sec-photo__cap {
  font-size: 7.4pt; color: var(--doc-ink); font-weight: 500; line-height: 1.3;
}
.ck-sec-photo--empty .ck-sec-photo__cap { color: var(--doc-soft); font-style: italic; }
.ck-sec-photo--empty { opacity: .5; }

/* form checklist rows */
.ck-form-row { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--border-subtle); }
.ck-form-label { flex: 1; font-size: var(--text-12); color: var(--text-primary); }
.ck-form-input { flex-shrink: 0; }

/* badge kW en documento */
.ck-kw-badge {
  display: inline-block; margin-left: 5px;
  font-family: var(--font-mono); font-size: 5.8pt; font-weight: 600;
  letter-spacing: .04em;
  background: var(--error-soft); color: var(--error);
  padding: 1px 5px; border-radius: var(--r-pill); vertical-align: middle;
}

/* ---------- impresión del documento ---------- */
/* Print nativo: se monta el `.doc` en #print-mount y se oculta el resto de la
   SPA. Usamos `display:none` en los hermanos (no `visibility:hidden`) para que
   el documento fluya en varias páginas sin generar hojas en blanco, y
   #print-mount en flujo normal (no `position:absolute`, que lo recortaría a
   una sola página). */
@media print {
  html, body { background: #fff !important; }

  /* Oculta todo menos el documento a imprimir */
  body > *:not(#print-mount) { display: none !important; }

  #print-mount { display: block !important; position: static !important; margin: 0; padding: 0; }
  #print-mount .doc-wrap { width: auto !important; height: auto !important; overflow: visible !important; transform: none !important; }
  #print-mount .doc { box-shadow: none !important; width: 100% !important; transform: none !important; }
  #print-mount .doc-accent { margin: 0 -14mm; }
  @page { margin: 0; }

  /* thead/tfoot se repiten en cada página */
  .doc thead { display: table-header-group; }
  .doc tfoot { display: table-footer-group; }

  /* evitar cortes dentro de secciones, firmas, fotos */
  .doc-sec { break-inside: avoid; }
  .doc-signs { break-inside: avoid; }
  .doc-foot { break-inside: avoid; }
  .rf-photo { break-inside: avoid; }
  .ck-dictamen { break-inside: avoid; }
  .acta-decl { break-inside: avoid; }

  /* filas de tabla no se partan */
  .doc-meas tr, .ck-tbl tr, .ent-tbl tr { break-inside: avoid; }
}

/* ════════════════════════════════════════════════════════════
   REFINAMIENTO v3 · 2026-08-10 · informes
   Este archivo carga DESPUÉS de components.css y redefine .btn,
   .field y .avatar con valores propios (radio 9px, transición
   `120ms ease`, hover sin gatear). En las pantallas que cargan
   ambos, el botón del sistema quedaba pisado por el del informe.
   Acá se realinean al sistema sin renombrar nada.
   El DOCUMENTO (.doc y todo lo que cuelga de él) no se toca: es
   papel, tiene sus propias reglas y ya está bien resuelto.
   ════════════════════════════════════════════════════════════ */

/* 1 · Botones del estudio = botones del sistema */
.btn {
  font-size: var(--text-15);
  border-radius: var(--r-md);
  padding: 10px 16px;
  line-height: 1;
  transition:
    transform var(--duration-instant) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out);
}
.btn:active { transform: scale(0.97); }
.btn--primary {
  background: linear-gradient(180deg, var(--brand-yellow-300), var(--brand-yellow) 52%, var(--brand-yellow-dark));
  box-shadow: var(--shadow-yellow), inset 0 1px 0 rgba(255,255,255,.60), inset 0 -1px 0 rgba(120,95,0,.16);
}
.btn--primary:active { box-shadow: 0 1px 2px rgba(230,187,0,.28), inset 0 1px 3px rgba(120,95,0,.26); }
.btn--ghost { border-width: 1.5px; }
/* Hover sólo con mouse real: en iPhone se quedaba pegado tras el tap */
.btn--primary:hover, .btn--ghost:hover { background: unset; }
@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover { background: linear-gradient(180deg, var(--brand-yellow), var(--brand-yellow-dark) 60%, #D9AF00); }
  .btn--ghost:hover   { background: var(--cream-100); color: var(--text-primary); border-color: var(--border-default); }
}
.btn:disabled, .btn[disabled], .btn[aria-disabled="true"] {
  background: var(--surface-sunken); color: var(--text-tertiary);
  border-color: var(--border-subtle); box-shadow: none;
  cursor: not-allowed; transform: none;
}

/* 2 · Botones de icono · objetivo táctil y hover gateado */
.icon-btn {
  border-width: 1px;
  border-radius: var(--r-md);
  box-shadow: 0 1px 2px rgba(26,26,24,.05), inset 0 1px 0 rgba(255,255,255,.7);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    transform var(--duration-instant) var(--ease-out);
}
.icon-btn:active { transform: scale(0.94); }
.icon-btn:hover, .icon-btn--danger:hover { border-color: var(--border-default); }
@media (hover: hover) and (pointer: fine) {
  .icon-btn:hover { border-color: var(--brand-yellow-dark); color: var(--text-primary); }
  .icon-btn--danger:hover { border-color: var(--error); color: var(--error); }
}
@media (pointer: coarse) { .icon-btn { width: var(--touch-min); height: var(--touch-min); } }

/* 3 · Campos del estudio con el foco del sistema */
.inp, .ta, .sel {
  border-width: 1px;
  border-radius: var(--r-md);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out);
}
.inp:focus, .ta:focus, .sel:focus {
  border-color: var(--brand-yellow-dark);
  box-shadow: 0 0 0 3px rgba(255,207,0,.18);
}
.inp:focus-visible, .ta:focus-visible, .sel:focus-visible {
  box-shadow: 0 0 0 3px rgba(255,207,0,.18), var(--focus-ring);
}
.inp::placeholder, .ta::placeholder { color: var(--text-tertiary); }
.field__hint { color: var(--text-secondary); }
.meas-tbl th { color: var(--text-secondary); }
.add-row { border-width: 1px; border-radius: var(--r-md); min-height: 38px; }
@media (hover: hover) and (pointer: fine) { .add-row:hover { border-color: var(--brand-yellow-dark); background: var(--brand-yellow-soft); } }

/* 4 · Tarjetas de informe y de plantilla */
.rep-card, .tpl-card, .hito-card {
  border-width: 1px;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out);
}
.rep-card:hover, .tpl-card:hover { transform: none; box-shadow: var(--shadow-card); border-color: var(--border-default); }
@media (hover: hover) and (pointer: fine) {
  .rep-card:hover, .tpl-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-hover);
    border-color: var(--brand-yellow-dark);
  }
}
.rep-card:active, .tpl-card:active { transform: scale(0.99); }
.rep-card__ic { border-radius: var(--r-md); box-shadow: inset 0 .5px 0 rgba(255,255,255,.5); }
.rep-card__phase, .rep-card__soon, .tpl-soon, .tpl-ref { color: var(--text-tertiary); }
.rep-card__desc, .tpl-desc { color: var(--text-secondary); }
.recent { border-radius: var(--r-lg); }
.recent__row .dt, .recent__row .dl { color: var(--text-secondary); }

/* 5 · Estudio · el overlay sí tiene contenido detrás: vidrio real */
.studio-overlay {
  background: rgba(20,20,15,.48);
  -webkit-backdrop-filter: blur(8px) saturate(120%);
  backdrop-filter: blur(8px) saturate(120%);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .studio-overlay { background: rgba(20,20,15,.62); }
}
.studio { border-radius: var(--r-xl); border: 1px solid var(--border-subtle); }
.studio__head, .studio__foot {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--glass-line);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .studio__head, .studio__foot { background: var(--surface-elevated); }
}
.studio__ic { border-radius: var(--r-md); }
.studio__titles p { color: var(--text-secondary); }
.form-pane { background: transparent; }
.preview-pane { background: #4B4B46; }
.preview-tag { color: #CFCFC7; }
.doc-wrap { border-radius: var(--r-xs); }

/* 6 · Selector de fotos · el aspecto "apagado" pasa de opacidad a
   saturación: la miniatura sigue legible mientras se elige. */
.photo-slot:not(.sel) { opacity: 1; filter: saturate(.25) brightness(1.04); }
.photo-slot { transition: filter var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out), transform var(--duration-instant) var(--ease-out); }
.photo-slot:active { transform: scale(0.97); }
.photo-slot.sel { filter: none; border-width: 2px; }
.sub-check { border-radius: var(--r-sm); transition: background-color var(--duration-fast) var(--ease-out), transform var(--duration-instant) var(--ease-out); }
.sub-check:active { transform: scale(0.92); }

/* 7 · El DOCUMENTO es papel: siempre blanco, siempre tinta oscura.
   Los semánticos claros del modo oscuro (#5FCB8E y compañía) darían
   1.9:1 sobre la hoja. Se re-anclan a los valores de impresión dentro
   del alcance de .doc, en los dos temas. */
.doc, .doc-wrap, #print-mount {
  --success: #276749;
  --warning: #9D470A;
  --error:   #A52424;
  --info:    #1565C0;
  --text-secondary: var(--doc-soft);
  --text-tertiary:  var(--doc-soft);
  --cream-100: #ECEAE5;
  --cream-50:  #FAFAF6;
}

/* 8 · Píldoras y estados del estudio */
.tag-pill { font-variant-numeric: tabular-nums; }
.subtabs { margin-bottom: 4px; }
.tabrule { border-color: var(--border-subtle); }

/* ── v61 · El estudio en el iPhone/iPad instalado ─────────────────────
   La app dibuja bajo la barra de estado (black-translucent). Con 26 px de
   margen fijo, el encabezado del estudio —con la X— quedaba debajo de la
   hora y la batería. En el teléfono, además, esos márgenes le robaban
   ancho a un formulario que se llena con el dedo: ahí va a pantalla
   completa. */
.studio-overlay {
  padding: calc(26px + var(--safe-top)) calc(26px + var(--safe-right))
           calc(26px + var(--safe-bottom)) calc(26px + var(--safe-left));
}
@media (max-width: 767px) {
  .studio-overlay { padding: 0; }
  .studio {
    width: 100%; height: 100%;
    border-radius: 0; border: none;
    padding-top: var(--safe-top);
    padding-bottom: var(--safe-bottom);
  }
  .studio__head { padding: 10px 12px; gap: 10px; }
  .studio__ic { display: none; }
  .studio__titles h2 { font-size: var(--text-16); line-height: 1.2; }
}

/* ═══ css/responsive.css ═══ */
/* ═══════════════════════════════════════════════════════════
   responsive.css · Capa iPad / mobile / tablet
   Carga DESPUÉS de tokens, reset, components, layout
   ═══════════════════════════════════════════════════════════ */

/* ───────────────────────────────────────────────────────────
   1 · Neutralizar :hover en touch devices
   En iPad/iOS los :hover quedan "pegados" tras un tap. Los
   envolvemos en @media (hover:none) para que en touch usen
   :active en su lugar.
   ─────────────────────────────────────────────────────────── */
@media (hover: none) {
  /* Anula efectos hover problemáticos · usa :active en su lugar */
  button:hover,
  a:hover,
  .btn:hover,
  .icon-btn:hover,
  .icon-btn-mini:hover,
  .rep-card:hover,
  .card:hover,
  .tpl-card:hover {
    transform: none !important;
    box-shadow: inherit !important;
  }
}

/* ───────────────────────────────────────────────────────────
   2 · Safe-area iOS (notch iPad Pro M)
   ─────────────────────────────────────────────────────────── */
body {
  padding-top:    var(--safe-top);
  padding-bottom: var(--safe-bottom);
  padding-left:   var(--safe-left);
  padding-right:  var(--safe-right);
}
/* v61 · Hasta 1024 px la cabecera del teléfono/iPad (`.m-header`) ya deja
   libre la franja de la barra de estado. Sumarla también acá la duplicaba:
   ~118 px vacíos arriba en el iPhone antes de los íconos. */
@media (max-width: 1024px) {
  body { padding-top: 0; }
}
/* v61 · iPad horizontal (app instalada): la barra superior es pegajosa y, al
   bajar, se iba a `top: 0` — debajo de la hora y la batería. Se pega justo
   bajo la barra de estado (en el computador la medida es 0). */
@media (min-width: 1025px) {
  .app-topbar { top: var(--safe-top); }
}

/* ───────────────────────────────────────────────────────────
   3 · Tablets (iPad Pro 11 portrait, iPad estándar)
   641-1024 px de ancho
   - Sidebar pasa a drawer overlay
   - Bottom nav oculto (no se ve en tablet)
   - Grid 2 columnas para cards
   - Modales más anchos
   ─────────────────────────────────────────────────────────── */
@media (min-width: 641px) and (max-width: 1024px) {
  /* Fase 4 · El drawer de menú de acá NUNCA existió: apuntaba a
     `.sidebar` / `.nav-side` / `[data-sidebar]` / `.sidebar-backdrop`, y el
     marcado real usa `.app-sidebar` + `#sidebar-overlay` con la clase `open`
     (ver layout.css). Eran ~60 líneas muertas — y el `z-index: 1500` que
     fijaban era justo lo que el sistema prohíbe: por encima del piso de los
     modales. Se borran; el menú real vive en `--z-sidebar` (100), debajo de
     cualquier modal, que es donde corresponde.
     Hallazgo 6 de docs/design/AUDITORIA-REDISENO-v3.md. */

  /* App-content ocupa todo el ancho cuando sidebar es drawer */
  .app-content,
  [data-app-content] {
    margin-left: 0 !important;
    padding-left: 16px;
    padding-right: 16px;
  }

  /* Bottom nav oculto en tablet (vuelve hamburguesa en topbar) */
  .bottom-nav,
  [data-bottom-nav] {
    display: none !important;
  }

  /* Topbar visible con hamburguesa */
  .topbar,
  [data-topbar] {
    display: flex !important;
  }
  .topbar-hamburger {
    display: inline-flex !important;
  }

  /* Grid responsive · dashboard, listas */
  .grid--2 { grid-template-columns: repeat(2, 1fr) !important; }
  .grid--3 { grid-template-columns: repeat(2, 1fr) !important; }
  .grid--4 { grid-template-columns: repeat(2, 1fr) !important; }

  /* Drawer Sei.ui.modals · full-screen en tablet portrait */
  .sei-drawer {
    width: 100vw !important;
    max-width: 100vw !important;
    border-radius: 0 !important;
  }

  /* formModal · más ancho */
  .sei-modal {
    max-width: 92vw !important;
    width: 92vw !important;
  }
}

/* ───────────────────────────────────────────────────────────
   4 · Mobile / phones (≤640px)
   - Sidebar drawer
   - Bottom nav visible
   - Grid 1 columna
   - Modales full-screen
   ─────────────────────────────────────────────────────────── */
@media (max-width: 640px) {
  /* (mismo caso: el drawer de `.sidebar` no existe — ver arriba) */

  /* Grid colapsa a 1 columna */
  .grid--2, .grid--3, .grid--4 { grid-template-columns: 1fr !important; }

  /* Modales full-screen.
     v61 · En la app instalada del iPhone la pantalla llega hasta arriba de
     todo (barra de estado translúcida): el modal deja libre la franja de la
     hora/batería y la de la raya de inicio, o la X queda debajo de la batería
     y no se puede tocar. El overlay no suma su propio margen acá. */
  .sei-modal-overlay,
  .modal-overlay {
    padding: 0 !important;
  }
  .sei-modal,
  .modal {
    max-width: 100vw !important;
    width: 100vw !important;
    max-height: 100dvh !important;
    height: 100dvh !important;
    border-radius: 0 !important;
    box-sizing: border-box;
    padding-top: var(--safe-top) !important;
    padding-bottom: var(--safe-bottom) !important;
    padding-left: var(--safe-left);
    padding-right: var(--safe-right);
  }
  /* v61 · Una confirmación de dos líneas («¿Armo el borrador?») a pantalla
     completa dejaba los botones abajo de todo, lejos del texto. Las chicas
     suben desde abajo, a la altura del pulgar. */
  .sei-modal.sei-modal--sm,
  .sei-modal.sei-modal--xs {
    height: auto !important;
    max-height: calc(100dvh - var(--safe-top) - 8px) !important;
    padding-top: 0 !important;
    border-radius: var(--r-lg) var(--r-lg) 0 0 !important;
  }

  /* Drawer ocupa todo */
  .sei-drawer {
    width: 100vw !important;
    max-width: 100vw !important;
    border-radius: 0 !important;
  }

  /* Page-header más compacto */
  .page-header {
    flex-direction: column !important;
    align-items: stretch !important;
    gap: 8px !important;
  }
  .page-header__actions {
    width: 100%;
    flex-wrap: wrap;
  }
  /* Antes: flex:1 en cada botón → con 3-4 acciones se aplastaban al 25% y el
     texto se rompía/encimaba. Ahora van a 2 por fila, con el texto entero. */
  .page-header__actions .btn {
    flex: 1 1 calc(50% - var(--space-2));
    white-space: nowrap;
  }
}

/* ───────────────────────────────────────────────────────────
   5 · Scroll horizontal con indicador para tablas anchas
   Usar clase .table-scroll en contenedor padre de table.
   ─────────────────────────────────────────────────────────── */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  position: relative;
}
.table-scroll table {
  min-width: 600px;
}
@media (max-width: 1024px) {
  /* Sombra a la derecha si hay más contenido oculto */
  .table-scroll::after {
    content: '';
    position: sticky;
    right: 0;
    top: 0;
    bottom: 0;
    width: 24px;
    background: linear-gradient(to right, transparent, var(--cream-base) 80%);
    pointer-events: none;
    margin-left: -24px;
    display: block;
  }
}

/* ───────────────────────────────────────────────────────────
   6 · Inputs número/fecha · botones controles más grandes
   ─────────────────────────────────────────────────────────── */
@media (max-width: 1024px) {
  input[type="date"]::-webkit-calendar-picker-indicator,
  input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    padding: 8px;
  }
}

/* ───────────────────────────────────────────────────────────
   7 · PWA standalone (modo app instalada en iPad)
   ─────────────────────────────────────────────────────────── */
@media all and (display-mode: standalone) {
  /* Sin gap arriba para notch */
  body {
    /* safe-area ya se aplica desde regla 2 */
  }
}

/* ───────────────────────────────────────────────────────────
   8 · Tablas anchas en tablet/mobile · scroll horizontal automático
   Cualquier <table> dentro de .card adquiere scroll horizontal
   en lugar de desbordar el contenedor.
   ─────────────────────────────────────────────────────────── */
@media (max-width: 1024px) {
  /* Fase 4 · Tanda A: la tabla que ya vive dentro de `.table-wrap` queda
     fuera de esta regla. El wrapper scrollea mejor (la tabla conserva
     `display: table`, así que las columnas siguen alineadas) y encima el
     `overflow` de la propia tabla mataba la cabecera pegajosa. */
  .card > table:not(.table) {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
  /* Mejor: envolver en wrapper para sombra indicadora ".table-scroll" */
  .table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* ───────────────────────────────────────────────────────────
   9 · iPad Pro 11 landscape (>1024) · sidebar fija pero
   margen del contenido un poco menor para no apretar
   ─────────────────────────────────────────────────────────── */
@media (min-width: 1025px) and (max-width: 1280px) {
  .app-content {
    padding-left: 18px;
    padding-right: 18px;
  }
}

/* ───────────────────────────────────────────────────────────
   10 · Listas tipo .vis-row, .recent__row, etc. · padding
   más generoso para touch en tablet/mobile
   ─────────────────────────────────────────────────────────── */
@media (max-width: 1024px) {
  .vis-row,
  .recent__row,
  .sub-row,
  .ck-form-row {
    min-height: 56px;
  }
}

/* ───────────────────────────────────────────────────────────
   11 · Chips/tags más cómodos para touch
   ─────────────────────────────────────────────────────────── */
@media (max-width: 1024px) {
  .chip,
  .tag-pill {
    min-height: 36px;
    padding: 8px 14px !important;
  }
}

/* ───────────────────────────────────────────────────────────
   12 · Form fields espaciados para touch
   ─────────────────────────────────────────────────────────── */
@media (max-width: 1024px) {
  .field,
  .sei-field {
    margin-bottom: 14px;
  }
  .field input,
  .field select,
  .field textarea,
  .sei-field input,
  .sei-field select,
  .sei-field textarea,
  .inp,
  .input,
  .ta,
  .sel {
    min-height: 44px;
    padding: 11px 13px !important;
  }
  .ta,
  textarea.ta,
  textarea.input {
    min-height: 80px !important;
  }
}

/* ───────────────────────────────────────────────────────────
   13 · Page header en tablet · stack si hay muchas acciones
   ─────────────────────────────────────────────────────────── */
@media (min-width: 641px) and (max-width: 1024px) {
  .page-header__actions {
    flex-wrap: wrap;
  }
}

/* ── TERRENO · 15-sep-2026 · menos cabecera antes del contenido ──
   Medido a 412 px en la lista de OT: barra superior «Órdenes de trabajo»,
   debajo «Operaciones · lo de hoy», debajo las pestañas, debajo OTRA VEZ
   «Órdenes de trabajo», y recién a los 590 px la primera OT. Tres títulos
   para llegar a lo que se vino a ver. En el teléfono la barra superior ya
   dice dónde estás: el título grande del área y el de la página sobran; el
   subtítulo del área (« 0 abiertas · ninguna atrasada ») se queda porque es
   información, no decoración. Y «Exportar» no tiene sentido con el dedo. */
@media (max-width: 767px) {
  .area-frame .home-hello { display: none; }
  .area-frame .page-header__title { display: none; }
  #ot-btn-export { display: none; }
}

/* ═══ css/pulido.css ═══ */
/* ============================================================
   PULIDO · 15-sep-2026 · «que no sea tosca»
   Bloque aditivo y va ÚLTIMO en la cascada (assets.php). No cambia la
   forma ni la paleta: le baja el volumen a lo que ya hay. Tres ideas:

     1. El color MARCA, no pinta. La cifra va en tinta; el estado va en
        un punto, un delta o una línea de 2px arriba. Los velos de color
        que teñían tarjetas enteras quedan a la mitad (o menos).
     2. Menos capas al reposo. Sombra de descanso más leve, y la fuerte
        sólo cuando el mouse está encima. Un placeholder es crema liso,
        no rayas.
     3. Movimiento con sentido. Lo que aparece entra (fade + 6px, en
        cascada de 40ms); lo que se toca responde. Nada dura más de
        360ms. `prefers-reduced-motion` ya lo apaga desde tokens.css.

   Medido en producción el 15-sep-2026 (1920×1080): las cuatro cifras de
   Finanzas iban una verde, dos rojas y una naranja sobre tarjetas
   teñidas; Proyectos tenía 16 rectángulos rayados de «sin foto»; nada
   entraba, todo aparecía de golpe.
   ============================================================ */

/* ── 1 · Sombras de descanso · más aire, menos peso ───────── */
:root {
  --shadow-card:
    0 1px 1px rgba(26, 26, 24, 0.03),
    0 3px 10px rgba(26, 26, 24, 0.045),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.6);
  --shadow-hover:
    0 2px 4px rgba(26, 26, 24, 0.05),
    0 10px 28px rgba(26, 26, 24, 0.09),
    inset 0 0.5px 0 rgba(255, 255, 255, 0.7);
}

/* ── 2 · El color marca, no pinta ─────────────────────────── */
/* Velo diagonal de los KPI con estado: a la mitad. El punto de color
   de la etiqueta ya dice lo que hay que decir. */
.kpi--success::after, .kpi--warning::after,
.kpi--error::after,   .kpi--info::after { opacity: .45; }

/* Teñidos con línea arriba (tiles, hero de Finanzas, áreas en alerta):
   la línea baja a 2px y el gradiente se apaga antes de la mitad. */
.tile--brand, .tile--ok, .tile--warn, .tile--alert, .tile--info,
.fin-hero, .kpi--accent, .area--alert, .area--warn, .card--accent {
  border-top-width: 2px;
}
.tile--brand { background: linear-gradient(155deg, var(--brand-yellow-soft), var(--surface-elevated) 40%); }
.tile--ok    { background: linear-gradient(155deg, var(--success-soft),      var(--surface-elevated) 40%); }
.tile--warn  { background: linear-gradient(155deg, var(--warning-soft),      var(--surface-elevated) 40%); }
.tile--alert { background: linear-gradient(155deg, var(--error-soft),        var(--surface-elevated) 40%); }
.tile--info  { background: linear-gradient(155deg, var(--info-soft),         var(--surface-elevated) 40%); }
.fin-hero    { background: linear-gradient(160deg, var(--brand-yellow-soft), var(--surface-elevated) 42%); }
.kpi--accent { background: linear-gradient(158deg, var(--brand-yellow-soft), var(--surface-elevated) 40%); }
.area--alert { background: linear-gradient(158deg, var(--error-soft),   var(--surface-elevated) 36%); }
.area--warn  { background: linear-gradient(158deg, var(--warning-soft), var(--surface-elevated) 36%); }

/* Los rellenos bajo las sparklines: una insinuación, no una mancha.
   v38 · El relleno ya viene con su propio degradado desde `Sei.ui.spark`,
   así que la opacidad pareja de antes sobra. Y el dibujo pasó de
   `polygon`+`polyline` a un `path` con curva, por lo que los dos
   selectores viejos ya no apuntaban a nada. */
.spark path[stroke] { stroke-width: 1.6; }

/* Placeholder «sin foto» de las obras: crema liso con un icono tenue.
   (proyectos.js dejó de pintar las rayas; esto es lo que queda.) */
/* 18-sep-2026 · el icono era `lu-image`, o sea el dibujito universal de
   "la imagen no cargo". Una obra sin foto no es un error: es una obra a la
   que todavia nadie le saco una foto. Con el casco y una trama suave se
   lee como un hueco a proposito, que es lo que es. */
.proy-foto--vacia {
  background:
    repeating-linear-gradient(135deg,
      transparent, transparent 9px,
      color-mix(in srgb, var(--text-tertiary) 8%, transparent) 9px,
      color-mix(in srgb, var(--text-tertiary) 8%, transparent) 10px),
    var(--cream-100);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-tertiary);
}
.proy-foto--vacia svg { width: var(--ico-lg); height: var(--ico-lg); opacity: .6; stroke: currentColor; fill: none; stroke-width: 1.6; }

/* Título de vista DENTRO de un área: el área ya puso el título grande
   («Comercial»); el de la vista («Presupuestos») es un subtítulo, no un
   segundo titular compitiendo a 28px. */
.area-frame .page-header__title { font-size: var(--text-20); letter-spacing: -0.02em; }
.area-frame .page-header { margin-top: 2px; }

/* ── 3 · Movimiento con sentido ───────────────────────────── */
@keyframes seiIn {
  from { opacity: 0; transform: translateY(6px); }
}

/* Lo que monta una vista entra en cascada: primero el encabezado, después
   cada bloque, 40ms entre uno y otro. Se aplica sobre los hijos directos
   del contenedor de la vista, que es lo que cada `innerHTML` reemplaza. */
#app-content > .vista-marco { animation: none; }
#app-content > *, #area-body > *, .vista-marco > * {
  animation: seiIn .34s var(--ease-out) both;
}
#area-body > *:nth-child(2), #app-content > *:nth-child(2), .vista-marco > *:nth-child(2) { animation-delay: 40ms; }
#area-body > *:nth-child(3), #app-content > *:nth-child(3), .vista-marco > *:nth-child(3) { animation-delay: 80ms; }
#area-body > *:nth-child(4), #app-content > *:nth-child(4), .vista-marco > *:nth-child(4) { animation-delay: 120ms; }
#area-body > *:nth-child(n+5), #app-content > *:nth-child(n+5), .vista-marco > *:nth-child(n+5) { animation-delay: 150ms; }

/* Tarjetas y KPI: cada una entra por su cuenta, en cascada corta. */
.kpi, .tile, .kpiv, .card {
  animation: seiIn .36s var(--ease-out) both;
}
:is(.kpi, .tile, .kpiv, .card):nth-child(2) { animation-delay: 40ms; }
:is(.kpi, .tile, .kpiv, .card):nth-child(3) { animation-delay: 80ms; }
:is(.kpi, .tile, .kpiv, .card):nth-child(4) { animation-delay: 120ms; }
:is(.kpi, .tile, .kpiv, .card):nth-child(5) { animation-delay: 160ms; }
:is(.kpi, .tile, .kpiv, .card):nth-child(n+6) { animation-delay: 190ms; }
/* Dentro de un modal o un drawer la tarjeta no entra: ya entró el modal. */
:is(.modal, .drawer, .modal-overlay) .card { animation: none; }

/* Botones: el secundario y el fantasma también se levantan al pasar el
   mouse (sólo el primario lo hacía) y todos bajan al presionar. */
@media (hover: hover) and (pointer: fine) {
  .btn--secondary:hover, .btn--ghost:hover { transform: translateY(-1px); }
  .btn--secondary:hover { box-shadow: var(--shadow-sm); }
}

/* Filas y pestañas: el cambio de fondo se desliza, no salta. */
tbody tr, .pressable-row, .badge, .nav-item, .sidebar-nav a, .bottom-nav a {
  transition:
    background-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out);
}

/* Iconos de acción en filas de tabla: aparecen al pasar por la fila. */
@media (hover: hover) and (pointer: fine) {
  tbody tr .btn--ghost.btn--sm { opacity: .6; transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-instant) var(--ease-out), background-color var(--duration-fast) var(--ease-out); }
  tbody tr:hover .btn--ghost.btn--sm, tbody tr .btn--ghost.btn--sm:focus-visible { opacity: 1; }
}

/* Inputs: el foco crece un pelo, no sólo cambia el borde. */
.input, .field input, .field select, .field textarea, input.input, select.input, textarea.input {
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}

/* Barras de avance: el relleno crece al montarse. */
.progress-bar__fill { transform-origin: left; animation: seiGrowX .7s var(--ease-out) both; animation-delay: .15s; }
@keyframes seiGrowX { from { transform: scaleX(0); } }

@media (prefers-reduced-motion: reduce) {
  #app-content > *, #area-body > *, .vista-marco > *, .kpi, .tile, .kpiv, .card, .progress-bar__fill {
    animation: none !important; transform: none !important;
  }
}

/* ============================================================
   v38 · 18-sep-2026
   Lo que cierra la tanda de fundamentos: el salto al contenido, el
   tamaño mínimo de los campos en el teléfono, la grilla óptica de los
   iconos y la transición entre pantallas.
   ============================================================ */

/* ---------- Salto al contenido ----------
   Con teclado había que pasar por las nueve áreas y las pestañas antes
   de llegar a la tabla. Es el primer nodo enfocable del documento y
   sólo se ve cuando tiene el foco. */
.salto-contenido {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: var(--z-salto);
  background: var(--brand-navy);
  color: var(--brand-yellow);
  font-family: var(--font-display);
  font-size: var(--text-13);
  font-weight: 700;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--r-pill);
  text-decoration: none;
  box-shadow: var(--shadow-overlay);
  transform: translateY(-200%);
  transition: transform var(--duration-fast) var(--ease-out);
}
.salto-contenido:focus-visible,
.salto-contenido:focus { transform: translateY(0); outline: none; }

/* ---------- Campos en el teléfono ----------
   Safari en iPhone hace zoom al tocar un campo cuya letra mide menos de
   16 px, y después la página queda corrida. Eran cuatro campos; en vez
   de perseguirlos uno por uno, el piso se pone acá para todo puntero
   grueso. `max()` para no achicar los que ya son más grandes. */
@media (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
  select,
  textarea {
    font-size: max(16px, 1em);
  }
}

/* ---------- Iconos en la grilla óptica ----------
   Había nueve tamaños distintos y varios caían entre líneas de píxel
   (10, 11, 13, 15, 17, 22, 34, 36), que es lo que los hacía verse
   borrosos. Tres tamaños, todos múltiplos de 4. */
.ico-sm, .ico-sm svg { width: var(--ico-sm); height: var(--ico-sm); }
.ico-md, .ico-md svg { width: var(--ico-md); height: var(--ico-md); }
.ico-lg, .ico-lg svg { width: var(--ico-lg); height: var(--ico-lg); }

/* ---------- Transición entre pantallas ----------
   Cambiar de vista parpadeaba: el contenido viejo desaparecía de golpe
   y el nuevo aparecía de golpe. Con esto se cruza en 180 ms. Donde el
   navegador no lo soporta (Firefox y Safari viejos) no pasa nada —
   queda el corte de siempre, que es lo que hay hoy. */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) {
    animation: sei-vt-sale 140ms var(--ease-out) both;
  }
  ::view-transition-new(root) {
    animation: sei-vt-entra 180ms var(--ease-out) both;
  }
  @keyframes sei-vt-sale  { to   { opacity: 0; } }
  @keyframes sei-vt-entra { from { opacity: 0; transform: translateY(6px); } }
}

/* ---------- Tendencia en las tarjetas de Inicio ----------
   La cifra sola dice cuánto; no dice si viene subiendo o cayendo. La
   línea va entre la cifra y su contexto, pegada a la cifra, para que se
   lean como una sola cosa. */
.area__viz { display: block; margin: 2px 0 8px; }
.area__viz .spark { height: 34px; margin-top: 0; }
.area--focus .area__viz .spark { height: 42px; }
/* El anillo de avance reemplaza a la cifra cuando lo que importa es la
   proporción y no el número suelto. */
.area__figure--anillo { display: flex; align-items: center; gap: var(--space-3); }
.area__figure--anillo .area__figure-txt { display: flex; flex-direction: column; line-height: 1.1; }

/* ---------- El estado vacío que ya existía ----------
   `.empty` se usa en 51 lugares y no se toca su marcado: lo que cambia
   es cómo se ve. Antes era un icono de línea de 24 px dentro de un
   cuadrado gris de 48 — el mismo en Compras, en Leads y en Personas, y
   lo bastante chico como para leerse igual que un error.
   Acá gana aire, el icono crece a la grilla óptica y el cuadrado se
   vuelve un disco de marca. Las vistas que se vayan migrando a
   `Sei.ui.estado.vacio()` cambian además el icono por una ilustración. */
.empty { padding: var(--space-10) var(--space-6); gap: var(--space-3); }
.empty__icon {
  width: 64px; height: 64px;
  border-radius: var(--r-pill);
  background: var(--brand-yellow-soft);
  color: var(--brand-ink);
  box-shadow: inset 0 0 0 1px var(--brand-yellow-100);
}
.empty__icon svg { width: var(--ico-lg); height: var(--ico-lg); stroke-width: 1.7; }
.empty__desc { max-width: 44ch; line-height: 1.55; }
/* Si la vista pone un botón adentro del vacío, que respire. */
.empty .btn { margin-top: var(--space-2); }

/* ══════════════════════════════════════════════════════════
   v43 · la pista de la salud del cliente
   Sale en el rail del asistente de cotización, y SÓLO cuando hay algo que
   hacer distinto (pedirle anticipo). Un cartel que aparece cada vez que se
   elige un cliente se vuelve parte del fondo en una semana.
   ══════════════════════════════════════════════════════════ */
.salud-pista {
  display: flex; gap: 9px; align-items: flex-start;
  margin-bottom: 12px; padding: 11px 13px;
  border-radius: var(--r-md);
  font-size: var(--text-12); line-height: 1.5;
}
.salud-pista .lu { width: 16px; height: 16px; flex: none; margin-top: 1px; }
.salud-pista--ojo {
  background: var(--warning-soft);
  border: 1px solid color-mix(in srgb, var(--warning) 32%, var(--warning-soft));
  color: var(--warning);
}
.salud-pista--ojo b { color: var(--warning); }

/* ═══ css/maqueta.css ═══ */
/* ============================================================
   MAQUETA · la hoja de la propuesta, puesta encima de la app
   v53 · 20-sep-2026

   Es `docs/design/ds/propuesta/05-pantallas.html` copiada casi tal
   cual, con los tokens de la app en vez de los de la ficha. Va después
   de todo (menos modo obra) a propósito: lo que la propuesta define,
   manda; lo que no define, queda como estaba.

   Hasta la v52 la app tenía la paleta, los radios y las sombras de la
   propuesta, pero cada pantalla se componía a su manera. Por eso «no se
   parecía»: el color era el mismo y la forma no. Acá están las formas:
   la tarjeta de cifra, la tarjeta con cabecera, la fila, el chip, el
   distintivo con forma, la tabla, el ranking, la apilada, la carpeta y
   los cinco gráficos.
   ============================================================ */

/* ---------- escala de la ficha ---------- */
:root {
  --mq-e1: 8px; --mq-e2: 12px; --mq-e3: 16px; --mq-e4: 24px;
  --mq-ease: var(--ease-out);
  --mq-dur-1: var(--duration-fast);
  --mq-dur-2: var(--duration-base);
  --mq-dur-3: var(--duration-slow);
}

/* ============================================================
   1 · SHELL · lo que la ficha dibuja alrededor de cada pantalla
   ============================================================ */
/* El botón de crear es amarillo con degradado y brillo interior, no
   negro: en la propuesta es lo único que grita en la barra. */
.topbar-nuevo {
  background: linear-gradient(180deg, var(--brand-yellow-300), var(--brand-yellow) 58%, #E6BB00);
  color: var(--brand-navy);
  font-weight: 700;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6);
  transition: box-shadow var(--mq-dur-2) var(--mq-ease), transform var(--mq-dur-1) var(--mq-ease);
}
@media (hover: hover) and (pointer: fine) {
  .topbar-nuevo:hover { background: linear-gradient(180deg, var(--brand-yellow-300), var(--brand-yellow) 58%, #E6BB00); box-shadow: var(--shadow-yellow); }
}
.topbar-nuevo:active { transform: translateY(1px) scale(.985); }

/* El avatar: tinta sobre amarillo en la ficha (navy con iniciales oro). */
.topbar-avatar {
  background: var(--brand-navy);
  color: var(--brand-yellow);
  width: 26px; height: 26px;
  font-size: var(--text-10);
}

/* El ítem activo del menú, con el mismo velo que el riel de la ficha. */
.sidebar-item--active { background: rgba(255, 207, 0, .16); }

/* El buscador es una pastilla hundida con el atajo en mono a la derecha. */
.topbar-search {
  background: var(--cream-100);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  transition: border-color var(--mq-dur-2) var(--mq-ease), background var(--mq-dur-2) var(--mq-ease);
}
.topbar-search:hover { background: var(--cream-50); border-color: var(--border-default); }

/* Las pestañas de área: texto con subrayado amarillo, no pastilla. */
.area-tabs {
  background: transparent !important;
  border: 0 !important;
  border-bottom: 1px solid var(--border-subtle) !important;
  border-radius: 0 !important;
  padding: 0 !important;
  gap: 2px !important;
  box-shadow: none !important;
  margin-bottom: var(--mq-e3);
}
.area-tabs button {
  border-radius: 0 !important;
  background: transparent !important;
  box-shadow: none !important;
  padding: 9px 11px !important;
  font-size: var(--text-12);
  color: var(--text-secondary);
  border-bottom: 2px solid transparent !important;
  margin-bottom: -1px;
  min-height: 0;
  transition: color var(--mq-dur-2) var(--mq-ease), border-color var(--mq-dur-2) var(--mq-ease);
}
.area-tabs button[aria-pressed="true"] {
  color: var(--text-primary) !important;
  border-bottom-color: var(--brand-yellow) !important;
  transform: none !important;
}
.area-tabs button:hover { color: var(--text-primary); }

/* La cabecera de página: título 19/800, meta al lado, acciones a la
   derecha con la última en amarillo. */
.home-head { align-items: flex-end; gap: var(--mq-e2); flex-wrap: wrap; }
.home-hello { font-size: var(--text-20); font-weight: 800; letter-spacing: -.01em; }
.home-date { font-size: var(--text-12); color: var(--text-secondary); }
.home-head .btn--ghost { background: var(--brand-yellow); color: var(--brand-navy); font-weight: 700; border-color: var(--brand-yellow); }

/* ============================================================
   2 · TARJETA DE CIFRA (.kpi)
   Rótulo en mayúsculas chicas, cifra 24/800, delta en mono con la tinta
   de la serie, pie gris, y abajo una tendencia o una barra con
   degradado. Sin tintes de estado: el estado lo dice el texto.
   ============================================================ */
.kpis {
  display: grid;
  /* 172 y no 158 como la ficha: «$119.678.000» a 24 px mide 156 y en
     la ficha nunca hubo una cifra de doce caracteres. */
  grid-template-columns: repeat(auto-fit, minmax(172px, 1fr));
  gap: var(--mq-e2);
  margin-bottom: var(--mq-e3);
}
.kpi, .hk {
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  padding: var(--mq-e2) var(--mq-e3);
  display: flex; flex-direction: column; gap: 3px;
  position: relative; overflow: hidden;
  box-shadow: none;
  text-decoration: none; color: inherit;
  transition: box-shadow var(--mq-dur-2) var(--mq-ease), border-color var(--mq-dur-2) var(--mq-ease);
}
a.kpi:hover, .hk:hover { box-shadow: var(--shadow-card); border-color: var(--border-default); transform: none; }
.kpi--success, .kpi--warning, .kpi--error, .kpi--info, .kpi--accent {
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-top-width: 1px;
  box-shadow: none;
}
.kpi--success::after, .kpi--warning::after, .kpi--error::after, .kpi--info::after { display: none; }
.kpi--success .kpi__label::before, .kpi--warning .kpi__label::before,
.kpi--error .kpi__label::before, .kpi--info .kpi__label::before, .kpi--accent .kpi__label::before { display: none; }
.hk__cinta { display: none; }

.kpi__label, .hk__rot {
  font-size: var(--text-10);
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-secondary);
  font-weight: 600;
  display: inline-flex; align-items: center; gap: 5px;
}
.hk__rot svg { width: 12px; height: 12px; opacity: .75; }
.kpi__value, .hk__num {
  font-family: var(--font-display);
  font-size: var(--text-22);
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -.015em;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}
.hk--alert .hk__num { color: var(--text-primary); }
.kpi__delta {
  font-family: var(--font-mono);
  font-size: var(--text-10);
  font-weight: 600;
  display: inline-flex; align-items: center; gap: 4px;
  margin: 0;
}
.kpi__delta--up::before { content: '↑'; }
.kpi__delta--dn::before { content: '↓'; }
.kpi__pie, .hk__pie { font-size: var(--text-11); color: var(--text-secondary); line-height: 1.35; }
.kpi__pie b, .hk__pie b { color: var(--text-primary); font-weight: 600; }
/* Lo que necesita atención se dice con la TINTA del estado, en el texto. */
.kpi__pie .mal, .hk__pie .mal { color: var(--error); font-weight: 600; }
.kpi__pie .ojo, .hk__pie .ojo { color: var(--warning); font-weight: 600; }

.kpi__spark, .hk__spark { display: block; margin-top: auto; padding-top: 6px; }
.kpi__spark .spark, .hk__spark .spark { width: 100%; height: 26px; display: block; }
.kpi__cola, .hk__bar { display: block; margin-top: auto; padding-top: 7px; }
.kpi__bar, .hk__bar i {
  display: block; height: 6px;
  background: var(--cream-200);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.kpi__bar i {
  display: block; height: 100%; width: 0;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--ca, var(--viz-1)), var(--cb, var(--viz-1-deep)));
  animation: mq-crece 950ms var(--mq-ease) 200ms forwards;
}
@keyframes mq-crece { to { width: var(--w, 62%); } }
/* La tarjeta vieja del Inicio trae el ancho en línea: se anima con la
   transición de la barra, no con la animación de la variable. */
.hk__bar i > i {
  display: block; height: 100%; border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--viz-f, var(--viz-1)), var(--viz-d, var(--viz-1-deep)));
}

.hk--empty { border-style: dashed; }

/* Las tarjetas de cifra viejas (.kpiv, .ot-stat, .fin-box, .tile) toman
   la misma forma: sin franja lateral de color, rótulo en mayúsculas
   chicas, cifra negra. */
.kpiv-fila { display: grid; grid-template-columns: repeat(auto-fit, minmax(172px, 1fr)); gap: var(--mq-e2); margin-bottom: var(--mq-e3); }
.kpiv, .ot-stat, .fin-box, .tile {
  background: var(--surface-elevated); border: 1px solid var(--border-subtle);
  border-radius: var(--r-md); padding: var(--mq-e2) var(--mq-e3);
  box-shadow: none;
}
.kpiv:hover, .ot-stat:hover, .fin-box:hover, .tile:hover { transform: none; box-shadow: none; }
.kpiv::before { display: none; }
.kpiv__l, .ot-stat .l, .fin-box__l, .tile__l {
  font-size: var(--text-10); letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-secondary); font-weight: 600; margin-bottom: 3px;
}
.kpiv__v, .ot-stat .n, .fin-box__v, .tile__v {
  font-family: var(--font-display); font-weight: 800; font-size: var(--text-24);
  line-height: 1.12; letter-spacing: -.015em; color: var(--text-primary) !important;
  font-variant-numeric: tabular-nums;
}
.kpiv__d, .fin-box__venc { font-size: var(--text-11); color: var(--text-secondary); margin-top: 3px; }

/* ============================================================
   3 · TARJETA CON CABECERA (.tar / .panel)
   `.tar` es la de la ficha, sin relleno propio: la cabecera y las filas
   van de borde a borde y el cuerpo trae su relleno.
   `.panel` es la vieja (la usan 22 vistas) y conserva su relleno; la
   cabecera y las filas SANGRAN hasta el borde con margen negativo, que
   es lo que hace que se vea igual sin tocar cada vista.
   ============================================================ */
.tar, .panel {
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: none;
}
.tar { padding: 0; }
.panel { padding: 16px 18px; }
.panel:hover { box-shadow: none; transform: none; }
.tar__h, .panel__h {
  display: flex; align-items: center; gap: 8px;
  padding: var(--mq-e1) var(--mq-e3);
  border-bottom: 1px solid var(--border-subtle);
  min-height: 38px;
  margin: 0;
}
.panel__h { margin: -16px -18px 14px; }
.tar__h h4, .panel__h .t {
  font-family: var(--font-display);
  font-size: var(--text-13);
  font-weight: 700;
  margin: 0;
  letter-spacing: 0;
  color: var(--text-primary);
}
.panel__h .lu { width: 15px; height: 15px; }
.tar__mas, .panel__act, .panel__h .n {
  margin-left: auto;
  font-size: var(--text-11);
  font-weight: 600;
  color: var(--brand-ink);
  background: none; border: 0; padding: 0;
  text-decoration: none;
  font-family: var(--font-body);
  display: inline-flex; align-items: center; gap: 4px;
  cursor: default;
}
a.tar__mas, .panel__act { cursor: pointer; }
.panel__h .n { font-family: var(--font-mono); background: none; color: var(--brand-ink); }
.panel__h .n--muted { color: var(--text-tertiary); }
.tar__cuerpo { padding: var(--mq-e3); }
.panel .viz-leyenda { margin-top: 10px; }
/* Las filas dentro de un `.panel` viejo salen hasta el borde. */
.panel > .att, .panel > .ot, .panel > .obra, .panel > .per-row, .panel > .fila,
.panel > .stag > .att, .panel > .stag > .fila { margin-left: -18px; margin-right: -18px; }
.panel > .att:last-child, .panel > .ot:last-child, .panel > .obra:last-child,
.panel > .per-row:last-child, .panel > .fila:last-child { margin-bottom: -16px; }
.panel > .att--mas { margin: 0 -18px -16px; }
.panel > .att:first-of-type, .panel > .ot:first-of-type { border-top: 0; }

/* Dos columnas: 1.55 a 1, o mitad y mitad. */
.dos, .home-cols {
  display: grid;
  grid-template-columns: 1.55fr 1fr;
  gap: var(--mq-e3);
  align-items: start;
  margin-bottom: var(--mq-e3);
}
.dos--par { grid-template-columns: 1fr 1fr; }
@media (max-width: 767px) { .dos, .dos--par, .home-cols { grid-template-columns: 1fr; } }
.dash-stagger > .panel, .dash-stagger > .tar { margin-bottom: var(--mq-e3); }

/* ============================================================
   4 · LA FILA (.fila)  · avatar de dos letras, título y detalle,
   distintivo y cifra en mono. Las filas viejas (.att, .ot, .obra,
   .per-row) toman la misma métrica.
   ============================================================ */
.fila, .att, .ot, .obra, .per-row {
  display: flex; align-items: center; gap: var(--mq-e2);
  padding: 10px var(--mq-e3);
  border-bottom: 1px solid var(--border-subtle);
  border-top: 0;
  text-decoration: none; color: inherit;
  transition: background var(--mq-dur-2) var(--mq-ease);
  border-radius: 0;
  margin: 0;
}
.fila:last-child, .stag > .fila:last-child, .att:last-child, .ot:last-child, .obra:last-child, .per-row:last-child { border-bottom: 0; }
.fila:hover, .att:hover, .ot:hover, .obra:hover, .per-row:hover { background: var(--brand-yellow-soft); }
.fila__av, .att__ico {
  width: 30px; height: 30px;
  border-radius: var(--r-sm);
  display: grid; place-items: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-11);
  flex: none;
  background: linear-gradient(145deg, var(--cream-50), var(--cream-200));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9);
  color: var(--text-secondary);
}
.att--cobro .att__ico, .att--pago .att__ico, .att--ot .att__ico, .att--info .att__ico, .att--ok .att__ico {
  background: linear-gradient(145deg, var(--cream-50), var(--cream-200));
  color: var(--text-secondary);
}
.att__ico svg { width: 14px; height: 14px; }
/* Un anillo en el lugar del avatar (Proyectos): sin la cajita crema. */
.fila__av:has(> .viz-anillo) { background: none; box-shadow: none; width: auto; height: auto; }
.fila[data-obra] { cursor: pointer; }
.fila__tx, .att__body, .ot__body { min-width: 0; flex: 1; }
.fila__tx b, .att__t, .ot__t {
  display: block; font-size: var(--text-12); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: 1.3;
}
.fila__tx span, .att__s, .ot__s {
  display: block; font-size: var(--text-11); color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fila__pl, .att__amt {
  font-family: var(--font-mono);
  font-size: var(--text-12); font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.att--mas { padding: 8px var(--mq-e3); font-size: var(--text-11); color: var(--text-tertiary); }
/* La acción de la fila de atención es un distintivo con forma. */
.att__act {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-10); font-weight: 700; padding: 2px 8px;
  border-radius: var(--r-pill); white-space: nowrap;
}
.att__act::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.att__act--error, .att__act--cobrar, .att__act--urgente { background: var(--error-soft); color: var(--error); }
.att__act--error::before, .att__act--cobrar::before, .att__act--urgente::before {
  width: 0; height: 0; background: transparent; border-radius: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent; border-bottom: 7px solid currentColor;
}
.att__act--warning, .att__act--pagar, .att__act--plazo { background: var(--warning-soft); color: var(--warning); }
.att__act--warning::before, .att__act--pagar::before, .att__act--plazo::before { border-radius: 0; transform: rotate(45deg); }
.att__act--info, .att__act--seguir { background: var(--info-soft); color: var(--info); }
/* La prioridad de la OT vieja era una franja lateral; la ficha la dice
   con el distintivo, así que la franja se va. */
.ot__prio { display: none; }

/* ============================================================
   5 · DISTINTIVO CON FORMA (.badge)
   Lo vencido se lee por la forma y no sólo por el rojo: círculo para
   lo que está bien o informa, rombo para lo que avisa, triángulo para
   lo malo, cuadrado para el borrador, raya para lo anulado.
   ============================================================ */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-10); font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  white-space: nowrap;
  line-height: 1.5;
}
.badge--success, .badge--warning, .badge--error, .badge--info, .badge--draft, .badge--void { border: 0; }
.badge--success::before, .badge--warning::before, .badge--error::before,
.badge--info::before, .badge--draft::before, .badge--void::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: none; box-shadow: none;
}
.badge--warning::before { border-radius: 0; transform: rotate(45deg); }
.badge--error::before {
  width: 0; height: 0; background: transparent; border-radius: 0;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-bottom: 7px solid currentColor;
}
.badge--draft::before { border-radius: 0; }
.badge--void { text-decoration: none; }
.badge--void::before { border-radius: 0; height: 2px; width: 8px; }
/* Con un ícono adentro, el ícono es la forma. */
.badge:has(> svg:first-child)::before { display: none; }
.badge--count::before { display: none; }

/* ============================================================
   6 · CHIPS CON CONTEO (.chip / .pres-chips)
   Pastilla con borde, el número en mono sobre crema; la elegida es
   navy con letra blanca.
   ============================================================ */
.chips, .pres-chips {
  display: flex; gap: 7px; flex-wrap: wrap;
  margin-bottom: var(--mq-e3);
  background: transparent !important; border: 0 !important; box-shadow: none !important;
  padding: 0 !important; border-radius: 0 !important;
  overflow: visible !important;
}
.chip, .pres-chips .tab {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--border-default);
  background: var(--surface-elevated);
  border-radius: var(--r-pill);
  padding: 5px 11px;
  font-family: var(--font-body);
  font-size: var(--text-12);
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  min-height: 0;
  box-shadow: none;
  transition: background var(--mq-dur-2) var(--mq-ease), color var(--mq-dur-2) var(--mq-ease), border-color var(--mq-dur-2) var(--mq-ease);
}
.chip b, .pres-chips .tab b {
  font-family: var(--font-mono);
  font-size: var(--text-10); font-weight: 600;
  background: var(--cream-100);
  border-radius: var(--r-pill);
  padding: 1px 6px;
  color: var(--text-secondary);
  margin: 0;
}
.chip:hover, .pres-chips .tab:hover { border-color: var(--text-tertiary); background: var(--surface-elevated); color: var(--text-secondary); }
.chip--on, .chip[aria-pressed="true"], .pres-chips .tab--active, .pres-chips .tab[aria-pressed="true"] {
  background: var(--brand-navy) !important;
  border-color: var(--brand-navy) !important;
  color: var(--tinta-sobre-color) !important;
  font-weight: 600;
  box-shadow: none !important;
}
.chip--on b, .chip[aria-pressed="true"] b, .pres-chips .tab--active b, .pres-chips .tab[aria-pressed="true"] b {
  background: rgba(255, 255, 255, .18); color: var(--tinta-sobre-color);
}
.chip[disabled], .pres-chips .tab[disabled] { opacity: .45; cursor: default; }
.chip:active { transform: none; }

/* ============================================================
   7 · TABLA
   ============================================================ */
.table thead th, table thead th {
  text-align: left;
  font-family: var(--font-body);
  font-size: var(--text-10);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-secondary);
  font-weight: 700;
  padding: 8px var(--mq-e3);
  background: var(--surface-sunken);
  border-bottom: 0;
}
.table tbody td, table tbody td {
  padding: 9px var(--mq-e3);
  font-size: var(--text-12);
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: middle;
}
.table tbody tr:hover, table tbody tr:hover { background: var(--brand-yellow-soft); }
.table .num, td.n, .table td[align="right"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cod { font-family: var(--font-mono); font-size: var(--text-11); font-weight: 600; }
.tar > table, .tar > .table, .panel > table, .panel > .table { margin: 0; }

/* ============================================================
   8 · GRÁFICOS (.viz)  · regla: relleno vivo, trazo profundo, texto con
   la tinta terciaria. Grilla apenas insinuada, sin marcos ni ejes.
   ============================================================ */
.viz { width: 100%; max-width: 100%; height: auto; max-height: 300px; overflow: visible; display: block; }
/* Un SVG con viewBox de 820 le dice a la grilla que su columna mide 820
   como mínimo, y la grilla le cree: la página se va de costado. Los
   hijos de la grilla y la tarjeta parten de cero. */
.dos > *, .dos--par > *, .home-cols > *, .kpis > *, .tar, .tar__cuerpo, .panel { min-width: 0; }
.viz text { font-family: var(--font-mono); fill: var(--text-tertiary); }
.viz .rej { stroke: var(--viz-grid); stroke-width: 1; }
.viz .rej.cero { stroke: var(--text-secondary); stroke-dasharray: 3 3; }   /* v55 · la línea del cero cuando hay valores bajo cero */
.viz .eje { fill: var(--text-tertiary); }
.sviz { max-height: 300px; }

.viz .barra { transform-box: fill-box; transform-origin: bottom; animation: mq-sube 760ms var(--mq-ease) both; }
@keyframes mq-sube { from { transform: scaleY(0); } to { transform: scaleY(1); } }
.viz .trazo {
  stroke-dasharray: var(--len, 900);
  stroke-dashoffset: var(--len, 900);
  animation: mq-dibuja 1100ms var(--mq-ease) 120ms forwards;
}
@keyframes mq-dibuja { to { stroke-dashoffset: 0; } }
.viz .relleno { opacity: 0; animation: mq-aparece 700ms var(--mq-ease) 620ms forwards; }
@keyframes mq-aparece { to { opacity: 1; } }
.viz .punto { transform-box: fill-box; transform-origin: center; opacity: 0; animation: mq-pop 420ms var(--ease-spring) 980ms forwards; }
@keyframes mq-pop { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: scale(1); } }
.viz .halo { transform-box: fill-box; transform-origin: center; opacity: .22; animation: mq-latido 3.2s var(--mq-ease) 1.2s infinite; }
@keyframes mq-latido { 0%, 70%, 100% { opacity: .22; transform: scale(1); } 35% { opacity: 0; transform: scale(2.4); } }

/* La dona: 172 px, punta redonda, hueco entre segmentos, y la leyenda
   centrada abajo (no al costado). */
.dviz { display: flex; flex-direction: column; align-items: center; gap: 0; }
.dviz__svg { width: 172px; height: 172px; max-width: 100%; }
.dviz__arco { animation-delay: var(--del, 0ms); }
.dviz + .viz-leyenda, .dviz .viz-leyenda { justify-content: center; margin-top: 10px; }

/* La leyenda: cuadrado con degradado, nombre gris, cifra en mono negra. */
.viz-leyenda {
  display: flex; gap: 16px; flex-wrap: wrap; align-items: center;
  font-size: var(--text-11); color: var(--text-secondary);
  margin-top: 10px;
}
.viz-leyenda--centro { justify-content: center; }
.viz-leyenda__it { display: inline-flex; align-items: center; gap: 7px; }
.viz-leyenda__punto { width: 9px; height: 9px; border-radius: var(--r-xs); display: block; flex: none;
  background: linear-gradient(135deg, var(--viz-f, var(--viz-1)), var(--viz-d, var(--viz-1-deep))); }
.viz-leyenda__val { font-family: var(--font-mono); font-size: var(--text-11); color: var(--text-primary); font-variant-numeric: tabular-nums; font-weight: 600; }

/* El ranking: nombre y cifra arriba, la pista de 8 px cruzando abajo. */
.viz-rank { display: flex; flex-direction: column; gap: 11px; }
.viz-rank__li { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; align-items: baseline; }
.viz-rank__nom { font-size: var(--text-12); font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.viz-rank__val { font-family: var(--font-mono); font-size: var(--text-12); font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.viz-rank__pista { grid-column: 1 / -1; height: 8px; background: var(--cream-100); border-radius: var(--r-pill); overflow: hidden; display: block; }
.viz-rank__barra {
  display: block; height: 100%; width: 0; border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--viz-f, var(--viz-1)), var(--viz-d, var(--viz-1-deep)));
  animation: mq-crece 900ms var(--mq-ease) var(--del, 0ms) forwards;
}

/* La apilada: 12 px, tramos separados 2 px, cada uno con su degradado. */
.viz-apilada { display: flex; gap: 2px; height: 12px; border-radius: var(--r-pill); overflow: visible; background: transparent; }
.viz-apilada__tramo {
  display: block; height: 100%; width: 0; min-width: 0;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--viz-f, var(--viz-1)), var(--viz-d, var(--viz-1-deep)));
  animation: mq-ancho 820ms var(--mq-ease) var(--del, 0ms) both;
}
@keyframes mq-ancho { from { width: 0; } to { width: var(--w); } }

/* El anillo: 52 px, pista crema, trazo de 6 con la punta redonda. */
.viz-anillo__pista { stroke: var(--cream-100); }

/* ============================================================
   9 · CARPETAS (.carp)  · documentos, reportes, menú, marca
   ============================================================ */
.carpetas {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(134px, 1fr));
  gap: var(--mq-e2);
  margin-bottom: var(--mq-e3);
}
.carpetas--anchas { grid-template-columns: repeat(auto-fill, minmax(184px, 1fr)); }
.carp {
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-md);
  padding: var(--mq-e2);
  display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  text-align: left; text-decoration: none; color: inherit;
  font-family: var(--font-body);
  cursor: pointer;
  transition: transform var(--mq-dur-2) var(--mq-ease), box-shadow var(--mq-dur-2) var(--mq-ease), border-color var(--mq-dur-2) var(--mq-ease);
}
.carp:hover { transform: translateY(-2px); box-shadow: var(--shadow-card); border-color: var(--brand-yellow-300); }
/* La carpeta elegida (Reportes): borde y fondo de marca, como el chip. */
.carp[aria-pressed="true"] { border-color: var(--brand-yellow); background: var(--brand-yellow-soft); }
.carp__ico { width: 22px; height: 22px; stroke: var(--brand-ink); fill: var(--brand-yellow-100); stroke-width: 1.6; }
.carp b { font-size: var(--text-12); font-weight: 600; }
.carp span { font-size: var(--text-11); color: var(--text-secondary); }
.carp .badge { margin-top: 2px; }

/* ============================================================
   10 · ENTRADA ESCALONADA (.stag) y la nota al pie
   ============================================================ */
.stag > * { animation: mq-entra 320ms var(--mq-ease) both; }
.stag > :nth-child(1) { animation-delay: 40ms; }
.stag > :nth-child(2) { animation-delay: 85ms; }
.stag > :nth-child(3) { animation-delay: 130ms; }
.stag > :nth-child(4) { animation-delay: 175ms; }
.stag > :nth-child(5) { animation-delay: 220ms; }
.stag > :nth-child(6) { animation-delay: 265ms; }
.stag > :nth-child(n+7) { animation-delay: 300ms; }
@keyframes mq-entra { from { opacity: .25; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

.pie-nota { margin-top: 10px; font-size: var(--text-11); color: var(--text-secondary); line-height: 1.5; }
.pie-nota b { color: var(--text-primary); }
.tar__cuerpo > .pie-nota:last-child { margin-bottom: 0; }

/* El aviso accionable de la ficha de Materiales: borde de la serie. */
.tar--aviso { border-left: 3px solid var(--viz-1-deep); }
.tar--aviso .tar__cuerpo { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

/* ============================================================
   11 · MOVIMIENTO REDUCIDO
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .kpi__bar i, .hk__bar i > i, .viz-rank__barra { animation: none; width: var(--w, 0); }
  .viz-apilada__tramo { animation: none; width: var(--w); }
  .viz .trazo { animation: none; stroke-dashoffset: 0; }
  .viz .relleno, .viz .punto { animation: none; opacity: 1; }
  .viz .halo, .viz .barra, .stag > * { animation: none; }
}

/* ============================================================
   12 · TELÉFONO
   ============================================================ */
@media (max-width: 640px) {
  .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--mq-e1); }
  .kpi, .hk { padding: 10px 12px; }
  .kpi__value, .hk__num { font-size: var(--text-20); }
  .tar__cuerpo { padding: var(--mq-e2); }
  .fila, .att, .ot, .obra, .per-row { padding: 9px var(--mq-e2); }
  .viz { max-height: 190px; }
  .carpetas { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* El ancho estirado a 1397 se ve bien; a 360 el formato ancho da 66 px
     de alto y las barras se vuelven hilos. Se desliza, como el riel. */
  .viz-desliza { overflow-x: auto; scrollbar-width: thin; -webkit-overflow-scrolling: touch; }
  .viz-desliza .viz { min-width: 560px; }
}

/* ═══ css/modo-obra.css ═══ */
/* ══════════════════════════════════════════════════════════
   v42 · idea 3 · MODO OBRA
   ══════════════════════════════════════════════════════════
   La misma app, leída con guantes, a contraluz y con el 12% de batería.
   Se prende a mano (`js/ui/terreno.js`) y queda guardado en el teléfono.

   Qué cambia y por qué cada cosa:

   · FONDO OSCURO. No es gusto: a pleno sol el teléfono sube el brillo al
     máximo para poder leer un fondo blanco, y ahí es donde se va la
     batería. Con fondo oscuro se lee con menos brillo. Por eso pisa los
     tokens de superficie en vez de pedir el tema oscuro: el modo obra tiene
     que verse igual aunque la app esté en claro.

   · LETRA GRANDE. El texto de la OT pasa de 16 a 22 px y el de apoyo de 12
     a 15. No es «todo el doble»: agrandar parejo no arregla nada, porque lo
     que cuesta leer arriba de un andamio es lo chico.

   · BOTONES DE 56 px. 44 es el mínimo para un dedo; 56 es el mínimo para un
     dedo con guante de trabajo, que es lo que hay puesto.

   · EL CRONÓMETRO ES LO MÁS GRANDE DE LA PANTALLA. Es el número que después
     termina en «horas vendidas contra horas reales». Si nadie lo mira,
     nadie cierra la OT a tiempo y esa comparación miente.

   Va último en `assets.php`, después de `pulido.css`: tiene que ganarle a
   todo lo demás, y por eso todo acá está bajo `body.modo-obra`. Sin esa
   clase, este archivo no hace absolutamente nada.
   ══════════════════════════════════════════════════════════ */

body.modo-obra {
  /* Las superficies, pisadas. Un gris carbón con una pizca de verde: el
     negro puro sobre OLED a pleno sol da un contraste que cansa. */
  --surface-base:     #12140C;
  --surface-elevated: #21251A;
  --surface-sunken:   #0B0D07;
  --surface-raised:   #2A2E20;
  --border-subtle:    #333827;
  --border-default:   #474D38;
  --text-primary:     #F2F4EA;
  --text-secondary:   #B9BDA9;
  --text-tertiary:    #8C9180;
  --text-disabled:    #6B6F60;
  --cream-100:        #2A2E20;
  --cream-50:         #1F2217;

  --cream-base:       #12140C;

  /* El modo obra pisa el tema CLARO, asi que hereda sus velos y lavados:
     un lavado negro sobre este fondo no se ve. Van los de oscuro. */
  --velo:             rgba(0, 0, 0, .72);
  --velo-suave:       rgba(0, 0, 0, .45);
  --lavado:           rgba(255, 255, 255, .08);
  --lavado-fuerte:    rgba(255, 255, 255, .15);
  /* El oro oscuro (#7A6000) es tinta para fondo claro: acá da 2,7:1. */
  --brand-ink:        #FFCF00;

  /* Semánticos subidos de brillo: los de modo claro se apagan sobre negro.
     Y con ellos SUS FONDOS SUAVES. Sin esto pasa lo que pasó al probarlo:
     el distintivo «En curso» quedaba en 1,37:1 —amarillo brillante sobre
     crema claro— o sea, invisible justo en la tarjeta que más importa.
     Por eso `tools/contraste.php` ahora también mide este modo. */
  --success: #6FDC9C;  --success-soft: #1B3A25;
  --warning: #FFC24D;  --warning-soft: #3D2F0E;
  --error:   #FF9A9A;  --error-soft:   #48211F;
  --info:    #8CC0F5;  --info-soft:    #16304A;
  --void:      #9D9B91;  --void-soft:  #2E3128;
  --draft:     #C4CAD6;  --draft-soft: #2A2F38;

  background: var(--surface-base);
  color: var(--text-primary);
}

/* El shell entero, no sólo la vista: si la barra de arriba queda clara,
   el ojo se sigue yendo ahí y el contraste general no mejora. */
body.modo-obra .app-content,
body.modo-obra .app-main-wrapper,
body.modo-obra .app-topbar,
body.modo-obra .app-sidebar {
  background: var(--surface-base);
  color: var(--text-primary);
}

/* ── El aviso de que está prendido ───────────────────────── */
/* Un modo que cambia toda la pantalla y no se anuncia es un modo en el que
   alguien queda atrapado sin saber cómo salir. */
.modo-obra__barra {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin: 0 0 14px;
  padding: 10px 14px;
  border-radius: var(--r-md);
  background: var(--surface-raised);
  border: 1px solid var(--border-default);
  font-size: var(--text-13);
}
.modo-obra__barra b { font-family: var(--font-display); }
.modo-obra__barra .lu { width: 18px; height: 18px; }

/* Fuera del modo, el botón es discreto: está ahí para quien lo busca. */
.modo-obra__btn {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 40px; padding: 0 12px;
  background: none; border: 1px solid var(--border-subtle); border-radius: var(--r-md);
  color: var(--text-secondary); font: inherit; font-size: var(--text-12);
  cursor: pointer;
}
.modo-obra__btn .lu { width: 15px; height: 15px; }
.modo-obra__btn:hover { background: var(--cream-100); color: var(--text-primary); }
body.modo-obra .modo-obra__btn { min-height: 48px; font-size: var(--text-14); }

/* ── Mi día, agrandado ───────────────────────────────────── */
body.modo-obra .midia { max-width: 760px; }
body.modo-obra .midia__fecha { font-size: 26px; }
body.modo-obra .midia__sub   { font-size: var(--text-14); }
body.modo-obra .midia__h     { font-size: var(--text-13); letter-spacing: .08em; }

body.modo-obra .midia__ot {
  border-radius: var(--r-xl);
  padding: 18px 18px 14px;
  margin-bottom: 16px;
  border-left-width: 6px;
}
body.modo-obra .midia__hora { font-size: 24px; }
body.modo-obra .midia__num  { font-size: var(--text-13); }
body.modo-obra .midia__t    { font-size: 22px; line-height: 1.2; }
body.modo-obra .midia__cli  { font-size: var(--text-15); }
body.modo-obra .midia__desc { font-size: var(--text-15); -webkit-line-clamp: 4; }
body.modo-obra .midia__dir  { font-size: var(--text-15); }
body.modo-obra .midia__dir .lu { width: 18px; height: 18px; }

/* 56 px: un dedo con guante de trabajo. */
body.modo-obra .midia__acciones .btn { min-height: 56px; font-size: var(--text-16); border-radius: var(--r-md); }
body.modo-obra .midia__link { min-height: 52px; font-size: var(--text-13); }
body.modo-obra .midia__link .lu { width: 18px; height: 18px; }

/* v45 · las filas y los filtros, con medida de guante.
   56 px es un dedo; los iconos de la derecha son destinos de pulgar y
   por eso pasan a 62 px de ancho: son links que abren Maps o el
   teléfono, y errarles en obra significa marcar a otro cliente. */
body.modo-obra .midia__filtros .tab { min-height: 52px; font-size: var(--text-14); padding: 10px 16px; }
body.modo-obra .mifila { border-radius: var(--r-xl); border-left-width: 6px; margin-bottom: 12px; }
body.modo-obra .mifila__cuerpo { min-height: 72px; padding: 12px 16px; gap: 14px; }
body.modo-obra .mifila__hora { width: 76px; }
body.modo-obra .mifila__hora b { font-size: var(--text-20); }
body.modo-obra .mifila__hora > span { font-size: var(--text-12); }
body.modo-obra .mifila__tx b { font-size: var(--text-16); }
body.modo-obra .mifila__tx > span { font-size: var(--text-13); }
body.modo-obra .mifila__ico { width: 62px; }
body.modo-obra .mifila__ico .lu { width: 22px; height: 22px; }
body.modo-obra .midia__anillo { --anillo-tam: 56px; }
body.modo-obra .midia__nada { font-size: var(--text-15); }

/* ── El cronómetro ───────────────────────────────────────── */
.crono {
  display: flex; align-items: baseline; gap: 8px;
  margin: 10px 0 2px;
}
.crono__n {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 30px;
  line-height: 1;
  color: var(--warning);
  font-variant-numeric: tabular-nums;
}
.crono__t { font-size: var(--text-12); color: var(--text-secondary); }
/* En modo obra es lo más grande que hay en la tarjeta, a propósito. */
body.modo-obra .crono__n { font-size: 52px; }
body.modo-obra .crono__t { font-size: var(--text-14); }

.crono--largo .crono__n { color: var(--error); }
.crono__aviso { font-size: var(--text-11); color: var(--error); margin-top: 2px; }
body.modo-obra .crono__aviso { font-size: var(--text-13); }

/* ── La hoja de ruta ─────────────────────────────────────── */
.ruta {
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-lg);
  padding: 12px 14px;
  margin-bottom: 14px;
}
.ruta__cab { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.ruta__t { font-family: var(--font-display); font-weight: 700; font-size: var(--text-14); }
.ruta__sub { font-size: var(--text-12); color: var(--text-secondary); }
.ruta__paradas { display: flex; flex-direction: column; gap: 2px; margin: 10px 0 0; }
.ruta__p { display: flex; align-items: baseline; gap: 9px; font-size: var(--text-12); padding: 3px 0; }
.ruta__n {
  flex: none; width: 20px; height: 20px; border-radius: 50%;
  background: var(--brand-yellow); color: #1A1A18;
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}
.ruta__hora { font-family: var(--font-mono); color: var(--text-secondary); flex: none; }
.ruta__que { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ruta__nota { font-size: var(--text-11); color: var(--text-secondary); margin: 8px 0 0; }
.ruta__btn { min-height: 44px; }

body.modo-obra .ruta__t    { font-size: var(--text-16); }
body.modo-obra .ruta__sub  { font-size: var(--text-14); }
body.modo-obra .ruta__p    { font-size: var(--text-15); padding: 5px 0; }
body.modo-obra .ruta__n    { width: 26px; height: 26px; font-size: 13px; }
body.modo-obra .ruta__btn  { min-height: 56px; font-size: var(--text-16); }

/* El número de parada se repite en la tarjeta, para poder atarlas. */
.midia__parada {
  flex: none; width: 20px; height: 20px; border-radius: 50%;
  background: var(--brand-yellow); color: #1A1A18;
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}
body.modo-obra .midia__parada { width: 28px; height: 28px; font-size: 14px; }

/* Con el dedo y a contraluz, el foco tiene que verse de lejos. */
body.modo-obra :focus-visible {
  outline: 3px solid var(--brand-yellow);
  outline-offset: 2px;
}

/* Quien pidió menos movimiento no lo pierde por entrar al modo obra. */
@media (prefers-reduced-motion: reduce) {
  body.modo-obra * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ── La ficha de la OT, abierta desde Mi día ─────────────── */
/* `ot-detalle.js` inyecta su `<style>` en tiempo de ejecución, o sea
   DESPUÉS de este archivo. Que igual gane no es suerte: estos selectores
   llevan dos clases y los suyos una, así que ganan por especificidad y no
   por orden — que es lo único en lo que se puede confiar acá. */
body.modo-obra .sei-drawer-title { font-size: 22px; }
body.modo-obra .sei-drawer-meta  { font-size: var(--text-14); }
body.modo-obra .otdet-card       { padding: 16px; border-radius: var(--r-lg); }
body.modo-obra .otdet-eyebrow    { font-size: var(--text-13); }
body.modo-obra .otdet-textarea   { font-size: var(--text-16); min-height: 120px; }

/* El checklist es lo que se toca con el guante puesto. */
body.modo-obra .otck__t    { font-size: var(--text-16); line-height: 1.3; }
body.modo-obra .otck__meta { font-size: var(--text-12); }
body.modo-obra .otck__item { padding: 12px 8px; gap: 14px; }
body.modo-obra .otck__box  { width: 40px; height: 40px; flex-basis: 40px; border-radius: var(--r-md); }
body.modo-obra .otck__box .lu { width: 22px; height: 22px; }
body.modo-obra .otck__btn  { width: 44px; height: 44px; flex-basis: 44px; opacity: 1; }
body.modo-obra .otck__btn .lu { width: 20px; height: 20px; }
body.modo-obra .sei-drawer-foot .btn { min-height: 56px; font-size: var(--text-16); }

