/* components.css — librería compartida de componentes UI (FUENTE ÚNICA).

   Patrones recurrentes en los tres stacks: KPI hero, KPI card, banners
   de estado, empty-state, tags/badges semánticos, botones, tabla con
   sticky header. Diseñado para componerse — usá `.kpi-card.is-hero`
   para variantes, no clases duplicadas.

   Filosofía minimalist + emil:
     - Densidad alta, ruido bajo.
     - Tipografía tabular en métricas.
     - Hover/focus apenas perceptibles (80–140ms).
     - Cero shadows pesadas: el panel se distingue por borde 1px, no
       por elevación.

   Carga después de base.css y antes de style.css del proyecto. */


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  KPI hero — número grande naranja sobre card                     ║
   ╚══════════════════════════════════════════════════════════════════╝
   Usado en /inicio (reports), /proxima (meetings), /inicio (mrp).
   Layout: label superior (uppercase muted) + valor hero (44px accent)
   + delta opcional (chip pequeño) + footnote opcional (text-xs muted). */

.kpi-hero {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow);
    min-width: 0;
}
.kpi-hero__label {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.kpi-hero__value {
    font-size: var(--text-kpi-hero);
    font-weight: 600;
    color: var(--accent);
    line-height: 1;
    letter-spacing: -0.025em;
    font-variant-numeric: tabular-nums;
    /* font-feature-settings ss01 activa el alternate set "single-storey g"
       en Inter — más uniforme con los demás caracteres en sizes grandes
       (44px+). Cost cero en runtime, ganancia tipográfica visible. */
    font-feature-settings: "ss01";
}
.kpi-hero__delta {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--text-sm);
    color: var(--fg-muted);
    font-variant-numeric: tabular-nums;
}
.kpi-hero__delta.is-up   { color: var(--ok); }
.kpi-hero__delta.is-down { color: var(--color-danger); }
.kpi-hero__footnote {
    font-size: var(--text-xs);
    color: var(--fg-faint);
}


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  KPI card — métrica secundaria, valor 24px sobre card            ║
   ╚══════════════════════════════════════════════════════════════════╝
   Usado en grids de KPIs (3-4 por fila). Hover apenas perceptible
   (border-color, sin elevation). */

.kpi-card {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: var(--space-3);
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    /* Single-property hover (border-color). Probamos doble transition con
       color-mix bg pero el paint cost de animar bg en cards grandes se
       suma en hover-cycling (KPI grid de 3+ cards), y la ganancia visual
       no justifica el cost. KISS. */
    transition: border-color var(--dur-base) var(--ease);
}
.kpi-card:hover { border-color: var(--border-hover); }
.kpi-card__label {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--fg-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.kpi-card__value {
    font-size: var(--text-kpi);
    font-weight: 600;
    color: var(--fg);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
    letter-spacing: -0.015em;
}
.kpi-card__footnote {
    font-size: var(--text-xs);
    color: var(--fg-faint);
    margin-top: 4px;
}


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  KPI grid layout                                                 ║
   ╚══════════════════════════════════════════════════════════════════╝
   Auto-fit responsive: 280px min por columna, gap consistente. */

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


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Banner — info / warning / danger / success                      ║
   ╚══════════════════════════════════════════════════════════════════╝
   Full border (no side-stripe — side-stripe es decorativo y está
   bannedo por impeccable). El status se transmite con un dot leading
   colored + ARIA role/aria-live cuando sea contenido dinámico. Variants
   `is-info / is-warn / is-danger / is-success`. */

.banner {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-3);
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    color: var(--fg);
    font-size: var(--text-sm);
    line-height: 1.5;
    transition: border-color var(--dur-slow) var(--ease);
}

/* Leading dot 8px — reemplaza el side-stripe como portador del status.
   margin-top alinea con la primera línea de texto sin asumir line-height. */
.banner__icon {
    flex: 0 0 8px;
    width: 8px;
    height: 8px;
    margin-top: 6px;
    border-radius: 50%;
    background: var(--accent);
    display: inline-block;
}
.banner.is-info    .banner__icon { background: var(--info); }
.banner.is-warn    .banner__icon { background: var(--warn); }
.banner.is-danger  .banner__icon { background: var(--color-danger); }
.banner.is-success .banner__icon { background: var(--ok); }

/* Cuando el banner contiene un icono SVG en vez del dot leading, el
   wrapper inline-flex deja que el SVG mantenga su size original. */
.banner__icon.is-svg {
    flex: 0 0 auto;
    width: auto;
    height: auto;
    margin-top: 0;
    background: transparent;
    color: var(--accent);
    border-radius: 0;
    display: inline-flex;
}
.banner.is-info    .banner__icon.is-svg { color: var(--info); background: transparent; }
.banner.is-warn    .banner__icon.is-svg { color: var(--warn); background: transparent; }
.banner.is-danger  .banner__icon.is-svg { color: var(--color-danger); background: transparent; }
.banner.is-success .banner__icon.is-svg { color: var(--ok); background: transparent; }

.banner__body { flex: 1; min-width: 0; }
.banner__title {
    font-weight: 600;
    margin-bottom: 2px;
    color: var(--fg);
}


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Empty state — placeholder cuando no hay datos                   ║
   ╚══════════════════════════════════════════════════════════════════╝
   Centered, generous padding, optional icon + title + body + CTA.
   Aplicar cuando count === 0 en lista/tabla/grid. */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-5) var(--space-4);
    text-align: center;
    color: var(--fg-muted);
    background: var(--bg-card);
    border: 1px dashed var(--border);
    border-radius: var(--radius-card);
    min-height: 200px;
}
.empty-state__icon {
    color: var(--fg-faint);
    margin-bottom: var(--space-1);
    width: 32px;
    height: 32px;
}
.empty-state__title {
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--fg);
    margin: 0;
}
.empty-state__body {
    font-size: var(--text-sm);
    color: var(--fg-muted);
    max-width: 48ch;
    margin: 0;
}
.empty-state__cta { margin-top: var(--space-2); }


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Tag / Badge semántico                                           ║
   ╚══════════════════════════════════════════════════════════════════╝
   Reemplaza badges saturados (background opaco rojo/verde) por
   variantes con fondo a 16% de opacidad + texto en color saturado.
   Lectura: clara, baja saturación de superficie, jerarquía visual
   tranquila. */

.tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    background: var(--bg-soft);
    color: var(--fg-muted);
    border-radius: var(--radius-pill);
    font-size: var(--text-xs);
    font-weight: 600;
    line-height: 1.4;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border: 1px solid transparent;
    font-variant-numeric: tabular-nums;
}
.tag.is-ok      { background: rgba(22, 163, 74, 0.14);  color: #4ade80; }
.tag.is-warn    { background: rgba(234, 179, 8, 0.14);  color: #facc15; }
.tag.is-danger  { background: rgba(239, 68, 68, 0.14);  color: #fca5a5; }
.tag.is-info    { background: rgba(96, 165, 250, 0.14); color: #93c5fd; }
.tag.is-accent  { background: rgba(242, 169, 0, 0.14);  color: var(--accent-soft); }
.tag.is-muted   { background: var(--bg-soft);           color: var(--fg-muted); }

/* Variante sin uppercase para counts/números puros */
.tag.is-count {
    text-transform: none;
    letter-spacing: 0;
    font-weight: 500;
}


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Button — primary / secondary / ghost / danger                   ║
   ╚══════════════════════════════════════════════════════════════════╝
   Sin gradients por default. Primary es accent flat; los gradients
   quedan reservados para el logo/KPI hero (jerarquía emocional). */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    /* min-height 44px para tap target WCAG en mobile sin afectar desktop
       (padding visual no cambia, line-height + padding mantienen). */
    min-height: 32px;
    padding: 8px 14px;
    background: var(--bg-soft);
    color: var(--fg);
    border: 1px solid var(--border);
    border-radius: var(--radius-btn);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: 500;
    line-height: 1;
    cursor: pointer;
    user-select: none;
    /* Color/bg transitions a 140 ms (sweet spot perceptual), transform
       a 100 ms con strong ease-out — emil: press feedback debe ser
       más snappy que color transitions. */
    transition: background    var(--dur-base) var(--ease),
                border-color  var(--dur-base) var(--ease),
                color         var(--dur-base) var(--ease),
                transform     var(--dur-fast) var(--ease-out);
    text-decoration: none;
    white-space: nowrap;
}
.btn:hover {
    background: var(--bg-card);
    border-color: var(--border-hover);
    color: var(--fg);
}
/* Scale 0.97 reemplaza translateY(1px) — apple-style press feedback,
   y el origin queda centrado en el botón. */
.btn:active { transform: scale(0.97); }
.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

.btn.is-primary {
    background: var(--accent);
    color: var(--fg-inverse);
    border-color: var(--accent);
    font-weight: 600;
}
.btn.is-primary:hover {
    background: var(--accent-soft);
    border-color: var(--accent-soft);
    color: var(--fg-inverse);
}

.btn.is-ghost {
    background: transparent;
    border-color: transparent;
    color: var(--fg-muted);
}
.btn.is-ghost:hover {
    background: var(--bg-soft);
    color: var(--fg);
}

.btn.is-danger {
    color: #fca5a5;
    border-color: rgba(239, 68, 68, 0.32);
    background: rgba(239, 68, 68, 0.08);
}
.btn.is-danger:hover {
    background: rgba(239, 68, 68, 0.16);
    border-color: rgba(239, 68, 68, 0.5);
    color: #fecaca;
}

.btn.is-sm  { padding: 4px 10px; font-size: var(--text-xs); }
.btn.is-lg  { padding: 12px 20px; font-size: var(--text-base); }


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Section title — header de bloque dentro de página               ║
   ╚══════════════════════════════════════════════════════════════════╝
   El estilo más usado en los tres stacks. Mantiene jerarquía sin
   competir con page-title. */

.section-title {
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--fg);
    letter-spacing: -0.005em;
    margin: 0 0 var(--space-3) 0;
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
}
.section-title__meta {
    font-size: var(--text-sm);
    font-weight: 400;
    color: var(--fg-faint);
}


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Card — superficie genérica                                      ║
   ╚══════════════════════════════════════════════════════════════════╝ */

.card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: var(--space-3);
}
.card.is-interactive {
    cursor: pointer;
    transition: border-color var(--dur-base) var(--ease);
}
.card.is-interactive:hover { border-color: var(--border-hover); }


/* ── glass-elev — elevación premium opt-in ───────────────────────────
   Glassmorphism oscuro Probusol para superficies JERÁRQUICAS de baja
   frecuencia (KPI hero, chart principal). NO usar como default ni en
   chips/tooltips/badges/inputs/filas de tabla (sería el anti-patrón
   "glass everywhere" que lee como AI cheap y mata performance).

   Profundidad = sombra en capas + borde con luz cenital. El blur es
   sutil (sobre OLED negro casi no refracta; pide un ambient detrás —
   ver --ambient-accent en el hero). border 1px transparent reserva el
   espacio que dibuja el pseudo-borde ::before. */
.glass-elev {
    position: relative;
    /* superficie charcoal clara (tier superior) — flota sobre OLED negro
       por contraste tonal, no por blur. */
    background: var(--surface-elev-2);
    border: 1px solid transparent;
    border-radius: 12px;
    box-shadow: var(--shadow-glass-card);
    transition: transform var(--dur-slow) var(--ease-out),
                box-shadow var(--dur-slow) var(--ease-out);
}
/* borde con luz cenital: anillo de 1px con gradiente vertical, recortado
   vía mask (técnica padding-box xor border-box). Más premium que un
   border-color plano — capta la "luz" desde arriba como hardware. */
.glass-elev::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: var(--glass-border-grad);
    -webkit-mask: linear-gradient(#000 0 0) content-box,
                  linear-gradient(#000 0 0);
            mask: linear-gradient(#000 0 0) content-box,
                  linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    pointer-events: none;
}
/* hover: sube 1px + sombra inferior más profunda (no laterales) + halo
   accent un poco más visible. NO sólo border-color (regla soft/emil). */
.glass-elev.is-interactive:hover,
a.glass-elev:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-glass-card-hover);
}
/* reduced-motion: preserva la sombra (comprensión de profundidad),
   elimina el movimiento (regla emil). */
@media (prefers-reduced-motion: reduce) {
    .glass-elev { transition: box-shadow var(--dur-slow) var(--ease-out); }
    .glass-elev.is-interactive:hover,
    a.glass-elev:hover { transform: none; }
}


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Tabla — sticky header + zebra opcional                          ║
   ╚══════════════════════════════════════════════════════════════════╝
   Clase `.tbl-base` aplica padding, hover sutil de fila y sticky header.
   `.tbl-base.is-zebra` agrega rayas alternadas a baja opacidad. */

.tbl-base {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    /* scroll-behavior solo aplica cuando hay scroll programático (anchors,
       scrollIntoView). En 120Hz se ve fluido sin costo extra. */
    scroll-behavior: smooth;
}
/* Sticky thead — position:sticky alcanza. NO usamos transform:translateZ(0)
   por-th: cada th se promovería a su propia GPU layer (tablas con 8 cols
   = 8 layers), y la composición de muchas layers puede ralentizar más de
   lo que ayuda. El sticky positioning ya está optimizado en chromium
   moderno; meter translateZ no aporta y rompe inheritance del background
   en columnas sticky horizontales (mrp/plan first col sticky). */
.tbl-base thead th {
    position: sticky;
    top: var(--header-h, 56px);
    background: var(--bg-card);
    color: var(--fg-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    text-align: left;
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border);
    z-index: 5;
    white-space: nowrap;
}
.tbl-base tbody td {
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border);
    color: var(--fg);
    vertical-align: middle;
}
.tbl-base tbody tr {
    transition: background var(--dur-fast) var(--ease);
}
.tbl-base tbody tr:hover {
    background: rgba(255, 255, 255, 0.02);
}
.tbl-base.is-zebra tbody tr:nth-child(odd) {
    background: rgba(255, 255, 255, 0.015);
}
.tbl-base.is-zebra tbody tr:hover {
    background: rgba(255, 255, 255, 0.04);
}
.tbl-base td.is-num,
.tbl-base th.is-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Toolbar — barra horizontal de acciones / filtros                ║
   ╚══════════════════════════════════════════════════════════════════╝ */

.toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    flex-wrap: wrap;
}
.toolbar > .spacer { flex: 1; }


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Divider                                                         ║
   ╚══════════════════════════════════════════════════════════════════╝ */

.divider {
    height: 1px;
    background: var(--border);
    margin: var(--space-3) 0;
    border: 0;
}
.divider.is-vertical {
    width: 1px;
    height: auto;
    align-self: stretch;
    margin: 0 var(--space-2);
}


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Delta arrow inline (▲ +12% / ▼ -3.4%)                          ║
   ╚══════════════════════════════════════════════════════════════════╝
   Para deltas dentro de tabla/lista — minimalist, sin background. */

.delta {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--fg-muted);
    font-variant-numeric: tabular-nums;
}
.delta.is-up   { color: var(--ok); }
.delta.is-down { color: var(--color-danger); }
.delta.is-flat { color: var(--fg-faint); }
.delta::before {
    font-size: 9px;
    line-height: 1;
    transform: translateY(-1px);
}
.delta.is-up::before   { content: "▲"; }
.delta.is-down::before { content: "▼"; }
.delta.is-flat::before { content: "—"; }


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Tabs — switch entre vistas dentro de una página                 ║
   ╚══════════════════════════════════════════════════════════════════╝
   `.tabs` es el contenedor, `.tabs__item` cada tab. El indicador activo
   se renderiza vía CSS: el item con [aria-selected=true] gana un
   pseudo-elemento `::after` con la barra accent. Sin JS, sin clip-path
   trickery — para el caso de tabs que no se cambian via fetch (tabs
   server-rendered con state en URL/path), esta versión es la simple
   correcta. Para tabs con animation cross-state, usar la variante
   `.tabs--animated` (JS escribe transform/width al indicador externo). */

.tabs {
    display: inline-flex;
    gap: var(--space-3);
    border-bottom: 1px solid var(--border);
    position: relative;
    /* min-height 44px para que cada tab cumpla tap target WCAG en mobile
       sin necesidad de padding extra (el padding visual queda compacto). */
}
.tabs__item {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 36px;
    padding: var(--space-2) 0;
    color: var(--fg-muted);
    font-size: var(--text-sm);
    font-weight: 500;
    cursor: pointer;
    background: transparent;
    border: 0;
    font-family: inherit;
    transition: color var(--dur-base) var(--ease);
    text-decoration: none;
}
.tabs__item:hover { color: var(--fg); }
.tabs__item[aria-selected="true"],
.tabs__item.is-active {
    color: var(--fg);
    font-weight: 600;
}
.tabs__item[aria-selected="true"]::after,
.tabs__item.is-active::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 2px;
    background: var(--accent);
    border-radius: var(--radius-chip) var(--radius-chip) 0 0;
    /* Promote a su propio layer para que el indicador, cuando se mueva
       entre tabs (via cross-fade en navegación), no fuerce repaint
       del header sticky. */
    transform: translateZ(0);
}


/* ╔══════════════════════════════════════════════════════════════════╗
   ║  Mobile tap targets — WCAG 2.1 / 2.5.5                          ║
   ╚══════════════════════════════════════════════════════════════════╝
   En viewports <=1024px (breakpoint mobile canonical), aumentamos los
   targets táctiles a 44px mínimo. Aplica a botones, links de tablas,
   chips, items de sidebar — todo lo que el dedo va a tocar.

   Defínelo acá (en components shared) en vez de en cada mobile.css de
   proyecto: el patrón es universal, los proyectos no deberían tener
   que repetirlo. */

@media (max-width: 1024px) {
    .btn { min-height: 44px; padding-top: 12px; padding-bottom: 12px; }
    .btn.is-sm { min-height: 36px; padding-top: 8px; padding-bottom: 8px; }
    .tabs__item { min-height: 44px; }
    .tag {
        /* En mobile las pills suelen ser clickables (filtros). Padding
           vertical extra sin que cambie el visual significantly. */
        padding-top: 4px;
        padding-bottom: 4px;
    }
    /* Links que actúen como botones (con class .btn o role=button) ya
       cubiertos arriba. Anchors plain dentro de párrafo NO necesitan
       44px — son texto seleccionable, no tap targets en sí. */

    /* KPI cards en mobile — un poco más de padding para que el value
       quede aireado y el touch target sea cómodo si la card es
       clickable. KPI hero queda igual (ya tiene padding generoso). */
    .kpi-card { padding: var(--space-3); }
    .kpi-card__value { font-size: 28px; }

    /* Banner en mobile — padding vertical reducido (los spans suelen
       wrappear a varias líneas, no queremos doble aire). */
    .banner { padding: var(--space-2) var(--space-3); }
}

/* ── safe-area utilities (iOS notch / Dynamic Island / home indicator) ─
   Aplicar `.safe-area-bottom` a elementos fixed/sticky en el bottom
   (CTAs, mobile nav drawer, snackbars) para que respeten el home
   indicator iOS. Idem `.safe-area-top` para top bars custom (el header
   ya está cubierto por el viewport-fit=cover + padding del layout). */
.safe-area-top    { padding-top:    var(--safe-area-top, 0); }
.safe-area-bottom { padding-bottom: var(--safe-area-bottom, 0); }
.safe-area-left   { padding-left:   var(--safe-area-left, 0); }
.safe-area-right  { padding-right:  var(--safe-area-right, 0); }

/* Bottom CTA — patrón fixed-en-mobile, inline-en-desktop. Útil para
   "Guardar", "Procesar minuta", "Aprobar OC" donde la acción primaria
   debe quedar siempre alcanzable en mobile. */
.bottom-cta {
    display: flex;
    gap: var(--space-2);
    justify-content: flex-end;
    margin-top: var(--space-4);
}
@media (max-width: 1024px) {
    .bottom-cta {
        position: sticky;
        bottom: 0;
        background: linear-gradient(180deg,
                    transparent 0%,
                    var(--bg) 50%);
        padding: var(--space-3) var(--space-3) calc(var(--space-3) + var(--safe-area-bottom, 0));
        margin-left: calc(-1 * var(--space-3));
        margin-right: calc(-1 * var(--space-3));
        z-index: 50;
    }
}

/* ============================================================
   POLISH B.3 — Micro-interacciones Emil-style + K.1 Quick Wins
   ============================================================
   Todas con prefers-reduced-motion guard (heredado de motion.css).
   Aplicables sin opt-in: clases base ya enriquecidas.
*/

/* ── B.3.1 KPI card breathing hover ──────────────────────────────────── */
.kpi-card,
.kpi-hero {
    transition: transform var(--dur-base) var(--ease),
                border-color var(--dur-base) var(--ease),
                background var(--dur-base) var(--ease);
}
.kpi-card:hover {
    transform: scale(1.005);
    border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
    background: color-mix(in srgb, var(--accent) 2%, var(--bg-card, var(--bg)));
}

/* ── B.3.2 Spring on button press ────────────────────────────────────── */
.btn,
button[data-magnetic],
.btn--primary,
.btn--ghost {
    transition: transform var(--dur-spring) var(--ease-spring),
                background var(--dur-base) var(--ease),
                border-color var(--dur-base) var(--ease);
}
.btn:active,
button[data-magnetic]:active {
    transform: scale(0.96);
}

/* ── B.3.5 Focus rings ───────────────────────────────────────────────── */
:focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-ring-offset);
    border-radius: inherit;
}
.btn:focus-visible {
    outline-offset: var(--focus-ring-offset-btn);
}
/* Color signature: inputs reciben cyan, NO buttons (B.4 regla) */
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
    outline-color: var(--accent-live);
}

/* ── B.3.8 Texture sutil global ────────────────────────────────────────
   z-index:-1 + pointer-events:none asegura que noise queda debajo del
   contenido sin necesidad de tocar position/z-index de los hijos del
   body. La regla previa `body > * { position: relative; z-index: 1 }`
   rompía el position:fixed del .drawer-overlay en iOS Safari (mobile
   sidebar sin tap-to-close). */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: -1;
    background-image: url('/static/assets/noise.svg');
    background-size: 200px 200px;
    opacity: 0.015;
    mix-blend-mode: overlay;
}

/* ── B.4 Color signature electric cyan ───────────────────────────────── */
.sync-banner.is-live,
.banner.is-live,
.banner--live {
    border-color: var(--accent-live);
    background: color-mix(in srgb, var(--accent-live) 6%, transparent);
}
.live-dot {
    display: inline-block;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--accent-live);
    box-shadow: 0 0 8px var(--accent-live-soft);
    animation: live-pulse 1.4s var(--ease) infinite;
    flex-shrink: 0;
    margin-right: var(--space-1);
}
.is-active-filter::after {
    content: '';
    display: block;
    height: 2px;
    background: var(--accent-live);
    margin-top: 2px;
    border-radius: 1px;
}

/* ── K.1 #1 mono numeric (taste + ui-ux-pro-max) ─────────────────────── */
.numeric,
.kpi-card__valor,
.kpi-hero__valor,
.kpi-card__delta,
.kpi-hero__delta,
.tbl .numeric,
th.numeric, td.numeric,
.tag .numeric,
kbd {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'ss01', 'tnum';
    letter-spacing: -0.01em;
}

/* ── K.1 #2 <dialog> nativo styling ──────────────────────────────────── */
dialog {
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--bg-card, var(--bg));
    color: var(--fg);
    padding: var(--space-4);
    max-width: 560px;
    width: calc(100vw - 2 * var(--space-4));
    box-shadow: 0 24px 48px -20px rgba(0,0,0,0.5);
}
dialog::backdrop {
    background: rgba(0,0,0,0.45);
    backdrop-filter: blur(4px);
}
dialog[open] {
    animation: fade-up var(--dur-slow) var(--ease-out);
}

/* ── K.1 #4 clip-path tabs activos (gesto kinético) ──────────────────── */
.tabs {
    position: relative;
    display: flex;
    gap: 0;
    border-bottom: 1px solid var(--border);
}
.tabs__item {
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
    color: var(--fg-muted);
    border: 0;
    background: transparent;
    transition: color var(--dur-base) var(--ease);
}
.tabs__item:hover { color: var(--fg); }
.tabs__item[aria-selected="true"],
.tabs__item.is-active { color: var(--fg); }
.tabs__indicator {
    position: absolute;
    bottom: -1px;
    height: 2px;
    background: var(--accent);
    transition: clip-path var(--dur-tab) var(--ease-out),
                left var(--dur-tab) var(--ease-out),
                width var(--dur-tab) var(--ease-out);
    pointer-events: none;
}

/* ── K.1 #5 divide-y (densidad > 7, sin cards envoltorio) ─────────────── */
.divide-y > * + * {
    border-top: 1px solid var(--border);
}
.divide-y > * {
    padding: var(--space-3) 0;
}
.divide-y--tight > * { padding: var(--space-2) 0; }

/* ── K.1 #10 Asymmetric enter/exit timing ─────────────────────────────── */
.motion-hold-in {
    transition: opacity 1500ms linear, transform 1500ms linear;
}
.motion-hold-out {
    transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
}

/* ── K.3 brutalist ASCII framing markers ──────────────────────────────── */
.framing {
    display: inline-block;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--warm-700, var(--fg-faint, var(--fg-muted)));
    letter-spacing: 0.18em;
    text-transform: uppercase;
}

/* ── K.3 minimalist <kbd> teclas físicas ──────────────────────────────── */
kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background: var(--bg-soft, var(--bg));
    color: var(--fg-muted, var(--fg));
    font-family: var(--font-mono);
    font-size: 11px;
    line-height: 1;
}

/* ── K.3 soft eyebrow tags microscópicos ──────────────────────────────── */
.eyebrow {
    display: inline-block;
    padding: 2px 10px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border);
    font-family: var(--font-mono, sans-serif);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.2em;
    color: var(--warm-500, var(--fg-muted));
    background: transparent;
}

/* ── empty-state con ilustración (B.1) ────────────────────────────────── */
.empty-state--illustrated {
    text-align: center;
    padding: var(--space-5) var(--space-4);
}
.empty-state__illustration {
    width: 192px;
    height: 192px;
    margin: 0 auto var(--space-3);
    opacity: 0.85;
    filter: drop-shadow(0 1px 0 rgba(242, 169, 0, 0.04));
}

/* ── K.1 #3 Container Queries baseline ────────────────────────────────── */
.bento {
    container-type: inline-size;
    display: grid;
    gap: var(--space-3);
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
@container (min-width: 540px) {
    .bento .kpi-card--span-2 { grid-column: span 2; }
}
@container (min-width: 820px) {
    .bento .kpi-card--span-3 { grid-column: span 3; }
}

/* ── K.1 #7 Aceternity glow border conic (opt-in via .glow-border) ─────
   Para MRP cards en hover: el accent #F2A900 gira alrededor del border.
   Solo activo en pointer:fine + reduced-motion guard. */
@property --glow-angle {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: false;
}
@keyframes glow-rotate {
    from { --glow-angle: 0deg; }
    to   { --glow-angle: 360deg; }
}
.glow-border {
    position: relative;
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--bg-card, var(--bg));
}
.glow-border::before {
    content: '';
    position: absolute;
    inset: -1px;
    border-radius: inherit;
    padding: 1px;
    background: conic-gradient(
        from var(--glow-angle, 0deg),
        transparent 0deg,
        transparent 270deg,
        var(--accent) 320deg,
        transparent 360deg
    );
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    -webkit-mask-composite: xor;
    opacity: 0;
    transition: opacity var(--dur-slow) var(--ease);
    pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
    .glow-border:hover::before {
        opacity: 1;
        animation: glow-rotate 4s linear infinite;
    }
}
@media (prefers-reduced-motion: reduce) {
    .glow-border:hover::before { animation: none; }
}

/* ── MagicUI-style marquee (K.2 shadcn third-party) ───────────────────── */
@keyframes marquee {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
}
.marquee {
    overflow: hidden;
    --marquee-dur: 35s;
}
.marquee__track {
    display: inline-flex;
    gap: var(--space-4);
    animation: marquee var(--marquee-dur) linear infinite;
    will-change: transform;
}
.marquee:hover .marquee__track { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
    .marquee__track { animation: none; }
}

/* ── Terminal effect (K.2 MagicUI etoro logs) ─────────────────────────── */
.terminal {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--ok, #4ade80);
    background: var(--warm-900, var(--bg));
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: var(--space-3);
}
.terminal-caret::after {
    content: '▎';
    color: var(--accent);
    animation: pulse-soft 1s steps(2) infinite;
}

/* ── Banner live variant explicit ─────────────────────────────────────── */
.banner--live .live-dot { animation-duration: 1.2s; }

/* ── Banner ok/info/warn/err variants ─────────────────────────────────── */
.banner {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-card);
    border: 1px solid var(--border);
    background: var(--bg-soft, var(--bg));
    color: var(--fg);
    font-size: var(--text-sm);
}
.banner__msg { flex: 1; }
.banner__dismiss {
    border: 0;
    background: transparent;
    color: var(--fg-muted);
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
}
.banner--ok    { border-color: color-mix(in srgb, var(--ok, #4ade80) 50%, var(--border)); }
.banner--warn  { border-color: color-mix(in srgb, var(--warn, #f59e0b) 50%, var(--border)); }
.banner--err   { border-color: color-mix(in srgb, var(--err, #f87171) 50%, var(--border)); }
.banner--info  { border-color: var(--border); }

/* ── Tag variants ─────────────────────────────────────────────────────── */
.tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border);
    background: var(--bg-soft, transparent);
    color: var(--fg);
    font-size: var(--text-xs);
    font-weight: 500;
    line-height: 1.4;
}
.tag--with-dot .tag__dot {
    display: inline-block;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.85;
}
.tag--info    { border-color: color-mix(in srgb, var(--accent) 25%, var(--border)); color: var(--accent); }
.tag--ok      { border-color: color-mix(in srgb, var(--ok, #4ade80) 50%, var(--border)); color: var(--ok, #4ade80); }
.tag--warn    { border-color: color-mix(in srgb, var(--warn, #f59e0b) 50%, var(--border)); color: var(--warn, #f59e0b); }
.tag--err     { border-color: color-mix(in srgb, var(--err, #f87171) 50%, var(--border)); color: var(--err, #f87171); }
.tag--accent  { border-color: var(--accent); color: var(--accent); }
.tag--live    { border-color: var(--accent-live); color: var(--accent-live); }
.tag--live .tag__dot { background: var(--accent-live); box-shadow: 0 0 6px var(--accent-live-soft); }

/* ── KPI card variant ─────────────────────────────────────────────────── */
.kpi-card__delta.is-positive { color: var(--ok, #4ade80); }
.kpi-card__delta.is-negative { color: var(--err, #f87171); }
.kpi-hero__delta.is-positive { color: var(--ok, #4ade80); }
.kpi-hero__delta.is-negative { color: var(--err, #f87171); }

/* ── icon system (Lucide-style overrides) ─────────────────────────────── */
.icon {
    width: 20px;
    height: 20px;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke: currentColor;
    fill: none;
    flex-shrink: 0;
}
.icon--sm { width: 16px; height: 16px; }
.icon--lg { width: 24px; height: 24px; }
.icon--xl { width: 32px; height: 32px; }

/* Cockpit redesign block movido a polish.css (2026-05-29) — polish.css
   carga DESPUÉS de style.css local en cada servicio, así la cascada gana
   sobre `.sidebar nav a { font-size: 13.5px }` y otras reglas locales
   con misma specificity. Mantener este file libre de overrides que
   colisionen con style.css. */

