/* interaccion.css — capa de estados y tokens v2 de la flota (homogeneización visual, 15-oct-2026).

   FUENTE ÚNICA: services/_shared-ui/static/interaccion.css. Se copia tal cual a las 14 apps con
   tema claro/oscuro (NO al MES, mes-espejo, catalogo, cocheras ni scentcanvas) y se enlaza
   DESPUÉS de tema.css y ANTES de <app>-claro.css, así gana por ORDEN contra la Capa A, polish.css
   y el CSS de cada app a igual especificidad, y el claro de cada app la sigue pisando.

   Revisión adversarial de la cascada (opus, 15-oct 12:00) aplicada: isla de logistics y Litepicker,
   pill-toggle de la isla, radio de foco = radio de reposo, disabled sin tocar grillas, /ventas del CRM,
   tinte de fila en claro, press sin deshabilitados, movimiento reducido completo.
   Decisiones del dueño (15-oct-2026, láminas D1-D3 sobre páginas reales):
     D1-B superficies: la tarjeta queda POR ENCIMA de la página (antes era un pozo más oscuro).
     D2-C campos: borde de reposo visible (>= 3:1) y foco pegado (borde dorado + 1 px del mismo
          color, sin anillo separado).
     D3-B radios: control 8 · tarjeta 12 · pill 999. La isla de filtros conserva los suyos.

   RONDA 2 (15-oct-2026 tarde, láminas D5-D13 sobre páginas reales; §4 a §11):
     D5-C  tarjeta sólida por encima de la página en las 14 (sin vidrio, blur, halo ni degradé en el hero).
     D6-D  dorado plano en primario / «Actualizar» / solapas / barras de progreso; solapa activa de los
           gráficos en tinte con contorno; primario con relieve.
     D7-A  ítem activo del sidebar COMO HOY. EXCEPCIÓN DEL DUEÑO sobre D6 y D13: ni se aplana ni se tiñe
           ni pasa a «tecla blanca» (ver §5 y §11).
     D8-C  toda cifra en Inter tabular; mono sólo en códigos, kbd, grillas editables y planillas de mrp.
     D9-B  misma piel de header (fondo = página, hairline como sombra, título 600) + logo centrado.
     D10-C un solo lenguaje de tabla.   D11-C un solo molde de pastilla de estado.
     D12-C ritmo 24/24/32, lateral 32, techo 24 (con exclusiones).   D13-C claro con papel con jerarquía.
   Lo que va en la hoja de cada app (no acá): el +16 px de max-width de deposito/design/lims (D12), la
   excepción de cashflow del logo (<1440 px, a la izquierda) y el logo nuevo de mrp/logistics/etoro (D9).

   Reglas de la capa:
     · Todo cuelga de @media screen: la impresión y los PDF quedan exactamente como estaban.
     · No toca caja, salvo la ronda 2 por decisión del dueño: §10 (D12) fija padding y margin de
       .content y de las tarjetas; el resto (D5-D11, D13) no mueve el layout: líneas como sombra inset,
       colores y familia tipográfica, nunca width/height/gap/font-size/line-height.
     · Sólo se transicionan color, background-color, border-color, box-shadow, opacity y transform.
     · Sin !important. El anillo cian !important de polish.css:84-90 se apaga redefiniendo la
       variable que lee (--accent-live) EN el campo enfocado.
     · Los hover van bajo (hover: hover) and (pointer: fine): nada queda pegado en táctil.
   Contrastes (WCAG, oscuro, sobre página #0f0f11 · tarjeta #18181b · soft #1d1d21 · elev #212125
   · elev-2 #29292e): --fg-faint #8f8f8f = 5,92 · 5,48 · 5,20 · 4,96 · 4,48; --field-border
   #74747c = 4,13 · 3,82 · 3,63 · 3,46 · 3,12; acento #F2A900 = 9,53 · 8,82 · 8,36 · 7,98 · 7,20.
   Claro: --field-border #8a857a = 3,31 sobre el papel #f4f3ef y 3,67 sobre blanco. */

@media screen {

/* ── §0 Tokens v2 ────────────────────────────────────────────────────────────────────
   Oscuro en :root (0,1,0); el claro lo fija tema.css con :root[data-tema="claro"] (0,2,0),
   que gana por especificidad. Todo token de superficie de acá tiene su par claro en tema.css. */
:root {
    /* motion: 150 ms (color, hover) y 200 ms (paneles, solapas). Sin rebote: el spring queda
       como alias de --ease-out para no editar polish.css/components.css (la Capa A llega al MES). */
    --dur-base: 150ms;
    --dur-slow: 200ms;
    --dur-tab: 200ms;
    --ease-spring: var(--ease-out);
    --dur-spring: var(--dur-base);

    /* superficies D1-B: página más oscura, tarjeta un paso más clara */
    --bg: #0f0f11;
    --bg-soft: #1d1d21;
    --bg-card: #18181b;
    --cockpit-surface: #18181b;
    --surface-elev: #212125;
    --surface-elev-2: #29292e;
    /* hairlines: con la tarjeta más clara, #1f1f1f quedaba en 1,07:1 y las tablas/chips perdían
       sus divisores. #28282d = 1,21:1 sobre la tarjeta (lo que tenían antes sobre el pozo, 1,2:1). */
    --border: #28282d;

    /* texto tenue legible (AA) sobre todas las superficies; antes #7d7d7d (3,5 a 4,6:1) */
    --fg-faint: #8f8f8f;

    /* rojo de error: con las superficies D1-B, #ef4444 quedaba en 4,46 sobre --bg-soft (etiqueta
       «Rechazado» de LIMS). #f05252 = 5,50 página · 5,09 tarjeta · 4,83 soft · 4,61 elev. Un solo rojo:
       --color-danger (#e05252) pasa a ser el mismo. El claro sigue con #b42318 (tema.css). */
    --err: #f05252;
    --color-danger: var(--err);

    /* acento: los pares que sólo existían en claro (tema.css:118-119) */
    --accent-fill: var(--accent);
    --accent-ink: var(--accent);

    /* foco: un solo color en toda la flota (el cian queda sólo para lo «en vivo») */
    --focus-color: var(--accent);
    --focus-ring: 2px solid var(--focus-color);

    /* campos (D2-C): cada app lo aplica en SU regla de campo */
    --field-border: #74747c;
    --field-border-hover: #9a9aa2;
    --focus-field-shadow: 0 0 0 1px var(--focus-color);

    /* hover, filas y elevación */
    --hover-tint: color-mix(in srgb, var(--fg) 7%, transparent);
    --row-hover: color-mix(in srgb, var(--accent) 6%, transparent);
    --shadow-1: inset 0 1px 0 rgba(255, 255, 255, 0.04);
    --shadow-2: 0 6px 16px -4px rgba(0, 0, 0, 0.5);
    --shadow-3: var(--shadow-pop);
    --disabled-opacity: 0.5;

    /* radios D3-B (la isla de filtros los fija aparte, más abajo) */
    --radius-btn: 8px;
    --radius-card: 12px;

    /* alturas de control: definidas para uso futuro, NO aplicadas (cambiarían el layout) */
    --control-h-sm: 32px;
    --control-h-md: 36px;
    --control-h-touch: 44px;
}
:root[data-tema="claro"] {
    --field-border: #8a857a;
    --field-border-hover: #5f5b51;
    --row-hover: color-mix(in srgb, var(--accent-fill) 10%, transparent);
    /* D13-C: neutros y sombras sepia (hover, tarjeta en reposo y en hover) y el papel hondo del sidebar */
    --hover-tint: rgba(60, 40, 0, 0.05);
    --shadow-1: 0 1px 2px rgba(60, 40, 0, 0.07), 0 1px 0 rgba(60, 40, 0, 0.03);
    --shadow-2: 0 1px 2px rgba(60, 40, 0, 0.06), 0 8px 20px -10px rgba(60, 40, 0, 0.24);
    --bg-rail: #e7e4dc;   /* RIEL-C (dueño, 16-oct 09:08): un paso más hondo que --bg-soft #ecebe5, así el ítem activo (como hoy) se recorta */
}

/* La isla de filtros (tarjeta sticky: Período · Filtros · pills, y sus popovers) queda IDÉNTICA:
   conserva los radios que tenía antes de esta capa. */
:is(.fb, .fb-popover, .fb-dim, .filter-island, .toolbar-seccion, .isla-filtros, .cp-isla,
    .isla, .litepicker) {
    --radius-btn: 6px;
    --radius-card: 8px;
}

/* ── §1 Estados ────────────────────────────────────────────────────────────────────── */

/* 1.1 Transición única de lo accionable. Reemplaza el spring de polish.css:61-73,
       components.css:723-730 y la copia de cashflow/logistics components.css:95-101. */
.btn, .btn--primary, .btn--ghost, .btn--secondary, .btn--danger,
button.refresh, .cmd-palette-trigger, button[data-magnetic],
.pill, .chip, .tabs__item, .tema-toggle, .sidebar-salir__btn,
[role="tab"]:where(:not(.fb *, .isla-filtros *, .toolbar-seccion *, .cp-isla *)),
button[aria-pressed]:where(:not(.fb *, .isla-filtros *, .toolbar-seccion *, .cp-isla *)) {
    transition: color var(--dur-base) var(--ease),
                background-color var(--dur-base) var(--ease),
                border-color var(--dur-base) var(--ease),
                box-shadow var(--dur-base) var(--ease),
                opacity var(--dur-base) var(--ease),
                transform var(--dur-base) var(--ease-out);
}
/* campos: selector de elemento (0,0,1); si la app ya declara su transition, gana la suya */
:where(input, select, textarea):where(:not(.fb *, .fb-popover *, .isla-filtros *, .toolbar-seccion *, .cp-isla *)) {
    transition: border-color var(--dur-base) var(--ease),
                box-shadow var(--dur-base) var(--ease),
                background-color var(--dur-base) var(--ease);
}
/* tarjetas: sólo color y sombra (sin el scale de polish.css:52-58) */
.card, .kpi-card, .chart-card {
    transition: border-color var(--dur-base) var(--ease),
                box-shadow var(--dur-base) var(--ease),
                background-color var(--dur-base) var(--ease),
                transform var(--dur-base) var(--ease-out);
}

/* 1.2 Press: corto (100 ms) y suelta en 150 ms. Sólo controles: nunca filas ni campos. */
.btn:active, .pill:active, .chip:active, .tabs__item:active,
button.refresh:active, .cmd-palette-trigger:active, [role="tab"]:active, button[aria-pressed]:active {
    transition-duration: var(--dur-fast);
}
:is(.btn, button.refresh, .cmd-palette-trigger):active:not(:disabled, [aria-disabled="true"], .is-disabled) {
    transform: scale(0.97);
}

/* 1.3 Hover (sólo puntero fino) */
@media (hover: hover) and (pointer: fine) {
    /* ghost compartido: el mismo sentido en los dos temas (hacia el texto) */
    .btn.is-ghost:hover, .btn--ghost:hover {
        background-color: var(--hover-tint);
        color: var(--fg);
    }
    /* default para chips/solapas que no tienen hover propio (0,0,0: cualquier regla de la app gana) */
    :where(.chip, .tabs__item, [role="tab"], button[aria-pressed]):where(:hover:not(:disabled, [aria-disabled="true"], [aria-selected="true"], [aria-pressed="true"])) {
        background-color: var(--hover-tint);
    }
    /* tarjetas ACCIONABLES: un paso de borde + sombra corta. Las de lectura no reaccionan. */
    a.card:hover, .card.is-interactive:hover, a.kpi-card:hover, .card[role="link"]:hover {
        border-color: var(--border-hover);
        box-shadow: var(--shadow-2);
    }
    /* filas de tabla: tinte como background-image para no pisar el fondo propio de la celda
       (tintes por tipo, celdas sticky opacas). Sin transición: se usan cientos de veces por día.
       D10: el mismo tinte en todas las tablas de la flota (antes sólo .tbl y .tbl-base). */
    :is(.tbl, .tbl-base, .tabla, .lista-tabla, .plan-tabla, .tabla-simple) > tbody > tr:hover > :is(td, th) {
        background-image: linear-gradient(var(--row-hover), var(--row-hover));
    }
    /* claro: tema.css:299 `:root[data-tema="claro"] .tbl tr:hover td { background: … }` (0,4,2) borra la imagen;
       el `html` suma uno para ganarle también a logistics-claro.css (`.tabla tbody tr:hover td`, 0,4,3) */
    html:root[data-tema="claro"] :is(.tbl, .tbl-base, .tabla, .lista-tabla, .plan-tabla, .tabla-simple) > tbody > tr:hover > :is(td, th) {
        background-image: linear-gradient(var(--row-hover), var(--row-hover));
    }
}
/* KPI de lectura: sin el scale de polish.css:52-58 / components.css:717-721 */
.kpi-card:hover { transform: none; }

/* 1.4 Foco de teclado — un solo color.
   (a) Todo campo enfocado: el outline !important de polish.css:84-90 lee --accent-live; acá se
       vuelve dorado, así ningún campo queda con el cian. Campos sin borde (buscador de la isla,
       grillas editables) conservan ese anillo como único indicador. (0,1,0) */
:where(input, select, textarea):focus-visible {
    --accent-live: var(--focus-color);
}
/* (a2) Radio de reposo por defecto (0,0,0): sólo lo toman los campos SIN regla propia (UA, radio 0),
       así el radio de foco de (b) coincide con el de reposo. */
:where(input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"], [type="color"],
                 [type="hidden"], [type="submit"], [type="button"], [type="reset"], [type="image"]),
       select, textarea):where(:not(.grilla-editable *, .fb *, .isla-filtros *, .toolbar-seccion *, .cp-isla *)) {
    border-radius: var(--radius-btn);
}
/* (b) D2-C en los campos CON borde: borde dorado + 1 px pegado, sin anillo separado (el outline
       queda transparente: no ocupa lugar y Windows lo pinta en modo de alto contraste). Las esquinas
       vuelven a su radio (components.css:741 y polish.css:89 forzaban `border-radius: inherit`).
       Especificidad (0,1,1) y después de polish: gana a `input:focus-visible` de polish.css; las
       reglas de foco propias de cada app (>= (0,2,0)) siguen ganando y se ajustan app por app. */
input:where(:not([type="checkbox"], [type="radio"], [type="range"], [type="file"], [type="color"],
               [type="hidden"], [type="submit"], [type="button"], [type="reset"], [type="image"],
               .pill-search__input, [data-fb-custom-input], .grilla-editable *, .fb *,
               .isla-filtros *, .toolbar-seccion *, .cp-isla *)):focus-visible,
select:where(:not(.grilla-editable *, .fb *, .isla-filtros *, .toolbar-seccion *, .cp-isla *)):focus-visible,
textarea:where(:not(.grilla-editable *, .fb *, .isla-filtros *, .toolbar-seccion *, .cp-isla *)):focus-visible {
    --accent-live: transparent;
    border-color: var(--focus-color);
    box-shadow: var(--focus-field-shadow);
    border-radius: var(--radius-btn);
}

/* (c) el buscador píldora de la isla LIMS: polish.css:89 lo cuadraba al enfocar (999 -> 0) */
.isla-filtros__busqueda input:focus-visible { border-radius: var(--radius-pill); }

/* 1.5 Deshabilitado: legible, sin hover, cursor claro. NO se quita el pointer-events:none de
       components.css:319 (es lo único que frena el clic en un <a class="btn" aria-disabled>). */
:where(input, select, textarea):where(:disabled):where(:not(.grilla-editable *)) {
    opacity: var(--disabled-opacity);
    cursor: not-allowed;
}
:where(.chip, .pill, [role="tab"], button[aria-pressed]):where(:disabled, [aria-disabled="true"]) {
    opacity: var(--disabled-opacity);
    cursor: not-allowed;
}

/* 1.6 Cargando (aria-busy, ya lo ponen mrp, crm y cashflow): barrido tenue de fondo, sin cambiar
       ancho ni texto. button.refresh tiene el suyo. */
.btn[aria-busy="true"]:not(.refresh) {
    cursor: progress;
    background-image: linear-gradient(100deg, transparent 30%,
                      color-mix(in srgb, currentColor 16%, transparent) 50%, transparent 70%);
    background-size: 220% 100%;
    background-repeat: no-repeat;
    animation: ia-ocupado 1.1s linear infinite;
}
@keyframes ia-ocupado { from { background-position: 120% 0; } to { background-position: -120% 0; } }

/* 1.7 Error: el mismo rojo en el borde; el mensaje lo pone cada app (no se toca el DOM). */
:is(input, select, textarea)[aria-invalid="true"] {
    border-color: var(--err);
}
:is(input, select, textarea)[aria-invalid="true"]:focus-visible {
    box-shadow: 0 0 0 1px var(--err);
}

/* 1.8 Nombre de la app en el sidebar sin texto con gradiente (chrome.css:30-34 y su copia por
       app; tema.css:416-420 en claro). Dorado plano: en claro es la tinta #855900. */
.sidebar .brand .accent {
    background: none;
    -webkit-text-fill-color: currentColor;
    color: var(--accent);
}
:root[data-tema="claro"] .sidebar .brand .accent {
    background: none;
    -webkit-text-fill-color: currentColor;
}

/* ── §2 Forma (D3-B) ──────────────────────────────────────────────────────────────────
   polish.css:188, 342-353 fijaba radios literales (tarjetas 16, botones 12, pills 14) que
   contradecían los tokens. Acá vuelven a los tokens. Lo de la isla de filtros (.fb-*,
   .vistas-save__btn, .filtro-chip-activo) NO está en esta lista: queda como estaba. */
.card, .kpi-card, .chart-card, .chart-card--premium, .feed-alertas, .resumen-list, .sync-card,
.modal-panel, .placeholder {
    border-radius: var(--radius-card);
}
.btn, .toggle-link {
    border-radius: var(--radius-btn);
}
.pill-toggle:where(:not(:is(.fb, .toolbar-seccion, .isla-filtros, .cp-isla, .filter-island) *)),
.pill-toggle:where(:not(:is(.fb, .toolbar-seccion, .isla-filtros, .cp-isla, .filter-island) *)) .pill {
    border-radius: var(--radius-pill);
}


/* crm /ventas: ventas.css se carga DESPUÉS de la capa (crm base.html:188-194) con literales 16/14/11. */
.page-ventas:not(.page-ventas *) :is(.card, .kpi-card, .chart-card, .chart-card--premium, .sync-card, .modal-panel) {
    border-radius: var(--radius-card);
}
.page-ventas .pill-toggle:where(:not(:is(.fb, .toolbar-seccion, .isla-filtros, .cp-isla, .filter-island) *)),
.page-ventas .pill-toggle:where(:not(:is(.fb, .toolbar-seccion, .isla-filtros, .cp-isla, .filter-island) *)) .pill {
    border-radius: var(--radius-pill);
}

/* ── §3 Movimiento reducido: sin transform ni barrido (motion.css:14-23 ya lleva las duraciones
       a ~0); el foco y los cambios de color siguen. ─────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    :is(.btn, button.refresh, .cmd-palette-trigger):active:not(:disabled, [aria-disabled="true"], .is-disabled),
    .btn--primary:active, .pill:active, button[data-magnetic]:active { transform: none; }
    .data-card:hover, .kpi-card:hover, .card:hover { transform: none; }
    .btn[aria-busy="true"]:not(.refresh) { animation: none; background-image: none; opacity: 0.75; }
}


/* ══════════════════════════════════════════════════════════════════════════════════════
   RONDA 2 (15-oct-2026 tarde) — decisiones del dueño D5 a D13, una sección por decisión.
   Especificidad: el claro va con `html:root[data-tema="claro"]` (0,2,1) porque <app>-claro.css
   carga DESPUÉS de esta capa; tema.css carga antes y se le gana por orden. Sin !important.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── §4 Tarjeta sólida por encima de la página (D5-C) ─────────────────────────────────────
   Decisión del dueño: en las 14 apps la tarjeta es sólida y queda POR ENCIMA de la página (D1-B de
   verdad): sin vidrio, sin blur, sin halo y sin degradé dorado en el hero. La red de fondo (reports,
   meetings, wapp) sigue, pero sólo se ve ENTRE tarjetas.
   NO cambia: botones ghost e inputs (--border-elev y --shadow-glass-card NO se redefinen: los leen
   .btn.is-ghost, los campos y el modal); la KPI .kpi-card--accent de crm conserva su borde dorado y
   .kpi-alerta el de peligro; .area-card (border-top de 3 px con el color del área); la isla de filtros.
   Un solo bloque sirve a los dos temas: la sombra sale de --shadow-1/--shadow-2 (sepia en claro, §0).
   --glass-bg no se toca: sólo lo lee el bottom-nav del CRM (las tarjetas no lo usan). */
:root {
    --card-glass: var(--bg-card);          /* en claro --bg-card es #fff: no hace falta un par */
    --card-glass-content: var(--bg-card);
    --card-glass-blur: none;
    --ambient-accent: none;                /* el halo existía para que el vidrio «refracte» */
}
/* tema.css fija --ambient-accent también en `body` (0,2,1): sin esto la herencia desde html no llega */
html:root[data-tema="claro"], html:root[data-tema="claro"] body { --ambient-accent: none; }
/* Borde y sombra DIRECTOS en la tarjeta, en reposo. Con puntero fino el :hover queda afuera de esta regla
   (:not(:hover)) para no pisar el borde de las tarjetas accionables ni los hover propios de cada app;
   en táctil (sin hover real) el :hover queda PEGADO después de un toque, así que ahí la regla de reposo
   no lo excluye y la tarjeta vuelve al reposo: nada queda marcado. (0,3,2)
   Borde con significado, que NO se pisa: .kpi-alerta, .kpi-card--accent y [class*="kpi-card--"] (menos
   los de ancho .kpi-card--span-*), .card--accent (crm /ventas), el semáforo .kpi-card.is-ok/.is-warn/
   .is-err/.is-danger/.is-neutro (briefing). El :where() de las variantes suma 0: la regla sigue en (0,3,2).
   Todas llevan la sombra de reposo, también .kpi-alerta y .kpi-card--accent: con la sombra vieja en reposo y
   --shadow-1 en el hover de §12 la KPI «saltaba» al pasar el puntero (se medía en crm /). */
@media (hover: hover) and (pointer: fine) {
    html body :is(.card, .kpi-card, .sync-card, .chart-card, .chart-card--premium, .section-block,
                  .cockpit-hero__card, .data-panel):not(.kpi-alerta, .kpi-card--accent):not(:hover):where(:not(:has(.tbl-scroll))) {
        box-shadow: var(--shadow-1);
    }
    html body :is(.kpi-alerta, .kpi-card--accent):is(.card, .kpi-card):not(:hover) {
        box-shadow: var(--shadow-1);
    }
    html body :is(.card, .kpi-card, .sync-card, .chart-card, .chart-card--premium, .section-block,
                  .cockpit-hero__card, .data-panel):not(.kpi-alerta, .kpi-card--accent):not(:hover):where(:not(
                  :has(.tbl-scroll), [class*="kpi-card--"]:not([class*="kpi-card--span"]), .card--accent,
                  .kpi-card:is(.is-ok, .is-warn, .is-err, .is-danger, .is-neutro))) {
        border-color: var(--border);
    }
    /* hover: sombra corta en todas; el borde pasa a --border-hover (sin esto volvería al --border-elev
       de polish.css y la tarjeta «saltaría» a un hairline blanco). Alerta, acento y semáforo conservan su borde. */
    html body :is(.card, .kpi-card, .sync-card, .chart-card, .chart-card--premium, .section-block,
                  .cockpit-hero__card, .data-panel):hover:where(:not(:has(.tbl-scroll))) {
        box-shadow: var(--shadow-2);
    }
    html body :is(.card, .kpi-card, .sync-card, .chart-card, .chart-card--premium, .section-block,
                  .cockpit-hero__card, .data-panel):not(.kpi-alerta, .kpi-card--accent):hover:where(:not(
                  :has(.tbl-scroll), [class*="kpi-card--"]:not([class*="kpi-card--span"]), .card--accent,
                  .kpi-card:is(.is-ok, .is-warn, .is-err, .is-danger, .is-neutro))) {
        border-color: var(--border-hover);
    }
}
@media not ((hover: hover) and (pointer: fine)) {
    html body :is(.card, .kpi-card, .sync-card, .chart-card, .chart-card--premium, .section-block,
                  .cockpit-hero__card, .data-panel):not(.kpi-alerta, .kpi-card--accent):where(:not(:has(.tbl-scroll))) {
        box-shadow: var(--shadow-1);
    }
    html body :is(.kpi-alerta, .kpi-card--accent):is(.card, .kpi-card) {
        box-shadow: var(--shadow-1);
    }
    html body :is(.card, .kpi-card, .sync-card, .chart-card, .chart-card--premium, .section-block,
                  .cockpit-hero__card, .data-panel):not(.kpi-alerta, .kpi-card--accent):where(:not(
                  :has(.tbl-scroll), [class*="kpi-card--"]:not([class*="kpi-card--span"]), .card--accent,
                  .kpi-card:is(.is-ok, .is-warn, .is-err, .is-danger, .is-neutro))) {
        border-color: var(--border);
    }
}
/* hero de reports/meetings/crm: sólido, sin el anillo de luz ni el degradé dorado de su ::before */
body .cockpit-hero__card { background: var(--bg-card); }
body .cockpit-hero__card::before { display: none; }

/* ── §5 Dorado de acción (D6-D) y sidebar (D7-A) ──────────────────────────────────────────
   D6-D: el relleno dorado queda PLANO en el primario, «Actualizar», las solapas activas de los
   gráficos y las barras de progreso; la solapa activa pasa a tinte 16 % + contorno + texto dorado
   (tinta en claro); el primario gana relieve (filo de luz arriba y contorno un tono más oscuro) y
   pierde el resplandor dorado. Todo sale del token --accent-grad, que ahora vale el dorado plano.
   EXCEPCIÓN DEL DUEÑO (D7-A): el ÍTEM ACTIVO DEL SIDEBAR queda COMO HOY en los dos temas, con su barra
   de degradé y resplandor y su fondo: .sidebar re-declara el degradé EXACTO de hoy (oscuro: base.css,
   claro: tema.css), así que ni se aplana ni se tiñe. Tampoco cambia la isla de filtros.
   Barras de progreso en claro: el dorado plano #F2A900 da 1,8:1 sobre el papel; usan la tinta
   #855900 (5,1 a 6,1:1). */
:root { --accent-grad: var(--accent); }
/* tema.css fija --accent-grad también en `body` (0,2,1): se le gana con (0,2,2) */
html:root[data-tema="claro"], html:root[data-tema="claro"] body { --accent-grad: var(--accent-fill); }
:is(.sidebar, .fb, .fb-popover, .fb-dim, .filter-island, .toolbar-seccion, .isla-filtros, .cp-isla,
    .isla, .litepicker) {
    --accent-grad: linear-gradient(135deg, #FFCC00 0%, #F2391C 100%);
}
html:root[data-tema="claro"] :is(.sidebar, .fb, .fb-popover, .fb-dim, .filter-island, .toolbar-seccion,
                                 .isla-filtros, .cp-isla, .isla, .litepicker) {
    --accent-grad: linear-gradient(135deg, #f2a900 0%, #e8590c 100%);
}
html:root[data-tema="claro"] :is(.ld-emb-barra, .progress__bar, .sow-bar__fill) {
    background: var(--accent);
}
/* primario y «Actualizar»: plano con relieve (la isla conserva el suyo) */
body :is(.btn.is-primary, .btn--primary):where(:not(:is(.fb, .fb-popover, .fb-dim, .filter-island, .toolbar-seccion, .isla-filtros, .cp-isla, .isla, .litepicker) *)),
body .header button.refresh {
    background: var(--accent-fill);
    border-color: transparent;
    color: #1a0d00;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.30),
                0 0 0 1px color-mix(in srgb, var(--accent-fill) 72%, #000);
}
@media (hover: hover) and (pointer: fine) {
    /* 12 % más oscuro, sin filtro. El segundo selector le gana a tema.css:398 (hover del primario en claro) */
    body :is(.btn.is-primary, .btn--primary):where(:not(:is(.fb, .fb-popover, .fb-dim, .filter-island, .toolbar-seccion, .isla-filtros, .cp-isla, .isla, .litepicker) *)):hover,
    html:root[data-tema="claro"] body :is(.btn.is-primary, .btn--primary):where(:not(:is(.fb, .fb-popover, .fb-dim, .filter-island, .toolbar-seccion, .isla-filtros, .cp-isla, .isla, .litepicker) *)):hover,
    body .header button.refresh:hover {
        background: color-mix(in srgb, var(--accent-fill) 88%, #000);
        filter: none;
    }
    /* sin el levantado de 1 px de chrome.css:270, pero el press (§1.2) se conserva: por eso :not(:active) */
    body :is(.btn.is-primary, .btn--primary):where(:not(:is(.fb, .fb-popover, .fb-dim, .filter-island, .toolbar-seccion, .isla-filtros, .cp-isla, .isla, .litepicker) *)):hover:not(:active),
    body .header button.refresh:hover:not(:active) {
        transform: none;
    }
}
/* solapa activa de los gráficos: tinte + contorno. :root x3 = (0,6,1) gana a tema.css:386
   (.pill--secondary.pill--active en claro, 0,6,0) y a chrome.css:541, así primarias y secundarias de
   una misma barra quedan iguales. Contorno >= 3:1: 60 % en oscuro (3,9-4,0:1), 90 % de la tinta en
   claro (3,9-5,0:1). */
:root:root:root body .pill-toggle:where(:not(:is(.fb, .toolbar-seccion, .isla-filtros, .cp-isla, .filter-island) *)) .pill.pill--active {
    background: color-mix(in srgb, var(--accent-fill) 16%, transparent);
    color: var(--accent);
    border-color: transparent;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-fill) 60%, transparent);
    font-weight: 600;
}
html:root[data-tema="claro"]:root:root body .pill-toggle:where(:not(:is(.fb, .toolbar-seccion, .isla-filtros, .cp-isla, .filter-island) *)) .pill.pill--active {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 90%, transparent);
}
@media (hover: hover) and (pointer: fine) {
    :root:root:root body .pill-toggle:where(:not(:is(.fb, .toolbar-seccion, .isla-filtros, .cp-isla, .filter-island) *)) .pill.pill--active:hover {
        background: color-mix(in srgb, var(--accent-fill) 22%, transparent);
        filter: none;
    }
}

/* ── §6 Cifras en Inter tabular (D8-C) ────────────────────────────────────────────────────
   Decisión del dueño: TODA cifra en Inter tabular (KPI, importes, cantidades, porcentajes y totales
   de tabla). La mono queda SÓLO para códigos (.mono, td.mono, code, «DMF03»), kbd, los campos de las
   grillas editables (cashflow /lineas) y las planillas de mrp (.plan-tabla: en Inter una celda de
   «meses» se recorta, medido). Hoy polish.css y cada app fuerzan .num/.numeric a mono.
   Declarado y sin arreglo: en crm /clientes un nombre largo («DISTRIBUIDORA MDH BAHIA S.R.L») pasa a
   2 líneas (+20 px) al ensancharse la columna numérica; un min-width no entra sin desbordar. */
body :is(.kpi-card__valor, .kpi-card__value, .kpi-hero__valor, .kpi-hero__value,
         .cockpit-hero__value, .data-card__valor, .card .value) {
    font-family: var(--font-sans, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Geist Sans", Roboto, sans-serif);
    font-weight: 600;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum' 1, 'cv11' 1;
}
body :is(.numeric, .num, .total-col, .total-mes, .total-mes-col, .fila-total-horas,
         .fila-total-equipo, .dia-hs-total, .tbl td.num, th.numeric, td.numeric, .tag .numeric,
         .cockpit-hero__delta, .data-card__delta, .kpi-card__delta, .kpi-hero__delta,
         .alerta__valor, .impacto-valor, .breadcrumb-chip__valor, [data-tween], [data-kpi],
         .wapp-volumen__num, .wapp-volumen__lim, .cell-ventana, .tb-time):where(:not(.plan-tabla *, :is(.grilla-editable, .sueldos-grilla) :is(input, select, textarea))) {
    font-family: var(--font-sans, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Geist Sans", Roboto, sans-serif);
    letter-spacing: 0;
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum' 1, 'cv11' 1;
}
/* .proxima-hero__num (meetings, «#30») queda en mono A PROPÓSITO: es un código de reunión, no una cifra. */
/* el código bajo el nombre (crm /clientes «DMF03») es un código, no una cifra: sigue en mono */
html body .clientes-sub.numeric {
    font-family: var(--font-mono, 'JetBrains Mono', ui-monospace, monospace);
    font-feature-settings: normal;
}

/* ── §7 Header: una sola piel + logo centrado (D9-B) ──────────────────────────────────────
   Decisión del dueño: misma piel en las 14 (hoy hay 7 fondos, dos translúcidos): fondo = la página,
   hairline inferior dibujado como sombra de 1 px (el borde queda transparente: no suma caja) y título
   en 600 con -0,022 em (hoy 600/650/700). NO cambia alturas ni tamaños. En claro el header queda sobre
   el papel de la página (D13); el segundo selector le gana a tema.css y a los <app>-claro.css
   (mrp-claro.css:129 pinta .header). */
body .header,
html:root[data-tema="claro"] body .header {
    background: var(--bg);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border-bottom-color: transparent;
    box-shadow: 0 1px 0 var(--border);
}
body .header h1 {
    font-weight: 600;
    letter-spacing: -0.022em;
}
/* Logo centrado (pedido del dueño, 15-oct 17:24): el enlace .header-brand pasa al centro del header en
   las apps que tienen logo. Sin DOM nuevo: absoluto dentro del header (sticky = posicionado); la imagen
   conserva su transform propio (el colapso por scroll escala la <img>, no el enlace). Excluye
   .header:has(.header__actions): reports ya lo centra con su grilla. mrp lo oculta a propósito; logistics
   y etoro no lo tienen. Sólo desde 1280 px: debajo, el logo queda en el flujo (a la izquierda, como hoy);
   centrado entre 1025 y ~1215 px tapaba el «Buscar ⌘K» de meetings, el buscador rápido de design y los
   títulos de lims. Las excepciones por app (cashflow, logistics, lims) van en su hoja. */
@media (min-width: 1280px) {
    body .header:not(:has(.header__actions)) > .header-brand {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        margin: 0;
        z-index: 1;
    }
}

/* ── §8 Un solo lenguaje de tabla (D10-C) ─────────────────────────────────────────────────
   Decisión del dueño: cabecera con el MISMO fondo opaco en toda la flota (--bg-soft; hoy hay 3 recetas)
   y una línea inferior más firme que los divisores; divisores de fila un 30 % más tenues; la última fila
   sin línea contra el borde de la tarjeta; la fila de totales en 600 con filete superior. Las líneas van
   como sombra inset o color de borde existente: no suman caja. El hover de fila está en §1.3.
   El total de cashflow /lineas no es tfoot ni tr.total: esta regla no lo alcanza (declarado).
   costeador /lista conserva su cebra. El segundo selector de la cabecera (claro) le gana a las
   sombras de cabecera sticky de costeador-claro / mrp-claro y al fondo de logistics-claro (sólo
   fondo y sombra: ver abajo). */
:is(.tbl, .tbl-base, .tabla, .lista-tabla, .plan-tabla, .tabla-simple) > thead > tr > th {
    font-weight: 500;
    letter-spacing: 0.05em;
    color: var(--fg-muted);
    background-color: var(--bg-soft);
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--fg) 16%, var(--bg-soft));
}
/* claro: SÓLO fondo y sombra llevan el peso extra (0,3,4) que necesitan para ganarle a mrp-claro /
   costeador-claro / logistics-claro. Tipografía y color quedan en la regla base (0,1,3), así los estados
   de cada app (columna ordenada, total del mes, th.num, feriado) ganan igual en los dos temas. */
html:root[data-tema="claro"] :is(.tbl, .tbl-base, .tabla, .lista-tabla, .plan-tabla, .tabla-simple) > thead > tr > th {
    background-color: var(--bg-soft);
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--fg) 16%, var(--bg-soft));
}
:is(.tbl, .tbl-base, .tabla, .lista-tabla, .tabla-simple) > tbody > tr > :is(td, th) {
    border-bottom-color: color-mix(in srgb, var(--border) 70%, transparent);
}
:is(.tbl, .tbl-base, .tabla, .tabla-simple) > tbody > tr:last-child > :is(td, th) {
    border-bottom-color: transparent;
}
:is(.tbl, .tbl-base, .tabla, .lista-tabla, .tabla-simple) :is(tfoot tr, tr.total, tr.fila-total, tr.subtotal) > :is(td, th) {
    font-weight: 600;
    color: var(--fg);
    box-shadow: inset 0 1px 0 color-mix(in srgb, var(--fg) 16%, var(--bg-card));
}

/* ── §9 Un solo molde de pastilla de estado (D11-C) ───────────────────────────────────────
   Decisión del dueño: tinte 12 % + contorno 35 % (sombra inset: no suma caja) + texto del color
   semántico en 500 y en oración (sin versalitas forzadas); los neutros, planos y sin contorno. Cubre
   .tag, .badge y los chips de estado de logistics (pedido, viaje, flete, turno). Texto: en oscuro el
   semántico se aclara un 22 % hacia el blanco (puros daban 4,2-4,6:1; así 5,2-6,0:1); en claro va la
   tinta oscura de cada semántico (4,6-6,5:1), y el info/accent en claro a AA (85 % con negro).
   Las categorías de reports (.badge-<estado> de stock) conservan su matiz dentro del molde.
   :root:root = (0,4,1): gana a `.tag.is-ok` (components, 0,2,0) y a `:root[data-tema="claro"] .badge.ok`
   (tema.css y reports/meetings/wapp-claro, 0,4,0). NO entra: .ab-estado de mrp (planilla densa). */
:is(.tag, .badge, .estado:where(:not(select, input, textarea, button))) { --estado-c: var(--fg-muted); }
:is(.tag--ok, .tag.is-ok, .badge.ok, .badge-normal, .pill--enviado, .estado-listo) { --estado-c: var(--ok); }
:is(.tag--warn, .tag.is-warn, .tag.is-actualizar, .badge.warn, .badge-atencion, .pill--enviando) { --estado-c: var(--warn); }
:is(.tag--err, .tag.is-danger, .badge.err, .badge-quebrado, .pill--fallido, .estado-error) { --estado-c: var(--err); }
:is(.tag--info, .tag--accent, .tag.is-accent, .feed-alertas__badge--alta, .pill--pendiente, .estado-procesando) { --estado-c: var(--accent); }
/* .tag.is-info (briefing, etoro) es el AZUL informativo (components.css:260); el dorado es del CRM
   (.tag--info BEM, components.css:1080) y de .is-accent: no se juntan. */
.tag.is-info { --estado-c: var(--info); }
.pill--cancelado { --estado-c: var(--fg-muted); }
.tag--live { --estado-c: var(--accent-live); }
.badge-critico { --estado-c: #e87a00; }
.badge-exceso { --estado-c: var(--info); }
.badge-obsoleto { --estado-c: #7a6f9c; }
html:root[data-tema="claro"] .badge-critico { --estado-c: #8e4c10; }
html:root[data-tema="claro"] .badge-obsoleto { --estado-c: #65529e; }
:root:root body :is(.tag, .badge, .estado:where(:not(select, input, textarea, button)), .pill--enviado, .pill--enviando, .pill--fallido, .pill--pendiente, .pill--cancelado) {
    --estado-fg: color-mix(in srgb, var(--estado-c) 78%, #ffffff);
}
:root:root body :is(.tag--info, .tag--accent, .tag.is-accent, .feed-alertas__badge--alta, .pill--pendiente, .estado-procesando) { --estado-fg: var(--estado-c); }
html:root[data-tema="claro"]:root body :is(.tag, .badge, .estado:where(:not(select, input, textarea, button)), .pill--enviado, .pill--enviando, .pill--fallido, .pill--pendiente, .pill--cancelado) {
    --estado-fg: var(--estado-c);
}
html:root[data-tema="claro"]:root body :is(.tag--info, .tag--accent, .tag.is-accent, .feed-alertas__badge--alta, .pill--pendiente, .estado-procesando) {
    --estado-fg: color-mix(in srgb, var(--estado-c) 85%, #000);
}
/* El molde NO entra a la isla de filtros (la leyenda de reports /compras vive dentro de .toolbar-seccion y
   debe quedar como hoy): el :where() de cada selector suma 0, la especificidad no cambia. Wapp (.pill--*
   de la cola) y meetings (.estado-*) no son .tag/.badge pero son la misma pastilla; .pill-toggle (solapas
   de los gráficos) no está en la lista. */
:root:root body :is(.tag, .badge):is(.tag, .badge):where(:not(.fb *, .fb-popover *, .toolbar-seccion *, .isla-filtros *, .cp-isla *, .filter-island *, .isla *)),
:root:root body :is(.estado:where(:not(select, input, textarea, button)), .pill--enviado, .pill--enviando, .pill--fallido, .pill--pendiente, .pill--cancelado):is(.estado:where(:not(select, input, textarea, button)), .pill--enviado, .pill--enviando, .pill--fallido, .pill--pendiente, .pill--cancelado):where(:not(.fb *, .fb-popover *, .toolbar-seccion *, .isla-filtros *, .cp-isla *, .filter-island *, .isla *, .pill-toggle *)) {
    color: var(--estado-fg);
    background: color-mix(in srgb, var(--estado-c) 12%, transparent);
    border-color: transparent;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--estado-c) 35%, transparent);
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
}
:root:root body :is(.tag--neutral, .tag.is-muted, .badge.muted, .badge-sin_movimiento, .ldl-chip--tipo,
                    .badge:not(.ok, .warn, .err, [class*="badge-"]),
                    .tag:not([class*="tag--"]):not([class*="is-"]),
                    .pill--cancelado, .estado:not(.estado-listo, .estado-error, .estado-procesando):where(:not(select, input, textarea, button))):where(:not(.fb *, .fb-popover *, .toolbar-seccion *, .isla-filtros *, .cp-isla *, .filter-island *, .isla *, .pill-toggle *)) {
    color: var(--fg-muted);
    background: var(--bg-soft);
    border-color: transparent;
    box-shadow: none;
}
/* chips de logistics: ya llevan su color en --c; sólo se unifica el molde */
body .chip:is([class*="chip--est-"], [class*="chip--via-"], [class*="chip--ff-"], [class*="chip--tur-"],
              .chip--ok, .chip--warn, .chip--err, .chip--info, .chip--neutro, .chip--naranja,
              .chip--teal, .chip--violeta) {
    font-weight: 500;
    background: color-mix(in srgb, var(--c, var(--st-gris)) 12%, transparent);
    border-color: color-mix(in srgb, var(--c, var(--st-gris)) 35%, transparent);
}

/* ── §10 Ritmo 24 / 24 / 32 (D12-C) ───────────────────────────────────────────────────────
   Decisión del dueño: tarjetas de contenido y KPI a 24 en TODAS (hoy 16/20/22/24 según la app), bloques
   hermanos a 24, sección a 32 (sin cambio), lateral 32 y techo 24. Sólo ≥ 1025 px.
   NO entra: logistics /hoy y la COLA de /pedidos (.page-hoy, .page-pedidos:has(.pedidos-cola): la ficha
   de pedido, que lleva el mismo body.page-pedidos, SÍ recibe D12), crm /hoy y el kanban (#kanban),
   costeador /lista (.page-lista), ni las tarjetas que envuelven una tabla (> table, > .tbl-wrap,
   > .tabla-wrap). El +16 px de max-width de deposito, design y lims va en la hoja de cada app
   (deposito.css:205, design.css:222, lims.css:283): sin él la columna Artículo del historial se parte.
   (0,1,1): igual que `body .content > * + *` de design/deposito/cashflow/logistics/lims; gana por
   orden (esas hojas cargan antes) y m-hoy.css:384 (page-informe, 0,2,1) sigue ganando.
   El margen entre bloques es UNA regla sobre el receptor (`.content > :where(:not(…))`), SIN combinador de
   hermanos, SIN :has() y SIN atributos en el ancla: medido en mrp /compras (766 filas, 18.000 nodos), el `A ~ B`
   anterior invalidaba a todos los hermanos siguientes ante CUALQUIER cambio de [hidden] o de clase (~3,5× el
   recálculo al filtrar con el buscador), y un :has() en la cadena de una regla de subject universal recalculaba
   la planilla entera en cada inserción en .content. Todo va en :where() (suma 0). Por eso:
     · «tiene un bloque anterior» se escribe :nth-child(1 of :not(<invisibles>)): el primer hijo que se ve queda sin
       margen, y un primer hijo que no se ve (script, link, style, template, svg de sprite, h1.sr-only, centinela
       vacío de 1 px, [hidden]) NO cuenta como «bloque anterior»: el techo queda en 24 y no en 48;
     · los SUPERPUESTOS no reciben margen (dialog, .modal, [popover], [role=dialog], los modales fijos
       con inset:0): el margen los corría 24 px, dejaba una franja sin velo y descentraba los <dialog>;
     · la isla de filtros y sus centinelas (.toolbar-seccion, .fb, .isla-filtros, .filter-island,
       .cp-isla, .filtros-sentinel) tampoco: la isla queda donde estaba, sin saltos al pegarse;
     · los toasts y modales fijos ([class*="modal"], [class*="toast"]) tampoco: no son bloques del flujo;
     · la «sección a 32» (.section-title, details.sync-card) conserva el margen propio de su app;
     · el kanban de crm (.kanban-nav, .kanban-board) y logistics /hoy y crm /hoy (.page-hoy) y costeador /lista
       (.page-lista) conservan el suyo. La COLA de /pedidos de logistics ya tiene 24 entre bloques (medido), así que
       la regla no la mueve; sus paddings siguen excluidos por :has(.pedidos-cola) más abajo;
     · los bloques pegados por diseño siguen pegados: etoro .runs-terminal-body y el .paginador conservan el margen
       de su app, y las parejas barra de tabla + tabla (.tbl-toolbar/.compras-acciones), título de sección +
       barra, eyebrow/título/subtítulo (meetings, etoro) y la ceja de crm /clientes/<id> con su fila de KPI
       (.eyebrow + .kpi-row) se ponen en 0 con reglas de receptor CLASEADO (un receptor universal con
       combinador volvería a recalcular la planilla al insertar);
     · conservan el margen de su app: la fila de chips de la ficha de cliente de crm (.cliente-tags, −8 px bajo
       la cabecera), la cabecera de etoro /ticker (.ticker-header, 8 px bajo su ceja) y la hoja de los
       informes (.informe-hoja: el documento y su lugar en la página quedan como hoy).
   El «1 de» lleva SÓLO rasgos estáticos (etiquetas y clases fijas): con [hidden], [popover], [role] o [class*=]
   adentro, cada cambio de hidden o de clase en CUALQUIER elemento (las filas de una planilla al filtrar) hacía
   recalcular a todos sus hermanos. Esos rasgos siguen excluyendo al receptor (eso sólo recalcula al propio).
   Medido el 16-oct a las 05:15, con la máquina sin carga, en mrp /compras (766 filas): 7 teclas en el buscador
   ~250 ms de recálculo en producción, ~475 con la capa (~630 con el «1 de» dinámico); ocultar media tabla 110 · 210
   (330). Costo conocido: insertar un hijo AL PRINCIPIO de .content cuesta ~90 ms por vez (el «1 de» cambia de
   dueño), y ninguna app lo hace (ni JS ni HTMX); insertar al final es barato. */
@media (min-width: 1025px) {
    body:where(:not(.page-hoy, .page-pedidos:has(.pedidos-cola), .page-lista):not(:has(#kanban))) :is(.kpi-card, .card):where(:not(:has(> table, > .tbl-wrap, > .tabla-wrap))) {
        padding: var(--space-4);
    }
    body:where(:not(.page-hoy, .page-pedidos:has(.pedidos-cola), .page-lista):not(:has(#kanban))) .content {
        padding-left: var(--space-5);
        padding-right: var(--space-5);
        padding-top: var(--space-4);
    }
    body:where(:not(.page-hoy, .page-lista)) .content >
    :where(:not(:nth-child(1 of :not(script, link, style, template, datalist, noscript, svg, .sr-only, dialog, .modal, .filtros-sentinel)),
                script, link, style, template, datalist, noscript, svg, [hidden], .sr-only,
                    dialog, .modal, [popover], [role="dialog"], [role="alertdialog"], [class*="modal"], [class*="toast"],
                    .filtros-sentinel, div[aria-hidden="true"]:empty,
                .toolbar-seccion, .fb, .isla-filtros, .filter-island, .cp-isla,
                .runs-terminal-body, .paginador, .section-title, details.sync-card,
                .kanban-nav, .kanban-board,
                .cliente-tags, .ticker-header, .informe-hoja)) {
        margin-top: var(--space-4);
    }
    /* pegados: barra de tabla + tabla, título de sección + barra, eyebrow/título/subtítulo → 0 */
    body:where(:not(.page-hoy, .page-lista)) .content > :is(.tbl-toolbar, .compras-acciones) + :is(.tbl-wrap, .tbl-fade-wrap, .tabla-wrap),
    body:where(:not(.page-hoy, .page-lista)) .content > .section-title + .tbl-toolbar,
    body:where(:not(.page-hoy, .page-lista)) .content > :is(.page-eyebrow, .page-title, .page-subtitle) + :is(.page-title, .page-subtitle),
    body:where(:not(.page-hoy, .page-lista)) .content > .eyebrow + .kpi-row {
        margin-top: 0;
    }
}

/* ── §11 Tema claro: papel con jerarquía (D13-C) ──────────────────────────────────────────
   Decisión del dueño: en claro el sidebar baja a un papel más hondo (#e7e4dc, --bg-rail en §0; RIEL-C) que la
   página, el header queda sobre el papel de la página con hairline (§7), las tarjetas siguen blancas
   (lo más claro de la pantalla; borde y sombra sepia en §4 y §0) y reports/meetings pierden el crema
   dorado de fondo (--ambient-accent, §4).
   EXCEPCIÓN DEL DUEÑO: el ítem activo del sidebar NO pasa a «tecla blanca»: queda como hoy (sidebar.css:
   degradé lateral sobre --bg-soft, barra y badge); sólo cambia el papel del sidebar alrededor. Sólo se
   toca el hover de los ítems NO activos, que con el riel nuevo (#eeece5) igualaba a --bg-soft (#ecebe5).
   NO se redefinen --border-elev, --shadow-glass-card ni --glass-bg (los leen ghost, campos, modal y el
   bottom-nav del CRM). Para la hoja de crm: crm-claro.css:49 le pone un degradé al sidebar. */
html:root[data-tema="claro"] body .sidebar { background: var(--bg-rail); }
html:root[data-tema="claro"] body .sidebar nav a:not(.active):hover { background: var(--hover-tint); }
html:root[data-tema="claro"] body :is(.feed-alertas, .resumen-list) { border-color: var(--border); }

/* ══════════════════════════════════════════════════════════════════════════════════════
   RONDA 2 · ARREGLOS DE FLOTA (15-oct-2026 17:24, aprobados: «Accesibilidad y bugs CSS» y «Pulido CSS
   restante»). A14, A15, A17, A18, A19. Mismo criterio que §4-§11: @media screen, sin !important, sin
   transiciones de caja; la isla de filtros, el ítem activo del sidebar y los documentos (.informe-hoja,
   impresión) no se tocan.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── §12 KPI de lectura sin hover de botón (A14) ──────────────────────────────────────────
   Las KPI que NO llevan a ningún lado no reaccionan al puntero: ni borde, ni sombra, ni translate, ni
   scale (components.css:717 / polish.css:53 les ponían scale, borde teñido y fondo; §4 les sumaba
   sombra y borde). Las accionables (a.kpi-card, [role=link], con href, .is-interactive, o dentro de un
   enlace) tienen UN gesto: borde --border-hover + --shadow-2. Esta sección corrige el choque con la
   regla de hover de tarjetas de §4 para las KPI no clickeables (las accionables conservan lo de §4).
   Accionable = a, [role=link], [href], [onclick], [data-href], [tabindex], .is-interactive o hija de un
   enlace/.kpi-card-link. Las variantes con color propio (--accent, .kpi-alerta, kpi-card--*) conservan su
   borde y fondo: sólo pierden el movimiento y la sombra de hover. (0,4,2): gana a §4 y a los -claro. */
html:root body .kpi-card:not(a, [role="link"], [href], [onclick], [data-href], [tabindex], .is-interactive,
                             .kpi-alerta, .kpi-card--accent, [class*="kpi-card--"], .is-ok, .is-warn, .is-err, .is-danger, .is-neutro,
                             :is(a, [role="link"], .kpi-card-link) *):hover {
    transform: none;
    border-color: var(--border);
    box-shadow: var(--shadow-1);
    background-color: var(--bg-card);
}
html:root body .kpi-card:is(.kpi-alerta, .kpi-card--accent, [class*="kpi-card--"], .is-ok, .is-warn, .is-err, .is-danger, .is-neutro):not(a, [role="link"], [href],
                             [onclick], [data-href], [tabindex], .is-interactive,
                             :is(a, [role="link"], .kpi-card-link) *):hover {
    transform: none;
    box-shadow: var(--shadow-1);
}
html:root body :is(.data-card, .cockpit-hero__card):not(a, [role="link"], [href], [onclick], [data-href], [tabindex], .is-interactive,
                              :is(a, [role="link"], .kpi-card-link) *):hover {
    transform: none;
    border-color: var(--border);
    box-shadow: var(--shadow-1);
}
@media (hover: hover) and (pointer: fine) {
    html:root body :is(a.kpi-card, .kpi-card[role="link"], .kpi-card[href], .kpi-card.is-interactive,
                       .kpi-card-link .kpi-card, a.data-card, .data-card[role="link"], .data-card[href]):hover,
    html:root body .kpi-card-link:hover .kpi-card {
        transform: none;
        border-color: var(--border-hover);
        box-shadow: var(--shadow-2);
        background-color: var(--bg-card);
    }
}

/* ── §13 Sin pulsos infinitos ni resplandores (A15) ───────────────────────────────────────
   Los puntos «en vivo» y de estado pulsan 3 veces y quedan quietos (animation-iteration-count: 3) y
   pierden el halo de color: el punto ya dice «vivo» por su color. Los de sincronización (ok/warn/err)
   cambian el glow por un anillo plano de 2 px teñido al 25 %. .glow-border y .marquee, que existen en la
   capa compartida y nadie usa, quedan apagados como red de seguridad. Movimiento reducido: quietos del
   todo. Alcance: .live-dot, .sincro-dot(--live), .tag--live .tag__dot, .badge-en-curso__icon,
   .motion-pulse y .conn-strip__dot (crm). NO toca los spinners (mrp-spin, cost-spin: trabajo en curso). */
:root:root body :is(.live-dot, .sincro-dot--live, .sincro-dot, .tag--live .tag__dot) {
    box-shadow: none;
}
:root:root body .sincro-ok .sincro-dot { box-shadow: 0 0 0 2px color-mix(in srgb, var(--ok) 25%, transparent); }
:root:root body .sincro-warn .sincro-dot { box-shadow: 0 0 0 2px color-mix(in srgb, var(--warn) 25%, transparent); }
:root:root body .sincro-err .sincro-dot { box-shadow: 0 0 0 2px color-mix(in srgb, var(--err) 25%, transparent); }
:root:root body :is(.live-dot, .sincro-dot--live, .badge-en-curso__icon, .motion-pulse,
                    .conn-strip__dot) {
    animation-iteration-count: 3;
}
:root:root body :is(.conn-strip--degraded, .conn-strip--offline) .conn-strip__dot {
    animation-iteration-count: 3;
}
.glow-border::before,
.glow-border:hover::before { animation: none; opacity: 0; }
.marquee__track { animation: none; transform: none; }
@media (prefers-reduced-motion: reduce) {
    :root:root body :is(.live-dot, .sincro-dot--live, .badge-en-curso__icon, .motion-pulse, .conn-strip__dot) {
        animation: none;
    }
    :root:root body :is(.conn-strip--degraded, .conn-strip--offline) .conn-strip__dot { animation: none; }
}

/* ── §14 Pantalla de acceso de flota (A17) ────────────────────────────────────────────────
   Las 8 (cashflow, crm, deposito, design, lims, logistics, mrp, wapp) con la MISMA tarjeta: 380 de
   ancho, superficie sólida --bg-card con borde hairline, sombra --shadow-2 y padding 32/24; mismo botón
   de Google (44 de alto, radio de control, borde de campo, --bg-soft; blanco de logistics sale). El pie
   pasa de --fg-faint (#8f8f8f sobre #29292e = 4,48:1) a --fg-muted; lo mismo el resumen del acceso
   alternativo y el aviso «no configurado» de crm/lims (idéntico problema). wapp es un formulario de
   token, sin botón de Google: sólo comparte ancho, superficie y sombra. crm y lims llevan su CSS en un
   <style> de la página, que carga DESPUÉS de esta capa: se les gana por especificidad (0,4,2), no por
   orden. cashflow/logistics/mrp/design/deposito: `.acceso-card.glass-elev` (0,2,0) también queda abajo.
   Se apaga el pseudo-borde de luz de .glass-elev (display:none en ::before). */
html:root:root body :is(.acceso-card, .login-card):is(.acceso-card, .login-card) {
    width: 100%;
    max-width: 380px;
    box-sizing: border-box;
    padding: var(--space-5) var(--space-4);
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-2);
}
html body .acceso-card.glass-elev::before { display: none; }
html:root body .login-shell { max-width: 380px; }
html:root body :is(.acceso-card__pie, .acceso-sep, .acceso-alt > summary) { color: var(--fg-muted); }
html:root body .acceso-alt > summary:hover { color: var(--fg); }
html:root body .acceso-google {
    min-height: 44px;
    gap: var(--space-2);
    padding: 0 var(--space-3);
    border: 1px solid var(--field-border);
    border-radius: var(--radius-btn);
    background: var(--bg-soft);
    color: var(--fg);
    font-size: var(--text-sm);
    font-weight: 600;
    box-shadow: none;
}
html:root[data-tema="claro"] body .acceso-google {
    background: var(--bg-card);
    border-color: var(--field-border);
}
@media (hover: hover) and (pointer: fine) {
    html:root body .acceso-google:hover {
        background: var(--hover-tint);
        border-color: var(--field-border-hover);
        color: var(--fg);
    }
    html:root[data-tema="claro"] body .acceso-google:hover {
        background: var(--bg-soft);
        border-color: var(--field-border-hover);
    }
}

/* ── §15 Estado vacío canónico (A18) ──────────────────────────────────────────────────────
   Contorno punteado visible (18 % del texto), título 15/600 --fg, cuerpo 13 --fg-muted, y min-height 120
   dentro de una tarjeta (la base trae 200 pensados para una página entera). Cubre .empty-state (macro
   _empty_state: __title y __mensaje son el título; __body y __hint, el cuerpo) y .vacio (logistics,
   cashflow, deposito, design).
   EXCLUIDOS: las variantes --inline y --info del macro (mrp las usa a propósito sin marco ni alto);
   los vacíos de logistics /hoy y /pedidos (.page-hoy / .page-pedidos: m-hoy.css y m-pedidos.css calculan
   alturas); todo lo que viva en una celda de tabla (td/th; las celdas .empty de mrp incluidas). Esta
   sección corrige un choque con lo propio de deposito y design (deposito.css:321, design.css:340 sacan
   el marco del vacío dentro de .card): A18 pide el contorno visible también ahí. */
body .empty-state:not(.empty-state--inline, .empty-state--info):not(:is(td, th, .page-hoy, .page-pedidos) *),
body .vacio:not(td, th, tr):not(:is(td, th, .page-hoy, .page-pedidos) *) {
    border: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent);
    border-radius: var(--radius-card);
}
body .card .empty-state:not(.empty-state--inline, .empty-state--info):not(:is(td, th, .page-hoy, .page-pedidos) *),
body :is(.card, .section-block, .data-panel, .chart-card) .vacio:not(td, th, tr):not(:is(td, th, .page-hoy, .page-pedidos) *) {
    border: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent);
    min-height: 120px;
    box-sizing: border-box;
}
body :is(.card, .section-block, .data-panel, .chart-card) .vacio:not(td, th, tr):not(:is(td, th, .page-hoy, .page-pedidos) *) {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
body .empty-state:not(.empty-state--inline, .empty-state--info) :is(.empty-state__title, .empty-state__mensaje),
body .vacio:not(:is(.page-hoy, .page-pedidos) *) .vacio__titulo {
    font-size: 15px;
    font-weight: 600;
    color: var(--fg);
}
body .empty-state:not(.empty-state--inline, .empty-state--info) :is(.empty-state__body, .empty-state__hint),
body .vacio:not(:is(.page-hoy, .page-pedidos) *) :is(.vacio__sub, .vacio__msg, p:not(.vacio__titulo)) {
    font-size: 13px;
    color: var(--fg-muted);
}

/* ── §16 Prosa (A19) ──────────────────────────────────────────────────────────────────────
   Títulos con text-wrap-style: balance (se acabaron las viudas de una palabra), descripciones con pretty;
   .page-description sin cursiva, en --fg-muted (era --fg-faint) y a 72 caracteres como máximo. Los
   documentos (.informe-hoja) quedan como están. */
/* Longhands (text-wrap-style), NO el atajo `text-wrap`: el atajo también fija text-wrap-mode:wrap y
   pisaba el `white-space: nowrap` + elipsis de los títulos del header (.header h1) y de los paneles
   de cada app en toda la flota. Con el longhand el nowrap de la app sigue mandando y el balance sólo
   actúa donde el título ya podía partirse. .sr-only queda afuera (su nowrap es parte del recorte). */
body :is(h1, h2, .page-title):not(.informe-hoja *, .sr-only) { text-wrap-style: balance; }
body :is(.page-description, .page-subtitle, .empty-state__body, .empty-state__hint, .acceso-card__sub):not(.informe-hoja *, .sr-only) {
    text-wrap-style: pretty;
}
body .page-description {
    font-style: normal;
    color: var(--fg-muted);
    max-width: 72ch;
}

} /* @media screen */
