/* =============================================================
   TotalGes Móvil — Estilos
   Paleta alineada con la web corporativa www.mueblesvillegas.com
   - Rojo Muebles Villegas  #c5332c  (acento principal)
   - Texto oscuro           #212529
   - Gris secundario        #7a7a7a
   - Fondo suave            #fafafa
   - Stack tipográfico de sistema (-apple-system, Segoe UI, Roboto…)

   Pensado para uso con guantes: botones grandes (mín 48px),
   tipografía amplia, contrastes altos.
   ============================================================= */

:root {
    /* Marca corporativa */
    --c-primary: #c5332c;          /* Rojo Muebles Villegas */
    --c-primary-dark: #8e231e;     /* Hover / pressed */
    --c-primary-soft: #fbeae9;     /* Fondos suaves */

    /* Texto */
    --c-text: #212529;             /* Antracita, Bootstrap dark */
    --c-text-soft: #7a7a7a;
    --c-text-muted: #969696;

    /* Superficies */
    --c-bg: #fafafa;
    /* Mismo color que --c-bg pero TRANSPARENTE: para degradados que se funden
       con el fondo (el del compositor flotante del chat). No vale `transparent`
       a secas: Safari lo interpola pasando por gris y se ve una banda sucia. */
    --c-bg-fade: rgba(250, 250, 250, 0);
    --c-bg-soft: #eef2f6;          /* Botones/chips/píldoras sobre fondo claro */
    --c-card: #ffffff;
    --c-border: #e6e6e6;
    --c-border-strong: #cccccc;

    /* Estados */
    --c-danger: #c5332c;            /* Coincide con el rojo corporativo */
    --c-warning: #f0a020;
    --c-success: #2c8f3f;
    --c-info: #1e6cc4;

    /* Estados de incidencia (mapeados al esquema cromático corporativo) */
    --estado-nueva: #c5332c;
    --estado-revision: #f0a020;
    --estado-reparacion: #1e6cc4;
    --estado-resuelta: #2c8f3f;
    --estado-cerrada: #6c757d;

    /* Cronómetro en marcha (MV16). Mismo rojo intenso que la barra .timer-bar,
       para que el resaltado del listado y la barra se lean como lo mismo.
         --c-tiempo        relleno/borde (badge del grupo, franja de la tarjeta)
         --c-tiempo-soft   fondo del chip "⏱ En curso"
         --c-tiempo-txt    TEXTO del chip sobre ese fondo suave (5,2:1 en claro)
         --c-tiempo-contra texto sobre el relleno sólido       (6,6:1 en claro) */
    --c-tiempo: #b71c1c;
    --c-tiempo-soft: rgba(183, 28, 28, 0.14);
    --c-tiempo-txt: #b71c1c;
    --c-tiempo-contra: #ffffff;

    /* ===== ÁMBAR de aviso (L2/C5) =====
       El bloque `td-` de la visita llegó a tener cinco ámbares distintos escritos a
       mano (#ff9800, #ef6c00, #e65100, #fff3e0, #7a4a00) y tres de ellos por debajo
       del contraste mínimo. Aquí viven los cuatro únicos tonos, y lo que distingue
       cada significado NO es el color sino la FORMA:
         diff (cambiado)  → franja izquierda      (--c-warn-line)
         pendiente        → píldora               (--c-warn-bg + --c-warn-fg)
         falta / aviso    → ⚠ delante             (--c-warn-line en el filete)
         aplacado         → su icono propio
         --c-warn      relleno SÓLIDO; el texto encima va en blanco (4,88:1 medido)
         --c-warn-bg   fondo de píldora/franja (translúcido en oscuro: si fuera crema
                       fijo, el texto claro del tema oscuro quedaría ilegible)
         --c-warn-fg   TEXTO sobre --c-warn-bg (5,54:1 en claro · 7,05:1 en oscuro)
         --c-warn-line filete/franja/borde (elemento gráfico, no texto) */
    --c-warn: #a85f00;
    --c-warn-bg: #fff3e0;
    --c-warn-fg: #9a4d00;
    --c-warn-line: #e65100;

    /* RONDA 147 §3 — EL AVISO DEL PLANO, que ya NO es el ámbar de los cambios. Petición
       literal del usuario (14-sep): «el color para avisar al usuario ha de ser distinto del
       amarillo de los cambios; uno no usado, representativo y que se note al imprimir en
       blanco y negro». El ámbar (--c-warn-*) se queda para «esto cambió»; esto es «esto hay
       que mirarlo». Contrastes medidos sobre el fondo de la casa en ficheros claude/
       INFORME_ronda147.md.
         --c-aviso       el color del dibujo (lienzo y papel): rojo oscuro
         --c-aviso-bg    fondo de la banda/píldora (translúcido en oscuro)
         --c-aviso-fg    TEXTO sobre --c-aviso-bg
         --c-aviso-line  filete/borde (elemento gráfico, no texto) */
    --c-aviso: #b71c1c;
    --c-aviso-bg: #fdecea;
    --c-aviso-fg: #8c1515;
    --c-aviso-line: #b71c1c;

    /* Sombras */
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.06);
    --shadow-2: 0 4px 14px rgba(0, 0, 0, 0.08);
    --shadow-3: 0 8px 24px rgba(0, 0, 0, 0.12);

    /* Radii */
    --radius-sm: 6px;
    --radius: 10px;
    /* El escalón «md» faltaba DEFINIRLO (ronda 20 §4): había 8 usos de `var(--radius-md)`
       —5 de ellos SIN respaldo—, así que esos cinco resolvían a 0 y salían en pico. Es el
       mismo 10 del token sin sufijo: son el mismo escalón con dos nombres. */
    --radius-md: 10px;
    --radius-lg: 16px;

    /* Espaciado */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;

    /* Ritmo de los BLOQUES DE BÚSQUEDA / FORMULARIO (1.89.0, MV4).
       REGLA DURA: ningún input, botón o chip pegado al borde de la pantalla ni
       pegado a otro control. Estas tres variables son el único sitio donde se
       ajusta ese ritmo — no volver a escribir números sueltos en estas zonas.
         --esp-borde: separación lateral con el borde de la pantalla
         --esp-fila:  separación vertical entre filas del bloque
         --esp-chip:  separación entre controles/chips de una misma fila     */
    --esp-borde: 12px;
    --esp-fila: 8px;
    --esp-chip: 6px;

    /* Tipografía: stack de sistema (igual que mueblesvillegas.com) */
    --font-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
                 Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    /* 1.175.0 — la base sube de 16 a 17 px (≈ un punto tipográfico más) porque
       el usuario veía la app corta de letra. Es el TAMAÑO NORMAL, el que el
       ajuste "🔠 Tamaño de letra" (#/ajustes) enseña como 100 %: quien tuviera
       guardado un 85 % o un 130 % conserva su porcentaje tal cual y simplemente
       parte de una base un poco mayor. ⚠ El valor va DUPLICADO en
       `js/core/preferencias.js` (constante BASE_PX), que es quien manda de
       verdad —escribe el font-size del <html> en píxeles—: si se cambia aquí,
       cambiarlo también allí. Este de aquí es el que vale mientras el JS de
       preferencias todavía no ha corrido (evita el salto de tamaño al arrancar). */
    --font-size-base: 17px;
    --font-size-big: 1.125rem;
    --font-size-h1: 1.375rem;
    --font-size-h2: 1.125rem;
}

/* Modo oscuro. Se activa de dos maneras:
   - AUTOMÁTICO (sin data-tema o data-tema ausente): sigue prefers-color-scheme,
     salvo que el usuario haya forzado el tema CLARO en Ajustes.
   - FORZADO: html[data-tema="oscuro"] desde #/ajustes (por dispositivo). */
@media (prefers-color-scheme: dark) {
    :root:not([data-tema="claro"]) {
        --c-bg: #121212;
        --c-bg-fade: rgba(18, 18, 18, 0);
        --c-bg-soft: #2a2a2a;       /* Píldoras/chips/toolbar sobre fondo oscuro
                                       (antes caía al fallback claro #eef2f6 → iconos
                                       blancos sobre fondo claro = invisibles) */
        --c-card: #1e1e1e;
        --c-text: #f1f1f1;
        --c-text-soft: #a0a0a0;
        --c-text-muted: #707070;
        --c-border: #2c2c2c;
        --c-border-strong: #3a3a3a;
        --c-primary-soft: rgba(197, 51, 44, 0.16);
        /* El #b71c1c queda muy apagado sobre #121212: se aclara (MV16). El texto
           del chip sube a #ff8a80 (5,0:1 sobre su fondo) y el badge sólido pasa
           a texto oscuro (5,0:1): con blanco se quedaba en 3,5:1. */
        --c-tiempo: #ef5350;
        --c-tiempo-soft: rgba(239, 83, 80, 0.20);
        --c-tiempo-txt: #ff8a80;
        --c-tiempo-contra: #1a1a1a;
        /* L2/C5: el relleno sólido no cambia (blanco encima sigue dando 4,88:1);
           el fondo pasa a translúcido y el texto a ámbar claro (7,05:1). */
        --c-warn-bg: rgba(255, 152, 0, 0.18);
        --c-warn-fg: #ffb74d;
        --c-warn-line: #ffa726;
        /* RONDA 147 §3 — el aviso en tema OSCURO: el rojo oscuro no se lee sobre fondo negro, así
           que el texto sube a un rojo claro y el fondo es translúcido (mismo patrón que el ámbar). */
        --c-aviso: #ef5350;
        --c-aviso-bg: rgba(211, 47, 47, 0.20);
        --c-aviso-fg: #ef9a9a;
        --c-aviso-line: #e57373;
        --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
        --shadow-2: 0 4px 14px rgba(0, 0, 0, 0.5);
    }
}
:root[data-tema="oscuro"] {
    --c-bg: #121212;
    --c-bg-fade: rgba(18, 18, 18, 0);
    --c-bg-soft: #2a2a2a;
    --c-card: #1e1e1e;
    --c-text: #f1f1f1;
    --c-text-soft: #a0a0a0;
    --c-text-muted: #707070;
    --c-border: #2c2c2c;
    --c-border-strong: #3a3a3a;
    --c-primary-soft: rgba(197, 51, 44, 0.16);
    --c-tiempo: #ef5350;
    --c-tiempo-soft: rgba(239, 83, 80, 0.20);
    --c-tiempo-txt: #ff8a80;
    --c-tiempo-contra: #1a1a1a;
    --c-warn-bg: rgba(255, 152, 0, 0.18);
    --c-warn-fg: #ffb74d;
    --c-warn-line: #ffa726;
    /* RONDA 147 §3 — el aviso en tema OSCURO: el rojo oscuro no se lee sobre fondo negro, así
       que el texto sube a un rojo claro y el fondo es translúcido (mismo patrón que el ámbar). */
    --c-aviso: #ef5350;
    --c-aviso-bg: rgba(211, 47, 47, 0.20);
    --c-aviso-fg: #ef9a9a;
    --c-aviso-line: #e57373;
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-2: 0 4px 14px rgba(0, 0, 0, 0.5);
}

/* ===== Acentos de color (Ajustes → 🎨; por dispositivo) =====
   Cambian el color primario (botones, precios, destacados). El rojo
   corporativo es el valor por defecto de :root (sin data-acento). */
:root[data-acento="azul"] {
    --c-primary: #1e6cc4;
    --c-primary-dark: #14508f;
    --c-primary-soft: #e7f0fa;
}
:root[data-acento="verde"] {
    --c-primary: #2c8f3f;
    --c-primary-dark: #1f6b2e;
    --c-primary-soft: #e7f5ea;
}
:root[data-acento="morado"] {
    --c-primary: #7b3fa0;
    --c-primary-dark: #5c2e78;
    --c-primary-soft: #f2eaf7;
}
/* Naranja/Gris (1.98.0, MV15). Naranja quemado #c0570a: 4,56:1 con blanco →
   AA para TEXTO NORMAL en botón primario y chip activo (no solo para texto
   grande). Los tonos de apoyo son GRISES CÁLIDOS, de ahí el nombre. */
:root[data-acento="naranja"] {
    --c-primary: #c0570a;
    --c-primary-dark: #8f4108;     /* hover/pressed — 7,17:1 con blanco */
    --c-primary-soft: #f0eeec;     /* gris cálido, no naranja pastel */
}
/* Gris oscuro (1.98.0, MV15). Antracita #4a4f55: 8,26:1 con blanco. */
:root[data-acento="grisoscuro"] {
    --c-primary: #4a4f55;
    --c-primary-dark: #34383d;     /* hover/pressed — 11,9:1 con blanco */
    --c-primary-soft: #eceef0;
}
/* En oscuro, el "soft" del acento se atenúa igual que el rojo por defecto */
:root[data-tema="oscuro"][data-acento="azul"] { --c-primary-soft: rgba(30, 108, 196, 0.20); }
:root[data-tema="oscuro"][data-acento="verde"] { --c-primary-soft: rgba(44, 143, 63, 0.20); }
:root[data-tema="oscuro"][data-acento="morado"] { --c-primary-soft: rgba(123, 63, 160, 0.22); }
:root[data-tema="oscuro"][data-acento="naranja"] { --c-primary-soft: rgba(192, 87, 10, 0.20); }
/* El antracita sobre fondo #121212 quedaría casi invisible (2,1:1 contra el
   fondo): en oscuro se ACLARA a #6b7078, que mantiene 4,98:1 con el texto
   blanco del botón y sube a 3,8:1 contra el fondo. Es el único acento que
   cambia de color con el tema, y es a propósito. */
:root[data-tema="oscuro"][data-acento="grisoscuro"] {
    --c-primary: #6b7078;
    --c-primary-dark: #565b62;
    --c-primary-soft: rgba(107, 112, 120, 0.22);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-tema="claro"])[data-acento="azul"] { --c-primary-soft: rgba(30, 108, 196, 0.20); }
    :root:not([data-tema="claro"])[data-acento="verde"] { --c-primary-soft: rgba(44, 143, 63, 0.20); }
    :root:not([data-tema="claro"])[data-acento="morado"] { --c-primary-soft: rgba(123, 63, 160, 0.22); }
    :root:not([data-tema="claro"])[data-acento="naranja"] { --c-primary-soft: rgba(192, 87, 10, 0.20); }
    :root:not([data-tema="claro"])[data-acento="grisoscuro"] {
        --c-primary: #6b7078;
        --c-primary-dark: #565b62;
        --c-primary-soft: rgba(107, 112, 120, 0.22);
    }
}

* {
    box-sizing: border-box;
    -webkit-tap-highlight-color: transparent;
}

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    font-family: var(--font-base);
    font-size: var(--font-size-base);
    line-height: 1.4;
    color: var(--c-text);
    background: var(--c-bg);
    overscroll-behavior-y: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* El tamaño de letra de la app está en rem: el ajuste "Tamaño de letra"
   (#/ajustes) escala el font-size del elemento raíz por JS. El body hereda
   en rem para que TODO escale de forma uniforme. */
body { font-size: 1rem; }

/* ⚠ 1.175.0 — Los controles de formulario NO heredan la tipografía del
   documento: el navegador les pone la del sistema (en Chrome, Arial 13,333 px
   FIJOS). Por eso todo texto que viviera dentro de un <button> sin tamaño
   propio se quedaba FUERA del ajuste "Tamaño de letra": se veía más pequeño
   que el resto de la app y no crecía ni un píxel al subir el porcentaje.
   Le pasaba al nombre de la conversación en 💬 Mensajes (13,3 px clavados
   frente a los 16 px del resto), al nombre del archivo adjunto de una burbuja
   y a las tarjetas de documento del ERP —y era una trampa esperando a
   cualquier tarjeta-botón nueva, porque en la app las tarjetas pulsables son
   <button> por accesibilidad—.
   Con `inherit` el botón pasa a ser tipográficamente transparente: los que ya
   traían su propio font-size (.btn, .chip, .tag…) no cambian nada, y los que
   no lo traían pasan a heredar como cualquier <div>. */
button, input, select, textarea, optgroup { font-family: inherit; }
button { font-size: inherit; }

#app {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    padding-bottom: env(safe-area-inset-bottom);
}

/* === Splash inicial === */
.splash {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: var(--c-primary);
}

.splash-logo {
    font-size: 2.25rem;
    font-weight: 800;
    letter-spacing: -0.5px;
    margin-bottom: var(--space-3);
}

.splash-sub {
    color: var(--c-text-soft);
}

/* === Topbar (estilo navbar corporativo) === */
.topbar {
    background: var(--c-card);
    color: var(--c-text);
    /* ⚠ 1.151.0 — el hueco de arriba era SOLO el `safe-area` del dispositivo: en un
       navegador de escritorio (o de Android) vale 0px, así que la barra quedaba con
       12px abajo y 0 arriba y los botones ⚙/✏ se veían PEGADOS al borde superior.
       Con `max()` el escritorio recupera su margen simétrico y en iPhone/iPad —donde
       el safe-area es mayor que 12px— no cambia absolutamente nada. */
    padding: max(env(safe-area-inset-top, 0px), var(--space-3)) var(--space-4) var(--space-3);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    box-shadow: var(--shadow-1);
    position: sticky;
    top: 0;
    z-index: 10;
    border-bottom: 3px solid var(--c-primary);
}

.topbar h1 {
    margin: 0;
    font-size: var(--font-size-h1);
    font-weight: 700;
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.topbar-brand {
    color: var(--c-primary);
    font-weight: 800;
    letter-spacing: -0.3px;
}

/* Bloque de título pulsable (logo + versión) para forzar actualización */
.topbar-title {
    flex: 1;
    min-width: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: var(--c-primary-soft);
}

.topbar-title h1 {
    flex: none;
}

.topbar-version {
    font-size: 0.625rem;
    line-height: 1.1;
    color: var(--c-text-muted);
    margin-top: 1px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.topbar-title:active .topbar-version {
    color: var(--c-primary);
}

.topbar-back {
    background: transparent;
    border: none;
    color: var(--c-text);
    font-size: 1.75rem;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    cursor: pointer;
    line-height: 1;
}

.topbar-back:active {
    background: var(--c-primary-soft);
}

.topbar-action {
    background: transparent;
    border: none;
    color: var(--c-primary);
    font-size: 0.9375rem;
    font-weight: 600;
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
    border-radius: var(--radius-sm);
    min-height: 40px;
}

.topbar-action:active {
    background: var(--c-primary-soft);
}

/* === Chip de estado de sincronización (topbar) === */
.sync-chip {
    border: none;
    border-radius: 999px;
    padding: 4px 10px;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1;
    min-height: 28px;
    cursor: pointer;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    color: #fff;
    transition: opacity .15s;
}

.sync-chip:active { opacity: .8; }

/* Verde: todo al día */
.sync-chip.sync-ok { background: #16a34a; }
/* Ámbar: hay acciones pendientes de subir */
.sync-chip.sync-pend { background: #d97706; }
/* Rojo: sin conexión */
.sync-chip.sync-off { background: #dc2626; }
/* Azul: sincronizando ahora */
.sync-chip.sync-proc { background: #2563eb; }

/* Indicador (punto ámbar) por incidencia con cambios pendientes de subir */
.card-ref-wrap {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.pend-dot {
    color: #d97706;
    font-size: 0.8125rem;
    font-weight: 700;
    line-height: 1;
}

/* === Login === */
.login-page {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: stretch;
    padding: var(--space-5);
    background:
        radial-gradient(circle at top right, rgba(197, 51, 44, 0.08), transparent 70%),
        linear-gradient(180deg, var(--c-bg) 0%, var(--c-bg) 100%);
}

.login-brand {
    text-align: center;
    margin-bottom: var(--space-5);
}

.login-brand-logo {
    font-size: 2.5rem;
    font-weight: 800;
    color: var(--c-primary);
    letter-spacing: -1px;
    margin: 0;
    line-height: 1;
}

/* Logo TG arriba: identidad de la app, grande y visible. */
.login-brand-tg {
    display: block;
    margin: 0 auto;
    width: auto;
    height: clamp(140px, 28vh, 260px);
    object-fit: contain;
    -webkit-user-drag: none;
    user-select: none;
}
/* Logo Villegas debajo del card de login: cliente de la app. Más pequeño. */
.login-brand-bottom {
    margin-top: var(--space-5);
    text-align: center;
}
.login-brand-bottom .login-brand-img {
    width: auto;
    height: clamp(60px, 12vh, 110px);
    max-height: none;
    margin: 0 auto;
}
.login-brand-top { margin-bottom: var(--space-4); }

.login-brand-sub {
    color: var(--c-text-soft);
    font-size: 0.875rem;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    margin-top: var(--space-2);
}

.login-brand-img {
    display: block;
    margin: 0 auto var(--space-4);
    /* Logo responsive: ocupa el 80% del viewport en cualquier dispositivo,
       con un techo razonable para pantallas grandes (50% del alto disponible).
       Así crece con la pantalla en vez de quedarse en 200 px fijo en móvil. */
    width: min(80vw, 600px);
    max-height: min(50vh, 320px);
    height: auto;
    object-fit: contain;
}

.login-card {
    background: var(--c-card);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    box-shadow: var(--shadow-3);
    border: 1px solid var(--c-border);
}

.login-title {
    text-align: center;
    margin: 0 0 var(--space-5);
    font-size: 1.25rem;
    color: var(--c-text);
    font-weight: 600;
}

.login-card label {
    display: block;
    margin-bottom: var(--space-1);
    font-size: 0.8125rem;
    color: var(--c-text-soft);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

.login-card input[type="text"],
.login-card input[type="password"] {
    width: 100%;
    padding: var(--space-3) var(--space-4);
    border: 2px solid var(--c-border);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-big);
    background: var(--c-card);
    color: var(--c-text);
    margin-bottom: var(--space-4);
    -webkit-appearance: none;
    transition: border-color 0.15s ease;
}

.login-card input:focus {
    outline: none;
    border-color: var(--c-primary);
    box-shadow: 0 0 0 3px var(--c-primary-soft);
}

.login-footer {
    text-align: center;
    color: var(--c-text-muted);
    font-size: 0.75rem;
    margin-top: var(--space-4);
}

/* === Botones === */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-5);
    min-height: 48px;
    border: none;
    border-radius: var(--radius-sm);
    font-size: var(--font-size-big);
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    text-decoration: none;
    transition: transform 0.05s ease, opacity 0.15s ease, background 0.15s ease;
    letter-spacing: 0.2px;
}

.btn:active {
    transform: scale(0.98);
}

.btn-primary {
    background: var(--c-primary);
    color: #fff;
    width: 100%;
}

.btn-primary:hover {
    background: var(--c-primary-dark);
}

.btn-secondary {
    background: var(--c-card);
    color: var(--c-primary);
    border: 2px solid var(--c-primary);
}

.btn-success { background: var(--c-success); color: #fff; }
.btn-danger { background: var(--c-danger); color: #fff; }
.btn-info { background: var(--c-info); color: #fff; }
.btn-warning { background: var(--c-warning); color: #fff; }

.btn[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

/* === Lista incidencias === */
.list-page {
    flex: 1;
    display: flex;
    flex-direction: column;
}

.list-filters {
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    padding: var(--esp-fila) var(--esp-borde);
    display: flex;
    flex-wrap: wrap;
    /* MV8: fila envuelve en pantallas estrechas — separación entre líneas
       envueltas (--esp-fila) distinta de la separación entre chips de la
       misma línea (--esp-chip); un `gap` único dejaba las líneas a 6px. */
    row-gap: var(--esp-fila);
    column-gap: var(--esp-chip);
}

.chip {
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--c-border-strong);
    background: var(--c-card);
    border-radius: 20px;
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    color: var(--c-text);
    min-height: 36px;
    display: inline-flex;
    align-items: center;
    transition: all 0.15s ease;
}

.chip.active {
    background: var(--c-primary);
    color: #fff;
    border-color: var(--c-primary);
}

/* Acceso "⬆ Pendientes (N)" del listado de incidencias: ámbar, como el chip de
   sincronización cuando hay cambios sin subir. Solo se pinta si N > 0. */
.chip.chip-pendientes {
    background: var(--c-warn, #c77800);
    border-color: var(--c-warn, #c77800);
    color: #fff;
    font-weight: 700;
}

.filter-select {
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--c-border-strong);
    background: var(--c-card);
    border-radius: 20px;
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--c-text);
    min-height: 36px;
    cursor: pointer;
    max-width: 100%;
}

.list-cards {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-3);
}

.card {
    background: var(--c-card);
    border-radius: var(--radius);
    padding: var(--space-4);
    margin-bottom: var(--space-3);
    box-shadow: var(--shadow-1);
    cursor: pointer;
    border-left: 4px solid var(--c-border-strong);
    border-top: 1px solid var(--c-border);
    border-right: 1px solid var(--c-border);
    border-bottom: 1px solid var(--c-border);
    transition: transform 0.1s ease, box-shadow 0.15s ease;
}

.card:active {
    transform: scale(0.99);
    box-shadow: var(--shadow-2);
}

.card.estado-nueva     { border-left-color: var(--estado-nueva); }
.card.estado-revision  { border-left-color: var(--estado-revision); }
.card.estado-reparacion { border-left-color: var(--estado-reparacion); }
.card.estado-resuelta  { border-left-color: var(--estado-resuelta); }
.card.estado-cerrada   { border-left-color: var(--estado-cerrada); }

.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-2);
}

.card-ref {
    font-size: 0.75rem;
    color: var(--c-text-soft);
    font-weight: 700;
    letter-spacing: 0.5px;
}

.card-fecha {
    font-size: 0.75rem;
    color: var(--c-text-muted);
}

.card-title {
    font-size: var(--font-size-big);
    font-weight: 600;
    margin: 0 0 var(--space-2);
    line-height: 1.3;
}

.card-cliente {
    color: var(--c-text);
    margin-bottom: var(--space-1);
    font-size: 0.9375rem;
    font-weight: 500;
}

.card-localidad {
    color: var(--c-text-soft);
    font-size: 0.8125rem;
}

.card-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-3);
}

.tag {
    padding: 3px 10px;
    border-radius: 12px;
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

.tag-estado-nueva     { background: rgba(197, 51, 44, 0.12);  color: var(--estado-nueva); }
.tag-estado-revision  { background: rgba(240, 160, 32, 0.16); color: #b27500; }
.tag-estado-reparacion { background: rgba(30, 108, 196, 0.14); color: var(--estado-reparacion); }
.tag-estado-resuelta  { background: rgba(44, 143, 63, 0.14);  color: var(--estado-resuelta); }
.tag-estado-cerrada   { background: rgba(108, 117, 125, 0.14); color: var(--estado-cerrada); }

.tag-prioridad-urgente { background: rgba(197, 51, 44, 0.12); color: var(--c-danger); }
.tag-prioridad-alta    { background: rgba(240, 160, 32, 0.16); color: #b27500; }
.tag-prioridad-media   { background: rgba(30, 108, 196, 0.14); color: var(--c-info); }
.tag-prioridad-baja    { background: rgba(108, 117, 125, 0.14); color: var(--c-text-soft); }

.tag-icono {
    background: var(--c-primary-soft);
    color: var(--c-primary);
}

/* Badge "líneas pendientes" en las tarjetas del buscador de albaranes (salida). */
.badge-pendientes {
    display: inline-block;
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 0.78rem;
    font-weight: 700;
    background: rgba(199, 120, 0, 0.14);
    color: var(--c-warn, #c77800);
    border: 1px solid rgba(199, 120, 0, 0.35);
}
.badge-pendientes.badge-completo {
    background: rgba(46, 125, 50, 0.12);
    color: #2e7d32;
    border-color: rgba(46, 125, 50, 0.35);
}

.empty-busca {
    margin: 18px 12px;
    color: var(--c-text-soft, #6c757d);
    font-style: italic;
    text-align: center;
}

/* Módulo Almacén */
.almacen-resumen {
    padding: 4px 12px;
    font-size: 0.85rem;
    color: var(--c-text-soft, #6c757d);
}
.alm-exist {
    font-weight: 700;
    font-size: 1.05rem;
    margin-left: auto;
}
.alm-ok   { color: #1e7e34; }   /* con stock */
.alm-cero { color: #b08800; }   /* cero */
.alm-neg  { color: #c0392b; }   /* negativo */
.alm-mas  { width: 100%; margin: 8px 0; }
.alm-scan { flex: 0 0 auto; padding: 0 14px; font-size: 1.1rem; }
.alm-meta {
    display: flex; gap: 12px; flex-wrap: wrap;
    font-size: 0.8rem; color: var(--c-text-soft, #6c757d); margin: 2px 0 4px;
}
.alm-meta .alm-libre { font-weight: 600; }
.alm-card { cursor: pointer; }
.alm-detalle { display: flex; flex-direction: column; gap: 6px; max-height: 50vh; overflow-y: auto; }
.alm-detalle-row {
    display: flex; justify-content: space-between; align-items: center;
    padding: 8px 10px; border-bottom: 1px solid var(--c-border, #eee);
}
.alm-detalle-row .alm-exist { font-size: 1rem; }

/* Disponibilidad sin conexión */
.tag-offline {
    background: rgba(40, 167, 69, 0.16);
    color: #1e7e34;
    font-weight: 600;
}
.tag-offline-parcial {
    background: rgba(108, 117, 125, 0.16);
    color: var(--c-text-soft, #6c757d);
}

/* === Cambios guardados sin cobertura, pendientes de enviar (1.99.1, F3) === */

/* Etiqueta de un dato que aún no ha subido (estado, firma, alta provisional…) */
.tag-pendiente {
    background: rgba(217, 119, 6, 0.16);
    color: #b45309;
    font-weight: 600;
}

/* Banner del detalle: "⏳ N cambios pendientes de enviar" */
.pend-banner {
    margin: var(--space-3, 12px);
    padding: 10px 12px;
    border-radius: 10px;
    background: rgba(217, 119, 6, 0.10);
    border: 1px solid rgba(217, 119, 6, 0.35);
}
.pend-banner-titulo { font-weight: 700; color: #b45309; margin-bottom: 4px; }
.pend-banner-lista {
    margin: 0; padding-left: 18px;
    font-size: 0.875rem; color: var(--c-text-soft, #6c757d);
}
.pend-banner-lista li { margin: 2px 0; }

/* Miniatura (foto o croquis) todavía en la cola: se marca con ⏳ en la esquina */
.foto-item.foto-pendiente { opacity: 0.92; outline: 2px dashed rgba(217, 119, 6, 0.6); outline-offset: -2px; }
.foto-pend-badge {
    position: absolute; top: 4px; left: 4px;
    background: rgba(217, 119, 6, 0.92); color: #fff;
    border-radius: 10px; padding: 1px 6px; font-size: 0.75rem; line-height: 1.4;
}

/* Movimiento de historial aún sin enviar: atenuado y con borde punteado */
.historial-item.historial-pendiente {
    opacity: 0.75;
    border-left: 3px dashed #d97706;
    padding-left: 8px;
}

/* Material preparado (incidencia) */
.tag-material {
    background: rgba(13, 110, 253, 0.14);
    color: #0a58ca;
    font-weight: 600;
}

/* Salida de mercancía contra albarán */
.salida-lbl { align-self: center; font-size: 0.9rem; color: var(--c-text-soft, #6c757d); margin-right: 6px; }
.salida-head {
    display: flex; justify-content: space-between; align-items: center;
    padding: 10px 12px; background: var(--c-bg-soft, #f3f4f6);
    border-radius: 10px; margin: 8px var(--space-3);
}
.salida-head-num { font-weight: 700; font-size: 1.05rem; }
.salida-head-cli { font-size: 0.88rem; color: var(--c-text-soft, #6c757d); }
.salida-scan-row { margin: 8px var(--space-3); display: flex; gap: 8px; }
.salida-scan-row .salida-scan-btn { flex: 2 1 0; }
.salida-scan-row .salida-manual-btn { flex: 1 1 0; font-weight: 600; padding: 14px 8px; }
.salida-linea.salida-on { outline: 2px solid #28a745; }
.salida-meta {
    display: flex; gap: 12px; flex-wrap: wrap;
    font-size: 0.82rem; color: var(--c-text-soft, #6c757d); margin: 3px 0;
}
.salida-stepper { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.salida-step {
    width: 44px; height: 44px; border-radius: 8px; font-size: 1.4rem; font-weight: 700;
    border: 1px solid var(--c-border, #ccc); background: var(--c-bg, #fff); color: var(--c-text, #222);
}
.salida-servir-val { min-width: 48px; text-align: center; font-size: 1.3rem; font-weight: 700; }
.salida-todo {
    margin-left: auto; padding: 0 14px; height: 40px; border-radius: 8px;
    border: 1px solid var(--c-primary, #0d6efd); background: var(--c-primary-soft, #e7f1ff); color: var(--c-primary, #0d6efd);
    font-weight: 600;
}
.salida-completa { margin-top: 8px; color: #1e7e34; font-weight: 600; }
.salida-confirm { padding: 8px 0; }
.salida-hint { font-size: 0.82rem; color: var(--c-text-soft, #6c757d); margin: 4px var(--space-3) 8px; }
.salida-stepper-hint { font-size: 0.82rem; color: var(--c-text-soft, #6c757d); font-style: italic; }

/* Inventario: selector de modo + ayuda */
.inv-modo-row { align-items: center; }
.inv-ayuda-btn {
    margin-left: auto; padding: 6px 10px; border-radius: 8px; font-size: 0.82rem; font-weight: 600;
    border: 1px solid var(--c-border, #ccc); background: var(--c-bg, #fff); color: var(--c-text, #222);
}
/* Botón de ayuda integrado en la cabecera (topbar). */
.topbar-ayuda {
    margin-left: auto;
    flex: 0 0 auto;
    padding: var(--space-2) var(--space-3);
    font-size: 0.85rem;
    border: 1px solid var(--c-primary);
    background: var(--c-primary-soft);
    color: var(--c-primary);
    white-space: nowrap;
}
.topbar-ayuda:active { background: var(--c-primary); color: #fff; }
.inv-ayuda { margin: 0 var(--space-3) 8px; padding: 12px; border: 1px solid var(--c-border, #e0e0e0); border-radius: 12px; background: var(--c-bg-soft, #f7f8fa); }
.inv-ayuda-bloque { margin-bottom: 10px; font-size: 0.86rem; color: var(--c-text, #222); }
.inv-ayuda-bloque:last-child { margin-bottom: 0; }
.inv-ayuda-tit { font-weight: 700; margin-bottom: 3px; }
.inv-ayuda-uso { font-size: 0.82rem; color: var(--c-text-soft, #6c757d); margin-top: 2px; }

/* Verificación de entrada por IA (resultado) */
.verif-lista { display: flex; flex-direction: column; gap: 6px; max-height: 60vh; overflow-y: auto; }
.verif-prov { font-size: 0.85rem; color: var(--c-text-soft, #6c757d); }
.verif-resumen { font-weight: 700; margin-bottom: 4px; }
.verif-linea { padding: 8px 10px; border-radius: 8px; }
.verif-art { font-weight: 600; font-size: 0.9rem; }
.verif-det { font-size: 0.82rem; opacity: 0.9; }
.salida-scan-btn { width: 100%; font-size: 1.05rem; padding: 14px; font-weight: 700; }
.salida-ean { font-size: 0.78rem; color: var(--c-text-soft, #6c757d); margin: 2px 0; letter-spacing: 0.3px; }
.salida-marcado { color: #0a58ca; font-weight: 700; }

/* Banner del último escaneo (para leer el mensaje en pantalla) */
.salida-banner { padding: 10px 12px; border-radius: 10px; margin: 8px var(--space-3); font-weight: 600; font-size: 0.95rem; }
.salida-banner-ok    { background: rgba(40,167,69,0.16);  color: #1e7e34; border: 1px solid rgba(40,167,69,0.4); }
.salida-banner-error { background: rgba(220,53,69,0.16);  color: #b02a37; border: 1px solid rgba(220,53,69,0.5); }
.salida-banner-aviso { background: rgba(255,193,7,0.18);  color: #997404; border: 1px solid rgba(255,193,7,0.5); }

/* Tarjetas de línea MUY resaltadas: borde grueso de color + tinte de fondo */
.salida-linea { border-left: 8px solid transparent; }
.salida-linea.salida-c-verde    { border-left-color: #28a745; background: rgba(40,167,69,0.10); }
.salida-linea.salida-c-amarillo { border-left-color: #e0a800; background: rgba(255,193,7,0.14); }
.salida-linea.salida-c-rosa     { border-left-color: #e8889a; background: rgba(232,136,154,0.16); }
.salida-linea.salida-c-rojo     { border-left-color: #dc3545; background: rgba(220,53,69,0.20); }
.salida-linea.salida-on { box-shadow: 0 0 0 3px rgba(13,110,253,0.55); }
.salida-linea .card-title { font-size: 1.05rem; font-weight: 700; }

/* Chips de nº de serie capturados */
.salida-series { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.salida-serie-chip { background: var(--c-primary-soft, #e7f1ff); color: var(--c-primary, #0d6efd); border-radius: 6px; padding: 2px 8px; font-size: 0.8rem; font-weight: 600; }

/* Modal de captura de nº de serie */
.salida-serie-backdrop {
    position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 1000;
    display: flex; align-items: center; justify-content: center; padding: 16px;
}
.salida-serie-modal {
    background: var(--c-bg, #fff); color: var(--c-text, #222);
    border-radius: 14px; padding: 16px; width: 100%; max-width: 420px;
    box-shadow: 0 10px 40px rgba(0,0,0,0.4);
}
.salida-serie-tit { font-weight: 700; font-size: 1.1rem; margin-bottom: 4px; }
.salida-serie-art { font-size: 0.9rem; color: var(--c-text-soft, #6c757d); margin-bottom: 6px; }
.salida-serie-hint { font-size: 0.82rem; color: var(--c-text-soft, #6c757d); margin-bottom: 10px; }
.salida-serie-err { color: #b02a37; background: rgba(220,53,69,0.12); border-radius: 8px; padding: 8px 10px; margin: 8px 0; font-size: 0.85rem; font-weight: 600; }
.salida-serie-acciones { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.salida-serie-acciones .btn { flex: 1 1 45%; }

/* Panel de estado de conexión al pie del menú principal (solo online) */
.conexion-panel {
    margin: 18px 4px 8px; padding: 14px;
    border: 1px solid var(--c-border, #e0e0e0); border-radius: 12px;
    background: var(--c-bg-soft, #f7f8fa);
}
.conexion-panel-titulo { font-weight: 700; font-size: 0.95rem; margin-bottom: 8px; }
.conexion-panel-chip { margin-bottom: 8px; }
.conexion-panel-texto { font-size: 0.85rem; color: var(--c-text-soft, #6c757d); margin-bottom: 8px; }
.conexion-panel-estados { margin: 0; padding-left: 18px; font-size: 0.82rem; color: var(--c-text-soft, #6c757d); }
.conexion-panel-estados li { margin: 2px 0; }
.conexion-panel-btn { margin-top: 10px; width: 100%; }

/* === Detalle === */
.detail-page {
    flex: 1;
    overflow-y: auto;
}

.tabs {
    display: flex;
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    position: sticky;
    top: 0;
    z-index: 5;
    /* MV8: los botones de pestaña quedaban pegados unos a otros (0px) y a los
       bordes laterales de la pantalla — separación mínima entre controles
       (--esp-chip) y del borde lateral (--esp-borde), aunque sea una barra de
       pestañas de ancho completo. */
    gap: var(--esp-chip);
    padding: 0 var(--esp-borde);
}

.tab {
    flex: 1;
    text-align: center;
    padding: var(--space-3);
    border: none;
    background: transparent;
    cursor: pointer;
    font-size: 0.9375rem;
    font-weight: 500;
    color: var(--c-text-soft);
    border-bottom: 3px solid transparent;
    min-height: 48px;
    font-family: inherit;
}

.tab.active {
    color: var(--c-primary);
    border-bottom-color: var(--c-primary);
    font-weight: 700;
}

.tab-content {
    padding: var(--space-4);
}

.section-title {
    font-size: 0.6875rem;
    text-transform: uppercase;
    color: var(--c-text-soft);
    margin: var(--space-5) 0 var(--space-2);
    font-weight: 700;
    letter-spacing: 0.8px;
}

.section-title:first-child {
    margin-top: 0;
}

.field {
    background: var(--c-card);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-2);
    box-shadow: var(--shadow-1);
    border: 1px solid var(--c-border);
}

.field-label {
    font-size: 0.6875rem;
    color: var(--c-text-soft);
    margin-bottom: 2px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 600;
}

.field-value {
    font-size: var(--font-size-big);
    word-break: break-word;
}

.field-value.big {
    font-size: 1rem;
    line-height: 1.5;
    white-space: pre-wrap;
}

.field-link {
    color: var(--c-primary);
    text-decoration: none;
    font-weight: 600;
}

/* Acciones flotantes (bottom sheet) */
.action-bar {
    position: sticky;
    bottom: 0;
    background: var(--c-card);
    border-top: 1px solid var(--c-border);
    padding: var(--space-3) var(--space-4) calc(var(--space-3) + env(safe-area-inset-bottom));
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
    gap: var(--space-2);
    z-index: 5;
    box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
}

/* === Galería de fotos === */
.gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: var(--space-2);
    margin-top: var(--space-3);
}

.gallery img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-sm);
    cursor: pointer;
    background: var(--c-border);
    border: 1px solid var(--c-border);
}

/* Celda de foto con botón de borrar superpuesto */
.foto-item {
    position: relative;
    aspect-ratio: 1;
}

.foto-item img {
    width: 100%;
    height: 100%;
}

.foto-del {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: none;
    background: rgba(200, 30, 30, 0.92);
    color: #fff;
    font-size: 1rem;
    line-height: 30px;
    text-align: center;
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
    padding: 0;
}

.foto-del:active {
    background: rgba(160, 20, 20, 0.95);
}

/* Miniatura de croquis: ligeramente más grande (los SVG inflados tienen más
   detalle que una foto JPG y necesitan algo más de área para verse). Además
   fondo blanco (los croquis se renderizan con bg blanco) y object-fit: contain
   para no recortar el dibujo. */
.croquis-item img {
    object-fit: contain !important;
    background: #fff;
    border: 1px solid var(--c-border);
}

.upload-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    border: 2px dashed var(--c-primary);
    background: var(--c-primary-soft);
    border-radius: var(--radius-sm);
    color: var(--c-primary);
    cursor: pointer;
    aspect-ratio: 1;
    font-size: 0.875rem;
    font-weight: 600;
    text-align: center;
    padding: var(--space-2);
}

.upload-card input[type="file"] {
    display: none;
}

/* === Historial === */
.historial-item {
    background: var(--c-card);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-2);
    box-shadow: var(--shadow-1);
    border: 1px solid var(--c-border);
    border-left: 3px solid var(--c-primary);
}

.historial-meta {
    font-size: 0.6875rem;
    color: var(--c-text-muted);
    margin-bottom: var(--space-1);
    display: flex;
    justify-content: space-between;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.historial-tipo {
    font-weight: 700;
    color: var(--c-primary);
    text-transform: uppercase;
    letter-spacing: 0.4px;
}

/* === RONDA 147 §2 — BLOQUEO CON MOTIVO (esperas largas: finalizar la visita) ===
   Velo modal que NO se cierra tocando fuera: mientras dura, la pantalla de debajo no se
   puede tocar y el botón no se puede volver a pulsar. Por encima de las hojas (z 100) para
   que un sheet abierto no se le cuele por delante. */
.bloqueo-velo {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 130;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
    backdrop-filter: blur(2px);
}
.bloqueo-caja {
    background: var(--c-card);
    color: var(--c-text);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
    max-width: 22rem;
    width: 100%;
    text-align: center;
    box-shadow: var(--shadow-3);
}
.bloqueo-caja .spinner { margin: 0 auto var(--space-3); }
.bloqueo-titulo { font-weight: 600; margin-bottom: 4px; }
.bloqueo-fase { font-size: 0.9rem; }
.bloqueo-motivo { font-size: 0.8rem; margin-top: var(--space-3); }

/* === Bottom sheet === */
.sheet-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 100;
    display: flex;
    align-items: flex-end;
    backdrop-filter: blur(2px);
}

.sheet {
    background: var(--c-card);
    width: 100%;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding: var(--space-4);
    padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom));
    max-height: 80vh;
    overflow-y: auto;
    /* RONDA 39 §1 — UNA HOJA NO SE DESPLAZA DE LADO, NUNCA. Con `overflow-y: auto` y
       `overflow-x: visible` el navegador calcula `overflow-x: auto`, así que cualquier hijo
       más ancho que la hoja la convierte en panel paneable: el vendedor intentaba bajar por
       la hoja de las cascadas y se le iba a la derecha, con la mitad de la pantalla en
       blanco («no veo el elemento que lo descuadre» — lo que sobresalía era el hueco vacío
       de un <select> con el nombre largo de la encimera). Los hijos que SÍ necesitan
       desplazarse a lo ancho (la botonera `.pr-sticky`) llevan su propio scroller y siguen
       funcionando dentro de este `hidden`. */
    overflow-x: hidden;
    box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.2);
}

.sheet h2 {
    margin: 0 0 var(--space-3);
    font-size: var(--font-size-h2);
    color: var(--c-text);
}

.sheet-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* Título de sección dentro de un sheet (p. ej. «🧩 Plantillas» al imprimir un
   presupuesto): separa grupos de botones sin recurrir a otro modal. */
.sheet-seccion {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--c-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-top: var(--space-2);
    padding-top: var(--space-2);
    border-top: 1px solid var(--c-border);
}

/* Descripción de la plantilla, debajo de su nombre en el sheet de imprimir: es lo
   que distingue dos plantillas parecidas («detallado» vs «sin detallar»). */
.plantilla-detalle {
    display: block;
    margin-top: 2px;
    font-size: 0.8rem;
    font-weight: 400;
    line-height: 1.35;
    opacity: 0.85;
}

/* Botones del sheet: que NO se trunquen — admiten varias líneas y alinean a la izquierda. */
.sheet .btn {
    white-space: normal;
    text-align: left;
    line-height: 1.3;
    padding: 12px 14px;
    height: auto;
    min-height: 44px;
}

/* Chips/atajos de plantillas de solución dentro del sheet de "cerrar incidencia".
   Se muestran como botones pequeños en flujo, no a ancho completo como los del sheet. */
.solucion-chips {
    display: flex;
    flex-wrap: wrap;
    /* MV12: los chips envuelven en varias líneas → la separación ENTRE LÍNEAS es
       --esp-fila y la de chips de la misma línea --esp-chip. Con el `gap: 6px`
       único que había antes, las líneas quedaban a 6px (por debajo del mínimo) y
       el auditor de márgenes lo marcaba como fallo. */
    row-gap: var(--esp-fila);
    column-gap: var(--esp-chip);
    margin-top: var(--esp-chip);
}
.sheet .btn.solucion-chip {
    width: auto;
    flex: 0 1 auto;
    padding: 8px 12px;
    min-height: 36px;
    font-size: 0.875rem;
    line-height: 1.2;
    border-radius: 20px;
    background: var(--c-bg-soft, #eef2f6);
    color: var(--c-text);
    border: 1px solid var(--c-border);
}
.sheet .btn.solucion-chip:hover {
    background: var(--c-primary);
    color: #fff;
    border-color: var(--c-primary);
}

/* El sheet de solución es más alto para que se vea bien el campo + chips + dictado. */
.sheet.sheet-solucion {
    max-height: 92vh;
}

/* Cabecera meta del visor de firma (firmante, fecha, estado al que se firmó). */
.firma-view-meta {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 0.8125rem;
    color: var(--c-text);
    margin: 4px 0 10px;
    padding: 8px 10px;
    background: var(--c-bg-soft, #f5f6f8);
    border-radius: 6px;
}

/* === Vacío / loading === */
.empty, .loading {
    text-align: center;
    padding: var(--space-6);
    color: var(--c-text-soft);
}

.spinner {
    border: 3px solid var(--c-border);
    border-top-color: var(--c-primary);
    border-radius: 50%;
    width: 36px;
    height: 36px;
    animation: spin 0.8s linear infinite;
    margin: var(--space-3) auto;
}

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

/* === Toast === */
#toast-container {
    position: fixed;
    bottom: calc(80px + env(safe-area-inset-bottom));
    left: 50%;
    transform: translateX(-50%);
    z-index: 200;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    width: calc(100% - var(--space-5) * 2);
    max-width: 480px;
    pointer-events: none;
}

.toast {
    background: var(--c-text);
    color: #fff;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    font-size: 0.9375rem;
    font-weight: 500;
    box-shadow: var(--shadow-2);
    animation: slideUp 0.25s ease;
    pointer-events: auto;
    /* RONDA 58 §6: el globo se cierra al tocarlo, así que se comporta como un botón
       (el manejador vive en core/ui.js, un solo sitio para toda la app). */
    cursor: pointer;
    transition: opacity 0.25s ease;
}

.toast.error { background: var(--c-danger); }
.toast.success { background: var(--c-success); }
.toast.warning { background: var(--c-warning); }

@keyframes slideUp {
    from { transform: translateY(100%); opacity: 0; }
    to   { transform: translateY(0); opacity: 1; }
}

/* === Offline banner === */
.offline-banner {
    background: var(--c-warning);
    color: #fff;
    text-align: center;
    padding: var(--space-2);
    font-size: 0.875rem;
    font-weight: 600;
}

/* Aviso de versión nueva disponible (core/actualizacion.js). Solo aparece cuando NO
   se puede recargar sola la app (hay algo escrito sin guardar, o ya se intentó una
   recarga por esta misma versión). Colores fijos: debe leerse en claro y en oscuro. */
.update-banner {
    display: flex; align-items: center; justify-content: center; gap: var(--space-3);
    flex-wrap: wrap;
    background: #1f6f3d; color: #fff;
    text-align: center; padding: var(--space-2) var(--space-3);
    font-size: 0.875rem; font-weight: 600; cursor: pointer;
}
.update-banner-btn {
    background: #fff; color: #1f6f3d; border: 0; border-radius: 6px;
    padding: 6px 14px; font-size: 0.875rem; font-weight: 700; cursor: pointer;
}
.update-banner-btn:disabled { opacity: .7; cursor: default; }

/* === Firma === */
.firma-page {
    flex: 1;
    display: flex;
    flex-direction: column;
    background: var(--c-bg);
}

.firma-canvas-wrap {
    flex: 1;
    padding: var(--space-3);
    display: flex;
    flex-direction: column;
}

.firma-canvas {
    flex: 1;
    width: 100%;
    background: #fff;
    border: 2px dashed var(--c-border-strong);
    border-radius: var(--radius-sm);
    touch-action: none;
}

/* === Editor de croquis ============================================= */
.croquis-page {
    flex: 1;
    display: flex;
    flex-direction: column;
    background: var(--c-bg);
}
.croquis-toolbar-wrap {
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.croquis-toolbar {
    display: flex;
    align-items: stretch;
    gap: 6px;
    padding: 8px;
    min-width: max-content;
}
.croquis-toolbar .tb-sep {
    width: 1px;
    background: var(--c-border);
    margin: 4px 4px;
}

/* Botón de herramienta: icono SVG arriba + label debajo */
.croquis-toolbar .btn-tool {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-width: 62px;
    padding: 6px 8px;
    border: 1px solid var(--c-border);
    background: var(--c-bg-soft, #eef2f6);
    color: var(--c-text);
    border-radius: 8px;
    cursor: pointer;
    line-height: 1;
}
.croquis-toolbar .btn-tool.active {
    background: var(--c-primary);
    color: #fff;
    border-color: var(--c-primary);
    box-shadow: 0 0 0 2px var(--c-primary) inset, 0 2px 6px rgba(0,0,0,0.15);
}
.croquis-toolbar .btn-tool .btn-svg {
    display: inline-flex;
}
.croquis-toolbar .btn-tool .btn-svg svg {
    display: block;
}
.btn-tool-label {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.2px;
    user-select: none;
}

/* Color swatches con check si activo */
.croquis-toolbar .btn-color {
    width: 38px;
    height: 38px;
    border: 2px solid var(--c-border);
    border-radius: 50%;
    cursor: pointer;
    padding: 0;
    align-self: center;
}
.croquis-toolbar .btn-color.active {
    border-color: var(--c-primary);
    box-shadow: 0 0 0 3px var(--c-primary) inset, 0 0 0 2px #fff inset;
}

/* Grosor preview */
.croquis-toolbar .btn-grosor .grosor-dot {
    display: inline-block;
    border-radius: 50%;
    background: var(--c-text);
}
.croquis-toolbar .btn-grosor.active .grosor-dot {
    background: #fff;
}

/* Action bar inferior con iconos + labels */
.action-bar-croquis .btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 8px 6px;
    line-height: 1;
    min-height: 56px;
}
.action-bar-croquis .btn .btn-svg svg {
    display: block;
}
.btn-accion-label {
    font-size: 0.75rem;
    font-weight: 600;
}

.croquis-canvas-wrap {
    flex: 1;
    padding: var(--space-2);
    display: flex;
    flex-direction: column;
    min-height: 0; /* permite que el canvas se reduzca dentro del flex */
}
.croquis-canvas {
    flex: 1;
    width: 100%;
    background: #fff;
    border: 2px dashed var(--c-border-strong);
    border-radius: var(--radius-sm);
    touch-action: none; /* clave para que pinch+pan no haga scroll del navegador */
}

/* === Texto auxiliar === */
.muted { color: var(--c-text-soft); }
/* Nombre REAL del trabajador junto al alias de usuario (1.178.0):
   «JOSEKA (José Carlos Ordóñez González)». El alias manda —es lo que se escribe
   en una mención y lo que sale en las bolitas de los avatares—, así que el
   paréntesis va un punto más pequeño, en peso normal y con el color suave: se
   lee cuando hace falta y no compite con el alias. Lo pinta
   `js/core/nombrePersona.js`, común al chat y al tablón. */
.nombre-real {
    color: var(--c-text-soft);
    font-weight: 400;
    font-size: 0.92em;
}
.text-center { text-align: center; }
/* MV8: `.mt-2` se usaba en 28 sitios (detail.js, editar.js, croquis.js, tomaDatos.js,
   ajustes.js...) pero NUNCA se había definido — sin CSS, esos botones/campos quedaban
   sin separación (0px) del control anterior. Ej.: sheet "Cita" (Guardar/Quitar/Cancelar). */
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mb-3 { margin-bottom: var(--space-3); }

/* === Búsqueda en lista (cliente / localidad / orden) === */
.list-search {
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    padding: var(--esp-fila) var(--esp-borde);
    display: flex;
    flex-wrap: wrap;
    /* MV8: ver .list-filters — separación entre líneas envueltas != entre chips. */
    row-gap: var(--esp-fila);
    column-gap: var(--esp-chip);
}
/* 1.72.0 — el CAMPO DE BÚSQUEDA ocupa SIEMPRE su propia línea, a ancho completo,
   en todos los buscadores; los botones (Buscar, Cámara…) y los chips caen debajo.
   Antes compartían fila y en pantallas estrechas el campo se quedaba en nada. */
.list-search .filter-input {
    flex: 1 1 100%;
    min-width: 0;
}
/* Los botones de la fila de búsqueda mantienen su ancho natural (si no, el
   width:100% global de .btn-primary los convierte en una barra roja enorme). */
.list-search .btn {
    flex: 0 0 auto;
    width: auto;
    white-space: nowrap;
}
/* Variante histórica "nowrap" (buscador de artículos, almacén): se conserva la
   clase porque la usan varias páginas, pero ya NO fuerza una sola línea — el
   input manda y las acciones se colocan debajo. */
.list-search-nowrap {
    flex-wrap: wrap;
    align-items: center;
}
.list-search-nowrap .filter-input {
    flex: 1 1 100%;
    min-width: 0;   /* clave: si no, el input no encoge y los botones bajan */
}
/* Bug: .btn-primary tiene width:100% global → el botón "Buscar" se comía
   todo el ancho del flexbox y empujaba la cámara fuera del viewport.
   En esta variante "nowrap" forzamos width:auto a TODOS los botones para
   que respeten su contenido y dejen el flex:1 al input. */
.list-search-nowrap .btn {
    flex: 0 0 auto;
    width: auto;
    white-space: nowrap;
}
.art-btn-buscar { padding: 6px 12px; }

/* =============================================================
   BLOQUE DE BÚSQUEDA UNIFICADO (1.89.0, MV4)
   Mismo ORDEN en catálogo de electros/accesorios, buscador de artículos,
   almacén y consulta rápida de artículo:
     1) fila de búsqueda: input (flex) + 📷 escáner — SIEMPRE en una línea
     2) fila de marca: input a toda la anchura
     3) filtros propios de la pantalla (chips/selectores)
     4) chips de orden
     5) chip ⚙ Avanzados
     6) fila de acciones: "🔎 Buscar" grande + "🧹" pequeño
   Ritmo con --esp-borde / --esp-fila / --esp-chip: nada pegado al borde de la
   pantalla ni a otro control, y ningún botón huérfano a media anchura.
   ============================================================= */
.busq-bloque {
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    padding: var(--esp-fila) var(--esp-borde);
    display: flex;
    flex-direction: column;
    gap: var(--esp-fila);
}
.busq-fila {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* MV8: si el contenido de la fila no cabe en una línea (375px) y envuelve,
       la separación entre líneas envueltas es --esp-fila, no --esp-chip (que es
       solo para controles contiguos en la MISMA línea). */
    row-gap: var(--esp-fila);
    column-gap: var(--esp-chip);
}
/* Fila vacía (filtros que no aplican a la pantalla): no deja hueco */
.busq-fila:empty { display: none; }

/* Fila 1 — el 📷 NUNCA salta de línea: nowrap + input que puede encoger */
.busq-fila-buscar { flex-wrap: nowrap; }
.busq-fila-buscar .filter-input { flex: 1 1 auto; min-width: 0; }
.busq-fila-buscar .btn {
    flex: 0 0 auto;
    width: auto;
    white-space: nowrap;
    padding: var(--space-2) var(--space-3);
    min-height: 40px;
}

/* Fila 2 — marca a toda la anchura */
.busq-fila-marca .filter-input { flex: 1 1 100%; min-width: 0; }

/* Filas de filtros: los inputs comparten línea sin pegarse */
.busq-fila .filter-input { min-width: 130px; }
.busq-fila .filter-select { max-width: 100%; }

/* Fila final — Buscar grande + 🧰🔎 + 🧹 pequeños (cuadrados, nunca a media anchura) */
.busq-acciones { flex-wrap: nowrap; }
.busq-acciones .busq-btn-buscar {
    flex: 1 1 auto;
    width: auto;
    min-height: 44px;
}
.busq-acciones .busq-btn-limpiar,
.busq-acciones .busq-btn-avanzada {
    flex: 0 0 auto;
    width: auto;
    min-width: 48px;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    font-size: 1.05rem;
}

/* Fila de PRESENTACIÓN (1.93.0, MV8): "Por pág. [100]" a la izquierda y el
   toggle de vista ☰/▦ a la derecha, justo encima de los botones de acción.
   El .art-vista-grupo ya se empuja solo con su margin-left:auto. */
.busq-fila-presentacion { align-items: center; }
.busq-fila-presentacion .art-pp-lbl { margin-left: 0; }

/* =============================================================
   CONTENEDOR ÚNICO DE FILTROS CON WRAP (1.99.9, MV11)
   El buscador de artículos vuelve al estilo compacto pre-1.87: en lugar de una
   fila apilada por grupo de filtros, TODOS fluyen en un solo contenedor con
   wrap — el mismo contrato de flex/gaps que `.list-filters` (row-gap
   --esp-fila entre líneas envueltas, column-gap --esp-chip entre controles de
   la misma línea), pero sin repetir el fondo/borde/padding que ya aporta
   `.busq-bloque`.
   Los tamaños compactos NO son nuevos: son los mismos de `.chip.chip-orden` /
   `.chip-grupo .chip` (padding 4px 10px, 0.78rem) y el min-height 36px de
   `.chip`.
   ============================================================= */
.busq-fila-filtros {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    row-gap: var(--esp-fila);
    column-gap: var(--esp-chip);
}
/* `.busq-plegable` (del componente de filtros colapsables) pone
   flex-direction:column — aquí manda la fila con wrap (2 clases > 1). */
.busq-plegable.busq-fila-filtros {
    flex-direction: row;
    flex-wrap: wrap;
    row-gap: var(--esp-fila);
    column-gap: var(--esp-chip);
    gap: var(--esp-fila) var(--esp-chip);
}
/* Chips y botones del contenedor, en su variante compacta. El alto/typo son los
   de `.chip.chip-orden` (0.78rem, min-height 36px de `.chip`); el padding lateral
   baja al escalón --space-2 del sistema para que quepan 3-4 controles por línea
   a 375px (medido: con 4px 10px salían 6 líneas, con --space-1/--space-2 salen 5). */
.busq-fila-filtros .chip { padding: var(--space-1) var(--space-2); font-size: 0.78rem; }
/* `.chip-grupo .chip` y `.chip.chip-orden` se declaran MÁS ABAJO en este fichero
   (misma especificidad) → hay que repetirlos aquí para que no vuelvan a 4px 10px. */
.busq-fila-filtros .chip-grupo .chip,
.busq-fila-filtros .chip.chip-orden { padding: var(--space-1) var(--space-2); }
/* El grupo de ESTADO es UN control de tres posiciones: sus chips van más juntos
   entre sí (--space-1) que con el resto de filtros (--esp-chip). */
.busq-fila-filtros .chip-grupo { column-gap: var(--space-1); }
.busq-fila-filtros .btn {
    flex: 0 0 auto;
    width: auto;              /* .btn-primary trae width:100% global */
    white-space: nowrap;
    min-height: 36px;
    padding: var(--space-1) var(--space-2);
    font-size: 0.78rem;
}
/* La marca fluye como un control más (≈12 caracteres), no a ancho completo.
   Desde la 1.168.0 el control es `.marcas-multi` (typeahead con chips) y su
   ancho vive con el resto de su bloque, al final de esta hoja. */
/* "Por pág." + su desplegable son un solo bloque para el wrap */
.art-pp-grupo { display: inline-flex; align-items: center; gap: var(--space-1); }
.art-pp-grupo .art-pp-lbl { margin-left: 0; }
/* La flecha manual ▴/▾ va como último elemento del propio contenedor */
.busq-fila-filtros .filtros-plegar { min-width: 40px; min-height: 36px; padding: 4px 10px; }

/* Chips del grupo de ORDEN (1.94.0, MV9; orden definitivo en 1.95.0, MV10).
   Los tres van CONTIGUOS en la fila de "⭐ Recomendados", pero sin contenedor
   propio: el aspecto compacto que antes les daba .chip-grupo lo llevan los
   propios chips, así que se ven iguales estén donde estén y la fila puede
   partirse con el wrap normal de .busq-fila si no cabe (375 px). */
.chip.chip-orden { padding: 4px 10px; font-size: 0.78rem; }

/* ===== Icono COMPUESTO 🧰 + 🔎 (1.93.0, MV8) =====
   No hay emoji de "caja de herramientas con lupa": se monta con dos spans
   dentro del botón — la caja de fondo y la lupa pequeña abajo-derecha, con un
   halo del color del botón para que se lea sobre la caja. Reutilizable en
   cualquier botón-icono. */
.icono-lupa-caja {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.icono-lupa-caja .ilc-caja { font-size: 1.15rem; line-height: 1; }
.icono-lupa-caja .ilc-lupa {
    position: absolute;
    right: 2px;
    bottom: 1px;
    font-size: 0.68rem;
    line-height: 1;
    text-shadow: 0 0 2px var(--c-card, #fff), 0 0 3px var(--c-card, #fff);
    pointer-events: none;
}

/* Variante EMBEBIDA: el contenedor padre ya aporta el margen lateral
   (catálogo: .cat-page) — el bloque solo pone el ritmo vertical. */
.busq-bloque-embebido {
    padding: 0;
    background: none;
    border-bottom: none;
}

/* =============================================================
   FILTROS COLAPSABLES (1.90.0 MV5; plegado PARCIAL desde 1.92.0 MV7)
   Al buscar con resultados se pliega SOLO el bloque central de filtros y en su
   sitio queda la cabecera "▸ Filtros [chips activos] · N". Quedan siempre a la
   vista la fila 1 (texto + 📷), la fila 2 (marca), la fila de acciones
   (🔎 Buscar + 🧹 + flecha ▴/▾) y el 🧰 del catálogo. Tocar la cabecera —o la
   flecha ▾— despliega. Mismo ritmo (--esp-*) que el resto.
   ============================================================= */
.busq-plegable {
    display: flex;
    flex-direction: column;
    gap: var(--esp-fila);
}
/* Plegado SIN animación de altura, a propósito: con una transición de
   max-height el estado final depende de que el navegador avance los frames
   (con la app en segundo plano o el render congelado el bloque se quedaba a
   medio plegar). display:none es determinista y no deja hueco muerto ni gap
   del flex padre. */
.busq-plegable.busq-plegado { display: none; }

/* Cabecera táctil (solo visible en estado plegado) */
.filtros-cab {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--esp-chip);
    width: 100%;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    background: var(--c-bg-soft);
    color: var(--c-text);
    font-size: 0.85rem;
    text-align: left;
    cursor: pointer;
}
.filtros-cab:active { opacity: 0.85; }
.filtros-cab-flecha { color: var(--c-text-soft); font-size: 0.9rem; }
.filtros-cab-tit { font-weight: 700; white-space: nowrap; }
.filtros-cab-chips {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--esp-chip);
    min-width: 0;
}
/* Chips del resumen: informativos (no editables) — tocarlos despliega igual,
   porque son spans DENTRO del botón de la cabecera. */
.filtros-cab-chip {
    display: inline-block;
    max-width: 100%;
    padding: 2px var(--space-2);
    border-radius: 999px;
    background: var(--c-card);
    border: 1px solid var(--c-border);
    color: var(--c-text);
    font-size: 0.78rem;
    line-height: 1.5;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.filtros-cab-vacio { color: var(--c-text-soft); font-size: 0.8rem; }
.filtros-cab-total { color: var(--c-text-soft); font-size: 0.8rem; white-space: nowrap; }

/* Flecha MANUAL de plegado (1.91.0, MV6; bidireccional desde 1.92.0, MV7):
   al lado del "🔎 Buscar" y del 🧹 de la fila final, que NO se pliega. ▴ pliega
   aunque no haya resultados y sin lanzar búsqueda (no depende de la preferencia
   de ⚙ Ajustes) y ▾ vuelve a abrir. Área táctil 44 px como el resto. */
.filtros-plegar {
    flex: 0 0 auto;
    width: auto;
    min-width: 48px;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    background: var(--c-bg-soft);
    color: var(--c-text-soft);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
}
.filtros-plegar:active { opacity: 0.85; }
/* Fila propia (pantallas sin fila de acciones): la flecha, a la derecha. */
.busq-fila-plegar { justify-content: flex-end; }

/* Campos de FILTRO de los buscadores. 1.211.0: eran los «cuadros de búsqueda
   diminutos» que reportó el usuario — 0,875 rem (14,9 px con la letra al 100 %)
   y 36 px de alto, por debajo del área táctil normal de la app y, sobre todo,
   por debajo de los 16 px a partir de los cuales iOS deja de hacer zoom al
   enfocar el campo. Ahora miden lo mismo que un `.form-input` y siguen
   ESCALANDO con la preferencia de tamaño de letra (van en rem, no en px). */
.filter-input {
    flex: 1 1 140px;
    min-width: 120px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-size: 1rem;
    color: var(--c-text);
    background: var(--c-card);
    min-height: 44px;
}

.topbar-action-primary {
    background: var(--c-primary);
    color: #fff;
    font-weight: 700;
}
.topbar-action-primary:active { background: var(--c-primary); opacity: .85; }

/* === Formulario de nueva incidencia === */
.form-page {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.form-label {
    font-weight: 700;
    font-size: 0.875rem;
    margin-top: var(--space-3);
    color: var(--c-text);
}

.form-input {
    width: 100%;
    padding: var(--space-3);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    /* 1.211.0: sin `font-family: inherit` un <select> —el filtro de EMPRESA de los
       buscadores— se pinta con la tipografía por defecto del sistema y no con la
       de la app: se veía más pequeño que los campos de al lado. */
    font-family: inherit;
    font-size: 1rem;
    color: var(--c-text);
    background: var(--c-card);
    box-sizing: border-box;
    min-height: 44px;
    /* RONDA 39 §1: ningún campo puede pedir más ancho del que le da su contenedor. Un
       <select> con opciones largas (los materiales de encimera, «ENCIMERA NATURAMIA (2CM)-
       GRUPO 1 - PULIDO - WARWICK RUBI», 537 px de contenido en una hoja de 343 px útiles) es
       el único elemento de las hojas del plano que se pasa: Chrome lo recorta solo, pero
       WebKit no encoge los controles por debajo de su contenido y arrastraba la hoja entera.
       El texto largo se sigue leyendo entero en el título de la fila y en las cifras. */
    min-width: 0;
    max-width: 100%;
}

/* El valor elegido se corta con puntos suspensivos en vez de a hachazo (el desplegable
   nativo sigue enseñando el nombre completo al abrirse). */
select.form-input {
    text-overflow: ellipsis;
}

textarea.form-input { resize: vertical; }

/* L1/A3 — estilo de SOLO LECTURA. Una visita completada se pinta con todos los
   controles deshabilitados: por defecto el navegador (y iOS con especial saña)
   los agrisa hasta hacerlos ilegibles. Aquí el dato se lee a color pleno y lo
   que cambia es el CONTINENTE: fondo suave y borde discontinuo = "no se toca".
   `-webkit-text-fill-color` es imprescindible: sin él iOS ignora el `color`. */
.form-input:disabled {
    background: var(--c-bg-soft);
    color: var(--c-text);
    -webkit-text-fill-color: var(--c-text);
    opacity: 1;
    border-style: dashed;
}
/* Chips deshabilitados: los NO elegidos se apagan y el elegido se lee entero
   (en solo lectura importa el valor marcado, no la lista de posibilidades). */
.chip[disabled] { opacity: 0.45; cursor: default; }
.chip.active[disabled] { opacity: 1; }

/* iOS Safari da a los input date/time un ancho intrínseco propio que ignora
   width:100% y se desborda por la derecha (texto solapando el borde). Forzamos
   appearance none + min-width 0 para que respeten el ancho del contenedor. */
input[type="date"].form-input,
input[type="datetime-local"].form-input,
input[type="time"].form-input {
    -webkit-appearance: none;
    appearance: none;
    min-width: 0;
    max-width: 100%;
    text-align: left;
}
/* El indicador nativo del datepicker en WebKit puede empujar el contenido;
   lo alineamos para que no se solape. */
input[type="date"].form-input::-webkit-date-and-time-value {
    text-align: left;
}
/* 1.149.0 — los `type="search"` traen en WebKit su propia apariencia (cápsula
   gris y aspa nativa) que se cuela por encima del borde/fondo de `.form-input`:
   el buscador de personas del chat se veía distinto al resto de campos. */
input[type="search"].form-input {
    -webkit-appearance: none;
    appearance: none;
    min-width: 0;
}
/* El aspa de "limpiar" se deja: es útil y ya no rompe el borde del campo. */

.albaran-resultados {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    max-height: 260px;
    overflow-y: auto;
}

.albaran-item {
    text-align: left;
    background: var(--c-card);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    /* Más padding (antes 2/3 quedaba ahogado en móvil) y un poco más de aire
       entre cards consecutivas. */
    padding: 10px 12px;
    margin-bottom: 8px;
    cursor: pointer;
    /* Anchura completa para que el botón ocupe toda la fila — antes el botón
       <button> heredaba un ancho propio que dejaba huecos a la derecha. */
    display: block;
    width: 100%;
    box-sizing: border-box;
}
.albaran-item:active { background: var(--c-primary-soft); }
.albaran-num { font-weight: 700; color: var(--c-primary, #1976d2); }
.albaran-cli { font-size: 0.875rem; font-weight: 600; margin-top: 3px; }
.albaran-loc { font-size: 0.8125rem; color: var(--c-text-soft); margin-top: 2px; }
.albaran-desc {
    font-size: 0.8125rem; color: var(--c-text); margin-top: 2px;
    font-style: italic;
    background: var(--c-bg-soft); padding: 2px 6px; border-radius: 4px;
    display: inline-block;
}
.albaran-row-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
/* Fila de metadata: chips (tipo venta + empresa) a la izda + fecha a la dcha.
   flex-wrap permite que, si el chip de empresa es muy largo, la fecha baje a
   una nueva línea sin pisar nada. La fecha conserva su sitio (margin-left:auto). */
.albaran-row-meta {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: 4px 6px; margin-top: 4px;
}
.albaran-chips {
    display: inline-flex; gap: 4px; flex-wrap: wrap; min-width: 0;
    flex: 1 1 auto;
}
.albaran-chip {
    font-size: 0.6875rem; padding: 1px 6px; border-radius: 10px;
    background: var(--c-bg-soft); color: var(--c-text-soft);
    border: 1px solid var(--c-border);
    max-width: 220px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Tipo de venta: azul tenue (distintivo pero discreto). */
.albaran-chip-tv {
    background: #e3f2fd; color: #1565c0; border-color: #bbdefb;
}
/* Empresa: gris MUY suave — antes morado, demasiado llamativo. Lo dejamos
   neutro para que no compita visualmente con el resto de la info. */
.albaran-chip-emp {
    background: var(--c-bg-soft, #eef2f6);
    color: var(--c-text-soft, #6c757d);
    border-color: var(--c-border, #e6e6e6);
    font-weight: 500;
}
.albaran-fecha {
    font-size: 0.6875rem; color: var(--c-text-muted, #969696); white-space: nowrap;
    margin-left: auto;
}
/* Tarjeta de feedback del material auto-cargado en new-servicio. */
.material-card .material-info {
    padding: 6px 10px;
    border-radius: 6px;
    background: var(--c-bg-soft);
    color: var(--c-text);
}
.material-card .material-info.ok    { background: rgba(44, 143, 63, 0.16); color: var(--c-success); }
.material-card .material-info.warn  { background: rgba(240, 160, 32, 0.16); color: #b27500; }
.material-card .material-info.muted { color: var(--c-text-soft); }

/* Albarán que YA tiene incidencias abiertas: ribete naranja izq + badge. */
.albaran-item-warn { border-left: 4px solid var(--c-warning); }
.badge-incidencias-abiertas {
    font-size: 0.75rem;
    font-weight: 700;
    background: var(--c-warning);
    color: #fff;
    padding: 2px 8px;
    border-radius: 10px;
    white-space: nowrap;
}

/* Lista de material en nueva incidencia para servicio técnico (#104).
   Una fila = [cantidad][descripción ancha][botón quitar]. Compacta para
   que entren varias líneas en pantalla móvil. */
.material-lista { display: flex; flex-direction: column; gap: 6px; margin: 6px 0 8px; }
.material-row { display: flex; gap: 6px; align-items: center; }
.material-row .mat-cant { flex: 0 0 80px; text-align: right; }
.material-row .mat-desc { flex: 1 1 auto; min-width: 0; }
.material-row .mat-del  { flex: 0 0 44px; padding: 8px 0; font-weight: 700; }

.albaran-seleccion .card.selected,
.card.selected {
    border-left: 4px solid var(--c-primary);
}

/* Toggle de dirección de orden (asc/desc) */
.dir-toggle {
    min-width: 40px;
    text-align: center;
    font-weight: 700;
    cursor: pointer;
}

/* Cabecera de grupo (agrupar por albarán) */
.group-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-2);
    margin: var(--space-4) var(--space-1) var(--space-1);
    padding: var(--space-3);
    background: var(--c-primary-soft);
    color: var(--c-primary);
    border-radius: var(--radius-sm);
    position: sticky;
    top: 0;
    z-index: 1;
}

.group-header.sel {
    outline: 2px solid var(--c-primary);
}

.group-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
}

.group-title-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    /* Con el badge "⏱ N en curso" (MV16) la fila puede no caber a 375px: se
       envuelve en vez de desbordar la columna de texto de la cabecera. */
    flex-wrap: wrap;
    row-gap: var(--esp-chip);
}

.group-title {
    font-weight: 700;
    font-size: 0.9375rem;
}

.group-count {
    background: var(--c-primary);
    color: #fff;
    border-radius: 12px;
    padding: 1px 9px;
    font-size: 0.75rem;
    min-width: 22px;
    text-align: center;
}

.group-cliente {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--c-text);
}

.group-dir,
.group-desc {
    font-size: 0.8125rem;
    color: var(--c-text-soft);
    font-weight: 500;
}

.group-desc {
    color: var(--c-text);
}

/* Botón "Seleccionar" del grupo: misma forma que "+ Nueva" (pastilla sólida)
   pero en otro tono (verde azulado) para diferenciarlo. */
.group-action {
    flex: 0 0 auto;
    align-self: center;
    background: #0f766e;
    color: #fff;
    border: none;
    border-radius: 18px;
    padding: 8px 14px;
    font-size: 0.8125rem;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
}

.group-action:active { opacity: .85; }

.group-action.active {
    background: var(--c-text-soft);
}

/* Tarjeta en modo selección múltiple */
.card-body { flex: 1; min-width: 0; }

.card.selectable {
    display: flex;
    align-items: stretch;
    gap: var(--space-3);
}

.card-check {
    flex: 0 0 30px;
    width: 30px;
    height: 30px;
    align-self: center;
    border: 2px solid var(--c-primary);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-card);   /* tic blanco en light, gris oscuro en dark */
    font-weight: 800;
    font-size: 1.0625rem;
    line-height: 1;
    background: var(--c-card);
}

.card-check.on {
    background: var(--c-primary);
    border-color: var(--c-primary);
}

.card.selected {
    border-left: 4px solid var(--c-primary) !important;
    box-shadow: 0 0 0 2px var(--c-primary-soft) inset;
}

/* Barra inferior de selección múltiple (2 filas: contador + acciones) */
.selbar {
    position: sticky;
    bottom: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom));
    background: var(--c-card);
    border-top: 1px solid var(--c-border);
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.12);
    z-index: 5;
}

.selbar-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.selbar-count {
    font-weight: 700;
    font-size: 0.875rem;
}

.selbar-cancel {
    background: none;
    border: none;
    color: var(--c-text-soft);
    font-weight: 700;
    font-size: 0.8125rem;
    cursor: pointer;
}

/* MV12: con 7 acciones (entró "▶ Empezar") una sola fila deja los botones
   ilegibles a 375px → se permite envolver. row-gap --esp-fila entre líneas y
   column-gap --esp-chip entre botones de la misma línea, como el resto de la app. */
.selbar-actions {
    display: flex;
    flex-wrap: wrap;
    row-gap: var(--esp-fila);
    column-gap: var(--esp-chip);
}

.selbar-act {
    /* Base ~72px: caben 4 por fila a 375px y 7 de una en pantallas anchas. */
    flex: 1 1 72px;
    width: auto;
    min-width: 0;
    padding: 10px 2px;
    font-size: 0.75rem;
}

/* Filas de checkbox (pantalla de parte) */
.check-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.9375rem;
    padding: 6px 0;
}

/* Visor de firma del cliente */
.firma-view {
    background: var(--c-card);
    border-radius: var(--radius);
    padding: var(--space-4);
    max-width: 92vw;
    text-align: center;
}

.firma-view-img {
    width: 100%;
    max-height: 60vh;
    object-fit: contain;
    background: #fff;
    border: 1px solid var(--c-border);
    border-radius: 8px;
}

/* Visor de foto a pantalla completa */
.foto-full-view {
    background: var(--c-card);
    border-radius: var(--radius);
    padding: var(--space-3);
    max-width: 96vw;
    text-align: center;
}

.foto-full-img {
    width: 100%;
    max-height: 80vh;
    object-fit: contain;
    background: #000;
    border-radius: 8px;
}

/* Aviso en pantalla de resolución por lotes */
.resolver-aviso {
    background: var(--c-primary-soft);
    color: var(--c-primary);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    font-weight: 600;
    font-size: 0.875rem;
    margin-bottom: var(--space-2);
}

/* === Alta enriquecida: buscar+escanear, dictado === */
.fila-buscar {
    display: flex;
    gap: var(--space-2);
    align-items: stretch;
}

.fila-buscar .form-input {
    flex: 1;
    min-width: 0;
}

.btn-escaner {
    flex: 0 0 auto;
    white-space: nowrap;
    padding: var(--space-2) var(--space-3);
}

.fila-descripcion {
    display: flex;
    gap: var(--space-2);
    align-items: flex-start;
}

.fila-descripcion .form-input {
    flex: 1;
    min-width: 0;
}

.btn-dictar {
    flex: 0 0 auto;
    white-space: nowrap;
    align-self: stretch;
    padding: var(--space-2) var(--space-3);
}

.btn-grabando {
    background: rgba(200, 30, 30, 0.95) !important;
    color: #fff !important;
    animation: pulso-grabando 1.1s ease-in-out infinite;
}

/* Fila contenedora del botón IA — ahora va DEBAJO del textarea full-width
   para no quitarle espacio. El botón ocupa todo el ancho disponible. */
.fila-ia-croquis {
    margin-top: 6px;
    display: flex;
    gap: 6px;
    align-items: stretch;
}
.fila-ia-croquis .btn-ia-croquis {
    flex: 1;
    width: auto;
}
.fila-ia-croquis .btn-ia-ayuda {
    flex: 0 0 44px;
}

/* Botón "IA → Croquis": destacado en violeta para distinguirlo del dictado
   (azul) y de los botones secundarios normales. Siempre visible: el handler
   muestra mensaje explicativo si no hay texto suficiente o red. */
.btn-ia-croquis {
    flex: 0 0 auto;
    white-space: nowrap;
    align-self: stretch;
    padding: var(--space-2) var(--space-3);
    background: linear-gradient(135deg, #7c3aed, #a855f7) !important;
    color: #fff !important;
    border: none;
    font-weight: 600;
}
.btn-ia-croquis:hover:not(:disabled) {
    background: linear-gradient(135deg, #6d28d9, #9333ea) !important;
}
.btn-ia-croquis:disabled {
    opacity: 0.6;
    cursor: progress;
}

/* Boton "?" de ayuda IA: pequeño, mismo color que IA pero outline para
   diferenciarlo del primario. Abre el modal con ejemplos por plantilla. */
.btn-ia-ayuda {
    background: #fff !important;
    color: #7c3aed !important;
    border: 2px solid #a855f7 !important;
    font-weight: 700;
    font-size: 1.15rem;
    padding: 0 var(--space-2);
    min-width: 44px;
    cursor: pointer;
}
.btn-ia-ayuda:hover {
    background: #f5f0ff !important;
}

/* Modal de ayuda IA: scroll vertical (puede ser largo) y bloques por
   categoria con destacado de los ejemplos en mono / cita.
   Usamos las variables --c-* del tema (NO --color-* que no existen y caian
   al fallback claro → texto blanco sobre fondo blanco en dark mode). */
.ayuda-ia-sheet {
    max-height: 85vh;
    overflow-y: auto;
    color: var(--c-text);
}
.ayuda-ia-intro {
    color: var(--c-text-soft);
    margin: 0 0 var(--space-3) 0;
}
/* Caja de consejos: fondo violeta translucido (funciona en light/dark porque
   se mezcla con el color de la sheet). Texto siempre con --c-text. */
.ayuda-ia-consejos {
    background: rgba(168, 85, 247, 0.10);
    border-left: 4px solid #a855f7;
    border-radius: 6px;
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-3);
    color: var(--c-text);
}
.ayuda-ia-consejos h3 {
    margin: 0 0 var(--space-2) 0;
    font-size: 1rem;
    color: #a855f7;  /* violeta vivo, legible en ambos temas */
}
.ayuda-ia-consejos ul {
    margin: 0;
    padding-left: 1.2rem;
    font-size: 0.9rem;
    line-height: 1.45;
    color: var(--c-text);
}
.ayuda-ia-h3 {
    margin: var(--space-3) 0 var(--space-2);
    font-size: 1rem;
    border-bottom: 1px solid var(--c-border);
    padding-bottom: 4px;
    color: var(--c-text);
}
.ayuda-ia-cat {
    margin-bottom: var(--space-3);
}
.ayuda-ia-cat h3 {
    margin: var(--space-2) 0;
    font-size: 0.95rem;
    color: #a855f7;  /* violeta vivo, legible en ambos temas */
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
/* Tarjeta de plantilla: fondo sutil para destacar sobre la sheet sin perder
   contraste en dark. En light usa gris muy claro; en dark un overlay neutro. */
.ayuda-ia-plantilla {
    background: var(--c-bg-soft);
    border: 1px solid var(--c-border);
    border-radius: 6px;
    padding: var(--space-2) var(--space-3);
    margin-bottom: 6px;
}
.ayuda-ia-plantilla-titulo {
    font-weight: 700;
    color: var(--c-text);
    margin-bottom: 2px;
}
.ayuda-ia-plantilla-desc {
    font-size: 0.85rem;
    color: var(--c-text-soft);
    margin-bottom: 4px;
    font-style: italic;
}
/* Ejemplo de texto a dictar: bloque tipo cita. Fondo neutro (no blanco hardcoded). */
.ayuda-ia-ejemplo {
    font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
    font-size: 0.82rem;
    background: var(--c-card);
    border: 1px solid var(--c-border);
    border-left: 3px solid #7c3aed;
    border-radius: 3px;
    padding: 4px 8px;
    margin: 3px 0;
    color: var(--c-text);
}

@keyframes pulso-grabando {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

.gallery-alta {
    margin-bottom: var(--space-3);
}

/* === Escáner de código de barras (overlay pantalla completa) === */
.escaner-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.92);
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
}

.escaner-marco {
    width: 100%;
    max-width: 480px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    position: relative;
}

.escaner-video {
    width: 100%;
    border-radius: var(--radius-md);
    background: #000;
    max-height: 70vh;
    object-fit: cover;
}

.escaner-guia {
    position: absolute;
    top: 50%;
    left: 10%;
    right: 10%;
    height: 2px;
    background: rgba(255, 60, 60, 0.9);
    box-shadow: 0 0 8px rgba(255, 60, 60, 0.8);
    transform: translateY(-50%);
    pointer-events: none;
}

.escaner-texto {
    color: #fff;
    font-size: 0.9375rem;
    font-weight: 600;
    text-align: center;
}

/* === Tiempo por incidencia (botón grande Empezar/Terminar) === */
.tiempo-bloque {
    margin: var(--space-3) 0;
    padding: var(--space-3);
    background: var(--c-card);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-md);
    text-align: center;
}

.btn-tiempo {
    width: 100%;
    border: none;
    border-radius: var(--radius-md);
    color: #fff;
    font-size: 1.375rem;
    font-weight: 800;
    padding: 20px;
    cursor: pointer;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}

.btn-tiempo.empezar { background: #27ae60; }
.btn-tiempo.parar { background: #c0392b; }
.btn-tiempo:active { transform: translateY(1px); }
.btn-tiempo:disabled { opacity: 0.6; }

.tiempo-info {
    margin-top: var(--space-3);
}

.tiempo-total {
    font-size: 1.0625rem;
    font-weight: 700;
    color: var(--c-text);
}

.tiempo-encurso {
    margin-top: 4px;
    font-size: 0.875rem;
    color: #c0392b;
    font-weight: 600;
}

/* === Agenda (citas) === */
.agenda-barra {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    padding: var(--space-2) var(--space-3);
}
.agenda-barra .btn { flex: 1 1 auto; min-width: 90px; }

.agenda-rango {
    font-size: 1rem;
    font-weight: 800;
    color: var(--c-text);
    padding: var(--space-2) var(--space-3) 0;
}

.agenda-offline {
    margin: var(--space-2) var(--space-3);
    padding: 8px 12px;
    background: #fff3cd;
    color: #856404;
    border-radius: var(--radius-sm);
    font-weight: 600;
    font-size: 0.8125rem;
}

.agenda-dia {
    margin: var(--space-3) var(--space-3) var(--space-1);
    font-size: 0.875rem;
    font-weight: 800;
    color: var(--c-primary);
    border-bottom: 2px solid var(--c-border);
    padding-bottom: 4px;
}

.agenda-card {
    margin: var(--space-2) var(--space-3);
    padding: var(--space-3);
    background: var(--c-card);
    border: 1px solid var(--c-border);
    border-left: 5px solid var(--c-primary);
    border-radius: var(--radius-md);
    cursor: pointer;
}

.agenda-cab {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 4px;
}
.agenda-hora { font-size: 1rem; font-weight: 800; color: var(--c-text); }
.agenda-prio {
    font-size: 0.75rem;
    font-weight: 700;
    color: #fff;
    background: var(--c-primary);
    padding: 2px 8px;
    border-radius: 10px;
}
.agenda-cli { font-weight: 700; margin-bottom: 2px; }
.agenda-desc { color: var(--c-text); font-size: 0.875rem; margin-bottom: 4px; }
.agenda-dir, .agenda-tel {
    display: block;
    font-size: 0.875rem;
    color: var(--c-primary);
    text-decoration: none;
    margin-top: 2px;
}
.agenda-notas { font-size: 0.8125rem; color: var(--c-text-secondary, #666); margin-top: 4px; }

/* === Barra global de cronómetro (siempre visible si hay trabajo en curso) === */
.timer-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1200;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    background: #b71c1c;
    color: #fff;
    padding: 10px 14px;
    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
}

.timer-bar-info {
    display: flex;
    flex-direction: column;
    cursor: pointer;
    min-width: 0;
}

.timer-bar-ref {
    font-weight: 800;
    font-size: 0.875rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.timer-bar-time {
    font-size: 1.25rem;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

.timer-bar-stop {
    flex: 0 0 auto;
    border: none;
    border-radius: var(--radius-sm);
    background: #fff;
    color: #b71c1c;
    font-size: 1rem;
    font-weight: 800;
    padding: 12px 18px;
    cursor: pointer;
}

.timer-bar-stop:active { background: #f0f0f0; }

/* La zona de texto de la barra es pulsable (MV16): lleva a la incidencia en
   curso, o al sheet de las activas si hay varias. Feedback táctil sin cambiar
   el tamaño de la barra. */
.timer-bar-info:active { opacity: 0.7; }
.timer-bar-info:focus-visible { outline: 2px solid #fff; outline-offset: 2px; border-radius: var(--radius-sm); }

/* === Incidencias con el cronómetro EN MARCHA (MV16) ===
   Franja izquierda y anillo en el rojo del cronómetro (el mismo de .timer-bar)
   + chip "⏱ En curso" como primera etiqueta de la tarjeta. */
.card.card-tiempo-activo {
    border-left-color: var(--c-tiempo);
    box-shadow: var(--shadow-1), inset 0 0 0 2px var(--c-tiempo-soft);
}

.tag-tiempo-activo {
    background: var(--c-tiempo-soft);
    color: var(--c-tiempo-txt);
    font-weight: 700;
}

/* MV17 — pulso de atención al llegar al listado desde "📋 Ver en el listado"
   del temporizador. Dura ~2 s (3 pulsos) y el JS retira la clase al terminar
   (`animationend`), así que no queda ningún estilo pegado en la tarjeta. */
@keyframes tg-flash-tiempo {
    0%, 100% { box-shadow: var(--shadow-1), inset 0 0 0 2px var(--c-tiempo-soft); }
    50%      { box-shadow: 0 0 0 3px var(--c-tiempo), var(--shadow-2), inset 0 0 0 2px var(--c-tiempo-soft); }
}

.card.card-tiempo-flash {
    animation: tg-flash-tiempo 0.66s ease-in-out 3;
}

/* Con "movimiento reducido" no se anima: se marca de forma estática (el
   temporizador de seguridad del JS es quien retira la clase en este caso). */
@media (prefers-reduced-motion: reduce) {
    .card.card-tiempo-flash {
        animation: none;
        box-shadow: 0 0 0 3px var(--c-tiempo), var(--shadow-1), inset 0 0 0 2px var(--c-tiempo-soft);
    }
}

/* Contador de cronómetros en marcha en la cabecera del albarán, junto al
   contador de incidencias del grupo. */
.group-encurso {
    background: var(--c-tiempo);
    color: var(--c-tiempo-contra);
    border-radius: 12px;
    padding: 1px 9px;
    font-size: 0.75rem;
    font-weight: 700;
    white-space: nowrap;
}

/* === Checklist modal (preview de acciones en grupo) === */
.checklist-intro {
    font-size: 0.8125rem;
    color: var(--c-text-secondary, #555);
    margin-bottom: var(--space-2);
}
.checklist {
    max-height: 50vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.checklist-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 8px;
    border-bottom: 1px solid var(--c-border);
    cursor: pointer;
}
.checklist-item input[type="checkbox"] {
    width: 22px;
    height: 22px;
    margin-top: 2px;
    flex: 0 0 auto;
}
.checklist-texto { min-width: 0; }
.checklist-label { font-weight: 700; font-size: 0.875rem; }
.checklist-sub {
    font-size: 0.75rem;
    color: var(--c-text-secondary, #666);
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

/* === Menú inicial (home) === */
.home-page { padding-bottom: 40px; }
.home-seccion {
    font-size: 0.8125rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--c-text-secondary, #777);
    padding: var(--space-3) var(--space-3) var(--space-1);
}
.home-grid {
    display: grid;
    /* minmax(0, 1fr): sin el minmax, el min-width implícito (auto) de las
     * tarjetas con títulos largos ensancha su columna y la fila se sale
     * del viewport (las tarjetas de la derecha quedaban cortadas). */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
    padding: 0 var(--space-3);
}
/* Pie del menú principal: nombre del trabajador a la izquierda y botón
 * "Cerrar sesión" pequeño a la derecha, en una sola línea. Antes el botón
 * ocupaba todo el ancho debajo del nombre y resultaba demasiado prominente. */
.home-salir {
    padding: var(--space-5) var(--space-3) var(--space-3);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
}
.home-usuario {
    flex: 1 1 auto;
    min-width: 0;
    font-weight: 600;
    color: var(--color-text-muted, #666);
    font-size: 0.95rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.home-salir-btn {
    flex: 0 0 auto;
    padding: 6px 14px;
    font-size: 0.88rem;
}
.home-card {
    display: flex;
    align-items: center;
    gap: 10px;
    text-align: left;
    padding: 16px 12px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-md);
    background: var(--c-card);
    cursor: pointer;
    min-height: 76px;
    min-width: 0;
}
.home-card:active { background: var(--c-primary-soft); }
.home-card-off { opacity: .5; cursor: default; }
/* position:relative SIEMPRE: es el ancla del badge (nº pendientes/borradores).
   Sin ella el badge absolute se ancla al viewport y aparece en la CABECERA,
   tapado detrás del botón ✎ (bug v1.14-1.21.1 en la tarjeta de toma de datos). */
.home-card-ico { font-size: 1.875rem; flex: 0 0 auto; position: relative; }
/* El contenedor del texto (div sin clase) debe poder encoger para que el
 * título haga wrap dentro de la tarjeta en vez de ensancharla. */
.home-card > div:not(.home-card-ico) { min-width: 0; }
.home-card-tit { font-weight: 800; font-size: 0.9375rem; color: var(--c-text); overflow-wrap: break-word; }
.home-card-sub { font-size: 0.75rem; color: var(--c-text-secondary, #777); overflow-wrap: break-word; }

/* === Asignar incidencias === */
/* MV8: contenedor de resultados (tarjeta del albarán + lista de incidencias +
   selector de montador + botón Asignar) — mismo ritmo vertical que .form-page,
   para que ningún control quede pegado al de encima/debajo. */
.asignar-panel {
    display: flex;
    flex-direction: column;
    gap: var(--esp-fila);
}
.asignar-item {
    padding: 8px 10px;
    border-bottom: 1px solid var(--c-border);
}
.asignar-ref { font-weight: 700; font-size: 0.875rem; }
.asignar-desc { font-size: 0.8125rem; color: var(--c-text-secondary, #666); }
.asignar-nota {
    margin: var(--space-2) 0;
    font-weight: 600;
    color: var(--c-primary);
}

/* Footer común: logo + versión, presente en todas las pantallas. Al tocarlo actualiza la app.
   RONDA 31 §5 — COMPACTO en TODA la app («reduce el pie de página general de toda la app»):
   lo que hasta la 2.0.12 solo tenía el visor de planos. Sigue siendo tocable: 30 px de alto
   útil más el área del logo, y el `min-height` garantiza el objetivo táctil aunque el
   usuario tenga la tipografía al mínimo. */
.app-footer {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 6px 12px calc(6px + env(safe-area-inset-bottom, 0px));
    min-height: 30px;
    box-sizing: border-box;
    margin-top: 6px;
    border-top: 1px solid var(--c-border);
    color: var(--c-text-secondary, #777);
    font-size: 0.68rem;
    cursor: pointer;
    user-select: none;
    background: var(--c-bg, #fff);
}
.app-footer:active { background: #f0f0f0; }
.app-footer-logo { width: 14px; height: 14px; opacity: .85; }

/* === Pendientes de sincronizar (almacén offline) === */
.pend-cont { padding: 0 var(--space-3) var(--space-4); }
.pend-cab {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-3); padding: var(--space-3) 0;
    flex-wrap: wrap;
}
.pend-resumen { display: flex; flex-direction: column; gap: 2px; }
.pend-resumen-tot { font-weight: 700; }
.pend-resumen-err { color: var(--c-danger, #c62828); font-size: 0.88rem; }
.pend-resumen-exp { color: var(--c-warn, #c77800); font-size: 0.88rem; font-weight: 600; }
.pend-sync-btn { white-space: nowrap; }
.pend-sync-btn:disabled { opacity: .5; cursor: not-allowed; }

.pend-grupo-tit {
    margin: var(--space-4) 0 var(--space-2);
    font-size: 0.95rem; color: var(--c-text-soft, #6c757d);
    text-transform: uppercase; letter-spacing: 0.5px;
    border-bottom: 1px solid var(--c-border, #e0e0e0); padding-bottom: 4px;
}

.pend-card {
    background: var(--c-card, #fff);
    border: 1px solid var(--c-border, #e0e0e0);
    border-radius: var(--radius-md, 10px);
    padding: var(--space-3); margin-bottom: var(--space-3);
    box-shadow: var(--shadow-1, 0 1px 2px rgba(0,0,0,.06));
}
.pend-card-exp { border-color: var(--c-danger, #c62828); background: rgba(198,40,40,0.04); }

.pend-card-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-2); margin-bottom: 6px; flex-wrap: wrap;
}
.pend-card-titulo { display: flex; align-items: center; gap: var(--space-2); }
.pend-tipo { font-weight: 700; }
.pend-badge {
    font-size: 0.72rem; padding: 2px 7px; border-radius: 10px; font-weight: 700;
    color: #fff;
}
.pend-badge.badge-info   { background: var(--c-primary, #1976d2); }
.pend-badge.badge-warn   { background: var(--c-warn, #c77800); }
.pend-badge.badge-danger { background: var(--c-danger, #c62828); }

.pend-restante { font-size: 0.82rem; font-weight: 600; }
.pend-verde { color: #2e7d32; }
.pend-ambar { color: #c77800; }
.pend-rojo  { color: #c62828; }
.pend-exp   { color: #c62828; font-weight: 700; }

.pend-card-body { font-size: 0.88rem; color: var(--c-text, #222); line-height: 1.5; }
.pend-fecha { color: var(--c-text-soft, #6c757d); font-size: 0.82rem; }
.pend-error {
    color: var(--c-danger, #c62828); font-size: 0.85rem; margin-top: 4px;
    background: rgba(198,40,40,0.06); padding: 4px 6px; border-radius: 4px;
}

.pend-acciones {
    display: flex; gap: var(--space-2); margin-top: var(--space-2);
    flex-wrap: wrap;
}
.btn-sm { padding: 4px 10px; font-size: 0.82rem; }

/* --- Pendientes de INCIDENCIAS (F4): extras sobre las clases de arriba --- */
/* Enlace a la incidencia afectada dentro de la tarjeta. */
.pend-inc-link {
    background: none; border: none; padding: 0; font: inherit; font-weight: 700;
    color: var(--c-primary, #1976d2); text-decoration: underline; cursor: pointer;
}
/* Badge con el dueño de una operación de otro usuario del dispositivo. */
.pend-badge.pend-badge-ajena { background: var(--c-text-soft, #6c757d); }
/* Aviso que encabeza el bloque de operaciones ajenas. */
.pend-nota-ajenas {
    font-size: 0.85rem; color: var(--c-text-soft, #6c757d);
    background: rgba(108,117,125,0.08); border-radius: var(--radius-md, 10px);
    padding: var(--space-2) var(--space-3); margin-bottom: var(--space-3);
}
/* Miniatura de la foto pendiente, leída del blob del dispositivo. */
.pend-foto-mini {
    display: block; margin-top: var(--space-2); max-width: 100%; max-height: 200px;
    border-radius: var(--radius-md, 10px); border: 1px solid var(--c-border, #e0e0e0);
}

.pend-detalle { max-height: 60vh; overflow-y: auto; font-size: 0.88rem; }
.pend-detalle-lineas { margin-top: 4px; }
.pend-detalle-lin { padding: 3px 0; border-bottom: 1px dashed var(--c-border, #e0e0e0); font-family: ui-monospace, Menlo, monospace; font-size: 0.78rem; }
.pend-detalle-lin-mas { font-style: italic; color: var(--c-text-soft, #6c757d); padding: 6px 0; }

/* Línea de artículo PENDIENTE de resolver (escaneada offline sin red). */
.linea-pendiente {
    background: linear-gradient(180deg, rgba(255,193,7,0.12), rgba(255,193,7,0.04));
    border-left: 4px solid var(--c-warn, #c77800) !important;
}
.linea-pendiente .card-title {
    color: var(--c-warn, #c77800);
    font-style: italic;
}
.linea-pendiente .card-ref::before {
    content: '⏳ ';
}

/* Badge sobre la tarjeta del home (Almacén → Pendientes) */
.home-card-badge {
    position: absolute; top: -4px; right: -8px;
    background: var(--c-danger, #c62828); color: #fff;
    border-radius: 999px; padding: 2px 7px; font-size: 0.72rem; font-weight: 700;
    min-width: 18px; text-align: center; line-height: 1;
    border: 2px solid var(--c-card, #fff);
}

/* ==================== VENTAS — CRM ==================== */

/* Chip de estado con color configurado en BD (estilo inline para fondo/texto) */
.estado-chip {
    display: inline-block;
    padding: 6px 14px;
    border-radius: 16px;
    font-weight: 700;
    font-size: 0.9rem;
    box-shadow: 0 1px 3px rgba(0,0,0,.12);
    text-align: center;
}
.estado-chip-inline {
    display: inline-block; padding: 2px 10px; border-radius: 12px;
    font-weight: 700; font-size: 0.85rem;
    /* Estados con nombre largo: que envuelvan, nunca recortar */
    white-space: normal; overflow-wrap: anywhere; max-width: 100%;
}

/* Pantalla cambiar estado: tarjeta oportunidad */
.op-card {
    background: var(--c-card, #fff);
    border: 1px solid var(--c-border, #e0e0e0);
    border-radius: 12px;
    padding: 14px;
    margin: 10px var(--space-3);
    box-shadow: var(--shadow-1, 0 1px 2px rgba(0,0,0,.06));
}
.op-card-head { display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; }
.op-card-pres { font-weight: 700; font-size: 1.05rem; color: var(--c-primary, #1976d2); }
.op-card-tit  { font-weight: 600; margin-bottom: 4px; }
.op-card-cli  { color: var(--c-text, #222); }
.op-card-tel  { color: var(--c-text-soft, #6c757d); font-size: 0.9rem; margin-top: 4px; }
.op-card-imp  { color: var(--c-text, #222); font-weight: 600; margin-top: 6px; }
.op-card-estado-row { margin-top: 12px; display: flex; align-items: center; gap: 10px; }
.op-card-estado-row .lbl { color: var(--c-text-soft, #6c757d); font-size: 0.9rem; }

.op-flags { display: flex; flex-wrap: wrap; gap: var(--esp-chip); margin-top: var(--esp-fila); }
.flag {
    display: inline-block; padding: 3px 10px; border-radius: 10px;
    font-size: 0.78rem; font-weight: 700;
}
.flag-ok       { background: rgba(46,125,50,0.12); color: #2e7d32; }
.flag-no       { background: rgba(198,40,40,0.12); color: #c62828; }
.flag-cerrada  { background: rgba(108,117,125,0.18); color: #555; }
.flag-medicion { background: rgba(199,120,0,0.12); color: #c77800; }

.op-acciones { display: flex; gap: 10px; margin: 14px var(--space-3); }
.op-acciones .btn { flex: 1; }
.btn-lg { padding: 14px 16px; font-size: 1rem; }
.op-ver-detalle { display: block; margin: 0 var(--space-3) 18px; width: calc(100% - 24px); }

/* Rejilla de estados (al cambiar) — auto-fit + min altura uniforme */
.estados-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 10px;
    padding: 12px var(--space-3) 20px;
}
.estado-btn {
    border: 0; cursor: pointer;
    padding: 14px 12px;
    border-radius: 12px;
    display: flex; flex-direction: column; justify-content: center; align-items: center;
    text-align: center;
    min-height: 80px;
    transition: transform 0.08s, box-shadow 0.08s;
    box-shadow: 0 2px 4px rgba(0,0,0,0.08);
}
.estado-btn:hover { box-shadow: 0 3px 8px rgba(0,0,0,0.16); }
.estado-btn:active { transform: scale(0.96); }
.estado-nom { font-weight: 700; font-size: 0.95rem; line-height: 1.15; word-break: break-word; }
.estado-marcas { display: flex; flex-wrap: wrap; gap: 3px; justify-content: center; margin-top: 5px; }
.estado-marca {
    font-size: 0.66rem; opacity: 0.92; font-weight: 700;
    background: rgba(255,255,255,0.22); padding: 1px 5px; border-radius: 6px;
}

/* Buscador de oportunidades (cambiar estado) */
/* Modos exactos por ID. Desde la ronda 34 son TRES (ID oportunidad · ID presupuesto ·
   Nº Teowin): se envuelven a dos líneas en pantallas estrechas en vez de recortar
   los rótulos. */
.cambest-pres-row { padding: 0 var(--space-3) 6px; display: flex; gap: 8px; flex-wrap: wrap; }
.btn-pres { flex: 1 1 30%; min-width: 130px; }
.cambest-lista { padding: 0 var(--space-3); }
.cambest-card {
    margin: 8px 0; padding: 10px 12px;
    background: var(--c-card, #fff);
    border: 1px solid var(--c-border, #e0e0e0);
    border-radius: 10px;
    cursor: pointer;
    transition: background 0.1s;
}
.cambest-card:active { background: var(--c-bg-soft, #f5f5f5); }
/* Nombre del cliente completo (sin cortar) y estado en su propia línea debajo */
.cambest-card-head { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.cambest-card-cli { font-weight: 700; overflow-wrap: anywhere; }
.cambest-card-head .estado-chip-inline { align-self: flex-start; }
.cambest-card-tit { color: var(--c-text, #222); font-size: 0.88rem; margin: 3px 0; }
.cambest-card-dir { font-size: 0.78rem; color: var(--c-text-soft, #6c757d); margin: 2px 0; }
.cambest-card-meta { display: flex; gap: 8px; flex-wrap: wrap; font-size: 0.8rem; margin-top: 4px; }
.cambest-pres { color: var(--c-primary, #1976d2); font-weight: 600; }
.cambest-imp { font-weight: 700; }

/* ---- Ronda 34: atajo a la visita del plano de Teowin buscado ----
   Solo aparece buscando por "📐 Nº Teowin": la tarjeta se marca con el filo izquierdo
   y debajo salen LAS visitas que llevan ese plano, cada una con su acceso directo a la
   toma de datos. Es un botón DENTRO de una tarjeta clicable: su onclick para el evento. */
.op-card-teowin { border-left: 4px solid var(--c-primary, #1976d2); }
.op-teowin-hits { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.op-teowin-hit {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
    width: 100%; text-align: left; cursor: pointer;
    padding: 8px 10px;
    border: 1px solid var(--c-primary, #1976d2);
    border-radius: var(--radius-md, 10px);
    background: var(--c-bg-soft, #f5f7fb);
    color: inherit; font: inherit;
}
.op-teowin-hit:active { background: var(--c-border, #e0e0e0); }
.op-teowin-hit-cod { font-weight: 700; color: var(--c-primary, #1976d2); }
.op-teowin-hit-txt { font-size: 0.8rem; color: var(--c-text-soft, #6c757d); }
.op-teowin-hit-cta { margin-left: auto; font-size: 0.8rem; font-weight: 600; color: var(--c-primary, #1976d2); }

/* Ronda 49 — avisos del plano sin resolver: el MISMO ámbar del punto del plano, para que
   la señal sea la misma aquí y allí. El botón hereda todo de .op-teowin-hit; solo cambia
   el color, que es lo que distingue «hay algo que mirar» de «hay una visita con ese plano». */
.flag-aviso-plano { background: rgba(230,145,0,0.16); color: #b06a00; }
.op-aviso-hit { border-color: #e69100; }
.op-aviso-hit .op-teowin-hit-cod,
.op-aviso-hit .op-teowin-hit-cta { color: #b06a00; }

/* RONDA 67 §1 — la fila del aviso enseña DE QUÉ ES (petición literal del usuario) y con qué
   VISITA va. El cuerpo es una columna para que el texto largo del aviso («El frigorífico
   elegido … mide 193,5 y el hueco es de 177: NO ENTRA») quepa entero sin empujar al CTA. */
.op-aviso-fila { align-items: flex-start; }
.op-aviso-cuerpo { display: flex; flex-direction: column; gap: 2px; flex: 1 1 200px; min-width: 0; }
.op-aviso-cab { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
.op-aviso-visita {
    font-size: 0.72rem; font-weight: 700; letter-spacing: 0.02em;
    padding: 1px 7px; border-radius: 999px;
    background: rgba(230,145,0,0.16); color: #b06a00;
}
.op-aviso-linea { font-size: 0.8rem; color: var(--c-text, #222); overflow-wrap: anywhere; }
.op-aviso-mas { color: var(--c-text-soft, #6c757d); font-style: italic; }

/* Grupo de chips mutuamente excluyentes (alta/baja/todos en artículos) */
.chip-grupo { display: inline-flex; flex-wrap: wrap; gap: var(--esp-chip); align-items: center; }
.chip-grupo .chip { padding: 4px 10px; font-size: 0.78rem; }

/* ============== Buscador de artículos: paginación + sin-carga ============== */
.art-pp-lbl { font-size: 0.78rem; color: var(--c-text-soft); margin-left: 6px; align-self: center; }
.art-page-size {
    font-size: 0.85rem; padding: 4px 6px;
    border: 1px solid var(--c-border); background: var(--c-card); border-radius: 4px;
}
.art-pager-bar {
    display: flex; align-items: center; justify-content: center; gap: 10px;
    padding: 10px 6px;
}
.art-pager-info { font-size: 0.9rem; }
.art-page-input {
    width: 50px; padding: 4px 6px; font-size: 0.9rem;
    border: 1px solid var(--c-border); border-radius: 4px; text-align: center;
}
.art-buscador-hint { padding: 0 var(--space-3); }
.art-buscador-tip { font-size: 0.82rem; color: var(--c-text-soft); }

/* Card "dado de baja" → más apagado */
.art-card-baja { opacity: 0.55; }
.tag-baja { background: #fde2e1; color: #8e231e; border: 1px solid #c5332c; }
.tag-marca { background: #fff7e0; color: #8a6a00; border: 1px solid #d6a900; font-weight: 600; }

/* Tags clicables (familia / marca / grupo): cursor + leve hover para indicar
   que se puede filtrar por ese valor pulsándolos. */
.tag-clickable { cursor: pointer; transition: filter .12s, box-shadow .12s; }
.tag-clickable:hover { filter: brightness(0.95); box-shadow: 0 1px 3px rgba(0,0,0,0.18); }

/* Botón "🧹 Limpiar" del buscador — más compacto para no robar tanto sitio. */
.art-btn-limpiar { padding-left: 8px; padding-right: 8px; }

/* ============== Ficha de artículo: PVP destacado + tabla admin ============== */
.art-pvp-grande {
    display: flex; align-items: baseline; justify-content: space-between;
    padding: 10px 12px; background: var(--c-primary-soft); border-radius: 8px;
    border: 1px solid var(--c-primary); margin-bottom: 6px;
}
.art-pvp-grande-lbl { font-size: 0.85rem; color: var(--c-primary-dark); font-weight: 600; }
.art-pvp-grande-val { font-size: 1.6rem; font-weight: 800; color: var(--c-primary-dark); }
.art-pvp-aux { font-size: 0.82rem; color: var(--c-text-soft); padding: 0 4px; }

/* Árbol de familias del catálogo de accesorios (sheet 🌳 Familias) */
.fam-arbol { max-height: 55vh; overflow-y: auto; margin-top: 8px; }
.fam-nodo { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; }
/* Toggle GRANDE y con aspecto de botón: que se vea que pliega/despliega la rama */
.fam-toggle {
    width: 40px; min-width: 40px; height: 40px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1.15rem; line-height: 1; cursor: pointer;
    background: var(--c-bg-soft); color: var(--c-text);
    border: 1px solid var(--c-border); border-radius: 8px;
}
.fam-toggle:active { background: var(--c-border); }
/* Las hojas no despliegan nada: punto discreto sin caja */
.fam-toggle.fam-hoja { background: none; border: none; color: var(--c-text-soft); cursor: default; }
.fam-nombre {
    background: none; border: none; text-align: left; flex: 1 1 auto;
    padding: 7px 6px; font-size: 0.92rem; color: var(--c-text); cursor: pointer;
    border-radius: 8px;
}
.fam-nombre:active { background: var(--c-bg-soft); }
.fam-nombre.active { background: var(--c-primary-soft, #e3f2fd); font-weight: 700; }
.fam-nombre.fam-hit { font-weight: 700; }

/* Chip del proveedor de accesorios: a la derecha de la fila del botón 🌳 Familias */
.cat-chip-derecha { margin-left: auto; }

/* 🔌 Selector de CATEGORÍA DE ELECTRO del buscador general (1.155.0): las mismas
   categorías del catálogo de electros y, debajo de cada una, sus familias. */
.electro-sel { max-height: 60vh; overflow-y: auto; margin-top: 8px; }
.electro-cat {
    display: block; width: 100%; text-align: left;
    background: var(--c-bg-soft); color: var(--c-text);
    border: 1px solid var(--c-border); border-radius: 10px;
    padding: 11px 12px; margin-top: 6px; font-size: 0.95rem; cursor: pointer;
}
.electro-cat:active { background: var(--c-border); }
.electro-cat.active { background: var(--c-primary-soft, #e3f2fd); border-color: var(--c-primary); font-weight: 700; }
.electro-fams { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 0 2px 10px; }

/* Aviso del buscador sobre QUÉ precios se están enseñando (con o sin el
   descuento de catálogo). Discreto: una línea bajo el bloque de filtros. */
.art-aviso-dto {
    margin: 6px 12px 0; padding: 7px 10px; border-radius: 8px;
    font-size: 0.83rem; line-height: 1.35;
    background: var(--c-bg-soft); color: var(--c-text-soft);
    border: 1px solid var(--c-border);
}
.art-aviso-dto.art-aviso-dto-on {
    background: var(--c-primary-soft, #e3f2fd); color: var(--c-primary-dark);
    border-color: var(--c-primary);
}
/* PVP tachado en la tarjeta del buscador cuando se enseña el precio de catálogo */
.art-card-pvp-tachado {
    text-decoration: line-through; color: var(--c-text-soft);
    font-size: 0.82rem; margin-right: 4px;
}

/* Botón 🛒 de la tarjeta del catálogo con unidades YA añadidas a la visita:
   el "+" pasa a ser el número y SOLO el filo exterior se marca en verde
   (el interior conserva su aspecto normal). */
.cat-card-add.cat-card-add-en {
    box-shadow: 0 0 0 2px var(--c-success, #2e7d32);
}
.cat-card-add.cat-card-add-en .art-btn-carrito-plus {
    font-weight: 800;
}

/* Capa de costes (admin): candado + desenfoque hasta que se muestran.
   El blur tapa los importes sin descolocar el layout; pointer-events/user-select
   evitan copiar o interactuar con lo tapado. */
.art-costes-cabecera { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.art-costes-cabecera .art-seccion-tit { margin: 0; }
.art-costes-candado { white-space: nowrap; }
.art-costes-body.art-costes-tapado {
    filter: blur(9px);
    opacity: 0.55;
    pointer-events: none;
    user-select: none;
    -webkit-user-select: none;
}

.art-tabla-pvp { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.art-tabla-pvp th, .art-tabla-pvp td {
    padding: 6px 8px; border-bottom: 1px solid var(--c-border); text-align: right;
}
.art-tabla-pvp th { background: var(--c-bg-soft); font-weight: 700; }
.art-tabla-pvp .art-pvp-lbl { text-align: left; font-weight: 600; }
.art-tabla-pvp .art-pvp-con { font-weight: 700; color: var(--c-primary-dark); }

/* La foto de la ficha es clicable → indicador visual + cursor */
.art-foto-clickable { cursor: zoom-in; transition: box-shadow .15s; }
.art-foto-clickable:hover { box-shadow: 0 0 0 2px var(--c-primary, #1976d2); }

/* Lightbox: overlay oscuro a pantalla completa con la imagen centrada */
.art-lightbox {
    position: fixed; inset: 0; z-index: 9999;
    background: rgba(0,0,0,0.88);
    display: flex; align-items: center; justify-content: center;
    padding: 20px; box-sizing: border-box;
}
.art-lightbox-img {
    max-width: 100%; max-height: 100%;
    object-fit: contain; box-shadow: 0 4px 24px rgba(0,0,0,0.6);
    background: #fff; border-radius: 4px;
}
.art-lightbox-cerrar {
    position: absolute; top: 12px; right: 12px;
    width: 44px; height: 44px; border-radius: 50%;
    background: rgba(255,255,255,0.92); color: #111;
    border: 0; font-size: 1.3rem; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
}

.art-vars { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; }
.art-formulas { display: flex; flex-direction: column; gap: 4px; }
.art-formula-row {
    display: flex; align-items: center; gap: 8px; padding: 4px 6px;
    background: var(--c-bg-soft); border-radius: 4px;
}
.art-formula-lbl { min-width: 60px; font-weight: 600; color: var(--c-text-soft); }
.art-formula-val {
    flex: 1; font-family: ui-monospace, "Cascadia Code", monospace; font-size: 0.85rem;
    color: var(--c-text); word-break: break-all;
}

/* ==================== HOME — secciones colapsables/ordenables ==================== */
.home-cuerpo { padding-bottom: 12px; }
.home-controles {
    display: flex; justify-content: flex-end; gap: 8px;
    padding: 6px var(--space-3);
}
/* Variante "al pie": el bloque va debajo del menú, separado y centrado
   para no competir visualmente con las tarjetas. */
.home-controles-pie {
    justify-content: center;
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px dashed var(--c-border);
}
.home-ordenar-btn, .home-reset-btn { font-size: 0.82rem; padding: 6px 12px; }

.home-bloque { margin-bottom: 4px; }
.home-sec-header {
    display: flex; align-items: center; gap: 8px;
    cursor: pointer; user-select: none;
    padding: 10px var(--space-3);
    transition: background 0.1s;
}
.home-sec-header:active { background: var(--c-bg-soft, #f5f5f5); }
.home-sec-edit { background: rgba(25, 118, 210, 0.06); }
.home-sec-cara { font-size: 0.85rem; color: var(--c-text-soft, #6c757d); transition: transform 0.15s; }
.home-sec-titulo { flex: 1; font-weight: 700; font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.4px; color: var(--c-text-soft, #6c757d); }
.home-sec-mover { display: flex; gap: 4px; }
.btn-mover { padding: 4px 10px; font-size: 0.85rem; min-width: 36px; }
.btn-mover:disabled { opacity: 0.35; cursor: not-allowed; }

/* Detalle de oportunidad */
.op-detalle { padding-bottom: 24px; }
.op-cabecera {
    background: var(--c-card, #fff); padding: 14px var(--space-3);
    border-bottom: 3px solid var(--c-primary, #1976d2);
    box-shadow: var(--shadow-1);
}
.op-cab-tit { font-size: 1.15rem; font-weight: 700; }
.op-cab-cli { color: var(--c-text-soft, #6c757d); margin: 4px 0 2px; }
.op-cab-empresa {
    font-size: 0.82rem; color: var(--c-text-muted, #969696);
    margin: 0 0 10px; font-weight: 500;
}
.op-pres-empresa { font-size: 0.78rem; color: var(--c-text-muted, #969696); }

/* ====== Sección "Estado de pedidos": tarjetas Encimera/Electro/Anticipo/Montaje ====== */
.op-pedidos-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 8px;
}
.op-pedido-card {
    border-radius: 8px; padding: 10px 12px;
    display: flex; flex-direction: column; gap: 4px;
    border: 1px solid;
    min-height: 90px;
}
.op-pedido-titulo { font-size: 0.85rem; font-weight: 700; }
.op-pedido-estado { font-size: 1.4rem; line-height: 1; }
.op-pedido-texto { font-size: 0.85rem; font-weight: 500; }
.op-pedido-texto-importe { font-size: 1.05rem; font-weight: 800; }
.op-pedido-texto-aux { font-size: 0.78rem; color: var(--c-text-soft, #6c757d); }
.op-pedido-ok {
    background: #e8f5ec; border-color: #2c8f3f; color: #1b5e25;
}
.op-pedido-ok .op-pedido-texto-aux { color: #2c5a35; }
.op-pedido-pend {
    background: #fff4e1; border-color: #f0a020; color: #6e4900;
}
.op-pedido-pend .op-pedido-texto-aux { color: #7a5a00; }
.op-pedido-na {
    background: var(--c-bg-soft, #eef2f6); border-color: var(--c-border, #e6e6e6);
    color: var(--c-text-muted, #969696);
}
.op-cab-estado-row { display: flex; align-items: center; gap: 10px; }
/* Trazabilidad del documento (1.187.0): una línea sobria justo encima del
   «🕓 Historial», que es donde se ve el detalle completo. */
.op-traza {
    margin-top: var(--esp-fila);
    color: var(--c-text-soft);
    font-size: 0.88rem;
    line-height: 1.35;
}
.op-acciones-mini { margin-top: var(--esp-borde); display: flex; flex-wrap: wrap; gap: var(--esp-fila) var(--esp-chip); }
.op-acciones-mini .btn { flex: 1 1 0; min-width: 150px; width: auto; }

.op-seccion {
    background: var(--c-card, #fff);
    margin: 10px var(--esp-borde); padding: 12px 14px;
    border-radius: 10px; box-shadow: var(--shadow-1);
}
.op-seccion-tit { margin: 0 0 8px; font-size: 0.95rem; color: var(--c-text-soft, #6c757d); text-transform: uppercase; letter-spacing: 0.4px; }
.op-seccion-body { font-size: 0.9rem; }
.op-kv { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px dashed var(--c-border, #e0e0e0); }
.op-kv:last-child { border-bottom: 0; }
.op-kv-k { color: var(--c-text-soft, #6c757d); flex: 0 0 auto; }
/* 1.175.0 — un email largo (`SARATOCINORODRIGUEZ@hotmail.com`) es UNA palabra
   sin sitios por donde partir: empujaba la fila fuera de la tarjeta y, a partir
   del 110 % de tamaño de letra, sacaba scroll horizontal a la pantalla ENTERA
   (medido a 375px: documento de 404 px de ancho). Con `anywhere` el correo
   parte donde haga falta y la ficha se queda dentro del móvil a cualquier
   tamaño de letra. Es un fallo que ya venía de antes; se ve ahora porque la
   letra normal ha subido un punto. */
.op-kv-v { font-weight: 600; text-align: right; min-width: 0; overflow-wrap: anywhere; }
.op-link { color: var(--c-primary, #1976d2); text-decoration: none; }
.op-obs { white-space: pre-wrap; line-height: 1.4; padding: 4px; }
.op-rechazo { color: #c62828; font-style: italic; }
.empty-mini { color: var(--c-text-soft, #6c757d); font-style: italic; padding: 6px 0; }

.op-pres-row, .op-hist-row {
    display: grid; gap: 4px;
    padding: 8px 0; border-bottom: 1px dashed var(--c-border, #e0e0e0);
}
.op-pres-row:last-child, .op-hist-row:last-child { border-bottom: 0; }
.op-pres-row-click { cursor: pointer; }
.op-pres-row-click:active { background: var(--c-bg-soft, #f5f5f5); }
.op-pres-num { font-weight: 700; color: var(--c-primary, #1976d2); }
.op-pres-fecha, .op-pres-total { font-size: 0.85rem; }
.op-pres-tags, .op-hist-tipo { display: flex; gap: 4px; flex-wrap: wrap; }
.tag-final     { background: rgba(255, 193, 7, 0.18); color: #b78500; }
.tag-aceptado  { background: rgba(46, 125, 50, 0.18); color: #2e7d32; }
.tag-rechazado { background: rgba(198, 40, 40, 0.18); color: #c62828; }
.tag-tipo      { background: var(--c-primary-soft); color: var(--c-primary); }
.op-hist-fecha { font-size: 0.78rem; color: var(--c-text-soft, #6c757d); }
.op-hist-desc  { font-size: 0.88rem; line-height: 1.35; }
.op-hist-user  { font-size: 0.78rem; color: var(--c-text-soft, #6c757d); }

/* ============ VENTAS — VISOR DE DOCUMENTOS (presupuesto / albarán) ============ */

.tag-pendiente { background: rgba(255, 152, 0, 0.18); color: #b26500; }
.doc-card-badges { display: flex; gap: var(--esp-chip); flex-wrap: wrap; }
.doc-lin-lista { display: grid; gap: 0; }
.doc-lin {
    padding: 8px 0; border-bottom: 1px dashed var(--c-border, #e0e0e0);
    display: grid; gap: 3px;
}
.doc-lin:last-child { border-bottom: 0; }
.doc-lin-click { cursor: pointer; }
.doc-lin-click:active { background: var(--c-bg-soft, #f5f5f5); }
.doc-lin-head { display: flex; gap: 6px; align-items: baseline; }
.doc-lin-cant { font-weight: 700; color: var(--c-primary, #1976d2); white-space: nowrap; }
.doc-lin-nombre { font-weight: 600; overflow-wrap: anywhere; }
.doc-lin-cod { font-size: 0.78rem; color: var(--c-text-soft, #6c757d); font-family: monospace; }
.doc-lin-coment { font-size: 0.82rem; color: var(--c-text-soft, #6c757d); white-space: pre-line; }
.doc-lin-importes {
    display: flex; gap: var(--esp-fila); align-items: center; flex-wrap: wrap;
    font-size: 0.85rem; margin-top: 2px;
}
.doc-lin-dto { color: #c62828; font-weight: 600; }
.doc-lin-subtotal { font-weight: 700; margin-left: auto; }
/* (1.145.0: `.doc-lin-importes-iva`, `.doc-lin-iva-pct` y `.doc-lin-chips` se
   retiraron al pasar las líneas de venta a tabla —`.dtab-*`, más abajo—; ya no
   quedaba nadie usándolas. Las demás `.doc-lin-*` siguen vivas en el visor de
   pedidos a proveedor y en cobros/incidencias del documento.) */
.tag-reservado { background: rgba(121, 85, 72, 0.16); color: #6d4c41; }
.tag-pedido { background: rgba(25, 118, 210, 0.14); color: #14538f; }
.tag-pedido-link { cursor: pointer; font-weight: 600; }
.tag-pedido-link:active { background: rgba(25, 118, 210, 0.3); }
.doc-total-row { border-top: 2px solid var(--c-border, #e0e0e0); margin-top: 4px; padding-top: 8px; }
.doc-total { font-size: 1.15rem; font-weight: 800; color: var(--c-primary, #1976d2); }
.doc-lin-filtro { width: 100%; margin-bottom: var(--esp-fila); box-sizing: border-box; }

/* ====== LÍNEAS DEL DOCUMENTO EN FORMA DE TABLA (1.145.0) ======
   Antes cada línea era una fila flexible: los importes empezaban donde acabara el
   nombre, así que de una línea a otra los números NO coincidían en vertical y no
   había forma de comparar precios de un vistazo. Ahora la plantilla de columnas se
   declara UNA sola vez (--dtab-cols) y la usan la cabecera y TODAS las filas: las
   columnas son compartidas de verdad.

   No se usa `subgrid` a propósito (Safari < 16 no lo tiene y esto se usa en iPad):
   con anchos fijos + 1fr el resultado es el mismo y funciona en todo.

   Cada celda se coloca por `grid-area` desde aquí, NO por su orden en el DOM:
     móvil (base)   fila 1: Cant. | Descripción | Subtotal
                    fila 2: detalle (código, medidas, comentario, nº serie)
                    fila 3: precio unitario | dto.      ← no caben 5 columnas en 375 px
                    fila 4: IVA % | precio c/IVA | subtotal c/IVA
                    fila 5: chips (servida / reservado / pedido)
     ≥ 680 px       fila 1: Cant. | Descripción | Precio | Dto. | Subtotal
                    fila 2: detalle · fila 3: c/IVA · fila 4: chips              */
/* ⚠ Los anchos van en `rem`, NUNCA en `em`: la variable se sustituye en CADA
   elemento y `em` se resolvería con la letra de ESE elemento — la cabecera, que va
   más pequeña, quedaba con las columnas más estrechas que las filas (medido: 51 px
   contra 66 px) y la leyenda no caía sobre sus datos. */
.dtab {
    --dtab-cols: 4.1rem minmax(0, 1fr) 5.9rem;
    --dtab-gap: 8px;
}
.dtab-head,
.dtab-fila {
    display: grid;
    grid-template-columns: var(--dtab-cols);
    column-gap: var(--dtab-gap);
    row-gap: 0;                 /* una fila que no existe no debe dejar hueco */
    align-items: baseline;
}
/* Cabecera-leyenda: fina, discreta y PEGADA mientras se recorre la lista. El
   desplazamiento vertical (`top`) lo pone el JS midiendo la barra de título, que
   cambia de alto con el safe-area del dispositivo. */
.dtab-head {
    position: sticky; top: 0; z-index: 3;
    background: var(--c-card, #fff);
    border-bottom: 1px solid var(--c-border, #e0e0e0);
    padding: 4px 0 5px;
    font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.4px;
    color: var(--c-text-soft, #6c757d);
}
.dtab-h-cant, .dtab-h-precio, .dtab-h-dto, .dtab-h-subt { text-align: right; }
.dtab-h-cant { grid-area: 1 / 1; }
.dtab-h-desc { grid-area: 1 / 2; }
.dtab-h-subt { grid-area: 1 / 3; }
.dtab-h-precio, .dtab-h-dto { display: none; }   /* móvil: van en la fila 3 */

.dtab-fila { padding: 7px 0; border-bottom: 1px dashed var(--c-border, #e0e0e0); }
.dtab-fila:last-child { border-bottom: 0; }

.dtab-cant { grid-area: 1 / 1; text-align: right; font-weight: 700; color: var(--c-primary, #1976d2); white-space: nowrap; }
.dtab-desc { grid-area: 1 / 2; font-weight: 600; overflow-wrap: anywhere; min-width: 0; }
.dtab-subt { grid-area: 1 / 3; text-align: right; font-weight: 700; white-space: nowrap; }
.dtab-det  { grid-area: 2 / 2 / auto / -1; padding-top: 2px; }
/* Precio y dto. en pantalla estrecha: bajan a su propia fila pero siguen en
   columnas fijas (precio a la derecha de la descripción, dto. bajo el subtotal),
   así que también se leen en vertical. "P.u." los identifica al no tener arriba
   su cabecera de columna. */
.dtab-precio { grid-area: 3 / 2; text-align: right; }
.dtab-dto    { grid-area: 3 / 3; text-align: right; color: #c62828; font-weight: 600; white-space: nowrap; }
.dtab-precio::before { content: 'P.u. '; color: var(--c-text-soft, #6c757d); font-weight: 400; }
/* Los mismos importes CON IVA, atenuados y bajo sus columnas. */
.dtab-ivapct    { grid-area: 4 / 1; }
.dtab-ivaprecio { grid-area: 4 / 2; }
.dtab-ivasubt   { grid-area: 4 / 3; }
.dtab-ivapct, .dtab-ivaprecio, .dtab-ivasubt {
    text-align: right; font-size: 0.76rem; color: var(--c-text-soft, #6c757d); white-space: nowrap;
}
.dtab-ivapct { font-size: 0.7rem; }   /* "IVA 21%" tiene que caber en la columna de cantidad */
.dtab-chips {
    grid-area: 5 / 1 / auto / -1;
    display: flex; gap: var(--esp-chip); flex-wrap: wrap; padding-top: var(--esp-chip);
}
/* Alineado vertical REAL de las cifras (mismo ancho para todos los dígitos). */
.dtab-cant, .dtab-precio, .dtab-dto, .dtab-subt,
.dtab-ivapct, .dtab-ivaprecio, .dtab-ivasubt { font-variant-numeric: tabular-nums; }

/* Línea de texto/comentario (sin precio ni subtotal): la descripción se queda con
   TODO el ancho en lugar de dejar celdas vacías a la derecha. Si además no lleva
   cantidad, empieza en el borde izquierdo. */
.dtab-fila-nota .dtab-desc { grid-area: 1 / 2 / auto / -1; font-weight: 500; }
.dtab-sincant  .dtab-desc  { grid-area: 1 / 1 / auto / 3; }
.dtab-fila-nota.dtab-sincant .dtab-desc { grid-area: 1 / 1 / auto / -1; }
.dtab-fila-nota.dtab-sincant .dtab-det  { grid-column: 1 / -1; }

/* Tablet/escritorio: caben las cinco columnas en la misma fila. El corte está en
   680 px y no antes: medido en 561 px, la descripción se quedaba en 107 px (los
   nombres de artículo salían en cuatro renglones) — por debajo de eso compensa
   más la disposición de móvil. */
@media (min-width: 680px) {
    .dtab { --dtab-cols: 4.2rem minmax(0, 1fr) 6.5rem 4.3rem 7.6rem; --dtab-gap: 10px; }
    .dtab-h-precio { display: block; grid-area: 1 / 3; }
    .dtab-h-dto    { display: block; grid-area: 1 / 4; }
    .dtab-h-subt   { grid-area: 1 / 5; }
    .dtab-precio   { grid-area: 1 / 3; }
    .dtab-dto      { grid-area: 1 / 4; }
    .dtab-subt     { grid-area: 1 / 5; }
    .dtab-precio::before { content: none; }
    .dtab-ivapct    { grid-area: 3 / 2; font-size: 0.76rem; }
    .dtab-ivaprecio { grid-area: 3 / 3; }
    .dtab-ivasubt   { grid-area: 3 / 5; }
    .dtab-chips     { grid-area: 4 / 1 / auto / -1; }
}
/* Monitor: columnas de números más holgadas (el ancho sobra y los importes
   grandes no deben quedar apretados contra la descripción). */
@media (min-width: 1000px) {
    .dtab { --dtab-cols: 5rem minmax(0, 1fr) 7.7rem 4.9rem 9rem; --dtab-gap: 14px; }
}

/* ============ COMPRAS — PEDIDOS A PROVEEDOR (visor) ============ */
/* Línea ANULADA del pedido: se conserva (es la traza de por qué desapareció el
   artículo) pero se apaga visualmente — gris y con el nombre tachado. */
.doc-lin-anulada { opacity: 0.62; }
.doc-lin-anulada .doc-lin-cant,
.doc-lin-anulada .doc-lin-nombre {
    text-decoration: line-through;
    color: var(--c-text-soft, #6c757d);
}

/* ---------- COMPRAS — edición (Fase B) ---------- */
/* Botonera de alta/edición/borrado: los botones nunca se pegan entre sí. */
.compras-alta { padding: 0 var(--space-3) var(--space-2); }
.compras-btn-nuevo { width: 100%; }
.compras-acciones {
    display: flex; flex-wrap: wrap; gap: 10px;
    padding: var(--space-2) var(--space-3);
}
.compras-acciones .btn { flex: 1 1 0; min-width: 140px; }
.compras-acciones-lin { padding: 0 0 var(--space-2); }
/* Aviso de MODO EDICIÓN con su salida bien visible (1.72.0): el usuario no sabía
   cómo salir del modo edición de un pedido. El botón sólo cierra el modo — los
   cambios ya están guardados, y el subtítulo lo dice. */
.compras-edicion-banner {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin: var(--space-2) var(--space-3);
    padding: 10px 12px;
    background: #fff8e1; color: #6d4c00;
    border: 1px solid #ffd699; border-left: 5px solid #ff9800;
    border-radius: var(--radius-sm);
}
.compras-edicion-txt { flex: 1 1 200px; min-width: 0; }
.compras-edicion-tit { font-weight: 700; }
.compras-edicion-sub { font-size: 0.8rem; opacity: 0.9; }
.compras-edicion-salir { flex: 0 0 auto; width: auto; white-space: nowrap; }
.compras-sustituir-aviso { margin: 0 0 var(--space-2); }

/* Cantidad editable EN LA PROPIA LÍNEA (− campo +) durante la edición. */
.doc-lin-cant-edit { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.doc-lin-cant-edit .doc-lin-cant-btn {
    width: 34px; min-width: 34px; height: 34px; padding: 0;
    font-size: 1.05rem; line-height: 1; font-weight: 700; flex: 0 0 auto;
}
.doc-lin-cant-input {
    width: 64px; min-width: 64px; text-align: center; padding: 4px 2px; min-height: 34px;
}
.doc-lin-cant-guardando { opacity: 0.5; }

.compras-campo { margin-bottom: 12px; }
.compras-campo .form-input { width: 100%; box-sizing: border-box; }
.compras-ayuda { font-size: 0.8rem; margin-top: 4px; }
.compras-form .op-seccion-body { padding-top: 8px; }

/* Resultados de los buscadores de proveedor y de artículo dentro del sheet.
   ⚠ Los hijos NO se encogen: es una columna flex con max-height y, en cuanto
   había muchos resultados (el buscador trae hasta 50), el navegador aplastaba
   cada tarjeta hasta su min-height y las líneas de texto se montaban unas sobre
   otras. Ése era el "listado solapado" que reportó el usuario. */
.compras-prov-lista, .compras-art-lista {
    display: flex; flex-direction: column; gap: 8px;
    max-height: 44vh; overflow-y: auto; margin-top: 10px;
}
.compras-prov-lista > *, .compras-art-lista > * { flex: 0 0 auto; }
.compras-prov-item, .compras-art-item { text-align: left; display: block; }
.compras-prov-nom, .compras-art-nom { font-weight: 700; }
.compras-prov-ok {
    margin-top: 10px; padding: 10px 12px; border-radius: 8px;
    background: var(--c-primary-soft, #e3f2fd);
    color: var(--c-text, #212529);
}

/* Tarjeta de resultado del buscador de PROVEEDORES (1.71.0).
   Antes reutilizaba `.btn`: heredaba el flex centrado y el interlineado de los
   botones y, con nombres largos, las líneas se montaban unas sobre otras. Ahora
   es un bloque propio, en columna, con una línea por dato y truncado elegante. */
.compras-prov-card {
    display: flex; flex-direction: column; align-items: stretch; gap: 2px;
    width: 100%; box-sizing: border-box;
    text-align: left; font-family: inherit; font-size: 1rem;
    padding: 10px 12px; min-height: 44px;
    border: 1px solid var(--c-border, #dee2e6);
    border-radius: var(--radius-sm, 8px);
    background: var(--c-card, #fff); color: var(--c-text, #212529);
    cursor: pointer;
}
.compras-prov-card:active { transform: scale(0.995); }
.compras-prov-card > span {
    display: block; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.compras-prov-card .compras-prov-nom { font-weight: 700; line-height: 1.35; }
.compras-prov-card .compras-prov-fiscal,
.compras-prov-card .compras-prov-meta {
    font-size: 0.85rem; line-height: 1.35; color: var(--c-text-soft, #6c757d);
}

/* ---------- COMPRAS — borradores locales y catálogo visual (Fase E, 1.71.0) ---------- */
.compras-borradores { padding: 0 var(--space-3) var(--space-2); }
.compras-borradores .op-seccion-tit { margin: var(--space-2) 0 6px; }
.compras-borr-card {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; margin-bottom: 8px;
    border-left: 4px solid var(--c-warning, #ffc107);
}
.compras-borr-abrir {
    flex: 1 1 auto; min-width: 0;
    display: flex; flex-direction: column; gap: 2px;
    background: none; border: none; padding: 0; cursor: pointer;
    text-align: left; font-family: inherit; font-size: 1rem; color: inherit;
}
.compras-borr-abrir > span {
    display: block; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.compras-borr-tit { font-weight: 700; line-height: 1.35; }
.compras-borr-meta { font-size: 0.85rem; line-height: 1.35; }
.compras-borr-nota { font-size: 0.8rem; line-height: 1.3; }
.compras-borr-borrar { flex: 0 0 auto; min-height: 40px; }
.compras-borr-lin { display: flex; align-items: flex-start; gap: 10px; }
.compras-borr-lin-datos { flex: 1 1 auto; min-width: 0; }
.compras-cat-cabecera {
    font-weight: 600; padding: 4px 0 8px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.compras-cat-netolbl { font-size: 0.8rem; }
/* Carrito: cantidad editable + papelera en la misma fila, sin pegarse. */
.cat-carrito-acciones { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.cat-carrito-cant { width: 76px; text-align: right; }

/* ---------- COMPRAS — normalizador (Fase C) ---------- */
/* Wizard de 4 pasos dentro de un sheet: la lista de propuestas hace scroll
   propio para que los botones del paso queden siempre a la vista. */
.compras-norm-desc { margin: 4px 0 10px; }
.compras-norm-lista {
    display: flex; flex-direction: column; gap: 8px;
    max-height: 46vh; overflow-y: auto;
}
.compras-norm-fila {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 10px 12px; border-radius: 8px;
    border: 1px solid var(--c-border, #dee2e6);
    cursor: pointer;
}
.compras-norm-check { width: 20px; height: 20px; margin-top: 2px; flex: 0 0 auto; }
.compras-norm-datos { flex: 1 1 auto; min-width: 0; }
.compras-norm-nom { font-weight: 700; }
.compras-norm-cant { margin-top: 4px; font-weight: 600; }
.compras-norm-det { font-size: 0.85rem; margin-top: 2px; }
/* Las que se van a anular, en rojo suave (el escritorio las pinta LightCoral). */
.compras-norm-elim {
    border-color: var(--c-danger, #dc3545);
    background: rgba(220, 53, 69, 0.08);
}
.compras-norm-elim .compras-norm-cant { color: var(--c-danger, #dc3545); }
.compras-norm-botones { padding: var(--space-2) 0 0; }
.compras-norm-resumen { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }

/* ---------- COMPRAS — envío del pedido por email (Fase D) ---------- */
/* El sheet es largo (destinatarios + asunto + firma + opciones): hace scroll
   propio y la lista de contactos se comporta como la del normalizador. */
.compras-envio-cuerpo { display: flex; flex-direction: column; }
.compras-envio-dest {
    display: flex; flex-direction: column; gap: 8px;
    max-height: 32vh; overflow-y: auto; margin-bottom: var(--space-2);
}
.compras-envio-fila {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 10px 12px; border-radius: 8px;
    border: 1px solid var(--c-border, #dee2e6);
    cursor: pointer;
}
.compras-envio-manual { margin: 10px 0; }
.compras-envio-manual > summary { cursor: pointer; font-weight: 600; padding: 6px 0; }
.compras-envio-ops { display: flex; flex-direction: column; gap: 8px; margin: 8px 0; }
.compras-envio-op { display: flex; align-items: center; gap: 10px; cursor: pointer; }
.compras-envio-op input { width: 20px; height: 20px; flex: 0 0 auto; }
.compras-envio-preview {
    width: 100%; height: 46vh; margin-top: 8px;
    border: 1px solid var(--c-border, #dee2e6); border-radius: 8px;
    background: #fff;   /* el email es claro siempre: no hereda el tema oscuro */
}
.compras-envio-aviso { margin: 6px 0; }
/* Firma fija del email (el nombre del comercial): informativa, no editable. */
.compras-envio-firma { margin: 10px 0 2px; }

/* ---------- Selector de conjunto (serie · ejercicio · empresa) ---------- */
.selconj {
    margin: 10px 0; padding: 10px 12px; border-radius: 8px;
    border: 1px solid var(--c-border, #dee2e6);
}
.selconj-cabecera { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.selconj-eti { font-weight: 700; }
.selconj-resumen { flex: 1 1 auto; color: var(--c-text-soft, #6c757d); }
.selconj-btn { padding: 6px 10px; font-size: 0.85rem; }
.selconj-campos { margin-top: 10px; }
.selconj-campo { margin-bottom: 10px; }
.selconj-campo .form-input { width: 100%; box-sizing: border-box; }

/* ==================== VENTAS — ARTICULO ==================== */

.art-detalle { padding-bottom: 24px; }
.art-cab {
    background: var(--c-card, #fff);
    display: flex; gap: 12px; padding: 14px var(--space-3);
    border-bottom: 3px solid var(--c-primary, #1976d2);
    box-shadow: var(--shadow-1);
}
.art-foto {
    width: 110px; height: 110px; object-fit: contain;
    background: #fafafa; border: 1px solid var(--c-border, #e0e0e0);
    border-radius: 8px; flex-shrink: 0;
}
.art-foto-placeholder {
    display: flex; align-items: center; justify-content: center;
    font-size: 3rem; color: var(--c-text-soft, #6c757d);
}
.art-cab-body { flex: 1; min-width: 0; }
.art-cab-cod  { font-weight: 700; color: var(--c-primary, #1976d2); font-size: 0.95rem; }
.art-cab-nom  { font-weight: 600; margin: 4px 0; line-height: 1.25; }
.art-cab-ean, .art-cab-ref, .art-cab-prov { font-size: 0.78rem; color: var(--c-text-soft, #6c757d); margin-top: 2px; }
.art-cab-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.tag-grupo { background: rgba(108, 117, 125, 0.14); color: #555; font-size: 0.7rem; }

.art-seccion {
    background: var(--c-card, #fff);
    margin: 10px var(--space-3); padding: 12px 14px;
    border-radius: 10px; box-shadow: var(--shadow-1);
}
.art-seccion-tit { margin: 0 0 8px; font-size: 0.95rem; color: var(--c-text-soft, #6c757d); text-transform: uppercase; letter-spacing: 0.4px; }
.art-seccion-body { font-size: 0.9rem; }
.art-kv { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px dashed var(--c-border, #e0e0e0); }
.art-kv:last-child { border-bottom: 0; }
.art-kv-k { color: var(--c-text-soft, #6c757d); }
.art-kv-v { font-weight: 600; text-align: right; }
.art-precio-pvp  { color: var(--c-primary, #1976d2); font-size: 1.05rem; }
.art-precio-neto { color: var(--c-warn, #c77800); font-size: 1rem; }
.art-exist-ok   { color: #2e7d32; }
.art-exist-cero { color: var(--c-text-soft, #6c757d); }
.art-exist-neg  { color: #c62828; }
.art-alm-row { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px dashed var(--c-border, #e0e0e0); }
.art-alm-row:last-child { border-bottom: 0; }
.art-alm-val { font-weight: 700; min-width: 60px; text-align: right; }
.art-ficha { white-space: pre-wrap; line-height: 1.4; padding: 4px; font-size: 0.85rem; }
.art-admin-only {
    color: var(--c-text-soft, #6c757d); font-style: italic;
    background: rgba(108,117,125,0.06); padding: 8px; border-radius: 6px; font-size: 0.85rem;
}
.btn-sm { padding: 5px 10px; font-size: 0.82rem; }

/* ===== Buscador de artículos — tarjeta con miniatura ===== */
.art-card-busca {
    display: flex; gap: 10px; padding: 10px;
    align-items: flex-start;
}
.art-mini {
    width: 72px; height: 72px; object-fit: contain;
    background: #fafafa;
    border: 1px solid var(--c-border, #e0e0e0); border-radius: 6px;
    flex-shrink: 0;
}
.art-mini-placeholder {
    display: flex; align-items: center; justify-content: center;
    font-size: 1.8rem; color: var(--c-text-soft, #6c757d);
}
.art-card-body { flex: 1; min-width: 0; }
.art-card-cabrow { display: flex; justify-content: space-between; align-items: center; }
.art-card-exist { font-weight: 700; font-size: 1rem; }
.art-card-nom { font-weight: 600; font-size: 0.92rem; margin: 3px 0; line-height: 1.25; }
.art-card-aux { font-size: 0.74rem; color: var(--c-text-soft, #6c757d); margin-bottom: 4px; }
.art-card-tags { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
/* Contadores de disponibilidad en la tarjeta (almacén/libre/pte. servir/proveedor) */
.art-card-contadores { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 0.76rem; margin-bottom: 4px; }
.art-card-cont { white-space: nowrap; color: var(--c-text-soft, #6c757d); }
.art-card-cont.art-exist-ok { color: var(--c-success, #2e7d32); font-weight: 600; }
.art-card-cont.art-exist-neg { color: var(--c-danger, #c62828); font-weight: 600; }
/* Cabecera de los borradores de la toma de datos (label + botón "ver todos") */
.td-borradores-head {
    display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;
    margin-bottom: var(--space-2);   /* el botón "Ver todos" no queda pegado a la lista */
}
/* Tarjeta de artículo de la visita que abre su ficha al tocarla */
.td-articulo-clicable { cursor: pointer; }
/* Botón "🔎 Elegir artículo del sistema" bajo un campo de texto (tirador...) */
.td-buscador-texto { margin-top: 4px; }
/* Campo enlazado al catálogo (readonly hasta confirmar la edición manual) */
.td-input-bloqueado { background: var(--c-bg, #f4f4f6); color: var(--c-text-soft, #666); }
/* Aviso inline antes de desligar del catálogo al editar a mano.
   L2/C5: fondo y filete por token (`--c-warn-bg` es translúcido en tema oscuro),
   así que el texto ya puede ser el del tema: 14,2:1 en claro y 10,4:1 en oscuro
   (antes era un crema fijo con texto marrón fijo). */
.td-aviso-edicion {
    background: var(--c-warn-bg); border-left: 4px solid var(--c-warn-line); border-radius: 6px;
    padding: var(--space-2) var(--space-3); margin: 6px 0;
    font-size: 0.8125rem; line-height: 1.4;
    color: var(--c-text);
}
.td-aviso-edicion-botones { display: flex; gap: 8px; margin-top: 6px; }

/* Aviso BLOQUEANTE de conflicto entre dispositivos (la visita se guardó desde otra
   pantalla): se queda arriba del todo hasta que se recarga. Colores FIJOS (fondo
   claro en ambos temas) → el texto también fijo para que se lea siempre. */
.td-conflicto {
    display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
    background: #fdecea; border: 2px solid #c62828; border-radius: 8px;
    color: #7f1d1d; padding: var(--space-3); margin-bottom: var(--space-3);
    position: sticky; top: 0; z-index: 60;
}
.td-conflicto-txt { flex: 1 1 220px; min-width: 0; font-size: 0.875rem; line-height: 1.4; }
.td-conflicto .btn { width: auto; flex: 0 0 auto; }
/* Pila de miniaturas (modelo + color + acabado) en la tarjeta de la zona de puertas */
.td-articulo-thumbs { display: flex; flex-direction: column; gap: 4px; flex-shrink: 0; }
.td-articulo-thumbs .td-articulo-thumb { cursor: pointer; }
/* Fotos del enlace (modelo / material / acabado) siempre visibles en el editor */
.mp-fotos-enlace { display: flex; gap: 12px; margin: 6px 0 4px; }
.mp-foto-enlace { text-align: center; }
.mp-foto-lbl { font-size: 0.6875rem; margin-top: 2px; }
.mp-mini-grande, .mp-mini-grande img { width: 72px !important; height: 72px !important; }

/* Botones del catálogo en el editor de zona de puertas: uno solo (elegir del
   catálogo) o dos ("Cambiar color" / "Cambiar modelo y color"). Nunca pegados. */
.td-botones-catalogo { display: flex; gap: var(--space-2); flex-wrap: wrap; margin: 6px 0; }
.td-botones-catalogo .btn { flex: 1 1 150px; }

/* Chip "Solo marmolista" del buscador de material (editor de encimera):
   separado del input de búsqueda para que no quede pegado. */
.td-chips-buscador { margin: 6px 0 10px; }

/* Botón auxiliar pequeño bajo un campo (p.ej. "⬇ Medir del plano Teowin") */
.td-btn-mini { margin-top: 6px; width: auto; font-size: .9rem; padding: 8px 12px; }

/* Aviso ámbar bajo un campo de altura cuando el plano Teowin discrepa de lo anotado.
   L2/C5: tokens `--c-warn-*` (translúcidos en oscuro) → el texto es el del tema. */
.td-aviso-plano {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
    background: var(--c-warn-bg); border-left: 4px solid var(--c-warn-line); border-radius: 6px;
    padding: 6px 10px; margin: 6px 0 0;
    font-size: 0.8125rem; line-height: 1.35; color: var(--c-text);
}
.td-aviso-plano .td-btn-mini { margin-top: 0; }

/* ===== Página de Ajustes (#/ajustes) ===== */
.ajustes-page { display: flex; flex-direction: column; gap: var(--space-4); }
.ajustes-seccion {
    background: var(--c-card); border: 1px solid var(--c-border);
    border-radius: var(--radius); padding: var(--space-4);
}
.ajustes-tit { margin: 0 0 4px; font-size: 1.05rem; }
.ajustes-ayuda { margin-bottom: var(--space-3); font-size: 0.85rem; }
.ajustes-escala { display: flex; align-items: center; gap: var(--space-4); }
.ajustes-escala-btn { min-width: 64px; font-size: 1rem; font-weight: 700; }
.ajustes-escala-btn-mas { font-size: 1.25rem; }
.ajustes-escala-valor { font-size: 1.1rem; font-weight: 700; min-width: 64px; text-align: center; }
.ajustes-preview {
    margin-top: var(--space-3); padding: var(--space-2) var(--space-3);
    border: 1px dashed var(--c-border-strong); border-radius: var(--radius-sm);
    color: var(--c-text-soft);
}
/* Botonera vertical dentro de una sección de ajustes (p.ej. Administración):
   evita que dos botones apilados queden pegados. */
.ajustes-botonera { display: flex; flex-direction: column; gap: var(--space-2); }

/* ===== 🖼 Mi foto (1.139.0) ===== */
.ajustes-foto-fila {
    display: flex; align-items: center; gap: var(--space-3);
    margin-bottom: var(--space-3);
}

/* Previsualización al enviar una foto (chat, tablón, avatares): la imagen a
   tamaño cómodo y debajo los chips "Reducida / Original". */
.foto-envio {
    display: flex; justify-content: center;
    margin-bottom: var(--space-2);
}
.foto-envio-preview {
    max-width: 100%; max-height: 40vh;
    border-radius: var(--radius-sm);
    object-fit: contain;
}
.foto-envio-chips { margin-bottom: var(--space-2); }
.foto-envio-nota { font-size: 0.85rem; }

.ajustes-acentos { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.ajustes-acento {
    display: inline-flex; align-items: center; gap: 8px;
    border: 2px solid var(--c-border); border-radius: 999px;
    background: var(--c-card); color: var(--c-text);
    padding: 8px 14px; cursor: pointer; font-size: 0.9rem;
}
.ajustes-acento.active { border-color: var(--c-primary); background: var(--c-primary-soft); font-weight: 700; }
.ajustes-acento-bola {
    width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0;
    box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15);
}
.ajustes-restablecer { margin-top: var(--space-1); }
.ajustes-nota { font-size: 0.8rem; }

/* ===== Página de Novedades (#/ajustes/novedades) ===== */
.novedades-page { display: flex; flex-direction: column; gap: var(--space-3); }
.novedades-intro { font-size: 0.85rem; }
.novedades-card { display: flex; flex-direction: column; gap: var(--space-2); }
.novedades-card-actual { border-color: var(--c-primary); box-shadow: 0 0 0 1px var(--c-primary) inset; }
.novedades-cab { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
.novedades-version { font-size: 1.05rem; font-weight: 700; color: var(--c-text); }
.novedades-badge {
    font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
    color: #fff; background: var(--c-primary); border-radius: 999px; padding: 2px 10px;
}
.novedades-fecha { margin-left: auto; font-size: 0.8rem; color: var(--c-text-muted); }
.novedades-titulo { font-size: 0.95rem; font-weight: 600; color: var(--c-primary); }
.novedades-lista { margin: 0; padding-left: 1.2em; display: flex; flex-direction: column; gap: 4px; }
.novedades-lista li { font-size: 0.9rem; line-height: 1.4; color: var(--c-text); }
.art-card-pvp { margin-left: auto; font-weight: 700; color: var(--c-primary, #1976d2); font-size: 0.88rem; }

/* ===== Topbar: acciones extra (ej. botón 🧹 Limpiar en buscador) =====
 * IMPORTANTE: la topbar tiene fondo BLANCO (var(--c-card)), no oscuro. Por eso
 * usamos el color primario para el texto del icono y un borde discreto del
 * mismo tono. Hubo una versión con color:#fff que dejaba los iconos invisibles
 * en tema claro. */
/* MV8: 4px sueltos (< --esp-chip) entre los iconos de la topbar (⚙️/✏️ en Inicio). */
.topbar-actions { display: flex; gap: var(--esp-chip); align-items: center; }
.topbar-actions .topbar-action {
    font-size: 1.05rem; padding: 4px 10px; min-height: 34px;
    background: transparent; color: var(--c-primary);
    border: 1px solid var(--c-primary-soft, rgba(25,118,210,0.35));
    border-radius: 6px; cursor: pointer; line-height: 1;
}
.topbar-actions .topbar-action:hover { background: var(--c-primary-soft); }

/* ===== Toggle vista lista/cuadrícula ===== */
.art-vista-grupo { display: inline-flex; gap: 6px; margin-left: auto; }
.art-vista-btn {
    width: 32px; height: 32px; border: 1px solid var(--c-border, #e6e6e6);
    background: var(--c-card, #fff); color: var(--c-text-soft, #6c757d);
    border-radius: 4px; cursor: pointer; font-size: 1.05rem; line-height: 1;
}
.art-vista-btn.active {
    background: var(--c-primary, #1976d2); color: #fff; border-color: var(--c-primary, #1976d2);
}

/* ===== Vista CUADRÍCULA para listados de artículos ===== */
.list-cards-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: 10px;
    padding: 10px;
}
.list-cards-grid .art-card-busca {
    flex-direction: column; align-items: stretch; padding: 8px;
}
.list-cards-grid .art-mini, .list-cards-grid .art-mini-placeholder {
    width: 100%; height: 130px; object-fit: contain; align-self: center;
}
.list-cards-grid .art-card-aux { font-size: 0.7rem; }
.list-cards-grid .art-card-nom { font-size: 0.85rem; }
.list-cards-grid .art-card-tags { font-size: 0.72rem; }

/* Cuadrícula en pantallas grandes (1.93.0, MV8): en la tablet el usuario quiere
   VER las fotos, no una rejilla de miniaturas. Las columnas crecen y con ellas
   la altura de la imagen; el móvil (<600px) se queda exactamente como estaba
   (2 columnas de 170px con foto de 130px). Solo media queries. */
@media (min-width: 600px) {
    .list-cards-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
    .list-cards-grid .art-mini, .list-cards-grid .art-mini-placeholder { height: 165px; }
}
@media (min-width: 768px) {
    /* iPad vertical (768): 3 columnas de ~235px con foto de 200px. El minmax es
       220 y NO 240: con 240 + gap la tercera columna ya no cabe en el ancho útil
       (753 − 20 de padding) y el grid se quedaba en 2 columnas — medido. En
       horizontal (1024) salen 4 columnas de ~236px. */
    .list-cards-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
    .list-cards-grid .art-mini, .list-cards-grid .art-mini-placeholder { height: 200px; }
    .list-cards-grid .art-card-nom { font-size: 0.92rem; }
    .list-cards-grid .art-card-aux { font-size: 0.76rem; }
    .list-cards-grid .art-card-tags { font-size: 0.78rem; }
}
@media (min-width: 1280px) {
    .list-cards-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
    .list-cards-grid .art-mini, .list-cards-grid .art-mini-placeholder { height: 220px; }
}

/* ===== Card de Almacén con miniatura ===== */
.alm-card-conmini { display: flex; gap: 10px; align-items: flex-start; padding: 8px; }
.alm-card-conmini .card-body { flex: 1; min-width: 0; }

/* ===== Cards de movimientos (salida/entrada/traspaso/inventario) con miniatura =====
   Layout flex: la mini al lado izquierdo + cuerpo de la línea + stepper a la
   derecha siguen funcionando como antes. */
.linea-con-mini { display: flex; gap: 10px; align-items: flex-start; padding: 8px; }
.linea-con-mini > .art-mini, .linea-con-mini > .art-mini-placeholder {
    flex-shrink: 0; width: 64px; height: 64px;
}
.linea-con-mini > .card-body { flex: 1; min-width: 0; }
/* En .salida-linea ya hay padding propio, evitamos doblarlo. */
.salida-linea.linea-con-mini { padding: 8px; }

/* ===== Botón flotante "↑ subir al principio" (todos los listados) ===== */
.btn-scroll-top {
    position: fixed;
    right: 14px;
    /* Sobre el panel de estado conexión (height ~30-40px) + footer móvil.
       80px deja hueco para que NO solape con nada al llegar al final. */
    bottom: 90px;
    width: 44px; height: 44px; border-radius: 50%;
    background: rgba(33, 37, 41, 0.55);
    color: #fff; border: 0; font-size: 1.4rem; line-height: 1;
    cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.25);
    z-index: 900;
    opacity: 0; pointer-events: none;
    transform: translateY(8px);
    transition: opacity .18s, transform .18s, background .12s;
}
.btn-scroll-top:hover { background: rgba(33, 37, 41, 0.85); }
.btn-scroll-top-visible {
    opacity: 0.78; pointer-events: auto; transform: translateY(0);
}
.btn-scroll-top-visible:hover { opacity: 1; }


/* ============================================================
   Toma de datos del cliente (visitas CRM) — v1.9.0
   ============================================================ */

/* Lista de visitas */
.td-lista { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.td-lista-head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.td-lista-tit { margin: 0; font-size: 1.125rem; }
.td-visitas { display: flex; flex-direction: column; gap: var(--space-2); }
.td-visita-card {
    text-align: left; background: var(--c-card); border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm); padding: var(--space-3); cursor: pointer;
    display: flex; flex-direction: column; gap: 4px;
}
.td-visita-row { display: flex; justify-content: space-between; align-items: center; }
.td-visita-ver { font-weight: 700; font-size: 1rem; }
.td-visita-sub { color: var(--c-text-soft); font-size: 0.8125rem; }
.td-visita-tags { display: flex; gap: 6px; flex-wrap: wrap; }

/* Formulario de la visita */
.td-form { padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-3); }
.td-cab { background: var(--c-card); border: 1px solid var(--c-border); border-radius: var(--radius-sm); padding: var(--space-3); }
.td-cab-tit { font-weight: 700; font-size: 1.0625rem; }
/* Título de la oportunidad + chip del TIPO DE VENTA (editable) en la misma fila */
.td-cab-tit-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.td-cab-tit-row .td-cab-tit { flex: 1; min-width: 0; }
.chip.td-cab-tipo { font-size: 0.78rem; padding: 3px 10px; white-space: nowrap; }
.td-cab-sub { color: var(--c-text-soft); font-size: 0.8125rem; margin-top: 2px; }
/* Botón de reasignar el vendedor de la visita (solo admin), junto a su nombre */
.td-cab-dueno-btn { margin: 0 var(--space-2) 0 var(--space-1); padding: 0 var(--space-2); }
.td-cab-diff { margin-top: 6px; font-size: 0.8125rem; background: var(--c-warn-bg); color: var(--c-text); border-radius: 6px; padding: 6px 8px; border-left: 4px solid var(--c-warn-line); }
.td-cab-lock { margin-top: 6px; font-size: 0.8125rem; background: #eceff1; color: #455a64; border-radius: 6px; padding: 6px 8px; }
.td-chip-guardado { font-size: 0.75rem; color: #2e7d32; font-weight: 600; white-space: nowrap; }
/* El autoguardado ha fallado: color fijo (legible en claro y oscuro sobre el chip) */
.td-chip-guardado.td-chip-error { color: #c62828; }

/* Acordeón */
/* `overflow: clip` (no `hidden`): recorta igual el radio de las esquinas pero NO
   crea un contenedor de scroll, así las cabeceras de bloque `position: sticky`
   de la ficha técnica (L1/A13) siguen pegándose a la barra superior. */
.td-sec { background: var(--c-card); border: 1px solid var(--c-border-strong); border-radius: var(--radius-sm); overflow: clip; scroll-margin-top: 72px; }
/* L1/A5: la cabecera de SECCIÓN es el título de mayor rango de la visita
   (1.125rem/800) y al abrirse deja una línea de separación con su contenido. */
.td-sec-head {
    width: 100%; display: flex; align-items: center; gap: 8px; padding: var(--space-3);
    background: none; border: none; cursor: pointer; font-size: 1.125rem; font-weight: 800; color: var(--c-text);
    text-align: left;
}
.td-sec-abierta .td-sec-head { border-bottom: 1px solid var(--c-border); }
.td-sec-abierta .td-sec-body { padding-top: var(--space-3); }
.td-sec-tit { flex: 1; }
/* L1/A6 (BUG de contraste): el ámbar #ff9800 con texto blanco daba 2,1:1 — muy
   por debajo del 4,5:1 exigido.
   ⚠ El #b26500 que proponía el estudio se midió sobre el navegador y da 4,42:1,
   todavía por debajo del umbral (ese tono se usa en los `tag-*` de estado como
   TEXTO sobre fondo claro, que es otro caso). Se baja a #a85f00, que medido con
   texto blanco da 4,88:1. */
.td-sec-badge {
    background: var(--c-warn); color: #fff; font-size: 0.75rem; font-weight: 700;
    border-radius: 12px; padding: 2px 8px; white-space: nowrap;
}
.td-sec-flecha { transition: transform 0.15s ease; color: var(--c-text-soft); }
.td-sec-abierta .td-sec-flecha { transform: rotate(180deg); }
.td-sec-body { display: none; padding: 0 var(--space-3) var(--space-3); flex-direction: column; gap: var(--space-2); }
.td-sec-abierta .td-sec-body { display: flex; }

.td-cab-presupuestos { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; }

/* L1/B4 — filtro por OPCIÓN a presupuestar, único y en la cabecera de la visita
   (antes se repetía en el gestor de opciones, en los electros y en los accesorios
   siendo siempre el mismo filtro global). Vacío si la visita no tiene opciones. */
.td-cab-filtro { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.td-cab-filtro:empty { display: none; }
.td-cab-filtro:not(:empty) { margin-top: var(--space-2); }
.td-cab-filtro-lbl { font-size: 0.875rem; font-weight: 700; color: var(--c-text-soft); white-space: nowrap; }
.td-cab-filtro .td-chips { margin-top: 0; }

/* Campos */
.td-campo { border-radius: 6px; padding: 2px 4px; }
.td-campo .form-label { margin-top: var(--space-2); }
/* L1/A2 (BUG tema oscuro): el fondo crema FIJO dejaba el texto blanco del tema
   oscuro sobre crema (ilegible), y afectaba a tarjetas enteras marcadas como
   cambiadas. Fondo TRANSLÚCIDO (mismo patrón que `.td-aplacado-det`): tiñe de
   ámbar en los dos temas y el texto conserva su `--c-text`. */
.campo-cambiado { background: var(--c-warn-bg); box-shadow: inset 4px 0 0 var(--c-warn-line); }
/* MV8: gap fijo (6px) también entre líneas envueltas — con varios chips que no
   caben en una fila (375px) quedaban a 6px en vez de --esp-fila (8px). */
.td-chips { display: flex; flex-wrap: wrap; row-gap: var(--esp-fila); column-gap: var(--esp-chip); margin-top: 4px; }
/* Campo comboTexto (medidas: patas/bajos/altos/columnas/zócalo…): separa la fila
   de chips del input de "otra medida a mano" para que no queden pegados. */
.td-combotexto .td-chips { margin-bottom: var(--space-2); }
.td-check-row { display: flex; align-items: center; gap: 8px; font-size: 0.9375rem; padding: var(--space-2) 0; }
.td-check-row input[type="checkbox"] { width: 20px; height: 20px; }
.td-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); }
/* Propuesta de accesorios leídos del plano Teowin: casilla + descripción + cantidad */
.td-acc-plano-row {
    display: flex; align-items: center; gap: 10px;
    padding: var(--space-2) 0; border-bottom: 1px solid var(--c-border);
}
.td-acc-plano-row input[type="checkbox"] { width: 22px; height: 22px; flex: none; }
.td-acc-plano-txt { flex: 1 1 auto; min-width: 0; }
.td-acc-plano-cant { flex: none; width: 72px; text-align: center; }
.td-teowin-row { display: flex; gap: 8px; align-items: center; margin-top: var(--space-2); margin-bottom: var(--space-2); }

/* Encimeras: el APLACADO destacado (chip propio + panel ámbar de detalles) */
.chip.chip-aplacado { border-color: var(--c-warn-line); color: var(--c-warn-fg); font-weight: 700; }
.chip.chip-aplacado.active { background: var(--c-warn); border-color: var(--c-warn); color: #fff; }
.td-aplacado-det {
    border-left: 4px solid var(--c-warn-line); background: var(--c-warn-bg);
    border-radius: 0 8px 8px 0; padding: 8px 12px 12px; margin-top: 6px;
}
.td-aplacado-det .form-label { margin-top: 0; color: var(--c-warn-fg); font-weight: 700; }
/* RONDA 111 — lo que dice la visita VIEJA («según la visita: hasta los altos») con su
   botón de confirmar al lado. Se envuelve en pantalla estrecha en vez de estrujar el
   botón, que es lo que se pulsa. */
.td-apl-prop {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--esp-sm, 8px);
    margin-top: var(--esp-sm, 8px);
}
.td-apl-prop .muted { flex: 1 1 12em; min-width: 0; }
/* RONDA 112 — la aclaración de qué es «la pared» y qué es «el ancho», bajo los chips del
   «Dónde va». Va pequeña y pegada a ellos: es una nota, no un campo. */
.td-apl-ayuda {
    font-size: 0.85em; line-height: 1.3;
    margin: calc(var(--esp-xs, 4px) * -1) 0 var(--esp-sm, 8px);
}
/* Cada RESPUESTA del aplacado («dónde va + hasta dónde llega»). Con una sola no se nota;
   con varias, la línea de arriba las separa sin meter una caja dentro de otra. */
.td-apl-fila-resp + .td-apl-fila-resp {
    border-top: 1px dashed var(--c-warn-line); margin-top: var(--esp-sm, 8px);
    padding-top: var(--esp-sm, 8px);
}
/* ...y destacado también en la TARJETA-RESUMEN de la zona */
.tag.tag-aplacado {
    background: var(--c-bg-soft); color: var(--c-text);
    border-left: 4px solid var(--c-warn-line); border-radius: 0 12px 12px 0;
    font-weight: 700; white-space: normal;
}

/* Descripción de la visita (cabecera) y en el listado */
.td-cab-descwrap { margin-top: 6px; }
.td-cab-desc { font-size: 0.875rem; }
/* Fecha de la visita en la cabecera (editable en borrador) */
.td-cab-fecha { margin-top: 6px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.td-cab-fecha-lbl { margin: 0; white-space: nowrap; }
.td-cab-fecha-in { font-size: 0.875rem; flex: 1 1 180px; min-width: 0; max-width: 260px; }

/* Comprobaciones al finalizar la visita (sheet de faltas, MV6) */
.td-faltas { margin-top: var(--space-2); display: flex; flex-direction: column; gap: 6px; }
.td-falta {
    display: flex; gap: 8px; align-items: flex-start;
    padding: 8px 10px; border-radius: 8px;
    background: var(--c-bg-soft);
    border-left: 4px solid var(--c-warn-line);
}
.td-falta-bloq { border-left-color: var(--c-danger); }
.td-falta-icono { flex: none; }
.td-falta-txt { flex: 1; min-width: 0; }
.td-visita-desc { font-size: 0.8125rem; margin-top: 2px; display: flex; gap: 6px; align-items: center; }
.td-visita-desc .muted { color: var(--c-text-soft); font-style: italic; }
.td-visita-desc-edit { color: var(--c-primary); font-weight: 700; padding: 2px 6px; cursor: pointer; }
.tag-cambios { white-space: normal; }

/* Sentinela del scroll infinito de los buscadores de la visita */
.td-buscar-mas { text-align: center; color: var(--c-text-soft); font-size: 0.8125rem; padding: 6px; min-height: 1px; }

/* Resultados del buscador de material: miniatura (toca la foto = zoom) + texto */
.albaran-item.td-mat-item { display: flex; align-items: center; gap: 10px; text-align: left; }
.td-mat-item-txt { flex: 1; min-width: 0; }

/* ===== MOLDE ÚNICO de tarjeta de la ficha (L1/B3) =====
   Nació para la encimera (cabecera con foto · zona+material · botones, y detalle
   a TODO EL ANCHO debajo) y en L1 pasa a usarlo TAMBIÉN puertas, electros y
   accesorios: con la fila flex de antes, tres miniaturas apiladas y un texto
   largo descuadraban la tarjeta entera. `.td-enc-*` se mantiene como alias
   histórico (la encimera lleva las dos clases). */
.td-articulo.td-card, .td-articulo.td-enc-card { display: block; }
/* La cabecera ENVUELVE: en 375 px, miniatura + nombre + tres botones dejaban el
   nombre en una columna de 105 px (cabeceras de 200 px de alto con una palabra
   por línea). Con el nombre reclamando un mínimo legible, los botones bajan a su
   propia fila antes que estrujar el texto. */
.td-card-cab, .td-enc-cab { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.td-card-tit, .td-enc-tit { flex: 1 1 11rem; min-width: min(11rem, 100%); }
.td-card-det, .td-enc-detalle {
    margin-top: 8px; display: flex; flex-direction: column;
    gap: 5px; align-items: flex-start; width: 100%;
}
.td-card-det .tag, .td-card-det .td-badge-pendiente,
.td-enc-detalle .tag, .td-enc-detalle .td-badge-pendiente { margin-top: 0; }
/* L1/A12: sin nada dentro, el detalle no debe dejar hueco ni margen. */
.td-card-det:empty, .td-enc-detalle:empty, .td-articulo-sub:empty { display: none; }

/* L1/A1 — los `.tag` del detalle llevan FRASES ("Fregadero: encastrado bajo
   encimera", "Zócalo de otro material (descontado del ML)"), no una palabra: a
   11px, en MAYÚSCULAS y con letter-spacing eran ilegibles. Dentro del detalle de
   una tarjeta son etiquetas de dato: 14px, caja y texto del tema, y una línea
   por dato. El aplacado conserva su color propio (es el único que distingue). */
.td-card-det .tag, .td-enc-detalle .tag {
    font-size: 0.875rem;
    text-transform: none;
    letter-spacing: 0;
    line-height: 1.35;
    white-space: normal;
    padding: 3px 10px;
}
.td-card-det .tag:not(.tag-aplacado), .td-enc-detalle .tag:not(.tag-aplacado) {
    background: var(--c-bg-soft);
    color: var(--c-text);
    font-weight: 600;
}

/* DATOS PRINCIPALES de una tarjeta (1.153.0): grosor de la encimera y tipo de canto.
   Se leían en el renglón gris de 13 px junto al resto del detalle, y son justo lo que
   se busca de un vistazo (y lo que hay que teclear al presupuestar): rótulo pequeño
   atenuado + VALOR al tamaño de dato principal (15 px, 600). Misma pinta en la tabla
   del modo lectura, donde el canto ya tiene columna propia. */
.td-enc-datos { display: flex; flex-wrap: wrap; gap: 6px 8px; align-items: center; }
.td-dato {
    display: inline-flex; align-items: baseline; gap: 6px;
    background: var(--c-bg-soft); border-radius: var(--radius-sm);
    padding: 3px 10px; max-width: 100%;
}
.td-dato-lbl { font-size: 0.8125rem; font-weight: 600; color: var(--c-text-soft); white-space: nowrap; }
.td-dato-val { font-size: 0.9375rem; font-weight: 600; color: var(--c-text); overflow-wrap: anywhere; }

/* Cantidad / código / precio / observaciones de la tarjeta (L1/B1, patrón
   `.doc-lin` de los documentos): la cantidad va DELANTE y destacada, el código en
   mono atenuado, el precio alineado a la derecha y las observaciones en SU
   renglón, respetando los saltos de línea. */
.td-card-nom { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; }
.td-card-cant { font-weight: 700; color: var(--c-primary); white-space: nowrap; }
.td-card-cod { font-size: 0.8125rem; font-family: monospace; color: var(--c-text-soft); overflow-wrap: anywhere; }
.td-card-precio { font-weight: 700; white-space: nowrap; margin-left: auto; align-self: center; }
/* L1/A10: las observaciones son CONTENIDO, no metadato → color de texto pleno. */
.td-card-obs { font-size: 0.875rem; line-height: 1.45; color: var(--c-text); white-space: pre-line; overflow-wrap: anywhere; }
/* Zona/ubicación de la tarjeta (L1/B5): hasta ahora compartía `.td-articulo-cat`
   con la CATEGORÍA del electro, que es otra cosa. */
.td-zona { font-size: 0.8125rem; font-weight: 700; color: var(--c-text-soft); text-transform: uppercase; letter-spacing: 0.3px; }
/* Los badges (opción, pendiente de alta, del catálogo…) en UNA fila que envuelve. */
.td-card-badges { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* L1/A11 — los botones ✎ ⧉ 🗑 de cada tarjeta eran `.btn-secondary`: borde rojo
   de 2px, tres por tarjeta, gritando más que el dato. Iconos neutros (patrón
   `.art-btn-carrito`), sin perder el área táctil. */
.td-btn-icono {
    flex-shrink: 0; width: 38px; height: 38px; min-height: 38px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1rem; line-height: 1; cursor: pointer;
    background: var(--c-card); color: var(--c-text);
    border: 1px solid var(--c-border-strong); border-radius: 8px;
}
.td-btn-icono:active { transform: scale(0.92); }

/* Botón ⛶ expandir/contraer todas las secciones (en el MENÚ SUPERIOR sticky) */
.topbar-actions .td-btn-indice { font-weight: 700; }
.td-cab-comercial { flex-wrap: wrap; }
/* L1/A2 (BUG tema oscuro): igual que `.campo-cambiado` — fondo translúcido en vez
   del crema fijo, que dejaba el texto claro del tema oscuro sobre crema. */
.td-dir-comp {
    display: flex; flex-direction: column; gap: 4px;
    background: var(--c-warn-bg); border-left: 4px solid var(--c-warn-line); border-radius: 6px;
    padding: var(--space-2) var(--space-3); font-size: 0.875rem;
}

/* ===== Catálogo visual de electrodomésticos (toma de datos) ===== */
.cat-page { display: flex; flex-direction: column; gap: var(--esp-fila); padding: var(--esp-fila) var(--esp-borde); }
.cat-contador { font-weight: 700; color: var(--c-primary); }
.cat-filtros { display: flex; flex-direction: column; gap: var(--esp-fila); }
.cat-cats { display: flex; flex-wrap: wrap; gap: var(--esp-chip); }
/* RONDA 153 — globo de «esto ya lo lleva la visita» en cada chip de categoría del
   catálogo de electros. Sin nada apuntado NO se pinta (cero ruido), así que el
   chip mide exactamente lo que medía antes. Sobre el chip ACTIVO (fondo primario)
   el globo se invierte para no perder contraste. */
.cat-cat-globo {
    margin-left: 6px;
    min-width: 1.25em;
    padding: 0 6px;
    border-radius: 10px;
    background: var(--c-primary);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1.5;
    text-align: center;
    white-space: nowrap;
}
.chip.active .cat-cat-globo { background: #fff; color: var(--c-primary); }
/* «El cliente no lo pone»: raya gris, no un 0 (que se leería como «falta»).
   El gris es LITERAL y no `--c-text-soft` (#7a7a7a medido en el navegador): con
   texto blanco ese tono da 4,36:1, por debajo del 4,5:1 que exige la app desde
   L1/A6. #5f6368 con blanco da 5,5:1 (y 5,5:1 también al revés, en el chip
   activo). */
.cat-cat-globo-nolleva { background: #5f6368; color: #fff; }
.chip.active .cat-cat-globo-nolleva { background: #fff; color: #5f6368; }
.cat-familias {
    display: flex; flex-wrap: wrap; gap: var(--esp-chip);
    padding: var(--esp-chip) var(--esp-fila); background: var(--c-card);
    border: 1px dashed var(--c-border); border-radius: var(--radius-sm);
}
.chip-sm { font-size: 0.75rem; padding: 3px 10px; }
.cat-filtros-fila { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.cat-filtros-fila .filter-input { flex: 1; min-width: 160px; }
.cat-filtros-fila .cat-filtro-marca { flex: 0 1 180px; min-width: 130px; }
/* 1.72.0 — el buscador principal del catálogo, en su propia línea a ancho completo
   (el 🔎 y el resto de filtros quedan debajo). */
.cat-filtros-fila .cat-filtro-texto { flex: 1 1 100%; min-width: 0; }
.cat-filtros-busqueda { align-items: center; }
.cat-filtros-busqueda .btn { flex: 0 0 auto; width: auto; }
.cat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--space-2);
}
/* Tablet vertical (1.93.0, MV8): el salto de tamaño del catálogo empezaba en
   900px, así que un iPad en vertical (768) enseñaba 4 columnas de miniaturas.
   Punto intermedio para que las fotos ya se vean grandes ahí. (A partir de
   900/1280 mandan los bloques de más abajo, que son posteriores en el fichero.) */
@media (min-width: 768px) {
    .cat-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
}
.cat-card {
    position: relative;
    display: flex; flex-direction: column; align-items: stretch; text-align: left;
    background: var(--c-card); border: 1px solid var(--c-border); border-radius: var(--radius-sm);
    padding: var(--space-2); cursor: pointer; min-width: 0;
}
.cat-card:active { transform: scale(0.98); }
.cat-card-img {
    display: flex; align-items: center; justify-content: center;
    aspect-ratio: 1 / 1; font-size: 2.625rem; overflow: hidden;
    background: #fff; border-radius: 6px;
}
.cat-card-img img { max-width: 100%; max-height: 100%; object-fit: contain; }
.cat-card-badges { position: absolute; top: 6px; left: 6px; display: flex; gap: 4px; }
.cat-badge {
    background: rgba(255,255,255,0.92); border-radius: 10px; padding: 1px 6px;
    font-size: 0.875rem; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
/* El nombre NUNCA se recorta (petición del usuario): sin line-clamp */
.cat-card-nombre {
    font-size: 0.875rem; font-weight: 600; line-height: 1.25; margin-top: 6px;
    word-break: break-word;
    min-height: 2.5em;
}
.cat-card-marca { font-size: 0.75rem; color: var(--c-text-soft); }
.cat-card-precios { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
.cat-card-pvp { font-size: 1rem; font-weight: 700; color: var(--c-primary); }
.cat-card-pvp-tachado { font-size: 0.75rem; color: var(--c-text-soft); text-decoration: line-through; }
.cat-card-dto { font-size: 0.6875rem; color: #2e7d32; font-weight: 600; }
.cat-contador {
    background: none; border: none; cursor: pointer;
    font-size: 1rem; font-weight: 700; color: var(--c-primary); padding: 4px 8px;
}
.cat-det-precios { display: flex; flex-direction: column; gap: 2px; margin-top: var(--space-2); }
.cat-det-precio-final { font-size: 1.25rem; font-weight: 800; color: var(--c-primary); }
.cat-det-img-grande { height: 300px; position: relative; cursor: zoom-in; }
/* Disponibilidad estilo B2B: ● verde = tienda, ● azul = proveedor, gris = sin stock */
.cat-stock { display: inline-flex; gap: 10px; align-items: center; }
.cat-stock-item { font-size: 0.8rem; font-weight: 600; white-space: nowrap; }
.cat-stock-tienda { color: var(--c-success, #2e7d32); }
.cat-stock-prov { color: #1565c0; }
.cat-stock-cero { color: var(--c-text-soft, #9e9e9e); font-weight: 400; }
/* .cat-card-stockrow: regla unificada más abajo (flex con el botón 🛒+ a la derecha) */
.cat-det-stock { margin: 6px 0 2px; }
.cat-det-stock-leyenda { font-size: 0.75rem; }

.cat-det-lupa {
    position: absolute; right: 8px; bottom: 8px; font-size: 1.25rem;
    background: rgba(255,255,255,0.9); border-radius: 50%; padding: 4px 6px;
    box-shadow: 0 1px 4px rgba(0,0,0,0.25);
}

/* Zoom a pantalla completa (pinch + doble toque) */
.zoom-overlay {
    position: fixed; inset: 0; z-index: 3000;
    background: rgba(0,0,0,0.92);
    display: flex; align-items: center; justify-content: center;
    overflow: hidden; touch-action: none;
}
.zoom-img {
    max-width: 96vw; max-height: 92vh; object-fit: contain;
    background: #fff; border-radius: 6px;
    transform-origin: center center; will-change: transform;
    user-select: none; -webkit-user-drag: none;
}
.zoom-cerrar {
    position: fixed; top: 14px; right: 14px; z-index: 3001;
    background: rgba(255,255,255,0.92); border: none; border-radius: 50%;
    width: 44px; height: 44px; font-size: 1.25rem; cursor: pointer;
    box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
.zoom-ayuda {
    position: fixed; bottom: 14px; left: 0; right: 0; text-align: center;
    color: rgba(255,255,255,0.75); font-size: 0.8125rem; pointer-events: none;
}
/* Navegación entre fotos dentro del visor (flechas + contador n/m) */
.zoom-nav {
    position: fixed; top: 50%; transform: translateY(-50%); z-index: 3001;
    background: rgba(255,255,255,0.85); border: none; border-radius: 50%;
    width: 46px; height: 46px; font-size: 1.625rem; line-height: 1; cursor: pointer;
    box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
.zoom-nav:active { transform: translateY(-50%) scale(0.94); }
.zoom-nav-prev { left: 10px; }
.zoom-nav-next { right: 10px; }
.zoom-contador {
    position: fixed; top: 18px; left: 0; right: 0; text-align: center;
    color: rgba(255,255,255,0.9); font-size: 0.9375rem; pointer-events: none;
}
/* RONDA 139 §4 — acciones del visor (guardar / copiar la foto del chat). Van
   sobre la ayuda, y por eso la ayuda baja su sitio cuando están. */
.zoom-acciones {
    position: fixed; left: 0; right: 0; bottom: calc(38px + env(safe-area-inset-bottom, 0px));
    z-index: 3001; display: flex; gap: var(--esp-chip); justify-content: center;
    flex-wrap: wrap; padding: 0 10px;
}
.zoom-accion {
    background: rgba(255,255,255,0.92); border: none; border-radius: 999px;
    padding: 9px 14px; font-size: 0.875rem; cursor: pointer;
    box-shadow: 0 2px 8px rgba(0,0,0,0.4); color: #111;
}
.zoom-accion:active { transform: scale(0.96); }

/* Carrito (resumen de lo añadido a la visita) */
.cat-carrito { display: flex; flex-direction: column; gap: var(--space-2); max-height: 55vh; overflow-y: auto; }
.cat-carrito-item {
    display: flex; align-items: center; gap: 10px;
    border: 1px solid var(--c-border); border-radius: var(--radius-sm);
    padding: var(--space-2);
}
.cat-carrito-img {
    display: flex; align-items: center; justify-content: center;
    width: 56px; height: 56px; flex-shrink: 0; font-size: 1.5rem;
    background: #fff; border-radius: 6px; overflow: hidden;
}
.cat-carrito-img img { max-width: 100%; max-height: 100%; object-fit: contain; }
.cat-carrito-info { flex: 1; min-width: 0; }
.cat-carrito-total { text-align: right; font-size: 1rem; font-weight: 800; color: var(--c-primary); padding: 4px 2px; }
/* Desglose del carrito por opción a presupuestar (1.123.0) */
.cat-carrito-desglose { text-align: right; font-size: 0.875rem; color: var(--c-text-soft); padding: 0 2px; }

/* Fila de DESTINO del catálogo: a qué opción a presupuestar cae lo que se añade.
   Persistente (no se pliega con los filtros) porque manda en el 🛒+ y en el detalle. */
.cat-destino {
    display: flex; flex-wrap: wrap; align-items: center;
    column-gap: var(--esp-chip); row-gap: var(--esp-fila);
    margin: var(--space-2) 0;
    padding: var(--space-2);
    border: 1px dashed var(--c-border); border-radius: var(--radius-sm);
}
.cat-destino-lbl { font-size: 0.875rem; font-weight: 700; color: var(--c-text-soft); }
.cat-destino .td-chips { margin-top: 0; }

/* RONDA 172 — «🚫 No lleva / no lo quiere» y «👤 Del cliente» de la categoría activa.
   Misma caja que la fila de destino (es otra acción que añade a la visita, no un
   filtro), así que tampoco se pliega y se ve sin desplazar. */
.cat-acciones-cat {
    display: flex; flex-wrap: wrap; align-items: center;
    column-gap: var(--esp-chip); row-gap: var(--esp-fila);
    margin: var(--space-2) 0;
    padding: var(--space-2);
    border: 1px dashed var(--c-border); border-radius: var(--radius-sm);
}
.cat-acciones-cat .td-chips { margin-top: 0; }

/* Miniaturas en la lista de artículos de la ficha */
.td-articulo-thumb {
    display: flex; align-items: center; justify-content: center;
    width: 48px; height: 48px; flex-shrink: 0; font-size: 1.25rem;
    background: #fff; border-radius: 6px; overflow: hidden;
    border: 1px solid var(--c-border);
}
.td-articulo-thumb img { max-width: 100%; max-height: 100%; object-fit: contain; }
.cat-paginacion {
    display: flex; align-items: center; justify-content: center; gap: var(--space-3);
    padding: var(--space-3) 0;
}
.cat-pag-info { font-size: 0.875rem; color: var(--c-text-soft); }
.cat-det-img {
    display: flex; align-items: center; justify-content: center;
    height: 220px; font-size: 4rem; background: #fff; border-radius: var(--radius-sm);
    border: 1px solid var(--c-border); overflow: hidden;
}
.cat-det-img img { max-width: 100%; max-height: 100%; object-fit: contain; }
.cat-det-nombre { font-size: 1.0625rem; font-weight: 700; margin-top: var(--space-2); }
.cat-det-sub { font-size: 0.8125rem; color: var(--c-text-soft); }
.cat-det-reco { color: #b8860b; font-weight: 600; margin-top: 2px; }

/* ===== Estrella de "recomendado" (solo admin) en los buscadores ===== */
/* ☆/⭐ interactiva; caja táctil ≥40px aunque el icono sea pequeño. */
.reco-star {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 40px; min-height: 40px;
    font-size: 1.25rem; line-height: 1;
    color: var(--c-text-soft); cursor: pointer;
    user-select: none; -webkit-user-select: none;
    border-radius: 50%; flex: 0 0 auto;
}
.reco-star:active { background: var(--c-primary-soft); }
.reco-star.reco-star-on { color: #f5a623; }   /* dorado, legible en claro y oscuro */
/* En las cards del catálogo va dentro de .cat-card-badges (esquina sup. izq.,
   opuesta al 🛒+ de abajo-derecha). Caja algo menor para no tapar la foto. */
.cat-card-badges .reco-star { min-width: 34px; min-height: 34px; font-size: 1.1rem; }
/* En el buscador general, en la cabecera de la card, anclada a la derecha. */
.art-card-cabrow .reco-star { margin-left: auto; margin-top: -8px; margin-bottom: -8px; }
/* En los items de buscador de la visita (albaran-item, display:block): a la
   derecha, centrada; el padding solo se reserva si la estrella está presente. */
.albaran-item { position: relative; }
.albaran-item .reco-star { position: absolute; top: 50%; right: 4px; transform: translateY(-50%); }
.albaran-item:has(.reco-star) .albaran-cli,
.albaran-item:has(.reco-star) .albaran-loc { padding-right: 46px; }
/* Fila del detalle del catálogo para admin (estrella + etiqueta). */
.cat-det-reco-admin { display: flex; align-items: center; gap: 6px; color: var(--c-text-soft); font-weight: 600; margin-top: 2px; }

/* ===== Mejoras toma de datos v1.17 ===== */
/* Input grande para el nº Teowin (se teclea de pie, delante del cliente) */
.td-input-grande {
    font-size: 1.375rem; height: 58px; letter-spacing: 2px;
    width: 100%; box-sizing: border-box; text-align: center; font-weight: 600;
}
.td-teowin-col { display: flex; flex-direction: column; gap: 8px; margin-bottom: var(--space-2); }
.td-btn-grande { height: 48px; font-size: 1rem; }
/* Las filas de las tarjetas de visita pueden envolver (no recortar nombres) */
.td-visita-row { flex-wrap: wrap; gap: 6px; }
.td-visita-ver { overflow-wrap: anywhere; }

/* Comercial de la oportunidad en la cabecera de la visita.
   1.149.0: el margen superior pasa de 6px sueltos a --esp-fila, que es el ritmo
   vertical del resto de filas de la cabecera (chips del resumen y botón de
   campos sin rellenar). */
.td-cab-comercial { display: flex; align-items: center; gap: 8px; margin-top: var(--esp-fila); font-size: 0.875rem; }
/* Botones de la derecha de la fila del comercial (patrón .td-visita-del): «🖨 Informe / PDF»
   y —RONDA 103 §2— «📂 Oportunidad». Van en un bloque propio que NO envuelve: si no caben,
   el bloque entero baja a la línea siguiente (la fila `.td-cab-comercial` sí envuelve), así
   que los dos botones se quedan siempre juntos y ninguno se sale del ancho de la pantalla. */
.td-cab-btns { margin-left: auto; display: flex; flex-wrap: nowrap; gap: 8px; }
.td-cab-btns .btn { white-space: nowrap; }

/* Eliminar borrador (icono dentro de la tarjeta de visita) */
.td-visita-del { margin-left: auto; padding: 2px 8px; font-size: 1rem; cursor: pointer; }

/* Planos y fotos de la visita */
/* L1/A12: 130px dejaba planos y croquis en sellos ilegibles → 160px. */
.td-fotos-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: var(--space-2); margin-top: var(--space-2);
}
.td-foto-cell { display: flex; flex-direction: column; border: 1px solid var(--c-border); border-radius: var(--radius-sm); overflow: hidden; }
.td-foto-img {
    display: block; width: 100%; aspect-ratio: 1 / 1; padding: 0; border: none;
    background: #fff; cursor: zoom-in;
}
.td-foto-img img { width: 100%; height: 100%; object-fit: cover; }
.td-foto-pie { display: flex; align-items: center; justify-content: space-between; gap: 4px; padding: 4px 6px; }
.td-foto-desc { font-size: 0.8125rem; color: var(--c-text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Ficha técnica en el detalle del catálogo */
.cat-det-ficha {
    white-space: pre-wrap; font-size: 0.8125rem; line-height: 1.45;
    background: var(--c-card); border: 1px solid var(--c-border); border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3); max-height: 220px; overflow-y: auto;
}

/* Modo "añadir a la visita" en el buscador avanzado */
.art-banner-carrito {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    background: #e8f5e9; border-left: 4px solid #2e7d32; border-radius: 6px;
    padding: var(--space-2) var(--space-3); margin: var(--space-2) var(--space-3) 0;
    font-size: 0.875rem;
    color: #1b5e20;   /* fondo claro fijo → texto fijo (legible también en tema oscuro) */
}
/* Botón discreto "🛒+" para añadir a la visita sin abrir la ficha: redondo,
   fondo neutro y el + pequeño en verde (nada de botón azul grande). */
.art-btn-carrito {
    position: relative; align-self: center; flex-shrink: 0;
    width: 38px; height: 38px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1rem; line-height: 1; padding: 0; cursor: pointer;
    background: var(--c-card); border: 1px solid var(--c-border);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.art-btn-carrito:active { transform: scale(0.92); }
.art-btn-carrito-plus {
    position: absolute; top: -3px; right: -3px;
    width: 15px; height: 15px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: #2e7d32; color: #fff;
    font-size: 0.6875rem; font-weight: 700; line-height: 1;
}
/* El mismo botón en la ESQUINA INFERIOR DERECHA de la tarjeta del catálogo:
   va DENTRO de la fila del stock (flex, empujado a la derecha), no flota
   sobre la tarjeta → no tapa foto, precios ni badges. */
.cat-card-add {
    position: relative; margin-left: auto; flex-shrink: 0;
    width: 34px; height: 34px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 0.9375rem; line-height: 1; cursor: pointer;
    background: var(--c-card); border: 1px solid var(--c-border);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.cat-card-add:active { transform: scale(0.92); }
.cat-card-stockrow { display: flex; align-items: center; gap: 6px; margin-top: auto; }
/* Línea de códigos (código · EAN · ref) en la tarjeta del catálogo */
.cat-card-codigos {
    font-size: 0.6875rem; color: var(--c-text-soft, #777);
    word-break: break-word; line-height: 1.3;
}

/* Subtítulos de listas dentro de la ficha (puertas por zonas, encimeras...).
   L1/A5: la jerarquía estaba INVERTIDA — este subtítulo pesaba más (1.02rem/800
   en banda primaria) que la cabecera de la sección que lo contiene. Ahora es una
   banda ATENUADA (neutra, 0.9375rem/700) claramente por debajo del `.td-sec-head`.
   L1/A13: dentro de la ficha va PEGADA arriba mientras se recorre el bloque, para
   saber siempre en qué bloque se está (patrón `.mp-grupo-cab`). */
.td-sublista-tit {
    margin-top: var(--space-4);
    padding: var(--space-2) var(--space-3);
    background: var(--c-bg-soft);
    border-left: 4px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    font-weight: 700;
    font-size: 0.9375rem;
    letter-spacing: 0.2px;
    color: var(--c-text);
    scroll-margin-top: 76px;
}
/* Nota: en el layout de una columna (móvil/tablet) el bloque es un flex y la
   cabecera viaja pegada arriba mientras se recorre; de 900px para arriba el
   bloque pasa a rejilla y la cabecera ocupa solo su fila, así que el `sticky`
   queda inerte (que es justo donde menos falta hace: ahí cabe todo de un vistazo
   y además está el índice lateral). */
.td-bloque > .td-sublista-tit {
    position: sticky;
    top: 56px;
    z-index: 2;
    margin-top: 0;
}

/* ===== L1/B2 — BLOQUES reales dentro de una sección =====
   El cuerpo de "Ficha técnica" era una lista PLANA de decenas de controles: cada
   bloque temático (puertas, casco, encimeras, accesorios, LED, otros datos) vive
   ahora en su propia `<section class="td-bloque">` con su cabecera y un filete de
   separación. El índice lateral (core/indiceVisita.js) lee las cabeceras tanto
   sueltas como dentro de un `.td-bloque`. */
.td-bloque { display: flex; flex-direction: column; gap: var(--space-2); }
.td-bloque + .td-bloque { margin-top: var(--space-4); border-top: 1px solid var(--c-border); padding-top: var(--space-3); }

/* Ajustes de ficha propios de una OPCIÓN a presupuestar (script 193: cierre a
   techo y LED; el zócalo y el fregadero se fueron a la encimera en el 194). Van
   en filas plegables DEBAJO de su bloque común: lo que se deja vacío hereda el
   valor común de la visita, y cada campo lo indica bajo su control. */
.td-opc-overrides { display: flex; flex-direction: column; gap: var(--space-1); margin: var(--space-1) 0 var(--space-3); }
.td-opc-toggle { width: 100%; text-align: left; }
.td-opc-toggle.td-opc-propia { border-color: var(--c-primary); color: var(--c-primary); font-weight: 600; }
.td-opc-bloque { margin-top: var(--space-2); }
.td-opc-ajustes {
    margin: var(--space-2) 0 var(--space-3);
    padding: var(--space-2) var(--space-3);
    border: 1px dashed var(--c-border); border-radius: var(--radius-sm);
}
.td-opc-campo { margin-bottom: var(--space-3); }
.td-opc-herencia { font-size: 0.8125rem; margin-top: var(--space-1); }

/* Series del casco (no excluyentes: una tarjeta por serie con sus colores) */
.td-series { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }
.td-serie-card {
    border: 1px dashed var(--c-border); border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
}
.td-serie-card.td-serie-activa { border-style: solid; border-color: var(--c-primary); }
.td-serie-chip { font-weight: 700; }

/* Artículos */
.td-articulos { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }
.td-articulo {
    display: flex; justify-content: space-between; align-items: center; gap: 8px;
    border: 1px solid var(--c-border); border-radius: var(--radius-sm); padding: var(--space-2) var(--space-3);
}
.td-articulo-info { min-width: 0; flex: 1; }
/* Fila fantasma: artículo de la visita base ELIMINADO en esta versión */
.td-articulo-eliminado { opacity: .65; border-style: dashed; }
.td-articulo-eliminado .td-articulo-nom { text-decoration: line-through; }
/* L1/A10: suelo de 13px en la ficha (0.75rem se reserva a badges de una palabra)
   y line-height de lectura en la línea de detalle. */
.td-articulo-cat { font-size: 0.8125rem; font-weight: 700; color: var(--c-primary); text-transform: uppercase; }
.td-articulo-nom { font-size: 0.9375rem; font-weight: 600; overflow-wrap: anywhere; }
.td-articulo-sub { font-size: 0.8125rem; line-height: 1.45; color: var(--c-text-soft); }
.td-articulo-acciones { display: flex; gap: 6px; flex-shrink: 0; }
/* Miniaturas de una PUERTA (modelo + color + acabado): en el molde de tarjeta se
   colocan en cuadrícula de 2 y no en una columna de tres pisos (L1/B3). */
.td-card-cab .td-articulo-thumbs { flex-direction: row; flex-wrap: wrap; max-width: 100px; gap: 4px; }
/* "✎ Usar como texto": vuelca el nombre del catálogo al campo manual sin enlazar.
   Compacto y a dos líneas como mucho, para no comerse la fila del resultado. */
.td-btn-texto {
    white-space: normal; line-height: 1.15; font-size: 0.75rem;
    padding: 4px 8px; max-width: 92px; flex-shrink: 0;
}
/* Buscador de MATERIAL de encimera sin resultados: mensaje + banner de anotación
   manual (mismo patrón que las puertas, 1.117.0). */
.td-sin-resultados { padding: var(--space-2) 0; }
/* Buscador del catálogo de puertas SIN coincidencias: el texto tecleado pasa a ser
   la anotación manual. Colores FIJOS (fondo claro en ambos temas) → texto fijo. */
.td-sin-coincidencia {
    display: block; width: 100%; margin-top: var(--space-2);
    background: var(--c-warn-bg); border: 1px solid var(--c-warn-line); color: var(--c-text);
    white-space: normal; line-height: 1.3; text-align: left; font-weight: 600;
}
.td-badge-pendiente {
    display: inline-block; margin-top: 2px; background: var(--c-warn-bg); color: var(--c-warn-fg);
    font-size: 0.75rem; font-weight: 700; border-radius: 999px; padding: 2px 10px;
}
/* Puerta enlazada al catálogo de modelos de fabricante */
.td-badge-catalogo {
    display: inline-block; margin-top: 2px; background: #e8f5e9; color: #2e7d32;
    font-size: 0.75rem; font-weight: 600; border-radius: 10px; padding: 1px 8px;
}
/* Electrodoméstico que aporta el cliente (no se valora, solo constancia) */
.td-badge-cliente {
    display: inline-block; margin-top: 2px; background: #e3f2fd; color: #1565c0;
    font-size: 0.75rem; font-weight: 600; border-radius: 10px; padding: 1px 8px;
}
.td-cli-detalles {
    border-left: 3px solid #90caf9; padding-left: 10px; margin: 4px 0 8px;
}
.td-art-sel-row { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 4px 0; font-weight: 600; }

/* RGPD */
.td-rgpd-texto {
    background: var(--c-card); border: 1px solid var(--c-border); border-radius: var(--radius-sm);
    padding: var(--space-3); font-size: 0.8125rem; line-height: 1.45; white-space: pre-wrap;
    max-height: 40vh; overflow-y: auto;
}
.td-rgpd-estado { border-radius: var(--radius-sm); padding: var(--space-3); font-size: 0.9375rem; }
.td-rgpd-estado.ok { background: #e8f5e9; color: #1b5e20; }
.td-rgpd-estado.pend { background: var(--c-warn-bg); color: var(--c-text); border-left: 4px solid var(--c-warn-line); }
.td-acciones-fin { display: flex; gap: 8px; flex-wrap: wrap; margin-top: var(--space-2); }
.td-acciones-fin .btn { flex: 1; min-width: 160px; }

/* ===== L1/B6 — "Ocultar los campos sin rellenar" (solo lectura) =====
   Una visita completada enseña decenas de campos vacíos entre los rellenos: los
   que tomaDatos.js marca como vacíos se esconden y queda solo lo anotado. No
   afecta a la edición ni al guardado: es puramente visual.
   L2/C1: en lectura la clase se pone DE ENTRADA y el interruptor de L1 lo
   sustituye el botón con contador de la cabecera (`.td-btn-vacios`). */
.td-ocultar-vacios .td-campo-vacio { display: none !important; }

/* ===== L1/A4 · A7 · A8 — de 768px para arriba (iPad vertical y monitor) =====
   En el móvil no cambia nada. */
@media (min-width: 768px) {
    /* A4: en iPad vertical la ficha se centra con un ancho cómodo (de 900px para
       arriba manda la regla de pantallas grandes, que la ensancha a 1150). */
    .td-form, .td-lista { max-width: 720px; margin: 0 auto; width: 100%; }
    /* A4: ancho de LÍNEA legible. A pantalla completa una observación o un
       subtítulo llegaba a ~120 caracteres por línea (el ojo se pierde al saltar);
       72ch es el tope tipográfico habitual. */
    .td-form .td-articulo-sub,
    .td-form .td-articulo-nom,
    .td-form .td-card-obs,
    .td-form .td-sec-body .muted,
    .td-form textarea.form-input { max-width: 72ch; }
    /* A8: las listas de tarjetas dejan de ser una columna única y aprovechan el
       ancho (patrón `.td-visitas` / `.cat-grid`). El selector de modelos de puerta
       vive dentro de un sheet estrecho: ése se queda en una columna. */
    .td-articulos:not(.mp-selector-lista) {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
        align-items: start;
    }
}

/* ===== v1.21: galería multi-imagen del artículo (tira de miniaturas) ===== */
.cat-det-tira { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.cat-det-tira:empty { display: none; }
.cat-det-mini {
    width: 64px; height: 64px; padding: 2px; cursor: pointer;
    background: #fff; border: 1px solid var(--c-border); border-radius: 6px;
    display: flex; align-items: center; justify-content: center; overflow: hidden;
    font-size: 1.25rem;
}
.cat-det-mini img { max-width: 100%; max-height: 100%; object-fit: contain; }
.cat-det-mini:active { transform: scale(0.95); }
.cat-det-mini.active { border-color: var(--c-primary, #1976d2); box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.25); }

/* v1.21.1: paginador ◀ n/m ▶ de la galería (cambia la foto grande) */
.cat-det-pager { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 6px; }
.cat-det-pager:empty { display: none; }
.cat-det-pager-btn {
    min-width: 52px; height: 38px; font-size: 1.125rem; line-height: 1;
    background: #fff; border: 1px solid var(--c-border); border-radius: 8px; cursor: pointer;
}
.cat-det-pager-btn:active { transform: scale(0.95); }
.cat-det-pager-info { font-size: 0.875rem; color: var(--c-text-muted, #666); min-width: 54px; text-align: center; }

/* 🧊 «Imagen para el 3D» (2.0.56): la etiqueta la pone el ERP de escritorio
   (arf_tipo, script 294) y la app solo la enseña. Sin la columna no se pinta
   nada y la galería se ve igual que siempre. */
.cat-det-mini-3d { position: relative; }
.cat-det-mini-badge {
    position: absolute; top: 0; right: 0;
    font-size: 0.7rem; line-height: 1;
    padding: 1px 2px;
    background: var(--c-bg, #fff);
    border-bottom-left-radius: 4px;
    pointer-events: none;
}
/* En el paginador va en su propia línea debajo del «3 / 7». */
.cat-det-pager-3d { display: block; font-size: 0.75rem; }

/* ===== v1.20: adaptación a pantallas grandes (tablet horizontal / escritorio) =====
   En móvil no cambia nada. A partir de 900px el contenido se centra con más ancho,
   los formularios pasan a 2 columnas (3 en ≥1280px), los sheets se muestran como
   diálogo centrado y las imágenes del catálogo crecen. */
@media (min-width: 900px) {
    /* Contenedores centrados aprovechando el ancho */
    .form-page { max-width: 900px; margin: 0 auto; width: 100%; }
    .td-form, .td-lista { max-width: 1150px; margin: 0 auto; width: 100%; }
    .cat-page { max-width: 1300px; margin: 0 auto; width: 100%; }
    .home-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); max-width: 1100px; margin: 0 auto; }

    /* Toma de datos: los CAMPOS de cada sección a 2 columnas; listas, títulos,
       gestores y botones siguen ocupando el ancho completo */
    .td-sec-abierta .td-sec-body, .td-sec-abierta .td-bloque {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--space-2) var(--space-5);
        align-items: start;
    }
    .td-sec-body > *, .td-bloque > * { grid-column: 1 / -1; min-width: 0; }
    .td-sec-body > .td-campo, .td-bloque > .td-campo { grid-column: auto; }
    /* L1/A7: el `.td-grid-2` de las 4 alturas (patas/bajos/altos/columnas) es a su
       vez una rejilla de 2: a 1/3 de columna (≥1280) quedaba estrangulado. Ocupa
       dos columnas — todo el ancho a 900, dos tercios a 1280. */
    .td-sec-body > .td-grid-2, .td-bloque > .td-grid-2 { grid-column: span 2; }

    /* Sheets: diálogo centrado en vez de hoja pegada abajo */
    .sheet-backdrop { align-items: center; justify-content: center; padding: 24px; }
    .sheet { max-width: 640px; border-radius: var(--radius-lg); max-height: 86vh; }
    .salida-serie-modal { max-width: 520px; }

    /* Catálogo y miniaturas: imágenes más grandes */
    .cat-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--space-3); }
    .cat-det-img { height: 380px; }
    .td-articulo-thumb { width: 72px; height: 72px; font-size: 1.75rem; }

    /* Listas de visitas en cuadrícula */
    .td-visitas { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); }
}

@media (min-width: 1280px) {
    .td-sec-abierta .td-sec-body, .td-sec-abierta .td-bloque { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .cat-grid { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
    .home-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); max-width: 1300px; }
    .td-visitas { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* =====================================================================
   L2 — MODO LECTURA de la visita (bloque C del estudio de legibilidad)
   Todo lo de aquí cuelga de `.td-lectura`, que tomaDatos.js pone en el
   formulario SOLO cuando la visita está en solo lectura (COMPLETADA sin
   corrección en curso). La EDICIÓN no ve ni una de estas reglas.
   ===================================================================== */

/* C1 — cada campo deja de ser un control de formulario y pasa a ser una pareja
   clave/valor (mismo patrón `field-label` / `field-value` que la ficha de un
   documento). La caja la define aquí `.td-campo-ro` para no depender del orden
   de `.field` (que se declara mil líneas antes y perdería contra `.td-campo`). */
.td-campo-ro {
    background: var(--c-card);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    box-shadow: var(--shadow-1);
}
.td-campo-ro .field-label { margin-bottom: 2px; }
.td-campo-ro .field-value {
    font-size: 1rem; line-height: 1.45;
    white-space: pre-line; overflow-wrap: anywhere;
}
/* El diff (franja izquierda) tiene que seguir ganando a la caja del campo: la caja
   lleva su propia sombra y, con la misma especificidad, borraba la franja. */
.td-lectura .td-campo-ro.campo-cambiado {
    background: var(--c-warn-bg);
    box-shadow: inset 4px 0 0 var(--c-warn-line), var(--shadow-1);
}

/* C1 — las BARAJAS de chips se quedan en el valor elegido. Los chips de datos
   están deshabilitados (es lo que los distingue de los mandos de la pantalla: el
   filtro por opción de la cabecera sigue siendo pulsable), así que en lectura los
   no marcados desaparecen en vez de pintar una fila de opciones descartadas. */
.td-lectura .chip[disabled]:not(.active) { display: none; }
.td-lectura .td-chips:empty { display: none; }
.td-lectura .chip.active[disabled] { cursor: default; }

/* C1 — campos sin rellenar: en lectura se OMITEN de entrada (el contador de la
   cabecera los enseña bajo demanda). En edición sigue mandando el interruptor
   de L1/B6 sobre `.td-ocultar-vacios`. */
.td-lectura.td-ocultar-vacios .td-campo-vacio { display: none !important; }
/* 1.149.0 — el botón es UNA FILA MÁS de la cabecera, no un botón suelto pegado a
   los chips del resumen. Antes heredaba el `inline-flex` de `.btn` sin margen
   ninguno: quedaba a 0 px de los chips de arriba (medido) y colgando del
   line-box. Como bloque con `fit-content` conserva el ancho de su texto, arranca
   en el mismo borde izquierdo que el resto de la cabecera y respeta el ritmo
   vertical --esp-fila, igual que la línea del comercial de debajo. */
.td-btn-vacios {
    display: flex;
    width: -webkit-fit-content;
    width: fit-content;
    margin: var(--esp-fila) 0 0;
}

/* C2 — listas homogéneas (puertas, encimeras) como TABLA. Cabecera pegada arriba
   y ancho completo; el detalle largo de una fila (observaciones, aplacado,
   badges) va en una segunda fila a todo lo ancho para no perder ningún dato. */
.td-tabla-wrap { width: 100%; overflow-x: auto; }
/* La lista que contiene una tabla NO es una rejilla de tarjetas (de 768 px para
   arriba `.td-articulos` se reparte en columnas de 330 px y la tabla se ahogaba). */
.td-form .td-articulos.td-lista-tabla { display: block; }
.td-tabla-lect {
    width: 100%; border-collapse: collapse; font-size: 0.875rem;
}
.td-tabla-lect th, .td-tabla-lect td {
    text-align: left; padding: 6px 8px; vertical-align: top;
    border-bottom: 1px solid var(--c-border);
    overflow-wrap: anywhere;
}
.td-tabla-lect thead th {
    font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--c-text-soft); font-weight: 700;
    border-bottom: 2px solid var(--c-border-strong);
}
.td-tabla-lect tbody tr.td-fila-dif > td { background: var(--c-warn-bg); }
.td-tabla-lect tbody tr.td-fila-dif > td:first-child { box-shadow: inset 4px 0 0 var(--c-warn-line); }
.td-tabla-lect tbody tr.td-fila-baja > td { opacity: .7; text-decoration: line-through; }
.td-tabla-lect .td-tabla-extra > td { padding-top: 0; border-bottom: 1px solid var(--c-border); }
.td-tabla-extra-cont { display: flex; flex-direction: column; gap: 5px; align-items: flex-start; }
.td-tabla-col-num { white-space: nowrap; }
/* Pantalla estrecha: la tabla se apila (cada celda con su rótulo delante). Es el
   mismo dato con la misma marca de cambio, sin scroll horizontal a ciegas. */
@media (max-width: 559px) {
    .td-tabla-lect, .td-tabla-lect tbody, .td-tabla-lect tr, .td-tabla-lect td { display: block; width: 100%; }
    .td-tabla-lect thead { display: none; }
    .td-tabla-lect tbody tr {
        border: 1px solid var(--c-border); border-radius: var(--radius-sm);
        padding: 4px 2px; margin-bottom: var(--space-2);
    }
    .td-tabla-lect tbody tr.td-tabla-extra { border-top: none; margin-top: -10px; border-radius: 0 0 var(--radius-sm) var(--radius-sm); }
    .td-tabla-lect td { border-bottom: none; display: flex; gap: 8px; padding: 3px 8px; }
    .td-tabla-lect td:empty { display: none; }
    .td-tabla-lect td::before {
        content: attr(data-col); flex: 0 0 5.5rem;
        font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.4px;
        color: var(--c-text-soft); font-weight: 700; padding-top: 2px;
    }
    .td-tabla-lect td[data-col=""]::before { display: none; }
    .td-tabla-lect tbody tr.td-fila-dif { box-shadow: inset 4px 0 0 var(--c-warn-line); }
    .td-tabla-lect tbody tr.td-fila-dif > td:first-child { box-shadow: none; }
}

/* C2 — las CUATRO ALTURAS (patas/bajos/altos/columnas) como lista compacta: en
   lectura no son cuatro campos que rellenar sino cuatro medidas que comparar de
   un vistazo, así que van en renglones de rótulo + valor sin caja propia. */
.td-lectura .td-grid-2 { grid-template-columns: 1fr; gap: 2px; }
.td-lectura .td-grid-2 > .td-campo-ro {
    display: flex; align-items: baseline; gap: 8px;
    box-shadow: none; border-radius: 0; border: none;
    border-bottom: 1px solid var(--c-border);
    padding: 5px var(--space-2);
}
.td-lectura .td-grid-2 > .td-campo-ro .field-label { flex: 0 0 9rem; margin-bottom: 0; }
.td-lectura .td-grid-2 > .td-campo-ro .field-value { flex: 1; min-width: 0; }
/* …pero una altura CAMBIADA respecto a la visita anterior conserva su franja. */
.td-lectura .td-grid-2 > .td-campo-ro.campo-cambiado { box-shadow: inset 4px 0 0 var(--c-warn-line); }

/* C2 — cliente y facturación como clave/valor de DOS columnas en pantalla ancha
   (el resto de secciones puede ir a tres, pero una ficha de datos personales se
   lee mejor en dos columnas anchas que en tres estrechas). */
@media (min-width: 900px) {
    .td-lectura .td-sec-cliente .td-sec-body,
    .td-lectura .td-sec-facturacion .td-sec-body {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* C3 — SUB-ACORDEONES (nivel 2) dentro de la ficha técnica. La cabecera del
   bloque pasa a ser un botón que pliega su contenido; abiertos todos por defecto
   (en edición nada cambia hasta que el vendedor pliegue algo a mano) y el estado
   se recuerda por visita. El índice lateral sigue leyendo `.td-sublista-tit`. */
.td-bloque-head {
    width: 100%; display: flex; align-items: center; gap: 8px;
    cursor: pointer; text-align: left; font-family: inherit;
}
.td-bloque-txt { flex: 1; min-width: 0; }
.td-bloque-flecha { flex: none; color: var(--c-text-soft); transition: transform 0.15s ease; }
.td-bloque-cerrado > .td-bloque-flecha, .td-bloque-cerrado .td-bloque-flecha { transform: rotate(-90deg); }
.td-bloque-cerrado > *:not(.td-sublista-tit) { display: none !important; }

/* C4 — RESUMEN de la visita en la cabecera: recuentos y cambios como chips que
   saltan a su sección. */
.td-cab-resumen { display: flex; flex-wrap: wrap; gap: var(--esp-chip); margin-top: var(--space-2); }
.td-cab-resumen:empty { display: none; }
.chip.td-res-chip { padding: 3px 10px; font-size: 0.8125rem; min-height: 30px; }
.chip.td-res-chip.td-res-dif { background: var(--c-warn-bg); border-color: var(--c-warn-line); color: var(--c-text); font-weight: 700; }
.chip.td-res-chip.td-res-pend { background: var(--c-warn-bg); border-color: var(--c-warn-line); color: var(--c-warn-fg); font-weight: 700; }

/* ===== Selector de modelos de puerta del catalogo (Fase 5, v1.24.0) ===== */
.mp-mini {
    width: 3rem; height: 3rem; flex: 0 0 auto; border-radius: var(--radius-sm);
    background: var(--c-bg-soft); display: flex; align-items: center; justify-content: center;
    overflow: hidden; font-size: 1.25rem;
}
.mp-mini img { width: 100%; height: 100%; object-fit: cover; }
.mp-selector-lista { max-height: 45vh; overflow-y: auto; margin-top: var(--space-2); }
.mp-selector-item { cursor: pointer; justify-content: flex-start; }
.mp-selector-item:active { background: var(--c-bg-soft); }
/* Cabecera del GRUPO dentro del selector (con su contador de colores). Va PEGADA
   arriba mientras se recorre la lista: al buscar un color en todos los grupos
   siempre se ve a qué grupo pertenece lo que se está mirando (1.86.0). */
.mp-grupo-cab {
    position: sticky; top: 0; z-index: 1;
    background: var(--c-card); padding: 4px 0; margin-top: 0;
}
/* Chips de los grupos de color: con muchos grupos (una laca puede tener 7-8) no
   pueden comerse el alto del sheet → altura acotada con scroll propio. */
.mp-chips-grupos { max-height: 108px; overflow-y: auto; }
/* "Ver N coincidencias en otros grupos" cuando hay un grupo elegido con los chips. */
.td-btn-otros-grupos { margin-top: var(--space-2); width: 100%; }
/* Color/variante que ya tiene la puerta (precarga del selector al editar). */
.mp-selector-item-actual { background: var(--c-primary-soft); border-color: var(--c-primary); }
.mp-selector-item-actual .td-articulo-nom { color: var(--c-primary); font-weight: 700; }

/* ===== Visor de planos Teowin (planta 2D) — Fase 2 visor movil ===== */
.plano-page {
    flex: 1;
    display: flex;
    flex-direction: column;
    background: var(--c-bg);
    min-height: 0;
}
.plano-chips {
    display: flex;
    gap: 8px;
    padding: 8px var(--space-2);
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    overflow-x: auto;
}
.plano-canvas-wrap {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    position: relative;   /* ancla la leyenda flotante */
}
/* Leyenda de colores por altura (bandas presentes en la planta) */
.plano-leyenda {
    position: absolute;
    left: 8px;
    bottom: 8px;
    background: rgba(255, 255, 255, 0.9);
    border: 1px solid rgba(0, 0, 0, 0.12);
    border-radius: 8px;
    padding: 6px 8px;
    font-size: 0.72rem;
    line-height: 1.4;
    color: #22252b;
    pointer-events: none;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
    max-width: 45%;
}
.plano-leyenda-fila { display: flex; align-items: center; gap: 6px; }
.plano-leyenda-sw {
    display: inline-block;
    width: 15px;
    height: 15px;
    border-radius: 3px;
    /* Los colores de banda son pasteles muy claros: sin un borde marcado el
       swatch se confunde con el panel blanco en pantallas de móvil. */
    border: 1px solid rgba(0, 0, 0, 0.45);
    flex: 0 0 auto;
}
/* Como .croquis-canvas pero con flex-basis 0: el tamaño del BACKING del canvas
   (sus atributos width/height, que pone el JS al medirse) no puede contar como
   "contenido" del flex — con basis auto se realimentaba al girar el móvil y la
   página crecía sin freno. Sin width/height en % (anulan el estirado del flex). */
.plano-canvas {
    flex: 1 1 0;
    min-height: 0;
    min-width: 0;
    touch-action: none; /* los gestos (pan/pinch) los gestiona el canvas */
    display: block;
}
/* Overlay de carga del visor 3D (sobre el canvas, hasta el primer render). */
.plano-cargando {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.85);
    z-index: 5;
    pointer-events: none;
    gap: 6px;
    padding: var(--space-2);
    text-align: center;
}
/* Panel de carga del 3D con fases (1.62.0): dice QUÉ se está haciendo y cuánto lleva, y en
   caso de fallo ofrece Reintentar/Cancelar (los botones sí reciben toques). */
.plano-carga-fase {
    font-weight: 700;
    color: var(--c-text);
    max-width: 30rem;
}
.plano-carga-det {
    font-size: 0.9rem;
    color: var(--c-text-soft);
    max-width: 30rem;
}
.plano-carga-det:empty { display: none; }
.plano-carga-barra {
    width: min(16rem, 70%);
    height: 6px;
    border-radius: 3px;
    background: var(--c-border);
    overflow: hidden;
}
.plano-carga-barra > i {
    display: block;
    height: 100%;
    width: 0;
    background: var(--c-primary);
    transition: width 0.15s linear;
}
.plano-carga-seg {
    font-size: 0.85rem;
    color: var(--c-text-soft);
    min-height: 1em;
}
.plano-carga-acciones {
    display: flex;
    gap: 10px;
    margin-top: 4px;
    pointer-events: auto;   /* el overlay no captura toques, pero los botones sí */
    flex-wrap: wrap;
    justify-content: center;
}
.plano-carga-acciones:empty { display: none; }
/* .btn-primary es 100% de ancho por defecto: aquí van los dos lado a lado. */
.plano-carga-btn { width: auto; min-width: 9rem; }
.plano-cargando.error .plano-carga-fase { color: var(--c-danger, #b3261e); }
/* Estado NO bloqueante de descarga de texturas (la escena ya se puede girar mientras bajan). */
.plano-estado-tex {
    position: absolute;
    top: 8px;
    left: 50%;
    transform: translateX(-50%);
    max-width: 92%;
    z-index: 4;
    padding: 6px 12px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.72);
    color: #fff;
    font-size: 0.85rem;
    pointer-events: none;
    text-align: center;
}
/* Panel del modo COTAS (MV19): ficha fija de la pieza tocada, en la esquina SUPERIOR
   derecha del lienzo. Arriba y no abajo porque la mitad inferior izquierda la ocupa la
   leyenda de colores y a 375 px se solapaban (medido: leyenda 8..128 px, panel desde
   91 px). Nunca pegado al borde: márgenes con las variables de ritmo. No captura toques
   (el plano sigue vivo debajo). */
.plano-cotas-panel {
    position: absolute;
    right: var(--esp-borde);
    top: var(--esp-borde);
    max-width: min(22rem, calc(100% - 2 * var(--esp-borde)));
    z-index: 4;
    padding: var(--esp-fila) 10px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.94);
    border: 1px solid rgba(0, 0, 0, 0.14);
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
    color: #22252b;
    font-size: 0.82rem;
    line-height: 1.35;
    pointer-events: none;
}
.plano-cotas-tit {
    font-weight: 700;
    margin-bottom: 2px;
    color: #b3261e;
}
.plano-cotas-lin + .plano-cotas-lin { margin-top: 2px; }
/* Botón «Siguiente capa» del modo Cotas (MV23): avanza a la pieza que hay DETRÁS de la
   seleccionada (pared → frente → mueble interior…) y enseña el contador «2/4». Va dentro
   del panel, que no captura toques, así que este control recupera los suyos. Alto ≥ 34 px
   y ancho completo para que sea cómodo con el dedo. */
.plano-cotas-capa {
    display: block;
    width: 100%;
    margin-top: var(--esp-fila);
    padding: 7px 10px;
    min-height: 34px;
    border: 1px solid rgba(0, 0, 0, 0.18);
    border-radius: 8px;
    background: #fff;
    color: #b3261e;
    font: inherit;
    font-weight: 700;
    text-align: center;
    cursor: pointer;
    pointer-events: auto;   /* el panel es pointer-events:none */
}
.plano-cotas-capa:active { background: #f2f2f2; }
/* Etiqueta HTML de una cota dibujada SOBRE el 3D (MV23): se coloca por JS proyectando el
   punto medio de la línea en cada fotograma; el translate la centra en ese punto. */
.plano-cota3d {
    position: absolute;
    z-index: 3;
    transform: translate(-50%, -50%);
    padding: 1px 6px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.9);
    border: 1px solid rgba(224, 138, 0, 0.65);
    color: #a05c00;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1.3;
    white-space: nowrap;
    pointer-events: none;
}
/* ── RONDA 91 — EL PINCEL DE MATERIALES EN EL 3D ────────────────────────────────────────
   Etiquetas del material ancladas al centro de cada pieza pintada («M2 · TOUCH SEDA»). La
   CAPA no recibe el puntero (el dedo tiene que llegar al lienzo para pintar) y las etiquetas
   tampoco: son rótulos, no botones. El reparto (cuáles caben) lo hace `plano3dPincel.js`. */
.p3d-etiqs {
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
    overflow: hidden;
}
.p3d-etiq {
    position: absolute;
    transform: translate(-50%, -50%);
    padding: 1px 6px;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.92);
    border: 1px solid rgba(0, 0, 0, 0.35);
    border-left-width: 4px;          /* la pestaña de color la pinta el JS con el material */
    color: #22252b;
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1.3;
    white-space: nowrap;
    pointer-events: none;
}
/* La paleta del pincel 3D va DEBAJO de los chips (no hay banner aquí como en el rellenar). */
.p3d-paleta { border-top: 1px solid var(--c-border); }

.plano-ficha-fila { margin-bottom: 6px; }
.plano-ficha-k { font-weight: 700; color: var(--c-text-soft); }
.btn-plano { white-space: nowrap; }
/* Slot de los botones del plano del documento («🗺 Ver plano Teowin» y
   «⚖ Comparar plano…»). Los botones se añaden por JS sin nodos de texto entre
   ellos, así que en un contenedor de bloque quedaban PEGADOS: el hueco lo pone
   ahora el propio slot con el ritmo estándar (--esp-fila entre líneas,
   --esp-chip entre botones de la misma línea) y el lateral --esp-borde, el
   mismo de .plano-info-slot para que el bloque del plano se lea como una pieza. */
.plano-btn-slot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    row-gap: var(--esp-fila);
    column-gap: var(--esp-chip);
    padding: 0 var(--esp-borde);
    margin-bottom: var(--esp-fila);
}
.plano-btn-slot:empty { display: none; }

/* ===== Modo APLACADO sobre el plano (Proyecto N) ===== */
.apl-banner {
    padding: 8px var(--space-2);
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
}
.apl-banner-tit {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    font-weight: 700;
    color: rgb(200, 60, 200);
}
.apl-total { font-variant-numeric: tabular-nums; }
.apl-banner-btns {
    flex: 1 1 0;
    min-width: 0;               /* imprescindible para compartir fila con el título (y no envolver) */
    display: flex;
    justify-content: flex-start;/* start (no end): si desbordan, el scroll a la derecha llega a TODOS */
    gap: 8px;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;      /* Firefox */
}
.apl-banner-btns::-webkit-scrollbar { display: none; }   /* WebKit */
.apl-btn { white-space: nowrap; flex: 0 0 auto; }
.apl-btn.active { background: rgb(200, 60, 200); border-color: rgb(200, 60, 200); color: #fff; }
.apl-guardar { border-color: #2e7d32; color: #2e7d32; font-weight: 700; }
.apl-hint { flex-basis: 100%; font-size: 0.76rem; }
/* Pantallas estrechas: chips más compactos (más visibles a la vez); el resto llega
   por scroll horizontal de la barra (todos alcanzables, sin desbordar la página). */
@media (max-width: 400px) {
    .apl-banner-btns .apl-btn { padding-left: 9px; padding-right: 9px; }
    .apl-banner { gap: 6px; }
}

/* Bloque "Dibujar en el plano" en la tarjeta del material de la toma de datos */
.td-apl-fila { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 4px; }
/* L1/A12: el magenta era el único color de la paleta que no significaba nada;
   el aplacado ya tiene su ámbar (tokens `--c-warn-*`, L2/C5) en chip, panel y tag,
   y lo que de verdad lo distingue de un aviso es SU ICONO. */
.td-apl-btn { border-color: var(--c-warn-line); color: var(--c-warn-fg); white-space: nowrap; }
.td-apl-hint { font-size: 0.76rem; margin-top: 4px; }
/* RONDA 113 §2: el aviso del aplacado dibujado con la herramienta ANTIGUA y su botón de
   pasarlo al plano relleno. A todo lo ancho y bajo el botón: es de la VISITA, no del
   material, y se lee una sola vez. (`.td-apl-resumen`, el recuento de tramos viejos, se
   retira con él.) */
.td-apl-legado { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
                 font-size: 0.8rem; margin-top: 2px; }

/* Panel del plano de la visita: botones + estado + boton de re-comprobar */
.plano-fila { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.plano-estado { color: var(--c-text-soft); font-size: 0.88rem; flex: 1 1 240px; }
.plano-recomprobar { min-width: 42px; }
/* RONDA 88 (2.0.80) — renglon fijo con el diseno que se esta viendo y su fecha de guardado,
   debajo de los botones: sin el, un plano REGUARDADO con el mismo numero no se distinguia. */
.plano-fecha { margin-top: var(--esp-xs, 4px); display: flex; align-items: center; gap: 4px; }

/* T1 (1.186.0) — tarjeta de las visitas BASADAS EN OTRA: recuerda que se puede cargar
   un plano nuevo y comparar. Ambar informativo (--c-warn-*, el mismo lenguaje que el
   resto de avisos de la visita), con su boton al lado y margenes de la casa. */
.plano-aviso-version {
    /* El slot del plano es una fila flex: la tarjeta ocupa su propia línea COMPLETA y
       los botones del plano quedan debajo (si no, se colaban a su derecha). */
    flex: 1 0 100%;
    display: flex; flex-wrap: wrap; gap: var(--esp-chip); align-items: center;
    background: var(--c-warn-bg); border-left: 4px solid var(--c-warn-line);
    border-radius: var(--radius-sm); padding: var(--space-2) var(--space-3);
    margin-bottom: var(--esp-fila);
}
.plano-aviso-txt { flex: 1 1 220px; color: var(--c-warn-fg); font-size: 0.8125rem; line-height: 1.4; }
.plano-aviso-btn { white-space: nowrap; }

/* Nº del plano Teowin del documento (presupuesto/albarán), NO editable, encima del
   botón de abrirlo. Mismo margen lateral que el slot del botón (--esp-borde) para
   que el bloque del plano se lea como una sola pieza. */
.plano-info-slot { padding: 0 var(--esp-borde); }
.plano-info-slot:empty { display: none; }
.plano-info { margin-bottom: var(--esp-fila); }
.plano-info-id { font-weight: 700; }
.plano-info-sub { color: var(--c-text-soft); font-size: 0.85rem; margin-top: 2px; line-height: 1.4; }
.plano-info-aviso { color: var(--c-text-soft); }

/* ===== Utilidades → Visor de planos Teowin (buscar por nº o nombre) ===== */
/* El campo se alinea con el título y las tarjetas (sin los 4px extra que .util-page
   da a los campos de las calculadoras, donde hay dos columnas). */
.util-page.vteo-page .td-campo { padding-left: 0; padding-right: 0; }
.vteo-acciones { display: flex; gap: var(--esp-chip); margin-top: var(--esp-fila); }
.vteo-acciones .btn { width: auto; min-width: 8rem; }
.vteo-tit { font-weight: 700; margin: var(--space-4) 0 var(--esp-fila); }
.vteo-card { padding: 10px 12px; margin-bottom: var(--esp-fila); }
.vteo-card-click { cursor: pointer; }
.vteo-card-off { opacity: 0.72; }
.vteo-card-nom { font-weight: 600; word-break: break-all; }
.vteo-card-sub { font-size: 0.85rem; margin-top: 2px; }
.vteo-chips { display: flex; flex-wrap: wrap; row-gap: var(--esp-fila); column-gap: var(--esp-chip); }

/* ===== Editor de zonas de acceso móvil (admin) ===== */
.zonas-page { display: flex; flex-direction: column; min-height: 100%; }
.zonas-mapa { height: 52vh; min-height: 300px; width: 100%; background: var(--c-bg-soft); }
/* Leaflet necesita que sus panes no hereden estilos raros del reset de la app */
.zonas-mapa .leaflet-container { font: inherit; }
.zonas-banner {
    display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: space-between;
    padding: 10px 12px; background: var(--c-primary-soft); border-bottom: 1px solid var(--c-border);
    position: sticky; top: 0; z-index: 500;
}
.zonas-banner-txt { font-weight: 600; color: var(--c-primary-dark); flex: 1 1 200px; }
.zonas-banner-acc { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.zonas-radio-lbl { display: inline-flex; align-items: center; gap: 6px; font-size: 0.9rem; }
.zonas-radio-input { width: 90px; padding: 6px 8px; border: 1px solid var(--c-border-strong); border-radius: var(--radius-sm); }

/* Marcador de vértice / centro: divIcon (sin PNG) */
.zonas-vertice {
    background: #fff; border: 3px solid #dc2626; border-radius: 50%;
    box-shadow: 0 1px 3px rgba(0,0,0,.4); cursor: grab;
}
.zonas-vertice-centro { border-color: #b91c1c; background: #fee2e2; }

.zonas-lista { padding: 12px; display: flex; flex-direction: column; gap: 10px; }
.zonas-lista-cab { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; justify-content: space-between; }
.zonas-lista-cab h3 { margin: 0; }
.zonas-nueva-btns { display: flex; flex-wrap: wrap; gap: 8px; }

.zonas-fila {
    border: 1px solid var(--c-border); border-radius: var(--radius); padding: 10px 12px;
    background: var(--c-card); display: flex; flex-direction: column; gap: 8px;
}
.zonas-fila-inactiva { opacity: 0.65; }
.zonas-fila-top { display: flex; gap: 8px; align-items: center; }
.zonas-input-nombre { flex: 1 1 auto; padding: 8px 10px; border: 1px solid var(--c-border-strong); border-radius: var(--radius-sm); font-size: 1rem; }
.zonas-input-ip { width: 100%; padding: 8px 10px; border: 1px solid var(--c-border-strong); border-radius: var(--radius-sm); font-family: monospace; }
.zonas-detalle { font-size: 0.88rem; }
.zonas-fila-bot { display: flex; gap: 10px; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.zonas-fila-acciones { display: flex; gap: 8px; align-items: center; }
.zonas-activa { display: inline-flex; align-items: center; gap: 6px; font-size: 0.92rem; }
.zonas-badge { font-size: 0.78rem; font-weight: 700; padding: 3px 8px; border-radius: 999px; white-space: nowrap; }
.zonas-badge-ip { background: #e0edff; color: #1d4ed8; }
.zonas-badge-gps { background: #dcfce7; color: #15803d; }
.zonas-badge-ddns { background: #fef3c7; color: #b45309; }
.zonas-borrar { color: var(--c-primary); }

.zonas-pie { position: sticky; bottom: 0; padding: 10px 12px; background: var(--c-card); border-top: 1px solid var(--c-border); display: flex; flex-direction: column; gap: 8px; }
.zonas-guardar { width: 100%; }
.zonas-por-usuario { width: 100%; }

/* Sheet "Zonas por usuario" (script 170) */
.zonas-uz-lista { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; max-height: 40vh; overflow-y: auto; }
.zonas-uz-item { display: flex; align-items: center; gap: 8px; font-size: 0.95rem; }
.zonas-uz-info { margin-top: 10px; font-size: 0.85rem; }

/* ===== Mapa de auditoría de accesos (Fase E4) ===== */
.mapacc-page { display: flex; flex-direction: column; min-height: 100%; }
.mapacc-filtros { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; padding: 12px; }
.mapacc-filtro { display: flex; flex-direction: column; gap: 4px; font-size: 0.82rem; color: var(--c-text-soft); }
.mapacc-filtro span { font-weight: 600; }
.mapacc-filtro-input { padding: 8px 10px; border: 1px solid var(--c-border-strong); border-radius: var(--radius-sm); font-size: 0.95rem; background: var(--c-card); color: var(--c-text); }
.mapacc-filtro-btn { justify-content: flex-end; }
.mapacc-resumen { padding: 0 12px 8px; font-size: 0.88rem; }
.mapacc-mapa { height: 52vh; min-height: 300px; width: 100%; background: var(--c-bg-soft); }
.mapacc-mapa .leaflet-container { font: inherit; }
.mapacc-popup { font-size: 0.9rem; line-height: 1.5; }

.mapacc-sin { padding: 12px; display: flex; flex-direction: column; gap: 10px; }
.mapacc-sin-tit { margin: 0; }
.mapacc-sin-vacia { padding: 6px 2px; }
.mapacc-sin-fila {
    border: 1px solid var(--c-border); border-left: 4px solid #16a34a; border-radius: var(--radius);
    padding: 10px 12px; background: var(--c-card); display: flex; flex-direction: column; gap: 4px;
}
.mapacc-sin-fila-ko { border-left-color: #dc2626; }
.mapacc-sin-top { display: flex; gap: 8px; align-items: center; }
.mapacc-sin-usuario { font-weight: 700; }
.mapacc-sin-fecha { font-size: 0.85rem; }
.mapacc-sin-meta { font-size: 0.85rem; }
.mapacc-sin-motivo { font-size: 0.85rem; color: #b91c1c; }
.mapacc-badge { font-size: 0.78rem; font-weight: 700; padding: 3px 8px; border-radius: 999px; white-space: nowrap; }
.mapacc-badge-ok { background: #dcfce7; color: #15803d; }
.mapacc-badge-ko { background: #fee2e2; color: #b91c1c; }

/* ===== Bloques «👥 Conectados» y «🖥 Sesiones activas» (2.0.4) =====
   Ritmo con las variables de la regla dura: nada pegado al borde ni entre sí. */
.mapacc-bloque {
    padding: var(--esp-borde);
    display: flex; flex-direction: column; gap: var(--esp-fila);
    border-top: 1px solid var(--c-border);
}
.mapacc-bloque-cab {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
    gap: var(--esp-chip);
}
.mapacc-bloque-tit { margin: 0; margin-right: auto; }   /* el título va junto al ▾, las acciones a la derecha */
.mapacc-plegar {
    background: none; border: none; color: var(--c-text-soft); font-size: 1.05rem;
    padding: 4px 6px; margin-right: var(--esp-chip); cursor: pointer; line-height: 1;
    min-width: 32px; min-height: 32px;
}
.mapacc-plegado .mapacc-lista,
.mapacc-plegado .mapacc-capa-toggle,
.mapacc-plegado .mapacc-nota { display: none; }
.mapacc-bloque-acciones { display: flex; align-items: center; gap: var(--esp-chip); }
.mapacc-btn-refrescar { min-width: 52px; padding: var(--space-2) var(--space-3); }
.mapacc-capa-toggle {
    display: flex; align-items: center; gap: var(--esp-chip);
    font-size: 0.88rem; color: var(--c-text-soft);
}
.mapacc-capa-toggle input { width: 20px; height: 20px; }
.mapacc-lista { display: flex; flex-direction: column; gap: var(--esp-fila); }
.mapacc-vacio { padding: var(--esp-fila) 2px; }
.mapacc-nota { font-size: 0.82rem; line-height: 1.45; }
.mapacc-fila {
    border: 1px solid var(--c-border); border-left: 4px solid #2563eb; border-radius: var(--radius);
    padding: 10px 12px; background: var(--c-card);
    display: flex; flex-direction: column; gap: 4px;
}
.mapacc-fila-singps { border-left-color: var(--c-border-strong); }
.mapacc-fila-top { display: flex; flex-wrap: wrap; align-items: center; gap: var(--esp-chip); }
.mapacc-chip-origen {
    font-size: 0.78rem; font-weight: 700; padding: 3px 8px; border-radius: 999px;
    background: #e0edff; color: #1d4ed8; white-space: nowrap;
}
.mapacc-hace { font-size: 0.82rem; margin-left: auto; }
.mapacc-btn-situar { align-self: flex-start; margin-top: var(--esp-chip); min-height: 40px; }

/* ===== Aviso RGPD de control de acceso (modal bloqueante tras login) ===== */
.aviso-acceso-overlay {
    position: fixed; inset: 0; z-index: 4000;
    background: rgba(0, 0, 0, 0.55);
    display: flex; align-items: center; justify-content: center;
    padding: var(--space-4);
}
.aviso-acceso-card {
    background: var(--c-card); color: var(--c-text);
    border: 1px solid var(--c-border); border-radius: var(--radius);
    max-width: 560px; width: 100%; max-height: 90vh; overflow-y: auto;
    padding: var(--space-4);
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.35);
}
.aviso-acceso-ico { font-size: 2rem; text-align: center; margin-bottom: var(--space-2); }
.aviso-acceso-cuerpo { font-size: 0.95rem; line-height: 1.5; }
.aviso-acceso-p { margin: 0 0 var(--space-3); }
.aviso-acceso-p:last-child { margin-bottom: 0; }
.aviso-acceso-acciones {
    display: flex; flex-direction: column; gap: var(--space-2);
    margin-top: var(--space-4);
}
.aviso-acceso-ok { width: 100%; }
.aviso-acceso-salir { width: 100%; }

/* ===================================================================
   UTILIDADES DE FABRICACIÓN (#/utilidades: puerta en cejo, hueco visto)
   Calculadoras de taller: campos + resultados destacados + dibujo en
   <canvas>. El canvas lleva fondo blanco FIJO (el croquis se lee igual
   en tema claro y oscuro, como el editor de croquis).
   =================================================================== */
/* Aire bajo la CABECERA: el primer bloque de cada utilidad (los campos, las
   tarjetas del menú) arrancaba pegado al borde inferior de la topbar. Es el
   mismo ritmo que usan los formularios largos del resto de la app. */
.util-page { padding-top: var(--space-5); padding-bottom: var(--space-5); }
.util-menu { padding-top: var(--space-4); }
.util-nota { font-size: 0.85rem; margin-top: var(--space-3); line-height: 1.45; }
/* Icono propio de una pantalla (hoy el del hueco visto), en la cabecera y en
   la tarjeta del menú. Se dimensiona en `em` para que siga el tamaño de letra
   que el usuario haya elegido en Ajustes. */
.topbar-ico { width: 1.5em; height: 1.5em; flex: 0 0 auto; display: block; }
.home-card-ico img { width: 1em; height: 1em; display: block; }
/* Logo de Teowin EN LÍNEA con el texto (1.120.0): sustituye al 🗺/🏠 en todo lo
   que lleva a ver un plano (botones de documentos y visitas, buscador del visor,
   planos recientes). Se dimensiona en `em` para seguir el tamaño de letra que el
   usuario haya elegido en Ajustes, y el PNG es transparente, así que no hace
   falta nada por tema. En los `.btn` (inline-flex con gap) la separación con el
   texto la pone el gap; en spans/divs, el espacio del propio texto. */
.ico-teowin { height: 1em; width: auto; vertical-align: -0.15em; flex: 0 0 auto; }
/* Iconos propios de ENCIMERA y APLACADO en línea con el texto (1.121.0): mismo
   criterio que el logo de Teowin. Sustituyen al 🍽/🪨 de la encimera y al 🧱 del
   aplacado en los chips de uso, las tarjetas-resumen, los títulos de sección y
   los botones del plano. PNG transparente → nada que ajustar por tema. */
.ico-encimera, .ico-aplacado, .ico-zocalo, .ico-fregadero { height: 1em; width: auto; vertical-align: -0.15em; flex: 0 0 auto; }
/* Los de ZÓCALO y FREGADERO son fotos full-bleed con esquinas redondeadas (no
   transparentes como los otros dos): a 1em justo el marco los empequeñece, así
   que van un pelín más altos y con su redondeo, para que no corten en seco
   junto al texto. */
.ico-zocalo, .ico-fregadero { height: 1.15em; border-radius: 3px; vertical-align: -0.25em; }
/* CASCO (1.146.0): el dibujo de la caja abierta de "hueco visto". Es un SVG
   cuadrado con aire alrededor del dibujo, así que va algo más alto para pesar lo
   mismo que el texto. Tonos neutros fijos → igual en claro y en oscuro. */
.ico-casco { height: 1.35em; width: auto; vertical-align: -0.32em; flex: 0 0 auto; }
.util-ayuda { font-size: 0.78rem; margin-top: 6px; }

/* Ritmo vertical de las calculadoras (auditoría de separaciones 1.54.0).
   `.form-label` global NO trae margen inferior: en formularios largos como
   estos la etiqueta quedaba PEGADA a su campo, y los chips de grosor, al
   input de valor libre. Se separa todo con las variables de espaciado. */
.util-page .form-label { margin-bottom: 6px; }
.util-page .td-campo { padding: 2px 4px var(--space-2); }
.util-page .td-chips { margin-top: 6px; margin-bottom: var(--space-2); }
.util-page .td-check-row { padding: var(--space-2) 4px; }
.util-page .util-errores:not(:empty) { margin-bottom: var(--space-2); }
/* Check no aplicable (LED sin estantes): se ve pero no invita a tocarlo. */
.util-check-inactivo { opacity: 0.55; }
.util-btn-reset { width: auto; align-self: flex-start; margin-top: var(--space-2); font-size: 0.9rem; padding: 8px 12px; }

.util-errores:empty { display: none; }
.util-error {
    background: #fdecea; color: #8a1f16; border-left: 4px solid #c5332c;
    border-radius: 6px; padding: 8px 10px; margin-top: var(--space-2); font-size: 0.9rem;
}

/* Resultados destacados */
.util-res {
    background: var(--c-card); border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm); padding: var(--space-3); margin-top: var(--space-3);
}
.util-res-fila { display: flex; flex-direction: column; gap: 2px; }
.util-res-lbl { font-size: 0.8rem; color: var(--c-text-soft); text-transform: uppercase; letter-spacing: .02em; }
.util-res-big { font-size: 2rem; font-weight: 800; color: var(--c-primary); line-height: 1.1; }
.util-res-valor { font-size: 1.25rem; font-weight: 700; }
.util-res-angulos { display: flex; gap: var(--space-4); margin-top: var(--space-3); flex-wrap: wrap; }
.util-res-extra { font-size: 0.8rem; margin-top: var(--space-3); line-height: 1.5; }

/* Lista de piezas del hueco visto */
.util-piezas { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
.util-pieza {
    display: flex; align-items: center; gap: 10px;
    background: var(--c-card); border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm); padding: 8px 10px;
}
.util-pieza-cant {
    min-width: 34px; height: 34px; border-radius: 17px;
    background: var(--c-primary); color: #fff; font-weight: 800;
    display: flex; align-items: center; justify-content: center;
}
.util-pieza-txt { min-width: 0; }
.util-pieza-nom { font-weight: 700; }
.util-pieza-med { font-size: 1.05rem; }

/* Texto del pedido, tal cual se manda */
.util-pedido {
    background: var(--c-card); border: 1px dashed var(--c-border-strong);
    border-radius: var(--radius-sm); padding: var(--space-3);
    font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
    font-size: 0.95rem; line-height: 1.5; white-space: pre; overflow-x: auto;
    margin: 4px 0 0; color: var(--c-text);
}
.util-botonera { display: flex; gap: 10px; flex-wrap: wrap; margin-top: var(--space-3); }
.util-botonera .btn { flex: 1 1 160px; }

/* Dibujo */
.util-canvas-wrap { margin-top: var(--space-4); }
.util-canvas {
    display: block; width: 100%;
    height: 62vw; min-height: 260px; max-height: 430px;
    background: #fff; border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    touch-action: none;   /* los tiradores del cejo se arrastran con el dedo */
}
.util-canvas-pie { font-size: 0.78rem; margin-top: 6px; text-align: center; }
/* Controles del croquis: repartir las baldas por igual y cotas entre ellas. */
.util-croquis-controles {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: 10px; margin-top: var(--space-3);
}
.util-btn-repartir { width: auto; font-size: 0.9rem; padding: 8px 12px; }
.util-check-cotas { padding: var(--space-2) 4px; }
/* Colocar las baldas por medida: una casilla por balda con la luz de debajo. */
.util-luces-bloque { margin-top: var(--space-3); }
.util-luces { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 6px; }
.util-luz-campo { padding: 2px 4px var(--space-2); }
.util-luces-pie { margin-top: 2px; padding: 0 4px; }
.util-btn-3d { width: 100%; margin-top: var(--space-3); }
.util-led-sep { margin-top: var(--space-2); }

/* --- LED v2: una tira por pieza (1.59.0) --------------------------------
   El bloque reúne el resumen de tiras puestas (chips con ✕), el alta por
   lista y los campos que solo aplican cuando hay LED. */
.util-led-bloque {
    margin-top: var(--space-3);
    padding: var(--space-3) var(--space-3) var(--space-2);
    background: var(--c-card); border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
}
.util-led-pista { margin-top: 0; margin-bottom: var(--space-2); }
.util-led-chips { margin-top: 0; margin-bottom: var(--space-2); }
/* Los chips del LED NO son una selección: son la lista de tiras puestas, con su
   ✕ para quitarlas. Van en ámbar (el color de la luz en el croquis) en vez del
   rojo de "chip activo", que aquí despistaba. La especificidad `.chip` es
   necesaria para ganarle a `.chip.active`. */
.chip.util-led-chip { background: #fff2cc; border-color: #d9a520; color: #6b5312; font-weight: 700; }
.util-led-vacio { font-size: 0.85rem; padding: 4px 0; }
.util-btn-led { width: auto; font-size: 0.9rem; padding: 8px 12px; }
.util-page .util-led-bloque .td-campo { padding-left: 0; padding-right: 0; }
/* Texto de los sheets del LED */
.util-led-sheet-txt { font-size: 0.95rem; line-height: 1.45; }
.util-led-sheet-ayuda { font-size: 0.85rem; color: var(--c-text-soft); line-height: 1.4; margin-top: 6px; }
/* Posiciones del LED: se MARCAN varias (una balda puede llevar dos tiras). */
.util-led-opciones { display: flex; flex-direction: column; gap: 4px; margin-top: var(--space-2); }
.util-led-opcion {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 10px 8px; border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm); background: var(--c-card); cursor: pointer;
}
.util-led-opcion input { margin-top: 3px; flex: 0 0 auto; }
.util-led-opcion-txt { min-width: 0; }
.util-led-opcion-nom { font-weight: 700; }
.util-led-opcion .util-led-sheet-ayuda { margin-top: 2px; }

/* Bloque de resultado (piezas + pedido + botones): lo señala el modo guiado. */
.util-resultado { margin-top: var(--space-3); }

/* --- PUERTAS DE RINCÓN (1.192.0) ---------------------------------------
   Tarjetas de resultado, estado del test de giro y simulador de apertura.
   Las separaciones salen SIEMPRE de las variables (--esp-* / --space-*). */
.rin-acciones {
    display: flex; flex-wrap: wrap;
    row-gap: var(--esp-fila); column-gap: var(--esp-chip);
    margin-top: var(--space-2);
}
.rin-btn-accion { width: auto; font-size: 0.9rem; padding: 8px 12px; }
.rin-desglose { margin-top: 2px; font-size: 0.85rem; }
.rin-sep { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--c-border); }
.rin-test-tit { font-size: 1.1rem; font-weight: 800; line-height: 1.3; }
.rin-test.rin-ok { border-color: var(--c-success, #2c8f3f); }
.rin-test.rin-ok .rin-test-tit { color: var(--c-success, #2c8f3f); }
.rin-test.rin-justo { border-color: #b26a00; }
.rin-test.rin-justo .rin-test-tit { color: #b26a00; }
.rin-test.rin-mal { border-color: var(--c-danger, #c5332c); }
.rin-test.rin-mal .rin-test-tit { color: var(--c-danger, #c5332c); }
.rin-alt-txt { margin-top: 4px; line-height: 1.5; }
.rin-alt.rin-alt-no .rin-alt-txt { color: var(--c-text-soft); }
/* --- 1.196.0: veredicto de la TACA EN L (2ª apertura comprobada) -------- */
.rin-taca-orden {
    margin-top: var(--space-2);
    line-height: 1.5;
    font-weight: 600;
    color: var(--c-success, #2c8f3f);
}
.rin-alt.rin-alt-no .rin-taca-orden { color: var(--c-danger, #c5332c); }
.rin-lbl-pedido { margin-top: var(--space-4); }

/* --- 1.197.0: taca RECTA con el corte en la puerta, holguras y taca a mano */
/* El detalle del corte va en letra normal pero separado del bloque de arriba:
   es lo que se lleva el carpintero a la sierra. */
.rin-taca-corte {
    margin-top: var(--space-2);
    line-height: 1.5;
    color: var(--c-text-soft);
}
/* Desglose de holguras al pie de las medidas (los 0,3 / 0,5 / mm de giro y de
   alto): letra pequeña, para que se VEA que se tienen en cuenta sin robarle
   protagonismo a la medida. */
.rin-holguras {
    margin-top: var(--space-2);
    font-size: 0.78rem;
    line-height: 1.5;
}
/* Campo «Ancho de taca» dentro de la tarjeta de la taca. */
.rin-bloque-taca {
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--c-border);
}
.rin-bloque-taca .form-input { max-width: 10rem; }
/* Puesta por debajo del mínimo: se avisa, pero NO se bloquea (manda el motor). */
.rin-bloque-taca.rin-taca-corta .form-input { border-color: var(--c-danger, #c5332c); }
.rin-bloque-taca.rin-taca-corta .util-ayuda { color: var(--c-danger, #c5332c); }

/* Simulador de apertura: etiqueta + deslizador + margen en vivo. */
.rin-sim {
    display: flex; align-items: center; flex-wrap: wrap;
    row-gap: var(--esp-fila); column-gap: var(--esp-chip);
    margin-top: var(--space-3);
}
.rin-sim-lbl {
    display: flex; align-items: baseline; gap: 6px;
    font-size: 0.85rem; color: var(--c-text-soft); flex: 0 0 auto;
}
.rin-apertura { font-weight: 800; font-size: 1rem; color: var(--c-text); }
.rin-slider { flex: 1 1 180px; min-width: 140px; accent-color: var(--c-primary); }
.rin-margen { flex: 0 0 auto; font-size: 0.9rem; font-weight: 700; }
.rin-margen-ok { color: var(--c-success, #2c8f3f); }
.rin-margen-mal { color: var(--c-danger, #c5332c); }

/* --- 1.194.0: bisagra, dos deslizadores y modo PUERTAS UNIDAS -------------
   El segundo deslizador (la otra hoja / el plegado del bifold) va justo
   debajo del primero, con el mismo ritmo que el resto de filas. */
.rin-sim + .rin-sim { margin-top: var(--esp-fila); }
/* Etiqueta del campo avanzado de fuga, dentro del bloque de la bisagra. */
.rin-lbl-fuga { margin-top: var(--space-2); }
/* Pre-check analítico: letra pequeña bajo el veredicto del test. */
.rin-precheck { font-size: 0.75rem; opacity: 0.9; }
/* Medidas de unas puertas unidas que NO valen: se ven, pero apagadas. */
.rin-atenuado { opacity: 0.55; }

/* --- 1.195.0: formulario ADAPTATIVO y alineado -------------------------
   Las cajas se colocan solas por filas lógicas (ángulo+alto · L1+F1 ·
   L2+F2 · grueso+fuga · holgura). Nada de anchos fijos: `auto-fit` con un
   mínimo en `rem` (así respeta el tamaño de letra de la app) deja DOS
   columnas en un iPhone de 375 px, y a partir de tablet se fijan esas dos
   columnas para que las parejas no se rompan en pantallas anchas. El ritmo
   sale de --esp-fila / --esp-chip como en el resto de la app. */
.rin-form {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
    row-gap: var(--esp-fila);
    column-gap: var(--esp-chip);
    align-items: start;
}
@media (min-width: 620px) {
    .rin-form { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 48rem; }
}
/* Cada campo es una columna: etiqueta arriba, caja debajo y la ayuda al pie.
   La etiqueta reserva dos líneas para que las CAJAS de una misma fila queden
   a la misma altura aunque un rótulo se parta en dos (nada de dientes de
   sierra), y la caja nunca lleva ancho en px. */
.rin-form > .td-campo {
    display: grid;
    grid-template-rows: auto auto 1fr;
    padding-left: 0; padding-right: 0;
    min-width: 0;
}
.rin-form > .td-campo > .form-label {
    display: flex; align-items: flex-end;
    min-height: 2.6em; margin-bottom: 4px;
}
/* Donde hay `subgrid` (Chrome 117+, Safari 16+) las tres filas de cada campo
   —rótulo, caja y ayuda— son las MISMAS para toda la fila de la rejilla: las
   cajas quedan alineadas al píxel aunque un rótulo ocupe dos líneas y el de
   al lado una. Sin subgrid queda el apaño del `min-height` de arriba, que
   deja como mucho unos píxeles de desnivel. */
@supports (grid-template-rows: subgrid) {
    .rin-form { grid-auto-rows: auto; }
    .rin-form > .td-campo { grid-row: span 3; grid-template-rows: subgrid; }
    .rin-form > .td-campo > .form-label { min-height: 0; }
}
.rin-form > .td-campo .form-input { width: 100%; }
/* Bloques que ocupan la fila entera (los chips de la bisagra). */
.rin-bloque-ancho { grid-column: 1 / -1; }

/* --- 1.195.0: zoom del croquis y vista 3D ------------------------------- */
.rin-btn-zoom {
    width: auto; font-size: 1rem; line-height: 1;
    padding: 8px 12px; min-width: 44px;
}
.rin-zoom-nivel { font-size: 0.85rem; font-weight: 700; }

/* --- 1.195.0: overlay de la vista 3D (sobre el patrón .hv3d-*) ----------
   Los dos deslizadores viven DENTRO del overlay, en una barra al pie; la
   pista sube para no quedar debajo de ellos. */
.rin3d-barra {
    position: absolute; left: 0; right: 0;
    bottom: max(8px, env(safe-area-inset-bottom));
    padding: var(--esp-fila) var(--esp-borde);
    display: flex; flex-direction: column; gap: var(--esp-fila);
    background: rgba(255, 255, 255, 0.9);
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}
.rin3d-sim { display: flex; flex-direction: column; gap: 2px; }
.rin3d-sim-lbl {
    display: flex; align-items: baseline; flex-wrap: wrap;
    row-gap: 2px; column-gap: var(--esp-chip);
    font-size: 0.85rem; color: #5c5c55;
}
.rin3d-sim-nom { flex: 1 1 auto; min-width: 0; }
.rin3d-sim-gr { font-weight: 800; font-size: 1rem; color: #2b2b28; }
.rin3d-sim-margen { font-size: 0.85rem; font-weight: 700; }
.rin3d-slider { width: 100%; flex: none; }
.rin3d-pista { bottom: 132px; }
.rin3d-etiqueta { font-size: 0.9rem; }
.rin-banner {
    background: #fdecea; color: #8a1f16; border-left: 4px solid #c5332c;
    border-radius: 6px; padding: 10px 12px; margin-top: var(--space-3);
    font-size: 0.92rem; font-weight: 700; line-height: 1.45;
}

/* --- Croquis a PANTALLA COMPLETA ---------------------------------------
   `fs-activo` vale para la nativa y para la falsa; `fs-falsa` solo añade el
   anclaje a la ventana. El canvas se estira con `flex: 1 1 0` + `min-height:
   0` (con `height: 100%` colapsa y con `flex-basis: auto` el backing se
   realimenta y la página crece sin freno). */
.util-btn-fs { width: auto; font-size: 0.9rem; padding: 8px 12px; }
.util-canvas-wrap.fs-activo {
    background: var(--c-bg);
    display: flex; flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
    margin-top: 0;
    overflow: auto;
}
.util-canvas-wrap.fs-falsa { position: fixed; inset: 0; z-index: 2900; }
.util-canvas-wrap.fs-activo .util-canvas {
    flex: 1 1 0; min-height: 0; max-height: none; height: auto;
}
/* A pantalla completa el pie sobra: manda el dibujo. */
.util-canvas-wrap.fs-activo .util-canvas-pie { display: none; }
.util-canvas-wrap.fs-activo .util-croquis-controles { margin-top: 0; }

/* --- MODO GUIADO --------------------------------------------------------
   Tarjeta fija abajo con la explicación del paso y el bloque resaltado. */
.util-guia-lanzador {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: 10px; margin-bottom: var(--space-3);
}
.util-btn-guia { width: auto; font-size: 0.95rem; padding: 10px 14px; }
.util-guia-lanzador .util-ayuda { margin-top: 0; flex: 1 1 180px; }
.util-guia-panel {
    position: fixed; left: 0; right: 0;
    /* Por encima del visor 3D (3000) y de la pantalla completa falsa del
       croquis (2900): si el asistente se queda DEBAJO, con el 3D abierto no se
       puede ni pasar de paso ni salir, y el usuario acaba atrapado en el
       canvas. Estando encima, "Siguiente" cierra lo que estorbe (lo hace la
       página en `antesDeMover`) y sigue explicando. Los sheets no compiten: la
       tarjeta se esconde sola mientras hay uno abierto. */
    bottom: 0; z-index: 3200;
    background: var(--c-card); border-top: 2px solid var(--c-primary);
    box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.18);
    padding: var(--space-3) var(--space-3) max(var(--space-3), env(safe-area-inset-bottom));
    display: flex; flex-direction: column; gap: 6px;
}
/* La tarjeta se va ARRIBA cuando el bloque explicado está al final de la
   página y no se puede subir más (si no, quedaría debajo de ella). */
.util-guia-panel.util-guia-arriba {
    top: 0; bottom: auto;
    border-top: none; border-bottom: 2px solid var(--c-primary);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
    padding: max(var(--space-3), env(safe-area-inset-top)) var(--space-3) var(--space-3);
}
/* Mientras hay un sheet abierto (el del LED) la tarjeta estorba. */
.util-guia-panel.util-guia-oculto { display: none; }
/* Compactada (a mano o porque el bloque no cabía): sin la explicación larga. */
.util-guia-panel.util-guia-plegado .util-guia-texto { display: none; }
/* Barra mínima: para bloques MÁS ALTOS que la pantalla (el del LED con varias
   tiras). Todo en una fila para que la tarjeta no le robe sitio al bloque. */
.util-guia-panel.util-guia-mini {
    flex-direction: row; align-items: center; gap: 8px;
    padding: 8px var(--space-3) max(8px, env(safe-area-inset-bottom));
}
.util-guia-panel.util-guia-mini .util-guia-titulo { display: none; }
.util-guia-panel.util-guia-mini .util-guia-cabecera { flex: 1 1 auto; gap: 6px; }
.util-guia-panel.util-guia-mini .util-guia-botones { flex: 0 0 auto; margin-top: 0; }
.util-guia-panel.util-guia-mini .util-guia-btn { font-size: 0.85rem; padding: 8px 10px; }
.util-guia-panel.util-guia-mini .util-guia-plegar,
.util-guia-panel.util-guia-mini .util-guia-salir { width: 32px; height: 32px; }
/* En la barra mínima el contador va corto ("5/7") y el botón de "ver también"
   se queda en su icono: en el paso del LED es el atajo al croquis. */
.util-guia-contador-corto { display: none; font-size: 0.8rem; font-weight: 800; color: var(--c-text-soft); }
.util-guia-panel.util-guia-mini .util-guia-contador { display: none; }
.util-guia-panel.util-guia-mini .util-guia-contador-corto { display: block; }
.util-guia-extra-corto { display: none; }
.util-guia-panel.util-guia-mini .util-guia-extra { width: auto; padding: 6px 10px; font-size: 1rem; margin-top: 0; }
.util-guia-panel.util-guia-mini .util-guia-extra-largo { display: none; }
.util-guia-panel.util-guia-mini .util-guia-extra-corto { display: inline; }
.util-guia-cabecera { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.util-guia-cab-botones { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.util-guia-plegar {
    width: 36px; height: 36px; border-radius: 18px; border: 1px solid var(--c-border-strong);
    background: transparent; color: var(--c-text); font-size: 1rem; cursor: pointer;
    display: flex; align-items: center; justify-content: center; flex: 0 0 auto;
}
/* Botón "ver también" del paso (saltar al croquis sin salir del guiado). */
.util-guia-extra {
    width: auto; font-size: 0.9rem; padding: 8px 12px; margin-top: 0;
    flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.util-guia-contador {
    font-size: 0.78rem; text-transform: uppercase; letter-spacing: .04em;
    color: var(--c-text-soft); font-weight: 700;
}
.util-guia-salir {
    width: 36px; height: 36px; border-radius: 18px; border: 1px solid var(--c-border-strong);
    background: transparent; color: var(--c-text); font-size: 1rem; cursor: pointer;
    display: flex; align-items: center; justify-content: center; flex: 0 0 auto;
}
.util-guia-titulo { font-weight: 800; font-size: 1.05rem; }
.util-guia-texto { font-size: 0.92rem; line-height: 1.45; color: var(--c-text); }
.util-guia-botones { display: flex; gap: 10px; margin-top: 4px; }
.util-guia-btn { flex: 1 1 0; }
/* Bloque señalado por la guía */
.util-guia-foco {
    outline: 3px solid var(--c-primary); outline-offset: 5px;
    border-radius: var(--radius-sm);
    background: var(--c-primary-soft);
}
/* Aire para que la tarjeta de la guía no tape el final del formulario. */
body.util-guiando .util-page { padding-bottom: 260px; }

/* ===================================================================
   Vista 3D del hueco visto (overlay a pantalla completa sobre la
   utilidad). Fondo claro fijo: el croquis 3D se lee igual en los dos
   temas, como el resto de dibujos técnicos de la app.
   =================================================================== */
.hv3d-overlay {
    position: fixed; inset: 0; z-index: 3000;
    background: #f7f7f4; overflow: hidden;
}
.hv3d-canvas { display: block; width: 100%; height: 100%; touch-action: none; }
.hv3d-cerrar {
    position: absolute; top: max(10px, env(safe-area-inset-top)); right: 10px;
    width: 44px; height: 44px; border-radius: 22px; border: none;
    background: rgba(0, 0, 0, 0.55); color: #fff; font-size: 1.3rem; line-height: 1;
    display: flex; align-items: center; justify-content: center; cursor: pointer;
}
/* Pantalla completa: mismo tamaño que el de cerrar, a su izquierda. */
.hv3d-fs {
    position: absolute; top: max(10px, env(safe-area-inset-top)); right: 62px;
    width: 44px; height: 44px; border-radius: 22px; border: none;
    background: rgba(0, 0, 0, 0.55); color: #fff; font-size: 1.2rem; line-height: 1;
    display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.hv3d-etiqueta {
    position: absolute; left: 12px; right: 12px;
    top: max(12px, env(safe-area-inset-top));
    margin-right: 114px;   /* deja hueco a los botones de cerrar y pantalla completa */
    background: rgba(20, 20, 20, 0.82); color: #fff;
    border-radius: 8px; padding: 10px 12px;
    font-size: 0.95rem; font-weight: 600; line-height: 1.35;
}
/* Cotas de luz que salen al arrastrar una balda en el 3D (van sobre el hueco
   al que corresponden; el JS las coloca proyectando el punto medio). */
.hv3d-cotas { position: absolute; inset: 0; pointer-events: none; }
.hv3d-cota {
    position: absolute; transform: translate(-50%, -50%);
    background: rgba(224, 96, 16, 0.95); color: #fff;
    border-radius: 6px; padding: 3px 8px;
    font-size: 0.85rem; font-weight: 700; white-space: nowrap;
}
/* Cota de la tira del techo mientras se corre en profundidad: cuelga del
   overlay (no del contenedor de las cotas de luz) y va en ámbar, el color con
   el que se pinta la tira. */
.hv3d-cota-led {
    pointer-events: none;
    background: rgba(168, 121, 10, 0.95);
}
.hv3d-pista {
    position: absolute; left: 12px; right: 12px;
    bottom: max(12px, env(safe-area-inset-bottom));
    text-align: center; font-size: 0.8rem; color: #5c5c55;
    background: rgba(255, 255, 255, 0.8); border-radius: 8px; padding: 6px 8px;
}

/* ===== Vista de IMPRESIÓN del informe de la toma de datos (1.72.0) =====
   El informe se pinta en un <iframe srcdoc> MISMO ORIGEN dentro de la app: así
   su propio botón "🖨 Imprimir / Guardar PDF" carga (/js/informe-print.js) y la
   impresión sale de un gesto real del usuario — que es lo que exige Safari en
   iPad. La ventana blob: de antes dejaba el botón muerto (path opaco). */
.informe-page {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    padding: 0;
}
.informe-marco {
    flex: 1 1 0;
    min-height: 0;
    width: 100%;
    border: 0;
    background: #fff;
}
/* Pista discreta de cómo guardar el PDF en iPad/iPhone (1.73.0): en iOS no hay
   "Guardar como PDF" en el cuadro de impresión — se pellizca la vista previa y
   se comparte. */
.informe-hint {
    flex: 0 0 auto;
    padding: 6px 12px;
    font-size: 0.8rem;
    color: var(--c-text-muted, #6b7280);
    background: var(--c-bg-soft, #f6f7f9);
    border-bottom: 1px solid var(--c-border, #e3e5e8);
}
/* ===== Vista previa A ESCALA de las hojas de albarán/presupuesto (1.171.0) =====
   La hoja se maqueta SIEMPRE al ancho útil de un A4 (192 mm = 210 − 10 izq − 8 der
   del @page) para que la pantalla y el papel sean idénticos. En el móvil eso no cabe
   (375 px ≈ 99 mm), así que el iframe conserva su ancho A4 y se REDUCE con
   transform: scale desde hojaDocumento.js — como un visor de PDF. Nada de esto llega
   a la impresión: el print sale del documento de dentro del iframe. */
.hoja-visor {
    display: block;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    background: #e9eaec;
    padding: 8px;
}
.hoja-lienzo { margin: 0 auto; }
.informe-marco.hoja-marco {
    flex: none;
    width: 192mm;
    min-width: 192mm;
    transform-origin: top left;
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
}
/* Plan B (sólo si contentWindow.print() falla): se imprime la página entera con
   el iframe estirado a su altura real; el resto de la app desaparece. */
@media print {
    body.imprimiendo-informe .hoja-visor { overflow: visible; background: none; padding: 0; }
    body.imprimiendo-informe .hoja-lienzo { width: auto !important; height: auto !important; }
    /* !important: desde 1.202.0 el ancho del iframe se fija EN LÍNEA con el ancho real
       del documento (fix del borde derecho cortado), y en papel hay que soltarlo. */
    body.imprimiendo-informe .informe-marco.hoja-marco { transform: none !important; width: 100% !important; min-width: 0 !important; box-shadow: none; }
    body.imprimiendo-informe .topbar,
    body.imprimiendo-informe .toast,
    body.imprimiendo-informe .sheet-backdrop,
    body.imprimiendo-informe .btn-subir,
    body.imprimiendo-informe .informe-hint { display: none !important; }
    body.imprimiendo-informe .informe-page { display: block; padding: 0; }
    body.imprimiendo-informe .informe-marco { width: 100%; border: 0; }
}

/* === Corrección de presupuestos tras rectificar el contacto (fase H6) ===
   Detalle campo → valor viejo → valor nuevo dentro del sheet de confirmación.
   La lista puede ser larga (varios presupuestos × 9 campos) → scroll propio. */
.td-presup-dif-lista {
    max-height: 45vh;
    overflow-y: auto;
    text-align: left;
}
.td-presup-dif {
    border: 1px solid var(--c-border);
    border-radius: 8px;
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-2);
    background: var(--c-bg-soft, transparent);
}
.td-presup-dif-cab { margin-bottom: 4px; }
.td-presup-dif-entregado { color: #b26a00; font-weight: 600; }
.td-presup-dif-campo { font-size: 0.8125rem; line-height: 1.5; }
.td-presup-dif-etq { color: var(--c-text-soft); }
.td-presup-dif-viejo { text-decoration: line-through; color: var(--c-text-soft); }

/* =====================================================================
   Características normalizadas (ArticulosCaracteristicas) — MV1 1.87.0
   ===================================================================== */

/* --- Badge de clase energética en la ficha del artículo ---
   Paleta de la etiqueta energética europea. El color de texto se elige por
   CONTRASTE: blanco sobre el verde y el rojo, negro sobre los tonos claros
   (B..F). Nada de estilos calculados en JS: aquí y solo aquí. */
.art-clase-badge {
    display: inline-block;
    min-width: 42px;
    padding: 2px 10px;
    border-radius: 4px;
    font-weight: 700;
    font-size: 0.85rem;
    letter-spacing: 0.5px;
    text-align: center;
}
.art-clase-a  { background: #2e7d32; color: #fff; }
.art-clase-b  { background: #7cb342; color: #1b1b1b; }
.art-clase-c  { background: #c0ca33; color: #1b1b1b; }
.art-clase-d  { background: #fdd835; color: #1b1b1b; }
.art-clase-e  { background: #ffb300; color: #1b1b1b; }
.art-clase-f  { background: #fb8c00; color: #1b1b1b; }
.art-clase-g  { background: #e53935; color: #fff; }
.art-clase-nd { background: var(--c-border-strong, #bbb); color: #1b1b1b; }

/* --- Variante COMPACTA para los sheets (catálogos, editores de la visita,
   ➕ del carrito) — 1.118.0. Dentro de un sheet no queremos card sobre card:
   mismo tratamiento que .cat-det-ficha (borde fino, sin sombra ni fondo propio)
   para que en claro y en oscuro se vea como un bloque más del formulario. --- */
.art-carac-compacta {
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    margin-top: var(--space-2);
    font-size: 0.9rem;
}
.art-carac-compacta-tit {
    font-size: 0.8125rem; font-weight: 600;
    color: var(--c-text-soft, #6c757d);
    text-transform: uppercase; letter-spacing: 0.4px;
    margin-bottom: 6px;
}

/* --- "Más detalles" del acr_json: plegable NATIVO (<details>), sin JS --- */
.art-carac-mas { margin-top: 6px; }
.art-carac-mas > summary {
    cursor: pointer;
    padding: 6px 0;
    font-size: 0.85rem;
    color: var(--c-primary, #1976d2);
    font-weight: 600;
    list-style: revert;
}
.art-carac-mas[open] > summary { margin-bottom: 4px; }
.art-carac-mas-body { padding-left: 4px; }

/* =====================================================================
   Slider de RANGO DOBLE (core/rangoDoble.js)
   Dos <input type=range> superpuestos: el input entero NO recibe puntero,
   solo su pulgar — así el dedo agarra siempre el pulgar más cercano.
   ===================================================================== */
.rgd { margin: 10px 0 16px; }
.rgd-cab {
    display: flex; justify-content: space-between; align-items: baseline;
    margin-bottom: 2px; font-size: 0.85rem;
}
.rgd-etiqueta { color: var(--c-text-soft, #6c757d); font-weight: 600; }
.rgd-valor { color: var(--c-text-soft, #6c757d); font-variant-numeric: tabular-nums; }
.rgd-valor-activo { color: var(--c-primary, #1976d2); font-weight: 700; }

.rgd-pista {
    position: relative;
    height: 34px;
    margin: 0 12px;               /* deja sitio a los pulgares en los extremos */
}
.rgd-pista::before {              /* pista de fondo */
    content: '';
    position: absolute; left: 0; right: 0; top: 15px; height: 4px;
    border-radius: 2px;
    background: var(--c-border-strong, #c9c9c9);
}
.rgd-fill {                        /* tramo seleccionado */
    position: absolute; top: 15px; height: 4px;
    border-radius: 2px;
    background: var(--c-primary, #1976d2);
}
.rgd-input {
    position: absolute; left: 0; top: 0;
    width: 100%; height: 34px;
    margin: 0; padding: 0;
    background: none;
    -webkit-appearance: none; appearance: none;
    pointer-events: none;          /* solo los pulgares reciben el toque */
    z-index: 3;
}
.rgd-input.rgd-max { z-index: 4; }
.rgd-input.rgd-encima { z-index: 5; }   /* mínimo pegado al tope derecho */
.rgd-input:focus { outline: none; }
.rgd-input::-webkit-slider-runnable-track { background: transparent; height: 34px; }
.rgd-input::-moz-range-track { background: transparent; height: 34px; }
.rgd-input::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    pointer-events: auto;
    width: 24px; height: 24px; margin-top: 5px;
    border-radius: 50%;
    background: var(--c-card, #fff);
    border: 2px solid var(--c-primary, #1976d2);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    cursor: pointer;
}
.rgd-input::-moz-range-thumb {
    pointer-events: auto;
    width: 22px; height: 22px;
    border-radius: 50%;
    background: var(--c-card, #fff);
    border: 2px solid var(--c-primary, #1976d2);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    cursor: pointer;
}
.rgd-input:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.35); }
.rgd-input:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.35); }

/* =====================================================================
   Hoja "⚙ Filtros avanzados" (core/filtrosAvanzados.js)
   ===================================================================== */
.fav-bloque {
    text-align: left;
    padding: 10px 0;
    border-bottom: 1px solid var(--c-border, #e0e0e0);
}
.fav-bloque:last-of-type { border-bottom: 0; }
.fav-tit {
    font-size: 0.8rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.4px; color: var(--c-text-soft, #6c757d);
    margin-bottom: 6px;
}
.fav-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.fav-chips-clase .chip { min-width: 46px; justify-content: center; }
.fav-nota, .fav-aviso {
    font-size: 0.78rem; color: var(--c-text-soft, #6c757d);
    line-height: 1.4; text-align: left;
}
.fav-nota { margin-top: 6px; }
/* MV12: subtítulos de la sección de filtros por familia dentro de la hoja ⚙ */
.fav-fam-sub {
    font-size: 0.78rem; font-weight: 600; text-align: left;
    color: var(--c-text-soft, #6c757d);
    margin: 8px 0 4px;
}
.fav-fam-sub:first-child { margin-top: 0; }
.fav-aviso {
    margin-top: 10px; padding: 8px 10px;
    background: var(--c-bg-soft, rgba(25, 118, 210, 0.07));
    border-left: 3px solid var(--c-primary, #1976d2);
    border-radius: 4px;
}

/* =====================================================================
   PIN de cambio rápido de usuario y bloqueo por inactividad (1.96.0, MV11)
   ===================================================================== */

/* --- Teclado numérico ---
   GEOMETRÍA PROPIA (regla 2 de UI, 1.97.2): el pad se dimensiona SOLO, sin
   depender del contenedor donde se monte (sheet de crear PIN, página de cambio
   de usuario, login con PIN, pantalla de bloqueo).
   Bug corregido: dentro de `.selusu` (flex column con align-items:center) el
   `.pinpad` no tenía `width`, así que se quedaba a fit-content; el grid de
   teclas (width:100%) medía entonces el mínimo del contenido → columnas de ~30px
   con min-height 60px = las "cápsulas verticales" de las capturas del usuario.
   Ahora: ancho fijo acotado (--pinpad-ancho), centrado, teclas con aspect-ratio 1
   y `flex: 0 0 auto` para que ningún contenedor pueda estirarlas. */
.pinpad {
    /* 3 teclas de 88px + 2 gaps de --esp-chip (6px) */
    --pinpad-ancho: 276px;
    display: flex;
    flex-direction: column;
    align-items: center;
    align-self: center;          /* no lo estira un contenedor con align-items:stretch */
    flex: 0 0 auto;              /* ni lo estira/encoge un flex column con altura */
    gap: var(--esp-fila);
    width: 100%;
    max-width: var(--pinpad-ancho);
    margin: var(--esp-fila) auto;
}

.pinpad-puntos {
    display: flex;
    gap: 14px;
    justify-content: center;
    min-height: 22px;
}

.pinpad-punto {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 2px solid var(--c-border-strong);
    background: transparent;
    transition: background 0.12s ease, border-color 0.12s ease;
}

.pinpad-punto.lleno {
    background: var(--c-primary);
    border-color: var(--c-primary);
}

/* Las dos últimas posiciones cuando admitimos 4-6 cifras */
.pinpad-punto-opc { border-style: dashed; }

.pinpad-teclas {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-auto-rows: auto;        /* filas por contenido: nunca se reparten el alto */
    align-content: start;
    gap: var(--esp-chip);
    width: 100%;
    max-width: var(--pinpad-ancho);
    margin: 0 auto;
    min-height: 0;
}

.pinpad-tecla {
    display: flex;               /* centra tanto el número como el SVG de borrar */
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
    font-weight: 600;
    padding: 0;
    aspect-ratio: 1 / 1;         /* tecla CUADRADA en cualquier contexto */
    min-height: 56px;            /* fallback si el motor no soporta aspect-ratio */
    height: auto;
    align-self: start;
    border-radius: var(--radius-lg);
    border: 1px solid var(--c-border);
    background: var(--c-bg-soft);
    color: var(--c-text);
    cursor: pointer;
}

.pinpad-tecla:active { background: var(--c-primary-soft); }
.pinpad-tecla-borrar { font-size: 1.3rem; }
.pinpad-tecla-borrar svg { display: block; }
.pinpad-hueco { display: block; }
.pinpad-ok { max-width: var(--pinpad-ancho); width: 100%; }
.pinpad-off { opacity: 0.5; pointer-events: none; }

.pin-sub { margin-bottom: var(--space-2); line-height: 1.35; }

/* Ritmo de TODOS los sheets (regla 1 de UI; PIN en 1.97.2, generalizado en
   1.100.0/MV12): un botón hijo DIRECTO de `.sheet` va a ancho completo, uno por
   fila y con --esp-fila de separación.

   Causa raíz que arregla: `.sheet` es un contenedor block y `.btn` es
   inline-flex, así que dos botones hermanos sueltos dentro del sheet caían en la
   MISMA línea, pegados (gap 0) — se veía en "Solución antes de cerrar", en la
   nota personalizada, en la cita y en los sheets de compras. Las clases mt-2/mt-3
   solo daban margen VERTICAL, que no era el problema.

   Los botones que sí deben ir en flujo (los chips de plantillas de solución) NO
   son hijos directos: viven dentro de `.solucion-chips`, así que no les afecta.
   Los márgenes laterales los da el padding del propio sheet. */
.sheet > .btn,
.selusu > .btn {
    display: flex;
    width: 100%;
    justify-content: center;
    text-align: center;
    margin-top: var(--esp-fila);
}
.selusu > .btn + .btn { margin-top: var(--esp-fila); }

.pin-input-clave {
    width: 100%;
    padding: 12px;
    font-size: 1.05rem;
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-lg);
    background: var(--c-card);
    color: var(--c-text);
}

/* --- Selector "¿Quién eres?" --- */
.selusu { display: flex; flex-direction: column; align-items: center; width: 100%; }
.selusu-tit { margin: 0 0 4px; font-size: 1.25rem; text-align: center; }
.selusu-sub { text-align: center; margin-bottom: var(--space-3); }
.selusu-ayuda { text-align: center; margin-top: var(--space-2); font-size: 0.85rem; }

.selusu-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 12px;
    width: 100%;
}

.selusu-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 14px 8px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-lg);
    background: var(--c-card);
    color: var(--c-text);
    cursor: pointer;
}

.selusu-card:active { background: var(--c-primary-soft); }
.selusu-card-actual { border-color: var(--c-primary); }

.selusu-avatar {
    width: 58px;
    height: 58px;
    border-radius: 50%;
    background: var(--c-primary);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.35rem;
    font-weight: 700;
}

.selusu-nombre { font-weight: 600; text-align: center; line-height: 1.2; }
.selusu-meta { font-size: 0.78rem; color: var(--c-text-muted); text-align: center; }

.selusu-error {
    color: var(--c-danger);
    text-align: center;
    min-height: 1.2em;
    font-weight: 600;
}

.selusu-error-sub { font-weight: 400; font-size: 0.85rem; margin-top: 4px; }
.selusu-card-box { width: 100%; }
/* En tablet la tarjeta se quedaba pegada a la izquierda (max-width sin centrar). */
.cambiar-usuario-page .login-card { max-width: 460px; margin-inline: auto; }
.cambiar-usuario-page > .btn { max-width: 460px; margin-inline: auto; width: 100%; }
.login-cambiar-usuario { width: 100%; margin-top: var(--space-3); }

/* --- Pantalla de bloqueo por inactividad --- */
.bloqueo-overlay {
    position: fixed;
    inset: 0;
    z-index: 2000;                 /* por encima de todo, incluidos sheets */
    background: var(--c-bg);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
    overflow-y: auto;
}

.bloqueo-caja {
    width: 100%;
    max-width: 460px;
    background: var(--c-card);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
    box-shadow: var(--shadow-2);
}

body.con-bloqueo { overflow: hidden; }

/* --- Ajustes: PIN y usuarios de la tablet --- */
.ajustes-pin-estado { margin-bottom: var(--space-2); font-weight: 600; }
.ajustes-pin-aviso { color: var(--c-warning); margin-bottom: var(--space-2); }

.ajustes-usuario-fila {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 0;
    border-bottom: 1px solid var(--c-border);
}

.ajustes-usuario-fila:last-child { border-bottom: none; }

.ajustes-usuario-ini {
    width: 38px;
    height: 38px;
    flex: 0 0 38px;
    border-radius: 50%;
    background: var(--c-bg-soft);
    color: var(--c-text);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
}

.ajustes-usuario-txt { flex: 1 1 auto; min-width: 0; }
.ajustes-usuario-nom { font-weight: 600; }
.btn-quitar-usuario { flex: 0 0 auto; padding: 6px 10px; }


/* =============================================================
   ASISTENTE DE LA APP (MV13, 1.101.0)
   Botón flotante 💬 + panel de chat. Colores del tema (claro y
   oscuro salen solos porque todo son variables) y separaciones
   con --esp-* como el resto de bloques de formulario.
   ============================================================= */

/* Botón flotante. Va POR ENCIMA del botón "subir" (.btn-scroll-top, que está a
   bottom:90px y mide 44px → ocupa hasta 134px): a 146px no se tapan nunca, ni
   siquiera cuando el de subir aparece al desplazarse. */
.asis-fab {
    position: fixed;
    right: 14px;
    bottom: calc(146px + env(safe-area-inset-bottom));
    width: 52px; height: 52px;
    border-radius: 50%;
    border: 0;
    background: var(--c-primary);
    color: #fff;
    font-size: 1.5rem; line-height: 1;
    cursor: pointer;
    box-shadow: var(--shadow-3);
    z-index: 90;              /* bajo los sheets (100) y los toasts */
    display: flex; align-items: center; justify-content: center;
    /* Ligeramente translúcido para que "flote" sobre el contenido sin robarle
       protagonismo (como .btn-scroll-top). Al tocarlo se vuelve opaco. */
    opacity: 0.92;
    transition: transform .12s, background .12s, opacity .12s;
}
.asis-fab:hover, .asis-fab:active { opacity: 1; }
.asis-fab:hover { background: var(--c-primary-dark); }
.asis-fab:active { transform: scale(0.94); }

/* El panel usa el mismo backdrop que los sheets, pero centrado y a casi toda
   la pantalla: una conversación necesita alto. */
.asis-backdrop { align-items: stretch; justify-content: center; }

.asis-panel {
    background: var(--c-card);
    width: 100%;
    max-width: 640px;                /* en tablet no se estira a lo ancho */
    margin: var(--esp-borde);
    margin-bottom: calc(var(--esp-borde) + env(safe-area-inset-bottom));
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-3);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* Una conversación necesita alto: el panel ocupa casi toda la pantalla en
       lugar de encogerse al contenido (si no, con el mensaje de bienvenida solo
       quedaba una tarjeta de 370px flotando en medio). */
    height: 100%;
    min-height: 0;
}

/* Cabecera */
.asis-cab {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--esp-chip);
    padding: var(--esp-fila) var(--esp-borde);
    border-bottom: 1px solid var(--c-border);
    background: var(--c-bg-soft);
    flex: 0 0 auto;
}
.asis-cab-tit { font-weight: 700; font-size: 1.0625rem; color: var(--c-text); }
.asis-cab-botones { display: flex; gap: var(--esp-chip); }
.asis-cab-btn {
    min-width: 40px; height: 40px;
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    background: var(--c-card);
    color: var(--c-text);
    font-size: 1.125rem; line-height: 1;
    cursor: pointer;
}
.asis-cab-btn.asis-activo { background: var(--c-primary-soft); border-color: var(--c-primary); }

/* Conversación */
.asis-conversacion {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: var(--esp-borde);
    display: flex;
    flex-direction: column;
    gap: var(--esp-fila);
}

.asis-bienvenida {
    background: var(--c-bg-soft);
    border-radius: var(--radius);
    padding: var(--space-3);
    display: flex; flex-direction: column; gap: var(--esp-fila);
}
.asis-bienvenida-txt { color: var(--c-text); line-height: 1.4; }
.asis-sugerencias { display: flex; flex-direction: column; gap: var(--esp-fila); }
.asis-sugerencia {
    text-align: left;
    background: var(--c-card);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    color: var(--c-text);
    padding: 10px 12px;
    min-height: 44px;
    font-size: 0.9375rem;
    cursor: pointer;
}
.asis-sugerencia:hover { border-color: var(--c-primary); }

.asis-burbuja {
    max-width: 88%;
    border-radius: var(--radius);
    padding: 10px 12px;
    line-height: 1.45;
    word-break: break-word;
}
.asis-burbuja-txt { display: block; }
.asis-usuario {
    align-self: flex-end;
    background: var(--c-primary);
    color: #fff;
}
.asis-asistente {
    align-self: flex-start;
    background: var(--c-bg-soft);
    color: var(--c-text);
    border: 1px solid var(--c-border);
}
.asis-pensando { opacity: 0.7; font-style: italic; }

/* Botones de acción dentro de una respuesta: ocupan su propia línea con aire. */
.asis-accion {
    display: block;
    width: 100%;
    margin-top: var(--esp-fila);
    text-align: left;
    white-space: normal;
    line-height: 1.3;
    min-height: 44px;
    height: auto;
}

/* Barra de escritura: desde 1.134.0 el cuadro es el COMPOSITOR compartido
   (`js/core/composer.js`, clases .comp-*), el mismo del chat interno y de
   "Preguntar al tablón". Aquí solo queda el marco de abajo del panel. */
.asis-barra {
    flex: 0 0 auto;
    border-top: 1px solid var(--c-border);
    background: var(--c-card);
    padding-bottom: env(safe-area-inset-bottom, 0);
}

/* En pantallas altas se deja aire arriba y abajo en vez de pegarse a los bordes. */
@media (min-height: 700px) {
    .asis-backdrop { align-items: center; }
    .asis-panel { height: 88vh; }
}

/* --- Guías paso a paso (MV14, 1.102.0) ---------------------------------
   Entrada a los recorridos guiados desde el mensaje de bienvenida: no gasta
   IA, así que va destacada por encima de las sugerencias de pregunta. */
.asis-guias-btn {
    width: 100%;
    min-height: 44px;
    margin-top: var(--esp-fila);
    text-align: left;
    white-space: normal;
    line-height: 1.3;
    height: auto;
}
/* Lista de guías dentro del sheet: título arriba y su explicación debajo. */
.asis-guia-intro {
    color: var(--c-text-soft);
    font-size: 0.9375rem;
    line-height: 1.4;
    margin-bottom: var(--esp-fila);
}
.asis-guia-item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    width: 100%;
    height: auto;
    min-height: 44px;
    padding: 10px var(--esp-borde);
    margin-bottom: var(--esp-fila);
    text-align: left;
    white-space: normal;
}
.asis-guia-tit { font-weight: 700; line-height: 1.3; }
.asis-guia-desc { font-size: 0.875rem; color: var(--c-text-soft); line-height: 1.35; }

/* ==========================================================================
   FILTROS AVANZADOS de los buscadores de DOCUMENTOS (1.126.0)
   Presupuestos, albaranes de cliente y pedidos a proveedor comparten el mismo
   panel colapsable (core/filtrosDocumentos.js): cabecera táctil con badge del
   nº de filtros activos y, debajo, los campos en filas de dos columnas que
   caen a una sola en pantallas estrechas.
   Espaciados SIEMPRE con las variables --esp-* (reglas de diseño móvil).
   ========================================================================== */
.docfil {
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    padding: 0 var(--esp-borde) var(--esp-fila);
}
.docfil-cab {
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    width: 100%;
    min-height: 44px;
    padding: var(--esp-fila) 0;
    background: none;
    border: 0;
    color: var(--c-text);
    font: inherit;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}
.docfil-cab.active { color: var(--c-primary); }
.docfil-flecha { color: var(--c-text-soft); }
.docfil-tit { flex: 1 1 auto; }
.docfil-badge {
    color: var(--c-primary);
    font-weight: 700;
}
.docfil-body {
    display: flex;
    flex-direction: column;
    row-gap: var(--esp-fila);
    padding-bottom: var(--esp-fila);
}
.docfil-fila {
    display: flex;
    flex-wrap: wrap;
    column-gap: var(--esp-chip);
    row-gap: var(--esp-fila);
}
.docfil-fila > .docfil-campo {
    flex: 1 1 140px;
    min-width: 0;
}
.docfil-campo {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.docfil-campo .form-input { width: 100%; }
.docfil-ayuda { font-size: 0.8125rem; }
.docfil-chips {
    display: flex;
    flex-wrap: wrap;
    column-gap: var(--esp-chip);
    row-gap: var(--esp-chip);
}

/* =========================================================================
   📌 TABLÓN DE ANUNCIOS (1.128.0)
   Reglas de UI del proyecto: márgenes SIEMPRE con las variables --esp-*, y
   nada de glifos técnicos Unicode (solo emojis que existen en iOS/Android).
   ========================================================================= */
.tablon-filtros { align-items: center; }
.tablon-filtros .form-input { flex: 1 1 160px; min-width: 0; }
.tablon-btn-buscar { flex: 0 0 auto; }
.tablon-chips {
    display: flex;
    flex-wrap: wrap;
    column-gap: var(--esp-chip);
    row-gap: var(--esp-fila);
    padding: 0 var(--esp-borde) var(--esp-fila);
}

.tablon-card { text-align: left; width: 100%; }
.tablon-card-fijado { border-left: 4px solid var(--c-primary); }
.tablon-extracto {
    font-size: 0.875rem;
    color: var(--c-text-soft, #6c757d);
    margin-top: 4px;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.tablon-autor { font-size: 0.8rem; color: var(--c-text-soft, #6c757d); margin-left: auto; }
.tablon-tag-nuevo { background: rgba(197, 51, 44, 0.12); color: var(--c-danger, #c5332c); font-weight: 700; }
.tablon-tag-visto { background: rgba(108, 117, 125, 0.14); color: var(--c-text-soft, #6c757d); }
.tablon-tag-ok    { background: rgba(44, 143, 63, 0.14); color: #1e7e34; font-weight: 700; }
.tablon-tag-viejo { background: rgba(240, 160, 32, 0.16); color: #b27500; }

.tablon-titulo { margin: var(--esp-fila) 0 2px; font-size: 1.25rem; line-height: 1.25; }
.tablon-meta { font-size: 0.85rem; margin-bottom: var(--esp-fila); }
/* El cuerpo se escribe a mano con saltos de línea: se respetan tal cual. */
.tablon-cuerpo {
    white-space: pre-wrap;
    word-break: break-word;
    line-height: 1.5;
    margin-bottom: var(--esp-fila);
}
.tablon-aviso {
    background: rgba(255, 193, 7, 0.18);
    border: 1px solid rgba(255, 193, 7, 0.5);
    color: #997404;
    border-radius: var(--radius-sm, 8px);
    padding: 10px 12px;
    margin-bottom: var(--esp-fila);
    font-weight: 600;
}
/* 1.166.0 — el documento a la vista es el anterior y la IA está rehaciendo el
   nuevo por detrás. Aviso DISCRETO: no es un problema, es información. */
.tablon-actualizando {
    display: flex;
    align-items: center;
    gap: var(--esp-chip, 6px);
    font-size: 0.85rem;
    color: var(--c-text-soft, #6c757d);
    background: rgba(108, 117, 125, 0.10);
    border-radius: var(--radius-sm, 8px);
    padding: 6px 10px;
    margin-bottom: var(--esp-fila);
}
/* Sustitución del texto sin salto seco cuando llega el documento nuevo. */
.tablon-fundido { animation: tablon-fundir 0.35s ease-in; }
@keyframes tablon-fundir {
    from { opacity: 0.25; }
    to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .tablon-fundido { animation: none; }
}

.tablon-galeria {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
    gap: var(--esp-chip);
    margin-bottom: var(--esp-fila);
}
.tablon-galeria img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-sm, 8px);
    border: 1px solid var(--c-border, #e0e0e0);
    cursor: pointer;
}
.tablon-btn-enterado { width: 100%; margin-top: var(--esp-fila); font-size: 1.05rem; padding: 14px; }
.tablon-btn-guardar  { width: 100%; margin-top: var(--esp-fila); font-size: 1.05rem; padding: 14px; }
.tablon-textarea { min-height: 180px; }
.tablon-check {
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    margin-top: var(--esp-fila);
}

.tablon-lectura {
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    padding: var(--esp-fila) 0;
    border-bottom: 1px solid var(--c-border, #e0e0e0);
}
.tablon-lectura-ico { font-size: 1.2rem; }
.tablon-lectura-nom { font-weight: 600; }

.tablon-grupo {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--esp-chip);
    padding: var(--esp-fila) 0;
    border-bottom: 1px solid var(--c-border, #e0e0e0);
}
.tablon-grupo-nom { font-weight: 700; }

/* Categoría RETIRADA (1.133.0): no se borra nunca —los anuncios viejos la
   siguen enseñando—, así que se ve atenuada al final de la lista y de los chips. */
.tablon-cat-retirada { opacity: 0.6; }
.chip.chip-inactiva { opacity: 0.7; font-style: italic; }

/* ----- IA del tablón (1.130.0): resumen, etiquetas y referencias [#id] ----- */
.tablon-accesos-ia { padding: 0 var(--esp-borde) var(--esp-fila); }
/* 1.190.0 — barra superior del tablón (📖 Lo esencial · 📖 Pautas · 🤖 Preguntar ·
   🧹 Reorganizar) responsive. En MÓVIL sigue igual: `.ajustes-botonera` los apila
   a ancho completo. En pantallas ANCHAS (tablet/escritorio) van en una sola línea
   a su ancho natural —envuelve si no caben— en vez de estirarse a todo lo ancho.
   El mismo criterio se aplica a la fila de buscar para que 🔎 Buscar no la ocupe. */
@media (min-width: 640px) {
    .tablon-accesos-ia { flex-direction: row; flex-wrap: wrap; align-items: center; }
    .tablon-accesos-ia > .btn { width: auto; flex: 0 1 auto; }
    /* Fila de buscar en una sola línea: el input llena TODO el ancho disponible
       y «🔎 Buscar» va a su ancho natural a la derecha. En móvil (<640px) sigue
       apilado: .btn-primary es width:100% y salta debajo (el flex-basis auto de
       .tablon-btn-buscar resuelve a ese 100%). Aquí lo forzamos a ancho natural. */
    .tablon-filtros { flex-wrap: nowrap; }
    .tablon-filtros .form-input { flex: 1 1 auto; min-width: 0; max-width: none; }
    .tablon-filtros .tablon-btn-buscar { width: auto; flex: 0 0 auto; }
}
.tablon-ia-resumen {
    background: var(--c-surface-alt, rgba(13, 110, 253, 0.06));
    border-left: 4px solid var(--c-primary);
    border-radius: var(--radius-sm, 8px);
    padding: 10px 12px;
    margin-bottom: var(--esp-fila);
    line-height: 1.45;
}
.tablon-ia-titulo {
    font-weight: 700;
    font-size: 0.85rem;
    margin-bottom: 4px;
    color: var(--c-text-soft, #6c757d);
}
.tablon-chips-ia { margin-bottom: var(--esp-fila); }
/* Chip "🤖 ¿Mejor en «X»?" del detalle (1.157.0): discreto pero tocable. */
.tablon-chip-sugerencia {
    border-color: var(--c-primary);
    color: var(--c-primary);
    font-weight: 600;
}
/* ----- 🧹 Reorganizar el tablón (1.157.0) ----- */
.tablon-reorg-tema { text-align: left; }
.tablon-reorg-anuncio {
    display: flex;
    gap: 8px;
    align-items: flex-start;
    padding: 8px 0;
    border-top: 1px solid var(--c-border, rgba(0, 0, 0, 0.08));
}
.tablon-reorg-anuncio > div { min-width: 0; flex: 1 1 auto; }
.tablon-reorg-tit { font-weight: 600; line-height: 1.3; }
/* Referencia al anuncio dentro de un texto generado ("… [#12]"). */
.tablon-ref {
    display: inline;
    background: none;
    border: none;
    padding: 0 2px;
    font: inherit;
    color: var(--c-primary);
    font-weight: 700;
    text-decoration: underline;
    cursor: pointer;
}

/* =========================================================================
   💬 CHAT INTERNO (1.129.0)
   Todo con las variables de tema (--c-*) para que el modo oscuro salga solo,
   y los márgenes con --esp-* (regla de UI del proyecto). Emojis únicamente:
   nada de glifos técnicos Unicode, que en iOS salen como cuadro vacío.
   ========================================================================= */

/* ---- Avatar con iniciales (el color de fondo lo pone el JS: es estable por
        us_id, así que la misma persona se reconoce siempre por su color) ---- */
.chat-avatar {
    flex: 0 0 auto;
    width: 42px; height: 42px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-weight: 700; font-size: 0.95rem;
    letter-spacing: 0.5px;
}
.chat-avatar-sm { width: 32px; height: 32px; font-size: 0.8rem; }
.chat-avatar-lg { width: 60px; height: 60px; font-size: 1.3rem; }
/* Avatar diminuto pegado a las burbujas ajenas de un grupo (1.139.0). */
.chat-avatar-xs { width: 26px; height: 26px; font-size: 0.68rem; }

/* Foto de perfil / de grupo dentro del círculo del avatar (1.139.0). Mientras
   la foto llega —o si no hay— se ven las iniciales de color de siempre. */
.chat-avatar { overflow: hidden; }
.chat-avatar-img {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
}
/* En la cabecera de la conversación va entre la flecha ← y el título. */
.chat-avatar-cab { margin-right: 4px; }

/* Nombre de la persona o del grupo en la cabecera de la conversación (1.175.0).
   Es el título de la pantalla y lo que dice CON QUIÉN se está hablando, así que
   va dos puntos por encima del h1 corriente (1,53rem frente a los 1,375rem de
   --font-size-h1) para que se lea de un vistazo. Lo pone `pages/chat.js` sobre
   el <h1> de la topbar; el resto de cabeceras de la app no se tocan.
   Sigue con `text-overflow: ellipsis` heredado de `.topbar h1`: un nombre largo
   se corta con puntos suspensivos, nunca empuja a los botones 🔍 e ℹ. */
.topbar h1.chat-cab-nombre { font-size: 1.53rem; }

/* ✍ «escribiendo…» bajo el nombre de la cabecera (2.0.3). Va DENTRO de
   `.topbar-title`, así que no empuja a los botones 🔍 e ℹ; y con una sola línea
   recortada, que en un grupo el rótulo puede ser largo («DANI (Daniel …) y
   MARIAJ (María Jesús …) están escribiendo…»). No reserva sitio cuando no hay
   nadie tecleando: nace con display:none y lo enseña `pages/chat.js`. */
.chat-cab-escribiendo {
    font-size: 0.8rem;
    font-style: italic;
    color: var(--c-success);
    line-height: 1.2;
    margin-top: 1px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Lo mismo en la LISTA: sustituye a la vista previa del último mensaje mientras
   dure, en el mismo hueco y con el mismo recorte (hereda `.chat-card-extracto`,
   que ya es una línea con puntos suspensivos). */
.chat-card-extracto.chat-card-escribiendo {
    color: var(--c-success);
    font-style: italic;
}

/* Fila "avatar + burbuja" de los mensajes ajenos en un GRUPO. */
.chat-fila-grupo {
    display: flex;
    align-items: flex-end;
    gap: 6px;
    align-self: flex-start;
    max-width: 90%;
}
.chat-fila-grupo .chat-burbuja { align-self: auto; max-width: 100%; }
.chat-fila-grupo .chat-avatar-xs { margin-bottom: 4px; }

/* ---- Lista de conversaciones ---- */
.chat-lista .chat-card {
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    text-align: left;
    width: 100%;
}
.chat-card-nueva { border-left: 4px solid var(--c-primary); }
.chat-card-cuerpo { flex: 1 1 auto; min-width: 0; }
.chat-card-linea1,
.chat-card-linea2 {
    display: flex; align-items: center; gap: var(--esp-chip);
}
.chat-card-linea2 { margin-top: 2px; }
/* 1.175.0 — El nombre de la persona o del grupo es lo que se BUSCA con la vista
   al recorrer la lista, y se veía MÁS PEQUEÑO que la propia vista previa del
   mensaje: se comía los 13,3px del <button> (ver `button { font-size: inherit }`
   más arriba) mientras el extracto iba a 14. Ahora copia EXACTAMENTE el molde
   que el usuario señaló como bueno —la tarjeta de cliente del buscador de
   albaranes—: nombre a 1rem en negrita (igual que `.cambest-card-cli`) y
   extracto a 0,88rem (igual que `.cambest-card-tit`). Si se cambia el tamaño de
   una de las dos parejas, cambiar la otra: van a juego a propósito. */
.chat-card-nombre { font-size: 1rem; font-weight: 700; flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-card-hora { flex: 0 0 auto; font-size: 0.78rem; color: var(--c-text-soft); }
.chat-card-extracto {
    flex: 1 1 auto; min-width: 0;
    font-size: 0.88rem; color: var(--c-text-soft);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-mute { flex: 0 0 auto; font-size: 0.85rem; }
.chat-badge {
    flex: 0 0 auto;
    min-width: 20px; height: 20px; padding: 0 6px;
    border-radius: 10px;
    background: var(--c-primary); color: #fff;
    font-size: 0.75rem; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center;
}

/* ---- Selector de personas (nuevo chat / añadir al grupo / supervisión) ----
   1.149.0 — las filas son <button> y NO llevaban reset del control nativo: en el
   navegador salían con el gris del sistema y un borde `outset` negro de 2 px, en
   Arial 13,3 px (medido), pegadas unas a otras a 2 px y con el marcado en rosa
   pálido. Nada de eso es el lenguaje de la app. Ahora son una LISTA suave: una
   tarjeta contenedora con el borde de la app, separadores finos entre filas,
   tipografía heredada y el marcado con el ACENTO del tema (franja izquierda +
   fondo suave + nombre en negrita + ✓ del acento). Los tres sitios que enseñan
   personas comparten el componente `filaPersona()` de pages/chat.js. */
.chat-personas {
    background: var(--c-card);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    /* Las filas se separan con su propio borde, no con hueco (el `gap: 2px` de
       `.checklist` dejaba una rejilla de líneas sueltas). */
    gap: 0;
    margin-top: var(--esp-chip);
    /* 1.178.1 — LA LISTA NO SE PODÍA DESPLAZAR (iPhone: se veía hasta la novena
       persona y no había forma de llegar al resto).
       `.checklist` trae `max-height: 50vh` + `overflow-y: auto`, pero el
       `overflow: hidden` de aquí arriba —que está para clipar al border-radius y
       es una regla POSTERIOR con la misma especificidad— pisaba ese `auto`: la
       caja quedaba capada a media pantalla y sin scroll propio. Y como no crecía,
       la página tampoco tenía desbordamiento que desplazar. Doble jaula. (Con el
       buscador sí aparecían: al filtrar quedaban pocas filas y cabían.)
       La solución NO es devolverle el scroll a la caja, sino quitarle el tope:
       la lista crece con su contenido y quien se desplaza es la PÁGINA
       (`.form-page` ya es `overflow-y: auto`). Un solo scroller, que es lo que
       aguanta bien el teclado abierto en iOS y deja el botón de crear al final.
       Sin tope de altura, `overflow: hidden` ya no recorta nada en vertical y
       sigue haciendo su trabajo de redondear las esquinas. */
    max-height: none;
}
.chat-persona {
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    width: 100%;
    text-align: left;
    /* Reset del botón nativo */
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--c-border);
    border-left: 3px solid transparent;   /* carril del marcado (no mueve el texto) */
    border-radius: 0;
    padding: var(--esp-fila) var(--space-3);
    min-height: 52px;                     /* diana táctil cómoda */
    font: inherit;
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--c-text);
    cursor: pointer;
    transition: background 0.12s ease;
}
.chat-personas .chat-persona:last-child { border-bottom: 0; }
.chat-persona:hover { background: var(--c-bg-soft); }
.chat-persona:active { background: var(--c-bg-soft); }
.chat-persona:focus-visible { outline: 2px solid var(--c-primary); outline-offset: -2px; }
.chat-persona-txt {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-persona-check {
    flex: 0 0 auto;
    min-width: 1.25rem;
    text-align: center;
    font-size: 1.125rem;
    font-weight: 700;
    color: var(--c-primary);
}
.chat-persona-sel {
    background: var(--c-primary-soft);
    border-left-color: var(--c-primary);
    font-weight: 700;
}
.chat-persona-sel:hover { background: var(--c-primary-soft); }

/* ---- La conversación ---- */
/* Desde 1.136.0 el compositor FLOTA sobre el hilo (posición absoluta, patrón del
   chat de Claude): el hilo ocupa todo el alto bajo la cabecera y se le abre por
   abajo un hueco del tamaño EXACTO de la tarjeta (`--comp-alto`, que publica
   `pages/chat.js` en cada cambio de altura). Con un hueco fijo, al enganchar una
   cita o al escribir diez líneas el último mensaje quedaba tapado. */
/* OJO con el alto de la página: `.list-page` trae `flex: 1` (= flex-basis 0 +
   grow), y eso GANA al `height: 100dvh` de aquí. Resultado hasta la 1.135.0: el
   hilo se estiraba con TODOS los mensajes, la página entera crecía y el
   compositor se iba hacia abajo con el dedo (medido: página de 8.743 px en una
   conversación de 60 mensajes, con la ventana de 812). El tope duro
   `max-height: 100dvh` + `min-height: 0` es lo que devuelve el scroll al hilo y
   deja el compositor donde tiene que estar. */
/* ===== EL ALTO DE LA CONVERSACIÓN, EN PÍXELES (RONDA 120b, 2.0.115) =====
   Historia corta, porque el sitio ha cambiado tres veces:
   · Hasta la 2.0.112 la página medía `100dvh` (o sea, se metía POR DEBAJO del
     teclado del iPhone, que no encoge el layout) y el teclado se compensaba en
     dos sitios indirectos: subiendo el compositor y engordando el hueco del
     hilo. El recorrido de scroll caía donde el dedo no llega.
   · La 2.0.113 (ronda 120) lo restó del ALTO, pero DEDUCIENDO cuánto tapa el
     teclado: un «gap» medido contra el borde de abajo de la página, y solo si
     había FOCO en el cuadro de escribir. En el arnés salía perfecto; en el
     iPhone REAL del usuario no cambió nada. Las dos condiciones son lo frágil:
     si iOS DESPLAZA el viewport en vez de encogerlo el gap sale 0, y si el
     `activeElement` no es el textarea en el instante de medir (iOS lo mueve
     durante la animación) tampoco se aplica nada.
   · Desde la 2.0.115 no se deduce nada: la página se coloca EN PÍXELES sobre el
     viewport visible, sin condición de foco y sin detectar gap —
         top    = --vv-top  = round(visualViewport.offsetTop)
         height = --vv-alto = round(visualViewport.height)
     — que cubre los dos comportamientos de iOS (encoger y desplazar) con la
     misma cuenta. Va con `position: fixed` porque el ancla tiene que ser el
     VIEWPORT, no el documento. Las variables las escribe `pages/chat.js` en cada
     paso de la tanda de medidas; la aritmética, en `core/chatViewport.js`.
   Si no hay `visualViewport` (navegador viejo) las variables no se escriben, el
   `var()` cae a `100dvh`/`0` y queda el comportamiento de siempre — que es el
   correcto en Android, donde el teclado SÍ encoge el layout.
   `--teclado` y `--cab-desfase` DESAPARECEN: no queda nadie que las lea. */
/* OJO, lo de siempre: `.list-page` trae `flex: 1`, y eso ganaría al alto de
   aquí; el tope duro `max-height` + `min-height: 0` es lo que devuelve el
   scroll al hilo y deja el compositor donde tiene que estar. */
/* El ALTO se aplica SIEMPRE (también en pantalla ancha: en un iPad con dos
   paneles el teclado también tapa, y ahí `--vv-alto` hace lo mismo que hacía el
   `calc(100dvh - --teclado)` de la ronda 120). Lo que NO se aplica en pantalla
   ancha es el `position: fixed` de abajo, que sacaría la conversación del layout
   de dos paneles. */
.chat-page {
    display: flex; flex-direction: column;
    position: relative;
    height: var(--vv-alto, 100dvh);
    max-height: var(--vv-alto, 100dvh);
    min-height: 0;
    overflow: hidden;
}
/* LA RAÍZ FIJA (`chat-vv`, ronda 120b). La marca la pone `pages/chat.js` en el
   `<html>` mientras hay una conversación abierta Y la pantalla NO es ancha (en
   tablet/escritorio manda el layout de dos paneles de la ronda 56, que no puede
   llevar la conversación a `position: fixed; left: 0`).
   Es el patrón estándar de los chats en iOS: el documento no tiene nada que
   arrastrar —ni alto de sobra ni scroll—, así que el único gesto posible cae
   sobre la lista. Sin esto, con el teclado abierto el documento seguía midiendo
   la pantalla entera y iOS ofrecía pasear el viewport visual, que es la
   sensación de «esto no se mueve» que reportó el usuario.
   ⚠ DESVIACIÓN DE LA SPEC, a propósito: `position: fixed` se pone en el BODY y
   al `<html>` se le da `overflow: hidden; height: 100%`. Fijar el elemento raíz
   es terreno raro (Safari lo trata de forma distinta según versión) y el efecto
   buscado —que no haya nada que desplazar— se consigue igual con el patrón que
   lleva años rodado. */
html.chat-vv {
    height: 100%;
    overflow: hidden;
    overscroll-behavior: none;
}
html.chat-vv body {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    overscroll-behavior: none;
}
/* Y la conversación colgada del VIEWPORT VISIBLE. `left/right: 0` es seguro
   porque esta regla solo vive con `chat-vv`, que no se pone en pantalla ancha.
   El `top` es lo que cubre el iOS que DESPLAZA el viewport en vez de encogerlo
   (`offsetTop > 0`), que es el caso ciego de la ronda 120: allí el «gap» salía 0
   y no se aplicaba nada. */
html.chat-vv .chat-page {
    position: fixed;
    top: var(--vv-top, 0px);
    left: 0; right: 0;
}
/* La conversación es a pantalla completa: el `padding-bottom: env(safe-area…)`
   de `#app` —que vale para las páginas con scroll normal— dejaba el fondo de
   `.chat-page` 34 px por encima del borde real de la pantalla, y ahí nacían los
   dos huecos (el residual sin teclado y una parte del de encima del teclado,
   porque la safe-area se contaba también en `--teclado` y en el compositor).
   La marca la pone y la quita `pages/chat.js` al entrar/salir. */
body.chat-pantalla #app { padding-bottom: 0; }
.chat-hilo {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* Explícito desde la 2.0.61: al deslizar una burbuja para responder, esos
       80 px de desplazamiento entran en el área desbordada y con el `auto`
       implícito habrían sacado una barra horizontal en mitad del gesto. */
    overflow-x: hidden;
    /* RONDA 120: al llegar al principio o al final de la conversación, el tirón
       que sobra NO se le pasa a la página. En iOS ese encadenamiento es parte de
       la sensación de «esto no se mueve»: el dedo llega al tope de la lista y lo
       que se mueve es la pantalla entera. `html, body` ya lo tenían puesto. */
    overscroll-behavior: contain;
    padding: var(--esp-fila) var(--esp-borde);
    /* ⚠ RONDA 120c: el hueco del compositor YA NO ES `padding-bottom` (era la
       causa del bug del iPhone; la explicación está en el `::after` de abajo).
       Aquí queda 0 y el sitio lo ocupa un espaciador que es CONTENIDO. */
    padding-bottom: 0;
    display: flex; flex-direction: column;
    gap: 2px;
    scroll-padding-bottom: calc(var(--comp-alto, 96px) + var(--esp-fila));
}
/* MENSAJES PEGADOS ABAJO (RONDA 120). Cuando la conversación no llena la
   pantalla —un grupo recién creado, tres mensajes— la lista se quedaba anclada
   ARRIBA y el último mensaje caía justo donde el compositor sube al abrirse el
   teclado. Este espaciador se come el sitio que sobra y empuja los mensajes
   contra el cuadro de escribir, con teclado y sin él.
   · Es un `::before`, NO un nodo: la conversación se vacía con `innerHTML = ''`
     y los mensajes antiguos se insertan con `insertBefore(n, hilo.firstChild)`,
     así que un DIV de verdad se perdería en cada recarga y descolocaría la
     paginación hacia atrás. Un pseudo-elemento es un ítem flex más y no existe
     para el DOM.
   · `flex: 1 1 0` y no `justify-content: flex-end`: con `flex-end` el contenido
     que desborda se corta por ARRIBA y no hay forma de llegar a él (el bug
     clásico de las columnas flex con scroll). Así, en cuanto el contenido pasa
     de la altura visible el espaciador se queda en 0 y el scroll es el normal.
   · Solo cuando hay MENSAJES de verdad (`[data-mm-env]`, la marca que
     `burbuja()` pone en el envoltorio de cada uno): mientras se abre lo que hay
     es el spinner «Abriendo…», y en una conversación vacía o con un error, un
     cartel — bajarlos al fondo no tendría sentido. Con eso queda fuera además la
     pantalla de supervisión, que monta sus burbujas por su cuenta y no tiene
     compositor del que apartarse.
   · Si el navegador no conoce `:has()` (Safari < 15.4, Chrome < 105) la regla
     entera se cae y la conversación se comporta como hasta la 2.0.112 —anclada
     arriba—, que es una degradación aceptable: el arreglo gordo de la ronda es
     el alto de `.chat-page`, y ese no depende de esto. */
.chat-hilo:has(> [data-mm-env])::before { content: ''; display: block; flex: 1 1 0; min-height: 0; }
/* ===== EL SITIO DEL COMPOSITOR ES CONTENIDO, NO RELLENO (RONDA 120c) =====
   Aquí estaba el bug del iPhone que la ronda 120b no llegó a cerrar, y la
   telemetría del teléfono REAL (8-sep, iOS 18.7, 430x873) lo deja en números:

     página 460 · hilo 390 · compositor 107 (arriba en 353) · relleno 115
     contenido de la conversación 352 px  →  hueco útil 390 − 8 − 115 = 267
     último mensaje abajo en 430 (77 px POR DEBAJO del borde del compositor)
     y `scrollHeight` 390 = `clientHeight`  →  NO HAY NADA QUE ARRASTRAR

   O sea: el contenido no cabía en el hueco útil, se metía en el `padding-bottom`
   (que es justo donde flota la tarjeta de escribir) y WebKit NO cuenta el
   relleno de abajo de un contenedor FLEX con scroll en su desbordamiento, así
   que tampoco daba recorrido para bajar hasta él. Con el teclado cerrado —o con
   la barra de predicción sola, medido a 805— sobraba sitio, el espaciador de
   arriba pegaba los mensajes al fondo y la holgura era exactamente `--esp-fila`:
   por eso solo fallaba con el teclado abierto. Y en Chrome el mismo caso da
   `scrollHeight` 475 y se desplaza sin más: por eso el arnés headless decía que
   estaba bien y el teléfono no.

   La regla, entonces: el sitio de la tarjeta se reserva con un espaciador que es
   CONTENIDO del hilo. Un ítem flex cuenta en el desbordamiento en todos los
   motores, así que el recorrido de scroll aparece siempre y el último mensaje
   nunca se dibuja detrás del compositor.
   · `flex: 0 0 auto`: no encoge aunque falte sitio (si encogiera, volveríamos al
     mismo sitio de otra manera).
   · `--comp-extra` es la corrección de la que tira `pages/chat.js` si tras la
     tanda de medidas el último mensaje sigue tapado — el cinturón, que en
     condiciones normales vale 0.
   · La supervisión queda fuera: no tiene compositor (ni ::after ni flecha). */
.chat-hilo:not(.chat-hilo-supervision)::after {
    content: ''; display: block;
    flex: 0 0 auto;
    /* `--comp-lift` (RONDA 125): si la tarjeta se sube para no quedarse debajo
       del teclado, el hueco tiene que subir con ella o el último mensaje se
       quedaría otra vez detrás. Vale 0 en cuanto la cuenta del alto es buena. */
    height: calc(var(--comp-alto, 96px) + var(--esp-fila) + var(--comp-extra, 0px) + var(--comp-lift, 0px));
}
/* La supervisión no tiene compositor: ni hueco ni flecha. */
.chat-hilo-supervision { max-height: none; padding-bottom: var(--esp-fila); }
/* La conversación ocupa la pantalla entera, así que el pie común (logo +
   versión) que `render()` cuelga detrás de cada página aquí sobra: dejaba 59 px
   de scroll DE DOCUMENTO que arrastraban hacia arriba al compositor anclado. */
.chat-page + .app-footer { display: none; }
.chat-envoltorio { display: flex; flex-direction: column; }
.chat-cargando { text-align: center; padding: var(--esp-fila) 0; }

.chat-dia {
    align-self: center;
    margin: var(--esp-fila) 0 4px;
    padding: 3px 10px;
    border-radius: 12px;
    background: var(--c-bg-soft);
    color: var(--c-text-soft);
    font-size: 0.78rem;
    text-transform: capitalize;
}
/* Separador «Mensajes nuevos» (1.165.0): marca dónde se quedó la lectura al
   abrir la conversación. Es EFÍMERO —vive solo esa apertura— y se pinta como
   una línea con el texto centrado, en el rojo corporativo suave para que se
   distinga de un separador de día sin dar el cante. */
.chat-nuevos {
    align-self: stretch;
    display: flex;
    align-items: center;
    gap: 8px;
    margin: var(--esp-fila) 0 4px;
    color: var(--c-primary);
    font-size: 0.78rem;
    font-weight: 600;
}
.chat-nuevos::before,
.chat-nuevos::after {
    content: '';
    flex: 1 1 auto;
    height: 1px;
    background: var(--c-primary);
    opacity: 0.35;
}
.chat-sistema {
    align-self: center;
    margin: 4px 0;
    padding: 4px 10px;
    border-radius: 10px;
    background: var(--c-bg-soft);
    color: var(--c-text-soft);
    font-size: 0.8rem;
    text-align: center;
    max-width: 90%;
}

.chat-burbuja {
    max-width: 82%;
    padding: 7px 10px 4px;
    border-radius: 12px;
    box-shadow: var(--shadow-1);
    word-break: break-word;
    margin-bottom: 2px;
    /* ↩️ RESPONDER DESLIZANDO (2.0.61). `position` para colgar el ↩️ que asoma
       por la izquierda, y `touch-action: pan-y` para quedarnos el movimiento
       HORIZONTAL (el vertical sigue siendo del hilo, que es quien scrollea). */
    position: relative;
    touch-action: pan-y;
    transition: transform 0.15s ease-out;
}
/* Si el navegador lo entiende, se le devuelve el pellizco para hacer zoom de la
   pantalla entera: lo único que necesitamos quitarle es el barrido horizontal. */
@supports (touch-action: pan-y pinch-zoom) {
    .chat-burbuja { touch-action: pan-y pinch-zoom; }
}
/* Mientras el dedo arrastra, la burbuja va PEGADA a él: la transición solo vale
   para la vuelta al soltar. */
.chat-burbuja-deslizando { transition: none; }
.chat-swipe-icono {
    position: absolute;
    left: -30px; top: 50%;
    transform: translateY(-50%);
    font-size: 1.1rem;
    line-height: 1;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.1s linear, filter 0.1s linear;
}
/* Armado = al soltar, responde. Se nota con un punto de sombra, no con color:
   el emoji lo pinta el sistema y su color no es nuestro. */
.chat-swipe-armado { filter: drop-shadow(0 0 3px var(--c-primary)); }
.chat-mia  { align-self: flex-end;   background: var(--c-primary-soft); border-bottom-right-radius: 4px; }
.chat-suya { align-self: flex-start; background: var(--c-card); border: 1px solid var(--c-border); border-bottom-left-radius: 4px; }
.chat-resaltada { outline: 2px solid var(--c-primary); }
.chat-autor { font-size: 0.78rem; font-weight: 700; margin-bottom: 2px; }
.chat-texto-cuerpo { white-space: pre-wrap; line-height: 1.35; }
.chat-eliminado { font-style: italic; color: var(--c-text-soft); }
/* FOTO DE LA BURBUJA, estilo WhatsApp (1.183.0): se ve ENTERA, con la forma
   que tiene —el servidor genera el thumb proporcional, sin recortar—, pero con
   un tope de alto para que una captura kilométrica no se coma la pantalla.
   `object-fit: cover` solo entra en juego al llegar a ese tope (recorte
   VISUAL); el toque sigue abriendo el zoom con la foto completa.
   ⚠ `max-height` tiene que valer lo mismo que ALTO_MAX_FOTO_CHAT en chat.js:
   con ese número se reserva el hueco antes de bajar la foto, y si los dos no
   coinciden vuelve el salto de layout que se arregló en la 1.182.0. */
.chat-foto {
    display: block;
    max-width: 220px; width: 100%;
    max-height: 340px;
    object-fit: cover;
    border-radius: 8px;
    cursor: pointer;
    margin-bottom: 2px;
}
/* HUECO RESERVADO de la foto (1.182.0). El <img> nace SIN src —el blob llega
   por fetch autenticado— y hasta entonces mediría 0 px, que es lo que dejaba
   una conversación con fotos sin nada que scrollear. El ancho y el
   `aspect-ratio` los pone el JS (reservarHuecoFoto); aquí solo va el gris de
   "esto es una foto que está llegando", que se quita al cargar. */
.chat-foto-reservada {
    background: var(--c-bg-soft, #eef2f6);
    min-height: 24px;
}
/* Burbuja de ARCHIVO (1.138.0): icono grande según el tipo, nombre original a
   una línea con puntos suspensivos (los nombres de verdad son larguísimos) y
   debajo el tamaño. Toda la tarjeta es el botón de descarga. */
.chat-archivo {
    display: flex; align-items: center; gap: var(--esp-chip);
    width: 100%; min-width: 12rem; max-width: 100%;
    text-align: left;
    border: 1px solid var(--c-border);
    border-radius: 8px;
    background: rgba(127, 127, 127, 0.08);
    color: inherit;
    padding: 8px 10px;
    margin-bottom: 2px;
    cursor: pointer;
}
.chat-archivo-icono { font-size: 1.5rem; flex: 0 0 auto; }
.chat-archivo-datos { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.chat-archivo-nombre {
    font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-archivo-peso { font-size: 0.75rem; color: var(--c-text-soft); }
.chat-archivo-flecha { flex: 0 0 auto; font-size: 1.1rem; color: var(--c-primary); }
/* Descargando: la tarjeta se apaga y no admite un segundo toque. */
.chat-archivo-ocupado { opacity: 0.55; pointer-events: none; }

/* TARJETA de un documento del ERP (1.141.0, mensajes 'E'). Mismo lenguaje que
   la del archivo —icono, texto a la izquierda, flecha— porque hacen lo mismo:
   tocar y abrir. Tres estados:
     · cargando  → el texto de respaldo + "Cargando…" (nunca sale vacía);
     · viva      → título, subtítulo y datos, con la flecha ›;
     · muda      → sin permiso, borrado o sin red: se apaga y se dice por qué. */
.chat-ref {
    display: flex; align-items: center; gap: var(--esp-chip);
    width: 100%; min-width: 12rem; max-width: 100%;
    text-align: left;
    border: 1px solid var(--c-border);
    border-left: 3px solid var(--c-primary);
    border-radius: 8px;
    background: rgba(127, 127, 127, 0.08);
    color: inherit;
    padding: 8px 10px;
    margin-bottom: 2px;
    cursor: pointer;
}
.chat-ref-icono { font-size: 1.5rem; flex: 0 0 auto; }
.chat-ref-cuerpo { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.chat-ref-titulo {
    font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-ref-sub {
    font-size: 0.8rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-ref-datos { font-size: 0.75rem; color: var(--c-text-soft); }
.chat-ref-flecha { flex: 0 0 auto; font-size: 1.2rem; color: var(--c-primary); }
.chat-ref-cargando { opacity: 0.7; }
/* Sin datos: no es un botón, es un texto. Se ve, pero no invita a tocarlo. */
.chat-ref-muda {
    cursor: default;
    border-left-color: var(--c-border);
    opacity: 0.85;
}

/* Bot de avisos "🤖 TotalGes" (1.141.0): avatar de icono fijo (no hay foto en
   base de datos) y aviso de solo lectura en el sitio del compositor. */
.chat-avatar-bot { background: var(--c-info); font-size: 1.2rem; }
/* 📝 «Mis notas» (2.0.56): tampoco hay foto que sacar (no hay otra persona), así
   que icono fijo — y de un color distinto al del bot, que no son lo mismo. */
.chat-avatar-notas { background: var(--c-text-soft); font-size: 1.2rem; }
.chat-solo-lectura {
    text-align: center;
    font-size: 0.8rem;
    color: var(--c-text-soft);
    padding: 10px var(--esp-borde);
}
/* Avisos del BOT «🤖 TotalGes» (1.190.2): son mensajes de sistema (`mm_tipo='S'`)
   y por defecto salen con el gris atenuado y el 0.8rem de `.chat-sistema`, que en
   la conversación de avisos se leían flojos y pequeños. SOLO en la conversación
   del bot (`.chat-hilo-bot`, que la marca `pages/chat.js`) se les sube el
   contraste al texto normal y el tamaño un punto —el resto de mensajes de sistema
   de los demás chats («X salió del grupo», etc.) siguen igual. */
.chat-hilo-bot .chat-sistema {
    color: var(--c-text);
    font-size: 1.02rem;
}

.chat-pie {
    display: flex; align-items: center; justify-content: flex-end;
    gap: 4px; margin-top: 2px;
}
.chat-hora { font-size: 0.7rem; color: var(--c-text-soft); }
.chat-check { font-size: 0.75rem; color: var(--c-text-soft); letter-spacing: -1px; }
/* Azul del "leído por todos": el mismo --c-info del resto de la app. */
.chat-check-leido { color: var(--c-info); font-weight: 700; }

/* Cita (responder a un mensaje) */
.chat-cita {
    display: block; width: 100%; text-align: left;
    border: none; border-left: 3px solid var(--c-primary);
    background: rgba(127, 127, 127, 0.10);
    border-radius: 6px;
    padding: 4px 8px; margin-bottom: 4px;
    cursor: pointer;
}
/* ↪ REENVIADO (2.0.58): cabecera fina encima del contenido, en el mismo sitio
   que la cita y con el mismo peso visual —dice de dónde viene el mensaje, no
   es el mensaje—. Sin borde ni fondo a propósito: la cita se puede TOCAR (lleva
   al mensaje citado) y esto no, así que no debe parecer un botón. */
.chat-reenviado {
    font-size: 0.75rem; font-style: italic;
    color: var(--c-text-soft);
    margin-bottom: 2px;
}
.chat-cita-autor { font-size: 0.75rem; font-weight: 700; color: var(--c-primary); }
.chat-cita-texto {
    font-size: 0.8rem; color: var(--c-text-soft);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Barra de la cita: desde 1.134.0 vive DENTRO de la tarjeta del compositor
   (`.comp-extra`), así que ya no lleva márgenes de pantalla propios. */
.chat-cita-barra {
    display: flex; align-items: center; gap: var(--esp-chip);
}
.chat-cita-barra .chat-cita { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }

/* Compositor: la tarjeta la pinta `core/composer.js` (clases .comp-*); aquí se
   la ancla FLOTANDO al borde de abajo de la conversación (1.136.0).
   · `bottom: 0` a secas desde la RONDA 120: el alto de `.chat-page` ES el del
     viewport visible (`--vv-alto`, ronda 120b), así que su borde de abajo ya es
     el borde de arriba del teclado y la tarjeta se pega ahí sola. Antes se subía
     aquí con `bottom: var(--teclado)` dejando la lista por debajo del teclado,
     que es lo que hacía imposible desplazarla.
   · `--comp-safe` (1.137.0) es la safe-area del iPhone: se aplica SOLO con el
     teclado cerrado. Con el teclado abierto la franja del indicador de inicio
     queda debajo del teclado; sumarla dejaba la tarjeta flotando sobre él. O
     una o la otra, nunca las dos.
   · El degradado funde los mensajes que pasan por detrás; la tarjeta en sí es
     OPACA, que para eso se lee lo que se escribe. */
/*  · `--comp-lift` (RONDA 125) es EL ANCLAJE MEDIDO: los píxeles que la tarjeta
      tiene que subir cuando el borde de abajo de `.chat-page` NO coincide con el
      borde de abajo de lo que se ve. Vale 0 mientras la cuenta del alto sea
      buena; lo publica `pages/chat.js` comparando la caja REAL del compositor
      con `visualViewport.offsetTop + height`, y sube y baja solo. Nació de la
      captura del 9-sep: `.chat-page` medía la pantalla entera con el teclado
      puesto y la tarjeta (y su botón de adjuntar) quedaba 338 px por debajo. */
.chat-compositor {
    position: absolute;
    left: 0; right: 0;
    bottom: var(--comp-lift, 0px);
    z-index: 6;
    padding-top: 10px;
    padding-bottom: var(--comp-safe, env(safe-area-inset-bottom, 0px));
    background: linear-gradient(to bottom, var(--c-bg-fade) 0%, var(--c-bg) 26px);
    /* SIN transición en `bottom`: el compositor tiene que seguir al teclado al
       instante (una animación propia se queda por detrás de la del sistema y
       deja ver el hueco). */
}
/* Pegada al borde: el relleno vertical de `.comp` (pensado para el asistente,
   que vive dentro de una columna con scroll) dejaba aquí otro hueco muerto. */
.chat-compositor .comp { padding-top: 0; padding-bottom: 2px; }
.chat-compositor .comp-tarjeta { box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16); }
.chat-btn-ico { flex: 0 0 auto; padding: 8px 10px; font-size: 1.1rem; }

/* Flecha "ir al final": círculo flotante centrado justo encima de la tarjeta,
   como en la app de Claude. Aparece al alejarse del final y lleva un contador
   cuando han entrado mensajes mientras se leía hacia arriba. */
.chat-ir-final {
    position: absolute;
    left: 50%;
    bottom: calc(var(--comp-alto, 96px) + 4px + var(--comp-lift, 0px));
    width: 40px; height: 40px;
    border-radius: 50%;
    border: 1px solid var(--c-border-strong);
    background: var(--c-card);
    color: var(--c-text);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.22);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1.15rem; line-height: 1;
    cursor: pointer;
    padding: 0;
    z-index: 7;
    opacity: 0; pointer-events: none;
    transform: translate(-50%, 8px);
    transition: opacity .18s, transform .18s;
}
.chat-ir-final-visible { opacity: 1; pointer-events: auto; transform: translate(-50%, 0); }
.chat-ir-final-flecha { line-height: 1; }
.chat-ir-final-punto {
    position: absolute; top: -5px; right: -5px;
    min-width: 17px; height: 17px; padding: 0 4px;
    border-radius: 9px;
    background: var(--c-primary); color: #fff;
    font-size: 0.65rem; font-weight: 700; line-height: 17px;
    text-align: center;
}

/* Información de la conversación */
.chat-info-cab { display: flex; align-items: center; gap: var(--esp-chip); margin: var(--esp-fila) 0; }
.chat-info-nombre { font-size: 1.15rem; font-weight: 700; }
.chat-miembro {
    display: flex; align-items: center; gap: var(--esp-chip);
    padding: var(--esp-fila) 0;
    border-bottom: 1px solid var(--c-border);
}
.chat-info-datos { flex: 1 1 auto; min-width: 0; }
/* ✏️ Lápiz de "editar el grupo" (1.180.0): pegado a la derecha del nombre y con
   área táctil de 40px, como el resto de los botones de la app. */
.chat-info-editar {
    flex: 0 0 auto;
    min-width: 40px; min-height: 40px;
    border: 1px solid var(--c-border-strong);
    background: var(--c-card); color: var(--c-text);
    border-radius: var(--radius-sm, 8px);
    font-size: 1rem; cursor: pointer;
}
/* DESCRIPCIÓN del grupo: respeta los saltos de línea que escribió la persona
   (el texto se pone con textContent, nunca con innerHTML). */
.chat-info-desc {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    margin: 0 0 var(--esp-fila);
    color: var(--c-text-muted, var(--c-text));
}
.chat-miembro-nom { flex: 1 1 auto; min-width: 0; }
.chat-miembro-fuera { opacity: 0.55; }

/* 📎 Galería de fotos y documentos de la conversación (1.180.0) */
.chat-galeria-chips {
    display: flex; flex-wrap: wrap; gap: var(--esp-chip);
    margin-bottom: var(--esp-fila);
}
.chat-galeria-chips .chip { min-height: 40px; }
.chat-galeria-rejilla {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--esp-chip);
}
.chat-galeria-celda {
    padding: 0; border: 0; background: none; cursor: pointer;
    border-radius: var(--radius-sm, 8px); overflow: hidden;
    aspect-ratio: 1 / 1;
}
.chat-galeria-foto { width: 100%; height: 100%; object-fit: cover; display: block; }
.chat-galeria-lista { display: flex; flex-direction: column; gap: var(--esp-chip); }
.chat-galeria-voz {
    border: 1px solid var(--c-border); border-radius: var(--radius-sm, 8px);
    padding: var(--esp-chip);
}
.chat-galeria-pie { font-size: 0.8rem; margin-top: 4px; }

/* ⭐ DESTACADOS (2.0.56) — la lista, en la información de la conversación y en
   la pantalla global «Mis destacados». Misma pinta en los dos sitios: lo pinta
   el mismo componente. */
.chat-destacados { display: flex; flex-direction: column; gap: var(--esp-chip); }
/* Cabecera de grupo (solo en la pantalla global): de qué conversación son las
   filas de debajo. */
.chat-dest-cab {
    display: flex; align-items: center; gap: var(--esp-chip);
    margin-top: var(--esp-fila);
    font-weight: 600;
}
.chat-dest-cab-txt { min-width: 0; overflow-wrap: anywhere; }
.chat-dest-fila {
    display: flex; align-items: stretch; gap: var(--esp-chip);
    border: 1px solid var(--c-border);
    border-radius: var(--radius-sm, 8px);
    overflow: hidden;
}
.chat-dest-ir {
    flex: 1 1 auto; min-width: 0;
    text-align: left; background: none; border: 0; cursor: pointer;
    padding: var(--esp-chip);
    color: inherit; font: inherit;
}
/* El extracto es de UNA línea: si un mensaje trae 4.000 caracteres, aquí se ve
   el principio y ya. `anywhere` para que una URL larga no ensanche la fila. */
.chat-dest-txt {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    overflow-wrap: anywhere;
}
.chat-dest-pie { font-size: 0.8rem; margin-top: 4px; }
/* Botón de quitar la estrella: visible SIEMPRE (nada de deslizar, que en una
   lista con scroll se pelea con el gesto de la página). */
.chat-dest-quitar {
    flex: 0 0 auto;
    background: none; border: 0; border-left: 1px solid var(--c-border);
    cursor: pointer; padding: 0 12px; font-size: 1.1rem; line-height: 1;
}
.chat-dest-quitar:disabled { opacity: 0.5; }
.chat-estado-fila { display: flex; align-items: center; gap: var(--esp-chip); padding: 4px 0; }
/* Fila de "Información del mensaje": ✓✓ + nombre y, debajo, las horas exactas
   de entrega y lectura (1.134.0). */
.chat-estado-persona { align-items: flex-start; }
.chat-estado-cuerpo { flex: 1 1 auto; min-width: 0; }
.chat-estado-horas { font-size: 0.8rem; }

/* Banner de supervisión: tiene que verse SIEMPRE, no es decorativo. */
.chat-supervision-banner {
    background: rgba(240, 160, 32, 0.18);
    border: 1px solid rgba(240, 160, 32, 0.55);
    color: #8a5a00;
    border-radius: var(--radius-sm, 8px);
    padding: 10px 12px;
    margin: var(--esp-fila) var(--esp-borde);
    font-weight: 600;
    font-size: 0.9rem;
}
:root[data-tema="oscuro"] .chat-supervision-banner { color: #ffd591; }

/* =====================================================================
   COMPOSITOR DE ESCRITURA COMPARTIDO (1.134.0) — `js/core/composer.js`
   ---------------------------------------------------------------------
   Una tarjeta con el textarea ARRIBA a todo el ancho y, debajo, los
   accesorios a la izquierda y un botón redondo pequeño ↑ a la derecha
   (patrón del chat de Claude). Sustituye a los tres cuadros de escribir
   que había: chat interno, asistente y "Preguntar al tablón".

   El botón NO usa `.btn`/`.btn-primary` a propósito: esas clases traen
   `width: 100%` global y volvería el problema de antes (el botón se comía
   la fila y del área de escritura no quedaba nada).
   ===================================================================== */
.comp { padding: var(--esp-fila) var(--esp-borde); }
.comp-tarjeta {
    display: flex; flex-direction: column;
    gap: 2px;
    padding: 4px 8px 6px 12px;
    border: 1px solid var(--c-border-strong);
    border-radius: 20px;
    background: var(--c-card);
}
.comp-tarjeta:focus-within { border-color: var(--c-primary); }
/* La zona de arriba (cita del chat) desaparece sin contenido: nada de huecos. */
.comp-extra:empty { display: none; }
.comp-extra { padding: 6px 0 2px; }
.comp-texto {
    width: 100%;
    box-sizing: border-box;
    border: none;
    background: transparent;
    color: var(--c-text);
    font-family: var(--font-base);
    font-size: 1rem;
    line-height: 1.4;
    padding: 8px 2px 4px;
    resize: none;
    overflow-y: hidden;   /* lo pone en 'auto' el propio componente al llegar al tope */
    outline: none;
}
.comp-texto::placeholder { color: var(--c-text-soft); }
.comp-fila {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--esp-chip);
}
.comp-accesorios { display: flex; align-items: center; gap: 4px; min-width: 0; }
/* Accesorios (📷 📋 🎤): redondos y discretos, nunca compiten con el ↑. */
.comp-accesorio {
    flex: 0 0 auto;
    width: 36px; height: 36px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--c-border);
    border-radius: 50%;
    background: transparent;
    color: var(--c-text);
    font-size: 1.05rem; line-height: 1;
    cursor: pointer;
    padding: 0;
}
.comp-accesorio.comp-accesorio-activo { background: var(--c-primary-soft); border-color: var(--c-primary); }
.comp-enviar {
    flex: 0 0 auto;
    width: 40px; height: 40px;
    display: inline-flex; align-items: center; justify-content: center;
    border: none; border-radius: 50%;
    background: var(--c-primary); color: #fff;
    font-size: 1.25rem; font-weight: 700; line-height: 1;
    cursor: pointer;
    padding: 0;
}
.comp-enviar:disabled { opacity: 0.4; cursor: default; }
/* Bloqueado SIN `disabled` nativo (2.0.111, patrón r82): con el dedo encima, un
   `disabled` se come el evento en vuelo y pierde el toque. */
.comp-enviar[aria-disabled="true"] { opacity: 0.4; cursor: default; }

/* ===== EL BOTÓN SE LLENA MIENTRAS EL MENSAJE SALE (2.0.111) ==============
   Medido en producción: el POST de un mensaje de texto daba p50 634 ms y p95
   4,8 s, y en ese rato la pantalla no decía nada — por eso se pulsaba dos veces
   y salían mensajes duplicados. El relleno crece de izquierda a derecha DENTRO
   del propio botón; el glifo va en su capa, encima, y no se mueve. */
.comp-enviar { position: relative; overflow: hidden; }
.comp-relleno {
    position: absolute; inset: 0 auto 0 0;
    width: 0; display: block;
    background: rgba(255, 255, 255, 0.42);
    transition: width 600ms linear;
    pointer-events: none;
}
.comp-glifo { position: relative; z-index: 1; line-height: 1; }
/* Enviando: el botón NO se apaga (antes bajaba a 0.6 y parecía deshabilitado);
   lo que cuenta que está trabajando es el relleno. */
.comp-enviar.comp-enviando { opacity: 1; }
.comp-enviar.comp-enviando[aria-disabled="true"] { opacity: 1; }
/* El ✓ se enseña con el campo YA vacío (o sea, con aria-disabled a true): sin
   esta línea el acuse de recibo saldría medio apagado y no se vería. */
.comp-enviar.comp-enviado,
.comp-enviar.comp-enviado[aria-disabled="true"] { background: var(--c-success); opacity: 1; }
.comp-enviar.comp-enviado .comp-relleno { transition: width 160ms linear; }
/* Falló: el ↻ tiene que verse pulsable, así que ni se apaga ni se bloquea. */
.comp-enviar.comp-fallo { background: var(--c-danger); opacity: 1; }
.comp-enviar.comp-fallo[aria-disabled="true"] { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .comp-relleno { transition: none; }
}
/* Accesorio APAGADO: el micrófono cuando el permiso está denegado o no hay
   micro. Sigue siendo pulsable a propósito (reintentar tras dar el permiso),
   pero se ve que ahora mismo no va — y su `title` dice por qué. */
.comp-accesorio.comp-accesorio-off { opacity: 0.45; border-style: dashed; }

/* ===== GRABANDO UNA NOTA DE VOZ (1.142.0) ==========================
   Mientras se graba, la tarjeta del compositor deja de ser un cuadro de
   escribir: se esconden el texto y la fila de botones y el panel de grabación
   ocupa su sitio (va en `.comp-extra`, el mismo hueco de la cita). El
   compositor compartido NO se toca: solo se le pone y se le quita esta clase. */
.comp-grabando .comp-texto,
.comp-grabando .comp-fila { display: none; }
.chat-grab {
    display: flex; align-items: center; gap: var(--esp-chip);
    padding: 2px 0;
}
.chat-grab-txt { font-size: 0.9rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-grab-tiempo { font-variant-numeric: tabular-nums; font-weight: 700; margin-left: auto; }
.chat-grab-tope { font-size: 0.75rem; color: var(--c-text-soft); }
.chat-grab-punto {
    flex: 0 0 auto;
    width: 10px; height: 10px; border-radius: 50%;
    background: #e53935;
    animation: chat-grab-latido 1.1s ease-in-out infinite;
}
@keyframes chat-grab-latido {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: 0.35; transform: scale(0.75); }
}
/* Sin animación para quien la ha desactivado en el sistema (el punto sigue
   estando, simplemente no parpadea). */
@media (prefers-reduced-motion: reduce) {
    .chat-grab-punto { animation: none; }
}
.chat-grab-enviar {
    flex: 0 0 auto;
    width: 40px; height: 40px;
    display: inline-flex; align-items: center; justify-content: center;
    border: none; border-radius: 50%;
    background: var(--c-primary); color: #fff;
    font-size: 1.25rem; font-weight: 700; line-height: 1;
    cursor: pointer; padding: 0;
}

/* ===== BURBUJA DE NOTA DE VOZ (1.142.0) ============================
   ▶/⏸ · barra de progreso tocable · duración · chip de velocidad. Mismo
   lenguaje que la tarjeta del archivo, pero sin flecha de descarga: aquí se
   escucha, no se baja. */
.chat-voz {
    display: flex; align-items: center; gap: var(--esp-chip);
    width: 100%; min-width: 13rem; max-width: 100%;
    border: 1px solid var(--c-border);
    border-radius: 8px;
    background: rgba(127, 127, 127, 0.08);
    padding: 8px 10px;
    margin-bottom: 2px;
}
.chat-voz-icono { flex: 0 0 auto; font-size: 1.15rem; }
.chat-voz-play {
    flex: 0 0 auto;
    width: 34px; height: 34px;
    display: inline-flex; align-items: center; justify-content: center;
    border: none; border-radius: 50%;
    background: var(--c-primary); color: #fff;
    font-size: 0.95rem; line-height: 1;
    cursor: pointer; padding: 0;
}
.chat-voz-play:disabled { opacity: 0.5; cursor: default; }
.chat-voz-centro { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
/* La barra es alta de verdad (10 px + relleno del contenedor) para poder
   tocarla con el dedo sin apuntar. */
.chat-voz-barra {
    height: 10px;
    border-radius: 5px;
    background: rgba(127, 127, 127, 0.28);
    cursor: pointer;
    overflow: hidden;
    touch-action: none;
}
.chat-voz-relleno {
    display: block; height: 100%; width: 0;
    border-radius: 5px;
    background: var(--c-primary);
}
.chat-voz-tiempo { font-size: 0.75rem; color: var(--c-text-soft); font-variant-numeric: tabular-nums; }
.chat-voz-vel {
    flex: 0 0 auto;
    min-width: 40px; height: 28px;
    border: 1px solid var(--c-border);
    border-radius: 14px;
    background: transparent;
    color: var(--c-text);
    font-size: 0.78rem; font-weight: 700; line-height: 1;
    cursor: pointer; padding: 0 6px;
}
.chat-voz-sonando .chat-voz-vel { border-color: var(--c-primary); color: var(--c-primary); }
.chat-voz-aviso { font-size: 0.72rem; margin-top: 2px; }

/* =====================================================================
   CHAT S3 (1.143.0) — MENCIONES @, REACCIONES, EDICIÓN y FIJADOS
   ===================================================================== */

/* ----- @ Menciones ----- */
/* La mención dentro del texto: mismo color que los enlaces de la app y fondo
   suave, para que se lea de un vistazo sin gritar. */
.chat-mencion {
    color: var(--c-primary);
    font-weight: 700;
    background: var(--c-primary-soft);
    border-radius: 4px;
    padding: 0 2px;
}
/* Burbuja en la que me mencionan A MÍ: una barra a la izquierda. El dato lo da
   el servidor (MsjMenciones), no el texto. */
.chat-burbuja-mencion { border-left: 3px solid var(--c-primary); }
.chat-mia.chat-burbuja-mencion { border-left: none; border-right: 3px solid var(--c-primary); }
/* Badge «@» de la lista de conversaciones: hay menciones sin leer. */
.chat-badge-mencion { background: var(--c-warning, #f0a020); }

/* Panel del selector de @: FLOTA sobre la tarjeta de escribir (el compositor
   compartido no se toca, solo se le cuelga esto encima). */
.chat-menciones {
    position: absolute;
    left: var(--esp-borde); right: var(--esp-borde);
    bottom: 100%;
    max-height: 46vh;
    overflow-y: auto;
    z-index: 8;
    border: 1px solid var(--c-border-strong);
    border-radius: 12px;
    background: var(--c-card);
    box-shadow: 0 -4px 18px rgba(0, 0, 0, 0.18);
    padding: 4px;
    margin-bottom: 6px;
}
.chat-menciones-cab {
    font-size: 0.75rem;
    color: var(--c-text-soft);
    padding: 4px 8px 6px;
}
.chat-menciones-item {
    display: flex; align-items: center; gap: 6px;
    width: 100%;
    text-align: left;
    border: none;
    background: transparent;
    color: var(--c-text);
    font-size: 0.95rem;
    padding: 9px 8px;
    border-radius: 8px;
    cursor: pointer;
}
.chat-menciones-item:hover,
.chat-menciones-item:focus { background: var(--c-bg-soft); }
.chat-menciones-arroba { color: var(--c-primary); font-weight: 700; }
.chat-menciones-nombre { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Nombre real del trabajador en el selector de menciones (1.178.0): se recorta
   antes que el alias, que es lo que de verdad hay que leer para elegir. */
.chat-menciones-real {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    margin-left: 6px;   /* aquí el alias y el nombre real son dos spans pegados */
}

/* ----- Reacciones ----- */
/* Agregado bajo la burbuja: "👍 2  ❤️ 1". Vacío no ocupa (lo oculta el JS). */
.chat-reac {
    display: flex; flex-wrap: wrap; gap: 4px;
    margin: 4px 0 2px;
}
.chat-reac-chip {
    display: inline-flex; align-items: center; gap: 3px;
    border: 1px solid var(--c-border);
    border-radius: 12px;
    background: var(--c-bg-soft);
    color: var(--c-text);
    font-size: 0.8rem; line-height: 1;
    padding: 3px 7px;
    cursor: pointer;
}
.chat-reac-chip-mia { border-color: var(--c-primary); background: var(--c-primary-soft); }
.chat-reac-emoji { font-size: 0.95rem; line-height: 1; }
.chat-reac-n { font-weight: 700; font-variant-numeric: tabular-nums; }
/* Fila de los seis emojis dentro de la hoja de acciones del mensaje. */
.chat-reac-fila {
    display: flex; justify-content: space-between; gap: 4px;
    padding: 2px 0 6px;
}
.chat-reac-btn {
    flex: 1 1 0;
    min-width: 0;
    height: 46px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--c-border);
    border-radius: 23px;
    background: transparent;
    font-size: 1.35rem; line-height: 1;
    cursor: pointer;
    padding: 0;
}
.chat-reac-btn-mia { border-color: var(--c-primary); background: var(--c-primary-soft); }

/* ----- Editado ----- */
.chat-editado { font-size: 0.7rem; color: var(--c-text-soft); font-style: italic; }
/* ⭐ Mi estrella en el pie de la burbuja (2.0.56): pequeña, que es una marca,
   no un adorno. Solo la ve quien la puso. */
.chat-destacado { font-size: 0.7rem; line-height: 1; }

/* ----- 📤 Burbuja de una subida en curso (2.0.58) -----
   Es una burbuja PROPIA de verdad (mismo ancho, mismo lado, misma tipografía)
   con tres piezas: lo que se manda, una barra y una línea de estado. Se pinta
   ANTES de que exista el mensaje y se SUSTITUYE por el real al llegar el 200,
   así que todo lo de aquí es transitorio y no toca ninguna clase del mensaje. */
.chat-subiendo { opacity: 0.92; }

/* ===== BURBUJA OPTIMISTA DE TEXTO (2.0.111) ==============================
   Lo escrito sale en la conversación EN EL ACTO, apagado y con «enviando…», y
   lo sustituye la burbuja de verdad al confirmarse. Apagada, no gris plano: se
   ve que es mía y que todavía está de camino. Si falla, el pie lo dice y el
   borde se vuelve rojo — la burbuja NO desaparece, porque el texto sigue en el
   campo esperando al «↻» del botón. */
.chat-fantasma { opacity: 0.62; }
.chat-fantasma-estado { font-style: italic; }
.chat-fantasma.chat-fantasma-error {
    opacity: 1;
    border: 1px solid var(--c-danger);
}
.chat-fantasma.chat-fantasma-error .chat-fantasma-estado { color: var(--c-danger); font-style: normal; }
.chat-subiendo-foto {
    display: block; max-width: 100%; border-radius: 8px; margin-bottom: 4px;
}
.chat-subiendo-ficha {
    display: flex; align-items: center; gap: var(--esp-chip);
    min-width: 12rem; margin-bottom: 4px;
}
.chat-subiendo-icono { font-size: 1.5rem; flex: 0 0 auto; }
.chat-subiendo-datos { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.chat-subiendo-nombre {
    font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-subiendo-peso { font-size: 0.75rem; color: var(--c-text-soft); }
.chat-subiendo-barra {
    height: 4px; border-radius: 999px;
    background: rgba(127, 127, 127, 0.25);
    overflow: hidden;
}
.chat-subiendo-relleno {
    display: block; height: 100%; width: 0;
    background: var(--c-primary);
    transition: width 0.15s linear;
}
/* Sin porcentaje que enseñar (subida no medible, o el servidor procesando):
   la barra se mueve sola para decir "esto sigue vivo". */
.chat-subiendo-indet .chat-subiendo-relleno {
    width: 40% !important;
    animation: chat-subiendo-vaiven 1.1s ease-in-out infinite;
}
@keyframes chat-subiendo-vaiven {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}
@media (prefers-reduced-motion: reduce) {
    .chat-subiendo-indet .chat-subiendo-relleno { animation: none; width: 100% !important; }
}
.chat-subiendo-estado {
    font-size: 0.75rem; color: var(--c-text-soft);
    margin-top: 4px;
}
.chat-subiendo-acciones {
    display: flex; gap: var(--esp-chip); margin-top: 4px;
}
.chat-subiendo-acciones:empty { display: none; }
.chat-subiendo-btn {
    border: 1px solid var(--c-border);
    border-radius: 999px;
    background: transparent; color: inherit;
    font-size: 0.75rem; padding: 3px 10px;
    cursor: pointer;
}
.chat-subiendo-btn-fuerte { border-color: var(--c-primary); color: var(--c-primary); font-weight: 600; }
/* Error: el rojo va en el BORDE y en el texto de estado, no en el fondo — la
   burbuja sigue siendo la del mensaje que se quiere mandar, no una alerta. */
.chat-subiendo-error { border: 1px solid var(--c-danger); }
.chat-subiendo-error .chat-subiendo-estado { color: var(--c-danger); }
.chat-subiendo-error .chat-subiendo-relleno { background: var(--c-danger); }

/* ----- Hoja de elegir conversación de destino (compartir / reenviar) ----- */
.chat-destinos { max-height: 46vh; overflow-y: auto; }
.chat-destino {
    display: flex; align-items: center; gap: var(--esp-chip);
    text-align: left; width: 100%;
}
.chat-destino-icono { font-size: 1.2rem; flex: 0 0 auto; }
.chat-destino-txt { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.chat-destino-nombre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-destino-sub { font-size: 0.75rem; color: var(--c-text-soft); }
.chat-destinos-vacio { padding: var(--esp-fila) 0; text-align: center; }
/* Con VARIOS destinos (2.0.58) la fila es una casilla de verdad. */
.chat-destino-marcable { cursor: pointer; }
.chat-destino-cb { flex: 0 0 auto; width: 20px; height: 20px; margin: 0; }

/* ----- ☑ Selección múltiple de mensajes (2.0.58) ----- */
/* Banda bajo la cabecera: cuántos van y por dónde se sale. Mismo lenguaje que
   las otras bandas del chat (fijados, ponte al día): una línea, sin ruido. */
.chat-selec-barra {
    display: flex; align-items: center; gap: var(--esp-chip);
    flex: 0 0 auto;
    padding: 6px var(--esp-lateral);
    background: rgba(127, 127, 127, 0.10);
    border-bottom: 1px solid var(--c-border);
}
.chat-selec-txt { flex: 1 1 auto; font-weight: 600; }
.chat-selec-cerrar {
    border: none; background: transparent; color: inherit;
    font-size: 1.1rem; cursor: pointer; padding: 2px 6px;
}
/* Barra de acciones: vive donde el compositor, que se aparta mientras dura
   (escribir y seleccionar son dos modos distintos, no dos cosas a la vez). */
.chat-selec-acciones {
    display: flex; gap: var(--esp-chip); justify-content: center; flex-wrap: wrap;
    padding: var(--esp-chip) var(--esp-lateral);
    background: var(--c-card);
    border: 1px solid var(--c-border-strong);
    border-radius: 20px;
    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.16);
}
/* El compositor se aparta mientras se selecciona (no se destruye: conserva lo
   que hubiera escrito y la cita enganchada). */
.comp-oculto { display: none !important; }
/* La casilla, delante del contenido de la burbuja. */
.chat-selec-marca {
    float: left; margin-right: 6px;
    font-size: 1rem; line-height: 1.2;
    color: var(--c-primary);
}
.chat-selec-marca-no { color: var(--c-text-soft); opacity: 0.5; }
.chat-burbuja-selec { outline: 2px solid var(--c-primary); outline-offset: 1px; }
/* Seleccionando, el texto de las burbujas no se selecciona con el dedo: el
   toque es para marcar, y un arrastre accidental no debe pintar de azul. */
.chat-hilo-selec .chat-burbuja { user-select: none; -webkit-user-select: none; cursor: pointer; }
.chat-editar-campo { width: 100%; box-sizing: border-box; resize: vertical; }

/* ----- Fijados ----- */
/* Banda fina bajo la cabecera con el último mensaje fijado. `flex: 0 0 auto`
   para que no le robe alto al hilo, que es quien tiene el scroll. */
.chat-fijado-barra {
    flex: 0 0 auto;
    display: flex; align-items: center; gap: 6px;
    padding: 6px var(--esp-borde);
    background: var(--c-bg-soft);
    border-bottom: 1px solid var(--c-border);
}
.chat-fijado-ir {
    flex: 1 1 auto; min-width: 0;
    display: flex; align-items: center; gap: 6px;
    border: none; background: transparent;
    color: var(--c-text);
    font-size: 0.85rem;
    text-align: left;
    padding: 0;
    cursor: pointer;
}
.chat-fijado-pin { flex: 0 0 auto; }
.chat-fijado-txt {
    min-width: 0; flex: 1 1 auto;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-fijado-contador {
    flex: 0 0 auto;
    border: 1px solid var(--c-border);
    border-radius: 10px;
    background: var(--c-card);
    color: var(--c-text-soft);
    font-size: 0.72rem; font-variant-numeric: tabular-nums;
    padding: 2px 7px;
    cursor: pointer;
}

/* ===== ÍNDICE LATERAL de la toma de datos (1.144.0) =====
   Panel de navegación con las cabeceras y subcabeceras de la visita. En monitor
   (≥1100px) va FIJO a la izquierda y el contenido se aparta a su derecha; en
   pantallas pequeñas es un CAJÓN que abre el botón flotante 🧭. */
.td-indice {
    position: fixed;
    top: 0; left: 0; bottom: 0;
    width: 272px;
    z-index: 80;                       /* bajo los sheets (1000) y los toasts */
    background: var(--c-card);
    border-right: 1px solid var(--c-border-strong);
    box-shadow: var(--shadow-2);
    display: none;                     /* solo se ve con .abierto */
    flex-direction: column;
    padding-top: env(safe-area-inset-top);
}
.td-indice.abierto { display: flex; }

.td-indice-cab {
    display: flex; align-items: center; gap: var(--esp-chip);
    padding: var(--esp-fila) var(--esp-borde);
    border-bottom: 1px solid var(--c-border);
    background: var(--c-bg-soft);
}
.td-indice-tit { flex: 1; min-width: 0; font-weight: 800; font-size: 0.9rem; }
.td-indice-plegar {
    flex: 0 0 auto;
    border: 1px solid var(--c-border); border-radius: var(--radius-sm);
    background: var(--c-card); color: var(--c-text);
    width: 32px; height: 32px; font-size: 0.95rem; line-height: 1; cursor: pointer;
}
.td-indice-plegar:active { transform: scale(0.94); }

.td-indice-lista {
    flex: 1 1 auto; min-height: 0;
    overflow-y: auto; overscroll-behavior: contain;
    padding: var(--esp-fila) var(--esp-chip) 24px;
    display: flex; flex-direction: column; gap: 2px;
}
.td-indice-item {
    border: 0; background: transparent; color: var(--c-text);
    text-align: left; cursor: pointer;
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    border-left: 3px solid transparent;
    font-size: 0.86rem; line-height: 1.3;
    /* Títulos largos: dos líneas con puntos suspensivos y a otra cosa. El texto de la
       entrada ya viene RECORTADO (fuera la aclaración entre paréntesis, 1.151.0);
       esto es la red de seguridad. */
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; overflow-wrap: anywhere;
    /* ⚠ 1.151.0 — la lista es un flex COLUMNA con altura limitada: sin esto los items
       se ENCOGÍAN (flex-shrink: 1) por debajo de su contenido y la segunda línea salía
       cortada por la mitad, pareciendo que un título pisaba al siguiente. */
    flex: 0 0 auto;
    min-height: calc(1.3em + 16px);
}
.td-indice-item:hover { background: var(--c-bg-soft); }
/* Icono PNG propio de la entrada (casco, encimera…, 1.153.0): el MISMO que lleva su
   encabezado en la página. Va dentro de un `-webkit-box` con line-clamp, así que se
   alinea con el texto como una letra más (nada de flex aquí). */
.td-indice-ico { vertical-align: -0.18em; margin-right: 4px; }
.td-indice-sub {
    margin-left: 12px;
    font-size: 0.79rem;
    color: var(--c-text-soft);
}
.td-indice-item.activa {
    background: var(--c-primary-soft);
    border-left-color: var(--c-primary);
    font-weight: 700;
}

/* Velo del cajón (solo en pantallas pequeñas) */
.td-indice-velo {
    position: fixed; inset: 0; z-index: 79;
    background: rgba(0, 0, 0, 0.45);
    display: none;
}
.td-indice-velo.visible { display: block; }

/* Acción "Abrir todo / Cerrar todo" de la cabecera del panel (1.151.0): es la que
   antes hacía el 🔽 de la barra superior, ocupado ahora por el 🧭 del índice. */
.td-indice-acciones {
    flex: 0 0 auto;
    padding: var(--esp-chip) var(--esp-chip) 0;
    /* RONDA 103 §2: la fila admite más de un botón («📂 Oportunidad») — en columna y
       con aire entre ellos, que apilados sin gap parecían un solo control partido. */
    display: flex; flex-direction: column; gap: 6px;
}
.td-indice-todo {
    width: 100%;
    border: 1px solid var(--c-border); border-radius: var(--radius-sm);
    background: var(--c-card); color: var(--c-text);
    font-size: 0.82rem; font-weight: 700;
    padding: 8px 10px; cursor: pointer; text-align: left;
}
.td-indice-todo:hover { background: var(--c-bg-soft); }
.td-indice-todo:active { transform: scale(0.98); }

/* Modo CAJÓN (pantalla pequeña): estrecho y por encima del velo */
.td-indice.td-indice-cajon {
    width: min(82vw, 320px);
    z-index: 80;
}

/* Con el panel FIJO abierto, la app entera se aparta a su derecha (incluido el pie
   de página, que es hermano de la página). El contenido conserva su max-width. */
#app.td-con-indice { padding-left: 272px; }

/* Sheet de "¿cambiar el estado de la oportunidad?" al completar la visita */
.td-est-actual { display: flex; align-items: center; gap: var(--esp-chip); flex-wrap: wrap; margin: 6px 0; }
.td-est-actual .lbl { font-size: 0.82rem; color: var(--c-text-soft); }
.td-est-grid { padding: 12px 0 4px; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }

/* ============================================================
   💬 CHAT S4 (1.148.0): búsqueda global y conversaciones archivadas
   ============================================================ */

/* ---- Buscador de mensajes ---- */
.chat-buscar-caja { padding: var(--esp-borde) var(--esp-borde) 0; }
.chat-buscar-ayuda { padding: 8px var(--esp-borde) 0; }
.chat-buscar-cuenta { padding: 4px 2px 0; }
/* La ficha de resultado es más alta que la de la lista: el extracto puede
   ocupar dos líneas y el nombre de quien escribió va en su propia línea. */
.chat-buscar-item { align-items: flex-start; }
.chat-buscar-item .chat-avatar { margin-top: 2px; }
.chat-buscar-autor {
    font-size: 0.78rem; font-weight: 700; color: var(--c-text-soft);
    margin-top: 2px;
}
.chat-buscar-extracto {
    margin-top: 2px;
    font-size: 0.875rem; line-height: 1.3;
    color: var(--c-text);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
}
/* El término encontrado. `mark` trae fondo amarillo del navegador, que en tema
   oscuro deja el texto ilegible: se pinta con los colores de la app. */
.chat-hit {
    background: var(--c-primary-soft);
    color: var(--c-text);
    border-radius: 3px;
    padding: 0 1px;
    font-weight: 700;
}

/* ---- Barra de "vienes de la búsqueda" (modo posicionado) ---- */
.chat-posicion-barra {
    flex: 0 0 auto;
    display: flex; align-items: center; gap: 6px;
    padding: 6px var(--esp-borde);
    background: var(--c-primary-soft);
    border-bottom: 1px solid var(--c-border);
    font-size: 0.85rem;
}
.chat-posicion-txt {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-posicion-ir {
    flex: 0 0 auto;
    border: 1px solid var(--c-border); border-radius: 12px;
    background: var(--c-card); color: var(--c-text);
    font-size: 0.78rem; font-weight: 700;
    padding: 3px 10px;
    cursor: pointer;
}

/* ---- 🤖 Banner de "Ponte al día" (1.152.0) ----
   Aparece bajo la cabecera cuando hay 15 o más mensajes sin leer. Es un AVISO,
   no una acción automática: nada ocurre hasta que se pulsa su botón. */
.chat-aldia-barra {
    flex: 0 0 auto;
    display: flex; align-items: center; gap: 6px;
    padding: 6px var(--esp-borde);
    background: var(--c-bg-soft);
    border-bottom: 1px solid var(--c-border);
    font-size: 0.85rem;
}
.chat-aldia-icono { flex: 0 0 auto; }
.chat-aldia-txt {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--c-text);
}
.chat-aldia-btn {
    flex: 0 0 auto;
    border: 1px solid var(--c-primary); border-radius: 12px;
    background: var(--c-primary); color: #fff;
    font-size: 0.78rem; font-weight: 700;
    padding: 3px 10px;
    cursor: pointer;
}
.chat-aldia-cerrar {
    flex: 0 0 auto;
    border: none; background: transparent;
    color: var(--c-text-soft);
    font-size: 0.9rem; line-height: 1;
    padding: 2px 4px;
    cursor: pointer;
}

/* ---- Hoja del resumen ---- */
.chat-aldia-intro { margin-bottom: 8px; }
.chat-aldia-cuerpo { min-width: 0; }
/* 1.190.0 — el resumen del bot («🤖 Ponte al día») se veía claro y pequeño. Se
   iguala a un mensaje NORMAL del chat: color de texto del tema (var(--c-text), no
   heredado/atenuado) y tamaño 1rem con el mismo interlineado que .chat-texto-cuerpo. */
.chat-aldia-texto {
    display: flex; flex-direction: column; gap: 2px;
    font-size: 1rem; line-height: 1.35;
    color: var(--c-text);
    overflow-wrap: anywhere;
}
.chat-aldia-linea, .chat-aldia-punto, .chat-aldia-seccion { color: var(--c-text); }
.chat-aldia-punto { display: flex; gap: 7px; align-items: flex-start; }
.chat-aldia-vinieta { flex: 0 0 auto; color: var(--c-primary); font-weight: 800; }
.chat-aldia-seccion { font-weight: 800; margin-top: 8px; }
.chat-aldia-hueco { height: 4px; }

/* ---- Sección "📁 Archivadas" al final de la lista ---- */
.chat-archivadas { margin-top: 10px; }
.chat-arch-cab {
    display: flex; align-items: center; gap: 8px;
    width: 100%; text-align: left;
    padding: 10px var(--esp-borde);
    border: 0; border-top: 1px solid var(--c-border);
    background: transparent;
    color: var(--c-text-soft);
    font-size: 0.9rem; font-weight: 700;
    cursor: pointer;
}
.chat-arch-flecha { flex: 0 0 auto; font-size: 0.8rem; }
.chat-arch-txt { flex: 1 1 auto; min-width: 0; }
/* Dentro del archivo las tarjetas van algo apagadas: se ven, pero no compiten
   con las conversaciones vivas de arriba. */
.chat-archivadas .chat-card { opacity: 0.82; }

/* ============================================================
   ENCUESTAS del chat (1.150.0, script 209) — solo en grupos
   ============================================================ */
/* La burbuja de una encuesta es más ancha que una de texto (lleva barras y
   porcentajes), pero NO se sale: hereda el ancho máximo de la burbuja y por eso
   todo lo de dentro va con min-width: 0. */
.chat-encuesta { min-width: 0; }
.enc { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.enc-pregunta {
    display: flex; align-items: flex-start; gap: 6px;
    font-weight: 700; line-height: 1.25;
    margin-bottom: 2px;
    overflow-wrap: anywhere;
}
.enc-icono { flex: 0 0 auto; }
/* Cada opción es un botón de una línea con su barra debajo del texto. */
.enc-op {
    display: flex; align-items: center; gap: 8px;
    width: 100%; min-width: 0;
    padding: 6px 8px;
    border: 1px solid var(--c-border); border-radius: 10px;
    background: var(--c-bg-soft); color: var(--c-text);
    font-size: 0.88rem; text-align: left;
    cursor: pointer;
}
.enc-op:disabled { cursor: default; opacity: 0.9; }
.enc-op-mia { border-color: var(--c-primary); background: var(--c-primary-soft); }
.enc-op-marca { flex: 0 0 auto; font-size: 1rem; line-height: 1; }
.enc-op-cuerpo { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.enc-op-txt { overflow-wrap: anywhere; }
.enc-op-n {
    flex: 0 0 auto;
    font-size: 0.78rem; font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--c-text-soft);
}
.enc-barra {
    height: 5px; border-radius: 3px;
    background: var(--c-border);
    overflow: hidden;
}
.enc-barra-relleno {
    height: 100%; border-radius: 3px;
    background: var(--c-primary);
    transition: width 0.25s ease;
}
/* "3 votos · ver quién": enlace discreto bajo la opción votada. */
.enc-quien {
    align-self: flex-start;
    border: 0; background: transparent;
    color: var(--c-text-soft);
    font-size: 0.74rem;
    padding: 0 0 2px 30px;
    text-decoration: underline;
    cursor: pointer;
}
.enc-pie { font-size: 0.74rem; color: var(--c-text-soft); margin-top: 2px; }
/* Cerrada: se sigue leyendo el resultado, pero se ve que ya no admite votos. */
.enc-cerrada .enc-op { opacity: 0.85; }

/* ---- Hoja de creación ---- */
.enc-nuevas { display: flex; flex-direction: column; gap: var(--esp-fila); }
.enc-nueva-op { width: 100%; box-sizing: border-box; }
.enc-aviso { margin-top: 4px; }
.enc-multiple {
    display: flex; align-items: center; gap: 8px;
    margin-top: var(--esp-fila);
    font-size: 0.9rem;
}

/* ============================================================
   🌙 No molestar fuera de horario (1.150.0) — ⚙ Ajustes
   ============================================================ */
.ajustes-horario { display: flex; flex-direction: column; gap: var(--esp-fila); }
.ajustes-horario-fila {
    display: flex; align-items: center; gap: var(--esp-chip);
    flex-wrap: wrap;
}
.ajustes-horario-fila label { font-size: 0.9rem; }
.ajustes-horario-hora { width: 7.5em; }
.ajustes-horario-check { display: flex; align-items: center; gap: 8px; font-size: 0.9rem; }

/* 🏢 Horario laboral estipulado por la empresa (1.156.0, script 211).
   SOLO LECTURA: se lee como una ficha informativa, no como un control más del
   bloque — de ahí el fondo suave y el filete, para que nadie intente tocarlo. */
.ajustes-horario-laboral {
    background: var(--c-bg-soft);
    border-left: 3px solid var(--c-info);
    border-radius: var(--radius-sm);
    padding: var(--esp-fila) var(--esp-borde);
    display: flex; flex-direction: column; gap: var(--space-1);
}
.ajustes-horario-laboral-tit { font-size: 0.9rem; font-weight: 600; }

/* 🕘 FRANJAS POR DÍA (1.176.0, script 215).
   Cada franja es una fila que envuelve a 375 px (día / de / a / papelera) y
   respeta los márgenes del sistema (--esp-*), como el resto de Ajustes. */
.ajustes-horario-franjas { display: flex; flex-direction: column; gap: var(--esp-chip); }
.ajustes-horario-franja {
    display: flex; align-items: center; gap: var(--esp-chip);
    flex-wrap: wrap;
}
.ajustes-horario-dia { width: 6.5em; }
.ajustes-horario-sep { font-size: 0.9rem; }
.ajustes-horario-quitar { padding: 4px 10px; line-height: 1.2; }
/* .ajustes-botonera apila en columna; aquí los botones van en fila y saltan
   solos cuando no caben (el atajo del turno partido es un botón largo). */
.ajustes-horario-acciones { flex-direction: row; flex-wrap: wrap; gap: var(--esp-chip); }
/* "Cambios sin guardar": oculto hasta que los haya, para no gritar de entrada. */
.ajustes-horario-sucio { display: none; }
.ajustes-horario-sucio.visible { display: block; }
.ajustes-horario-laboral-detalle {
    display: flex; flex-direction: column; gap: 2px;
    font-size: 0.85rem;
}
.ajustes-horario-laboral-dia { font-variant-numeric: tabular-nums; }
/* Aviso de solapamiento: INFORMATIVO (el ajuste se guarda igual), por eso el
   ámbar de aviso y no el rojo de error. */
.ajustes-horario-aviso {
    background: var(--c-warn-bg);
    color: var(--c-warn-fg);
    border-left: 3px solid var(--c-warn-line);
    border-radius: var(--radius-sm);
    padding: var(--esp-fila) var(--esp-borde);
    font-size: 0.85rem;
}

/* =============================================================
   SELECTOR DE MARCAS MÚLTIPLE con typeahead (1.168.0)
   core/marcasMulti.js — buscador de artículos y catálogo de la visita.

   El "campo" imita a .filter-input (mismo borde, radio, alto mínimo y fondo)
   pero por dentro lleva los chips de las marcas elegidas y el input que fluye
   detrás de ellos. El desplegable vive en <body> con position: fixed y lo
   posiciona el JS dentro de la banda visible (visualViewport) — aquí solo van
   colores, sombra y scroll.
   Espaciados con las variables del ritmo de los bloques de búsqueda
   (--esp-chip / --space-*): nada de números sueltos.
   ============================================================= */
.marcas-multi { position: relative; display: inline-flex; min-width: 0; flex: 0 1 auto; }
.marcas-campo {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1);
    padding: 3px var(--space-2);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    background: var(--c-card);
    min-height: 36px;
    min-width: 0;
    width: 100%;
    cursor: text;
}
.marcas-campo:focus-within { border-color: var(--c-primary); }
.marcas-chips { display: contents; }
.marcas-input {
    flex: 1 1 6ch;
    min-width: 6ch;
    border: 0;
    outline: none;
    background: transparent;
    color: var(--c-text);
    font-size: 0.875rem;
    padding: var(--space-1) 0;
    min-height: 28px;
}
/* Chip de marca elegida: mismo lenguaje visual que .chip.active (relleno de
   marca), en versión compacta para que quepan dos o tres en el campo. */
.marcas-chip {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    max-width: 14ch;
    padding: 2px var(--space-1) 2px var(--space-2);
    border-radius: 20px;
    background: var(--c-primary);
    color: #fff;
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.3;
}
.marcas-chip-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* La × es un glifo ASCII (U+00D7), no un emoji: se ve idéntica en iOS, Android
   y escritorio, y no se colorea sola como haría ❌/✖️. */
.marcas-chip-x {
    border: 0;
    background: transparent;
    color: #fff;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    /* Área táctil cómoda sin engordar el chip (el padding negativo lo compensa
       el propio flex del campo). */
    padding: 0 4px;
    min-width: 22px;
    min-height: 22px;
}
.marcas-chip-x:hover { opacity: 0.75; }

/* Desplegable de sugerencias (hijo de <body>: fixed sin recorte del bloque
   de filtros, que tiene overflow). */
.marcas-drop {
    position: fixed;
    z-index: 1200;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    background: var(--c-card);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-3);
    padding: var(--space-1);
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.marcas-drop[hidden] { display: none; }
.marcas-opcion {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--esp-chip);
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--c-text);
    font-size: 0.9rem;
    text-align: left;
    cursor: pointer;
    /* Con guantes / dedo gordo: alto de toque cómodo (regla de la app). */
    min-height: 44px;
}
.marcas-opcion:hover, .marcas-opcion.activa { background: var(--c-bg-soft); }
.marcas-opcion.activa { box-shadow: inset 0 0 0 2px var(--c-primary); }
.marcas-opcion-nombre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.marcas-opcion-num { color: var(--c-text-soft); font-size: 0.82rem; flex: 0 0 auto; }
.marcas-vacio { padding: var(--space-2) var(--space-3); color: var(--c-text-soft); font-size: 0.85rem; }

/* En el bloque de filtros del buscador ocupa lo mismo que ocupaba el antiguo
   input "Marca" (12ch), pero crece hasta 22ch cuando lleva chips dentro. */
.busq-fila-filtros .marcas-multi { flex: 0 1 auto; width: 13ch; }
.busq-fila-filtros .marcas-multi.marcas-con-chips { width: auto; max-width: 100%; min-width: 22ch; }
/* En el catálogo de la visita la marca tiene su propia fila: ocupa el ancho. */
.busq-fila-marca .marcas-multi { flex: 1 1 auto; width: 100%; }

/* ===== 🏭 Filtro por PROVEEDOR (1.190.0) =====
   Typeahead de selección ÚNICA (core/proveedorFiltro.js). Reutiliza el lenguaje
   visual del selector de marcas: mismo campo, mismo desplegable en <body>. */
.provf { position: relative; display: inline-flex; min-width: 0; flex: 0 1 auto; }
.provf-campo {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1);
    padding: 3px var(--space-2);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm);
    background: var(--c-card);
    min-height: 36px; min-width: 0; width: 100%; cursor: text;
}
.provf-campo:focus-within { border-color: var(--c-primary); }
.provf-chip-wrap { display: contents; }
.provf-input {
    flex: 1 1 6ch; min-width: 6ch;
    border: 0; outline: none; background: transparent; color: var(--c-text);
    font-size: 0.875rem; padding: var(--space-1) 0; min-height: 28px;
}
.provf-chip {
    display: inline-flex; align-items: center; gap: 2px;
    max-width: 100%;
    padding: 2px var(--space-1) 2px var(--space-2);
    border-radius: 20px; background: var(--c-primary); color: #fff;
    font-size: 0.78rem; font-weight: 600; line-height: 1.3;
}
.provf-chip-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.provf-chip-x {
    border: 0; background: transparent; color: #fff;
    font-size: 1rem; line-height: 1; cursor: pointer;
    padding: 0 4px; min-width: 22px; min-height: 22px;
}
.provf-chip-x:hover { opacity: 0.75; }
.provf-drop {
    position: fixed; z-index: 1200; overflow-y: auto; -webkit-overflow-scrolling: touch;
    background: var(--c-card); border: 1px solid var(--c-border-strong);
    border-radius: var(--radius-sm); box-shadow: var(--shadow-3);
    padding: var(--space-1); display: flex; flex-direction: column; gap: 2px;
}
.provf-drop[hidden] { display: none; }
.provf-opcion {
    display: flex; align-items: center; justify-content: space-between; gap: var(--esp-chip);
    width: 100%; padding: var(--space-2) var(--space-3);
    border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--c-text);
    font-size: 0.9rem; text-align: left; cursor: pointer; min-height: 44px;
}
.provf-opcion:hover, .provf-opcion.activa { background: var(--c-bg-soft); }
.provf-opcion.activa { box-shadow: inset 0 0 0 2px var(--c-primary); }
.provf-opcion-nombre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.provf-opcion-num { color: var(--c-text-soft); font-size: 0.82rem; flex: 0 0 auto; }
.provf-vacio { padding: var(--space-2) var(--space-3); color: var(--c-text-soft); font-size: 0.85rem; }

/* En el bloque de filtros del buscador de artículos: ancho acotado como la marca,
   crece cuando lleva el proveedor elegido dentro. */
.busq-fila-filtros .provf { flex: 0 1 auto; width: 15ch; }
.busq-fila-filtros .provf.provf-con-sel { width: auto; max-width: 100%; min-width: 18ch; }
/* En el panel de filtros de documentos (pedidos): ocupa su fila entera. */
.docfil-campo .provf { flex: 1 1 auto; width: 100%; }

/* ===================== Toma de datos 1.185.0 (M1/M2/M4) ===================== */

/* M1 — NOMBRE DEL CLIENTE en la barra superior: "Toma de datos (ANA RELINQUE
   GIRALDO)". Vive DENTRO del <h1> de la topbar, que ya recorta con puntos
   suspensivos (nowrap + ellipsis), así que un nombre largo no rompe la cabecera.
   Un peso normal y algo apagado para que la marca siga mandando. */
.td-cab-cliente { font-weight: 600; color: var(--c-text-soft); }

/* M2 — ESPEJO del filtro por opción dentro de "🎯 Opciones a presupuestar".
   Mismo componente que el de la cabecera; aquí se separa del resto del bloque
   con el ritmo de siempre (nunca números sueltos). */
.td-filtro-seccion:not(:empty) {
    margin-top: 0;
    margin-bottom: var(--esp-fila);
    padding-bottom: var(--esp-fila);
    border-bottom: 1px dashed var(--c-border);
}

/* M4 — "🚫 No lleva / no lo quiere": el chip del editor y el distintivo de la
   tarjeta. Gris pizarra (ni el azul de "del cliente" ni el ámbar de "pendiente
   de alta"), con contorno para que se lea igual en tema claro y oscuro. */
.chip.chip-nolleva.active {
    background: var(--c-text-soft);
    border-color: var(--c-text-soft);
    color: #fff;
}
.td-badge-nolleva {
    display: inline-block; margin-top: 2px;
    background: var(--c-bg-soft); color: var(--c-text);
    border: 1.5px solid var(--c-text-soft);
    font-size: 0.75rem; font-weight: 700; border-radius: 10px; padding: 1px 8px;
}
.td-nota-nolleva { margin-top: var(--esp-fila); }

/* RONDA 174 — aviso INLINE de la hoja «Anotar a mano»: «no lleva» y los electros de
   esa categoría se excluyen, así que hay que elegir. Va dentro de la hoja (showSheet
   no admite hojas anidadas), por eso es un bloque y no otro modal. */
.td-aviso-nolleva-excl {
    margin-top: var(--esp-fila);
    padding: var(--esp-fila) var(--esp-borde);
    border: 1.5px solid var(--c-warning, var(--c-border-strong));
    border-radius: 10px;
    background: var(--c-bg-soft);
    font-size: 0.9rem;
}
.td-aviso-nolleva-excl .td-chips { margin-top: var(--esp-fila); }

/* Dato de una visita antigua que ya NO se pregunta (1.187.0: el escurridor suelto,
   que ahora lo dice la instalación del fregadero). Se lee, no se edita: solo se
   puede quitar. */
.td-nota-legado {
    display: flex; align-items: center; gap: var(--esp-chip);
    flex-wrap: wrap;
    margin-top: var(--esp-fila);
    padding: var(--esp-fila) var(--esp-borde);
    border: 1px dashed var(--c-border-strong);
    border-radius: 10px;
    background: var(--c-bg-soft);
    color: var(--c-text-soft);
    font-size: 0.88rem;
}
.td-nota-legado > span { flex: 1 1 12rem; }

/* ===== Modo RELLENAR PLANO sobre la planta (planos rellenos, F1) =====
   Mismo esqueleto que la barra del aplacado (que ya está rodada en iPad): título +
   botonera que scrollea en horizontal si no cabe, y una línea de ayuda debajo. */
.pr-banner {
    padding: var(--esp-fila) var(--esp-borde);
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--esp-chip) var(--esp-fila);
}
.pr-banner-tit {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    font-weight: 700;
    color: var(--c-text);
}
.pr-resumen { font-variant-numeric: tabular-nums; color: var(--c-text-soft); font-weight: 400; }
.pr-banner-btns {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    justify-content: flex-start;
    gap: var(--esp-chip);
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
}
.pr-banner-btns::-webkit-scrollbar { display: none; }
/* Los MODOS, agrupados (ronda 20 §3). Conservan el comportamiento de la botonera: en fila,
   sin envolver y con scroll horizontal propio. ⚠ RONDA 62 §2: ya NO se pliegan — con ellos
   se fue la regla `[hidden]` que hacía falta para esconder el grupo y su fila. */
/* 🖨 y ✕, al final de la botonera y sin moverse de fila (r62 §2; hasta la r61 se mudaban a
   la línea del título al plegar los modos). */
.pr-banner-fijos {
    display: inline-flex;
    align-items: center;
    gap: var(--esp-chip);
    margin-left: auto;
    /* NOWRAP (ronda 25 §4): con `wrap`, «✕ Salir» se descolgaba a una tercera línea en
       cuanto el título ocupaba lo suyo. Aquí no envuelve nunca: o van los tres en su fila,
       o la botonera scrollea, que es el patrón de la casa. */
    flex-wrap: nowrap;
    flex: 0 0 auto;
}
.pr-banner-v2 .pr-banner-tit { flex-wrap: wrap; width: 100%; }
.pr-banner-modos {
    display: flex;
    gap: var(--esp-chip);
    flex-wrap: nowrap;
    align-items: center;
    /* Ancho INTRÍNSECO: quien scrollea es la botonera de fuera (`.pr-banner-btns`), como
       antes de agrupar. Si el grupo pudiera encogerse, recortaría los botones en vez de
       dejar que el padre se desplace. */
    flex: 0 0 auto;
    border-radius: var(--radius-md);
}
.pr-btn { white-space: nowrap; flex: 0 0 auto; }
/* RONDA 82 §2 — un botón BLOQUEADO porque hay un modo encendido. No lleva el atributo
   `disabled` a propósito: con él el navegador no dispara ningún evento de puntero y se
   perdería la ayuda por pulsación larga, que es justo la que explica por qué no se puede
   pulsar. La pinta es la misma que la de `[disabled]` en el resto de la app. */
.pr-btn.pr-bloqueado,
.chip.pr-bloqueado {
    opacity: 0.45;
    cursor: not-allowed;
}
/* RONDA 75 §2.2 — «🔄 Empezar de cero» es lo ÚNICO de esta botonera que destruye trabajo:
   se ve distinto para que no se pulse por inercia (y aun así pide confirmación con casilla). */
.pr-btn-peligro {
    border-color: var(--c-danger, #c62828);
    color: var(--c-danger, #c62828);
}
/* La hoja de confirmación del reset: el recuento de lo que se pierde y la casilla. */
.pr-reset-lista { margin: var(--esp-fila, 12px) 0; padding-left: 1.2em; }
.pr-reset-lista li { margin-bottom: 3px; }
.pr-reset-aviso {
    margin: var(--esp-fila, 12px) 0;
    color: var(--c-danger, #c62828);
    font-weight: 600;
}
.pr-reset-ok { display: flex; align-items: center; gap: 8px; margin-top: var(--esp-fila, 12px); }
/* RONDA 80 §5 — la lista de lo que la toma de datos dice y el plano no marca, en la hoja de
   «Antes de imprimir». Misma pinta que la lista del reset: es una enumeración corta que se
   lee de un vistazo antes de decidir. */
.pr-precheck-lista { margin: var(--esp-fila, 12px) 0; padding-left: 1.2em; }
.pr-precheck-lista li { margin-bottom: 4px; }
/* (La clase `.pr-btn-icono` del chevron de plegar se retiró con la ronda 62 §2, que quitó
   el plegado de la botonera del rellenar: era su único usuario.) */
.pr-btn.active { border-color: var(--c-primary); color: var(--c-primary); font-weight: 700; }
/* RONDA 127 — el chip de los códigos DICE en qué estado está («🔠 Ver códigos» / «🔠 Ocultar
   códigos»). Los dos rótulos se apilan en LA MISMA celda de una rejilla de una casilla, así
   que el ancho del botón es siempre el del texto más largo: cambiar de estado no reordena la
   botonera y el toque siguiente cae donde cayó el anterior. El gemelo invisible conserva su
   hueco (`visibility`, no `display`) y va con `aria-hidden` para que no se lea dos veces. */
.pr-cod-celdas { display: inline-grid; }
.pr-cod-celdas > span { grid-area: 1 / 1; }
.pr-cod-celdas > .pr-cod-medida { visibility: hidden; }
.plano-lateral .pr-cod-celdas { display: grid; }
.pr-huerfanos { border-color: var(--c-warn-line); color: var(--c-warn-fg); }
.pr-hint { flex-basis: 100%; font-size: 0.76rem; }

/* La hoja del mueble. Es LARGA a propósito (despiece + accesorios + aparatos + remates),
   así que la botonera de guardar se queda pegada arriba: hay que poder guardar sin
   recorrerla entera (riesgo anotado en la spec). */
.pr-hoja { display: flex; flex-direction: column; gap: var(--esp-fila); }
.pr-sticky {
    position: sticky;
    top: calc(-1 * var(--space-4));
    z-index: 2;
    display: flex;
    gap: var(--esp-chip);
    padding: var(--esp-fila) 0;
    margin: calc(-1 * var(--esp-fila)) 0 0;
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    /* RONDA 37 §2ter — con los CUATRO botones (Guardar / Propuesta / Descartar / Ocultar,
       desde la ronda 30) esta fila no cabe en 390 px: el último se cortaba y, al envolver su
       texto, los botones crecían a lo alto y la cabecera pegada se comía la hoja (pantallazo
       del rodaje). Mismo patrón que la botonera principal (`.pr-banner-btns`, ronda 25): en
       una sola línea, con scroll horizontal propio y sin barra a la vista. */
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
}
.pr-sticky::-webkit-scrollbar { display: none; }
/* Los botones no se encogen ni parten su texto: quien se desplaza es la fila. Así el alto de
   la cabecera es SIEMPRE el de un botón y el scroll vertical de la hoja no se mueve.
   ⚠ `width: auto` es imprescindible: `.btn-primary` va a ancho COMPLETO en las hojas (es lo
   que se quiere en una hoja normal, donde los botones se apilan), y aquí eso dejaba a
   «💾 Guardar» ocupando la fila entera y a los otros tres detrás, fuera de la vista. */
.pr-sticky > .btn { flex: 0 0 auto; width: auto; white-space: nowrap; }
/* RONDA 37 §2bis — el aviso de «estoy cargando» del modo rellenar, con el mismo criterio que
   el de la hoja de impresión (ronda 31 §5): ARRIBA y pegajoso, para que no lo tape nada. */
.pr-cargando {
    position: sticky;
    top: 0;
    z-index: 6;
    margin: 0 auto var(--space-3);
    max-width: 22rem;
    padding: var(--space-3);
    border-radius: 10px;
    background: var(--c-bg, #fff);
    box-shadow: 0 2px 10px rgba(0, 0, 0, .15);
}
.pr-cab { display: flex; flex-direction: column; gap: 2px; }
.pr-cab-ref { font-weight: 700; }
.pr-cab-desc { color: var(--c-text-soft); }
.pr-cab-med { font-variant-numeric: tabular-nums; color: var(--c-text-soft); font-size: 0.85rem; }
.pr-cab-fam { font-size: 0.74rem; word-break: break-word; }
.pr-aviso {
    border: 1px solid var(--c-warn-line);
    color: var(--c-warn-fg);
    border-radius: var(--radius);
    padding: var(--esp-fila) var(--esp-borde);
    font-size: 0.86rem;
}
/* RONDA 38 — el aviso YA VISTO conserva su texto (esa es media petición del usuario: «sigue
   guardando el motivo») pero pierde el ámbar: en gris apagado, para que no compita con los
   que sí siguen pendientes justo encima. */
.pr-aviso-visto {
    border-color: var(--c-border);
    color: var(--c-text-muted);
    opacity: .85;
}
/* Ronda 49 — la CONSTANCIA debajo del aviso asumido: «Dado por visto por DANI el 22/08».
   Pegada a su aviso (margen negativo arriba) y en letra pequeña: es un pie, no otro aviso. */
.pr-aviso-quien {
    margin: -4px 0 8px 2px;
    font-size: 0.78rem;
}
/* La fila del botón «✔ Dar por visto» / «↩ Volver a mostrarlo»: alineado a la derecha, como
   el resto de acciones sueltas de la hoja. */
.pr-aviso-acc { display: flex; justify-content: flex-end; }
/* RONDA 147 §3 — el aviso DEL PLANO (el que enciende el triángulo en la etiqueta) va en el
   color del AVISO; `.pr-aviso` a secas se queda en ámbar para lo INFORMATIVO de la hoja (la
   llamada a la toma de datos, «este servidor no guarda esto»…), que no es un problema que
   revisar. Sin esto, el vendedor veía el mismo marco para las dos cosas. */
.pr-aviso-alerta {
    border-color: var(--c-aviso-line);
    color: var(--c-aviso-fg);
}
.pr-sec {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--c-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-top: var(--esp-fila);
    padding-top: var(--esp-fila);
    border-top: 1px solid var(--c-border);
}
/* RONDA 69 §4.2 — SUBSECCIÓN dentro de una sección (las tres posiciones de frente del lote:
   superior / inferior / intermedios). Se lee como una cabecera pero SIN la línea de arriba,
   que es lo que separa una sección de otra: aquí son hermanas dentro de la misma. */
.pr-sec-sub {
    font-size: 0.8rem;
    font-weight: 600;
    margin-top: var(--esp-fila);
}
.pr-frentes, .pr-electros { display: flex; flex-direction: column; gap: var(--esp-fila); }
.pr-frente {
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
    padding: var(--esp-fila) var(--esp-borde);
    display: flex;
    flex-direction: column;
    gap: var(--esp-chip);
}
.pr-frente-cab { display: flex; align-items: center; gap: var(--esp-chip); flex-wrap: wrap; }
.pr-frente-n { font-weight: 700; color: var(--c-text-muted); }
.pr-frente-neto { font-variant-numeric: tabular-nums; font-weight: 600; }
.pr-tag {
    font-size: 0.7rem;
    border: 1px solid var(--c-warn-line);
    color: var(--c-warn-fg);
    border-radius: 999px;
    padding: 1px 8px;
}
.pr-quitar { align-self: flex-start; }
.pr-campo { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pr-campo-tit { font-size: 0.78rem; color: var(--c-text-muted); }
.pr-campo-ayuda { font-size: 0.72rem; }
/* RONDA 39 §1 — los textos de las hojas del plano llevan nombres de material que vienen de
   la tarifa y pueden ser larguísimos y sin espacios («DEKTON_KRETA_ULTRA»): que partan por
   donde haga falta antes que empujar la hoja de lado. Los <span> del check son ADEMÁS items
   de un flex en fila, donde el mínimo automático es la palabra más larga. */
.pr-campo-tit, .pr-campo-ayuda, .pr-explica, .pr-lineal-cifras, .pr-descartado, .pr-sec,
.pr-check > span, .pr-aviso {
    overflow-wrap: anywhere;
    min-width: 0;
}
.pr-num { max-width: 160px; }
/* RONDA 139 §2 — el aviso de rango de las medidas del CF/RE: se ve mientras el número está
   fuera de los topes de la casa y desaparece solo al corregirlo. */
.pr-med-aviso { color: var(--c-danger); min-height: 0; }
.pr-med-aviso:empty { display: none; }
.pr-check { display: flex; align-items: center; gap: var(--esp-chip); }
.pr-check input[type="checkbox"] { width: 22px; height: 22px; flex: none; }
/* RONDA 82 §1 — el grupo de RADIO de la hoja «Las puertas del plano»: se eligen dos o tres
   puertas y todas tienen que verse a la vez con su modelo, su color y su zona. Misma caja de
   toque que los checks (22 px) para que se pueda dar con el dedo. */
.pr-radios { display: flex; flex-direction: column; gap: var(--esp-chip, 8px); }
.pr-radio { display: flex; align-items: flex-start; gap: var(--esp-chip, 8px); min-width: 0; }
.pr-radio input[type="radio"] { width: 22px; height: 22px; flex: none; margin-top: 2px; }
.pr-radio-txt { display: flex; flex-direction: column; min-width: 0; overflow-wrap: anywhere; }
/* Cada color del diseño con su desplegable y su lista de muebles. */
.pr-color-teowin { margin-bottom: var(--esp-fila, 12px); }
/* El aviso de «este servidor todavía no guarda esto», con la pinta ámbar de la casa. */
.pr-aviso-ambar {
    background: var(--c-warn-bg, #fff8e1);
    color: var(--c-warn-fg, #7a5300);
    border-radius: var(--radius-md, 10px);
    padding: var(--esp-chip, 8px);
    margin-bottom: var(--esp-fila, 12px);
    font-size: 0.85rem;
}
.pr-fila { display: flex; align-items: center; gap: var(--esp-chip); flex-wrap: wrap; }
.pr-fila-txt { flex: 1 1 auto; min-width: 0; }
.pr-explica { font-size: 0.8rem; }
/* RONDA 52 §2 — la hoja del tirador: «Elegido: …» y las puertas de la visita a modo
   informativo. Solo lectura: aquí no se edita nada (las puertas se cambian en la visita). */
.pr-elegido {
    font-weight: 700;
    font-size: 0.92rem;
    margin: 0 0 var(--esp-chip);
    overflow-wrap: anywhere;
    min-width: 0;
}
.pr-elegido .pr-explica { font-weight: 400; }
.pr-puertas-info {
    margin: 0 0 var(--esp-fila);
    padding: var(--esp-chip) 10px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-md);
    background: var(--c-bg-soft);
}
.pr-puertas-info > summary { cursor: pointer; }
.pr-puerta-info {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-top: 4px;
    font-size: 0.82rem;
    line-height: 1.2;
    min-width: 0;
    overflow-wrap: anywhere;
}
.pr-puerta-info > span:last-child { display: flex; flex-direction: column; min-width: 0; }
.pr-puerta-info .pr-mat-swatch { margin-top: 2px; }
/* RONDA 61 — los bloques informativos de la hoja «Dos cosas que el plano no sabe»: los TRES
   datos del tirador (§1) y los DOS lados del cierre a techo (§2). Mismo marco que el de las
   puertas: es la misma clase de contenido, solo lectura y en la misma hoja. */
.pr-tirador-info {
    margin: 0 0 var(--esp-fila);
    padding: var(--esp-chip) 10px;
    border: 1px solid var(--c-border);
    border-radius: var(--radius-md);
    background: var(--c-bg-soft);
}
.pr-tirador-info > .pr-explica:first-child { margin-bottom: 2px; }
.pr-dato {
    font-size: 0.82rem;
    line-height: 1.3;
    margin-top: 4px;
    min-width: 0;
    overflow-wrap: anywhere;
}
.pr-dato > summary { cursor: pointer; }
.pr-dato-sub { font-size: 0.82rem; line-height: 1.3; margin: 2px 0 0 14px; overflow-wrap: anywhere; }
/* Que un dato NO esté dicho es información (§1): se marca sin gritar. */
.pr-dato-falta > span { opacity: 0.75; font-style: italic; }
.pr-desacuerdo {
    font-size: 0.82rem;
    line-height: 1.35;
    margin-top: 6px;
    padding: 6px 8px;
    border-radius: var(--radius-sm);
    background: var(--c-warn-bg);
    color: var(--c-warn-fg);
    overflow-wrap: anywhere;
}
/* RONDA 46 §2bis — qué hace «Descartar» y qué hace «Ocultar», debajo de la botonera de la
   ficha del mueble. Dos líneas cortas: la duda que planteó el usuario la van a tener los
   vendedores, y aquí es donde se responde. */
.pr-gestos {
    font-size: 0.75rem;
    line-height: 1.35;
    margin: var(--esp-chip) 0 0;
    overflow-wrap: anywhere;
    min-width: 0;
}
.pr-vacio { font-size: 0.85rem; }
.pr-descartado { font-size: 0.8rem; }
.pr-frente-tipo { color: var(--c-text-soft); }

/* Rellenar plano v2 (rodaje 19-ago): título arriba y BOTONES DEBAJO en su fila. */
.pr-banner-v2 { flex-direction: column; align-items: stretch; }
.pr-banner-v2 .pr-banner-tit { justify-content: flex-start; }
.pr-banner-v2 .pr-banner-btns { flex: none; width: 100%; }

/* PALETA del pincel de materiales (F1g §5.5). Va DEBAJO del banner y solo aparece en modo
   pincel: una fila de chips con su color, que scrollea en horizontal si no cabe (mismo
   esqueleto que la botonera, ya rodado en iPad). */
.pr-paleta {
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    padding: var(--esp-fila) var(--esp-borde);
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    overflow-x: auto;
    scrollbar-width: none;
}
.pr-paleta::-webkit-scrollbar { display: none; }
/* ── RONDA 90 §6 — QUE SE VEA QUE LA PALETA DESLIZA ────────────────────────────────────
   La barra de scroll está oculta desde la F1g, así que con muchos materiales el vendedor no
   sabía que había más a la derecha (ni que al final estaba el botón de reasignar). Ahora la
   paleta va dentro de un envoltorio que pinta un DEGRADADO en el borde por el que queda
   contenido, y en pantalla ancha unas flechas ‹ › para empujarla. Las clases `hay-izq` /
   `hay-der` las pone el JS al medir el scroll. */
.pr-paleta-wrap { position: relative; }
/* RONDA 140 §3b — EL ENVOLTORIO NO PUEDE ENSANCHAR A SU BARRA. Al enganchar el deslizador a
   filas que viven dentro de un contenedor FLEX (la botonera de modos `.pr-banner-btns`, la
   cabecera de la hoja del mueble `.pr-sticky`), el hijo del flex pasa a ser el envoltorio y la
   barra, ya sin la restricción del padre, se estiraba hasta su contenido (medido: 2.819 px en
   una pantalla de 375). Con esto el envoltorio ocupa lo que le den, nunca más, y la barra se
   queda dentro con su propio scroll.
   ⚠ `flex: 0 1 auto` y NO `1 1 auto`: con `grow` el envoltorio se repartía el hueco de una
   columna flex y una barra OCULTA (la de etiquetas, la de lote, la del corte, la bandeja de
   accesorios) seguía ocupando 85 px cada una — cuatro barras invisibles comiéndose el lienzo
   (medido: 85 px de plano en un teléfono). Sin `grow`, el envoltorio mide lo que mida su barra,
   y si ella está oculta mide cero. */
.pr-paleta-wrap { min-width: 0; max-width: 100%; align-self: stretch; flex: 0 1 auto; }
.pr-paleta-wrap > * { max-width: 100%; }
.pr-paleta-wrap::before,
.pr-paleta-wrap::after {
    content: '';
    position: absolute;
    top: 0; bottom: 0;
    width: 26px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s linear;
    z-index: 1;
}
.pr-paleta-wrap::before { left: 0; background: linear-gradient(90deg, var(--c-card), transparent); }
.pr-paleta-wrap::after { right: 0; background: linear-gradient(270deg, var(--c-card), transparent); }
.pr-paleta-wrap.hay-izq::before { opacity: 1; }
.pr-paleta-wrap.hay-der::after { opacity: 1; }
.pr-paleta-fle {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    min-height: 30px;
    min-width: 26px;
    border: 1px solid var(--c-border);
    border-radius: 999px;
    background: var(--c-bg);
    color: var(--c-text);
    display: none;
}
.pr-paleta-fle.izq { left: 2px; }
.pr-paleta-fle.der { right: 2px; }
/* RONDA 140 §3a — LAS FLECHAS SALEN SIEMPRE QUE HAYA ALGO MÁS POR ESE LADO, también en el
   teléfono (petición del usuario del 11-sep: la botonera de la hoja del mueble «es difícil de
   desplazar»). Antes solo aparecían a partir de 900 px. El dedo sigue arrastrando la fila
   igual —las flechas no tapan el gesto, solo son un atajo— y miden 32 px, que es el mínimo
   táctil de la casa. */
.pr-paleta-wrap.hay-izq .pr-paleta-fle.izq,
.pr-paleta-wrap.hay-der .pr-paleta-fle.der { display: block; }
@media (max-width: 899.98px) {
    .pr-paleta-fle { min-width: 32px; min-height: 32px; opacity: 0.92; padding: 0; }
}
/* RONDA 90 §6 — «🔁 Asignar por los colores de Teowin…» SIEMPRE a la vista: va el PRIMERO de
   la fila y se queda pegado a la izquierda mientras el resto desliza por debajo. */
.pr-mat-reasig {
    position: sticky;
    left: 0;
    z-index: 3;
    box-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.35);
}
/* RONDA 58 §1 — la barra del modo ☑ Varios. Misma barra inferior que la paleta del pincel
   (mismo patrón de modo), con el contador delante y los dos botones de acción al final. */
.pr-lote-n { flex: 0 0 auto; white-space: nowrap; font-size: 0.8125rem; }
.pr-lote .chip[disabled] { opacity: 0.45; pointer-events: none; }
/* RONDA 97 §1 — la barra del AJUSTE de la línea de corte del alzado virtual. Misma banda
   que la del lote (el vendedor ya sabe dónde mirar); el rótulo de la izquierda dice el largo
   y desde dónde se mira, y se recompone mientras el dedo la mueve. */
.pr-corte .chip[disabled] { opacity: 0.45; pointer-events: none; }
.pr-corte .pr-lote-n { color: var(--c-text); font-weight: 600; }
.pr-mat-caja { display: inline-flex; align-items: stretch; flex: 0 0 auto; }
.pr-mat {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    min-height: 34px;
    padding: 4px 10px;
    border: 1px solid var(--c-border);
    border-radius: 999px;
    background: var(--c-bg);
    color: var(--c-text);
    font-size: 0.82rem;
    white-space: nowrap;
}
.pr-mat.active { border-color: var(--c-primary); color: var(--c-primary); font-weight: 700; }
.pr-mat-txt { display: inline-flex; flex-direction: column; align-items: flex-start; line-height: 1.15; }
.pr-mat-zona { font-size: 0.7rem; color: var(--c-text-soft); font-weight: 400; }
.pr-mat-fuera { border-style: dashed; opacity: 0.75; }
/* RONDA 95b §3 — «↺ Quitar todo lo pintado» / «↺ Quitar las aperturas por frente»: van los
   ÚLTIMOS de su barra y se leen como lo que son, una acción que deshace, no un material más.
   Apagados cuando no hay nada que quitar (el `disabled` del botón). */
.pr-mat-reset {
    flex: 0 0 auto;
    white-space: nowrap;
    border-style: dashed;
    color: var(--c-text-soft);
}
.pr-mat-reset:not([disabled]):hover { color: var(--c-danger, #c0392b); border-color: currentColor; }
.pr-mat-reset[disabled] { opacity: 0.45; pointer-events: none; }
.pr-mat-swatch {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 4px;
    border: 1px solid var(--c-border);
    flex: 0 0 auto;
}
.pr-mat-def {
    min-height: 34px;
    min-width: 30px;
    margin-left: 2px;
    border: 1px solid var(--c-border);
    border-radius: 999px;
    background: var(--c-bg);
    color: var(--c-text-soft);
    font-size: 0.8rem;
}
.pr-paleta-vacia { font-size: 0.8rem; }
/* Título de grupo de la paleta (ronda 23 §2): «Puertas» / «Encimeras». Solo se pinta cuando
   hay más de un origen, así que la barra de siempre no cambia. */
.pr-mat-grupo {
    flex: 0 0 auto;
    align-self: center;
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding-left: 4px;
}
/* Separador entre las DOS secciones de la paleta (ronda 28 §5): «no tendrías que mezclar
   entre puertas y encimeras». Solo se pinta cuando hay los dos orígenes. */
.pr-mat-sep {
    flex: 0 0 auto;
    align-self: stretch;
    width: 1px;
    margin: 2px 4px;
    background: var(--c-border);
}
/* «Establecer como defecto y limpiar lo aplicado» (ronda 28 §5). Es destructivo y pide
   confirmación, así que se distingue del ★ de al lado. */
.pr-mat-limpiar { color: var(--c-danger); }

/* BANDEJA de accesorios de la visita (F4 §3.1). Reutiliza el esqueleto de la paleta del
   pincel —misma barra, mismos chips, mismo gesto de elegir arriba y tocar abajo—; lo suyo
   son los encabezados de OPCIÓN y la versión EN LÍNEA que se despliega dentro de la hoja
   del mueble (ahí envuelve en vez de scrollear: la hoja ya scrollea en vertical). */
.pr-band-grupo {
    flex: 0 0 auto;
    font-size: 0.72rem;
    font-weight: 700;
    color: var(--c-text-soft);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 0 2px;
}
.pr-band-chip { max-width: 62vw; }
.pr-band-chip .pr-mat-nom,
.pr-band-chip .pr-mat-zona { overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.pr-bandeja-inline {
    flex-wrap: wrap;
    overflow-x: visible;
    background: transparent;
    border-bottom: 0;
    padding: var(--esp-fila) 0;
}
.pr-bandeja-inline .pr-band-chip { max-width: 100%; }
.pr-accvis { display: flex; flex-direction: column; gap: var(--esp-chip); }
.pr-acc-fuera { font-size: 0.78rem; }
/* EL CÓDIGO del artículo bajo el nombre (amend §3.3), en la bandeja y en la hoja del mueble:
   secundario y en su propia línea — lo que distingue dos accesorios que se llaman igual. */
.pr-acc-cod { display: block; font-size: 0.72rem; color: var(--c-text-soft); font-weight: 400; }
/* RONDA 72 §1 — el pie de la fila de un aparato: con qué artículo de la visita va enlazado
   y, si no se ha podido comprobar, por qué. Informativo, nunca un aviso. */
.pr-el-nota { font-size: 0.78rem; margin: -2px 0 6px 2px; }

/* LEYENDA del modo rellenar (F1h §5): el papel de los vendedores siempre lleva una, y es
   lo que hace legible el código a quien recibe la hoja. Flota sobre el lienzo, arriba a la
   derecha (abajo a la izquierda ya está la leyenda de bandas del visor) y se pliega a un
   chip con un toque. Colores FIJOS a propósito: se lee encima del plano, que es blanco en
   los dos temas. */
.pr-leyenda {
    position: absolute;
    right: 8px;
    top: 8px;
    z-index: 4;
    background: rgba(255, 255, 255, 0.92);
    border: 1px solid rgba(0, 0, 0, 0.14);
    border-radius: 8px;
    padding: 6px 8px;
    font-size: 0.72rem;
    line-height: 1.5;
    color: #22252b;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14);
    max-width: 72%;
    cursor: pointer;
    user-select: none;
}
.pr-leyenda-plegada {
    padding: 2px 8px;
    min-width: 34px;
    min-height: 34px;          /* área de toque cómoda con el dedo */
    display: flex;
    align-items: center;
    justify-content: center;
}
.pr-leyenda-chip { font-size: 1rem; }
.pr-leyenda-estados { white-space: normal; }
.pr-leyenda-punto { font-size: 0.85rem; }
.pr-leyenda-sep { color: rgba(0, 0, 0, 0.35); }
.pr-leyenda-fila { display: flex; align-items: center; gap: 6px; }

/* RONDA 58 §5 — LA LEYENDA EN COLUMNAS (regla literal: «la leyenda en pantalla muéstrala
   más ordenada, en columnas»). Cada entrada es una casilla de una rejilla que se reparte
   sola: `auto-fill` con un mínimo de 132 px da 1-2 columnas en un móvil estrecho y 4-5 en
   un iPad o un portátil, sin media queries y sin que la leyenda deje de caber en su caja.
   La caja se ensancha hasta el 72 % (era 62 %) porque ahora el ancho es lo que le da las
   columnas; el alto lo limita el propio contenido, que es más corto al no envolver. */
.pr-leyenda-rejilla {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
    column-gap: 12px;
    row-gap: 1px;
    align-items: start;
}
.pr-leyenda-item {
    display: flex;
    align-items: center;
    gap: 5px;
    min-width: 0;              /* que un nombre de material largo no estire la columna */
}
.pr-leyenda-item > span:last-child { overflow-wrap: anywhere; }
/* Una entrada LARGA (la nota de la regleta virtual, el material por defecto) ocupa DOS
   columnas: partirla en una sola disparaba el alto de toda la fila. MEDIDO en 1366×768:
   con esto la leyenda pasa de 386×229 px (2 columnas) a 765×112 (5 columnas). */
.pr-leyenda-item.pr-leyenda-ancho { grid-column: span 2; }
/* En pantalla ancha la caja toma un ancho DEFINIDO: flotando y con `max-width` en % la
   rejilla se encoge al contenido y `auto-fill` se queda en dos columnas por mucho sitio
   que haya. En móvil no se toca (sigue el `max-width` de arriba, 1-2 columnas). */
@media (min-width: 700px) {
    .pr-leyenda { width: 56%; }
}
.pr-leyenda .pr-leyenda-sw {
    display: inline-block;
    width: 14px;
    height: 4px;
    border-radius: 2px;        /* como el SUBRAYADO del plano, no como un cuadro */
    flex: 0 0 auto;
}
/* RONDA 80 §3 — la muestra de un MATERIAL enseña las DOS capas con las que se marca en el
   plano: el fondo tintado con su color y el subrayado pleno debajo. Un cuadradito liso
   dejaría de parecerse a lo que hay que buscar en el dibujo. El fondo y el borde inferior
   los pone el JS (son el color de ESE material). */
.pr-leyenda .pr-leyenda-sw-mat {
    width: 18px;
    height: 13px;
    border-radius: 2px;
    box-sizing: border-box;
}

/* La MUESTRA de la leyenda de TRAZOS (amend F1h · contrato F1j §4; estilos de la ronda 22):
   un trocito de la línea de verdad —con su dash, o el círculo del LED vertical— porque el
   vendedor la busca por su FORMA, no por su color. La dibuja `muestraTrazo` en px, la misma
   función que compone la muestra en mm de la hoja impresa. */
.pr-leyenda .pr-leyenda-muestra {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    line-height: 0;
}
/* La fila de estilos es la más larga de la leyenda: se deja envolver en vez de recortarse. */
.pr-leyenda .pr-leyenda-trazos { flex-wrap: wrap; row-gap: 2px; }

/* La fila de un TRAZO en la hoja «LED y cierre» (F1j §2.7): el check de siempre y, en los
   cierres a techo, su MATERIAL y su nota debajo, sangrados para que se lean como detalle
   de esa fila y no como campos sueltos de la hoja. */
.pr-trazo {
    display: flex;
    flex-direction: column;
    gap: var(--esp-chip);
    padding-bottom: var(--esp-chip);
}
.pr-trazo .pr-campo { margin-left: 30px; }
.pr-trazo + .pr-trazo { border-top: 1px solid var(--c-border); padding-top: var(--esp-chip); }
/* RONDA 40 §2ter — pulso de atención en la fila del tramo que se acaba de tocar en el plano
   («✎ Editar»). Mismo patrón que el salto del listado del temporizador (MV17): la animación
   vive aquí y el JS retira la clase al terminar, así que no queda estilo pegado. */
@keyframes tg-flash-trazo {
    0%, 100% { box-shadow: inset 0 0 0 2px transparent; background: transparent; }
    50%      { box-shadow: inset 0 0 0 2px var(--c-primary); background: var(--c-primary-soft); }
}

.pr-trazo.pr-trazo-flash {
    border-radius: var(--radius-md);
    animation: tg-flash-trazo 0.66s ease-in-out 3;
}

/* Con "movimiento reducido" no se anima: se marca de forma estática y el temporizador de
   seguridad del JS es quien retira la clase. */
@media (prefers-reduced-motion: reduce) {
    .pr-trazo.pr-trazo-flash {
        animation: none;
        box-shadow: inset 0 0 0 2px var(--c-primary);
    }
}
/* RONDA 65 §1 — LAS SECCIONES DE MOBILIARIO del panel de lineales («bajos juntos, altos
   juntos…»). Es un <details> nativo: plegar no necesita JS y el estado lo lleva el navegador.
   La cabecera se lee como la de una sección de la hoja (`.pr-sec`) pero es clicable, así que
   lleva su cursor y su marca de foco. */
.pr-grupo { border-top: 1px solid var(--c-border); margin-top: var(--esp-fila); }
.pr-grupo > .pr-grupo-cab {
    display: flex;
    align-items: baseline;
    gap: var(--esp-chip);
    padding: var(--esp-fila) 0 var(--esp-chip);
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--c-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    cursor: pointer;
    list-style: none;                 /* el triángulo lo pone el ::before de abajo */
}
.pr-grupo > .pr-grupo-cab::-webkit-details-marker { display: none; }
.pr-grupo > .pr-grupo-cab::before {
    content: '▾';
    font-size: 0.8em;
    transition: transform 0.12s ease;
}
.pr-grupo:not([open]) > .pr-grupo-cab::before { transform: rotate(-90deg); }
.pr-grupo > .pr-grupo-cab:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }
/* El recuento y los metros: al lado del título, sin gritar. */
.pr-grupo-n {
    margin-left: auto;
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    font-size: 0.78rem;
}
/* La primera fila de un grupo no repite el separador de la cabecera. */
.pr-grupo > .pr-trazo:first-of-type { border-top: none; }
@media (prefers-reduced-motion: reduce) {
    .pr-grupo > .pr-grupo-cab::before { transition: none; }
}
/* RONDA 68 §2 — EL SUBGRUPO por ZONA DE INSTALACIÓN, dentro de su sección. Se sangra y se
   escribe en minúsculas para que la jerarquía se lea de un vistazo: la SECCIÓN grita en
   mayúsculas y la zona va debajo, en voz baja. Sin borde propio (ya lo pone la sección) y
   sin margen arriba en el primero, que si no la cabecera de la sección quedaba suelta. */
.pr-subgrupo { border-top: none; padding-left: var(--esp-fila); margin-top: 0; }
.pr-subgrupo > .pr-grupo-cab {
    padding-top: var(--esp-chip);
    text-transform: none;
    letter-spacing: 0;
    font-weight: 500;
}

/* El botón de recalcular: acción de la hoja, ancho completo y separada de la lista. */
.pr-sec-acciones { margin-top: var(--esp-fila); display: flex; flex-direction: column; gap: var(--esp-chip); }
.pr-recalcular { width: 100%; }
/* Las cifras de la cabecera de cada hoja de lineal (ronda 25 §1): metros por sitio del LED,
   m² por material del aplacado. Van juntas y antes de la lista, que es donde se buscan. */
.pr-lineal-cifras {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--esp-chip) var(--esp-borde);
    border: 1px solid var(--c-border);
    border-radius: var(--radius);
}
/* El aviso-CTA del material de la encimera (ronda 25 §3): el texto y, debajo, el botón que
   lleva a la toma de datos. Hereda el marco ámbar de .pr-aviso. */
.pr-cta {
    display: flex;
    flex-direction: column;
    gap: var(--esp-chip);
    align-items: flex-start;
    margin-left: 30px;
}

/* El pie de página, AÚN MÁS COMPACTO en el visor de planos: cada píxel es plano (petición
   del usuario, 19-ago). Desde la ronda 31 el pie general ya es compacto, así que aquí solo
   queda el apurado extra del visor. */
.plano-page + .app-footer,
.plano-page ~ .app-footer {
    padding: 3px 8px calc(3px + env(safe-area-inset-bottom, 0px));
    min-height: 0;
    margin-top: 0;
    font-size: 0.65rem;
}
.plano-page + .app-footer .app-footer-logo,
.plano-page ~ .app-footer .app-footer-logo { width: 12px; height: 12px; }

/* ── Hoja imprimible del plano relleno (planos rellenos, F2) ────────────────
   La barra de opciones NO se imprime: vive en la página, fuera del iframe con
   la hoja (que es el documento que sale por la impresora). */
.pi-barra {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--esp-chip);
    padding: var(--esp-chip) var(--esp-fila);
}
.pi-barra .form-input { width: auto; min-width: 9.5rem; }
/* EL REDONDEO DE LOS PANELES DEL FORMULARIO (ronda 20 §4). La barra de opciones y sus
   grupos de chips dejan de ser rectángulos a escuadra: mismo escalón que el resto de
   paneles de la app (`--radius-md`, definido arriba en esta misma ronda). */
.pi-barra-cont {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--esp-chip);
    border-radius: var(--radius-md);
    background: var(--c-bg-soft, #f6f7f9);
    border: 1px solid var(--c-border, #e0e0e0);
    padding: var(--esp-chip);
}
/* El stepper «Aa − 100 % +» ya se quería leer como UN control: ahora también se ve como
   uno. El chip «Encuadre manual» NO se toca —es una píldora a propósito (20px)—. */
/* ⚠ `display: flex` en la clase GANA al `[hidden] { display: none }` del navegador (cazado
   al plegar por primera vez en el navegador: la barra seguía midiendo 180 px con el grupo
   «oculto»). Hay que decirlo explícitamente, aquí y en la botonera de modos. */
.pi-barra-cont[hidden] { display: none; }
/* RONDA 71 §3.3 — y lo mismo con el chip «↺ Deshacer ajuste», que solo sale cuando el
   automático ha colocado alguna cota: sin esta línea se veía SIEMPRE (mismo motivo — la
   clase `.chip` trae su propio `display`). */
.pi-barra .chip[hidden] { display: none; }
.pi-barra .pi-fuente {
    border-radius: var(--radius-md);
    background: var(--c-bg, #fff);
    border: 1px solid var(--c-border, #e0e0e0);
    padding: 2px var(--space-2, 8px);
}
/* El chevron que pliega la barra (ronda 20 §3): al plegar quedan solo 🖨 y 📄, que viven
   en la barra de título — el lienzo del papel gana todo lo que ocupaba la barra. */
.pi-plegar { white-space: nowrap; }
.pi-avisos { padding: 0 var(--esp-fila); }
/* RONDA 147 §3 — la banda de avisos de la hoja pasa al color del AVISO (rojo), que ya no es
   el ámbar con el que se marcan los CAMBIOS del plano. */
.pi-aviso {
    color: var(--c-aviso-fg, #8c1515);
    font-size: 0.85rem;
    line-height: 1.35;
    padding: 2px 0;
}
/* RONDA 80 §5 — la fila del aviso que lleva un BOTÓN («✎ Volver a rellenar»): el botón manda
   su propio tamaño, así que la fila solo tiene que dejarle aire. */
.pi-aviso-accion { padding-top: 6px; }
/* RONDA 122 §1 — la banda de avisos se pliega como la barra de opciones. Cerrada deja UNA
   línea: el chip con el recuento y, si hay faltas del precheck, el «✎ Volver a rellenar» al
   lado (la salida no puede quedar escondida detrás del plegado). */
.pi-avisos-cab {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--esp-chip);
    padding: 2px 0;
}
.pi-avisos-resumen {
    color: var(--c-aviso-fg, #8c1515);
    border-color: var(--c-aviso-line, #b71c1c);
    white-space: nowrap;
}
/* `[hidden]` tiene que decirse a mano donde la clase trae su propio `display` (mismo motivo
   que `.pi-barra-cont[hidden]` de la ronda 20: si no, el chip seguía midiendo). */
.pi-avisos[hidden], .pi-avisos-cuerpo[hidden], .pi-avisos-cab .btn[hidden] { display: none !important; }

/* ── ENCUADRE de la impresión (planos rellenos, F2b) ────────────────────────
   Dos zooms que no se pueden confundir: la BARRA habla del papel («Escala del
   papel 1:25», la que se mide con regla) y el control FLOTANTE de la pantalla
   («Vista 120 %», que ni el papel ni el PDF llegan a ver). */
.pi-barra .pi-lupa { min-width: 2.4rem; font-weight: 700; }
.pi-escala { white-space: nowrap; }
/* Stepper «Aa − 100 % +» del tamaño de letra de la anotación (planos rellenos,
   F1m §7). Va agrupado para que se lea como UN control y no como tres botones
   sueltos entre los chips. */
.pi-fuente {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2, 6px);
    white-space: nowrap;
}
.pi-fuente-et { font-weight: 700; }
.pi-fuente-txt { min-width: 3.2rem; text-align: center; }
/* Chip «Encuadre manual · ↺ Auto»: sustituye al aviso «no cabe» en cuanto el
   vendedor mueve o gira la hoja (el recorte pasa a ser decisión suya). */
.pi-chip-manual {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2, 6px);
    padding: 3px 10px 3px 12px;
    border-radius: 20px;
    background: var(--c-bg-soft, #f6f7f9);
    border: 1px solid var(--c-border-strong, #c9ccd1);
    font-size: 0.8rem;
    color: var(--c-text-muted, #6b7280);
}
.pi-chip-manual .pi-sep { opacity: 0.5; }
.pi-chip-manual .chip.pi-auto { padding: 2px 10px; min-height: 26px; font-size: 0.78rem; }

/* El PAPEL como visor de PDF: hoja blanca con sombra sobre el gris (ya lo daba
   `.hoja-visor` / `.hoja-marco`) más el punteado del área imprimible y la lona
   que recoge el arrastre. Las dos capas viven EN LA PÁGINA, nunca dentro del
   documento que se imprime. */
.pi-visor { position: relative; }
/* RONDA 31 §5 — el aviso de carga de la hoja: arriba del todo, pegajoso y por encima del
   papel, para que ni el desplazamiento ni el pie de página lo tapen. */
.pi-visor .pi-cargando {
    position: sticky;
    top: 0;
    z-index: 6;
    margin: 0 auto var(--space-3);
    max-width: 22rem;
    padding: var(--space-3);
    border-radius: 10px;
    background: var(--c-bg, #fff);
    box-shadow: 0 2px 10px rgba(0, 0, 0, .15);
}
.hoja-lienzo.pi-papel { position: relative; }
.pi-imprimible {
    position: absolute;
    border: 1px dashed rgba(0, 0, 0, 0.28);
    pointer-events: none;
}
.pi-lona {
    position: absolute;
    inset: 0;
    cursor: grab;
    touch-action: none;   /* el dedo mueve el PLANO, no la página */
}
.pi-lona.arrastrando { cursor: grabbing; }
/* RONDA 70 §1.2 — con «✋ Mover etiquetas» el cursor dice que ahí se coge algo. */
.pi-lona.moviendo-etiq { cursor: crosshair; }
.pi-lona.moviendo-etiq.arrastrando { cursor: grabbing; }

/* RONDA 73 §1.2 — LA BARRA DE LO SELECCIONADO. Va PEGADA ABAJO del papel y por encima de la
   lona: con el dedo, una barrita fija que dice qué hay cogido y da las dos acciones es mucho
   más manejable que una pulsación larga sobre un rótulo de 5 x 3 px. */
.pi-selbar {
    position: absolute;
    left: 50%;
    bottom: 10px;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: var(--esp-chip, 6px);
    max-width: calc(100% - 20px);
    padding: 5px 10px;
    border-radius: 22px;
    background: var(--c-card, #fff);
    border: 1px solid var(--c-border-strong, #c9ccd1);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
    z-index: 3;
}
.pi-selbar .chip { min-height: 32px; padding: 2px 10px; font-size: 0.85rem; }
/* ⚠ RONDA 75 §7 — LA TRAMPA DE SIEMPRE, otra vez: el `display: flex` de la clase GANA al
   `[hidden] { display: none }` del navegador, así que la barra de selección se veía
   SIEMPRE (y con ella un «↺ Automática» que, sin nada seleccionado, no hacía nada — la
   queja literal del usuario: «¿qué hace el botón Automática? Parece que no hace nada»).
   El repo ya había parcheado el mismo par para `.pi-barra`; aquí faltaba. */
.pi-selbar[hidden] { display: none; }
.pi-selbar .chip[hidden] { display: none; }
/* El «↺ Automática» sobre una etiqueta que YA la coloca la herramienta se queda a la vista
   pero apagado: decir por qué no se puede pulsar enseña más que esconderlo. */
.pi-selbar .chip[disabled] { opacity: 0.5; cursor: default; }
.pi-sel-nombre {
    font-size: 0.82rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--c-text, #22252b);
}
/* Flota abajo a la derecha y SE QUEDA a la vista al desplazar la hoja (sticky dentro del
   propio visor, que es el que scrollea). */
.pi-vista {
    position: sticky;
    bottom: var(--esp-fila, 12px);
    display: flex;
    width: fit-content;
    align-items: center;
    gap: var(--esp-chip, 6px);
    margin: var(--esp-chip, 6px) var(--esp-fila, 12px) 0 auto;
    padding: 4px 8px;
    border-radius: 22px;
    background: var(--c-card, #fff);
    border: 1px solid var(--c-border-strong, #c9ccd1);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.16);
    z-index: 2;
}
.pi-vista .chip { min-height: 30px; padding: 2px 9px; font-size: 0.85rem; }
.pi-vista-txt { font-size: 0.8rem; white-space: nowrap; color: var(--c-text-muted, #6b7280); }

@media print {
    .pi-barra, .pi-avisos, .pi-vista, .pi-imprimible, .pi-lona,
    .pi-selbar { display: none !important; }
}

/* ============================================================
   #/sso-escritorio — arranque del chat abierto desde el ERP
   (WebView2). Pantalla de paso: solo se ve un instante mientras
   se canjea el ticket, o si algo falla.
   ============================================================ */
.sso-page {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: var(--space-5);
}

.sso-card {
    width: 100%;
    max-width: 420px;
    text-align: center;
}

.sso-card h2 {
    margin: 0 0 var(--space-3);
}

.sso-card .btn {
    margin-top: var(--space-3);
}

.sso-nota {
    color: var(--c-text-muted);
    font-size: 0.9em;
}

/* ============================================================
   🖥 RONDA 56 — APROVECHAR LA PANTALLA GRANDE
   Dos mejoras que solo actúan en tablet/escritorio (umbral 900 px, definido
   en `js/core/pantallaAncha.js`). En móvil NO cambia ni una regla: todo lo de
   aquí cuelga de clases que el JS solo pone cuando toca.
   ============================================================ */

/* ── §1. La barra de herramientas del plano, en COLUMNA a la izquierda ─────
   Se activa cuando la pantalla es ancha Y el plano es más alto que ancho
   (regla literal del usuario). El JS mueve los MISMOS nodos a dos columnas;
   aquí solo se les cambia la dirección. */
.plano-split {
    /* ⚠ `flex-basis: 0` NO es cosmético: con `auto`, la columna de herramientas
       (21 botones = 1.136 px de alto en el modo rellenar) pasaba su altura de
       contenido al padre y estiraba la PÁGINA ENTERA — en 1366×768 el lienzo
       salía de 1.136 px con la ventana de 768 y aparecía scroll de documento.
       Con base 0 la fila se queda con el hueco que hay y la columna scrollea
       por dentro, que es lo que se pedía. */
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    align-items: stretch;
}
.plano-lateral {
    /* Ancho fijo y modesto: lo que se le quita al lienzo por los lados se le
       devuelve multiplicado por arriba (las dos filas de barras eran ~100 px
       de ALTO en un plano que lo que necesita es alto). */
    flex: 0 0 var(--plano-lateral-ancho, 236px);
    width: var(--plano-lateral-ancho, 236px);
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* Scroll VERTICAL propio: mismo patrón de desbordamiento que la botonera
       horizontal de la r37, pero en el otro eje. */
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--c-card);
    border-right: 1px solid var(--c-border-strong);
}
.plano-cuerpo {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
/* Los chips del visor, apilados y a lo ancho de la columna. */
.plano-lateral .plano-chips {
    flex: 0 0 auto;
    flex-direction: column;
    align-items: stretch;
    overflow-x: visible;
    gap: var(--esp-chip);
    padding: var(--esp-fila) var(--esp-chip);
}
.plano-lateral .plano-chips .chip {
    width: 100%;
    justify-content: flex-start;
    text-align: left;
    white-space: normal;
}
/* La botonera del RELLENAR: título arriba y los modos uno debajo de otro. */
.plano-lateral .pr-banner {
    flex: 0 0 auto;
    padding: var(--esp-fila) var(--esp-chip);
    border-bottom: 0;
    border-top: 1px solid var(--c-border);
}
.plano-lateral .pr-banner-btns {
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
    overflow-x: visible;
    gap: var(--esp-chip);
}
.plano-lateral .pr-banner-modos {
    flex-direction: column;
    align-items: stretch;
    flex: 0 0 auto;
    gap: var(--esp-chip);
}
.plano-lateral .pr-btn {
    width: 100%;
    text-align: left;
    white-space: normal;
}
/* El chevron, 🖨 y ✕ siguen juntos, pero en fila que envuelve: en 236 px caben
   dos y el tercero baja, en vez de estirarse cada uno a lo ancho. */
.plano-lateral .pr-banner-fijos {
    margin-left: 0;
    flex-wrap: wrap;
    width: 100%;
}
.plano-lateral .pr-banner-fijos .pr-btn { width: auto; }
.plano-lateral .pr-banner-tit { width: 100%; flex-wrap: wrap; }

/* ── §1bis (RONDA 62). El vendedor elige dónde va el menú ──────────────────
   El botón vive en la fila de chips y viaja con ella. Arriba se va al final de
   la fila (empujado con `margin-left:auto` para que no se confunda con los
   chips de contenido); en el lateral es un botón más de la columna, a lo ancho
   como sus vecinos, así que el margen se anula. */
.plano-chips .chip-menu-pos { margin-left: auto; flex: 0 0 auto; white-space: nowrap; }
.plano-lateral .plano-chips .chip-menu-pos { margin-left: 0; }
/* La LEYENDA del rellenar, acotada al lienzo cuando el menú está al lado. Con la columna
   puesta en una pantalla pequeña el lienzo se queda en ~200 px de ancho y la leyenda, que
   se mide en % de ese ancho, se estiraba a 1.562 px y SACABA SCROLL A LA PÁGINA ENTERA
   (medido en 375×812 con el plano 18463). Acotada, se queda dentro y scrollea ella. Solo en
   el layout de columna: sin él la leyenda nunca ha desbordado. */
.plano-con-lateral .pr-leyenda {
    max-height: calc(100% - 16px);
    overflow-y: auto;
    overscroll-behavior: contain;
}
/* Desde la r62 el lateral puede pedirse también en pantalla PEQUEÑA (la
   elección del vendedor manda). Ahí los 236 px fijos se comerían el lienzo, así
   que por debajo del umbral la columna se mide en porcentaje. A partir de
   900 px, `min()` devuelve siempre los 236 px de la r56: nada cambia en tablet
   ni en escritorio. */
@media (max-width: 899px) {
    .plano-lateral { --plano-lateral-ancho: min(236px, 45vw); }
}

/* ── §2. Chat y tablón a DOS PANELES ──────────────────────────────────────
   El panel es FIJO y cuelga del <body> (no de #app), así sobrevive a la
   navegación entre conversaciones/anuncios. #app se aparta con un padding,
   exactamente igual que con el índice lateral de la visita. */
.pm-panel {
    position: fixed;
    top: 0; left: 0; bottom: 0;
    width: var(--pm-ancho, 320px);
    z-index: 70;                       /* sobre la topbar (10), bajo sheets (1000) */
    display: flex;
    flex-direction: column;
    background: var(--c-card);
    border-right: 1px solid var(--c-border-strong);
    padding-top: env(safe-area-inset-top);
}
#app.pm-con-panel { padding-left: var(--pm-ancho, 320px); }

.pm-lista {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}
/* Cabecera propia del panel: ahí viven las acciones de la LISTA (🔍 ➕ 🛡 en el
   chat; ➕ 👥 ⚙ 📰 en el tablón), porque la barra superior de la pantalla ya es
   la del detalle abierto. */
.pm-cab {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    padding: var(--esp-fila) var(--esp-borde);
    background: var(--c-bg-soft);
    border-bottom: 1px solid var(--c-border);
}
.pm-cab-tit { flex: 1 1 auto; min-width: 0; font-weight: 800; font-size: 0.95rem; }
.pm-cab-acciones { flex: 0 0 auto; display: flex; align-items: center; gap: var(--esp-chip); }

/* Lo abierto en la zona principal, marcado en la lista. */
.pm-item-activo {
    background: var(--c-primary-soft);
    border-left: 3px solid var(--c-primary);
}

/* Zona principal sin nada abierto todavía. */
.pm-vacio {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--esp-fila);
    padding: var(--esp-borde);
    color: var(--c-text-soft);
    text-align: center;
}
.pm-vacio-ico { font-size: 2.6rem; opacity: 0.7; }
.pm-vacio-txt { max-width: 34ch; }

/* ═══════════════════════════════════════════════════════════════════════════
   RONDA 63 §1 — PANEL LATERAL DE ELEMENTOS LINEALES
   ═══════════════════════════════════════════════════════════════════════════
   Espejo del panel maestro de la r56 pero por la DERECHA: la izquierda ya es de
   la barra de herramientas del plano (r62), así que las dos columnas no se
   pelean por el mismo hueco. Fijo y colgado del <body>, con #app apartado por
   padding — igual que `.pm-panel`. */
.pl-panel {
    position: fixed;
    top: 0; right: 0; bottom: 0;
    width: var(--pl-ancho, 340px);
    z-index: 70;                       /* sobre la topbar (10), bajo las sheets (1000) */
    display: flex;
    flex-direction: column;
    background: var(--c-card);
    border-left: 1px solid var(--c-border-strong);
    padding-top: env(safe-area-inset-top);
}
#app.pl-con-panel { padding-right: var(--pl-ancho, 340px); }

.pl-cab {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    padding: var(--esp-fila) var(--esp-borde);
    background: var(--c-bg-soft);
    border-bottom: 1px solid var(--c-border);
}
.pl-cab-tit { flex: 1 1 auto; min-width: 0; font-weight: 800; font-size: 0.95rem; }
.pl-x { flex: 0 0 auto; }

.pl-cuerpo {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--esp-fila) var(--esp-borde);
}
/* Los campos largos (el desplegable de material trae nombres de 500 px) no pueden
   sacar scroll horizontal al panel: misma lección que la ronda 39 con las hojas. */
.pl-cuerpo { overflow-x: hidden; }
.pl-cuerpo .form-input { min-width: 0; max-width: 100%; }

.pl-pie {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    padding: var(--esp-fila) var(--esp-borde);
    padding-bottom: calc(var(--esp-fila) + env(safe-area-inset-bottom));
    background: var(--c-bg-soft);
    border-top: 1px solid var(--c-border);
}
.pl-estado { flex: 1 1 auto; min-width: 0; font-size: 0.8rem; }
.pl-guardar { flex: 0 0 auto; }

/* La fila que el dedo/ratón está señalando, marcada a juego con el resaltado del
   lienzo: el vendedor tiene que poder unir las dos con la mirada. */
.pl-cuerpo .pr-trazo.pl-fila-activa {
    background: var(--c-primary-soft);
    box-shadow: inset 3px 0 0 var(--c-primary);
}
/* La marca de SUPERPUESTO (§2), sobre la fila implicada. */
.pl-solape, .pr-trazo .pl-solape {
    display: block;
    margin-top: 4px;
    font-size: 0.78rem;
    color: var(--c-warn-fg);
    background: var(--c-warn-bg);
    border-left: 3px solid var(--c-warn-line);
    border-radius: var(--radius);
    padding: 4px 8px;
}

/* Pantalla estrecha CON el panel abierto (girar la tablet): NO se cierra —cerrar
   descartaría el borrador—, se comporta como un cajón sobre el lienzo. */
@media (max-width: 899px) {
    .pl-panel {
        --pl-ancho: min(340px, 86vw);
        box-shadow: -6px 0 18px rgba(0, 0, 0, 0.22);
    }
    #app.pl-con-panel { padding-right: 0; }
}
/* Y en pantallas medianas, que el panel no se coma el lienzo. */
@media (min-width: 900px) and (max-width: 1100px) {
    .pl-panel { --pl-ancho: min(340px, 32vw); }
    #app.pl-con-panel { padding-right: min(340px, 32vw); }
}

/* ============================================================
   🧭 RONDA 60 — EL ASISTENTE GUIADO del rellenar plano
   Un nodo hermano de la fila de chips y de la botonera: con el menú ARRIBA sale
   como una banda bajo la barra superior, y con el menú EN COLUMNA (r62) viaja a
   la columna de herramientas y se pinta como su primera tarjeta.
   El panel de lineales de la r63 vive a la DERECHA, así que no se pisan.
   ============================================================ */
.pr-asistente {
    background: var(--c-card);
    border-bottom: 1px solid var(--c-border);
    /* La barra de acento a la izquierda es la señal de «estás en modo guiado»: se ve de
       reojo sin leer nada y desaparece al cerrar la guía. */
    border-left: 3px solid var(--c-primary);
    padding: var(--esp-fila) var(--esp-borde);
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.pra-cab {
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    flex-wrap: wrap;
}
.pra-tit { font-weight: 700; color: var(--c-text); }
.pra-prog { font-variant-numeric: tabular-nums; font-size: 0.85em; }
/* Los diez puntos. Son botones de verdad (se puede saltar a cualquier paso: el asistente
   sugiere, no impone) y caben en una línea también en móvil. */
.pra-puntos {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}
.pra-punto {
    width: 26px;
    height: 26px;
    flex: 0 0 auto;
    border-radius: 50%;
    border: 1px solid var(--c-border-strong);
    background: var(--c-bg);
    color: var(--c-text-soft);
    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}
.pra-punto.es-hecho {
    /* El verde del tema. El fondo va en rgba del mismo verde (el patrón que ya usa
       `.material-info.ok`): nada de `color-mix`, que no lo tiene ningún iOS anterior al
       16.2 y esta app se usa en iPad viejos. */
    background: rgba(44, 143, 63, 0.14);
    border-color: var(--c-success);
    color: var(--c-success);
}
.pra-punto.es-saltado { opacity: 0.55; text-decoration: line-through; }
.pra-punto.es-actual { outline: 2px solid var(--c-primary); outline-offset: 1px; color: var(--c-text); }
/* El título del paso ES el botón de plegar su explicación: se pinta como texto, no como
   botón, para que la tarjeta siga leyéndose de corrido. */
.pra-paso {
    font-weight: 700;
    color: var(--c-text);
    background: none;
    border: 0;
    padding: 0;
    text-align: left;
    font-size: inherit;
    font-family: inherit;
    cursor: pointer;
    width: 100%;
}
.pra-que { color: var(--c-text-soft); font-size: 0.9em; }
.pra-estado {
    font-size: 0.9em;
    font-weight: 600;
    color: var(--c-text-soft);
}
.pra-estado.es-hecho { color: var(--c-success); }
.pra-estado.es-saltado { color: var(--c-text-soft); font-weight: 400; font-style: italic; }
.pra-acciones {
    display: flex;
    align-items: center;
    gap: var(--esp-chip);
    flex-wrap: wrap;
}
/* Las dos flechas viven en la cabecera, pegadas al «Paso N de 10». */
.pra-flechas { display: inline-flex; gap: 4px; margin-left: auto; }
.pra-flecha { min-width: 30px; padding-left: 8px; padding-right: 8px; font-weight: 700; }
.pra-x { margin-left: 0; flex: 0 0 auto; }
.pra-fin { font-weight: 700; color: var(--c-success); }
.pra-nota { font-size: 0.85em; }

/* En la COLUMNA de herramientas (r62): tarjeta a lo ancho, con los botones apilados como
   sus vecinos de la columna y sin borde inferior (lo pone la barra de chips de debajo). */
.plano-lateral .pr-asistente {
    flex: 0 0 auto;
    border-bottom: 0;
    border-top: 0;
    padding: var(--esp-fila) var(--esp-chip);
}
/* En la columna: los dos botones de abajo a lo ancho, uno debajo de otro. */
.plano-lateral .pra-acciones { flex-direction: column; align-items: stretch; width: 100%; }
.plano-lateral .pra-accion,
.plano-lateral .pra-saltar { width: 100%; }

/* ══════════════════════════════════════════════════════════════════════════
   📋 DESPIECE PARA EL PRESUPUESTO (ronda 86 §1)
   La hoja que lista lo que hay que pedir. Tablas compactas y con scroll propio:
   son 6 columnas y en un móvil no caben — el que scrollea es el bloque, nunca
   la página (norma de la ronda 39).
   ══════════════════════════════════════════════════════════════════════════ */
.pr-despiece { display: flex; flex-direction: column; gap: var(--esp-fila); }
.pr-desp-sec { border-top: 1px solid var(--c-border); padding-top: var(--esp-fila); }
.pr-desp-sec > summary {
    cursor: pointer;
    font-weight: 600;
    font-size: 0.9rem;
    padding: 2px 0;
}
.pr-desp-sec[open] > summary { margin-bottom: var(--esp-fila); }
.pr-desp-tabla {
    display: block;
    overflow-x: auto;
    width: 100%;
    border-collapse: collapse;
    font-size: 0.8rem;
}
.pr-desp-tabla th, .pr-desp-tabla td {
    border: 1px solid var(--c-border);
    padding: 3px 6px;
    text-align: left;
    vertical-align: top;
    white-space: nowrap;
}
.pr-desp-tabla th { background: var(--c-bg-alt, rgba(127, 127, 127, 0.12)); font-weight: 600; }
.pr-desp-tabla td:first-child, .pr-desp-tabla th:first-child { text-align: right; }
.pr-desp-res { display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: 0.85rem; }

/* ── RONDA 92 §3 — LEYENDA DEL PINCEL DE APERTURA DEL VISOR 3D ─────────────────────────────
   Va al final de la misma barra deslizante que los chips de apertura: no es interactiva, solo
   dice qué significa cada color del tinte. Se lee de un vistazo y no roba sitio a los chips
   porque comparte con ellos el scroll horizontal de `.pr-paleta`. */
.p3d-leyenda-ap {
    flex: 0 0 auto;
    align-self: center;
    display: flex;
    align-items: center;
    gap: var(--esp-chip, 6px);
    white-space: nowrap;
}
.p3d-ley-it {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.72rem;
    color: var(--c-text-soft);
}
.p3d-ley-it .pr-mat-swatch { width: 12px; height: 12px; }

/* ── RONDA 93 §1 — LA MUESTRA DE UNA LÍNEA en la leyenda del pincel de apertura ────────────
   El tinte se enseña con un cuadrado (`.pr-mat-swatch`); una LÍNEA no es un relleno, así que
   su muestra es una raya —lo que de verdad se ve en el plano—. */
.p3d-ley-linea {
    display: inline-block;
    width: 16px;
    height: 3px;
    border-radius: 1px;
}

/* ===== RONDA 95a — VISTA POR ALZADOS =====================================================
   Capa propia ENCIMA del lienzo de la planta, dentro del mismo `.plano-canvas-wrap` (que ya
   es `position: relative`). La planta se queda debajo intacta: salir es quitar este nodo. */
.alz-capa {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    background: var(--c-card, #fff);
    min-height: 0;
}
.alz-pestanas {
    display: flex;
    gap: 8px;
    align-items: center;
    padding: 6px var(--space-2);
    border-bottom: 1px solid var(--c-border);
    overflow-x: auto;
    flex: 0 0 auto;
}
.alz-islas {
    font-size: 12px;
    color: var(--c-text-muted, #666);
    white-space: nowrap;
}
/* RONDA 95b §1 — las pestañas de los ALZADOS VIRTUALES se distinguen de las de pared: son
   una decisión del vendedor (una línea que él ha trazado), no una pared del diseño. */
.alz-pestanas .alz-virtual { border-style: dashed; }
.alz-pestanas .alz-nuevo { white-space: nowrap; border-style: dashed; opacity: 0.9; }
.alz-pestanas .alz-nuevo[disabled] { opacity: 0.45; pointer-events: none; }
/* RONDA 95b §2 — la previsualización de la HOJA DE ALZADOS: el papel, centrado y encogido
   para verlo entero (el `transform` es de pantalla; el papel y el PDF van a escala real). */
.alz-lienzo { display: block; overflow: auto; padding: 8px 0; }
.alz-marco { transform-origin: top left; }
/* El lienzo se come todo lo que sobra y NUNCA scrollea la página: el zoom es suyo. */
.alz-canvas {
    flex: 1 1 0;
    min-height: 0;
    width: 100%;
    display: block;
    touch-action: none;
    background: #fff;
}
.alz-barra {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 6px var(--space-2);
    border-top: 1px solid var(--c-border);
    flex: 0 0 auto;
    max-height: 34vh;
    overflow-y: auto;
}
/* La paleta salta a su propia línea y scrollea sola: con muchos materiales no puede empujar
   los botones de modo fuera de la pantalla (lección de la botonera de la r62). */
.alz-paleta {
    flex: 1 0 100%;
    display: flex;
    gap: 6px;
    overflow-x: auto;
    padding-top: 4px;
}
.alz-pie {
    padding: 4px var(--space-2) 6px;
    font-size: 12px;
    color: var(--c-text-muted, #666);
    border-top: 1px solid var(--c-border);
    flex: 0 0 auto;
}

/* ── RONDA 104 §2 — «Cambios del relleno respecto a la visita N» ─────────────────────────
   Bloque informativo dentro de «🗺 Cambios en el plano» de la toma de datos. Va en la banda
   ÁMBAR de la app (los mismos tokens que el resto de avisos: `--c-warn-bg`/`--c-warn-fg`,
   con contraste medido en claro y en oscuro) porque es exactamente eso: «mira lo que ha
   cambiado». No es un campo editable, así que no lleva ni borde de input ni fondo de tarjeta. */
.td-cambios-relleno {
    margin-top: var(--space-4);
    padding: var(--space-3);
    background: var(--c-warn-bg);
    color: var(--c-warn-fg);
    border-radius: var(--radius-sm);
    border-left: 4px solid var(--c-warn-fg);
}
.td-cambios-relleno .td-sub {
    font-weight: 700;
    margin-bottom: var(--space-2);
}
.td-cr-grupo {
    margin-top: var(--space-2);
    font-weight: 600;
    font-size: 0.875rem;
    opacity: 0.85;
}
.td-cr-lista {
    margin: var(--space-1) 0 0 var(--space-4);
    padding: 0;
}
.td-cr-lista li {
    margin: 2px 0;
    font-size: 0.9375rem;
    overflow-wrap: anywhere;
}
/* En la hoja del mueble del rellenar, los cambios se pintan con la misma banda. */
.pr-cambios {
    margin: var(--space-2) 0;
    padding: var(--space-2) var(--space-3);
    background: var(--c-warn-bg);
    color: var(--c-warn-fg);
    border-radius: var(--radius-sm);
    border-left: 4px solid var(--c-warn-fg);
}
.pr-cambios .pr-cambios-tit { font-weight: 700; margin-bottom: 2px; }
.pr-cambios ul { margin: 0 0 0 var(--space-4); padding: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   RONDA 123 §2 — «🗂 Elegir del catálogo» dentro del editor de un electro.
   Es el catálogo METIDO EN UNA HOJA: la rejilla necesita alto propio y scroll,
   porque la hoja no puede crecer sin fin (el fondo tiene que seguir viéndose).
   ═══════════════════════════════════════════════════════════════════════════ */
.td-art-catalogo { width: 100%; margin-bottom: var(--space-2); }
.sel-cat-chips, .sel-fam-chips { margin-bottom: var(--space-2); }
.sel-cat-filtros {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-2); margin: var(--space-2) 0;
}
.sel-cat-filtros .marcas-multi { flex: 1 1 160px; min-width: 0; }
.sel-cat-grid {
    max-height: 46vh; overflow-y: auto; overflow-x: hidden;
    /* Rejilla más apretada que la del catálogo grande: aquí se elige, no se enseña. */
    grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
    padding-right: 2px;
}
@media (min-width: 900px) {
    .sel-cat-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); max-height: 52vh; }
}
.sel-cat-pie {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-2); margin-top: var(--space-2);
}

/* RONDA 135 §2 — LA BANDA DE «ESTA VISITA NO LA PUEDES EDITAR».
   Vive en la cabecera del modo «Rellenar plano» y dura toda la sesión (no es un toast: es una
   condición, no un suceso). Hereda el marco ámbar de .pr-aviso; lo único que añade es que
   ocupe la fila entera y no se pegue a los botones de debajo. */
.pr-solo-lectura {
    width: 100%;
    margin: var(--esp-fila) 0 0;
    background: var(--c-warn-bg);
    line-height: 1.3;
}
