/* =====================================================================
   Twenty Design System — capa de tema global
   Se carga DESPUÉS de tabler.css y global.css: reescribe los tokens de
   Tabler para que todas las vistas hereden la escala tipográfica, la
   paleta y los radios del design system de Twenty.
   ===================================================================== */

:root {
    /* ---------- Escala de grises (Twenty) ---------- */
    --tw-gray-0: #ffffff;
    --tw-gray-10: #fbfbfb;
    --tw-gray-15: #f9f9f9;
    --tw-gray-20: #f6f6f6;
    --tw-gray-25: #f1f1f1;
    --tw-gray-30: #ededed;
    --tw-gray-35: #e3e3e3;
    --tw-gray-40: #d6d6d6;
    --tw-gray-45: #cbcbcb;
    --tw-gray-50: #b3b3b3;
    --tw-gray-55: #999999;
    --tw-gray-60: #818181;
    --tw-gray-65: #666666;
    --tw-gray-70: #4b4b4b;
    --tw-gray-75: #3d3d3d;
    --tw-gray-80: #333333;
    --tw-gray-85: #292929;
    --tw-gray-90: #1b1b1b;
    --tw-gray-100: #000000;

    /* ---------- Fondos ---------- */
    --tw-bg-primary: var(--tw-gray-0);
    --tw-bg-secondary: var(--tw-gray-15);
    --tw-bg-tertiary: var(--tw-gray-20);
    --tw-bg-quaternary: var(--tw-gray-25);
    --tw-bg-transparent-light: rgba(0, 0, 0, 0.04);
    --tw-bg-transparent-lighter: rgba(0, 0, 0, 0.02);
    --tw-bg-danger: #fff2f2;
    --tw-bg-overlay: rgba(27, 27, 27, 0.08);

    /* ---------- Bordes ---------- */
    --tw-border-light: var(--tw-gray-25);
    --tw-border-medium: var(--tw-gray-30);
    --tw-border-strong: var(--tw-gray-35);

    /*
       ---------- Texto ----------

       Los tonos secundarios no salen de la escala de grises, y es a propósito.

       La escala está pensada para bordes y fondos, donde un gris claro es lo que
       se quiere. Usada para texto sobre blanco daba lo siguiente: `gray-50`
       —el tono de `.tw-meta`, que es el que lleva media pantalla— **2.10 : 1**, y
       `gray-45` —el de `.tw-empty-value`— **1.62 : 1**. Los mismos dos papeles en
       el tema oscuro dan 4.55 : 1 y 2.80 : 1: el oscuro estaba medido y el claro
       había heredado la escala sin mirar, así que la misma clase se leía en uno y
       se desvanecía en el otro.

       Ahora cada tono lleva su contraste sobre blanco al lado. El de `light` está
       en 4.5 : 1 porque es donde vive el texto que se lee de verdad —la línea de
       datos de una tarjeta, la nota de un paso—; `extra-light` se queda por
       debajo a propósito, porque es para lo que **no** hay que leer: un valor
       vacío, un separador, un fondo de campo.
    */
    --tw-font-primary: #171717; /* 17.9 : 1 */
    --tw-font-secondary: #5c5c5c; /* 7.0 : 1 */
    --tw-font-tertiary: #6b6b6b; /* 5.5 : 1 */
    --tw-font-light: #737373; /* 4.8 : 1 · el mínimo para texto normal */
    --tw-font-extra-light: #8f8f8f; /* 3.1 : 1 · sólo para lo que no se lee */
    --tw-font-inverted: var(--tw-gray-0);

    /* ---------- Acentos ---------- */
    --tw-accent: #1961ed;
    --tw-accent-hover: #144ec0;
    --tw-accent-secondary: rgba(25, 97, 237, 0.08);
    --tw-accent-tertiary: rgba(25, 97, 237, 0.04);

    /* ---------- Relleno de un botón de acción ----------
       Aparte de los `--tw-tag-*-fg`: el verde de un chip se lee sobre el
       fondo pálido del chip, y el de un botón tiene que sostener texto
       blanco encima. En oscuro los dos van en direcciones contrarias —el
       chip aclara, el botón se mantiene saturado— y con un solo token uno
       de los dos siempre sale ilegible. */
    --tw-solid-green: #1f7a45;
    --tw-solid-red: #b32020;
    --tw-font-on-solid: #ffffff;

    /* ---------- Tags / chips ---------- */
    /* El acento, con el nombre de un tono de tag. No es un color nuevo: es el
       mismo `--tw-accent` de siempre, y existe para que lo que pinta tramos y
       leyendas por su nombre de tono —`tw-etapas-seg--accent` y el punto de
       `leyendaDeEtapas`— pueda nombrarlo igual que a los demás. Sin esto, el
       tramo «en este estimado» salía del acento y su punto de la leyenda no
       salía de ningún sitio. */
    --tw-tag-accent-fg: var(--tw-accent);
    --tw-tag-green-bg: #d7f5e1;
    --tw-tag-green-fg: #1f7a45;
    --tw-tag-turquoise-bg: #d0f4ee;
    --tw-tag-turquoise-fg: #17786c;
    --tw-tag-sky-bg: #d6eefb;
    --tw-tag-sky-fg: #10618f;
    --tw-tag-blue-bg: #dbe5fe;
    --tw-tag-blue-fg: #1b4bc4;
    --tw-tag-purple-bg: #e4ddfb;
    --tw-tag-purple-fg: #5232a8;
    --tw-tag-pink-bg: #fbddf2;
    --tw-tag-pink-fg: #a02a7f;
    --tw-tag-red-bg: #fbdbdb;
    --tw-tag-red-fg: #b32020;
    --tw-tag-orange-bg: #fde6d3;
    --tw-tag-orange-fg: #a85410;
    --tw-tag-yellow-bg: #fff3d0;
    --tw-tag-yellow-fg: #8a6100;
    --tw-tag-gray-bg: var(--tw-gray-30);
    --tw-tag-gray-fg: var(--tw-gray-70);

    /* ---------- Tipografía ---------- */
    --tw-font-family:
        'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    --tw-font-size-xxs: 0.625rem; /* 10px */
    --tw-font-size-xs: 0.6875rem; /* 11px */
    --tw-font-size-sm: 0.75rem; /* 12px */
    --tw-font-size-md: 0.8125rem; /* 13px */
    --tw-font-size-lg: 1rem; /* 16px */
    --tw-font-size-xl: 1.25rem; /* 20px */
    --tw-font-size-xxl: 1.5rem; /* 24px */

    --tw-weight-regular: 400;
    --tw-weight-medium: 500;
    --tw-weight-semibold: 600;

    /* ---------- Espaciado y formas ---------- */
    --tw-spacing: 4px;
    --tw-radius-xs: 2px;
    --tw-radius-sm: 4px;
    --tw-radius-md: 8px;
    --tw-radius-lg: 12px;
    --tw-radius-pill: 50px;

    --tw-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.04), 0 0 4px rgba(0, 0, 0, 0.08);
    --tw-shadow-strong: 2px 4px 16px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.04);

    /* ---------- Medidas de layout ---------- */
    --tw-sidebar-width: 236px;
    --tw-row-height: 32px;
    --tw-record-panel-width: 400px;
    --tw-record-rail-width: 44px; /* la ficha plegada: lo que queda a la vista */
    --tw-screen-gutter: 8px; /* respiro contra el borde de la pantalla */
    --tw-settings-column: 960px; /* lo que se lee, acotado: ver `.tw-settings-column` */
    --tw-kanban-column-width: 284px;
    --tw-icon-size: 16px; /* icono de fila, campo y menú */
    --tw-icon-size-sm: 14px; /* icono dentro de un botón o chip */
    --tw-tab-height: 38px;
    --tw-tab-gap: 8px; /* separación entre pestañas y padding lateral */

    /* ---------- Movimiento ---------- */
    --tw-transition: 0.1s ease;

    /* Una sola curva para todo el sistema. `ease-out` de salida rápida: el
       movimiento acompaña al clic en lugar de hacerse notar. Las duraciones son
       cortas a propósito —esto es una herramienta de trabajo, no una
       presentación— y todas se anulan juntas con `prefers-reduced-motion`. */
    --tw-ease: cubic-bezier(0.2, 0, 0.13, 1);
    --tw-duration-fast: 0.12s;
    --tw-duration-base: 0.22s;
    --tw-duration-slow: 0.36s;

    /* Esqueletos de carga */
    --tw-skeleton-base: var(--tw-gray-25);
    --tw-skeleton-shine: var(--tw-gray-15);
}

/* =====================================================================
   0. Modo oscuro
   ---------------------------------------------------------------------
   El tema se decide en `html[data-bs-theme]`, que escribe el guion de
   `theme.js` antes de pintar. Se usa el mismo atributo que Bootstrap para
   que los componentes de Tabler, DataTables y flatpickr cambien con él;
   lo que sigue reescribe sus valores, porque el oscuro que trae Tabler es
   un azul marino (#151f2c) y aquí la base es negro neutro.

   La escala de grises se invierte de punta a punta, así que todo lo que
   ya usa `--tw-gray-NN` —un botón oscuro, un tooltip— se da la vuelta
   solo. Lo que NO se invierte son los fondos: en claro la tarjeta es más
   clara que la página, y en oscuro tiene que ser más clara también, no
   más oscura. Por eso los `--tw-bg-*` se declaran a mano debajo.
   ===================================================================== */

[data-bs-theme='dark'] {
    color-scheme: dark;

    /* ---------- Escala de grises, invertida ---------- */
    --tw-gray-0: #000000;
    --tw-gray-10: #0a0a0a;
    --tw-gray-15: #0f0f0f;
    --tw-gray-20: #141414;
    --tw-gray-25: #1a1a1a;
    --tw-gray-30: #222222;
    --tw-gray-35: #2b2b2b;
    --tw-gray-40: #363636;
    --tw-gray-45: #414141;
    --tw-gray-50: #4f4f4f;
    --tw-gray-55: #666666;
    --tw-gray-60: #818181;
    --tw-gray-65: #9b9b9b;
    --tw-gray-70: #b5b5b5;
    --tw-gray-75: #c4c4c4;
    --tw-gray-80: #d2d2d2;
    --tw-gray-85: #dfdfdf;
    --tw-gray-90: #ececec;
    --tw-gray-100: #ffffff;

    /* ---------- Fondos ---------- */
    /* `secondary` es el fondo de página y del sidebar: negro. `primary` es
       la superficie que se levanta encima —tarjeta, menú, modal—. */
    --tw-bg-primary: #121212;
    --tw-bg-secondary: #000000;
    --tw-bg-tertiary: #1c1c1c;
    --tw-bg-quaternary: #262626;
    --tw-bg-transparent-light: rgba(255, 255, 255, 0.06);
    --tw-bg-transparent-lighter: rgba(255, 255, 255, 0.03);
    --tw-bg-danger: #2a1414;
    --tw-bg-overlay: rgba(0, 0, 0, 0.65);

    /* ---------- Bordes ---------- */
    --tw-border-light: #1f1f1f;
    --tw-border-medium: #2a2a2a;
    --tw-border-strong: #3a3a3a;

    /* ---------- Texto ---------- */
    --tw-font-primary: #ededed;
    --tw-font-secondary: #b5b5b5;
    --tw-font-tertiary: #9b9b9b;
    --tw-font-light: #7d7d7d;
    --tw-font-extra-light: #5c5c5c;
    /* Lo que va encima del acento sigue siendo blanco: el acento no cambia. */
    --tw-font-inverted: #ffffff;

    /* ---------- Acentos ---------- */
    /* Mismo azul, un punto más claro: el #1961ed de claro se apaga sobre
       negro y deja los enlaces por debajo del contraste legible. */
    --tw-accent: #3b7ff5;
    --tw-accent-hover: #5c96f7;
    --tw-accent-secondary: rgba(59, 127, 245, 0.16);
    --tw-accent-tertiary: rgba(59, 127, 245, 0.08);

    /* El relleno del botón se satura, no se aclara: aclararlo lo dejaba con
       texto blanco sobre menta. El texto encima sigue siendo blanco. */
    --tw-solid-green: #2b9c53;
    --tw-solid-red: #ca3c3c;
    --tw-font-on-solid: #ffffff;

    /* ---------- Tags / chips ---------- */
    /* Se invierte la pareja: el fondo pasa a ser el tinte apagado y el
       texto, el tono claro. Los `-fg` son además los colores con los que
       se dibujan las gráficas, así que tienen que leerse sobre negro. */
    --tw-tag-green-bg: #10291b;
    --tw-tag-green-fg: #6bd898;
    --tw-tag-turquoise-bg: #0d2a27;
    --tw-tag-turquoise-fg: #5cd4c4;
    --tw-tag-sky-bg: #0d2634;
    --tw-tag-sky-fg: #68c0ec;
    --tw-tag-blue-bg: #121f3d;
    --tw-tag-blue-fg: #7ea6f5;
    --tw-tag-purple-bg: #1e1836;
    --tw-tag-purple-fg: #b098f5;
    --tw-tag-pink-bg: #2e1327;
    --tw-tag-pink-fg: #ee8cce;
    --tw-tag-red-bg: #2e1515;
    --tw-tag-red-fg: #ef8b8b;
    --tw-tag-orange-bg: #2e1e0d;
    --tw-tag-orange-fg: #eda45f;
    --tw-tag-yellow-bg: #2b2409;
    --tw-tag-yellow-fg: #e2c257;
    --tw-tag-gray-bg: var(--tw-gray-30);
    --tw-tag-gray-fg: var(--tw-gray-70);

    /* ---------- Sombras ---------- */
    /* Sobre negro una sombra no se ve: lo que separa la superficie de la
       página es el propio salto de fondo, y la sombra solo profundiza. */
    --tw-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.5), 0 0 4px rgba(0, 0, 0, 0.6);
    --tw-shadow-strong: 2px 4px 16px rgba(0, 0, 0, 0.7), 0 2px 4px rgba(0, 0, 0, 0.5);

    /* Esqueletos: el brillo tiene que ir por encima de la base, no debajo. */
    --tw-skeleton-base: #161616;
    --tw-skeleton-shine: #242424;
}

/* =====================================================================
   1. Mapeo sobre los tokens de Tabler / Bootstrap
   ===================================================================== */

:root,
[data-bs-theme='light'],
[data-bs-theme='dark'] {
    --tblr-font-sans-serif: var(--tw-font-family);
    --tblr-headings-font-family: var(--tw-font-family);
    --tblr-body-font-family: var(--tw-font-family);
    --tblr-font-size-base: var(--tw-font-size-md);
    --tblr-body-font-size: var(--tw-font-size-md);
    --tblr-body-font-weight: var(--tw-weight-regular);
    --tblr-body-line-height: 1.5;

    --tblr-body-color: var(--tw-font-primary);
    --tblr-body-color-rgb: 23, 23, 23;
    --tblr-emphasis-color: var(--tw-font-primary);
    --tblr-secondary-color: var(--tw-font-secondary);
    --tblr-tertiary-color: var(--tw-font-tertiary);
    --tblr-muted: var(--tw-font-tertiary);

    --tblr-body-bg: var(--tw-bg-secondary);
    --tblr-bg-surface: var(--tw-bg-primary);
    --tblr-bg-surface-secondary: var(--tw-bg-secondary);
    --tblr-bg-surface-tertiary: var(--tw-bg-tertiary);
    --tblr-bg-surface-dark: var(--tw-gray-90);

    --tblr-border-color: var(--tw-border-medium);
    --tblr-border-color-translucent: var(--tw-border-medium);
    --tblr-border-width: 1px;
    --tblr-border-radius: var(--tw-radius-sm);
    --tblr-border-radius-sm: var(--tw-radius-xs);
    --tblr-border-radius-lg: var(--tw-radius-md);
    --tblr-border-radius-xl: var(--tw-radius-lg);
    --tblr-border-radius-pill: var(--tw-radius-pill);

    --tblr-primary: var(--tw-accent);
    --tblr-primary-rgb: 25, 97, 237;
    --tblr-link-color: var(--tw-accent);
    --tblr-link-hover-color: var(--tw-accent-hover);

    --tblr-secondary: var(--tw-gray-65);
    --tblr-dark: var(--tw-gray-90);
    --tblr-light: var(--tw-gray-20);

    --tblr-box-shadow: var(--tw-shadow-strong);
    --tblr-box-shadow-sm: var(--tw-shadow-light);
    --tblr-box-shadow-lg: var(--tw-shadow-strong);

    --tblr-font-weight-normal: var(--tw-weight-regular);
    --tblr-font-weight-medium: var(--tw-weight-medium);
    --tblr-font-weight-bold: var(--tw-weight-semibold);
}

/* Lo que Tabler solo define en su bloque oscuro y el mapa de arriba no toca.
   Sin esto se cuela su azul marino en los fondos suaves de los badges, en el
   hover de las filas y en los campos de formulario. */
[data-bs-theme='dark'] {
    --tblr-body-color-rgb: 237, 237, 237;
    --tblr-body-bg-rgb: 0, 0, 0;
    --tblr-emphasis-color: var(--tw-gray-100);
    --tblr-emphasis-color-rgb: 255, 255, 255;
    --tblr-secondary-bg: var(--tw-bg-tertiary);
    --tblr-tertiary-bg: var(--tw-bg-primary);

    --tblr-bg-forms: var(--tw-bg-primary);
    --tblr-active-bg: var(--tw-bg-tertiary);
    --tblr-disabled-color: var(--tw-font-extra-light);
    --tblr-border-color-active: var(--tw-border-strong);
    --tblr-border-dark-color: var(--tw-border-strong);
    --tblr-code-color: var(--tw-font-primary);
    --tblr-code-bg: var(--tw-bg-tertiary);
    --tblr-btn-color: var(--tw-font-on-solid);

    /* Fondos suaves de badge (`bg-*-lt`): los del design system, no los de
       Tabler. Cada uno con el mismo tinte que su chip `tw-tag`. */
    --tblr-primary-lt: var(--tw-tag-blue-bg);
    --tblr-blue-lt: var(--tw-tag-blue-bg);
    --tblr-azure-lt: var(--tw-tag-sky-bg);
    --tblr-indigo-lt: var(--tw-tag-purple-bg);
    --tblr-purple-lt: var(--tw-tag-purple-bg);
    --tblr-pink-lt: var(--tw-tag-pink-bg);
    --tblr-red-lt: var(--tw-tag-red-bg);
    --tblr-danger-lt: var(--tw-tag-red-bg);
    --tblr-orange-lt: var(--tw-tag-orange-bg);
    --tblr-warning-lt: var(--tw-tag-orange-bg);
    --tblr-yellow-lt: var(--tw-tag-yellow-bg);
    --tblr-lime-lt: var(--tw-tag-green-bg);
    --tblr-green-lt: var(--tw-tag-green-bg);
    --tblr-success-lt: var(--tw-tag-green-bg);
    --tblr-teal-lt: var(--tw-tag-turquoise-bg);
    --tblr-cyan-lt: var(--tw-tag-sky-bg);
    --tblr-info-lt: var(--tw-tag-sky-bg);
    --tblr-secondary-lt: var(--tw-tag-gray-bg);
    --tblr-muted-lt: var(--tw-tag-gray-bg);
    --tblr-light-lt: var(--tw-bg-tertiary);
    --tblr-dark-lt: var(--tw-bg-quaternary);
    --tblr-github-lt: var(--tw-bg-quaternary);
}

/* =====================================================================
   2. Base
   ===================================================================== */

body {
    font-family: var(--tw-font-family);
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
    background-color: var(--tw-bg-secondary);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4,
h5,
h6,
.h1,
.h2,
.h3,
.h4,
.h5,
.h6 {
    font-family: var(--tw-font-family);
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-semibold);
    letter-spacing: -0.01em;
}

h1,
.h1 {
    font-size: var(--tw-font-size-xxl);
}
h2,
.h2 {
    font-size: var(--tw-font-size-xl);
}
h3,
.h3 {
    font-size: var(--tw-font-size-lg);
}
h4,
.h4 {
    font-size: var(--tw-font-size-md);
}
h5,
.h5,
h6,
.h6 {
    font-size: var(--tw-font-size-sm);
}

/* ---------------------------------------------------------------------
   Escala de encabezados semántica.
   Usar SIEMPRE estas clases en lugar de h1..h6 sueltos, para que el mismo
   tipo de contenido tenga el mismo tamaño en todo el sistema.
   --------------------------------------------------------------------- */

/* Título de la barra superior de una pantalla (breadcrumb). */
.tw-page-title {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
    letter-spacing: 0;
    margin: 0;
    min-width: 0;
}

/* La cola tenue del breadcrumb. Cambia el color y el peso, **no el tamaño**:
   sigue siendo el título, y encogerla lo parte en dos alturas.

   Por eso vive aquí dentro y no suelta. Fuera de un título la clase de texto
   secundario es `.tw-meta` —tenue y a doce— y `.tw-muted` no pinta nada. */
.tw-page-title .tw-muted,
.tw-page-title .tw-sep {
    color: var(--tw-font-light);
    font-weight: var(--tw-weight-regular);
}

/* Nombre de un registro concreto: una pieza, un cliente, una orden.
   Debe verse igual en todas las pantallas de detalle. */
.tw-record-title {
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    letter-spacing: -0.01em;
    margin: 0;
    word-break: break-word;
}

/* Encabezado de una sección o bloque dentro de la pantalla. */
.tw-section-title {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    margin: 0;
}

/* Apostilla del encabezado: la salvedad que la sección tiene que confesar
   ("se enseñan 500 de 3.412"). Va apagada y en peso normal porque no es parte
   del título; va **dentro** del título porque callarla convertiría una tabla
   recortada en una tabla que parece completa. */
.tw-section-title .tw-section-note {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
    margin-left: 8px;
}

/* Pregunta de un diálogo de confirmación ("¿Eliminar pieza?"). */
.tw-dialog-title {
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    margin: 0 0 4px;
}

/* Encabezado de un grupo del menú lateral. */
.tw-nav-section-title {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-light);
    letter-spacing: 0;
    text-transform: none;
    margin: 0;
}

/* Etiqueta de subsección / grupo de campos. */
.tw-subsection-title {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
    margin: 0;
}

/* Un encabezado nunca debe heredar el tamaño de .form-label */
h1.form-label,
h2.form-label,
h3.form-label,
h4.form-label {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
}

small,
.small {
    font-size: var(--tw-font-size-sm);
}

.text-muted,
.text-secondary {
    color: var(--tw-font-tertiary) !important;
}

a {
    text-decoration: none;
}

hr,
.dropdown-divider {
    border-color: var(--tw-border-light);
    opacity: 1;
}

/* Scrollbars discretos, como en Twenty */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--tw-gray-40) transparent;
}

*::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

*::-webkit-scrollbar-track {
    background: transparent;
}

*::-webkit-scrollbar-thumb {
    background-color: var(--tw-gray-40);
    border-radius: var(--tw-radius-pill);
    border: 2px solid transparent;
    background-clip: content-box;
}

*::-webkit-scrollbar-thumb:hover {
    background-color: var(--tw-gray-50);
    background-clip: content-box;
}

/* =====================================================================
   3. Sidebar
   ===================================================================== */

/* Sin línea divisoria: el sidebar comparte fondo con la pantalla y lo que
   separa es el margen de la tarjeta de contenido. */
.navbar-vertical.navbar-expand-lg {
    width: var(--tw-sidebar-width);
    padding-right: 0 !important;
    background-color: var(--tw-bg-secondary);
    border-right: 0;
    box-shadow: none;
}

/* Tabler desplaza el contenido 15rem (240px) fijos; el sidebar mide 236px,
   y esa diferencia dejaba una franja vacía a su derecha. */
.navbar-vertical.navbar-expand-lg ~ .page-wrapper,
.navbar-vertical.navbar-expand-lg + .page-wrapper {
    margin-left: var(--tw-sidebar-width);
}

.navbar-vertical .navbar-brand {
    background-color: var(--tw-bg-secondary) !important;
    border-bottom: 0 !important;
    padding: 0 var(--tw-spacing) !important;
}

.navbar-vertical .navbar-brand h2,
.navbar-vertical .navbar-brand .fs-4 {
    font-size: var(--tw-font-size-md) !important;
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

.navbar-vertical .navbar-nav {
    font-weight: var(--tw-weight-medium);
    gap: 1px;
}

/* La especificidad tiene que igualar la de Tabler, que fija su propio padding */
.navbar-vertical.navbar-expand-lg .navbar-nav .nav-link {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
    border-radius: var(--tw-radius-sm);
    padding: 4px 8px !important;
    margin: 0 6px;
    min-height: 28px;
    transition: background-color 0.1s ease;
}

.navbar-vertical .navbar-nav .nav-link:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

/* Caja fija para el icono: así todos los textos arrancan en la misma
   columna y el icono queda centrado respecto a la línea de texto. */
.navbar-vertical .navbar-nav .nav-link .nav-link-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 var(--tw-icon-size);
    width: var(--tw-icon-size);
    height: var(--tw-icon-size);
    color: var(--tw-font-tertiary);
    margin-right: 0;
}

/* El gap del enlace ya separa icono y texto: el me-2 del markup sobra */
.navbar-vertical .navbar-nav .nav-link .ti {
    display: block;
    font-size: var(--tw-icon-size) !important;
    line-height: 1;
    margin-right: 0 !important;
}

.navbar-vertical .navbar-nav .nav-link .nav-link-title {
    line-height: 20px;
}

/* ── El renglón de la pantalla abierta ─────────────────────────────────────
 *
 * Fondo gris sólido, sin barra lateral azul.
 *
 * El selector es `aria-current="page"`, que es lo que el servidor escribe desde
 * el 9 de septiembre de 2026: un mecanismo en lugar de dos, y el mismo atributo
 * que un lector de pantalla anuncia solo. La clase `.current` sigue aquí porque
 * la escriben las pestañas de ficha, que son otra cosa. */
/* `.current` la escribía un guion del armazón que se retiró el 9 de septiembre
 * de 2026; se queda porque las pestañas de ficha usan la misma clase y esta
 * regla les da su fondo. En el menú lateral ya no la pone nadie. */
.navbar-vertical .navbar-nav .nav-link.current::before {
    background: var(--tw-bg-quaternary) !important;
    inset: 0 !important;
    border-radius: var(--tw-radius-sm) !important;
}

/* El fondo va en el propio enlace y no en un `::before`, que es como estaba
   escrito el de `.current`: ese pseudo-elemento se pinta sólo si algo le da
   `content`, y en la barra lateral no lo tiene, así que la regla de al lado
   lleva tiempo sin dibujar nada. Aquí no hace falta ninguno —el enlace ya es
   una caja con su propio radio— y así el resalte no depende de una herencia
   de Tabler que puede irse en la próxima actualización. */
.navbar-vertical.navbar-expand-lg .navbar-nav .nav-link[aria-current='page'] {
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-primary);
}

.navbar-vertical .navbar-nav .nav-link.current {
    color: var(--tw-font-primary);
}

/* Y su icono, que es lo único que queda del renglón cuando el menú se pliega:
   sin esto, en el riel el resalte era un cuadro gris con el icono del mismo
   gris apagado que los demás. */
.navbar-vertical .navbar-nav .nav-link[aria-current='page'] .nav-link-icon {
    color: var(--tw-font-primary);
}

.navbar-expand-lg .nav-item.active:after,
.navbar-vertical .nav-item.active:after {
    display: none !important;
}

/* Encabezados de grupo del menú ("Operación", "Administración") */
.navbar-vertical .navbar-nav .nav-link .text-uppercase {
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-medium);
    letter-spacing: 0.02em;
    color: var(--tw-font-light) !important;
}

/* No son navegables: no deben reaccionar al puntero como un enlace */
.navbar-vertical .navbar-nav div.nav-link {
    margin-top: 12px;
    cursor: default;
}

.navbar-vertical .navbar-nav div.nav-link:hover {
    background-color: transparent;
}

.navbar-vertical .navbar-nav .nav-link.disabled {
    color: var(--tw-font-extra-light);
    opacity: 1;
    cursor: not-allowed;
}

/* Un módulo apagado sin más se lee como "no tengo permiso". Estos no existen
   todavía, así que se dicen con el mismo distintivo que las pestañas. */
.navbar-vertical .navbar-nav .nav-link.disabled .nav-link-title::after {
    content: 'Pronto';
    display: inline-block;
    margin-left: 6px;
    font-size: var(--tw-font-size-xxs);
    font-weight: var(--tw-weight-medium);
    line-height: 1;
    letter-spacing: 0.02em;
    padding: 3px 5px;
    border-radius: var(--tw-radius-xs);
    background-color: var(--tw-tag-gray-bg);
    color: var(--tw-tag-gray-fg);
    vertical-align: middle;
}

/* =====================================================================
   4. Superficie de página
   ===================================================================== */

/* El shell mide exactamente una pantalla: nunca hay scroll de ventana.
   Lo que scrollea es el contenedor interno de cada vista.
   El contenido de la vista se estira con flex — un `height: 100%` no
   resolvería aquí, porque .page-wrapper es a su vez un flex item de .page.
   El `overflow: auto` queda como red de seguridad para las vistas que
   todavía no acotan su propio alto. */
.page {
    height: 100dvh;
    max-height: 100dvh;
    overflow: hidden;
}

.page-wrapper {
    display: flex;
    flex-direction: column;
    height: 100dvh;
    max-height: 100dvh;
    /* Un flex item nace con min-height:auto y el contenido lo estira: sin
       esto el wrapper crece más allá de la pantalla y reaparece el scroll. */
    min-height: 0;
    overflow: auto;
    background-color: var(--tw-bg-secondary);
}

/* El contenido de la vista ocupa la pantalla completa. Modales y offcanvas
   son position:fixed, así que no participan de este reparto. */
.page-wrapper > * {
    flex: 1 1 auto;
    min-height: 0;
}

/* Sin esto los hijos flex se niegan a encogerse y desbordan su contenedor */
.page-wrapper .flex-fill,
.page-wrapper .flex-grow-1 {
    min-height: 0;
}

.page-header,
.page-body {
    background-color: transparent;
}

.container-xl,
.container-fluid {
    max-width: none;
}

/* Respiro entre el contenido y el borde de la pantalla. Si cambia, hay que
   actualizar BOTTOM_GAP en adjustScrollY() (public/scripts/global.js). */
.page-wrapper .container-xl.p-3,
.page-wrapper .container-fluid.p-3 {
    padding-bottom: var(--tw-screen-gutter) !important;
}

/* En papel no hay pantalla que acotar.

   El shell mide 100dvh y recorta con `overflow`, que es justo lo que se quiere
   en la pantalla y lo contrario de lo que hace falta en la impresora: **toda
   hoja imprimible salía de una sola página**, con lo que no cabía en el primer
   viewport perdido sin aviso. Se vio el 17 de septiembre de 2026 en la hoja de
   revisión de recorridos —«1 página», cortada a la mitad de la lista— y le
   pasaba igual al packing list y al gafete, que se sirven con el mismo armazón.

   Se desmonta el reparto entero: alto automático, sin recorte y en bloque, para
   que el contenido fluya de una página a la siguiente en vez de repartirse como
   items de un flex que sólo sabe de una pantalla. Es el mismo arreglo que ya
   llevaba el armazón de kiosco en `station.css`. */
@media print {
    html,
    body {
        height: auto !important;
        max-height: none !important;
        overflow: visible !important;
    }

    .page,
    .page-wrapper {
        display: block !important;
        height: auto !important;
        max-height: none !important;
        min-height: 0 !important;
        overflow: visible !important;
    }

    .page-wrapper > * {
        flex: none !important;
        min-height: 0 !important;
    }
}

/* =====================================================================
   5. Cards
   ===================================================================== */

.card {
    background-color: var(--tw-bg-primary);
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    box-shadow: none;
}

.card-header {
    background-color: transparent;
    border-bottom: 1px solid var(--tw-border-light);
    min-height: 40px;
    padding: 0 12px;
}

.card-title {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    margin-bottom: 0;
}

.card-footer {
    background-color: transparent;
    border-top: 1px solid var(--tw-border-light);
}

/* =====================================================================
   6. Tablas
   ===================================================================== */

.table {
    --tblr-table-bg: var(--tw-bg-primary);
    --tblr-table-border-color: var(--tw-border-light);
    --tblr-table-hover-bg: var(--tw-bg-transparent-lighter);
    --tblr-table-striped-bg: var(--tw-bg-secondary);
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
    margin-bottom: 0;
}

.table > thead > tr > th,
.table thead th,
#products-table > thead > tr > th {
    background-color: var(--tw-bg-primary) !important;
    color: var(--tw-font-tertiary) !important;
    font-size: var(--tw-font-size-sm) !important;
    font-weight: var(--tw-weight-medium) !important;
    text-transform: none;
    letter-spacing: 0;
    height: var(--tw-row-height);
    padding: 0 8px !important;
    border-bottom: 1px solid var(--tw-border-light);
    white-space: nowrap;
    vertical-align: middle;
}

.table > thead > tr > th .ti,
.table thead th .ti {
    font-size: 0.875rem !important;
    color: var(--tw-font-light);
}

.table > tbody > tr > td,
.table td {
    height: var(--tw-row-height);
    padding: 0 8px !important;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
    border-bottom: 1px solid var(--tw-border-light);
    vertical-align: middle;
}

/* La celda que sí parte en dos renglones.

   `td { white-space: nowrap }` —de global.css— está para que un texto largo no
   ensanche la tabla, y en una tabla de ancho automático no lo consigue: la
   columna crece hasta el contenido más largo, así que el recorte con puntos
   suspensivos que lo acompaña no llega a hacerse nunca. Donde la celda lleva
   una lista —los números de parte de un nesteo, doce en el renglón más largo—
   el resultado era una columna de trescientos y pico píxeles que empujaba las
   otras nueve fuera del panel.

   Lo que hace falta ahí no es cortar, es **partir**: el ancho del `th` vuelve a
   mandar y lo que no cabe pasa al renglón de abajo, que se lee entero. Los
   puntos suspensivos se descartaron por lo mismo que en la ficha —ver
   `.tw-field-label`—: medio número de parte no se puede leer. */
.table td.tw-cell-wrap {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

/* Cuentas de una celda como texto de color, no como insignias: «10 sin
   certificado · 1 sin país». Una píldora por cuenta partía la fila en dos
   renglones y la tabla se leía como un montón de etiquetas; así cabe en uno y
   la píldora de la fila queda para el estado. El punto lo pone el CSS, para que
   quien arma la celda no tenga que acordarse del separador. */
.tw-flags {
    display: inline;
}

.tw-flag {
    font-weight: var(--tw-weight-medium);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.tw-flag + .tw-flag::before {
    content: '·';
    margin: 0 6px;
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
}

/* Y cuando la primera marca del grupo es la cifra principal —«58 de 310 pza» en
   la celda de avance del listado—, el punto también va detrás de ella. Sin esto
   salía «58 de 310 pza3 de 6 cajas», todo pegado. Sólo dentro de `.tw-flags`:
   `.tw-num` se usa en media aplicación y ahí no separa nada. */
.tw-flags .tw-num + .tw-flag::before {
    content: '·';
    margin: 0 6px;
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
}

/* Y cuando una de las marcas la trae un componente que ya se envuelve solo
   —`banderaDePrioridad` devuelve su propio `.tw-flags`—, los dos grupos se leen
   como uno: el punto también va entre ellos. Sin esto, «Propuesta de Empaque
   Prioridad alta» salía pegado y parecía una sola frase. */
.tw-flag + .tw-flags::before,
.tw-flags + .tw-flag::before {
    content: '·';
    margin: 0 6px;
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
}

.tw-flag .ti {
    margin-right: 3px;
    font-size: 0.95em;
    vertical-align: -1px;
}

.tw-flag--red {
    color: var(--tw-tag-red-fg);
}

.tw-flag--orange {
    color: var(--tw-tag-orange-fg);
}

.tw-flag--yellow {
    color: var(--tw-tag-yellow-fg);
}

.tw-flag--green {
    color: var(--tw-tag-green-fg);
}

.tw-flag--quieto {
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-tertiary);
}

/* La prisa, pegada a la identidad de la fila · en el mismo renglón.
 *
 * Iba en una línea propia debajo del folio, y eso costaba una línea: en el
 * listado de órdenes la fila con bandera medía 40 px y la de al lado 32. En una
 * tabla de seiscientas, lo primero que se ve de las urgentes no es que corran
 * prisa: es que son más altas que las demás, y el ojo lee eso como un desorden
 * de la tabla antes que como un dato.
 *
 * Así que va detrás del nombre, en el mismo renglón y un punto más pequeña, que
 * es exactamente lo que ya hacía la torre con `.plano-orden-prisa`. La fila no
 * cambia de alto y todas las de la tabla miden lo mismo.
 *
 * `inline-block` para que el margen de la izquierda no se coma el espacio en
 * blanco anterior, y `nowrap` para que «Prioridad alta» no se parta en dos. */
.tw-prisa {
    display: inline-block;
    margin-left: 6px;
    font-size: 11px;
    white-space: nowrap;
}

/* Y la prisa dicha con un icono · para una lista apretada de números de parte.
 *
 * El contenido de una caja son seis pastillas en dos renglones, y dentro de
 * cada una el rótulo «Prioridad alta» le gana al número de parte, que es lo que
 * se está leyendo. Así que ahí la prisa es una llama del color del tono, con el
 * rótulo en el `title`, delante del número y no detrás: el ojo barre la columna
 * por la izquierda y lo que tiene que encontrar es cuál de los seis.
 *
 * `vertical-align` y no `baseline`: el glifo de Tabler cuelga un punto por
 * debajo de la caja de la letra y sin esto el número de parte queda alto. */
.tw-prisa-marca {
    margin-right: 2px;
    font-size: 14px;
    vertical-align: -1px;
}

.tw-prisa-marca--red {
    color: var(--tw-tag-red-fg);
}

.tw-prisa-marca--orange {
    color: var(--tw-tag-orange-fg);
}

/* Y la celda de la matriz de reparto que cruza con una partida urgente.
 *
 * Es el único sitio de esa pantalla donde la prisa tiene dónde apoyarse: la
 * columna es una orden entera y el renglón un número de parte, pero su cruce es
 * una partida. El tinte va en toda la celda —nunca en una barra al costado— y
 * es el del fondo de la pastilla roja, que junto a un campo de captura no puede
 * competir con el propio número que se teclea. */
.lk-celda-prisa {
    background-color: var(--tw-tag-red-bg);
}

.table.table-bordered > tbody > tr > td,
.table.table-bordered > thead > tr > th {
    border-right: 1px solid var(--tw-border-light);
    border-left: 0;
}

.table.table-bordered > tbody > tr > td:last-child,
.table.table-bordered > thead > tr > th:last-child {
    border-right: 0;
}

.table-hover > tbody > tr:hover > * {
    background-color: var(--tw-bg-transparent-lighter);
    --tblr-table-accent-bg: transparent;
    color: var(--tw-font-primary);
}

.table thead .sorting:after,
.table thead .sorting_asc:after,
.table thead .sorting_desc:after {
    color: var(--tw-font-extra-light);
}

/* ---------------------------------------------------------------------
   DataTables. El alto del cuerpo scrolleable lo calcula adjustScrollY()
   en global.js midiendo el espacio real hasta el borde de la pantalla:
   el `scrollY` de la config es sólo el valor inicial.
   --------------------------------------------------------------------- */
.dataTables_wrapper,
.dt-container {
    font-size: var(--tw-font-size-md);
}

.dataTables_scrollHead,
.dt-scroll-head {
    background-color: var(--tw-bg-primary);
}

/* Pie de tabla: una sola barra inferior, como el footer de conteo de Twenty */
.dataTables_info,
.dataTables_length,
.dataTables_filter {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

/* Pie de conteo, como la barra inferior de Twenty ("Count all 217") */
.dataTables_info {
    display: flex;
    align-items: center;
    gap: 16px;
    min-height: 32px;
    padding: 0 8px;
    border-top: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-primary);
    color: var(--tw-font-tertiary);
    font-size: var(--tw-font-size-sm);
}

.dataTables_info .tw-count {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 6px;
    border-radius: var(--tw-radius-sm);
    color: var(--tw-font-tertiary);
}

.dataTables_info .tw-count strong {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

.dataTables_info .tw-count-label {
    color: var(--tw-font-light);
}

.bottom-table {
    min-height: 36px;
    padding: 0 12px !important;
    margin-top: 0 !important;
    border-top: 1px solid var(--tw-border-light);
    align-items: center;
}

.bottom-table .dataTables_paginate {
    padding-top: 0 !important;
}

.dataTables_paginate .page-link {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    border: 0;
    border-radius: var(--tw-radius-sm) !important;
    min-width: 26px;
    padding: 3px 8px;
}

.dataTables_paginate .page-link:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.page-link.active,
.active > .page-link {
    background-color: var(--tw-bg-quaternary) !important;
    border-color: transparent !important;
    color: var(--tw-font-primary) !important;
    border-radius: var(--tw-radius-sm) !important;
}

/* =====================================================================
   7. Botones
   =====================================================================
   Un botón se elige por lo que hace, no por cómo se ve:

     .btn                      acción neutra: Cancelar, Cerrar, Ver, Editar,
                               Imprimir, Filtrar, Volver, Anterior
     .btn.btn-primary          la que cierra la pantalla o el modal: Guardar,
                               Crear, Confirmar. Una sola por contexto
     .btn.btn-ghost            acción de fondo, casi siempre sólo icono,
                               dentro de una fila o una tarjeta
     .btn.btn-outline-danger   abre algo destructivo o peligroso: Eliminar,
                               Dar de baja, Cerrar sesiones, Revocar
     .btn.btn-danger           lo ejecuta, ya dentro de la confirmación
     .btn.btn-outline-warning  degrada algo pero se puede deshacer: Cuarentena

   El verde no es un color de botón: significa «salió bien» y vive en los
   chips de estado. `.btn-success` se pinta igual que la primaria para que
   cualquier resto —o cualquier pantalla nueva escrita de memoria— no vuelva
   a abrir la brecha entre Guardar verde y Guardar negro.

   Lo mismo con las variantes de Tabler que no tienen sentido aquí
   (`btn-secondary`, `btn-light`, `btn-outline-primary`, `btn-outline-secondary`,
   `btn-link`): todas caen en la neutra en vez de sacar el azul crudo del
   framework, que en oscuro se sale del tema.

   Las estaciones no usan esta escala: tienen `.st-btn` en station.css, con
   la primaria en azul porque a un metro de distancia el negro no se separa
   del fondo de la terminal. */

/* ── UN BOTÓN MIDE 28 PÍXELES ────────────────────────────────────────────
 *
 * Todos. El de texto, el de icono, el primario, el de una barra y el de un
 * modal. Hasta el 20 de septiembre de 2026 medían tres cosas distintas y las
 * tres estaban a un píxel de las otras, que es la peor distancia: no se ve de
 * frente y se ve en cuanto hay dos juntos en la misma barra.
 *
 *     .btn                28.57 px   (719 botones en las pantallas)
 *     .btn.btn-sm         29.50 px   (148)
 *     .btn.btn-icon       28.00 px   (57)
 *
 * SON DOS AVERÍAS, Y NINGUNA SE VE LEYENDO LA REGLA:
 *
 *   · **`min-height` cuenta el borde.** Con `box-sizing: border-box` —que es
 *     lo que pone Tabler— los 28 de aquí abajo son la caja entera, borde
 *     incluido. Pero el contenido ya sumaba 18.57 de renglón + 8 de relleno +
 *     2 de borde = 28.57, así que el `min-height` no llegaba nunca a aplicarse
 *     y el número escrito no era el número dibujado. El de icono sí lo
 *     alcanzaba —no tiene relleno— y por eso ése, y sólo ése, medía 28.
 *
 *   · **`btn-sm` se colaba por el renglón.** El relleno y el cuerpo de letra
 *     de `.btn-sm` ya los pisa esta regla, así que la clase parecía inerte;
 *     pero Tabler le pone además `--tblr-btn-line-height: 1.5` en otro bloque
 *     que esta hoja no tocaba. Un píxel más de renglón, un píxel más de botón.
 *     De ahí que «Distribuir en cajas» y «Nueva caja» —las dos con `btn-sm`—
 *     no cuadraran con el «Acciones» de la barra de arriba, que no la lleva.
 *
 * Se arregla fijando el renglón en píxeles: 18 + 8 + 2 = 28 exactos, y el
 * `min-height` pasa a ser lo que dice que es. Al ser una declaración de esta
 * hoja —que carga después de Tabler— gana también a `btn-sm` y a `btn-lg`, así
 * que las dos dejan de mover la caja: un `btn-sm` de más o de menos en una
 * plantilla ya no descuadra la barra donde cae.
 *
 * La medida del dedo va aparte, al final de la hoja: ahí el botón crece a 44 y
 * el de una barra a 38, y también por dónde está y no por la clase que lleve.
 */
.btn {
    --tblr-btn-font-size: var(--tw-font-size-md);
    --tblr-btn-font-weight: var(--tw-weight-medium);
    --tblr-btn-padding-y: 4px;
    --tblr-btn-padding-x: 8px;
    --tblr-btn-border-radius: var(--tw-radius-sm);
    --tblr-btn-line-height: 18px;
    min-height: 28px;
    box-shadow: none;
    border-color: var(--tw-border-strong);
    color: var(--tw-font-primary);
    background-color: var(--tw-bg-primary);
    transition:
        background-color 0.1s ease,
        border-color 0.1s ease;
}

.btn:hover {
    background-color: var(--tw-bg-tertiary);
    border-color: var(--tw-border-strong);
    color: var(--tw-font-primary);
}

.btn:focus,
.btn:focus-visible {
    box-shadow: none;
    outline: none;
}

.btn-primary {
    background-color: var(--tw-gray-90);
    border-color: var(--tw-gray-90);
    color: var(--tw-gray-0);
}

.btn-primary:hover,
.btn-primary:active,
.btn-primary:focus {
    background-color: var(--tw-gray-80) !important;
    border-color: var(--tw-gray-80) !important;
    color: var(--tw-gray-0) !important;
}

/**
 * El botón de una opción de `elegir`: icono, rótulo y una línea de detalle.
 *
 * LO QUE SE VEÍA ANTES, y era feo con motivo: el detalle iba en un `form-hint`
 * con `d-block`, pero un `.btn` es `inline-flex` centrado, así que `d-block` no
 * bajaba de renglón —lo convertía en otro elemento de la misma fila— y el
 * resultado era «Seguir imprimiendo Te quedan 8 por sacar» todo seguido, con el
 * icono empujado fuera y el detalle en el gris de un formulario, que sobre el
 * azul de la primaria no se lee.
 *
 * Se arregla con estructura y no con más clases de utilidad: el botón pasa a
 * tener un icono a la izquierda y un bloque de texto en columna. El detalle
 * hereda el color del rótulo y baja la opacidad, que es lo que lo hace legible
 * lo mismo sobre azul que sobre el fondo neutro.
 *
 * El segundo selector es para el kiosco: allí `.modal .btn` centra su contenido
 * y esto tiene que alinearse a la izquierda, porque son dos frases de largos
 * distintos y centradas no se leen en columna.
 */
.tw-choice,
.modal .btn.tw-choice {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    text-align: left;
    padding: 12px 16px;
    height: auto;
}

.tw-choice-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    min-width: 0;
}

.tw-choice-hint {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular);
    /* Del color del rótulo, no del gris de los formularios: sobre la primaria
       aquél desaparece. */
    color: currentColor;
    opacity: 0.72;
    white-space: normal;
}

/* La lista de resultados del diálogo con buscador.

   Se acota por alto y scrollea sola en vez de dejar crecer el modal: el campo
   de búsqueda tiene que quedarse a la vista mientras se lee el resultado, que
   es lo que permite corregir una letra sin volver a empezar. El tope va en
   `vh` para la tablet —en horizontal y con el teclado abierto queda poca
   pantalla— y en `rem` para el escritorio, y manda el más bajo. */
.tw-choice-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: min(38vh, 22rem);
    overflow-y: auto;
    /* El foco del botón se dibuja fuera del borde: sin este aire se recorta
       contra el borde del contenedor que scrollea. */
    padding: 2px;
    margin-inline: -2px;
}

/* Que la lista se desplace en vez de aplastar los renglones: en una columna
   flexible con alto máximo los hijos encogen por omisión, y con siete opciones
   de dos líneas el texto se salía del recuadro (5-oct-2026, «¿De qué insumo
   es?»). */
.tw-choice-list > * {
    flex-shrink: 0;
}

/* Buscando, sin resultados, o la búsqueda falló. Es el `tw-empty-state` de
   siempre con la altura de un modal: sus 64px de aire mueven el diálogo
   entero cada vez que se teclea una letra. */
.tw-choice-state {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 24px 16px;
    color: var(--tw-font-light);
    text-align: center;
}

.tw-choice-state .ti {
    color: var(--tw-font-extra-light);
}

/* ── La opción que se lee entera · un radio con su explicación ─────────────

   Los diálogos de «Por decidir» pintaban sus salidas con `.tw-item-row`, que es
   el renglón de 32 px de una columna lateral: `nowrap` y puntos suspensivos. La
   explicación no se recortaba —el bloque de texto no tenía `min-width: 0`— sino
   que se salía del diálogo por la derecha, y la mitad de la frase quedaba
   pintada encima del fondo de la página.

   Aquí el radio se alinea con la primera línea, el texto envuelve, y la elegida
   lleva el borde entero y el fondo del acento: nada de barra a un lado. */
.tw-radio-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* En columnas cuando las opciones son cortas y hay sitio: dos respuestas a la
   misma pregunta se comparan mejor lado a lado. Bajan a una sola por debajo de
   dos columnas de 15rem, sin punto de corte que mantener. */
.tw-radio-list--cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.tw-radio-option {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin: 0;
    padding: 8px 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    cursor: pointer;
    transition:
        background-color var(--tw-transition),
        border-color var(--tw-transition);
}

.tw-radio-option:hover {
    background-color: var(--tw-bg-transparent-light);
}

.tw-radio-option:has(> input:checked) {
    border-color: var(--tw-accent);
    background-color: var(--tw-accent-secondary);
}

.tw-radio-option > .form-check-input {
    flex: 0 0 auto;
    float: none;
    margin: 2px 0 0;
}

.tw-radio-option-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}

.tw-radio-option-title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
    overflow-wrap: anywhere;
}

.tw-radio-option-hint {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    overflow-wrap: anywhere;
}

/*
 * La opción que se configura adentro · una tarjeta con su ajuste.
 *
 * Nació en el alta de procesos (28 sep 2026): once casillas sueltas con sus
 * ajustes al fondo del diálogo —lecturas por pieza, datos del baño, ciclo
 * mínimo— se marcaban a ciegas. Aquí el ajuste vive dentro de la opción que lo
 * enciende y sólo aparece con ella. El título es un `label for` y no envuelve
 * lo anidado: un clic en un campo de adentro no apaga la opción.
 */
.tw-radio-option:has(> input:disabled) {
    opacity: 0.55;
}

.tw-radio-option:has(> input:disabled) .tw-radio-option-title {
    cursor: not-allowed;
}

.tw-radio-option > .tw-radio-option-text > label.tw-radio-option-title {
    cursor: pointer;
    margin: 0;
}

.tw-option-nested {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--tw-border-light);
}

.tw-option-nested .tw-radio-option {
    background-color: var(--tw-bg-primary);
}

/* El paso a paso fijo al lado del formulario: se ve la consecuencia sin bajar. */
.tw-config-preview {
    position: sticky;
    top: 0;
}

.tw-config-section + .tw-config-section {
    margin-top: 20px;
}

.tw-config-question {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 8px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

.tw-config-question .tw-step-num {
    flex: 0 0 auto;
}

/* El pie de la ficha: lo que se decide va separado de lo que se lee. */
.tw-modal-part {
    margin-top: 4px;
    padding-top: 16px;
    border-top: 1px solid var(--tw-border-light);
}

.tw-modal-part + .tw-modal-part {
    margin-top: 16px;
}

/* Toda la familia «no pasa nada si le picas» aterriza en la neutra. */
.btn-secondary,
.btn-light,
.btn-outline-primary,
.btn-outline-secondary,
.btn-link {
    background-color: var(--tw-bg-primary);
    border-color: var(--tw-border-strong);
    color: var(--tw-font-primary);
}

.btn-secondary:hover,
.btn-light:hover,
.btn-outline-primary:hover,
.btn-outline-secondary:hover,
.btn-link:hover {
    background-color: var(--tw-bg-tertiary);
    border-color: var(--tw-border-strong);
    color: var(--tw-font-primary);
}

/* Verde retirado de los botones: lo que quede se lee como primaria. */
.btn-success {
    background-color: var(--tw-gray-90);
    border-color: var(--tw-gray-90);
    color: var(--tw-gray-0);
}

.btn-success:hover,
.btn-success:active,
.btn-success:focus {
    background-color: var(--tw-gray-80) !important;
    border-color: var(--tw-gray-80) !important;
    color: var(--tw-gray-0) !important;
}

/* Relleno rojo: sólo el botón que consuma el borrado dentro de la
   confirmación. Fuera de un modal siempre va la versión de contorno. */
.btn-danger {
    background-color: var(--tw-solid-red);
    border-color: var(--tw-solid-red);
    color: var(--tw-font-on-solid);
}

.btn-danger:hover,
.btn-danger:active,
.btn-danger:focus {
    background-color: var(--tw-tag-red-fg);
    border-color: var(--tw-tag-red-fg);
    color: var(--tw-font-on-solid);
}

/* Peligroso pero todavía no ejecutado: el color va en el texto, no en el
   relleno, para que no compita con la primaria de la misma pantalla.
   El `-fg` del chip es el que está calibrado para leerse en los dos temas;
   `--tw-solid-red` se apaga demasiado sobre negro. */
.btn-outline-danger,
.btn-warning,
.btn-outline-warning {
    background-color: var(--tw-bg-primary);
    border-color: var(--tw-border-strong);
}

.btn-outline-danger {
    color: var(--tw-tag-red-fg);
}

.btn-warning,
.btn-outline-warning {
    color: var(--tw-tag-orange-fg);
}

.btn-outline-danger:hover {
    background-color: var(--tw-tag-red-bg);
    border-color: var(--tw-tag-red-bg);
    color: var(--tw-tag-red-fg);
}

.btn-warning:hover,
.btn-outline-warning:hover {
    background-color: var(--tw-tag-orange-bg);
    border-color: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
}

.btn-ghost,
.btn-transparent {
    background-color: transparent;
    border-color: transparent;
    color: var(--tw-font-secondary);
}

.btn-ghost:hover,
.btn-transparent:hover {
    background-color: var(--tw-bg-transparent-light);
    border-color: transparent;
    color: var(--tw-font-primary);
}

.btn-icon {
    min-width: 28px;
    padding: 0;
}

/* --- Apagado y trabajando ---------------------------------------------
   Tabler no repinta el botón deshabilitado: lo arma con las variables
   `--tblr-btn-disabled-*`, y su regla `.btn:disabled` (0,2,0) gana a las
   variantes de arriba (0,1,0). El efecto era que la escala entera se caía
   justo al deshabilitar: «Guardar» negro salía azul y `.btn-success` volvía
   a salir verde —los dos colores que esta sección retiró—. Se corrige en el
   origen, dando valor a esas variables, y no repitiendo las reglas con
   `!important`, que sólo movería el problema al siguiente estado.

   Apagado y trabajando no son el mismo estado. El apagado baja la opacidad
   porque todavía no se puede pulsar. El que trabaja acaba de recibir el
   clic: conserva su color y su opacidad —es la única prueba de que el clic
   llegó— y sólo apaga el rótulo para girar el indicador encima. La parte
   animada de `is-busy` vive con las demás transiciones, más abajo. */
.btn {
    --tblr-btn-disabled-bg: var(--tw-bg-primary);
    --tblr-btn-disabled-color: var(--tw-font-primary);
    --tblr-btn-disabled-border-color: var(--tw-border-strong);
    --tblr-btn-disabled-opacity: 0.45;
    /* Color del aro que gira; cada variante lo ajusta a su propio fondo. */
    --tw-btn-spinner: var(--tw-font-secondary);
}

.btn-primary,
.btn-success {
    --tblr-btn-disabled-bg: var(--tw-gray-90);
    --tblr-btn-disabled-color: var(--tw-gray-0);
    --tblr-btn-disabled-border-color: var(--tw-gray-90);
    --tw-btn-spinner: var(--tw-gray-0);
}

.btn-danger {
    --tblr-btn-disabled-bg: var(--tw-solid-red);
    --tblr-btn-disabled-color: var(--tw-font-on-solid);
    --tblr-btn-disabled-border-color: var(--tw-solid-red);
    --tw-btn-spinner: var(--tw-font-on-solid);
}

.btn-secondary,
.btn-light,
.btn-outline-primary,
.btn-outline-secondary,
.btn-link,
.btn-outline-danger,
.btn-warning,
.btn-outline-warning {
    --tblr-btn-disabled-bg: var(--tw-bg-primary);
    --tblr-btn-disabled-color: var(--tw-font-primary);
    --tblr-btn-disabled-border-color: var(--tw-border-strong);
}

.btn-outline-danger {
    --tblr-btn-disabled-color: var(--tw-tag-red-fg);
    --tw-btn-spinner: var(--tw-tag-red-fg);
}

.btn-warning,
.btn-outline-warning {
    --tblr-btn-disabled-color: var(--tw-tag-orange-fg);
    --tw-btn-spinner: var(--tw-tag-orange-fg);
}

.btn-ghost,
.btn-transparent {
    --tblr-btn-disabled-bg: transparent;
    --tblr-btn-disabled-color: var(--tw-font-secondary);
    --tblr-btn-disabled-border-color: transparent;
}

/* En Twenty el icono de un botón es menor que el de una fila de tabla:
   con 16px junto a texto de 13px el botón se ve pesado. */
.btn .ti {
    font-size: 0.875rem !important;
}

.bg-primary-lt {
    background-color: var(--tw-accent-secondary) !important;
    color: var(--tw-accent) !important;
    border-color: transparent !important;
}

.bg-danger-lt {
    background-color: var(--tw-tag-red-bg) !important;
    color: var(--tw-tag-red-fg) !important;
    border-color: transparent !important;
}

.bg-success-lt {
    background-color: var(--tw-tag-green-bg) !important;
    color: var(--tw-tag-green-fg) !important;
    border-color: transparent !important;
}

.bg-blue-lt {
    background-color: var(--tw-tag-blue-bg) !important;
    color: var(--tw-tag-blue-fg) !important;
}

/* Un `btn-ghost text-danger` es la versión de icono de la destructiva: el
   botón no lleva relleno y el color entra por la utilidad de texto. Tabler
   la resuelve con `--tblr-danger`, que nunca se mapeó y sale en su rojo
   crudo —ilegible sobre el negro del tema oscuro—, así que las tres se
   enganchan al `-fg` del chip equivalente, que sí está calibrado. */
.text-danger {
    color: var(--tw-tag-red-fg) !important;
}

.text-success {
    color: var(--tw-tag-green-fg) !important;
}

.text-warning {
    color: var(--tw-tag-orange-fg) !important;
}

/* =====================================================================
   8. Badges / tags
   ===================================================================== */

/* Los `bg-*-lt` de Tabler se arman con tripletas `--tblr-*-lt-rgb`, no con el
   token plano, así que en oscuro hay que reescribir la regla entera: fondo y
   texto salen del mismo par que usa el chip `tw-tag` equivalente. */
[data-bs-theme='dark'] .bg-primary-lt,
[data-bs-theme='dark'] .bg-blue-lt {
    background-color: var(--tw-tag-blue-bg) !important;
    color: var(--tw-tag-blue-fg) !important;
}

[data-bs-theme='dark'] .bg-azure-lt,
[data-bs-theme='dark'] .bg-cyan-lt,
[data-bs-theme='dark'] .bg-info-lt {
    background-color: var(--tw-tag-sky-bg) !important;
    color: var(--tw-tag-sky-fg) !important;
}

[data-bs-theme='dark'] .bg-indigo-lt,
[data-bs-theme='dark'] .bg-purple-lt {
    background-color: var(--tw-tag-purple-bg) !important;
    color: var(--tw-tag-purple-fg) !important;
}

[data-bs-theme='dark'] .bg-pink-lt {
    background-color: var(--tw-tag-pink-bg) !important;
    color: var(--tw-tag-pink-fg) !important;
}

[data-bs-theme='dark'] .bg-red-lt,
[data-bs-theme='dark'] .bg-danger-lt {
    background-color: var(--tw-tag-red-bg) !important;
    color: var(--tw-tag-red-fg) !important;
}

[data-bs-theme='dark'] .bg-orange-lt,
[data-bs-theme='dark'] .bg-warning-lt {
    background-color: var(--tw-tag-orange-bg) !important;
    color: var(--tw-tag-orange-fg) !important;
}

[data-bs-theme='dark'] .bg-yellow-lt {
    background-color: var(--tw-tag-yellow-bg) !important;
    color: var(--tw-tag-yellow-fg) !important;
}

[data-bs-theme='dark'] .bg-green-lt,
[data-bs-theme='dark'] .bg-lime-lt,
[data-bs-theme='dark'] .bg-success-lt {
    background-color: var(--tw-tag-green-bg) !important;
    color: var(--tw-tag-green-fg) !important;
}

[data-bs-theme='dark'] .bg-teal-lt {
    background-color: var(--tw-tag-turquoise-bg) !important;
    color: var(--tw-tag-turquoise-fg) !important;
}

[data-bs-theme='dark'] .bg-secondary-lt,
[data-bs-theme='dark'] .bg-muted-lt,
[data-bs-theme='dark'] .bg-light-lt,
[data-bs-theme='dark'] .bg-dark-lt,
[data-bs-theme='dark'] .bg-github-lt {
    background-color: var(--tw-tag-gray-bg) !important;
    color: var(--tw-tag-gray-fg) !important;
}

/* `text-dark` sobre uno de esos fondos escribiría negro sobre negro. */
[data-bs-theme='dark'] .text-dark {
    color: var(--tw-font-primary) !important;
}

.badge {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    padding: 2px 8px;
    border-radius: var(--tw-radius-pill);
    line-height: 18px;
}

.badge.bg-success {
    background-color: var(--tw-tag-green-bg) !important;
    color: var(--tw-tag-green-fg) !important;
}

.badge.bg-danger {
    background-color: var(--tw-tag-red-bg) !important;
    color: var(--tw-tag-red-fg) !important;
}

.badge.bg-warning {
    background-color: var(--tw-tag-yellow-bg) !important;
    color: var(--tw-tag-yellow-fg) !important;
}

.badge.bg-primary {
    background-color: var(--tw-tag-blue-bg) !important;
    color: var(--tw-tag-blue-fg) !important;
}

.badge.bg-info {
    background-color: var(--tw-tag-sky-bg) !important;
    color: var(--tw-tag-sky-fg) !important;
}

.badge.bg-secondary,
.badge.bg-light,
.badge.bg-dark {
    background-color: var(--tw-tag-gray-bg) !important;
    color: var(--tw-tag-gray-fg) !important;
}

/* Chip de campo tipo Twenty (dominio, etiqueta, relación) */
.tw-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    max-width: 100%;
    padding: 1px 6px;
    font-size: var(--tw-font-size-md);
    line-height: 18px;
    color: var(--tw-font-primary);
    background-color: var(--tw-bg-transparent-light);
    border-radius: var(--tw-radius-sm);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;

    /* ── Y también cuando el chip es un `button` ──────────────────────────

       El chip nació para enlaces y rótulos —hoy son veintiséis `a` y quince
       `span`— así que nada de esto hacía falta. Al usarlo por primera vez en un
       `button` —«Ver desglose», que abre los códigos de una tanda de impresión—
       salió con el **borde y la tipografía del navegador**: una caja con
       recuadro en mitad de una bitácora donde todo lo demás es plano.

       El arreglo va aquí y no en aquel botón: si un chip se ve distinto según
       la etiqueta que lo lleve, deja de ser un componente. Y la etiqueta correcta
       ahí es `button` —abre un diálogo, no lleva a ninguna dirección—, así que
       ponerle un `a` con `href="#"` para que se viera bien sería mentir en el
       HTML para arreglar el CSS.

       Es un no-op para los `a` y los `span`, que no traen borde ni fuente
       propia. */
    appearance: none;
    border: 0;
    font-family: inherit;
    font-weight: inherit;
}

.tw-chip .ti {
    font-size: 0.875rem !important;
    color: var(--tw-font-tertiary);
}

button.tw-chip {
    cursor: pointer;
}

/* El hover cubría sólo `a.tw-chip`, así que un chip pulsable escrito como
   `button` no respondía al pasar por encima: se veía apagado justo donde hay
   que pulsar. */
a.tw-chip:hover,
button.tw-chip:hover {
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-primary);
}

/* =====================================================================
   9. Formularios
   ===================================================================== */

.form-control,
.form-select,
.ts-control {
    font-size: var(--tw-font-size-md) !important;
    min-height: 28px;
    padding: 3px 8px !important;
    border-color: var(--tw-border-strong);
    border-radius: var(--tw-radius-sm);
    color: var(--tw-font-primary);
    box-shadow: none;
}

.form-control::placeholder,
.ts-control input::placeholder {
    color: var(--tw-font-light);
}

/* El campo con lupa. El `padding: 3px 8px !important` de arriba le gana al
   relleno que Tabler reserva para el icono (`.input-icon .form-control`), y el
   texto corría por debajo de la lupa — se veía en «Buscar permiso». Se repone
   con el mismo peso, y a 2rem en vez de las 2.5 de Tabler porque el icono es
   `ti-sm` y los campos de este tema son compactos: la lupa cabe y el texto no
   pierde un tercio del campo. */
.input-icon .form-control:not(:last-child),
.input-icon .form-select:not(:last-child) {
    padding-right: 2rem !important;
}

.input-icon .form-control:not(:first-child),
.input-icon .form-select:not(:first-child) {
    padding-left: 2rem !important;
}

.input-icon .input-icon-addon {
    min-width: 2rem;
}

.form-control:focus,
.form-select:focus,
.ts-wrapper.focus .ts-control {
    border-color: var(--tw-accent);
    box-shadow: 0 0 0 3px var(--tw-accent-secondary);
}

/* ---------------------------------------------------------------------
   Tom Select.
   Su hoja viene de un CDN y trae el blanco escrito a mano —`#fff` en el
   campo y en el desplegable, `#303030` en el texto—, así que en oscuro se
   quedaba encendido en medio de la pantalla. Se reescribe con tokens, no
   con un bloque `[data-bs-theme='dark']`: en claro da exactamente los
   mismos valores que ya tenía y así solo hay una versión que mantener.
   --------------------------------------------------------------------- */

.ts-control,
.ts-wrapper.single.input-active .ts-control,
.ts-wrapper.multi.has-items .ts-control,
/* `.full` la pone Tom Select en cuanto el desplegable tiene algo elegido, y su
   hoja le escribe `#fff` con dos clases de peso: le ganaba al `.ts-control` de
   arriba, que sólo tiene una. En oscuro, un desplegable de uno solo se quedaba
   blanco en cuanto se elegía —o si nacía con valor— mientras los de marcar
   varios se veían bien, que es como se descubrió. */
.full .ts-control {
    background: var(--tw-bg-primary);
    color: var(--tw-font-primary);
}

.ts-control input,
.ts-control > input {
    color: var(--tw-font-primary);
}

/* ---------------------------------------------------------------------
   Un renglón, siempre.

   `.ts-control` es una caja flexible con `flex-wrap: wrap`, así que un
   nombre que no cabe —«KONE INDUSTRIAL S.A. DE C.V.» en un filtro de
   13 rem— se parte en dos y el campo crece a dos renglones: más alto que
   los desplegables de al lado, con la fila entera descuadrada y saltando
   cada vez que se cambia el filtro. El texto se recorta con puntos
   suspensivos, que es lo que hace el `<select>` nativo y lo que hace el
   resto de las tablas de este sistema. El nombre completo sigue estando
   en el desplegable al abrirlo.
   --------------------------------------------------------------------- */
.ts-wrapper.single .ts-control {
    flex-wrap: nowrap;
    /* Sitio para la flecha, que va posicionada encima y taparía la última
       letra o los puntos suspensivos. */
    padding-right: 22px !important;
}

.ts-wrapper.single .ts-control > .item {
    /* `display: block` y no el `flex` que le pone Tabler: los puntos
       suspensivos no se dibujan dentro de una caja flexible —el texto se queda
       cortado a hueso, sin avisar de que sigue—. */
    display: block;
    min-width: 0;
    flex: 0 1 auto;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Al abrir un desplegable con buscador, el sitio es para escribir: lo elegido
   se aparta y vuelve al cerrar. Dejarlo puesto tendría dos costes —el texto
   tecleado se lee pegado al nombre anterior, y en un filtro de 13 rem quedaría
   un hueco de tres centímetros para escribir—. Es además lo que hace el
   sistema del que sale este tema: al abrir, el campo se convierte en buscador.
   Lo que había sigue marcado en la lista.

   MIENTRAS ESTÁ ABIERTO, no mientras tiene el foco, y esa palabra era el fallo.
   `input-active` dura todo lo que dure el foco, así que al elegir una opción el
   desplegable se cerraba y el campo se quedaba **en blanco**: el valor estaba
   puesto —el `<select>` lo tenía, el botón de guardar se encendía— y lo único
   que faltaba era verlo. En el panel se notaba poco porque el foco se va al
   campo siguiente; en una terminal de piso, donde se elige y se mira, el
   operador lee un campo vacío y vuelve a elegir. `dropdown-active` es la clase
   que TomSelect pone sólo con la lista desplegada.

   `tw-select-lista` la pone `select.js` en los desplegables sin buscador: ahí
   no hay nada que teclear, así que lo elegido se queda a la vista. */
.ts-wrapper.single.input-active.dropdown-active:not(.tw-select-lista) .ts-control > .item {
    display: none;
}

/* Y sin esto el campo de escritura exige sus 7 rem y empuja al renglón de
   abajo justo lo que se acaba de arreglar. */
.ts-wrapper.single .ts-control > input {
    min-width: 0;
}

.ts-wrapper.disabled .ts-control,
.ts-wrapper.disabled .ts-control * {
    background-color: var(--tw-bg-tertiary) !important;
    color: var(--tw-font-light) !important;
    opacity: 1;
}

/* El desplegable es una superficie levantada, como los menús del sistema. */
.ts-dropdown {
    background: var(--tw-bg-primary);
    color: var(--tw-font-primary);
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-sm);
    box-shadow: var(--tw-shadow-strong);
    margin-top: 2px;

    /*
     * Por encima del modal de Bootstrap, que es 1055.
     *
     * Hace falta para los desplegables que cuelgan del `body` —los que viven
     * dentro de una tarjeta, que si no los recorta—. TomSelect sólo calcula la
     * posición cuando el padre es literalmente `body`; con cualquier otro padre
     * lo pega sin colocarlo y acaba al fondo de la página. Así que la salida es
     * `body` + este z-index, y no un padre intermedio.
     */
    z-index: 1060;
}

.ts-dropdown .option,
.ts-dropdown .create,
.ts-dropdown .no-results,
.ts-dropdown .optgroup-header {
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-md);
}

/* El encabezado de grupo, que hasta ahora se saltaba el tema.

   La hoja de TomSelect trae `background: #fff` clavado en `.optgroup-header`, y
   aquí sólo se le estaba cambiando el color de la letra. En claro no se notaba
   —el fondo del desplegable es blanco de todas formas—, pero en oscuro salía una
   barra blanca de lado a lado del menú, con el nombre del grupo en gris encima.

   Se vio en el desplegable de países, y le pasaba lo mismo al de unidades
   —`materials/units.js`, que agrupa medida y empaque—: son los dos únicos del
   sistema con grupos, así que el defecto llevaba ahí desde que existe el tema
   oscuro y sólo lo veía quien abría ese desplegable.

   Va con el fondo del desplegable y no con uno propio: el grupo ya se separa con
   el filo de arriba y con la letra más pequeña, y un segundo tono dentro de un
   menú de dos centímetros es ruido. */
.ts-dropdown .optgroup-header,
.ts-dropdown .no-results {
    color: var(--tw-font-tertiary);
    background: var(--tw-bg-primary);
}

.ts-dropdown .optgroup-header {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
}

.ts-dropdown .optgroup:not(:first-child) .optgroup-header {
    border-top: 1px solid var(--tw-border-light);
}

.ts-dropdown .active {
    background-color: var(--tw-bg-tertiary);
    color: var(--tw-font-primary);
}

.ts-dropdown .active.create {
    color: var(--tw-font-primary);
}

.ts-dropdown .selected,
.ts-dropdown .active.selected {
    background-color: var(--tw-accent-secondary);
    color: var(--tw-font-primary);
}

.ts-dropdown .disabled {
    color: var(--tw-font-extra-light);
}

/* El resaltado de la búsqueda: un subrayado del acento, no un amarillo. */
.ts-dropdown [data-selectable] .highlight {
    background: var(--tw-accent-secondary);
    color: var(--tw-font-primary);
    border-radius: var(--tw-radius-xs);
}

/* Fichas de una selección múltiple: el mismo chip gris que `tw-tag`. */
.ts-wrapper.multi .ts-control > div {
    background: var(--tw-tag-gray-bg);
    color: var(--tw-tag-gray-fg);
    border: 0;
    border-radius: var(--tw-radius-xs);
}

.ts-wrapper.multi .ts-control > div.active {
    background: var(--tw-accent);
    color: var(--tw-font-on-solid);
}

.ts-wrapper.plugin-remove_button .item .remove {
    border-left-color: var(--tw-border-medium);
    color: inherit;
}

.ts-wrapper.plugin-remove_button .item .remove:hover {
    background: var(--tw-bg-transparent-light);
}

/* La flecha del selector sencillo la dibuja Tom Select con bordes. */
.ts-wrapper.single .ts-control:after {
    border-top-color: var(--tw-font-tertiary) !important;
}

/* ---------------------------------------------------------------------
   Mientras el servidor busca.

   El indicador que trae la hoja del CDN es un aro gris de 5 px de grosor
   metido en una caja de 30 px: en un campo de 28 px de alto parece un
   error de maquetación. Se cambia por un aro fino del color de acento, y
   la flecha del campo se convierte en ese mismo aro mientras dura la
   consulta — la actividad se ve donde está la vista, en el campo, y no
   solo en el desplegable.
   --------------------------------------------------------------------- */

@keyframes tw-select-cargando {
    to {
        transform: rotate(360deg);
    }
}

.ts-dropdown .spinner {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 34px;
    margin: 0;
}

.ts-dropdown .spinner:after {
    content: '';
    display: block;
    width: 16px;
    height: 16px;
    margin: 0;
    border: 2px solid var(--tw-border-strong);
    border-top-color: var(--tw-accent);
    border-radius: 50%;
    animation: tw-select-cargando 0.7s linear infinite;
}

/* La flecha del campo es la de Bootstrap, dibujada como fondo: se apaga
   mientras carga y en su sitio gira el mismo aro. */
.ts-wrapper.form-select.loading {
    background-image: none;
}

.ts-wrapper.single.loading .ts-control {
    position: relative;
}

.ts-wrapper.single.loading .ts-control:after {
    content: '';
    position: absolute;
    top: 50%;
    right: 10px;
    width: 12px;
    height: 12px;
    margin-top: -6px;
    border: 2px solid var(--tw-border-strong) !important;
    border-top-color: var(--tw-accent) !important;
    border-radius: 50%;
    animation: tw-select-cargando 0.7s linear infinite;
}

.form-label {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
    margin-bottom: 2px;
}

.form-hint {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* ── El ancho del control lo pide el dato ─────────────────────────────
 *
 * Un campo que no tiene con quién compartir renglón se lo queda entero:
 * a medias deja el otro medio en blanco y se lee como si faltara algo
 * por poner ahí. Pero que la **columna** ocupe el renglón no obliga al
 * **control** a estirarse con ella: un sitio en el desplegable —tres
 * cifras— dentro de una caja de 470 px anuncia que ahí cabe mucho, y lo
 * que cabe son tres cifras.
 *
 * Se pone sobre el control, no sobre la columna, y por eso también sobre
 * el `input-group`: un campo con sufijo —«%», «MB»— es el grupo entero.
 */
.form-control.tw-w-num,
.form-select.tw-w-num,
.input-group.tw-w-num {
    max-width: 8rem;
}

.form-control.tw-w-code,
.form-select.tw-w-code,
.input-group.tw-w-code {
    max-width: 14rem;
}

.input-group-text {
    font-size: var(--tw-font-size-md);
    background-color: var(--tw-bg-tertiary);
    border-color: var(--tw-border-strong);
    color: var(--tw-font-tertiary);
}

/* La casilla no se aplasta. Como hija de un renglón flex —las bandejas, el
   riel del nesteador, cualquier lista con casilla— el navegador la encogía
   para hacerle sitio al texto largo de al lado, y una casilla ovalada se lee
   como un control roto. Su tamaño es su tamaño. */
.form-check-input {
    flex-shrink: 0;
}

.form-check-input:checked {
    background-color: var(--tw-accent);
    border-color: var(--tw-accent);
}

/* =====================================================================
   10. Tabs
   ===================================================================== */

/* Una sola definición de pestaña para todo el sistema: la barra de la vista
   de registro y la de las tarjetas con tabs deben verse idénticas. */
.nav-tabs,
.tw-record-tabs {
    display: flex;
    align-items: stretch;
    gap: var(--tw-tab-gap);
    border-bottom: 1px solid var(--tw-border-light);
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}

.nav-tabs::-webkit-scrollbar,
.tw-record-tabs::-webkit-scrollbar {
    display: none;
}

/* Cuando las pestañas no caben, el degradado avisa que hay más hacia ese
   lado: sin él una pestaña simplemente se corta y parece que no existe.
   Las clases las pone el script de la barra de pestañas en el layout. */
.nav-tabs.has-overflow-end,
.tw-record-tabs.has-overflow-end {
    mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent 100%);
}

.nav-tabs.has-overflow-start,
.tw-record-tabs.has-overflow-start {
    mask-image: linear-gradient(to right, transparent 0, #000 40px);
}

.nav-tabs.has-overflow-start.has-overflow-end,
.tw-record-tabs.has-overflow-start.has-overflow-end {
    mask-image: linear-gradient(to right, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
}

/* Tabler compensa el padding original del card-header con márgenes
   negativos; como aquí el padding es distinto, esos márgenes empujaban
   la primera pestaña fuera del card (que recorta con overflow:hidden). */
.card-header-tabs {
    margin: 0 !important;
    width: 100%;
}

.card-header:has(.card-header-tabs) {
    padding: 0 12px;
    align-items: flex-end;
}

.nav-tabs .nav-link,
.tw-record-tabs .nav-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: var(--tw-tab-height);
    padding: 0 var(--tw-tab-gap);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-tertiary);
    border: 0;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    white-space: nowrap;
    flex: 0 0 auto;
}

.nav-tabs .nav-link:hover,
.tw-record-tabs .nav-link:hover {
    color: var(--tw-font-primary);
    border-bottom-color: var(--tw-border-strong);
    background-color: transparent;
}

.nav-tabs .nav-link.active,
.tw-record-tabs .nav-link.active,
.card-header-tabs .nav-link.active {
    color: var(--tw-font-primary) !important;
    background-color: transparent !important;
    border-bottom: 2px solid var(--tw-gray-90) !important;
}

/* ── Lo que no cabe va a «Más» ───────────────────────────────────────────

   Lo pone `components/tabs.js`: mide la barra y pliega en un menú las
   pestañas que sobran, de la última a la primera, nunca la activa. Con el
   menú puesto la barra deja de recortar —el menú cuelga fuera de ella— y el
   degradado de desplazamiento se apaga: ya no hay nada oculto a un lado. */
.nav-tabs.tw-tabs--menu,
.tw-record-tabs.tw-tabs--menu {
    overflow: visible;
    mask-image: none !important;
}

.tw-tabs-more .dropdown-menu {
    min-width: 12rem;
    padding: 4px;
}

.tw-tabs-more .dropdown-menu .nav-item {
    display: block;
}

.tw-tabs-more .dropdown-menu .nav-link {
    display: flex;
    width: 100%;
    min-height: 32px;
    margin: 0;
    padding: 4px 10px;
    border: 0 !important;
    border-radius: var(--tw-radius-sm);
    white-space: nowrap;
}

.tw-tabs-more .dropdown-menu .nav-link:hover {
    background-color: var(--tw-bg-transparent-light);
}

/* Un contador en cero no invita a entrar: se lee, pero no llama. */
.nav-link .tw-tag[data-zero],
.tw-subtabs .tw-tag[data-zero] {
    opacity: 0.45;
}

/* ── Pestañas agrupadas · el control segmentado dentro del panel ──────────

   «Fabricación» reúne Revisiones, Insumos, Procesos y Componentes; abrirla
   enseña un carril con las cuatro. Es el mismo carril gris de `.tw-seg`,
   sólo que cada opción es una pestaña de Bootstrap (`role="tab"`), así que
   la dirección y la memoria de pestañas la entienden igual. */
.tw-subtabs {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px;
    padding: 2px;
    /* Alineado con lo que viene debajo: la barra de vista lleva 12 px. Y
       mide lo que miden sus opciones: dentro del panel en columna, el
       carril se estiraba a todo el ancho. */
    margin: 12px 12px 0;
    align-self: flex-start;
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
}

/* Dentro del contenido de la ficha el padding ya lo pone el contenedor
   (24 px): el carril arranca donde arranca el texto, no 24 px más adentro. */
.tw-record-content .tw-subtabs {
    margin-left: 0;
    margin-right: 0;
    margin-top: 0;
    margin-bottom: 12px;
}

.tw-subtabs > .tw-subtab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid transparent;
    border-radius: var(--tw-radius-sm);
    /* 5 px y no 3: con el borde del carril suma 34 px, lo que mide una
       pestaña, para que en columna las dos filas queden a la misma altura. */
    padding: 5px 10px;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-tertiary);
    text-decoration: none;
    white-space: nowrap;
    transition:
        background-color var(--tw-transition),
        color var(--tw-transition);
}

.tw-subtabs > .tw-subtab:hover {
    color: var(--tw-font-primary);
}

.tw-subtabs > .tw-subtab.active {
    background-color: var(--tw-bg-primary);
    border-color: var(--tw-border-light);
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

/* ── Pestañas verticales dentro de un panel · el carril a la izquierda ─────

   Para un listado que junta dos preguntas distintas —en «Por decidir →
   Certificados», lo que falta capturar y lo que leyó la IA—: el mismo carril
   de `.tw-subtabs`, puesto en columna a la izquierda y con el contenido al
   lado, como las secciones de una ficha en monitor ancho. Las opciones siguen
   siendo pestañas de Bootstrap, así que la dirección y la memoria las entienden
   igual. En el teléfono vuelve a ser una fila encima del contenido. */
.tw-pane-split {
    flex: 1 1 auto;
    min-height: 0;
    display: grid;
    grid-template-columns: clamp(200px, 15vw, 240px) minmax(0, 1fr);
}

.tw-subtabs.tw-subtabs--vertical {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    align-self: stretch;
    gap: 2px;
    margin: 0;
    padding: 12px 8px;
    border-radius: 0;
    background-color: transparent;
    border-right: 1px solid var(--tw-border-light);
}

.tw-subtabs--vertical > .tw-subtab {
    width: 100%;
    padding: 7px 10px;
}

.tw-subtabs--vertical > .tw-subtab.active {
    background-color: var(--tw-bg-transparent-light);
    border-color: transparent;
}

.tw-pane-split > .tab-content {
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.tw-pane-split > .tab-content > .tab-pane.active {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

@media (max-width: 768px) {
    .tw-pane-split {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto minmax(0, 1fr);
    }

    .tw-subtabs.tw-subtabs--vertical {
        flex-direction: row;
        overflow-x: auto;
        border-right: 0;
        border-bottom: 1px solid var(--tw-border-light);
    }

    .tw-subtabs--vertical > .tw-subtab {
        width: auto;
    }
}

/* El panel de un grupo apila el carril y el contenido; el panel hijo activo
   ocupa el resto, como haría de estar suelto en la barra. */
.tw-record-main > .tab-content > .tab-pane.tw-pane-group.active {
    display: flex;
    flex-direction: column;
}

.tw-pane-group > .tab-content {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.tw-pane-group > .tab-content > .tab-pane.active {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* ── En monitores anchos, la barra es una columna ─────────────────────────

   A partir de 1440 px la ficha tiene sitio para una lista vertical de
   secciones: sin límite de ancho, con contador y sin nada que plegar. Sólo en
   la ficha de registro; los listados con pestañas siguen en horizontal. */
@media (min-width: 1440px) {
    /* Sólo donde hay carril que poner · una ficha sin pestañas no reserva su
       columna.

       La primera columna mide siempre lo mismo, tenga o no una lista de
       secciones que meter dentro. En «Mi cuenta» o en el alta de una orden de
       producción —fichas de una sola pantalla, sin pestañas— quedaba una banda
       vacía de 216 px entre el panel de la izquierda y el contenido, que
       arrancaba a media pantalla. Con `:has()` la rejilla se monta sólo cuando
       el carril existe; sin él la sección sigue siendo la columna flexible de
       siempre y el contenido empieza pegado al panel. */
    .tw-record .tw-record-main:has(> .tw-record-tabs) {
        display: grid;
        /* La columna crece un poco con la pantalla: «Renglones y
           certificados» no cabe en 200 px y un rótulo cortado es una
           pestaña que no se lee. Lo que aun así no quepa parte en dos
           líneas, más abajo. */
        grid-template-columns: clamp(200px, 15vw, 260px) minmax(0, 1fr);
        /* El contenido se lleva el alto que sobra · si no, se corta a media
           pantalla.

           La columna de pestañas se estira a lo largo de cuarenta renglones
           (`grid-row: 1 / span 40`, abajo) para llegar hasta el fondo sea cual
           sea el número de hermanos. Con todos los renglones en `auto`, el
           sobrante de la rejilla se reparte **a partes iguales entre los
           cuarenta**: el del contenido se quedaba con una cuarentava parte y
           los treinta y nueve vacíos con el resto. En la recepción eso dejaba
           el panel doscientos píxeles más corto que la tarjeta —la ficha se
           cortaba a mitad del segundo renglón y debajo quedaba un hueco negro—
           y el desplazamiento de dentro empezaba antes de tiempo.

           El primer renglón es el del contenido: declarado en `1fr` se lleva
           todo el sobrante y no queda nada que repartir. Va con `minmax(0, …)`
           para que pueda encoger por debajo de lo que mide su contenido, que es
           la condición para que `overflow-y: auto` de dentro desplace en vez de
           empujar. Lo que se coloque después —si alguna ficha añade un hermano
           en la segunda columna— sigue midiendo lo suyo. */
        grid-template-rows: minmax(0, 1fr);
        grid-auto-rows: auto;
    }

    .tw-record .tw-record-main > .tw-record-tabs {
        grid-column: 1;
        grid-row: 1 / span 40;
        flex-direction: column;
        align-items: stretch;
        gap: 2px;
        padding: 12px 8px;
        border-bottom: 0;
        border-right: 1px solid var(--tw-border-light);
        overflow: visible;
        mask-image: none !important;
    }

    .tw-record .tw-record-main > :not(.tw-record-tabs) {
        grid-column: 2;
        min-width: 0;
    }

    /* El contenido arranca a la altura de la primera pestaña de la columna:
       ésta lleva 12 px arriba y el contenido traía 16. Así el carril de un
       grupo queda en la misma línea que «Fabricación». */
    .tw-record .tw-record-main > .tw-record-content {
        padding-top: 12px;
    }

    .tw-record .tw-record-main > .tw-record-tabs .nav-item {
        display: block;
    }

    .tw-record .tw-record-main > .tw-record-tabs .nav-link {
        display: flex;
        align-items: flex-start;
        width: 100%;
        min-height: 34px;
        margin: 0;
        padding: 7px 10px;
        border-bottom: 0;
        border-radius: var(--tw-radius-sm);
        /* En columna el rótulo puede partir: mejor dos líneas que salirse. */
        white-space: normal;
        overflow-wrap: anywhere;
        line-height: 1.25;
        text-align: left;
    }

    .tw-record .tw-record-main > .tw-record-tabs .nav-link .ti {
        flex: 0 0 auto;
        margin-top: 1px;
    }

    .tw-record .tw-record-main > .tw-record-tabs .nav-link:hover {
        background-color: var(--tw-bg-transparent-light);
        border-bottom: 0;
    }

    /* La abierta pesa más que la que se está señalando.

       Iba con `--tw-bg-secondary`, que en oscuro es negro puro: la pestaña
       abierta quedaba *más apagada* que el fondo del panel y el puntero, que
       aclara, la pisaba. Es el mismo gris sólido con el que el menú de la
       izquierda marca la pantalla en la que se está
       (`.nav-link[aria-current='page']`), y por la misma razón. */
    .tw-record .tw-record-main > .tw-record-tabs .nav-link.active {
        background-color: var(--tw-bg-quaternary) !important;
        border-bottom: 0 !important;
    }

    /* El contador, al filo derecho: en columna los números forman su propia
       vertical y se leen de un vistazo, en vez de quedar colgados donde acabe
       cada rótulo. El `!important` es contra el `ms-1` del marcado —una
       utilidad de Bootstrap, que gana por serlo y no por ser más específica—.

       Sin relleno arriba y abajo porque el contador no debe decidir el alto
       del renglón: con él medía 22 px y las pestañas con número quedaban dos
       píxeles más altas que las que no lo llevan. Al pasar el puntero de una a
       otra se veía saltar el recuadro. */
    .tw-record .tw-record-main > .tw-record-tabs .nav-link .tw-tag {
        margin-left: auto !important;
        padding-block: 0;
    }

    /* La píldora del contador se pinta *sobre* lo que tenga debajo y no con un
       gris fijo. El gris fijo era casi el mismo tono que el recuadro del
       puntero: al señalar «Cajas» el número se quedaba flotando sin píldora,
       mientras los de al lado seguían con la suya. Los distintivos de color
       —los que dicen algo— se quedan con el suyo. */
    .tw-record .tw-record-main > .tw-record-tabs .nav-link .tw-tag:not([class*='tw-tag--']) {
        background-color: var(--tw-bg-transparent-light);
    }
}

/* El gap ya separa icono y texto: el me-2 del markup duplicaba la distancia */
.nav-tabs .nav-link .ti,
.tw-record-tabs .nav-link .ti {
    font-size: var(--tw-icon-size) !important;
    margin-right: 0 !important;
}

/* Pestañas de módulos que todavía no existen. Antes iban con `.disabled`:
   se veían igual que una pestaña normal pero no respondían al clic, así que
   parecía que la aplicación estaba rota. Ahora se marcan con el distintivo
   "Pronto" y siguen siendo navegables a propósito — el panel explica qué
   falta y dónde se hace hoy esa tarea. */
.nav-tabs .nav-link.tw-tab-soon,
.tw-record-tabs .nav-link.tw-tab-soon {
    color: var(--tw-font-extra-light);
}

.nav-tabs .nav-link.tw-tab-soon:hover,
.tw-record-tabs .nav-link.tw-tab-soon:hover {
    color: var(--tw-font-light);
    border-bottom-color: var(--tw-border-medium);
}

.nav-tabs .nav-link.tw-tab-soon.active,
.tw-record-tabs .nav-link.tw-tab-soon.active {
    color: var(--tw-font-secondary) !important;
    border-bottom-color: var(--tw-border-strong) !important;
}

.nav-tabs .nav-link.tw-tab-soon .ti,
.tw-record-tabs .nav-link.tw-tab-soon .ti {
    opacity: 0.7;
}

.nav-tabs .nav-link.tw-tab-soon::after,
.tw-record-tabs .nav-link.tw-tab-soon::after {
    content: 'Pronto';
    font-size: var(--tw-font-size-xxs);
    font-weight: var(--tw-weight-medium);
    line-height: 1;
    letter-spacing: 0.02em;
    padding: 3px 5px;
    border-radius: var(--tw-radius-xs);
    background-color: var(--tw-tag-gray-bg);
    color: var(--tw-tag-gray-fg);
}

/* Pestaña deshabilitada de verdad: la que aún está cargando o que el rol
   no puede abrir. Se distingue de la anterior en que no se puede pulsar. */
.nav-tabs .nav-link.disabled,
.tw-record-tabs .nav-link.disabled {
    color: var(--tw-font-extra-light);
    cursor: not-allowed;
}

/* Tabs "pill" existentes de la app */
ul.new-tabs[data-bs-toggle='tabs'] .nav-link.active::before {
    background: var(--tw-bg-quaternary) !important;
    border-radius: var(--tw-radius-sm) !important;
}

/* =====================================================================
   11. Dropdowns, modales y offcanvas
   ===================================================================== */

.dropdown-menu {
    font-size: var(--tw-font-size-md) !important;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    box-shadow: var(--tw-shadow-strong);
    padding: 4px;
}

.dropdown-item {
    font-size: var(--tw-font-size-md) !important;
    border-radius: var(--tw-radius-sm);
    padding: 5px 8px;
    color: var(--tw-font-primary);
    min-height: 28px;
    display: flex;
    align-items: center;
}

.dropdown-item:hover,
.dropdown-item:focus {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.dropdown-item .ti,
.dropdown-item svg {
    width: 16px;
    height: 16px;
    font-size: 1rem !important;
    color: var(--tw-font-tertiary);
}

.dropdown-item.text-danger,
.dropdown-item.text-danger .ti,
.dropdown-item.text-danger svg {
    color: var(--tw-tag-red-fg) !important;
}

/* El menú de las altas · cada opción con su renglón de «cuándo se usa».
   El icono se queda arriba, a la altura del título, y no centrado contra las
   dos líneas; el texto de apoyo puede envolver sin empujar el menú entero. */
.tw-menu-alta {
    width: 23rem;
}

.tw-menu-alta .dropdown-item {
    align-items: flex-start;
    gap: 10px;
    padding: 7px 10px;
    white-space: normal;
}

.tw-menu-alta .dropdown-item .ti {
    flex: 0 0 auto;
    margin-top: 2px;
}

.modal-content,
.offcanvas {
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-lg);
    box-shadow: var(--tw-shadow-strong);
}

.offcanvas {
    border-radius: 0;
}

.modal-header,
.offcanvas-header {
    border-bottom: 1px solid var(--tw-border-light);
    padding: 10px 16px;
    min-height: 44px;
}

/* El título del cajón, igual que el del modal: sin esto salía con el tamaño de
   Tabler, el doble que cualquier otro título de la pantalla. */
.modal-title,
.offcanvas-title {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    margin: 0;
    /* Un título largo en un diálogo pequeño («Parece que hubo un error al imprimir», `modal-sm`) parte renglón en vez de salir cortado (11-oct). */
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    min-width: 0;
    flex: 1 1 auto;
}

.modal-footer,
.offcanvas-footer {
    border-top: 1px solid var(--tw-border-light);
    padding: 10px 16px;
}

/*
 * Un botón del pie que dice lo que hace es largo («Cancelar lo empezado de
 * REC-2026-00032-L067-002») y en un diálogo pequeño se salía del cuadro por
 * la izquierda: `.btn` no parte renglón. En el pie sí lo parte (9-oct-2026).
 */
.modal-footer > .btn {
    white-space: normal;
    max-width: 100%;
}

/*
 * El cuerpo del modal heredaba los 24 px de Tabler mientras el encabezado y el
 * pie usaban 16 px, así que el título y los botones quedaban desalineados
 * respecto a los campos. Se unifica el margen lateral en los tres.
 */
.modal-body,
.offcanvas-body {
    padding: 16px;
}

.modal-backdrop.show {
    opacity: 0.08;
}

/* Tooltips */
.tooltip-inner {
    font-size: var(--tw-font-size-sm);
    background-color: var(--tw-gray-90);
    border-radius: var(--tw-radius-sm);
    padding: 4px 8px;
}

/* Avatares */
.avatar {
    border-radius: var(--tw-radius-sm);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-secondary);
}

.avatar.avatar-rounded,
.avatar.rounded-circle {
    border-radius: var(--tw-radius-pill);
}

/* Tagify: usado en el formato de código de la etiqueta del cliente */
.tagify {
    --tags-border-color: var(--tw-border-strong);
    --tags-hover-border-color: var(--tw-gray-50);
    --tags-focus-border-color: var(--tw-accent);
    --tag-bg: var(--tw-bg-quaternary);
    --tag-hover: var(--tw-bg-tertiary);
    --tag-text-color: var(--tw-font-primary);
    --tag-remove-bg: var(--tw-tag-red-bg);
    --tag-pad: 2px 6px;
    --tag-inset-shadow-size: 1.2em;
    --tagify-dd-item-pad: 5px 8px;

    min-height: 28px;
    padding: 3px;
    border-radius: var(--tw-radius-sm);
    font-size: var(--tw-font-size-md);
}

.tagify__tag {
    margin: 2px;
}

.tagify__tag > div {
    border-radius: var(--tw-radius-sm);
    padding: var(--tag-pad);
}

.tagify__tag-text {
    font-size: var(--tw-font-size-md);
    line-height: 18px;
}

.tagify__input {
    margin: 2px;
    padding: 2px 4px;
    font-size: var(--tw-font-size-md);
}

.tagify__input:empty::before {
    color: var(--tw-font-light);
}

.tagify__dropdown {
    --tagify-dd-bg-color: var(--tw-bg-primary);
    --tagify-dd-text-color: var(--tw-font-primary);
    --tagify-dd-color-primary: var(--tw-accent);
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    box-shadow: var(--tw-shadow-strong);
}

.tagify__dropdown__wrapper {
    border: 0;
    border-radius: var(--tw-radius-md);
    background: var(--tw-bg-primary);
}

/* Las sugerencias en fila (`tags-inline`). La hoja de la librería sólo las pinta
   bajo `.light-style`/`.dark-style`, que aquí no existen: salían con el fondo
   blanco de la librería y el texto claro del tema oscuro, sin leerse. */
.tags-inline .tagify__dropdown__item {
    border: 1px solid var(--tw-border-medium);
    background: var(--tw-bg-secondary);
    color: var(--tw-font-primary);
}

.tags-inline .tagify__dropdown__item--active,
.tags-inline .tagify__dropdown__item:hover {
    background: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary) !important;
}

/* Un marcador no se edita: su texto es el nombre, lo impreso sale de la orden. */
.tagify__tag[editable='false'] {
    cursor: grab;
}

.tagify__dropdown__item {
    font-size: var(--tw-font-size-md);
    border-radius: var(--tw-radius-sm);
    margin: 2px;
}

.tagify__dropdown__item--active {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

/* Dropzone */
.dropzone {
    border: 1px dashed var(--tw-border-strong) !important;
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
    color: var(--tw-font-tertiary);
    min-height: 0;
    padding: 12px !important;
}

.dropzone:hover {
    border-color: var(--tw-accent) !important;
    background-color: var(--tw-accent-tertiary);
}

.dz-button {
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-tertiary);
    background: none;
    border: 0;
}

/* =====================================================================
   12. Página de registro (perfil) — usada por /pieces/:id
   ===================================================================== */

.tw-page {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    overflow: hidden;
    background-color: var(--tw-bg-primary);
}

/* ---------------------------------------------------------------------
   Pantalla de listado, tal como la arma Twenty: la barra de título va
   directamente sobre el fondo gris, y todo lo demás —barra de vista,
   tabla y pie de conteo— vive dentro de una tarjeta con margen uniforme.
   Lo que parece un hueco bajo la tabla es en realidad ese margen.
   --------------------------------------------------------------------- */
.tw-screen {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    overflow: hidden;
    background-color: var(--tw-bg-secondary);
}

/* La tarjeta flota sobre el fondo gris con las cuatro esquinas redondeadas:
   quien separa del sidebar es su propio margen, no una línea divisoria. */
.tw-panel {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    margin: 0 var(--tw-screen-gutter) var(--tw-screen-gutter);
    background-color: var(--tw-bg-primary);
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    overflow: hidden;
}

/* La vista de registro reparte la tarjeta en dos columnas: campos y pestañas */
.tw-panel.tw-record {
    flex-direction: row;
}

/* Galería de tarjetas: para colecciones cortas y visuales, donde una tabla
   con cuatro filas resulta pesada (por ejemplo los diseños de etiqueta). */
.tw-card-grid {
    display: grid;
    /* `min(240px, 100%)` y no `240px` a secas: `minmax` con un mínimo fijo
       **no baja de él**, así que en una pantalla de 375 px la columna
       seguía midiendo lo suyo y la tarjeta se salía por la derecha. Es el
       mismo arreglo en las ocho rejillas del tema. */
    grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr));
    gap: 12px;
}

.tw-select-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    transition:
        border-color var(--tw-transition),
        box-shadow var(--tw-transition);
}

.tw-select-card:hover {
    border-color: var(--tw-border-strong);
    box-shadow: var(--tw-shadow-light);
}

.tw-select-card.is-selected {
    border-color: var(--tw-tag-green-fg);
    box-shadow: 0 0 0 1px var(--tw-tag-green-fg);
}

.tw-select-card-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
}

.tw-select-card-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-select-card-specs {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.tw-select-card-spec {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-tertiary);
}

.tw-select-card-spec .ti {
    font-size: var(--tw-icon-size) !important;
    color: var(--tw-font-light);
}

.tw-select-card-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--tw-border-light);
}

/* Código generado: monoespaciado para poder cotejarlo carácter a carácter */
.tw-code-sample {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-secondary);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
    word-break: break-all;
}

/* La misma muestra, cuando lo que se copia ocupa su propio renglón: una
   dirección, un comando. `inline-flex` la encogía al ancho del texto, así que
   una dirección corta dejaba media caja y el bloque no se leía como algo
   copiable. */
.tw-code-sample--block {
    display: flex;
    width: 100%;
    white-space: pre-wrap;
}

/* La etiqueta dibujada en pequeño, junto a la casilla que la elige.

   Dice CUÁL es el papel: dos diseños del mismo juego se parecen escritos
   —«CHICA · SERIAL» y «CHICA · NÚMERO DE PARTE»— y son dos papeles distintos
   sobre la mesa. Fondo blanco siempre, porque la etiqueta es papel, y por eso
   no sigue al tema oscuro. */
.tw-label-thumb {
    flex-shrink: 0;
    width: 180px;
    padding: 4px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    background-color: #fff;
}

/* Lienzo de la vista previa de etiqueta */
.tw-label-canvas {
    padding: 8px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-secondary);
}

/* Sección con encabezado dentro del contenido de una pestaña */
.tw-subpanel + .tw-subpanel {
    border-top: 1px solid var(--tw-border-light);
}

/* La cabecera de un subpanel: el título a la izquierda y sus acciones a la
   derecha.

   ENVUELVE, y no es un adorno. En la ficha de una recepción esa hilera llegó a
   seis botones —imprimir, dar de baja, aceptar, cuarentena, rechazar— dentro de
   un panel que comparte el ancho con la ficha y con el menú, y sin envolver el
   último se salía por la derecha y se **cortaba**: el operador no podía
   rechazar material porque el botón no existía en su pantalla. Le pasa a
   cualquier pantalla de este layout el día que le añaden una acción más, así
   que se arregla aquí y no en la que se rompió.

   Con `min-width: 0` en el hijo del título, el que cede sitio es el texto —que
   se puede recortar y sigue leyéndose— y no los botones, que recortados no
   sirven para nada. El `min-height` se queda como alto mínimo de una hilera; en
   cuanto envuelve, el `padding` vertical le da aire. */
.tw-subpanel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px 12px;
    min-height: 40px;
    padding: 6px 12px;
    border-bottom: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-primary);
}

.tw-subpanel-header > * {
    min-width: 0;
}

/* Y las hileras de dentro también: el bloque de acciones es un flex propio, así
   que sin esto la cabecera envolvía una sola pieza —el bloque entero— y el
   bloque seguía saliéndose. Cada grupo de botones se mantiene junto y baja
   entero, que es como se leen: «imprimir y dar de baja» por un lado, «aceptar,
   cuarentena y rechazar» por otro. */
.tw-subpanel-header .d-flex {
    flex-wrap: wrap;
}

.tw-subpanel-body {
    padding: 12px;
}

/* Identidad del registro dentro de la tarjeta, en las vistas de detalle */
.tw-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex: 0 0 auto;
    padding: 12px;
    border-bottom: 1px solid var(--tw-border-light);
}

/* Donde va la tabla: es quien recorta, así adjustScrollY lo toma de límite */
.tw-panel-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

/* ── La columna de lectura de una pantalla de ajustes ───────────────────────

   Una pantalla de configuración son dos o tres tarjetas con texto dentro, y
   estiradas a un monitor de 27 pulgadas se rompen de la misma forma: una
   dirección de sesenta caracteres queda flotando en metro y medio de vacío, y
   el párrafo de al lado se vuelve ilegible porque el ojo pierde el renglón al
   volver.

   Acota **lo que se lee**, no lo que se compara: una tabla, un tablero o un
   plano siguen ocupando el panel entero, porque ahí el ancho es información. */
.tw-settings-column {
    max-width: var(--tw-settings-column);
}

/* ── La pestaña que es una tabla: `--tabla` ─────────────────────────────────

   El cuerpo del panel recorta con `overflow: hidden` porque casi siempre lleva
   dentro una DataTable, que se desplaza sola —el mismo motivo que documenta
   `.tw-panel-body:has(> .tw-inbox-list)`—. Una tabla escrita a mano dentro de
   una `.table-responsive` no se desplaza sola: el carril crece con sus renglones
   hasta pasarse del panel y el recorte se lo come **sin dejar barra**. Es el
   peor de los fallos de alto, porque no se ve: la pantalla sale entera y bien, y
   los últimos renglones sencillamente no están. En la ficha de la orden de
   producción la tabla medía 1509 px dentro de un panel de 664 y no había forma
   de llegar al renglón trece.

   Las otras pestañas con esta forma lo taparon a mano con un `overflow-y-auto`
   en el cuerpo. Devuelve el desplazamiento, sí, pero deja la barra horizontal
   del carril **debajo del último renglón**, o sea fuera de la pantalla en cuanto
   la tabla es larga; y esta tabla mide 1200 px en un panel de 977, así que la de
   lado hace tanta falta como la de arriba abajo. Aquí el cuerpo se apila en
   columna y el carril se queda con el alto que sobra: las dos barras caen dentro
   de lo que se ve, y lo que no es el carril —la barra de lo marcado— se queda
   quieto arriba.

   VA DECLARADO EN LA PLANTILLA y no por `:has(> .table-responsive)` a secas,
   porque la misma etiqueta envuelve dos cosas distintas. En Permisos hay **dos**
   carriles en un mismo cuerpo y en Trazabilidad el carril es el último bloque de
   un tablero de tarjetas: en los dos, quien tiene que desplazarse es el cuerpo
   entero, y repartir el alto entre los carriles dejaría cada tabla en una tira.
   `--tabla` dice «esta pestaña **es** la tabla», que es cuando esto vale. */
.tw-panel-body--tabla {
    display: flex;
    flex-direction: column;
}

.tw-panel-body--tabla > :is(.table-responsive, .tw-table-scroll) {
    flex: 1 1 auto;
    min-height: 0;
}

/* Y el tablero dentro de un panel · el mismo fallo, del otro lado.

   `.tw-dash` ya se desplaza solo —trae `flex: 1 1 auto; min-height: 0;
   overflow-y: auto`—, pero eso sólo funciona si su padre es una columna flex,
   que es como vive en el resto de pantallas: colgando de `.tw-screen`. Dentro
   de un cuerpo de panel —el panorama de Seguimiento— el padre es un bloque, así
   que el `flex` no aplica, el tablero crece con sus secciones y el recorte del
   cuerpo se lleva las de abajo. Se le devuelve la columna y el tablero vuelve a
   hacer lo que ya sabía hacer. */
.tw-panel-body:has(> .tw-dash) {
    display: flex;
    flex-direction: column;
}

/* Dentro de la tarjeta la barra de vista no repite el borde superior */
.tw-panel > .tw-view-bar:first-child {
    border-top: 0;
}

/* La barra de título va sobre el fondo gris; quien delimita es la tarjeta */
.tw-screen > .tw-topbar {
    border-bottom: 0;
    min-height: 48px;
    height: auto;
    padding: 0 var(--tw-screen-gutter);
    flex-wrap: wrap;
}

.tw-record {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    background-color: var(--tw-bg-primary);
    /* Plegada, el panel se sale del flujo y se asoma encima del contenido:
       esto es lo que le da el marco de referencia. Ver «La ficha, plegada». */
    position: relative;
}

/* --- Barra superior con breadcrumb --- */
.tw-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    height: 40px;
    padding: 0 12px;
    background-color: var(--tw-bg-secondary);
    border-bottom: 1px solid var(--tw-border-light);
    flex: 0 0 auto;
}

.tw-page-title a {
    color: var(--tw-font-primary);
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 4px;
    border-radius: var(--tw-radius-sm);
}

.tw-page-title a:hover {
    background-color: var(--tw-bg-transparent-light);
}

/* --- Panel izquierdo (campos) --- */
/* Tres zonas: identidad fija arriba, campos scrolleando en medio y el bloque
   de relaciones anclado abajo, siempre a la vista. */
.tw-record-panel {
    width: var(--tw-record-panel-width);
    flex: 0 0 var(--tw-record-panel-width);
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    border-right: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-primary);
}

/* ── La ficha, plegada · el riel y la ojeada ──────────────────────────
 *
 * El panel se lleva 400 px —340 por debajo de 1400— en las diecisiete
 * fichas del panel, y quien abre «Partidas», «Embarques» o «Producción»
 * está mirando la otra mitad: una tabla de ocho columnas apretada contra
 * el borde derecho, mientras a la izquierda siguen quince rótulos que ya
 * se leyeron al entrar.
 *
 * ── Plegada no es desaparecida ───────────────────────────────────────
 *
 * La primera versión escondía el panel entero y la pantalla se quedaba sin
 * decir que existía: un botón en la barra de título es un botón, no una
 * pista de qué hay debajo. Queda un riel de 44 px con el icono del
 * registro y la palabra «Campos» de canto —se ve que ahí vive algo—, y al
 * pasar el cursor por encima el panel **se asoma**: sale flotando sobre el
 * contenido, se lee el dato que hacía falta y se retira solo al salir. Un
 * clic en el riel lo devuelve a su sitio para siempre.
 *
 * ── Por qué se asoma flotando y no empujando ─────────────────────────
 *
 * Porque abrirse y cerrarse dentro del flujo mueve la columna de la
 * derecha en cada pasada del ratón, y ahí hay tablas: cada rebote sería un
 * reparto de columnas de DataTables. Plegado, el panel sale del flujo
 * —`position: absolute` sobre la ficha, que por eso es `relative`— y quien
 * ocupa su hueco es el riel. Al asomarse no se mueve un píxel de la tabla.
 *
 * ── La excepción declara que lo es ───────────────────────────────────
 *
 * `:not([data-ficha-fija])`. Dos pantallas usan este panel como riel de
 * trabajo y no como ficha de campos —el alta de una orden de producción y
 * la importación de un nesteo, donde a la izquierda están los nesteos que
 * hay que elegir— y ahí plegar sería esconder la pantalla. Esas dos lo
 * marcan en su plantilla; las demás, que son fichas, se pliegan.
 */
html[data-ficha='plegada'] .tw-record > .tw-record-panel:not([data-ficha-fija]) {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 20;
    box-shadow: var(--tw-shadow-strong);
    /* Invisible y **sin recoger el ratón**: `opacity: 0` a secas dejaría una
       plancha de 400 px por delante de la tabla, y el clic en la primera
       columna no llegaría nunca a su renglón. */
    visibility: hidden;
    opacity: 0;
    transition:
        opacity var(--tw-duration-base) var(--tw-ease),
        visibility var(--tw-duration-base) var(--tw-ease);
}

/* La ojeada · mientras el ratón está en el riel o en el propio panel.
 *
 * Los dos hacen falta y no sobra ninguno: el riel es quien la abre, y el
 * panel —que sale justo encima del riel— es quien la mantiene mientras se
 * lee. Con `:focus-within` la misma ojeada llega desde el teclado, que es
 * como se recorre una ficha sin ratón. */
html[data-ficha='plegada'] .tw-ficha-riel:hover + .tw-record-panel:not([data-ficha-fija]),
html[data-ficha='plegada'] .tw-ficha-riel:focus-visible + .tw-record-panel:not([data-ficha-fija]),
html[data-ficha='plegada'] .tw-record > .tw-record-panel:not([data-ficha-fija]):hover,
html[data-ficha='plegada'] .tw-record > .tw-record-panel:not([data-ficha-fija]):focus-within {
    visibility: visible;
    opacity: 1;
    /* Un pelo de espera para entrar y ninguna para salir: el riel está pegado
       al borde de la ficha y se cruza sin querer al ir a cualquier otro sitio.
       Sin la espera, cada pasada del ratón por la izquierda de la pantalla
       sacaba un panel de 400 px. */
    transition-delay: 0.15s;
}

/* El riel · lo pone `ficha-plegar.js` al lado del panel, y sólo se ve
   cuando la ficha está plegada. Es un botón entero: se pulsa donde sea. */
.tw-ficha-riel {
    display: none;
}

html[data-ficha='plegada'] .tw-record > .tw-ficha-riel {
    display: flex;
    flex: 0 0 var(--tw-record-rail-width);
    width: var(--tw-record-rail-width);
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 16px 0;
    border: 0;
    border-right: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-primary);
    color: var(--tw-font-light);
    font-family: var(--tw-font-family);
    font-size: var(--tw-font-size-md);
    cursor: pointer;
    transition: background-color var(--tw-transition);
}

html[data-ficha='plegada'] .tw-record > .tw-ficha-riel:hover {
    background-color: var(--tw-bg-secondary);
    color: var(--tw-font-primary);
}

/* «Campos», de canto. `vertical-rl` con la vuelta de 180 grados lo deja
   leyéndose de abajo arriba, que es como va el rótulo de un lomo. */
.tw-ficha-riel__texto {
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    letter-spacing: 0.02em;
}

/* El icono del registro, clonado de la identidad: es lo que dice de qué
   ficha se trata sin abrirla. Más chico que el de arriba —28 en vez de
   40— porque aquí no encabeza nada, sólo señala. */
.tw-ficha-riel .tw-record-avatar {
    width: 28px;
    height: 28px;
    font-size: var(--tw-font-size-md);
}

.tw-ficha-riel .tw-record-avatar .ti {
    font-size: 1rem !important;
}

/* El galón · el mismo icono que el botón de la barra, cerrando el grupo.
   Pegado al rótulo y no en el pie del riel: los tres —icono del registro,
   «Campos» y galón— son una sola cosa que se lee de un vistazo, y un icono
   solo a ochocientos píxeles de distancia no acompaña a nada. Acostado, en
   cambio, se va a la derecha: ahí lo que sobra es el ancho. */
.tw-ficha-riel__galon {
    margin-top: 4px;
    font-size: var(--tw-icon-size) !important;
}

/* El botón de la barra de título lo pone `ficha-plegar.js`. Va pegado al
   rastro de migas y no entre las acciones de la derecha: no es algo que se
   le haga al registro, es cómo se está mirando. */
.tw-ficha-plegar {
    flex: 0 0 auto;
    margin-right: 2px;
    color: var(--tw-font-light);
}

/* El rastro de migas se queda pegado al botón, no se va al centro.
 *
 * La barra reparte con `space-between`, y eso con dos hijos —título y
 * acciones— es «uno a cada extremo». Al meter el botón delante pasaron a ser
 * tres y el reparto hizo lo que le toca: el del medio, al medio. El título
 * quedaba flotando en mitad de la pantalla, lejos del registro que nombra.
 *
 * El margen automático se come el hueco antes que el reparto: botón y título
 * a la izquierda, acciones a la derecha, y el hueco entero en medio. Sólo
 * donde está el botón, que es donde apareció el problema. */
.tw-ficha-plegar + .tw-page-title {
    margin-right: auto;
}

.tw-record-fields {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0 12px 16px;
}

/* La identidad no scrollea: al recorrer campos siempre se sabe de qué
   registro se están viendo. */
.tw-record-identity {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 24px 12px 20px;
    border-bottom: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-primary);
}

.tw-record-avatar {
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    overflow: hidden;
}

.tw-record-avatar .ti {
    font-size: 1.25rem !important;
}

/* El tamaño lo define .tw-record-title en la escala de encabezados; aquí
   sólo se centra dentro del panel de identidad. */
.tw-record-identity .tw-record-title {
    text-align: center;
}

.tw-record-subtitle {
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-light);
}

.tw-record-panel .tw-section-title {
    padding: 8px 4px;
}

/* Grupo colapsable de campos */
.tw-field-group {
    margin-bottom: 4px;
}

.tw-field-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 4px;
    background: none;
    border: 0;
    border-radius: var(--tw-radius-sm);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
    cursor: pointer;
}

.tw-field-group-header:hover {
    background-color: var(--tw-bg-transparent-light);
}

.tw-field-group-header .ti {
    font-size: 1rem !important;
    color: var(--tw-font-light);
    transition: transform 0.15s ease;
}

.tw-field-group-header[aria-expanded='false'] .ti {
    transform: rotate(-180deg);
}

.tw-field-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 4px 0 12px;
}

.tw-field {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    min-height: 24px;
    padding: 2px 4px;
    border-radius: var(--tw-radius-sm);
}

/*
   Un rótulo no se corta nunca.

   Iba con `white-space: nowrap` + `overflow: hidden` y **sin** puntos
   suspensivos: cualquier rótulo de más de ~106 px se cortaba a media palabra
   —«Piezas en la etiqu»— sin ninguna señal de que faltara texto y sin forma de
   leerlo, porque tampoco había `title`. Afectaba a todas las fichas del panel,
   no a una pantalla.

   La columna sigue midiendo 128 px para que los valores queden alineados entre
   sí; lo que cambia es que el rótulo que no cabe pasa a dos líneas en lugar de
   perderse. Los puntos suspensivos se descartaron a propósito: en una ficha el
   rótulo es lo que da sentido al valor, y medio rótulo con «…» sigue sin
   poderse leer.
*/
.tw-field-label {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    flex: 0 0 128px;
    min-height: 20px;
    padding-top: 1px;
    font-size: var(--tw-font-size-md);
    line-height: 1.35;
    color: var(--tw-font-tertiary);
    white-space: normal;
    overflow-wrap: break-word;
}

/* La misma lista con la columna de rótulos más ancha. Es para las fichas dentro
   de un modal, donde hay sitio de sobra y rótulos largos —«Orden de producción»,
   «Nota de quien contó»— que en 128 px se parten en dos líneas cada uno y
   convierten una lista de quince campos en una escalera. */
.tw-field-list--wide .tw-field-label {
    flex-basis: 168px;
}

.tw-field-label .ti {
    font-size: 1rem !important;
    color: var(--tw-font-light);
    flex: 0 0 auto;
    /* Que el icono quede sobre la primera línea cuando el rótulo va a dos. */
    line-height: 1.35;
}

.tw-field-value {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 20px;
    display: flex;
    align-items: center;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
    padding: 0 4px;
    border-radius: var(--tw-radius-sm);
    word-break: break-word;
}

.tw-field-value.tw-editable {
    cursor: pointer;
}

.tw-field-value.tw-editable:hover {
    background-color: var(--tw-bg-transparent-light);
}

.tw-field-value.tw-empty {
    color: var(--tw-font-extra-light);
}

/* El texto que trae sus propios saltos de línea —un motivo de cancelación, una
   nota de recepción—. Vivía sólo dentro de `.tw-field-value`, y el mismo texto
   fuera de un campo —la tarjeta de «Cancelada», en la ficha del nesteo y en la
   de la orden de producción— perdía los saltos sin que se notara al escribirlo. */
.tw-multiline {
    white-space: pre-wrap;
    line-height: 1.45;
}

/* ── Dos bloques al lado, si el sitio da ─────────────────────────────────────

   Reparte los hijos en columnas **según lo que mida la caja**, y no según lo
   que mida la ventana. Es lo que `row` + `col-lg-*` de Bootstrap no puede
   hacer: sus puntos de corte miran el `viewport`, y estas tarjetas viven en el
   panel derecho de una ficha, que mide lo mismo con la ventana a 1440 que a
   2560 —lo que crece es el hueco de al lado—. Con 570 px de panel, «Material»
   y «Certificado de calidad» caían a 220 y 320 px: el rótulo se lleva 128 fijos
   y en lo que quedaba, «Sin definir» iba a dos líneas y un lote —
   `REC-2026-00013-L030`— se partía letra a letra en cuatro.

   Con `auto-fit` los dos bloques se ponen uno debajo del otro en cuanto no
   caben a 300 px, que es cuando dejan de leerse; en un panel ancho vuelven a
   ponerse al lado solos. El `min(100%, …)` es para que en una caja más estrecha
   que el mínimo la columna encoja en vez de desbordar. */
.tw-cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--tw-cols-min, 300px)), 1fr));
    gap: 16px 24px;
    align-items: start;
}

/* Bloque de relaciones al pie del panel (People / Opportunities en Twenty) */
.tw-relation-block {
    border-top: 1px solid var(--tw-border-light);
    padding: 12px;
    margin-top: 0;
}

/* Dentro del panel de registro se ancla abajo y scrollea por dentro si crece */
.tw-record-panel > .tw-relation-block {
    flex: 0 1 auto;
    min-height: 0;
    max-height: 40%;
    overflow-y: auto;
    background-color: var(--tw-bg-primary);
}

/* Una tarjeta suelta en el panel —el aviso de «Cancelado», en la ficha del
   nesteo y en la de la orden de producción— no se encoge. El panel es una
   columna flex y la tarjeta recorta lo que le sobra: sin esto, el último
   renglón del aviso —quién canceló y cuándo— se pierde detrás del borde. Quien
   cede el espacio es la lista de campos, que tiene su propio scroll. */
.tw-record-panel > .tw-card {
    flex: 0 0 auto;
}

.tw-relation-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 4px 8px;
}

.tw-relation-header h4 {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    margin: 0;
}

.tw-relation-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.tw-relation-item {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px;
    border-radius: var(--tw-radius-sm);
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
}

.tw-relation-item:hover {
    background-color: var(--tw-bg-transparent-light);
}

/* --- Panel derecho (tabs) --- */
.tw-record-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background-color: var(--tw-bg-primary);
}

/* La barra de pestañas se define una sola vez, junto a .nav-tabs.
   Aquí sólo lo específico de la vista de registro. */
.tw-record-tabs {
    flex: 0 0 auto;
    padding: 0 12px;
}

.tw-record-tabs .nav-item {
    flex: 0 0 auto;
}

/* ── Pestañas con las vistas al lado ─────────────────────────────────────
   Las pestañas cambian de pantalla; el segmentado de la derecha cambia cómo
   se mira ésta —las vistas de la torre, el recorrido y el panorama de
   Seguimiento—. Van en el mismo renglón y con dos formas distintas para que no
   se lean como dos filas de lo mismo, y dentro de la tarjeta porque son de lo
   que hay dentro de ella, no de la página.

   Cada mitad lleva su borde de abajo y las dos se estiran al mismo alto: así
   la línea sale corrida y el subrayado de la pestaña activa sigue cayendo
   encima. Si no caben, las vistas bajan a su propio renglón y lo llenan. */
.tw-tabs-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    flex: 0 0 auto;
    min-width: 0;
}

.tw-tabs-bar > .tw-record-tabs {
    flex: 999 1 auto;
    min-width: 0;
}

.tw-tabs-bar-end {
    flex: 1 0 auto;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    /* Sin relleno vertical: con él esta mitad pasaba de los 38 px de las
       pestañas, estiraba la barra y el subrayado quedaba flotando tres
       píxeles sobre la línea. El alto lo ponen las pestañas. */
    padding: 0 12px;
    border-bottom: 1px solid var(--tw-border-light);
}

/* Cuando la pestaña contiene una tabla, ocupa el alto restante en lugar de
   scrollear con padding como el contenido de texto. */
.tw-record-main > .tab-content {
    flex: 1 1 auto;
    min-height: 0;
}

/* La pantalla de listado con pestañas: la tarjeta manda el alto y quien se
   desplaza es la lista de dentro.

   Como elemento flex, `.tw-record-main` nace con `min-height: auto` y se
   estira con su contenido; la tarjeta recorta con `overflow: hidden`, así que
   una lista larga se cortaba por abajo **sin barra de desplazamiento**: no
   había forma de llegar al último renglón. Es la misma trampa que documenta
   `.page-wrapper` más arriba, un nivel más adentro. Va acotado a las que
   cuelgan de una tarjeta para no tocar la ficha de registro, donde el
   desplazamiento es de la página. */
.tw-panel > .tw-record-main {
    min-height: 0;
}

.tw-record-main > .tab-content > .tab-pane.active {
    height: 100%;
}

/* El contenido de una pestaña de texto, **acotado de verdad**.
 *
 * `.tw-record-content` promete desplazarse —trae `overflow-y: auto`— pero sus
 * `flex` no aplican: cuelga de un `.tab-pane`, que es block. Sin alto propio
 * crecía con su contenido, la tarjeta recortaba con `overflow: hidden` y una
 * pestaña larga —el checklist de doce puntos por renglón— se cortaba por abajo
 * **sin barra**: no había forma de llegar a Bloquear. El alto del pane es el
 * límite que le faltaba. En un teléfono no aplica: la regla del bloque
 * responsive lo devuelve a `height: auto` y quien desplaza es la página. */
.tw-record-main > .tab-content > .tab-pane.active > .tw-record-content {
    height: 100%;
}

/* Listado cuyas filas abren un registro: el cursor lo dice antes de pulsar.
   La clase la pone `filaAbre()` sobre la tabla que de verdad engancha el clic,
   no la vista: escrita a mano se desincronizaba —Embarques prometía el cursor
   y no abría nada— y hay tablas de solo lectura con el mismo aspecto. */
.tw-rows-link tbody > tr,
/* Y su equivalente en las tablas escritas a mano, que declaran el destino en
   el propio renglón. Aquí el atributo es a la vez la conducta y el cursor: no
   hay forma de que uno exista sin el otro. */
tr[data-abre] {
    cursor: pointer;
}

/* Columna que es sólo control (marcar, botonera): ahí la fila no abre, y el
   cursor tiene que dejar de prometerlo. */
.tw-rows-link tbody > tr > td.tw-cell-control,
tr[data-abre] > td.tw-cell-control {
    cursor: default;
}

/* Una fila que abre se separa del resto al apuntarla, aunque su tabla no lleve
   `.table-hover`: es la mitad de la promesa que hace el cursor. */
tr[data-abre]:hover > td {
    background-color: var(--tw-bg-transparent-light);
}

/* Celda de identidad: la columna que dice qué registro es la fila.
   Va en negrita y con el color del texto —no en azul de enlace— porque el
   destino ya lo anuncia la fila entera; el subrayado aparece al apuntar, que
   es cuando la pregunta «¿esto es pulsable?» se hace. Las referencias a otro
   registro son `.tw-chip`, y que se lean distinto es lo que evita confundir
   «esto es la fila» con «esto lleva a otra parte». */
.tw-record-link {
    font-weight: 600;
    color: var(--tw-font-primary);
    text-decoration: none;
}

.tw-record-link:hover {
    color: var(--tw-font-primary);
    text-decoration: underline;
}

.tw-record-link:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 2px;
    border-radius: var(--tw-radius-sm);
}

/* Pestaña cuyo contenido es una tabla: se apila en columna para que el cuerpo
   scrolleable ocupe el alto restante, en lugar de scrollear la pestaña entera.
   Va con `.active` a propósito: `display: flex` suelto vencería al
   `display: none` de Bootstrap y enseñaría las pestañas inactivas. */
.tw-record-main > .tab-content > .tab-pane.tw-pane-fill.active {
    display: flex;
    flex-direction: column;
}

.tw-record-content {
    /* El relleno lateral, con nombre: la tabla que va de orilla a orilla lo
       descuenta con un margen negativo y tiene que ser el mismo número. */
    --tw-record-content-gutter: 24px;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 16px var(--tw-record-content-gutter) 32px;
}

/* ── La tabla llega al filo del panel ────────────────────────────────────────

   Una tabla no lleva margen lateral en ninguna otra pantalla: en los listados
   ocupa el ancho entero de la tarjeta y la primera columna arranca en el filo.
   Dentro de una ficha se quedaba metida 24 px por cada lado —el relleno del
   contenido— y con eso se veía despegada del panel y perdía 48 px de los pocos
   que tiene. Los descuenta con un margen negativo: el título y el texto siguen
   con su sangría y la tabla, no.

   Vale para las dos cajas que envuelven una tabla: la de aquí
   —`.tw-table-scroll`— y la `.table-responsive` de Bootstrap, que es la que
   usan las pestañas escritas antes. Sólo como hijas directas: una tabla dentro
   de una tarjeta lleva el relleno de la tarjeta y ahí sí lo quiere. */
.tw-record-content > :is(.tw-table-scroll, .table-responsive),
.tw-record-content > .tab-content > .tab-pane > :is(.tw-table-scroll, .table-responsive) {
    margin-inline: calc(var(--tw-record-content-gutter) * -1);
}

.tw-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 64px 16px;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-md);
    text-align: center;
}

.tw-empty-state .ti {
    font-size: 1.5rem !important;
    color: var(--tw-font-extra-light);
}

/* El icono grande es el del estado vacío, no el de sus botones: un «Ver la
   bitácora» dentro del vacío salía con el icono a 1.5rem, el doble que en
   cualquier otro botón. Dentro de un botón manda la escala de botones. */
.tw-empty-state .btn .ti {
    font-size: 0.875rem !important;
    color: inherit;
}

/* Estado vacío con explicación: título de la sección que falta y una línea
   que dice qué hacer mientras tanto. Un "Próximamente" suelto no bastaba —
   no aclaraba si la función faltaba o si algo había fallado. */
.tw-empty-state-title {
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-secondary);
}

.tw-empty-state-text {
    max-width: 420px;
    line-height: 1.5;
}

.tw-empty-state-hint {
    max-width: 420px;
    line-height: 1.5;
    color: var(--tw-font-tertiary);
}

.tw-empty-state-hint strong {
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
}

/* Tarjeta compacta reutilizable dentro del panel derecho */
.tw-card {
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    overflow: hidden;
}

.tw-card + .tw-card {
    margin-top: 8px;
}

/**
 * …salvo cuando las coloca una rejilla.
 *
 * El margen de arriba está pensado para tarjetas **apiladas en una
 * columna**, donde separa una de la siguiente. Dentro de una rejilla el
 * hueco ya lo pone `gap`, y el margen de hermano adyacente sigue
 * aplicándose igual: baja 8 px **todas las tarjetas menos la primera**, así
 * que en el tablero la de la derecha arrancaba ocho píxeles por debajo de
 * la de su izquierda y las cabeceras no cuadraban.
 *
 * Es un desajuste pequeño y constante, del tipo que se mira diez veces sin
 * saber qué es.
 */
.tw-dash-grid > .tw-card + .tw-card,
.tw-card-grid > .tw-card + .tw-card,
.tw-links-grid > .tw-card + .tw-card,
.tw-docs-grid > .tw-card + .tw-card {
    margin-top: 0;
}

.tw-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 12px;
    min-height: 40px;
}

.tw-card-header h5 {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    margin: 0;
}

/* Un título de tarjeta que además lleva icono.

   El `h5` es un bloque, así que un `.ti` dentro se alinea por la línea base y
   con `line-height: 1` —que es lo que trae la fuente de iconos— sube un par de
   píxeles sobre el texto y se lee torcido. En fila y centrado se sienta a la
   altura de la palabra, y el icono se acota al tamaño de icono en vez de
   heredar el del título, que lo dejaba más grande que la letra que acompaña.

   Va con `:has()` y no con una clase aparte: un título con icono ya se
   distingue por llevarlo, y una clase que hay que acordarse de poner es una
   clase que el siguiente título se olvida. */
.tw-card-header h5:has(> .ti) {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.tw-card-header h5 > .ti {
    flex: 0 0 auto;
    font-size: var(--tw-icon-size) !important;
    line-height: 1;
    color: var(--tw-font-light);
}

.tw-card-body {
    border-top: 1px solid var(--tw-border-light);
    padding: 12px;
}

.tw-meta {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* Rejilla de miniaturas (imágenes de pieza / revisión) */
.tw-thumb-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.tw-thumb {
    position: relative;
    width: 72px;
    height: 72px;
    border-radius: var(--tw-radius-md);
    border: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-tertiary);
    background-size: cover;
    background-position: center;
    overflow: hidden;
}

.tw-thumb-add {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 72px;
    height: 72px;
    border-radius: var(--tw-radius-md);
    border: 1px dashed var(--tw-border-strong);
    background-color: var(--tw-bg-secondary);
    color: var(--tw-font-tertiary);
    cursor: pointer;
}

.tw-thumb-add:hover {
    border-color: var(--tw-accent);
    color: var(--tw-accent);
    background-color: var(--tw-accent-tertiary);
}

.tw-thumb .tw-thumb-remove {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 20px;
    height: 20px;
    min-height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-primary);
    color: var(--tw-tag-red-fg);
    opacity: 0;
    transition: opacity 0.1s ease;
    cursor: pointer;
}

.tw-thumb:hover .tw-thumb-remove {
    opacity: 1;
}

.tw-thumb .tw-thumb-remove .ti {
    font-size: 0.875rem !important;
}

/* Lista de archivos */
.tw-file-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 8px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    font-size: var(--tw-font-size-md);
}

.tw-file-row + .tw-file-row {
    margin-top: 4px;
}

.tw-file-row:hover {
    background-color: var(--tw-bg-transparent-lighter);
}

.tw-file-name {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-file-name .ti {
    color: var(--tw-font-tertiary);
    flex: 0 0 auto;
}

/* Timeline */
.tw-timeline {
    display: flex;
    flex-direction: column;
    gap: 0;
    position: relative;
}

.tw-timeline-item {
    display: flex;
    gap: 12px;
    padding-bottom: 16px;
    position: relative;
}

.tw-timeline-item:not(:last-child)::before {
    content: '';
    position: absolute;
    left: 11px;
    top: 24px;
    bottom: 0;
    width: 1px;
    background-color: var(--tw-border-medium);
}

.tw-timeline-icon {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-tertiary);
    color: var(--tw-font-tertiary);
    z-index: 1;
}

.tw-timeline-icon .ti {
    font-size: 0.875rem !important;
}

.tw-timeline-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-secondary);
    padding-top: 2px;
}

.tw-timeline-body strong {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

/* --- Bitácora con una acción por renglón ---------------------------------

   EL CASO QUE ESTO ARREGLA. La bitácora de Inicio pone la frase y la hora en
   la misma línea, repartidas con `space-between`, y eso funciona mientras el
   renglón sea sólo texto. Al añadirle un botón —«Ver desglose», que abre los
   códigos de una tanda de impresión— las tres cosas se pelearon por los 381 px
   de la tarjeta: el botón pedía 92 y se quedaba con 63, o sea recortado a
   «Ver desglo…», y la hora se partía en dos renglones —«hace 2» / «h»—.

   Peor que feo: **nada quedaba en columna**. La hora vive dentro del cuerpo, y
   el cuerpo mide distinto según el renglón lleve botón o no, así que las horas
   acababan en tres posiciones distintas y los botones en cinco anchos.

   LO QUE HACE. La hora baja debajo de la frase —que es como se lee un renglón
   en el resto de este tablero, ver `tw-dash-row`— y la acción se queda sola en
   su columna, sin encoger. Con un único texto en todos los botones, la columna
   es la misma en todos los renglones y se alinean sin fijar ningún ancho.

   Va como modificador y no en `.tw-timeline` a secas porque las otras dos
   bitácoras —el recorrido del contenedor y el pasaporte de la pieza— no llevan
   acción y ahí la hora al lado de la frase se lee mejor. */
.tw-timeline--actions .tw-timeline-body {
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 2px;
}

/* La hora, ya como segundo renglón. En una línea: «hace 2 h» partido es una
   cifra sin su unidad encima de la unidad sin su cifra. */
.tw-timeline--actions .tw-timeline-body .tw-meta {
    white-space: nowrap;
}

/* La acción no encoge y no parte su rótulo: es lo que la dejaba en «Ver
   desglo…». Centrada contra las dos líneas del cuerpo, que es donde el ojo la
   busca. */
.tw-timeline-action {
    flex: 0 0 auto;
    align-self: center;
}

/* =====================================================================
   13. Componentes del design system aún no usados en la app.
   Están listos para las pantallas de tareas y tableros que vienen después.
   ===================================================================== */

/* --- Etiqueta de estado (columna de tablero, campo de selección) --- */
.tw-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: var(--tw-radius-pill);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    line-height: 18px;
    white-space: nowrap;
    background-color: var(--tw-tag-gray-bg);
    color: var(--tw-tag-gray-fg);
}

/* El distintivo ya centra por `align-items`, pero el icono heredaba el tamaño
   del texto del distintivo y salía del alto de la píldora. El de dentro de un
   chip es el chico, igual que en los botones. El color no se toca: un icono de
   aviso tiene que ir del color del aviso. */
.tw-tag > .ti {
    flex: 0 0 auto;
    font-size: var(--tw-icon-size-sm) !important;
    line-height: 1;
}

/* El distintivo que se pulsa · abre el desglose de lo que dice.
   Es un botón de verdad —se llega con el tabulador y se dispara con Intro— y no
   un enlace falso: no lleva a otra pantalla, abre un diálogo encima de ésta. Lo
   único que necesita del navegador es que no le pinte su borde ni su fondo. */
button.tw-tag {
    border: 0;
    cursor: pointer;
    font-family: inherit;
    appearance: none;
}

button.tw-tag:hover {
    filter: brightness(1.08);
}

.tw-tag--green {
    background-color: var(--tw-tag-green-bg);
    color: var(--tw-tag-green-fg);
}
.tw-tag--turquoise {
    background-color: var(--tw-tag-turquoise-bg);
    color: var(--tw-tag-turquoise-fg);
}
.tw-tag--sky {
    background-color: var(--tw-tag-sky-bg);
    color: var(--tw-tag-sky-fg);
}
.tw-tag--blue {
    background-color: var(--tw-tag-blue-bg);
    color: var(--tw-tag-blue-fg);
}
.tw-tag--purple {
    background-color: var(--tw-tag-purple-bg);
    color: var(--tw-tag-purple-fg);
}
.tw-tag--pink {
    background-color: var(--tw-tag-pink-bg);
    color: var(--tw-tag-pink-fg);
}
.tw-tag--red {
    background-color: var(--tw-tag-red-bg);
    color: var(--tw-tag-red-fg);
}
.tw-tag--orange {
    background-color: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
}
.tw-tag--yellow {
    background-color: var(--tw-tag-yellow-bg);
    color: var(--tw-tag-yellow-fg);
}

/* --- Barra de vista: nombre de la vista, conteo y acciones --- */
.tw-view-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-height: 40px;
    padding: 0 12px;
    border-bottom: 1px solid var(--tw-border-light);
    flex: 0 0 auto;
}

.tw-view-name {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

.tw-view-name .tw-count-badge {
    color: var(--tw-font-light);
    font-weight: var(--tw-weight-regular);
}

.tw-view-actions {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* --- Alcance por revisión ---
   La barra de las tres recetas del número de parte: insumos, componentes y
   ruta. Elige de qué revisión se está hablando, y con eso la pantalla enseña
   una sola lista —la que aplica— en vez de apilar la base y las excepciones.

   Es una pestaña más pequeña que `.tw-record-tabs` a propósito: vive dentro de
   un panel que ya tiene sus pestañas arriba, y tienen que leerse como dos
   niveles distintos. El punto marca la revisión que tiene la suya. */
/* `flex-end` y no `center`: el subrayado de la pestaña activa es su borde
   inferior, y tiene que caer sobre el borde de la barra. Centrado, el botón
   —más alto que las pestañas— las levantaba y el subrayado quedaba flotando.
   El `min-height` evita que la barra dé un salto al aparecer ese botón. */
.tw-scope {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 8px;
    flex-wrap: wrap;
    min-height: 38px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-scope-tabs {
    display: flex;
    align-items: stretch;
    gap: var(--tw-tab-gap);
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}

.tw-scope-tabs::-webkit-scrollbar {
    display: none;
}

.tw-scope-tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 30px;
    padding: 0 4px;
    border: 0;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    background-color: transparent;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-tertiary);
    white-space: nowrap;
    flex: 0 0 auto;
    transition: color var(--tw-transition);
}

.tw-scope-tab:hover {
    color: var(--tw-font-primary);
    border-bottom-color: var(--tw-border-strong);
}

.tw-scope-tab.is-active {
    color: var(--tw-font-primary);
    border-bottom-color: var(--tw-gray-90);
}

.tw-scope-dot {
    width: 5px;
    height: 5px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-font-light);
    flex: 0 0 auto;
}

.tw-scope-tab.is-active .tw-scope-dot {
    background-color: var(--tw-gray-90);
}

.tw-scope-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    padding-bottom: 4px;
}

/* La fila que se está viendo prestada: es de la base, se enseña porque esta
   revisión la hereda, y por eso no trae controles. El trazo discontinuo dice
   que no vive aquí sin tener que escribirlo debajo de cada renglón. */
.tw-card.is-inherited {
    background-color: var(--tw-bg-transparent-lighter);
    border-style: dashed;
}

.tw-card.is-inherited .tw-card-body {
    color: var(--tw-font-secondary);
}

/* El renglón del selector que no se puede marcar: la placa sin etiqueta, y
   —con el cupo del programa lleno— todas las que no alcanzaron lugar. Se apaga
   entero, no sólo la casilla: una casilla gris junto a un renglón vivo se lee
   como un fallo de la pantalla, no como un estado de la placa. */
.tw-pick-row.is-off,
.tw-pick-group.is-off {
    opacity: 0.5;
}

.tw-pick-row.is-off:hover,
.tw-pick-group.is-off:hover {
    background-color: transparent;
}

/* --- Por dónde pasa: la ruta dibujada como recorrido -------------------
   Los extremos no se capturan. Toda pieza sale de Almacén y acaba en Empaque
   —eso es la nave, no la ruta del número de parte—, así que se dibujan siempre
   y sin controles: con ellos delante, lo capturado se lee como un recorrido
   completo y el hueco que falta se ve dónde va.

   Y por eso los extremos no llevan número: el número cuenta pasos de la ruta,
   que es lo único que aquí se decide. Numerar las dos paradas fijas correría
   toda la cuenta y prometería que se pueden mover.

   La línea de la izquierda es la que convierte la lista en recorrido. Va por la
   columna de las marcas, se sale del hueco entre filas para tocar a la vecina y
   se recorta antes del primer hito y después del último, para que el recorrido
   empiece y acabe en una parada y no en el aire. Es la idea de `.st-chain` del
   kiosco, escrita aparte porque aquí las filas son tarjetas. */
.tw-ruta {
    --tw-ruta-hueco: 8px;
    display: flex;
    flex-direction: column;
    gap: var(--tw-ruta-hueco);
}

.tw-ruta-fila {
    display: flex;
    align-items: stretch;
    gap: 8px;
}

/* La tarjeta del paso ocupa lo que quede. `min-width: 0` para que una nota
   larga se recorte dentro en vez de empujar la columna de las marcas. */
.tw-ruta-fila > .tw-card {
    flex: 1 1 auto;
    min-width: 0;
}

/* La columna de las marcas. Ancho fijo: así el número, el hito y la línea caen
   siempre en el mismo eje, lleve la fila asidero de arrastre o no. */
.tw-ruta-marca {
    position: relative;
    flex: 0 0 auto;
    width: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.tw-ruta-marca::before {
    content: '';
    position: absolute;
    left: 50%;
    top: calc(var(--tw-ruta-hueco) * -1);
    bottom: calc(var(--tw-ruta-hueco) * -1);
    width: 1px;
    margin-left: -0.5px;
    background-color: var(--tw-border-strong);
}

.tw-ruta > :first-child .tw-ruta-marca::before {
    top: 50%;
}

.tw-ruta > :last-child .tw-ruta-marca::before {
    bottom: 50%;
}

/* El número del paso. El fondo es el de la tarjeta a propósito: tapa la línea
   que lo cruza, que si no le sale por arriba y por abajo. */
.tw-ruta-num {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border: 1px solid var(--tw-border-strong);
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-primary);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-secondary);
}

/* El paso que todavía no existe: el círculo vacío del hueco. */
.tw-ruta-num--hueco {
    border-style: dashed;
}

/* Una parada fija. Disco lleno y con icono, no círculo con número: se distingue
   de un paso antes de leer nada. */
.tw-ruta-hito {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-secondary);
}

/* El icono se dice aquí y no en el disco: el tamaño de `.ti` lo fija la fuente
   de iconos y sólo se le gana desde el propio icono, como en los demás discos
   del sistema. Vive dentro de sus 24 px con aire alrededor —lleno de lado a
   lado parecía un botón—. */
.tw-ruta-hito .ti {
    font-size: 0.875rem !important;
}

/* Sin tarjeta: una parada fija no es un registro que se pueda abrir ni editar,
   y darle el mismo marco que a los pasos invitaba a pulsarla. */
.tw-ruta-extremo {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    min-height: 32px;
    padding: 4px 0;
    font-size: var(--tw-font-size-md);
}

.tw-ruta-extremo-nombre {
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
}

.tw-ruta-extremo-nota {
    color: var(--tw-font-light);
}

/* Cuando lo que dice la parada es que algo no cuadra —ningún paso recoge el
   material que sale de aquí—. Naranja y no rojo: la ruta se puede seguir
   capturando, lo que no se puede es cerrarla así. */
.tw-ruta-extremo-nota--aviso {
    color: var(--tw-tag-orange-fg);
}

/* Lo que falta, del tamaño de lo que falta: el trazo discontinuo dice que ahí
   va algo que todavía no se capturó. */
.tw-ruta-hueco {
    flex: 1 1 auto;
    min-width: 0;
    padding: 20px 16px;
    border: 1px dashed var(--tw-border-strong);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-transparent-lighter);
}

.tw-ruta-hueco-titulo {
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
}

/* El paso prestado de la base: el mismo trazo discontinuo que su tarjeta. */
.tw-ruta-fila.is-inherited .tw-ruta-num {
    border-style: dashed;
    color: var(--tw-font-light);
}

/* --- Casilla de selección --- */
.tw-checkbox {
    appearance: none;
    width: 16px;
    height: 16px;
    margin: 0;
    border: 1px solid var(--tw-border-strong);
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-primary);
    cursor: pointer;
    position: relative;
    transition:
        background-color var(--tw-transition),
        border-color var(--tw-transition);
}

.tw-checkbox:hover {
    border-color: var(--tw-gray-50);
}

.tw-checkbox:checked {
    background-color: var(--tw-accent);
    border-color: var(--tw-accent);
}

.tw-checkbox:checked::after {
    content: '';
    position: absolute;
    left: 5px;
    top: 1px;
    width: 4px;
    height: 9px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

.tw-checkbox--round {
    border-radius: var(--tw-radius-pill);
}

/* --- Celda o campo sin valor --- */
.tw-empty-value {
    color: var(--tw-font-extra-light);
}

/* --- Chip de registro con avatar (relación) --- */
.tw-chip-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    border-radius: var(--tw-radius-xs);
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-xxs);
    font-weight: var(--tw-weight-semibold);
    overflow: hidden;
}

/* --- Tablero kanban --- */
.tw-board {
    display: flex;
    gap: 0;
    height: 100%;
    min-height: 0;
    overflow-x: auto;
    overflow-y: hidden;
}

.tw-board-column {
    display: flex;
    flex-direction: column;
    flex: 0 0 var(--tw-kanban-column-width);
    width: var(--tw-kanban-column-width);
    min-height: 0;
    padding: 12px;
    border-right: 1px solid var(--tw-border-light);
}

.tw-board-column-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding-bottom: 12px;
    flex: 0 0 auto;
}

.tw-board-column-header .tw-board-column-count {
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-md);
}

.tw-board-column-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.tw-board-card {
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    padding: 8px;
    cursor: pointer;
    transition:
        border-color var(--tw-transition),
        box-shadow var(--tw-transition);
}

.tw-board-card:hover {
    border-color: var(--tw-border-strong);
    box-shadow: var(--tw-shadow-light);
}

.tw-board-card-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    padding-bottom: 6px;
}

.tw-board-card-row {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 24px;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
}

.tw-board-card-row .ti {
    font-size: var(--tw-icon-size) !important;
    color: var(--tw-font-light);
    flex: 0 0 auto;
}

.tw-board-new {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 6px 4px;
    margin-top: 8px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: none;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-md);
    cursor: pointer;
}

.tw-board-new:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

/* --- Lista de tareas agrupada por estado --- */
.tw-task-group + .tw-task-group {
    margin-top: 24px;
}

.tw-task-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 0 4px 8px;
}

.tw-task-group-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-semibold);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--tw-font-secondary);
}

.tw-task-group-title .tw-count-badge {
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
    text-transform: none;
    letter-spacing: 0;
}

.tw-task-row {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 40px;
    padding: 6px 8px;
    border-bottom: 1px solid var(--tw-border-light);
    cursor: pointer;
}

.tw-task-row:hover {
    background-color: var(--tw-bg-transparent-lighter);
}

.tw-task-title {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
    white-space: nowrap;
}

.tw-task-body {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-task-meta {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 0 0 auto;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-secondary);
}

.tw-task-meta .ti {
    font-size: var(--tw-icon-size) !important;
    color: var(--tw-font-light);
}

.tw-task-row.is-done .tw-task-title {
    text-decoration: line-through;
    color: var(--tw-font-tertiary);
}

/* Fecha vencida */
.tw-due--overdue,
.tw-due--overdue .ti {
    color: var(--tw-tag-red-fg) !important;
}

/* --- Pie de tabla con agregados ("Count all", "Max of...") --- */
.tw-table-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    min-height: 32px;
    padding: 0 8px;
    border-top: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-primary);
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
    flex: 0 0 auto;
}

/* Se envuelve: la tarjeta recorta lo que sobresale (`overflow: hidden`), así
   que tres cifras en una tarjeta angosta perdían la última sin avisar. */
.tw-agg-list {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 16px;
}

.tw-agg {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 6px;
    border-radius: var(--tw-radius-sm);
    white-space: nowrap;
}

.tw-agg:hover {
    background-color: var(--tw-bg-transparent-light);
}

/* Cuando la cifra lleva a algún sitio. Sin subrayado: ya se distingue por el
   fondo del hover que la clase trae de serie, y una fila de agregados con dos
   subrayadas y tres no parece un error de maquetación. */
a.tw-agg {
    color: inherit;
    text-decoration: none;
}

.tw-agg-label {
    color: var(--tw-font-light);
}

.tw-agg-value {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

/* Responsive: el panel de campos pasa arriba en pantallas chicas */
@media (max-width: 991.98px) {
    .tw-page {
        height: auto;
        min-height: 100dvh;
        overflow: visible;
    }

    /**
     * `.tw-panel.tw-record` y no `.tw-record` a secas.
     *
     * Esta regla llevaba tiempo escrita y **no hacía nada**: la ficha usa
     * las dos clases y `.tw-panel.tw-record { flex-direction: row }` gana
     * por especificidad, así que en un teléfono el panel de campos se
     * quedaba con todo el ancho y la columna de pestañas medía **cero
     * píxeles**. La pantalla se veía «bien» —no desbordaba— y la mitad
     * derecha sencillamente no estaba.
     */
    .tw-panel.tw-record {
        flex-direction: column;
    }

    .tw-record-panel {
        width: 100%;
        flex: 0 0 auto;
        border-right: 0;
        border-bottom: 1px solid var(--tw-border-light);
        overflow: visible;
    }

    /* ── Plegada en un teléfono: el riel se acuesta ───────────────────
     *
     * Apilada, el panel no es una columna a la izquierda sino una franja
     * arriba, así que su riel tampoco puede ser un lomo de 44 px: se pone
     * de canto a canto y se lee de corrido, con el icono del registro
     * delante. Se toca y la ficha se abre.
     *
     * Y aquí no hay ojeada. Asomarse es un gesto de ratón —se entra, se
     * lee, se sale— y en una pantalla táctil el equivalente sería un panel
     * de setecientos píxeles tapando el contenido hasta tocar en otro
     * sitio. El riel abre la ficha y ya está. */
    html[data-ficha='plegada'] .tw-record > .tw-ficha-riel {
        flex: 0 0 auto;
        width: 100%;
        flex-direction: row;
        justify-content: flex-start;
        gap: 8px;
        padding: 8px 12px;
        border-right: 0;
        border-bottom: 1px solid var(--tw-border-light);
    }

    .tw-ficha-riel__texto {
        writing-mode: horizontal-tb;
        transform: none;
    }

    .tw-ficha-riel__galon {
        margin-top: 0;
        margin-left: auto;
        /* De canto abre a la derecha y acostado abre hacia abajo: el mismo
           icono, girado, dice por dónde va a salir el panel. */
        transform: rotate(90deg);
    }

    html[data-ficha='plegada'] .tw-record > .tw-record-panel:not([data-ficha-fija]) {
        display: none;
    }

    /* ── La cadena de alturas se suelta hasta el final ────────────────
     *
     * `.tw-page` ya dejaba de medir una pantalla exacta aquí arriba, pero
     * la cadena seguía apretada tres niveles más abajo. Con la ficha en
     * columna, al panel de campos le tocaba su alto natural —es
     * `flex: 0 0 auto`— y a la columna de contenido lo que sobrara de la
     * pantalla: **ocho píxeles**. La cadena de eslabones, los enlaces de
     * identidad y la línea de tiempo se dibujaban encima de los campos.
     * En la colada era peor —el contenido cuelga directo de la tarjeta,
     * que recorta con `overflow: hidden`— y las tres tablas quedaban
     * **fuera de alcance**: recortadas y sin barra, no había forma de
     * llegar a ellas.
     *
     * Lo que hay que soltar es el reparto, no sólo el recorte. Quien
     * desplaza en un teléfono es `.page-wrapper`, que ya trae
     * `overflow: auto`; lo que le impedía tener nada que desplazar era
     * que sus hijos se estiran y se **encogen** con la pantalla
     * (`flex: 1 1 auto`). Con `flex: 1 0 auto` siguen llenando una
     * pantalla corta y dejan de encogerse por debajo de su contenido:
     * miden lo que miden y el sobrante se desplaza. */
    .page-wrapper > *,
    .tw-panel,
    .tw-record-main {
        flex: 1 0 auto;
    }

    .tw-screen {
        height: auto;
        min-height: 0;
        overflow: visible;
    }

    .tw-panel {
        overflow: visible;
    }

    /* El cuerpo de una tarjeta **de contenido**, que es el que lleva la
     * utilidad `overflow-auto` escrita en la plantilla. El cuerpo pelado
     * —el que envuelve una tabla de DataTables— sigue recortando: es el
     * límite que mide `adjustScrollY`, y sin él la tabla pierde su alto.
     *
     * `!important` porque compite con `.overflow-auto` de Bootstrap, que
     * lo lleva puesto. Es justo lo que hacía que la regla de aquí abajo
     * llevara tiempo escrita sin surtir efecto. */
    .tw-panel-body.overflow-auto,
    .tw-record-content {
        height: auto;
        overflow: visible !important;
    }

    .tw-record-main > .tab-content {
        min-height: 0;
        overflow: visible;
    }

    /* Los campos dejan de tener desplazamiento propio: dentro de una
     * página que ya se desplaza, un segundo desplazamiento de dos dedos
     * es una trampa. Miden lo que miden. */
    .tw-record-fields {
        flex: 0 0 auto;
        overflow: visible;
    }
}

/* ── La excepción: el listado con tabla, en una tablet ────────────────
 *
 * Un cuerpo de tarjeta **pelado** —sin la utilidad `overflow-auto`— es por
 * definición «aquí va una tabla, y este cuerpo es quien recorta»: es el
 * límite que mide `adjustScrollY` para darle su alto. Soltarle la cadena
 * de alturas lo deja medir lo que mida la tabla, y entonces se desplazan
 * las dos cosas —la página y la tabla dentro— que es el desplazamiento
 * doble que hace sentir roto un listado.
 *
 * Por debajo de 768 px no aplica: ahí la tabla se parte en tarjetas, el
 * cuerpo scrolleable de DataTables se suelta y lo que fluye es la página,
 * que es lo correcto. Así que la excepción vive sólo en la franja de la
 * tablet, donde la tabla sigue siendo una tabla. */
@media (min-width: 768px) and (max-width: 991.98px) {
    .tw-screen:has(> .tw-panel > .tw-panel-body:not(.overflow-auto)) {
        height: 100%;
        overflow: hidden;
        flex: 1 1 auto;
    }

    .tw-screen:has(> .tw-panel > .tw-panel-body:not(.overflow-auto)) > .tw-panel {
        flex: 1 1 auto;
        overflow: hidden;
    }
}

/* ── Primero la respuesta, después los campos ─────────────────────────
 *
 * En una ficha de registro el panel de campos va a la izquierda, y al
 * apilarse en un teléfono cae **encima** del contenido. En la mayoría de
 * las pantallas eso está bien: los campos *son* el registro.
 *
 * En las de consulta no. Quien abre el pasaporte de una pieza o el
 * recorrido de un contenedor en un teléfono viene con la etiqueta en la
 * mano y una pregunta concreta —¿por dónde pasó?, ¿qué le falta?—, y esa
 * respuesta vive en la columna de contenido. Con el panel delante hay que
 * pasar setecientos píxeles de fichas para llegar a ella.
 *
 * `display: contents` disuelve el `<aside>` y sus dos hijos pasan a ser
 * elementos de la columna: la identidad se queda arriba —hay que saber de
 * qué registro se habla—, el contenido sube detrás y los campos bajan al
 * final, que es donde se consultan cuando ya se sabe qué se está mirando.
 *
 * Es opt-in y no el comportamiento de toda ficha: se pide desde la
 * plantilla con `tw-record--content-first`, porque cuál de las dos
 * columnas contesta la pregunta lo sabe la pantalla, no el tema. */
@media (max-width: 991.98px) {
    .tw-record--content-first .tw-record-panel {
        display: contents;
    }

    .tw-record--content-first .tw-record-identity {
        order: 1;
    }

    .tw-record--content-first .tw-record-main {
        order: 2;
    }

    .tw-record--content-first .tw-record-fields {
        order: 3;
        padding-top: 12px;
        border-top: 1px solid var(--tw-border-light);
    }
}

/* =====================================================================
   14. Tablero de Inicio
   Tarjetas de indicador, gráficas y listas de resumen. La sección de
   «módulos en construcción» reutiliza los mismos componentes con borde
   discontinuo, para que la muestra no se confunda con la operación real.
   ===================================================================== */

/* --- Lienzo scrolleable --- */
.tw-dash {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 0 var(--tw-screen-gutter) var(--tw-screen-gutter);
}

.tw-dash-section-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 12px 4px 8px;
}

.tw-dash-section-head:first-child {
    padding-top: 4px;
}

.tw-dash-note {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* Caben las que quepan, pero nunca más de `--tw-dash-cols`. Sin tope la última
   hilera se parte donde toque y deja un hueco del ancho de las que faltaron,
   que se lee como si la pantalla estuviera incompleta; con tope, el renglón se
   reparte entre esas columnas y las hileras salen parejas. Si de ahí sale una
   tarjeta más angosta que `--tw-dash-min` manda el mínimo y la rejilla vuelve
   a acomodar las que quepan. El tope de 99 es «sin tope»: la cuenta cae por
   debajo del mínimo y la rejilla se comporta como siempre.

   La primera regla es el respaldo para un navegador que no sepa dividir entre
   una variable: sin ella la rejilla se quedaría en una sola columna. */
.tw-dash-grid {
    --tw-dash-cols: 99;
    --tw-dash-min: 210px;
    display: grid;
    gap: 8px;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--tw-dash-min), 100%), 1fr));
    grid-template-columns: repeat(
        auto-fit,
        minmax(
            min(
                100%,
                max(
                    var(--tw-dash-min),
                    calc((100% - (var(--tw-dash-cols) - 1) * 8px) / var(--tw-dash-cols) - 0.5px)
                )
            ),
            1fr
        )
    );
    align-items: stretch;
}

.tw-dash-grid--wide {
    --tw-dash-min: 320px;
}

/* Una tarjeta que necesita el doble de ancho que sus vecinas */
.tw-dash-wide {
    grid-column: span 2;
}

@media (max-width: 991.98px) {
    .tw-dash-wide {
        grid-column: span 1;
    }
}

/* --- Tarjeta de indicador --- */
.tw-kpi {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 12px;
    min-height: 116px;
}

.tw-kpi-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

.tw-kpi-label .ti {
    font-size: var(--tw-icon-size) !important;
    color: var(--tw-font-light);
}

.tw-kpi-value {
    font-size: var(--tw-font-size-xxl);
    font-weight: var(--tw-weight-semibold);
    line-height: 1.25;
    color: var(--tw-font-primary);
}

.tw-kpi-foot {
    margin-top: auto;
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 8px;
    min-height: 20px;
}

.tw-kpi-delta {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
    white-space: nowrap;
}

.tw-kpi-delta .ti {
    font-size: var(--tw-icon-size-sm) !important;
}

.tw-kpi-delta--up {
    color: var(--tw-tag-green-fg);
}

.tw-kpi-delta--down {
    color: var(--tw-tag-red-fg);
}

/* La chispa se pega al borde inferior de la tarjeta, como en Twenty */
.tw-kpi-spark {
    height: 34px;
    margin: 4px -12px -12px;
}

/* --- Bandejas: lo que espera una decisión ---------------------------------
   Más chicas que un indicador y siempre enlazadas: un indicador se mira y una
   bandeja se atiende, así que toda la tarjeta es el destino. Van arriba del
   tablero porque una cola que sólo se ve entrando a su pantalla se atiende
   cuando alguien se acuerda.

   Cuántas caben por renglón lo pone la vista con `--tw-dash-cols`: es la única
   que sabe cuántas bandejas quedaron después de los permisos. Las hileras miden
   todas lo mismo aunque a una etiqueta le toque partirse en dos renglones. */
.tw-dash-grid--queues {
    --tw-dash-min: 172px;
    grid-auto-rows: 1fr;
}

.tw-queue {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px 12px;
    color: var(--tw-font-primary);
    text-decoration: none;
}

a.tw-queue:hover {
    background-color: var(--tw-bg-transparent-lighter);
    color: var(--tw-font-primary);
}

.tw-queue-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

.tw-queue-label .ti {
    font-size: var(--tw-icon-size) !important;
    color: var(--tw-font-light);
    flex: 0 0 auto;
}

/* El número y su espera se pegan abajo, no debajo de la etiqueta: cuando una
   etiqueta se parte en dos renglones y las de al lado no, lo que se sale de
   línea es justo lo que se viene a leer de un vistazo. */
.tw-queue-value {
    margin-top: auto;
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
}

/* Reserva el renglón de la espera aunque no haya nada esperando: sin él, las
   tarjetas con cola y las vacías miden distinto y la rejilla queda dentada. */
.tw-queue-foot {
    min-height: 18px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* Sin nada en la cola la tarjeta deja de competir por la atención: sigue ahí
   —que esté vacía es información— pero en gris. */
.tw-queue.is-idle .tw-queue-value {
    color: var(--tw-font-light);
}

/* Lleva esperando más de lo que esta cola admite. El tono va en el borde
   entero y en el fondo, como en `.tw-note`: una barra de color al costado es
   la marca de una pantalla escrita de memoria. */
.tw-queue.is-hot {
    border-color: var(--tw-tag-red-fg);
    background-color: var(--tw-tag-red-bg);
}

.tw-queue.is-hot .tw-queue-label,
.tw-queue.is-hot .tw-queue-foot,
.tw-queue.is-hot .tw-queue-label .ti {
    color: var(--tw-font-secondary);
}

a.tw-queue.is-hot:hover {
    background-color: var(--tw-tag-red-bg);
    border-color: var(--tw-tag-red-fg);
}

/* --- Gráficas --- */
.tw-chart {
    height: 208px;
    margin: 0 -4px;
}

.tw-chart--donut {
    height: 176px;
}

.tw-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    padding-top: 8px;
}

/* La dona a la izquierda y su leyenda en columna a la derecha.
   Debajo, la leyenda se partía en dos renglones que se leen en zigzag y las
   cifras no quedaban alineadas entre sí; al lado, cada estado ocupa un
   renglón con su cifra a la derecha y se comparan de arriba abajo.

   Se parte con `flex-wrap` y no con una consulta de ancho: la tarjeta cambia
   de ancho según cuántas quepan en la rejilla, y en cuanto la leyenda ya no
   cabe a los 160px que pide se baja sola debajo de la dona. */
.tw-donut-split {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 16px;
}

.tw-donut-split > .tw-chart--donut {
    flex: 1 1 176px;
    max-width: 176px;
    margin: 0;
}

.tw-donut-split > .tw-legend {
    flex: 1 1 160px;
    min-width: 0;
    flex-wrap: nowrap;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    padding-top: 0;
}

/* Las cifras se despegan del nombre y se van al canto derecho: así se leen
   como una columna de números y no como el final de cada frase. */
.tw-donut-split .tw-legend-value {
    margin-left: auto;
    font-variant-numeric: tabular-nums;
}

/* El nombre del estado es lo único que puede ceder ancho: la cifra y el
   porcentaje no se recortan nunca. */
.tw-donut-split .tw-legend-item {
    min-width: 0;
    flex-wrap: nowrap;
    overflow: hidden;
    white-space: nowrap;
}

.tw-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

.tw-legend-dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: var(--tw-radius-xs);
}

.tw-legend-value {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

/* --- Listas de resumen dentro de una tarjeta --- */
.tw-dash-list {
    display: flex;
    flex-direction: column;
}

.tw-dash-row {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 40px;
    padding: 6px 12px;
    border-top: 1px solid var(--tw-border-light);
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
    text-decoration: none;
}

.tw-dash-row:first-child {
    border-top: 0;
}

a.tw-dash-row:hover {
    background-color: var(--tw-bg-transparent-lighter);
    color: var(--tw-font-primary);
}

.tw-dash-row-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.tw-dash-row-title {
    font-weight: var(--tw-weight-medium);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* En el diálogo del desglose el renglón no siempre es un código: puede ser una
   caja con su zona y su colada, y eso no cabe en una línea de un portátil. Ahí
   se deja envolver —recortar «desde el 9 sep» con puntos suspensivos esconde
   justo lo que hace elegir una caja y no otra—. Sólo dentro del diálogo: en los
   tableros el renglón sí es un valor suelto y la línea única los alinea. */
#tw-lista .tw-dash-row-title {
    white-space: normal;
    overflow: visible;
}

.tw-dash-row-sub {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tw-dash-row-aside {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
}

.tw-dash-row-num {
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-secondary);
    white-space: nowrap;
}

/* --- Renglón que se puede ocultar («Mis procesos pendientes» de Inicio) ---
   El renglón sigue siendo el enlace y el botón va a su lado, no dentro: un
   activable dentro de otro no es HTML válido. La raya y el fondo de pasar el
   ratón pasan a la envoltura para que el botón no quede fuera de la fila. */
.tw-dash-ocultable {
    display: flex;
    align-items: center;
    border-top: 1px solid var(--tw-border-light);
    transition: background-color var(--tw-duration-fast) var(--tw-ease);
}

.tw-dash-ocultable:first-child {
    border-top: 0;
}

.tw-dash-ocultable > .tw-dash-row {
    flex: 1 1 auto;
    min-width: 0;
    border-top: 0;
}

.tw-dash-ocultable:hover {
    background-color: var(--tw-bg-transparent-lighter);
}

/* El fondo lo pone la envoltura; el del enlace encima lo oscurecería al doble. */
.tw-dash-ocultable > a.tw-dash-row:hover {
    background-color: transparent;
}

/* Sale al pasar por la fila: diez ojos tachados fijos se leerían como una
   columna de datos. En pantalla táctil no hay «pasar», así que se queda. */
.tw-dash-ocultar {
    flex: 0 0 auto;
    margin-right: 8px;
    opacity: 0;
}

.tw-dash-ocultable:hover .tw-dash-ocultar,
.tw-dash-ocultar:focus-visible,
.tw-dash-ocultar.is-busy {
    opacity: 1;
}

@media (hover: none) {
    .tw-dash-ocultar {
        opacity: 1;
    }
}

.tw-dash-ocultos {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 4px 4px 4px 12px;
    border-top: 1px solid var(--tw-border-light);
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

.tw-dash-ocultos[hidden] {
    display: none;
}

/* Fuera de la tarjeta —cuando todo lo mío está oculto— no hay raya que la
   separe de nada: es una nota suelta. */
.tw-dash > .tw-dash-ocultos {
    border-top: 0;
    padding-left: 4px;
}

/* --- Barra de avance --- */
.tw-progress {
    /* `inline-block` y no `inline`: dentro de una celda de tabla —fuera de un
       contenedor flex— un `<span>` en línea ignora el ancho y la barra se
       colapsa en un punto. */
    display: inline-block;
    vertical-align: middle;
    width: 84px;
    height: 4px;
    /* La cifra va siempre pegada a la derecha de la barra —«420%» tocando el
       extremo verde—, porque el margen dependía de que cada plantilla se
       acordara de poner un `ms-2`. Se pone aquí y deja de ser opcional. */
    margin-right: 8px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-quaternary);
    overflow: hidden;
}

.tw-progress > span {
    display: block;
    height: 100%;
    border-radius: inherit;
    background-color: var(--tw-accent);
}

.tw-progress--green > span {
    background-color: var(--tw-tag-green-fg);
}

.tw-progress--orange > span {
    background-color: var(--tw-tag-orange-fg);
}

/* --- Barra de dos tramos --------------------------------------------------
   Dos cosas distintas contra el mismo total, apiladas en la misma barra: lo
   que ya se fue y lo que está listo para irse. Se lee de un vistazo cuánto le
   falta a una orden para poder cerrarse, que es lo que decide si se manda un
   embarque parcial hoy o se espera al lote de mañana.

   `inline-flex` en vez del `block` de un tramo: los dos `<span>` se ponen uno
   detrás del otro y cada uno lleva su porcentaje del ancho. Sin el radio a
   cero, el segundo tramo nace con la esquina redondeada del primero y entre
   los dos aparece una muesca que se lee como un hueco. */
.tw-progress--stack {
    display: inline-flex;
}

.tw-progress--stack > span {
    border-radius: 0;
}

/* Lo que ya salió de planta. */
.tw-progress--stack > .tw-progress-gone {
    background-color: var(--tw-tag-green-fg);
}

/* Lo declarado en un embarque que todavía no se libera.
   Sin este tramo la barra retrocedía al armar la carga --las piezas salían de
   «listas» sin entrar en «embarcadas»--, o sea que avanzar el trabajo se veía
   como perderlo.
   Naranja y no el turquesa que le tocaría por vecino: pegado entre el verde y
   el azul, el turquesa es exactamente el color de en medio y en tema claro los
   tres se leen como una sola mancha. Y dice lo que es, igual que el naranja del
   embarque propuesto: es el tramo que **espera a alguien** --la carga está
   armada y nadie la ha liberado--. */
.tw-progress--stack > .tw-progress-loaded {
    background-color: var(--tw-tag-orange-fg);
}

/* --- El reparto de una lámina ---------------------------------------------
   Los huecos que el acomodo saca de **una** lámina, repartidos entre las
   órdenes que se van a cortar en ella. Contesta la única pregunta del paso:
   de las 123 piezas que salen de cada lámina, ¿cuántas son de cada orden y
   cuántas salen sin dueño?

   Va contra una lámina y no contra el programa entero a propósito: todas las
   láminas cortan lo mismo, así que el reparto se decide una vez y se repite.
   Lo que multiplica —«× 10 láminas = 300 pza»— se dice en cada renglón, que es
   donde se teclea.

   El tramo rayado es lo libre y el rojo lo que se pasa de lo que la lámina
   saca. Cuando se pasa, la raya de `--tw-split-tope` marca dónde acababa la
   lámina: sin ella, una barra llena de color se lee como un reparto completo. */
.tw-split {
    position: relative;
    display: flex;
    width: 100%;
    height: 10px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-quaternary);
    overflow: hidden;
}

.tw-split > span {
    height: 100%;
    /* Sin encoger: los tramos ya vienen medidos en porcentaje, y flex los
       apretaría para que cupiera el que se pasa de la lámina —que es justo el
       que tiene que verse salirse—. */
    flex: 0 0 auto;
}

.tw-split-part {
    background-color: var(--tw-split-color, var(--tw-accent));
}

/* Lo que la lámina corta y ninguna orden pidió. Rayado y no gris liso: el gris
   liso es el fondo de la barra vacía, y uno dentro del otro no distingue una
   lámina a medias de una sin empezar. */
.tw-split-free {
    background-image: repeating-linear-gradient(-45deg, var(--tw-border-strong) 0 3px, transparent 3px 7px);
}

/* Lo que se reparte por encima de lo que la lámina saca. Va tachado encima de
   los tramos y no en un tramo propio: quien se pasa es una de las órdenes, y
   con el exceso aparte se veía dos veces —una en su color y otra en rojo—. El
   rayado deja ver por debajo cuál es la que se sale. */
.tw-split--pasado::before {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: var(--tw-split-tope, 100%);
    background-image: repeating-linear-gradient(-45deg, var(--tw-tag-red-fg) 0 4px, transparent 4px 8px);
}

.tw-split--pasado::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--tw-split-tope, 100%);
    width: 2px;
    background-color: var(--tw-font-primary);
}

/* El mismo color, en el renglón de su orden: es lo que dice qué tramo es cuál
   sin tener que pasar el ratón por encima. */
.tw-split-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: var(--tw-split-color, var(--tw-accent));
}

/* Los `-fg` de los tags, que son los tonos pensados para dibujar y que ya se
   leen en los dos temas. Ni naranja ni rojo ni amarillo: aquí esos tres
   significan otra cosa. */
.tw-split--sky {
    --tw-split-color: var(--tw-tag-sky-fg);
}

.tw-split--purple {
    --tw-split-color: var(--tw-tag-purple-fg);
}

.tw-split--turquoise {
    --tw-split-color: var(--tw-tag-turquoise-fg);
}

.tw-split--blue {
    --tw-split-color: var(--tw-tag-blue-fg);
}

.tw-split--pink {
    --tw-split-color: var(--tw-tag-pink-fg);
}

.tw-split--green {
    --tw-split-color: var(--tw-tag-green-fg);
}

/* --- Medidor de barritas --------------------------------------------------
   Lo mismo que `.tw-progress`, pero contado en tramos en lugar de en una raya
   continua. Se usa donde hay que comparar unos cuantos renglones entre sí:
   los tramos se cuentan de un vistazo, y el renglón más chico sigue pintando
   su primer tramo en vez de quedarse en una línea invisible —que es lo que le
   pasaba a la carga por cliente, donde sólo se veía la barra del más grande y
   los demás salían en blanco—.

   Los tramos no son nodos: son una máscara de rayas sobre dos fondos, el
   lleno y el vacío. Así la cantidad de tramos se acomoda sola al ancho que le
   toque a la tarjeta, el relleno se puede animar, y un renglón cuesta un
   elemento y no cuarenta. */
.tw-meter {
    --tw-meter-color: var(--tw-accent);
    /* Cuánto va lleno, en porcentaje. Lo escribe quien pinta el renglón. */
    --tw-meter-fill: 0%;
    flex: 1 1 auto;
    min-width: 48px;
    height: 16px;
    background-repeat: no-repeat;
    background-image:
        linear-gradient(var(--tw-meter-color), var(--tw-meter-color)),
        linear-gradient(var(--tw-bg-quaternary), var(--tw-bg-quaternary));
    background-size:
        var(--tw-meter-fill) 100%,
        100% 100%;
    -webkit-mask-image: repeating-linear-gradient(90deg, #000 0 2px, transparent 2px 5px);
    mask-image: repeating-linear-gradient(90deg, #000 0 2px, transparent 2px 5px);
    transition: background-size 320ms ease-out;
}

.tw-meter--green {
    --tw-meter-color: var(--tw-tag-green-fg);
}

.tw-meter--orange {
    --tw-meter-color: var(--tw-tag-orange-fg);
}

.tw-meter--red {
    --tw-meter-color: var(--tw-tag-red-fg);
}

.tw-meter--turquoise {
    --tw-meter-color: var(--tw-tag-turquoise-fg);
}

.tw-meter--purple {
    --tw-meter-color: var(--tw-tag-purple-fg);
}

/* Un renglón del medidor: quién, cuánto llena y la cifra. La cifra va fuera
   de la barra y alineada a la derecha —no dentro, como estaba— para que no
   dependa de que la barra sea lo bastante larga para sostenerla. */
.tw-meter-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.tw-meter-row {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 28px;
}

a.tw-meter-row {
    margin: 0 -4px;
    padding: 0 4px;
    border-radius: var(--tw-radius-sm);
    color: inherit;
    text-decoration: none;
}

a.tw-meter-row:hover {
    background-color: var(--tw-bg-transparent-light);
}

/* Un tercio para el nombre: es lo que deja sitio de sobra a la barra sin que
   «INDUSTRIA SIGRAMA S.A. DE C.V.» se coma la tarjeta entera. */
.tw-meter-name {
    flex: 0 0 34%;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-primary);
}

.tw-meter-value {
    flex: 0 0 auto;
    min-width: 56px;
    text-align: right;
    font-size: var(--tw-font-size-sm);
    font-variant-numeric: tabular-nums;
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

@media (prefers-reduced-motion: reduce) {
    .tw-meter {
        transition: none;
    }
}

/* --- Sección de módulos en construcción --- */
.tw-preview .tw-card {
    border-style: dashed;
    border-color: var(--tw-border-strong);
    background-color: var(--tw-bg-primary);
}

.tw-preview .tw-card-header {
    color: var(--tw-font-secondary);
}

.tw-preview-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 6px;
    border-radius: var(--tw-radius-sm);
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-tag-yellow-fg);
    background-color: var(--tw-tag-yellow-bg);
    white-space: nowrap;
}

.tw-preview-eta {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    white-space: nowrap;
}

.tw-dash-grid--kpi {
    --tw-dash-min: 196px;
}

/* Los indicadores, de dos en dos y a media altura.
 *
 * A 196 px de mínimo, en un teléfono la rejilla cae a una columna: cinco
 * tarjetas de 116 px de alto, cada una con un rótulo y un número, hacen
 * setecientos píxeles de casi nada antes de llegar al contenido de la
 * pantalla. Las cifras son cortas —un número y una línea de pie— así que
 * caben dos por renglón sin apretarse, y el alto mínimo sobra: en
 * escritorio iguala tarjetas de una rejilla ancha, en un teléfono sólo
 * añade hueco. */
@media (max-width: 767.98px) {
    .tw-dash-grid--kpi {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 6px;
    }

    .tw-dash-grid--kpi .tw-kpi {
        min-height: 0;
        padding: 10px;
    }

    .tw-dash-grid--kpi .tw-kpi-value {
        font-size: var(--tw-font-size-xl);
    }

    /* El pie deja de estar anclado abajo: sin alto mínimo no hay abajo al
       que anclarse, y `margin-top: auto` con `min-height: 20px` dejaba un
       renglón vacío bajo las tarjetas que no traen pie. */
    .tw-dash-grid--kpi .tw-kpi-foot {
        margin-top: 2px;
        min-height: 0;
        font-size: var(--tw-font-size-xs);
    }
}

/* Tarjetas con gráfica y lista dentro: necesitan más ancho. Cuántas caben por
   renglón lo pone cada pantalla con `--tw-dash-cols`, porque el tope depende
   de cuántas tarjetas trae: aquí van de dos a cuatro. */
.tw-dash-grid--pairs {
    --tw-dash-min: 480px;
}

/* --- Estado «en vivo» --- */
.tw-live {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 8px;
    border-radius: var(--tw-radius-pill);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-tag-green-fg);
    background-color: var(--tw-tag-green-bg);
    white-space: nowrap;
}

.tw-live-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: currentColor;
    animation: tw-pulse 1.8s ease-in-out infinite;
}

/* Mientras se está trayendo el refresco, el punto late más aprisa */
.tw-live.is-loading .tw-live-dot {
    animation-duration: 0.6s;
}

/* Sin conexión el punto se detiene y la píldora se vuelve neutra: un
   indicador «en vivo» que sigue parpadeando sobre datos viejos miente.

   `is-waiting` es la otra cara de lo mismo, antes del primer dato en vez de
   después del último: verde y latiendo sobre un panel que todavía no sabe nada
   promete un flujo que aún no existe. No se confunde con `is-loading`, que es
   el refresco de cada quince segundos sobre datos que ya están: ahí el verde
   está ganado y lo único que cambia es la prisa del punto. */
.tw-live.is-stale,
.tw-live.is-waiting {
    color: var(--tw-font-tertiary);
    background-color: var(--tw-bg-quaternary);
}

.tw-live.is-stale .tw-live-dot,
.tw-live.is-waiting .tw-live-dot {
    animation: none;
}

@keyframes tw-pulse {
    0%,
    100% {
        opacity: 1;
        transform: scale(1);
    }
    50% {
        opacity: 0.35;
        transform: scale(0.75);
    }
}

@media (prefers-reduced-motion: reduce) {
    .tw-live-dot {
        animation: none;
    }
}

/* Realce breve de lo que acaba de entrar en una lista viva */
.tw-flash {
    animation: tw-flash-in 1.4s ease-out;
}

@keyframes tw-flash-in {
    0% {
        background-color: var(--tw-accent-secondary);
    }
    100% {
        background-color: transparent;
    }
}

/* Cambio de cifra en un indicador: sube un pixel y vuelve */
.tw-kpi-value.is-bumped {
    animation: tw-bump 0.45s ease-out;
}

@keyframes tw-bump {
    0% {
        transform: translateY(3px);
        opacity: 0.4;
    }
    100% {
        transform: none;
        opacity: 1;
    }
}

/* --- Tabla del tablero --- */
.tw-dash-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--tw-font-size-md);
}

/* ── La tabla que no cabe se desplaza sola ───────────────────────────────────

   Una tabla dentro de una ficha compite por el ancho con el menú, el panel de
   campos y el carril de secciones: en 1440 px al contenido le quedan 522, y
   estas tablas piden entre 660 y 730. Sin caja propia el sobrante lo asumía
   `.tw-record-content`, que desplaza de lado la pestaña **entera** —título
   incluido— y no lo anuncia: las últimas columnas (el «Recibido» y el avance de
   la orden) sencillamente no estaban, cortadas contra el borde del panel.

   Con la caja el desplazamiento es de la tabla: sale su barra debajo, el resto
   de la pestaña se queda quieto y las columnas que faltan se alcanzan de lado.
   `overflow-y: clip` para no recortar por arriba lo que el navegador convierte
   en `hidden` al haber desplazamiento en el otro eje —los distintivos y las
   barras de avance de las celdas quedan enteros—.

   Lo que se pierde a cambio es el encabezado pegajoso: `thead th` se ancla al
   contenedor que desplaza, y pasa a ser esta caja en vez del panel. Es la mitad
   barata del trato: un encabezado que se va al bajar se recupera subiendo; una
   columna cortada contra el borde no se recupera de ninguna forma. */
.tw-table-scroll {
    overflow-x: auto;
    overflow-y: clip;
}

.tw-dash-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: 6px 12px;
    text-align: left;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-tertiary);
    background-color: var(--tw-bg-secondary);
    border-bottom: 1px solid var(--tw-border-light);
    white-space: nowrap;
}

.tw-dash-table tbody td {
    padding: 0 12px;
    height: var(--tw-row-height);
    border-bottom: 1px solid var(--tw-border-light);
    color: var(--tw-font-primary);
    white-space: nowrap;
}

.tw-dash-table tbody tr:last-child td {
    border-bottom: 0;
}

.tw-dash-table tbody tr:hover td {
    background-color: var(--tw-bg-transparent-lighter);
}

.tw-dash-table .tw-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-secondary);
}

/* Los enlaces de estas tablas son referencias —el proveedor, la ubicación—:
   se leen como texto y no compiten con la columna de identidad. Por eso la
   excepción a `.tw-record-link`, que tiene su propio peso y su subrayado al
   apuntar: sin ella, la identidad se veía más floja dentro de una ficha que en
   el listado del que sale, que es exactamente la incoherencia que se estaba
   quitando. */
.tw-dash-table a:not(.tw-record-link) {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
    text-decoration: none;
}

.tw-dash-table a:not(.tw-record-link):hover {
    text-decoration: none;
}

.tw-dash-scroll {
    max-height: 268px;
    overflow: auto;
}

/* ══════════════════════════════════════════════════════════════════════════
   Sin subrayado al pasar el cursor
   ══════════════════════════════════════════════════════════════════════════

   Bootstrap y Tabler subrayan cualquier enlace en `:hover`, y en una interfaz
   densa —tablas, fichas, migas de pan, menús— eso es ruido: el color y el
   cursor ya dicen que es un enlace.

   Va al final de este archivo, que es el último que carga, así que gana la
   cascada sin necesidad de `!important`. */
a:hover,
a:focus,
.dropdown-item:hover,
.nav-link:hover,
.list-group-item-action:hover {
    text-decoration: none;
}

/* ══════════════════════════════════════════════════════════════════════════
   Avisos
   ══════════════════════════════════════════════════════════════════════════

   Las alertas de Bootstrap traen su propio color de fondo saturado, su borde y
   su tipografía, y en medio de una ficha se ven como un parche pegado encima.
   Este es el mismo mensaje con la caja del sistema: borde fino y radio propio.

   SIN BARRA DE COLOR A LA IZQUIERDA. La llevó hasta el 6 de agosto de 2026 y se
   quitó de todo el sistema el mismo día: un filo grueso en un solo lado parte la
   caja en dos, no se alinea con nada de lo que tiene alrededor y es el adorno
   que delata una pantalla escrita de memoria en vez de sacada del sistema. El
   tono va donde ya iba en el resto del tema —el fondo y el borde de los tags,
   que son los únicos colores con versión de oscuro— y así el aviso es la misma
   caja de siempre, sólo que teñida. */
.tw-note {
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    padding: 12px 14px;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
}

.tw-note-title {
    font-weight: var(--tw-weight-medium);
    margin-bottom: 2px;
}

.tw-note-text {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* El tono sale de los tags del sistema, no de un hex suelto: son los únicos
   colores que ya tienen su versión de oscuro. Un #16a34a fijo aquí se lee
   apagado sobre negro. El texto se queda en el color primario: el fondo teñido
   ya dice el tono, y pintar además el párrafo lo vuelve ilegible en cuanto pasa
   de dos renglones. */
.tw-note--info {
    border-color: var(--tw-tag-blue-fg);
    background-color: var(--tw-tag-blue-bg);
}

.tw-note--success {
    border-color: var(--tw-tag-green-fg);
    background-color: var(--tw-tag-green-bg);
}

.tw-note--warning {
    border-color: var(--tw-tag-orange-fg);
    background-color: var(--tw-tag-orange-bg);
}

.tw-note--danger {
    border-color: var(--tw-tag-red-fg);
    background-color: var(--tw-tag-red-bg);
}

/* Dentro de la caja teñida, el texto secundario tiene que seguir siendo
   secundario: `--tw-font-light` sobre el tinte se acerca demasiado al fondo. */
.tw-note--info .tw-note-text,
.tw-note--success .tw-note-text,
.tw-note--warning .tw-note-text,
.tw-note--danger .tw-note-text,
.tw-note--info .tw-meta,
.tw-note--success .tw-meta,
.tw-note--warning .tw-meta,
.tw-note--danger .tw-meta {
    color: var(--tw-font-secondary);
}

/* El cotejo de un certificado · qué dice el sistema y qué leyó el lector, campo
   a campo, en «Lo que leyó del certificado».

   Iba en una tabla, y el `td` global lleva `nowrap`: la cita —a veces una fila
   entera de la tabla del papel— estiraba la tabla a varias veces el ancho del
   modal y el veredicto quedaba al fondo de un arrastre horizontal. Aquí nada es
   celda y todo parte renglón. */
.tw-cotejo {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.tw-cotejo-campo {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
    padding: 10px 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
}

.tw-cotejo-cabeza {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 4px 8px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

.tw-cotejo-cabeza .tw-count-badge {
    color: var(--tw-font-light);
    font-weight: var(--tw-weight-regular);
}

/* Las dos columnas se reparten a partes iguales y nunca se ensanchan por su
   contenido: una lista de doce coladas pasa al renglón de abajo. */
.tw-cotejo-valores {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px 16px;
}

.tw-cotejo-rotulo {
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-tertiary);
}

.tw-cotejo-valor {
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
    overflow-wrap: anywhere;
}

/* Cuando el país se dice distinto de una colada a otra: un renglón por cada una,
   separados por una línea y no por otra tarjeta. */
.tw-cotejo-parte {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.tw-cotejo-parte + .tw-cotejo-parte {
    padding-top: 8px;
    border-top: 1px solid var(--tw-border-light);
}

.tw-cotejo-parte .tw-cotejo-cabeza {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

.tw-cotejo-lista {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding-top: 2px;
}

.tw-cotejo-cita,
.tw-cotejo-nota {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    overflow-wrap: anywhere;
}

.tw-cotejo-cita {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Lo que no cuadra de una partida, en el panel de su certificado: un dato por
   renglón, sin viñeta y sin cita. La cita vive en «Ver lo que leyó». */
.mtc-hallazgos {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 6px 0 0;
    padding: 0;
    list-style: none;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-secondary);
    overflow-wrap: anywhere;
}

.mtc-hallazgos b {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

.tw-cotejo-transcripcion {
    max-height: 24rem;
    overflow: auto;
}

/* El detalle que se despliega bajo un renglón de una DataTable con scroll
   horizontal.

   La celda hija es tan ancha como la tabla, no como el hueco que se ve: sin
   esto, el pie del desglose —donde va el total— queda fuera de la pantalla y
   hay que arrastrar la tabla en horizontal para leer el número por el que se
   abrió el desplegable.

   `sticky` lo clava al borde izquierdo del área visible aunque la tabla se
   desplace, y el ancho máximo lo mantiene legible: un bloque de texto a lo
   ancho de una tabla de doce columnas no se lee, se barre. */
.tw-row-detail {
    position: sticky;
    left: 0;
    max-width: 46rem;
}

/* Renglón subordinado de una tabla · los componentes de un ensamble debajo de él.
   El fondo lo separa del renglón padre sin meter una tabla dentro de otra, que
   es lo que descuadra las columnas y hace que los números dejen de compararse
   en vertical. El texto baja un punto de color: son el detalle de la fila de
   arriba, no filas que compitan con ella. */
.tw-subrow > td {
    background-color: var(--tw-bg-transparent-lighter);
    color: var(--tw-font-secondary);
}

/* ══════════════════════════════════════════════════════════════════════════
   De qué se compone · la receta de un ensamble
   ══════════════════════════════════════════════════════════════════════════

   Aparece en tres sitios que dicen lo mismo con la misma forma: el diálogo que
   agrega la partida, el renglón desplegable de la orden y la ficha del número de
   parte. Es una lista corta de «número de parte → cuántos», y se escribió como
   componente en vez de como tabla porque una tabla de dos columnas con dos
   renglones trae encabezados, bordes y alineaciones que aquí sólo hacen ruido.

   LOS NÚMEROS VAN A LA DERECHA Y TABULARES. Se leen en columna —«2, 1, 4»— y
   con la cifra proporcional del cuerpo de texto la columna se desalinea sola en
   cuanto un número llega a dos dígitos, que es justo cuando alguien la está
   comparando. */
.tw-bom-list {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
    overflow: hidden;
}

.tw-bom-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-bom-row:last-child {
    border-bottom: 0;
}

.tw-bom-name {
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* «Pata izquierda», la posición dentro del ensamble. Va debajo del número de
   parte y no al lado: al lado compite con él, y lo que se busca en esta lista
   es el número de parte. */
.tw-bom-where {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    font-weight: var(--tw-weight-regular);
}

.tw-bom-qty {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    white-space: nowrap;
}

/* El total es la respuesta; el «2 por ensamble» es la explicación. Se distinguen
   por peso y color, no por separarlos en dos columnas: en una lista de dos
   renglones, dos columnas de números se comparan entre sí antes que consigo
   mismas. */
.tw-bom-total {
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

/* El pie: lo que va a salir de la impresora. Es la frase por la que existe todo
   el bloque, así que va dentro de la misma caja y no suelta debajo. */
.tw-bom-foot {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    border-top: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-transparent-light);
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

/* ══════════════════════════════════════════════════════════════════════════
   Avisos flotantes (toastr)
   ══════════════════════════════════════════════════════════════════════════

   Es el mismo mensaje que `.tw-note`, pero de paso: llega, se lee y se va.

   La caja es neutra —superficie, borde fino, el mismo gris en los cuatro
   casos— y el tono lo lleva solo el icono. `.tw-note` sí usa barra de color
   a la izquierda porque vive dentro de una ficha y compite con todo lo que
   tiene alrededor; el aviso flotante llega solo a una esquina vacía, así
   que el icono ya basta y el filo de color acaba siendo un adorno que
   además parte la caja en dos.

   Lo que había antes eran tres capas peleándose: la hoja de toastr trae la
   paleta de otro tema (verde #28C76F, texto #5d596c) con `!important`, y
   encima Tabler pinta `.toast { background: #ffffff }` a fuego, así que en
   oscuro el aviso salía como un rectángulo blanco. Aquí se apaga todo eso y
   se vuelve a construir con tokens; de ahí los `!important`, que son para
   ganarle a la hoja de la librería, no un atajo.

   El icono va con `mask-image` en vez de `background-image` porque la
   máscara toma el color de `--tw-toast-accent`: un solo dibujo sirve para
   los dos temas y para los cuatro tonos. */

#toast-container > div.toast {
    --tw-toast-accent: var(--tw-font-tertiary);

    position: relative;
    width: 22em !important;
    max-width: calc(100vw - 32px);
    /* Sangría izquierda para el icono, que va absoluto. */
    padding: 12px 16px 12px 42px !important;
    margin: 0 0 8px;
    background-color: var(--tw-bg-primary) !important;
    background-image: none !important;
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-md);
    box-shadow: var(--tw-shadow-strong);
    color: var(--tw-font-primary) !important;
    font-size: var(--tw-font-size-md);
    /* Sin `!important`: la animación de toastr escribe la opacidad en línea
       y con `!important` el aviso no podría desvanecerse. */
    opacity: 1;
}

#toast-container > div.toast::before {
    content: '';
    position: absolute;
    left: 14px;
    top: 13px;
    width: 18px;
    height: 18px;
    background-color: var(--tw-toast-accent);
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 18px 18px;
    mask-size: 18px 18px;
}

#toast-container > div.toast:hover {
    box-shadow: var(--tw-shadow-strong);
    border-color: var(--tw-border-strong);
    cursor: pointer;
}

#toast-container > div.toast .toast-title {
    margin: 0 0 2px !important;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
}

#toast-container > div.toast .toast-message {
    margin: 0 !important;
    font-size: var(--tw-font-size-md);
    line-height: 1.45;
    color: var(--tw-font-primary) !important;
    word-break: break-word;
}

/* Con título, el cuerpo baja a secundario: si los dos pesan lo mismo, el
   título deja de serlo. */
#toast-container > div.toast .toast-title + .toast-message {
    color: var(--tw-font-secondary) !important;
}

#toast-container > div.toast .toast-message a {
    color: var(--tw-accent);
    text-decoration: underline;
}

#toast-container > div.toast .toast-close-button {
    position: absolute;
    top: 6px;
    right: 8px !important;
    left: auto;
    width: 20px;
    height: 20px;
    font-size: 1rem;
    line-height: 20px;
    font-weight: var(--tw-weight-regular);
    text-shadow: none;
    opacity: 1;
    border-radius: var(--tw-radius-sm);
    color: var(--tw-font-tertiary) !important;
}

#toast-container > div.toast .toast-close-button:hover,
#toast-container > div.toast .toast-close-button:focus {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary) !important;
    opacity: 1;
}

/* La barra del temporizador es del tono del aviso, no negra: en oscuro una
   barra negra sobre superficie oscura no existe. */
#toast-container > div.toast .toast-progress {
    top: auto;
    bottom: 0;
    left: 0;
    height: 2px;
    background-color: var(--tw-toast-accent);
    opacity: 0.5;
    filter: none;
}

/* ── Tonos ─────────────────────────────────────────────────────────────── */

#toast-container > div.toast-success {
    --tw-toast-accent: var(--tw-tag-green-fg);
}

#toast-container > div.toast-error {
    --tw-toast-accent: var(--tw-tag-red-fg);
}

#toast-container > div.toast-warning {
    --tw-toast-accent: var(--tw-tag-orange-fg);
}

#toast-container > div.toast-info {
    --tw-toast-accent: var(--tw-accent);
}

#toast-container > div.toast-success::before {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0'/%3E%3Cpath d='M9 12l2 2l4 -4'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0'/%3E%3Cpath d='M9 12l2 2l4 -4'/%3E%3C/svg%3E");
}

#toast-container > div.toast-error::before {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0'/%3E%3Cpath d='M12 8v4'/%3E%3Cpath d='M12 16h.01'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0'/%3E%3Cpath d='M12 8v4'/%3E%3Cpath d='M12 16h.01'/%3E%3C/svg%3E");
}

#toast-container > div.toast-warning::before {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.24 3.957l-8.422 14.06a1.989 1.989 0 0 0 1.7 2.983h16.845a1.989 1.989 0 0 0 1.7 -2.983l-8.423 -14.06a1.989 1.989 0 0 0 -3.4 0z'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.24 3.957l-8.422 14.06a1.989 1.989 0 0 0 1.7 2.983h16.845a1.989 1.989 0 0 0 1.7 -2.983l-8.423 -14.06a1.989 1.989 0 0 0 -3.4 0z'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E");
}

#toast-container > div.toast-info::before {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0'/%3E%3Cpath d='M12 9h.01'/%3E%3Cpath d='M11 12h1v4h1'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0'/%3E%3Cpath d='M12 9h.01'/%3E%3Cpath d='M11 12h1v4h1'/%3E%3C/svg%3E");
}

/* El contenedor lo posiciona toastr; aquí solo se separa del borde y se
   asegura que ningún aviso quede debajo de un modal. */
#toast-container.toast-top-right,
#toast-container.toast-bottom-right {
    right: 16px;
}

#toast-container.toast-top-right,
#toast-container.toast-top-left {
    top: 16px;
}

#toast-container.toast-bottom-right,
#toast-container.toast-bottom-left {
    bottom: 16px;
}

@media (max-width: 480px) {
    #toast-container > div.toast {
        width: calc(100vw - 32px) !important;
    }
}

/* Filas de la matriz de permisos.

   Son muchas y muy juntas —58 capacidades en once dominios—, así que la fila
   necesita decir a simple vista dos cosas que en una lista plana se pierden:
   dónde termina una y empieza la siguiente, y cuáles se apartan de lo que el
   perfil concede.

   Lo segundo va por el borde **entero**, no por el fondo ni por un filo de
   color a la izquierda. El fondo saturado en cincuenta filas convierte la
   pantalla en un semáforo; el filo izquierdo es el adorno que se quitó de todo
   el sistema el 6 de agosto de 2026 (ver `.tw-note`). Y el qué —«concedido
   aparte» o «retirado de su perfil»— ya lo dice el distintivo de la fila con
   todas sus letras: el borde sólo tiene que hacerla encontrable al recorrer la
   lista de arriba abajo. */
.permission-row {
    border: 1px solid transparent;
    transition: background-color 0.12s ease;
}

.permission-row:hover {
    background-color: var(--tw-bg-secondary);
}

.permission-row.is-granted {
    border-color: var(--tw-tag-blue-fg);
}

.permission-row.is-revoked {
    border-color: var(--tw-tag-orange-fg);
}

/* Una capacidad que la cuenta no ejerce se lee en segundo plano: la pantalla se
   recorre buscando lo que sí tiene. */
.permission-row:has(.permission-checkbox:not(:checked)) .fw-medium {
    color: var(--tw-font-light);
}

.permission-row:has(.permission-checkbox:disabled) {
    opacity: 0.65;
}

/* Matriz de permisos.

   Cincuenta y ocho filas por siete columnas: lo que la hace legible no es el
   color sino poder seguir una fila con la vista hasta el final. De ahí la
   primera columna fija, el resaltado de fila completa al pasar el cursor y unas
   columnas de marca estrechas, para que el nombre de la capacidad se lleve todo
   el ancho que sobre. */
.tw-matrix {
    font-size: var(--tw-font-size-sm);
}

.tw-matrix th {
    vertical-align: bottom;
    white-space: nowrap;
}

.tw-matrix .tw-matrix-capability {
    min-width: 22rem;
    position: sticky;
    left: 0;
    background-color: var(--tw-bg-primary);
    z-index: 1;
}

.tw-matrix tbody tr:hover .tw-matrix-capability {
    background-color: var(--tw-bg-secondary);
}

.tw-matrix tbody tr:hover {
    background-color: var(--tw-bg-secondary);
}

.tw-matrix-cell {
    width: 4.5rem;
    font-size: 1rem;
    line-height: 1;
}

.tw-matrix-cell--operate {
    color: var(--tw-green, #16a34a);
}

/* Solo consulta: mismo símbolo que en el documento del plan y sin peso, para
   que la lectura rápida de una columna sea «dónde hay puntos llenos». */
.tw-matrix-cell--read {
    color: var(--tw-font-light);
}

.tw-matrix-group td {
    background-color: var(--tw-bg-secondary);
    font-size: var(--tw-font-size-sm);
}

.tw-matrix-legend {
    color: var(--tw-green, #16a34a);
}

/* =====================================================================
   Diseñador de etiquetas — /labels-designer
   =====================================================================
   Tres columnas fijas: los ajustes de la etiqueta a la izquierda, el lienzo
   en medio y el inspector del elemento a la derecha. Antes era una sola
   columna con todo apilado, así que para mover un texto un milímetro había
   que subir a lo alto de la página, escribir la coordenada y volver a bajar
   a ver el resultado. Aquí el control y lo que controla se ven a la vez.
   ===================================================================== */

.tw-designer-panel {
    flex-direction: row;
    overflow: hidden;
}

.tw-designer-side,
.tw-designer-inspector {
    display: flex;
    flex-direction: column;
    flex: 0 0 268px;
    width: 268px;
    min-height: 0;
    overflow-y: auto;
    background-color: var(--tw-bg-primary);
}

.tw-designer-side {
    border-right: 1px solid var(--tw-border-light);
}

.tw-designer-inspector {
    border-left: 1px solid var(--tw-border-light);
}

/* Bloque con encabezado dentro de una columna lateral */
.tw-designer-section + .tw-designer-section {
    border-top: 1px solid var(--tw-border-light);
}

.tw-designer-section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 10px 12px 2px;
}

.tw-designer-section-title {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin: 0;
}

.tw-designer-section-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px 12px 12px;
}

/* Campo compacto: etiqueta encima, control debajo, sin margen suelto */
.tw-field-block {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.tw-field-block > label,
.tw-field-block > .form-label {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
    margin: 0;
}

.tw-field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
}

.tw-field-row--three {
    grid-template-columns: repeat(3, 1fr);
}

/* --- Zona central: barra de herramientas, lienzo y pie de estado --- */

.tw-designer-stage {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    background-color: var(--tw-bg-tertiary);
}

.tw-designer-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 0 0 auto;
    flex-wrap: wrap;
    padding: 6px 12px;
    background-color: var(--tw-bg-primary);
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-tool-group {
    display: flex;
    align-items: center;
    gap: 2px;
}

.tw-tool-group + .tw-tool-group {
    padding-left: 12px;
    border-left: 1px solid var(--tw-border-light);
}

.tw-tool-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-width: 28px;
    height: 28px;
    padding: 0 8px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: none;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-secondary);
    cursor: pointer;
    transition: background-color var(--tw-transition);
}

.tw-tool-btn:hover:not(:disabled) {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.tw-tool-btn:disabled {
    color: var(--tw-font-extra-light);
    cursor: default;
}

.tw-tool-btn.is-active {
    background-color: var(--tw-accent-secondary);
    color: var(--tw-accent);
}

.tw-tool-btn .ti {
    font-size: var(--tw-icon-size) !important;
}

.tw-zoom-value {
    min-width: 44px;
    text-align: center;
    font-size: var(--tw-font-size-sm);
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-tertiary);
}

/* El hueco donde vive la etiqueta: gris, con la etiqueta centrada y flotando */
.tw-designer-canvas-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px;
}

/* `.canvas-container` lo crea Fabric alrededor del lienzo. Es quien pinta el
   papel: el lienzo va transparente para que la cuadrícula y el margen queden
   detrás del diseño y nunca acaben exportados con él. */
.tw-designer-canvas-scroll .canvas-container {
    flex: 0 0 auto;
    background-color: #ffffff;
    box-shadow: var(--tw-shadow-strong);
    outline: 1px solid var(--tw-border-strong);
}

/* Cuadrícula milimétrica, con una línea marcada cada 5 mm */
.tw-designer-canvas-scroll .canvas-container::before {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image:
        linear-gradient(to right, var(--tw-gray-30) 1px, transparent 1px),
        linear-gradient(to bottom, var(--tw-gray-30) 1px, transparent 1px),
        linear-gradient(to right, var(--tw-gray-20) 1px, transparent 1px),
        linear-gradient(to bottom, var(--tw-gray-20) 1px, transparent 1px);
    background-size:
        calc(var(--tw-mm, 4px) * 5) calc(var(--tw-mm, 4px) * 5),
        calc(var(--tw-mm, 4px) * 5) calc(var(--tw-mm, 4px) * 5),
        var(--tw-mm, 4px) var(--tw-mm, 4px),
        var(--tw-mm, 4px) var(--tw-mm, 4px);
    opacity: 0;
    transition: opacity var(--tw-transition);
}

.tw-designer-canvas-scroll.show-grid .canvas-container::before {
    opacity: 1;
}

/* Margen de seguridad: lo que quede fuera se arriesga a que la impresora lo
   recorte, aunque el ZPL lo acepte. */
.tw-designer-canvas-scroll .canvas-container::after {
    content: '';
    position: absolute;
    inset: calc(var(--tw-mm, 4px) * var(--tw-safe-margin, 2));
    pointer-events: none;
    border: 1px dashed var(--tw-tag-red-fg);
    opacity: 0;
    transition: opacity var(--tw-transition);
}

.tw-designer-canvas-scroll.show-safe-area .canvas-container::after {
    opacity: 0.45;
}

.tw-designer-statusbar {
    display: flex;
    align-items: center;
    gap: 16px;
    flex: 0 0 auto;
    flex-wrap: wrap;
    min-height: 32px;
    padding: 4px 12px;
    background-color: var(--tw-bg-primary);
    border-top: 1px solid var(--tw-border-light);
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

.tw-designer-statusbar .ti {
    font-size: var(--tw-icon-size-sm) !important;
    color: var(--tw-font-light);
}

.tw-status-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-variant-numeric: tabular-nums;
}

/* --- Lista de elementos --- */

.tw-item-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.tw-item-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 8px;
    border: 1px solid transparent;
    border-radius: var(--tw-radius-sm);
    cursor: pointer;
    transition:
        background-color var(--tw-transition),
        border-color var(--tw-transition);
}

.tw-item-row:hover {
    background-color: var(--tw-bg-transparent-light);
}

.tw-item-row.is-selected {
    background-color: var(--tw-accent-secondary);
    border-color: var(--tw-accent);
}

.tw-item-row .ti {
    font-size: var(--tw-icon-size) !important;
    color: var(--tw-font-tertiary);
    flex: 0 0 auto;
}

.tw-item-row.is-selected .ti {
    color: var(--tw-accent);
}

.tw-item-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
}

.tw-item-name {
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tw-item-meta {
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-light);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Aviso de que el elemento no lleva dato dinámico: se imprimirá literal */
.tw-item-row .tw-item-warn {
    color: var(--tw-tag-orange-fg) !important;
}

/* Estado vacío pequeño, para una columna lateral: el general reserva 64px de
   alto y aquí dejaba la lista descolgada del encabezado. */
.tw-empty-state--inline {
    padding: 20px 8px;
    gap: 4px;
}

.tw-empty-state--inline .tw-empty-state-title {
    font-size: var(--tw-font-size-md);
}

.tw-empty-state--inline .tw-empty-state-text {
    font-size: var(--tw-font-size-sm);
}

/* ══════════════════════════════════════════════════════════════════════════
   Bandeja · el renglón de algo que espera a que alguien lo mire
   ══════════════════════════════════════════════════════════════════════════

   Diferencias de conteo y Piezas rechazadas son la misma pantalla con distinto
   contenido: una lista de casos abiertos, cada uno con su identidad arriba, su
   procedencia debajo y, al lado, cuánto lleva esperando.

   NACE PORQUE SE ESTABA USANDO `.tw-item-row` PARA ESTO, y ese componente es
   otra cosa: el renglón de 32 px de una columna lateral —la lista de elementos
   del diseñador de etiquetas—, centrado verticalmente, con `nowrap` y recorte
   por puntos suspensivos. Metiéndole tres líneas de texto crecía a 60 px sin
   separador con el de abajo, el título quedaba centrado contra un bloque que ya
   no lo estaba, y todo lo que no cabía a lo ancho —que era justo la
   procedencia— se recortaba en silencio.

   Aquí el renglón se alinea arriba, se separa con una línea, y lo que no cabe
   pasa a la línea siguiente en vez de perderse. */

.tw-inbox-list {
    display: flex;
    flex-direction: column;
}

/* La pieza del filtro activo, en la barra de la vista.

   Un filtro que no se ve puesto es un tablero que miente por omisión: al acotar
   por un punto del recorrido, quien volvía a la pantalla leía «Órdenes a
   proveedor 4» y concluía que se habían perdido los certificados. La pieza lo
   dice y su aspa lo suelta, que es el gesto que la gente ya prueba. */

/* El subtítulo de una pantalla · su pregunta, al lado del título.

   Lo estrenan «Seguimiento» y «El piso», que existen para contestar dos
   preguntas parecidas y no se distinguían por el nombre. No es un cartel
   explicativo —de ésos no hay en esta casa—: es el rótulo de la pantalla,
   escrito una vez y en gris, como la nota que llevan las secciones de Inicio.

   Se esconde en pantalla estrecha: ahí el título ya ocupa el renglón y la
   pregunta se lee dentro, en lo que la pantalla enseña. */
.tw-page-note {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    margin-left: 10px;
}

@media (max-width: 768px) {
    .tw-page-note {
        display: none;
    }
}


.tw-filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: 8px;
    padding: 2px 6px 2px 8px;
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-transparent-light);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-secondary);
    cursor: pointer;
    transition: background-color var(--tw-transition);
}

.tw-filter-chip:hover {
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-primary);
}

.tw-filter-chip .ti {
    color: var(--tw-font-light);
}

/* ── La franja · toda la planta en un renglón ──────────────────────────────

   Lo que se lee desde la puerta antes de mirar ninguna ficha. Ocho tarjetas
   contestan «cómo va este puesto» y ninguna contestaba «cómo va la planta»:
   había que sumar de cabeza. */

.tw-franja {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0 28px;
    padding: 14px 18px;
    background-color: var(--tw-bg-secondary);
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-franja-dato {
    display: flex;
    align-items: baseline;
    gap: 6px;
}

.tw-franja-num {
    font-size: 1.5rem;
    font-weight: var(--tw-weight-semibold);
    line-height: 1.1;
    color: var(--tw-font-primary);
    font-variant-numeric: tabular-nums;
}

.tw-franja-que {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

.tw-franja-dato--alerta .tw-franja-num,
.tw-franja-dato--alerta .tw-franja-que {
    color: var(--tw-tag-red-fg);
}

/* El cero se apaga. Seis cifras en cero con el mismo peso que una de dos mil
   tiran de la mirada hacia donde no pasa nada, que es justo al revés. */
.tw-franja-dato--cero .tw-franja-num {
    color: var(--tw-font-extra-light);
}

/* Separa lo que hay ahora de lo que lleva el día: son dos lecturas y sin la
   línea se leen como una fila de seis cifras del mismo tipo. */
.tw-franja-sep {
    width: 1px;
    align-self: stretch;
    background-color: var(--tw-border-medium);
}

/* ── El pulso · doce horas, una barra por hora ─────────────────────────────

   Es lo que hace que la pantalla se sienta viva. Doce barras dicen si el puesto
   viene de trabajar toda la mañana o si lleva ocho horas parado, y ninguna de
   las otras cifras lo dice: la cola de ahora es la misma en los dos casos.

   La altura es relativa al mejor rato del propio puesto, no al de la planta: lo
   que se compara aquí es un puesto consigo mismo. */

.tw-pulso {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 26px;
    margin-top: 10px;
}

.tw-pulso-barra {
    flex: 1 1 0;
    min-height: 2px;
    height: max(2px, var(--alto, 0%));
    border-radius: 1px;
    background-color: var(--tw-font-secondary);
}

/* La hora sin cierres deja su hueco marcado y no desaparece: un hueco vacío se
   leería como que la gráfica empieza más tarde. */
.tw-pulso-barra.is-cero {
    background-color: var(--tw-border-medium);
}

/* La hora en curso, que va a medias por definición. */
.tw-pulso-barra.is-ahora {
    background-color: var(--tw-accent, var(--tw-font-primary));
}

.tw-puesto--warm .tw-pulso-barra,
.tw-puesto--hot .tw-pulso-barra {
    background-color: var(--tw-font-primary);
}

.tw-puesto--warm .tw-pulso-barra.is-cero,
.tw-puesto--hot .tw-pulso-barra.is-cero {
    background-color: var(--tw-border-strong);
}

/* Al día y sin haber cerrado nada hoy: la ficha se apaga. Una planta en reposo
   tiene que verse en reposo, y con ocho fichas gritando lo mismo no se ve nada.
   Sigue ahí y sigue abriéndose: que un puesto esté vacío es información. */
.tw-puesto--idle {
    background-color: transparent;
    border-style: dashed;
}

.tw-puesto--idle .tw-puesto-head,
.tw-puesto--idle .tw-puesto-linea {
    color: var(--tw-font-extra-light);
}

/* ── La tira de los que están al día ───────────────────────────────────────

   FUERA DE LA REJILLA, Y ESO ES LO QUE LA MANTIENE EQUILIBRADA. Con las fichas
   cortas dentro, la rejilla mezclaba dos formas: con la planta entera al día
   salían ocho recuadros vacíos de altos desiguales —el que lleva proveedor crece
   porque su nombre se va a otro renglón— y una segunda fila con dos huecos al
   lado. Eso no se arregla con medidas.

   Ahora la rejilla es sólo para lo que tiene trabajo, donde todas las fichas
   tienen la misma forma, y lo que está en reposo fluye aquí y se envuelve solo.
   Es el mismo reparto que «Fuera del camino» en Seguimiento. */

.tw-quietos {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    /* Con aire arriba: sin él las pastillas quedaban pegadas al borde de la
       rejilla, y con la rejilla desbordada, encima de la última ficha. */
    padding: 12px 18px 16px;
    background-color: var(--tw-bg-secondary);
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-quietos-label {
    font-size: var(--tw-font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--tw-font-extra-light);
    margin-right: 4px;
}

.tw-quieto {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 12px;
    border: 1px dashed var(--tw-border-medium);
    border-radius: var(--tw-radius-pill);
    background-color: transparent;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    cursor: pointer;
    transition:
        background-color var(--tw-transition),
        border-color var(--tw-transition);
}

.tw-quieto:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.tw-quieto[aria-pressed='true'] {
    border-style: solid;
    border-color: var(--tw-font-primary);
    color: var(--tw-font-primary);
}

.tw-quieto:focus-visible {
    outline: 2px solid var(--tw-font-primary);
    outline-offset: 2px;
}

/* ── Y cuando toda la planta está al día ───────────────────────────────────

   Era el caso que peor se veía y ahora es el que mejor: una planta al día tiene
   que **parecer** una planta al día, no una pantalla a medio cargar. Ocupa el
   sitio de la rejilla, centrado y en gris. */

.tw-puestos-calma {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 22px 12px 6px;
    color: var(--tw-font-light);
}

.tw-puestos-calma .ti {
    font-size: 22px !important;
    color: var(--tw-font-extra-light);
}

.tw-puestos-calma-titulo {
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-secondary);
}

.tw-puestos-calma-nota {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-extra-light);
}

/* ── Las fichas de puesto · «Procesos en planta» ───────────────────────────

   Una por proceso, en rejilla. No es el recorrido: allí el orden es el camino
   del material y por eso va en una fila que se desplaza; aquí no hay camino, hay
   doce puestos que se miran a la vez, y la rejilla es lo que deja compararlos.

   EL TONO ES LA EDAD DE SU COLA, con la misma escala que los puntos del
   recorrido: el mismo rojo tiene que significar lo mismo en las dos pantallas.
   Y una carga fuera de plazo pone el puesto en rojo aunque su cola sea de hoy:
   eso ya no es cola, es material perdido de vista. */

.tw-puestos {
    flex: 0 0 auto;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 8px;
    padding: 16px 14px;
    background-color: var(--tw-bg-secondary);
    border-bottom: 1px solid var(--tw-border-light);
    /* Con muchos procesos la rejilla crecería hasta comerse la lista de abajo.
       Se acota y se desplaza, que es la regla de la casa para lo que no cabe.
       El tope es generoso porque las fichas al día se encogen: lo que llena esto
       son los puestos con trabajo, y ésos son los que hay que ver. */
    max-height: 60vh;
    overflow-y: auto;
}

/* Hay más fichas debajo del corte. El desvanecido lo dice sin ocupar sitio; la
   clase la pone el guion sólo cuando de verdad hay desborde, para que una
   rejilla que cabe entera no salga con la última fila medio borrada.

   Es el mismo gesto que el del recorrido en Seguimiento: lo que no cabe se
   desplaza y se avisa, nunca se corta en seco. */
.tw-puestos--desborda {
    mask-image: linear-gradient(to bottom, #000 calc(100% - 36px), transparent 100%);
}

/* La máscara se come también el borde de abajo de la rejilla, y la ficha a
   medio borrar se fundía con la tira de «Al día»: la línea la pone la tira. */
.tw-puestos--desborda + .tw-quietos {
    border-top: 1px solid var(--tw-border-light);
}

.tw-puesto {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 12px 14px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    text-align: left;
    cursor: pointer;
    transition:
        background-color var(--tw-transition),
        border-color var(--tw-transition),
        box-shadow 0.16s ease,
        transform 0.16s ease;
}

.tw-puesto:hover {
    box-shadow: var(--tw-shadow-light);
    transform: translateY(-1px);
}

.tw-puesto:focus-visible {
    outline: 2px solid var(--tw-font-primary);
    outline-offset: 2px;
}

.tw-puesto[aria-pressed='true'] {
    outline: 2px solid var(--tw-font-primary);
    outline-offset: 1px;
    box-shadow: var(--tw-shadow-light);
    transform: translateY(-1px);
}

.tw-puesto-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
    margin-bottom: 4px;
}

/* La cifra y su unidad en la misma línea: el número solo, grande y sin nombre,
   se lee como el total del puesto y es la cola. */
.tw-puesto-cifras {
    display: flex;
    align-items: baseline;
    gap: 6px;
}

.tw-puesto-num {
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    line-height: 1.2;
    color: var(--tw-font-primary);
    font-variant-numeric: tabular-nums;
}

.tw-puesto-num-que {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

.tw-puesto-linea {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    line-height: 1.5;
}

/* El ritmo del día va detrás de una línea: arriba está la cola —lo que hay— y
   abajo el rendimiento —cómo va—, y son dos lecturas distintas. */
.tw-puesto-linea--sep {
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid var(--tw-border-light);
}

.tw-puesto-quiet {
    color: var(--tw-font-extra-light);
}

.tw-puesto-alerta {
    color: var(--tw-tag-red-fg);
}

.tw-puesto--warm {
    border-color: var(--tw-tag-yellow-fg);
    background-color: var(--tw-tag-yellow-bg);
}

.tw-puesto--hot {
    border-color: var(--tw-tag-red-fg);
    background-color: var(--tw-tag-red-bg);
}

.tw-puesto--hot .tw-puesto-num {
    color: var(--tw-tag-red-fg);
}

/* Con fondo de color, la línea de separación gris desaparece. */
.tw-puesto--warm .tw-puesto-linea--sep,
.tw-puesto--hot .tw-puesto-linea--sep {
    border-top-color: var(--tw-border-medium);
}

/* ── La silueta de una ficha, mientras baja la primera tanda ───────────────

   Se dibuja con las clases de la ficha real —la vista la trae puesta en el
   HTML— para que mida lo que va a medir el dato. Lo único que hay que reponer
   son dos cosas que el texto traía de suyo:

   · el alto de cada renglón, que sin texto dentro lo pondría el bloque del
     esqueleto y encogería la tarjeta unos píxeles por línea;
   · y que no invite a pulsarla. La ficha real es un botón; ésta no lleva
     nada dentro que abrir. */
.tw-puesto.is-esqueleto {
    cursor: default;
}

.tw-puesto.is-esqueleto:hover {
    box-shadow: none;
    transform: none;
}

.tw-puesto.is-esqueleto .tw-puesto-head,
.tw-puesto.is-esqueleto .tw-puesto-cifras,
.tw-puesto.is-esqueleto .tw-puesto-linea {
    display: flex;
    align-items: center;
}

.tw-puesto.is-esqueleto .tw-puesto-head {
    min-height: 20px;
}

.tw-puesto.is-esqueleto .tw-puesto-cifras {
    min-height: 24px;
}

.tw-puesto.is-esqueleto .tw-puesto-linea {
    min-height: 18px;
}

/* El renglón de la lista, por lo mismo: una barra mide lo que mide la barra y
   una línea de texto mide su interlínea, que es mayor. El margen repone esa
   diferencia y el renglón entra donde estaba su silueta. */
.tw-inbox-row.is-esqueleto .tw-inbox-main > .tw-skeleton,
.tw-inbox-row.is-esqueleto .tw-inbox-side > .tw-skeleton {
    margin: 3px 0;
}

@media (max-width: 768px) {
    .tw-puestos {
        grid-template-columns: 1fr;
        max-height: none;
    }
}

/* ── El recorrido · el mapa de procesos con los números vivos encima ───────

   QUÉ RESUELVE. La misma información en lista era ilegible: doce grupos y
   doscientos renglones no contestan la pregunta con la que se abre la pantalla,
   que es «¿dónde se está atascando la planta?». Un punto por flujo, en el orden
   real del material, la contesta antes de leer una sola palabra.

   EL NÚMERO ES LO ACCIONABLE. Lo que espera a un tercero y lo que está
   corriendo van en la barra de composición del pie, no en la cifra.

   EL COLOR ES LA EDAD DE LO MÁS VIEJO ACCIONABLE, y no la cantidad. Cinco
   órdenes de esta mañana no son un problema; una de hace tres semanas sí, aunque
   esté sola. El tono va en el **borde entero y en el fondo**, igual que
   `.tw-queue` y `.tw-note`: una barra de color al costado es la marca de una
   pantalla escrita de memoria.

   EL PUNTO VACÍO SE APAGA. No se esconde —el hueco en el recorrido diría que ese
   flujo no existe— pero deja de competir por la mirada. La planta al día tiene
   que verse tranquila.

   ES UN FILTRO, no un adorno: cada punto es un botón que acota la lista de
   abajo. Por eso son `<button>` de verdad y llevan `aria-pressed`. */

/* LA BANDA ES UNA BANDEJA, Y LOS PUNTOS SON FICHAS ENCIMA. El fondo secundario
   la separa de la lista sin una línea de más: se lee como el panel de mando de
   la pantalla y no como un renglón más grande. Es la misma relación que hay en
   todo el tema entre `.tw-panel` y las tarjetas que lleva dentro. */
.tw-flow {
    /* `0 0 auto` y no el `1 1 auto` de serie: el panel acota su alto con flex en
       columna, así que sin esto la fila del recorrido cede su espacio a la lista
       y los puntos se aplastan a 18 px. */
    flex: 0 0 auto;
    display: flex;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 0;
    padding: 16px 14px 14px;
    overflow-x: auto;
    background-color: var(--tw-bg-secondary);
}

/* El recorrido no cabe y sigue a la derecha. El desvanecido lo dice sin ocupar
   sitio; la clase la pone el guion sólo cuando de verdad hay desborde, para que
   un recorrido que cabe entero no salga con el último punto medio borrado. */
.tw-flow--desborda {
    mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent 100%);
}

/* Sin la fila de «fuera del camino» —quien no ve impresión ni decisiones— el
   cierre de la banda le toca al recorrido. */
.tw-flow:last-of-type {
    padding-bottom: 16px;
    border-bottom: 1px solid var(--tw-border-light);
}

/* El carril entre dos puntos. Una línea con su punta, no un galón suelto: lo que
   se dibuja es un recorrido, y dos cajas con un símbolo en medio no lo parecen.
   Va como elemento propio y no como `::after` del punto porque el último de la
   fila no lleva, y un pseudoelemento no sabe si está al borde. */
.tw-flow-arrow {
    flex: 0 0 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    color: var(--tw-border-strong);
}

.tw-flow-arrow::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    height: 1px;
    background-color: var(--tw-border-medium);
}

.tw-flow-arrow .ti {
    position: relative;
    font-size: 12px !important;
    /* Tapa el carril justo detrás de la punta. Va con el fondo de la bandeja,
       que es sobre lo que se dibuja. */
    background-color: var(--tw-bg-secondary);
}

.tw-flow-node {
    /* Reparten el ancho a partes iguales y no bajan de lo que hace falta para
       leer el nombre corto; por debajo de eso manda el desplazamiento. */
    flex: 1 1 0;
    min-width: 108px;
    max-width: 200px;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 10px 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    text-align: left;
    cursor: pointer;
    transition:
        background-color var(--tw-transition),
        border-color var(--tw-transition),
        box-shadow 0.16s ease,
        transform 0.16s ease;
}

/* La elevación al pasar por encima. Un píxel y una sombra ligera: lo justo para
   que el punto se sienta pulsable sin que la fila entera baile. */
.tw-flow-node:hover {
    box-shadow: var(--tw-shadow-light);
    transform: translateY(-1px);
}

.tw-flow-node:focus-visible {
    outline: 2px solid var(--tw-font-primary);
    outline-offset: 2px;
}

/* El elegido. Se marca con un anillo, la sombra y el mismo píxel de elevación:
   queda levantado del carril, que es lo que significa.

   El anillo va por fuera con `outline` y no en el borde. Con `border-color`
   apenas se distinguía sobre un punto en ámbar o en rojo —el borde ya lleva su
   propio tono de urgencia— y el filtro parecía no estar puesto. */
.tw-flow-node[aria-pressed='true'] {
    outline: 2px solid var(--tw-font-primary);
    outline-offset: 1px;
    box-shadow: var(--tw-shadow-light);
    transform: translateY(-1px);
}

.tw-flow-node-head {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tw-flow-node-head .ti {
    font-size: var(--tw-icon-size-sm) !important;
    color: var(--tw-font-light);
    flex: 0 0 auto;
}

.tw-flow-node-count {
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    line-height: 1.2;
    color: var(--tw-font-primary);
    font-variant-numeric: tabular-nums;
}

.tw-flow-node-foot {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    line-height: 1.3;
}

/* La segunda línea: lo que hay ahí y no le toca a nadie. Apagada y por debajo
   del pie: el orden de lectura es cuántos te tocan, desde cuándo, y sólo después
   qué más hay dentro. */
.tw-flow-node-sub {
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-extra-light);
    line-height: 1.3;
}

/* ── La barra de composición ───────────────────────────────────────────────

   Tres segmentos —te toca, esperando, va corriendo— a lo ancho del punto. Es lo
   que convierte un número en una lectura: «Compra 1» y «Compra 1» se leen igual
   aunque uno tenga cuatro esperas detrás y el otro ninguna, y la proporción se
   ve antes que las cifras.

   En escala de grises a propósito. El color de esta pantalla ya significa una
   cosa —la edad— y un segundo alfabeto de colores encima obliga a aprenderse
   dos leyendas para mirar un punto. */

.tw-flow-node-mix {
    display: flex;
    gap: 2px;
    margin-top: 6px;
    height: 3px;
}

.tw-flow-node-mix > span {
    border-radius: var(--tw-radius-pill);
    min-width: 3px;
}

.tw-flow-node-mix > .is-accion {
    background-color: var(--tw-font-secondary);
}

.tw-flow-node-mix > .is-espera {
    background-color: var(--tw-border-strong);
}

.tw-flow-node-mix > .is-curso {
    background-color: var(--tw-border-medium);
}

/* Los dos tonos de urgencia, y ninguno para lo normal. Salen de los mismos
   tokens que los chips, así que el tema oscuro los trae resueltos y un renglón
   rojo de la lista y un punto rojo del recorrido son el mismo rojo.

   NO HAY VERDE. Un punto con cinco órdenes de esta mañana no es un logro, es el
   trabajo del día: pintarlo de verde llenaría el recorrido de color y dejaría al
   rojo compitiendo con nueve verdes. Lo que está al día se ve **normal**, y sólo
   destaca lo que lleva parado. */
.tw-flow-node--warm {
    border-color: var(--tw-tag-yellow-fg);
    background-color: var(--tw-tag-yellow-bg);
}

.tw-flow-node--hot {
    border-color: var(--tw-tag-red-fg);
    background-color: var(--tw-tag-red-bg);
}

.tw-flow-node--hot .tw-flow-node-count {
    color: var(--tw-tag-red-fg);
}

.tw-flow-node--warm .tw-flow-node-head,
.tw-flow-node--warm .tw-flow-node-foot,
.tw-flow-node--hot .tw-flow-node-head,
.tw-flow-node--hot .tw-flow-node-foot {
    color: var(--tw-font-secondary);
}

.tw-flow-node--warm .tw-flow-node-head .ti,
.tw-flow-node--hot .tw-flow-node-head .ti {
    color: var(--tw-font-secondary);
}

/* Con fondo de color, la barra necesita su propio contraste: el gris de borde
   sobre ámbar o rojo claro desaparece. */
.tw-flow-node--warm .tw-flow-node-mix > .is-accion,
.tw-flow-node--hot .tw-flow-node-mix > .is-accion {
    background-color: var(--tw-font-primary);
}

/* Tiene cosas dentro pero ninguna espera a nadie —cuatro órdenes a proveedor
   con el camión en camino—. No se apaga del todo, porque se puede abrir y hay
   días que contar; pero no lleva color, porque no hay nada que reclamar. */
.tw-flow-node--quiet .tw-flow-node-count {
    color: var(--tw-font-light);
}

/* Tiene algo que decir pero no acota nada: el punto de las piezas embarcadas,
   que son etiquetas y no contenedores. Se pinta entero —hay una cifra que leer—
   pero no finge ser pulsable: el cursor de mano sobre algo que no responde es la
   forma más barata de enseñar a no fiarse de los demás puntos. */
.tw-flow-node--static {
    cursor: default;
}

/* Vacío: ni número, ni color, ni peso. */
.tw-flow-node--idle {
    background-color: transparent;
    border-style: dashed;
    cursor: default;
}

.tw-flow-node--idle .tw-flow-node-count,
.tw-flow-node--idle .tw-flow-node-head,
.tw-flow-node--idle .tw-flow-node-head .ti {
    color: var(--tw-font-extra-light);
}

/* El pin naranja de las puertas que nadie comprueba. Es un dato aparte de la
   cantidad y de la edad —un embarque que salió sin packing list puede ser de
   hoy— así que va como marca propia y no mezclado en el tono del punto. */
.tw-flow-node-flag {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    margin-top: 4px;
    padding: 1px 6px;
    border-radius: var(--tw-radius-pill);
    font-size: var(--tw-font-size-xs);
    background-color: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
    white-space: nowrap;
}

/* ── La entrada ────────────────────────────────────────────────────────────

   Los puntos aparecen de izquierda a derecha, como se recorre la planta. Dura
   menos de medio segundo en total y **sólo se hace la primera vez**: la pantalla
   se repinta cada minuto, y una animación en cada refresco pasa de gesto a tic.

   `--i` lo pone el guion con el índice del punto. Sin él el retraso es cero y la
   fila entra de golpe, que es exactamente lo que debe pasar si algo falla. */

@keyframes tw-flow-in {
    from {
        opacity: 0;
        transform: translateY(6px);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

.tw-flow--entra .tw-flow-node,
.tw-flow--entra .tw-flow-arrow {
    animation: tw-flow-in 0.32s ease both;
    animation-delay: calc(var(--i, 0) * 34ms);
}

/* Quien pidió menos movimiento no lo recibe. Un tablero que se lee todos los
   días no puede costar un mareo. */
@media (prefers-reduced-motion: reduce) {
    .tw-flow--entra .tw-flow-node,
    .tw-flow--entra .tw-flow-arrow {
        animation: none;
    }

    .tw-flow-node {
        transition: none;
    }

    .tw-flow-node:hover,
    .tw-flow-node[aria-pressed='true'] {
        transform: none;
    }
}

/* Lo que no está en el camino: impresión, decisiones, lo que se fue a la calle.
   Va en su propia fila y detrás de un rótulo, porque colgarlo del recorrido
   diría que el material pasa por ahí en ese orden, y no es cierto. */
.tw-flow-aside {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: 6px;
    padding: 0 14px 16px;
    background-color: var(--tw-bg-secondary);
    border-bottom: 1px solid var(--tw-border-light);
}

/* El rótulo se lleva su propio renglón. Compartiendo fila con los puntos les
   robaba el ancho de una ficha entera, así que a partir de cuatro el último
   caía solo a una segunda línea con un hueco al lado. */
.tw-flow-aside-label {
    flex: 1 0 100%;
    margin-bottom: 2px;
    font-size: var(--tw-font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--tw-font-extra-light);
}

.tw-flow-aside .tw-flow-node {
    flex: 0 1 158px;
}


/* ── El encabezado de grupo dentro de una bandeja ──────────────────────────

   Lo estrena «Procesos», que es la única bandeja que mezcla flujos: quince
   cadenas distintas en una sola lista se leen como un revoltijo si no se dice
   dónde acaba una y empieza otra. No es un título de sección —no lleva el peso
   ni el aire de `.tw-section-title`— sino un separador con nombre: pegajoso al
   hacer scroll, para que al bajar por doscientos renglones siempre se sepa de
   qué flujo son los que se están mirando.

   El fondo es opaco a propósito. Con `sticky` y fondo transparente, los
   renglones se ven pasar por debajo del rótulo. */

.tw-inbox-group {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    background-color: var(--tw-bg-secondary);
    border-bottom: 1px solid var(--tw-border-light);
    color: var(--tw-font-secondary);
}

/* No es el primero de la lista: los grupos que vienen después llevan una línea
   arriba para que el salto entre flujos se vea antes de leer el rótulo. */
.tw-inbox-group:not(:first-child) {
    border-top: 1px solid var(--tw-border-light);
}

/* ── El corte por naturaleza, dentro de un grupo ───────────────────────────

   «Te toca», «Esperando», «Va corriendo». Va por debajo del encabezado del
   flujo y con menos peso que él: el flujo es de qué se habla y esto es en qué
   estado de atención está, que es una jerarquía y no dos rótulos iguales.

   No es pegajoso, al contrario que `.tw-inbox-group`: son tres cortes cortos
   dentro de un grupo, y tres rótulos pegados compitiendo por la misma franja de
   arriba se pisan entre ellos al hacer scroll. */

.tw-inbox-sub {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px 4px;
    font-size: var(--tw-font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--tw-font-light);
}

/* «Se quedó sin hacer» lleva el único color de los cuatro rótulos. Es el bloque
   que no debería existir —esos renglones tenían que estar cerrados hace días— y
   los otros tres son estados normales de la planta. */
/* Bloqueado · el único rótulo en rojo. Es la clase que no se resuelve donde
   dice el renglón: pedirle a alguien lo que no puede hacer es como se pierde la
   confianza en una lista entera. */
.tw-inbox-sub--bloqueo {
    color: var(--tw-tag-red-fg);
}

.tw-inbox-sub--bloqueo .tw-inbox-sub-count {
    color: var(--tw-tag-red-fg);
}

/* Y en el renglón, el motivo en lugar de la instrucción. */
.tw-inbox-bloqueo {
    display: flex;
    align-items: baseline;
    gap: 5px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-tag-red-fg);
    line-height: 1.45;
}

.tw-flow-node-flag--bloqueo {
    background-color: var(--tw-tag-red-bg);
    color: var(--tw-tag-red-fg);
}

.tw-inbox-sub--olvido {
    color: var(--tw-tag-orange-fg);
}

.tw-inbox-sub--olvido .tw-inbox-sub-count {
    color: var(--tw-tag-orange-fg);
}

.tw-inbox-sub-count {
    color: var(--tw-font-extra-light);
    font-variant-numeric: tabular-nums;
}

.tw-inbox-group-title {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

/* El cuerpo del panel es `overflow: hidden` porque casi siempre lleva dentro una
   DataTable, que scrollea sola. Una bandeja no: si no se le devuelve el scroll,
   la lista se corta por abajo al llegar al alto de la pantalla y las diferencias
   más viejas —que van primero por antigüedad, pero también son las más— quedan
   fuera sin ninguna señal. */
.tw-panel-body:has(> .tw-inbox-list) {
    overflow-y: auto;
}

.tw-inbox-row {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--tw-border-light);
    transition: background-color var(--tw-transition);
}

.tw-inbox-row:last-child {
    border-bottom: 0;
}

.tw-inbox-row--link {
    cursor: pointer;
}

.tw-inbox-row--link:hover {
    background-color: var(--tw-bg-transparent-light);
}

/* Ya revisada / ya decidida: sigue en la lista —el histórico es la evidencia—
   pero deja de competir por la mirada con lo que todavía espera. */
.tw-inbox-row--done .tw-inbox-title {
    color: var(--tw-font-secondary);
}

.tw-inbox-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.tw-inbox-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

/* El nombre largo de la pieza, detrás del número de parte: el número identifica
   y la descripción es lo que la gente reconoce. */
.tw-inbox-subtitle {
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── LA IDENTIDAD DEL RENGLÓN ──────────────────────────────────────────────

   El código por el que se va a buscar el material, delante del número de parte
   y en el mismo sitio en todas las procedencias de la bandeja. En monoespaciado
   porque se compara carácter a carácter contra un papel pegado a una caja, y no
   se lee: se coteja.

   Con fondo cuando hay etiqueta que buscar —la caja apartada, el sobrante, el
   bulto emplayado— y sin él cuando lo que se enseña es sólo dónde estaba el
   material. La diferencia importa: uno se encuentra por el papel y el otro hay
   que ir a buscarlo preguntando. */
.tw-inbox-id {
    flex: 0 0 auto;
    padding: 1px 6px;
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-tertiary);
    color: var(--tw-font-primary);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular);
    letter-spacing: -0.01em;
    white-space: nowrap;
}

.tw-inbox-id--suelto {
    padding-left: 0;
    padding-right: 0;
    background-color: transparent;
    color: var(--tw-font-light);
    border-bottom: 1px dotted var(--tw-border-medium);
    border-radius: 0;
}

/* La nota escrita a mano, en una línea. Entera al pasar por encima.

   Cinco renglones seguidos con el mismo párrafo de oficina —la decisión que los
   creó a todos— convierten la lista en un muro: lo que distingue un renglón del
   siguiente queda arriba, y la nota sólo hace falta cuando ya se eligió cuál
   mirar. */
.tw-inbox-nota {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}

.tw-inbox-line {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    line-height: 1.45;
}

.tw-inbox-line--quiet {
    color: var(--tw-font-light);
}

/* La resta del paso. En cifras tabulares porque son números que se comparan
   entre renglones, y ahí un «1» más estrecho que un «7» desalinea la columna. */
.tw-inbox-sums {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    font-variant-numeric: tabular-nums;
}

/* ── La instrucción y la salida de cada renglón ────────────────────────────

   NINGUNA ACCIÓN SE QUEDA SIN DECIR DÓNDE SE HACE. El renglón decía «Cerrarla» y
   ahí se acababa; ahora dice dónde está ese botón, y a la derecha lleva la
   salida: o el verbo que te lleva al control, o a quién avisar si no te toca. */

/* Cómo se hace, debajo de qué falta. Más apagado y más pequeño: qué falta es lo
   que se lee de un vistazo y esto es lo que se lee cuando ya decidiste atenderlo. */
.tw-inbox-como {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    line-height: 1.45;
}

/* La salida. Con aspecto de botón, pero es un `<span>`: el renglón entero ya es
   el enlace que lleva al mismo sitio, y un activable dentro de otro no es HTML
   válido. Está para decir qué va a pasar al tocar. */
.tw-inbox-cta {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-top: 2px;
    padding: 3px 8px;
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-sm);
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-primary);
    white-space: nowrap;
    transition: background-color var(--tw-transition);
}

.tw-inbox-row--link:hover .tw-inbox-cta {
    background-color: var(--tw-bg-quaternary);
}

/* Nadie debe nada: sólo se mira. Sin flecha y en gris, para que no se lea como
   una tarea que espera a alguien. */
.tw-inbox-cta--mirar {
    border-color: transparent;
    padding-left: 0;
    color: var(--tw-font-light);
}

.tw-inbox-row--link:hover .tw-inbox-cta--mirar {
    background-color: transparent;
}

/* No te toca: no hay botón, hay un recado. Sin borde, para que no se ofrezca
   como algo que se pulsa. */
.tw-inbox-cta--avisar {
    border-color: transparent;
    padding-left: 0;
    color: var(--tw-font-light);
}

.tw-inbox-row--link:hover .tw-inbox-cta--avisar {
    background-color: transparent;
}


/* Quién lo movió por última vez. Va debajo del área y más apagado: el área dice
   a qué puerta llamar y el nombre a quién preguntar, en ese orden. Se recorta
   con puntos suspensivos porque hay cuentas de puesto con nombres largos
   —«DOBLEZ GALVANIZADO»— que si no empujan la columna entera. */
.tw-inbox-quien {
    max-width: 15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--tw-font-extra-light);
}

.tw-inbox-side {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 4px;
    text-align: right;
}

/* ── La cadena: de dónde salió esto ────────────────────────────────────────

   Orden de producción → nesteo → hoja → etapa → contenedor. Se lee de izquierda
   a derecha como se recorre la planta, y cada eslabón que tiene pantalla propia
   es un enlace: la pregunta que sigue a «faltan tres piezas» casi siempre es
   «¿de qué trabajo?», y contestarla no debería costar una búsqueda.

   El eslabón que no existe se escribe igualmente, apagado. Omitirlo dejaría la
   cadena más corta y a nadie preguntándose por qué: «sin nesteo» es un dato
   —esa hoja se dio de alta a mano, o es un ensamble— y un hueco no lo es. */

.tw-trace {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 2px 4px;
    font-size: var(--tw-font-size-sm);
}

.tw-trace-item {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 6px;
    border-radius: var(--tw-radius-sm);
    color: var(--tw-font-secondary);
    white-space: nowrap;
}

.tw-trace-item .ti {
    font-size: var(--tw-icon-size-sm) !important;
    color: var(--tw-font-light);
    flex: 0 0 auto;
}

a.tw-trace-item {
    color: var(--tw-font-primary);
}

a.tw-trace-item:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.tw-trace-item--empty {
    color: var(--tw-font-extra-light);
}

.tw-trace-item--empty .ti {
    color: var(--tw-font-extra-light);
}

/* El separador entre eslabones. Va por CSS y no escrito en el HTML para que no
   quede uno colgando al final cuando el último eslabón no existe. */
.tw-trace-item + .tw-trace-item::before {
    content: '›';
    margin-right: 2px;
    color: var(--tw-font-extra-light);
}

/* En un teléfono el renglón se apila.

   A 375 px la columna derecha se comía un tercio del ancho y empujaba el número
   de parte a partirse; y la cadena, que allí siempre va a más de una línea, sacaba
   el separador «›» al principio de cada renglón nuevo, como si el eslabón
   colgara de nada. Debajo de 768 px el aparte pasa a ser una línea horizontal al
   pie y la cadena se lee como lo que es: una lista de chips con su icono. */
@media (max-width: 767.98px) {
    .tw-inbox-row {
        flex-direction: column;
        gap: 8px;
    }

    .tw-inbox-side {
        flex-direction: row;
        align-items: center;
        flex-wrap: wrap;
        gap: 4px 10px;
        width: 100%;
        text-align: left;
    }

    .tw-inbox-side .btn {
        margin-top: 0 !important;
        margin-left: auto;
    }

    .tw-trace-item {
        padding-left: 0;
    }

    .tw-trace-item + .tw-trace-item::before {
        content: none;
    }
}

/* ── Control segmentado ────────────────────────────────────────────────────

   Dos o tres vistas de la misma lista, excluyentes entre sí. Existe porque se
   estaba resolviendo con `.btn-group` de Bootstrap, que trae su propio azul y
   su propio alto y se ve como un botonera pegada dentro de la barra de vista.
   Esto es lo que hace Twenty: un carril gris y la opción activa levantada. */

.tw-seg {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px;
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
}

.tw-seg > button {
    appearance: none;
    border: 1px solid transparent;
    border-radius: var(--tw-radius-sm);
    background: none;
    padding: 3px 10px;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-tertiary);
    white-space: nowrap;
    transition:
        background-color var(--tw-transition),
        color var(--tw-transition);
}

.tw-seg > button:hover {
    color: var(--tw-font-primary);
}

.tw-seg > button.active {
    background-color: var(--tw-bg-primary);
    border-color: var(--tw-border-light);
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

/* Hubo una variante chica —`.tw-seg--sm`, dos píxeles menos de aire y un cuerpo
   de letra por debajo— para cuando el conmutador vive dentro de un panel y no
   en la barra de la pantalla. Se retiró el 10 de septiembre de 2026: la tenían
   dos pantallas de las quince que usan este control, así que lo que producía no
   era una jerarquía sino dos tamaños del mismo botón según dónde cayera. Un
   control del sistema tiene **un** tamaño; lo que lo separa del contenido es el
   sitio donde está, no que encoja.

   La única excepción viva es el modo monitor de la torre, que agranda el suyo:
   ése no compite con nada porque se lee desde el otro lado de la nave. */

/* ── La barra de lo marcado ────────────────────────────────────────────────

   Aparece pegada a la tabla cuando hay renglones marcados y dice qué se puede
   hacer con ellos. Va **dentro** del cuerpo de la tabla y no en la barra de la
   pantalla: lo marcado es de la tabla, y una acción que vive lejos de lo que
   toca obliga a mirar dos sitios para saber sobre qué va a caer.

   El fondo la separa del encabezado sin encerrarla en una tarjeta: es una tira,
   no un panel. */
.tw-seleccion {
    display: none;
    padding: 8px 12px;
    border-bottom: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-secondary);
}

.tw-seleccion.d-flex {
    display: flex !important;
}

/* ── La pastilla, cuando lo que decide es un radio ─────────────────────────

   La de arriba se pinta con botones y su clase `.active`, que sirve cuando el
   estado lo lleva el guion. Cuando el estado es del formulario —«se cuenta en
   piezas o en bultos», que viaja con el renglón— lo correcto es un `radio`: el
   navegador se encarga de que sólo uno esté marcado, el `name` agrupa, y no hay
   estado duplicado que sincronizar.

   Se estilan las etiquetas hermanas del radio. `.btn-check` de Bootstrap ya
   esconde el campo; aquí sólo se le da la forma de la pastilla. */
.tw-seg > label {
    appearance: none;
    border: 1px solid transparent;
    border-radius: var(--tw-radius-sm);
    background: none;
    padding: 3px 10px;
    margin: 0;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-tertiary);
    white-space: nowrap;
    cursor: pointer;
    transition:
        background-color var(--tw-transition),
        color var(--tw-transition);
}

.tw-seg > label:hover {
    color: var(--tw-font-primary);
}

.tw-seg > input:checked + label {
    background-color: var(--tw-bg-primary);
    border-color: var(--tw-border-light);
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

/* Y la variante que ocupa el ancho de su columna, para cuando la pastilla es un
   campo más de un formulario y tiene que alinearse con los de al lado. */
.tw-seg--fill {
    display: flex;
}

.tw-seg--fill > label {
    flex: 1 1 0;
    text-align: center;
}

/* En oscuro el carril estaba del revés. El gris de `secondary` es el fondo de
   página —negro puro—, así que el carril salía más oscuro que la tarjeta donde
   vive: un hueco, no una pastilla. Y la opción marcada se pintaba de
   `primary`, que es justo el color de la tarjeta, con lo que la elegida se leía
   como un agujero recortado y la otra como la que estaba puesta. Encima de la
   barra de la pantalla, que también es negra, el carril desaparecía entero.

   Aquí se mantiene la idea de Twenty —carril por detrás, opción elegida
   levantada— con los dos peldaños que sí suben en oscuro: el carril un punto
   por encima de la tarjeta y la elegida otro punto más, con el borde medio. */
[data-bs-theme='dark'] .tw-seg {
    background-color: var(--tw-bg-tertiary);
}

[data-bs-theme='dark'] .tw-seg > button.active,
[data-bs-theme='dark'] .tw-seg > input:checked + label {
    background-color: var(--tw-bg-quaternary);
    border-color: var(--tw-border-medium);
}

/* `hidden` es un atributo del HTML, no una sugerencia: sin esto, cualquier
   elemento con `d-flex` o con `display` propio lo ignora y se ve igual. */
[hidden] {
    display: none !important;
}

/* Marca de cambios sin guardar en la barra de título */
.tw-dirty-mark {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-tag-orange-fg);
}

.tw-dirty-mark::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: currentColor;
}

/* Vista previa impresa: fondo a cuadros para distinguir el papel del hueco */
.tw-print-preview {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 220px;
    padding: 16px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
}

.tw-print-preview img {
    max-width: 100%;
    background-color: #ffffff;
    box-shadow: var(--tw-shadow-light);
}

.tw-zpl-source {
    max-height: 220px;
    overflow: auto;
    margin: 0;
    padding: 8px 10px;
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-gray-90);
    color: var(--tw-gray-25);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--tw-font-size-xs);
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-all;
}

/* En pantallas estrechas las columnas laterales estorban más de lo que ayudan:
   el lienzo pasa a ocupar todo el ancho y los paneles se apilan debajo. */
@media (max-width: 1100px) {
    .tw-designer-panel {
        flex-direction: column;
        overflow-y: auto;
    }

    .tw-designer-side,
    .tw-designer-inspector {
        flex: 0 0 auto;
        width: 100%;
        border: 0;
        border-bottom: 1px solid var(--tw-border-light);
    }

    .tw-designer-stage {
        min-height: 420px;
    }
}

/* =====================================================================
   Marca
   ---------------------------------------------------------------------
   No hay logotipo: la marca es el nombre escrito con la tipografía del
   sistema. Es lo que mejor aguanta las tres superficies —panel, kiosco y
   documento impreso en blanco y negro— sin un archivo de imagen por medio.
   ===================================================================== */

.brand-wordmark {
    font-family: var(--tw-font-family);
    font-weight: var(--tw-weight-semibold);
    letter-spacing: -0.02em;
    color: var(--tw-font-primary);
    white-space: nowrap;
}

/* En el kiosco se lee de pie y a un metro. */
.brand-wordmark--lg {
    font-size: 1.25rem;
    line-height: 1.2;
}

/*
   Iniciales de quien tiene la sesión abierta. Sustituyen al logotipo en el
   encabezado del panel: en una terminal compartida lo que hay que reconocer
   de un vistazo es la persona, no la empresa.
*/
.brand-initials {
    display: inline-grid;
    place-items: center;
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border-radius: var(--tw-radius-pill);
    background: var(--tw-accent-secondary);
    color: var(--tw-accent);
    font-family: var(--tw-font-family);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-semibold);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    user-select: none;
}

.brand-initials--lg {
    width: 40px;
    height: 40px;
    font-size: var(--tw-font-size-md);
}

/* =====================================================================
   16. Cadena de trazabilidad
   ---------------------------------------------------------------------
   El listón de eslabones de la pieza: recepción, certificado, colada,
   corte, contenedor, etiqueta, verificación, pallet y embarque.

   Tres estados y no dos, porque la diferencia importa: un eslabón
   `pending` todavía no toca, y uno `missing` **debió ocurrir y no hay
   dato**. Pintarlos igual escondería justo lo que el auditor busca.
   ===================================================================== */

.tw-chain {
    display: flex;
    align-items: flex-start;
    gap: 0;
    overflow-x: auto;
    padding: 4px 0 8px;
}

.tw-chain-step {
    flex: 1 1 0;
    min-width: 92px;
    position: relative;
    padding: 0 6px;
    text-align: center;
}

/* La línea que une un eslabón con el siguiente. */
.tw-chain-step:not(:last-child)::after {
    content: '';
    position: absolute;
    top: 9px;
    left: calc(50% + 12px);
    right: calc(-50% + 12px);
    height: 2px;
    background-color: var(--tw-border-medium);
}

.tw-chain-step--done:not(:last-child)::after {
    background-color: var(--tw-tag-green-fg);
}

.tw-chain-dot {
    position: relative;
    z-index: 1;
    display: inline-grid;
    place-items: center;
    width: 20px;
    height: 20px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-primary);
    border: 2px solid var(--tw-border-strong);
    color: var(--tw-font-light);
}

.tw-chain-dot .ti {
    font-size: 0.75rem !important;
}

.tw-chain-step--done .tw-chain-dot {
    background-color: var(--tw-tag-green-bg);
    border-color: var(--tw-tag-green-fg);
    color: var(--tw-tag-green-fg);
}

.tw-chain-step--missing .tw-chain-dot {
    background-color: var(--tw-tag-red-bg);
    border-color: var(--tw-tag-red-fg);
    color: var(--tw-tag-red-fg);
}

.tw-chain-label {
    display: block;
    margin-top: 6px;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
    line-height: 1.25;
}

.tw-chain-step--done .tw-chain-label {
    color: var(--tw-font-primary);
}

.tw-chain-detail {
    display: block;
    margin-top: 2px;
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-tertiary);
    overflow-wrap: anywhere;
}

.tw-chain-step--missing .tw-chain-detail {
    color: var(--tw-tag-red-fg);
}

/*
   Los eslabones de identidad: colada, placa, lote, contenedor, pallet…
   Cada uno es una tarjeta que lleva a su registro, así que se comportan
   como enlaces aunque se vean como campos.
*/
.tw-links-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));
    gap: 8px;
}

.tw-link-card {
    display: block;
    padding: 10px 12px;
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    text-decoration: none;
    transition:
        background-color 0.1s ease,
        border-color 0.1s ease;
}

a.tw-link-card:hover {
    background-color: var(--tw-bg-secondary);
    border-color: var(--tw-border-strong);
}

.tw-link-card-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-tertiary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.tw-link-card-value {
    margin-top: 4px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
    overflow-wrap: anywhere;
}

.tw-link-card--empty .tw-link-card-value {
    color: var(--tw-font-light);
    font-weight: var(--tw-weight-regular);
}

/* ── La cadena, de pie ───────────────────────────────────────────────
 *
 * En horizontal cada eslabón pide 92 px, así que los nueve del pasaporte
 * son ochocientos: en un teléfono la cadena se convertía en una tira que
 * hay que arrastrar de lado, y lo que se busca —el eslabón rojo— podía
 * estar en el trozo que no se ve. Un desplazamiento lateral no avisa de
 * lo que esconde.
 *
 * De pie caben los nueve en la misma vista: el punto y la línea bajan por
 * la izquierda, y el rótulo y su dato se leen en un renglón cada uno.
 * Nada se recorta y nada hay que arrastrar. */
@media (max-width: 767.98px) {
    .tw-chain {
        display: block;
        overflow-x: visible;
        padding: 4px 0;
    }

    .tw-chain-step {
        display: grid;
        grid-template-columns: 20px 1fr;
        column-gap: 12px;
        min-width: 0;
        padding: 0 0 14px;
        text-align: left;
    }

    /* La línea pasa de unir dos puntos de lado a bajar de uno al
       siguiente. Es la misma línea, girada un cuarto de vuelta. */
    .tw-chain-step:not(:last-child)::after {
        top: 22px;
        bottom: -2px;
        left: 9px;
        right: auto;
        width: 2px;
        height: auto;
    }

    .tw-chain-label,
    .tw-chain-detail {
        grid-column: 2;
        margin-top: 0;
    }

    .tw-chain-label {
        font-size: var(--tw-font-size-md);
        line-height: 1.35;
    }

    .tw-chain-detail {
        font-size: var(--tw-font-size-sm);
    }

    /* El último eslabón no arrastra el hueco de la línea que no tiene. */
    .tw-chain-step:last-child {
        padding-bottom: 0;
    }

    /* Dos tarjetas de identidad por renglón. Dieciséis apiladas a lo
       ancho son mil seiscientos píxeles de desplazamiento para leer
       dieciséis palabras; de dos en dos son la mitad, y el valor de cada
       una es corto —un folio, una colada— así que no se recorta. */
    .tw-links-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 6px;
    }

    .tw-link-card {
        padding: 8px 10px;
    }

    /* Y el rótulo de la tarjeta cabe: en versalitas y con el icono
       delante, «Orden a proveedor» no entra en 160 px de una línea. */
    .tw-link-card-label {
        line-height: 1.3;
    }

    /* ── La línea de tiempo ──────────────────────────────────────────
     *
     * El cuerpo reparte título y fecha a los dos extremos del renglón.
     * En 343 px no hay dos extremos: la fecha se comía la mitad del
     * título y los dos quedaban en tres palabras cada uno. La fecha baja
     * a su propio renglón, que es donde se lee sin competir. */
    .tw-timeline-body {
        flex-direction: column;
        align-items: flex-start;
        gap: 2px;
    }
}

/* =====================================================================
   Esqueletos de carga y microanimaciones
   =====================================================================

   Dos ideas y ninguna más:

   1. **Nada aparece de golpe ni se queda en blanco.** Mientras algo se trae
      del servidor se enseña su forma —el esqueleto—, no un texto de espera ni
      un hueco: quien mira ya sabe cuántas filas vienen y dónde va cada dato.
   2. **El movimiento explica, no adorna.** Sólo se mueve lo que cambió, dura
      poco y sigue siempre la misma curva. Un sistema en el que todo late
      cansa a las dos horas de turno.

   Todo lo de esta sección se apaga completo con `prefers-reduced-motion`.
   ===================================================================== */

/* --- Bloque de esqueleto --- */
.tw-skeleton {
    display: block;
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-skeleton-base);
    background-image: linear-gradient(
        90deg,
        transparent 0%,
        var(--tw-skeleton-shine) 45%,
        var(--tw-skeleton-shine) 55%,
        transparent 100%
    );
    background-size: 220% 100%;
    background-repeat: no-repeat;
    animation: tw-shimmer 1.25s var(--tw-ease) infinite;
    /* Sin contenido no tiene alto propio y colapsaría la fila */
    min-height: 10px;
}

/* Línea de texto: el ancho lo pone quien la usa para que dos renglones
   seguidos no midan lo mismo y el bloque no parezca una tabla de barras. */
.tw-skeleton--text {
    height: 10px;
    border-radius: var(--tw-radius-xs);
}

.tw-skeleton--title {
    height: 14px;
    border-radius: var(--tw-radius-xs);
}

.tw-skeleton--pill {
    height: 18px;
    border-radius: var(--tw-radius-pill);
}

.tw-skeleton--circle {
    border-radius: 50%;
}

.tw-skeleton--block {
    height: 100%;
    border-radius: var(--tw-radius-md);
}

/* Celdas de avance · **un renglón, como el resto de la fila**.

   El surtido y la producción llevaban la barra arriba y el desglose debajo, dos
   y tres renglones. Centrada cada celda por su cuenta —que es como se centra una
   celda de tabla— la barra quedaba por encima del identificador, del estado y
   del cliente, que van a media altura, y la fila se leía descuadrada. Se probó
   alinear la fila arriba y fue peor: el aire que la fila repartía se iba entero
   abajo.

   Con todo en una línea no hay nada que cuadrar, y la fila baja de tres
   renglones a uno: en la misma pantalla caben tres veces más órdenes. Lo que no
   cabe —cuántas quedan sin verificar— va en el `title` de la celda. */
.tw-avance-celda {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

/* El desglose, al lado de la cifra y en el gris del detalle. */
.tw-avance-detalle {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* Celda de tabla cuyo dato llega en una segunda petición · el listado de
   órdenes de cliente pide el surtido y la producción detrás de la tabla.

   Los renglones se dibujan con el alto que va a ocupar la celda de verdad —la
   barra y sus una o dos líneas de detalle— para que la fila no cambie de altura
   cuando el dato entra: en una tabla de scroll continuo, una fila que crece
   mueve todas las de abajo bajo el cursor. */
/* El hueco mide la altura de línea de la celda, no la del bloque de esqueleto,
   así que la fila no cambia de alto cuando el dato entra. En una tabla de
   scroll continuo eso importa el doble: Scroller mide una fila y da por hecho
   que todas miden igual. */
.tw-avance-esqueleto {
    height: calc(var(--tw-font-size-md) * 1.5);
}

@keyframes tw-shimmer {
    0% {
        background-position: 140% 0;
    }
    100% {
        background-position: -40% 0;
    }
}

/* Grupo de líneas: el último renglón más corto, como un párrafo de verdad */
.tw-skeleton-lines {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.tw-skeleton-lines .tw-skeleton:last-child {
    width: 60%;
}

/* --- Entrada de contenido --- */
/* Lo que llega después de su esqueleto sube dos pixeles al aparecer: es lo que
   distingue «ya cargó» de «siempre estuvo ahí». */
.tw-fade-in {
    animation: tw-fade-in var(--tw-duration-base) var(--tw-ease) both;
}

@keyframes tw-fade-in {
    from {
        opacity: 0;
        transform: translateY(2px);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

/* Tarjetas del tablero: entran en cascada, de arriba abajo. El retardo se
   corta a la sexta —una espera de medio segundo para ver la última tarjeta ya
   no es elegancia, es lentitud. */
.tw-dash .tw-card {
    animation: tw-card-in var(--tw-duration-slow) var(--tw-ease) both;
}

.tw-dash .tw-card:nth-child(2) {
    animation-delay: 0.04s;
}
.tw-dash .tw-card:nth-child(3) {
    animation-delay: 0.08s;
}
.tw-dash .tw-card:nth-child(4) {
    animation-delay: 0.12s;
}
.tw-dash .tw-card:nth-child(5) {
    animation-delay: 0.16s;
}
.tw-dash .tw-card:nth-child(n + 6) {
    animation-delay: 0.2s;
}

@keyframes tw-card-in {
    from {
        opacity: 0;
        transform: translateY(6px);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

/* --- Tarjetas y superficies --- */
.tw-card {
    transition:
        border-color var(--tw-duration-fast) var(--tw-ease),
        box-shadow var(--tw-duration-base) var(--tw-ease);
}

.tw-card:hover {
    border-color: var(--tw-border-strong);
}

/* La tarjeta que además es enlace sí se levanta: hay a dónde ir. */
a.tw-card:hover,
.tw-card--clickable:hover {
    box-shadow: var(--tw-shadow-light);
}

.tw-link-card {
    transition:
        background-color var(--tw-duration-fast) var(--tw-ease),
        border-color var(--tw-duration-fast) var(--tw-ease),
        transform var(--tw-duration-fast) var(--tw-ease);
}

a.tw-link-card:hover {
    transform: translateY(-1px);
}

a.tw-link-card:active {
    transform: none;
}

/* --- Botones, chips y etiquetas --- */
.btn,
.tw-chip {
    transition:
        background-color var(--tw-duration-fast) var(--tw-ease),
        border-color var(--tw-duration-fast) var(--tw-ease),
        color var(--tw-duration-fast) var(--tw-ease),
        box-shadow var(--tw-duration-fast) var(--tw-ease),
        transform var(--tw-duration-fast) var(--tw-ease);
}

/* Hundir el botón medio pixel al pulsarlo es la única confirmación táctil que
   hay en una pantalla: sin ella, un clic que tarda parece un clic perdido. */
.btn:active:not(:disabled),
.tw-chip:active {
    transform: translateY(0.5px);
}

.btn:disabled,
.btn.disabled {
    transition: none;
}

/* Botón trabajando: el texto se apaga y el contenido no salta de sitio.
   Se deshabilita mientras dura para que no se pueda pulsar dos veces, pero
   recupera la opacidad completa: un botón a 0.45 se lee como «no disponible»
   y lo que hay que leer es «te escuché, voy». Los colores del apagado y el
   color del aro salen de las variables de la sección 7. */
.btn.is-busy {
    pointer-events: none;
    color: transparent !important;
    position: relative;
    --tblr-btn-disabled-opacity: 1;
    opacity: 1;
}

.btn.is-busy::after {
    content: '';
    position: absolute;
    inset: 0;
    margin: auto;
    width: 13px;
    height: 13px;
    border: 2px solid var(--tw-btn-spinner, var(--tw-font-secondary));
    /* El hueco es lo que deja ver que gira: un aro cerrado parece quieto. */
    border-top-color: transparent;
    border-radius: 50%;
    animation: tw-spin 0.6s linear infinite;
}

/* Un botón sólo de icono no tiene rótulo que apagar, y el aro de 13px se le
   encima al glifo. El icono se esconde aparte y el aro se ajusta a la caja. */
.btn-icon.is-busy > * {
    visibility: hidden;
}

.btn-icon.is-busy::after {
    width: 12px;
    height: 12px;
}

/* Nada de animación para quien la pidió apagada: el aro se queda fijo, pero
   el botón sigue apagando el rótulo, que es lo que comunica el estado. */
@media (prefers-reduced-motion: reduce) {
    .btn.is-busy::after {
        animation-duration: 2.4s;
    }
}

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

.tw-tag {
    transition: background-color var(--tw-duration-fast) var(--tw-ease);
}

/* --- Foco visible --- */
/* Se dibuja con `outline` y no con `box-shadow`: el contorno no desplaza nada
   y se ve igual sobre una fila de tabla que sobre una tarjeta. */
.btn:focus-visible,
.tw-chip:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 1px;
    border-radius: var(--tw-radius-sm);
}

/* El botón que en realidad es la etiqueta de un `input file`.
   Quien recibe el foco es el campo, y el campo no se ve: sin esto, tabular hasta
   «Elegir .LXD» no enciende nada y el foco desaparece de la pantalla. */
.btn:focus-within {
    outline: 2px solid var(--tw-accent);
    outline-offset: 1px;
    border-radius: var(--tw-radius-sm);
}

/* --- Barra de avance --- */
.tw-progress > span {
    transition: width var(--tw-duration-slow) var(--tw-ease);
}

/* --- Modales y menús --- */
.modal.fade .modal-dialog {
    transition:
        transform var(--tw-duration-base) var(--tw-ease),
        opacity var(--tw-duration-base) var(--tw-ease);
    transform: translateY(8px) scale(0.99);
    opacity: 0;
}

.modal.show .modal-dialog {
    transform: none;
    opacity: 1;
}

/* El menú entra con `translate`, NO con `transform`: Popper escribe la posición
   del menú en el `transform` en línea del elemento, y una animación —aunque sea
   de tres fotogramas— gana en la cascada a cualquier estilo en línea. Con
   `transform` aquí, el `translate3d` de Popper se perdía y el menú se pegaba a
   la esquina superior izquierda del botón, ignorando `dropdown-menu-end` y
   saliéndose de la pantalla. `translate` es una propiedad aparte que se compone
   con el `transform` en vez de reemplazarlo. */
.dropdown-menu.show {
    animation: tw-menu-in var(--tw-duration-fast) var(--tw-ease) both;
}

@keyframes tw-menu-in {
    from {
        opacity: 0;
        translate: 0 -4px;
    }
    to {
        opacity: 1;
        translate: 0 0;
    }
}

.dropdown-item {
    transition: background-color var(--tw-duration-fast) var(--tw-ease);
}

/* --- Filas del tablero y de las listas de resumen --- */
.tw-dash-row,
.tw-dash-table tbody td {
    transition: background-color var(--tw-duration-fast) var(--tw-ease);
}

/* =====================================================================
   DataTables · carga y detalle
   =====================================================================

   La tabla es la pantalla principal del sistema: casi todo el trabajo pasa
   por una. Aquí se le quitan las dos cosas que la hacían sentir prestada —el
   cartel «Procesando...» en medio del contenido y el salto en blanco entre
   páginas— y se le pone en su lugar el esqueleto de sus propias filas.
   ===================================================================== */

/* El aviso de DataTables se sustituye por una barra fina sobre la tabla: el
   cartel centrado tapaba justo la fila que se estaba mirando. */
.dataTables_processing,
.dt-processing {
    top: 0 !important;
    left: 0 !important;
    right: 0 !important;
    bottom: auto !important;
    width: 100% !important;
    height: 2px !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
    color: transparent !important;
    font-size: 0 !important;
    overflow: hidden;
    z-index: 4;
}

.dataTables_processing::after,
.dt-processing::after {
    content: '';
    position: absolute;
    inset: 0;
    background-color: var(--tw-accent);
    transform-origin: left center;
    animation: tw-indeterminate 1.1s var(--tw-ease) infinite;
}

@keyframes tw-indeterminate {
    0% {
        transform: translateX(-100%) scaleX(0.35);
    }
    60% {
        transform: translateX(60%) scaleX(0.6);
    }
    100% {
        transform: translateX(100%) scaleX(0.35);
    }
}

/* Esqueleto de filas: se dibuja dentro del cuerpo scrolleable, con el mismo
   alto de fila y el mismo número de columnas que la tabla real, así que al
   llegar los datos nada cambia de sitio. */
.tw-dt-skeleton {
    position: absolute;
    inset: 0;
    z-index: 3;
    overflow: hidden;
    background-color: var(--tw-bg-primary);
    animation: tw-fade-in var(--tw-duration-fast) var(--tw-ease) both;
}

.tw-dt-skeleton.is-leaving {
    animation: tw-fade-out var(--tw-duration-fast) var(--tw-ease) both;
}

@keyframes tw-fade-out {
    to {
        opacity: 0;
    }
}

.tw-dt-skeleton-row {
    display: flex;
    align-items: center;
    gap: 24px;
    height: var(--tw-row-height);
    padding: 0 1.1rem;
    border-bottom: 1px solid var(--tw-border-light);
}

/* Las filas se apagan hacia abajo: la vista se queda en las primeras, que son
   las que van a llegar antes. */
.tw-dt-skeleton-row:nth-child(n + 7) {
    opacity: 0.7;
}
.tw-dt-skeleton-row:nth-child(n + 11) {
    opacity: 0.45;
}
.tw-dt-skeleton-row:nth-child(n + 15) {
    opacity: 0.25;
}

.tw-dt-skeleton-cell {
    flex: 1 1 0;
    height: 10px;
    border-radius: var(--tw-radius-xs);
}

/* Que no todas las celdas midan lo mismo es lo que hace que se lea como una
   tabla y no como un ecualizador. */
.tw-dt-skeleton-row .tw-dt-skeleton-cell:nth-child(3n) {
    flex-grow: 0.6;
}

.tw-dt-skeleton-row .tw-dt-skeleton-cell:nth-child(4n) {
    flex-grow: 1.4;
}

/* Cada fila arranca su brillo un poco después que la anterior: la onda recorre
   la tabla hacia abajo en lugar de latir toda a la vez. */
.tw-dt-skeleton-row:nth-child(2) .tw-dt-skeleton-cell {
    animation-delay: 0.06s;
}
.tw-dt-skeleton-row:nth-child(3) .tw-dt-skeleton-cell {
    animation-delay: 0.12s;
}
.tw-dt-skeleton-row:nth-child(4) .tw-dt-skeleton-cell {
    animation-delay: 0.18s;
}
.tw-dt-skeleton-row:nth-child(5) .tw-dt-skeleton-cell {
    animation-delay: 0.24s;
}
.tw-dt-skeleton-row:nth-child(n + 6) .tw-dt-skeleton-cell {
    animation-delay: 0.3s;
}

/* El contenedor del cuerpo tiene que poder alojar el esqueleto encima */
.dataTables_scrollBody,
.dt-scroll-body {
    position: relative;
}

/* Filas nuevas tras un dibujado: entran las primeras y el resto ya está.
   Animar doscientas filas cuesta más de lo que aporta. */
.tw-dt-drawn tbody tr:nth-child(-n + 12) {
    animation: tw-row-in var(--tw-duration-base) var(--tw-ease) both;
}

.tw-dt-drawn tbody tr:nth-child(2) {
    animation-delay: 0.015s;
}
.tw-dt-drawn tbody tr:nth-child(3) {
    animation-delay: 0.03s;
}
.tw-dt-drawn tbody tr:nth-child(4) {
    animation-delay: 0.045s;
}
.tw-dt-drawn tbody tr:nth-child(5) {
    animation-delay: 0.06s;
}
.tw-dt-drawn tbody tr:nth-child(6) {
    animation-delay: 0.075s;
}
.tw-dt-drawn tbody tr:nth-child(n + 7) {
    animation-delay: 0.09s;
}

@keyframes tw-row-in {
    from {
        opacity: 0;
        transform: translateY(3px);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

/* Encabezado fijo: la sombra sólo aparece cuando hay contenido debajo del
   borde, que es cuando de verdad hace falta separar. */
.dataTables_scrollHead,
.dt-scroll-head {
    transition: box-shadow var(--tw-duration-base) var(--tw-ease);
}

.tw-dt-scrolled .dataTables_scrollHead,
.tw-dt-scrolled .dt-scroll-head {
    box-shadow:
        0 1px 0 var(--tw-border-medium),
        0 4px 8px -6px rgba(0, 0, 0, 0.25);
}

/* Ordenar es una acción: el encabezado responde al cursor como un botón */
.table thead .sorting,
.table thead .sorting_asc,
.table thead .sorting_desc {
    transition:
        color var(--tw-duration-fast) var(--tw-ease),
        background-color var(--tw-duration-fast) var(--tw-ease);
    cursor: pointer;
}

.table thead .sorting:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.table thead .sorting_asc,
.table thead .sorting_desc {
    color: var(--tw-font-primary);
}

.table thead .sorting:after,
.table thead .sorting_asc:after,
.table thead .sorting_desc:after {
    transition:
        opacity var(--tw-duration-fast) var(--tw-ease),
        color var(--tw-duration-fast) var(--tw-ease);
}

.table-hover > tbody > tr > * {
    transition: background-color var(--tw-duration-fast) var(--tw-ease);
}

/* Tabla vacía: el mismo estado vacío del resto del sistema, no un renglón
   suelto en el centro. */
td.dataTables_empty,
td.dt-empty {
    padding: 28px 12px !important;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-md);
    text-align: center;
}

.dataTables_paginate .page-link {
    transition:
        background-color var(--tw-duration-fast) var(--tw-ease),
        color var(--tw-duration-fast) var(--tw-ease);
}

/* Conteo del pie: cuando cambia la cifra, parpadea una vez en lugar de saltar */
.dataTables_info .tw-count.is-bumped strong {
    animation: tw-count-in var(--tw-duration-base) var(--tw-ease);
}

@keyframes tw-count-in {
    from {
        opacity: 0.35;
        transform: translateY(2px);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

/* =====================================================================
   Movimiento reducido
   =====================================================================
   Quien lo pide en su sistema operativo ve exactamente la misma interfaz,
   quieta. Los esqueletos siguen ahí —son información, no adorno—, pero sin
   brillo que recorra la pantalla. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.001s !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001s !important;
    }

    .tw-skeleton,
    .tw-dt-skeleton-cell {
        animation: none !important;
        background-image: none;
    }

    .dataTables_processing::after,
    .dt-processing::after {
        animation: none !important;
        transform: scaleX(1);
        opacity: 0.6;
    }
}

/* --- Lista en espera (partial `_esqueleto.ejs` y `skeletonList()`) --- */
.tw-skeleton-list {
    display: flex;
    flex-direction: column;
}

.tw-skeleton-item {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 10px 0;
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-skeleton-item:last-child {
    border-bottom: 0;
}

.tw-skeleton-item-main {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 7px;
    min-width: 0;
}

/* La onda recorre la lista hacia abajo, igual que en la tabla */
.tw-skeleton-item:nth-child(2) .tw-skeleton {
    animation-delay: 0.06s;
}
.tw-skeleton-item:nth-child(3) .tw-skeleton {
    animation-delay: 0.12s;
}
.tw-skeleton-item:nth-child(4) .tw-skeleton {
    animation-delay: 0.18s;
}
.tw-skeleton-item:nth-child(n + 5) .tw-skeleton {
    animation-delay: 0.24s;
}

/* =====================================================================
   Tarjetas, contadores y gráficas
   ===================================================================== */

/* --- Gráfica que todavía no monta --- */
/* El contenedor está vacío hasta que ApexCharts dibuja dentro. `:empty` lo
   detecta sin una línea de JavaScript: mientras no haya gráfica, el hueco
   tiene la forma y el alto de la que viene, así que la tarjeta no cambia de
   tamaño al aparecer. */
.tw-chart:empty {
    height: 208px;
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-skeleton-base);
    background-image: linear-gradient(
        90deg,
        transparent 0%,
        var(--tw-skeleton-shine) 45%,
        var(--tw-skeleton-shine) 55%,
        transparent 100%
    );
    background-size: 220% 100%;
    background-repeat: no-repeat;
    animation: tw-shimmer 1.25s var(--tw-ease) infinite;
}

.tw-chart.tw-chart--donut:empty {
    height: 176px;
    border-radius: 50%;
    width: 176px;
    margin: 0 auto;
}

.tw-kpi-spark:empty {
    height: 34px;
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-skeleton-base);
    animation: tw-shimmer 1.25s var(--tw-ease) infinite;
    background-image: linear-gradient(
        90deg,
        transparent 0%,
        var(--tw-skeleton-shine) 45%,
        var(--tw-skeleton-shine) 55%,
        transparent 100%
    );
    background-size: 220% 100%;
    background-repeat: no-repeat;
}

/* Si ApexCharts no cargó, no hay gráfica que esperar: el hueco se queda quieto
   y en blanco en lugar de latir para siempre. */
.tw-no-charts .tw-chart:empty,
.tw-no-charts .tw-kpi-spark:empty {
    background: none;
    animation: none;
    height: 0;
}

/* La gráfica entra con la tarjeta, no antes */
.tw-chart .apexcharts-canvas {
    animation: tw-fade-in var(--tw-duration-base) var(--tw-ease) both;
}

/* --- Contadores --- */
/* Las cifras van en cifras tabulares: sin esto, cada dígito mide distinto y el
   número tiembla mientras corre. */
.tw-kpi-value,
.tw-agg-value,
.tw-dash-row-num,
.tw-count strong {
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum';
}

/* Cifra de una tarjeta de resumen que acaba de cambiar */
.tw-agg-value.is-bumped {
    animation: tw-bump 0.45s var(--tw-ease);
}

/* --- Leyenda --- */
.tw-legend-item {
    transition: opacity var(--tw-duration-fast) var(--tw-ease);
}

/* Cifra de una lista de resumen que acaba de cambiar */
.tw-dash-row-num.is-bumped {
    animation: tw-bump 0.45s var(--tw-ease);
}

/* =====================================================================
   Tablero todavía calculándose
   =====================================================================

   El servidor entrega Inicio sin esperar a sus agregaciones: si la primera
   tanda no llega a tiempo, manda el tablero vacío y el refresco del cliente lo
   rellena en cuanto está. Un tablero lleno de ceros durante ese segundo diría
   algo falso —«no hay órdenes abiertas»—, así que las cifras se enseñan como
   esqueleto: la forma está, el dato todavía no.
   ===================================================================== */
.tw-dash.is-loading .tw-kpi-value,
.tw-dash.is-loading .tw-agg-value,
.tw-dash.is-loading .tw-dash-row-num,
.tw-dash.is-loading .tw-queue-value,
.tw-dash.is-loading [data-kpi-note],
.tw-dash.is-loading [data-queue-note],
.tw-dash.is-loading [data-mod],
.tw-dash.is-loading #orders-count {
    color: transparent !important;
    border-radius: var(--tw-radius-xs);
    background-color: var(--tw-skeleton-base);
    background-image: linear-gradient(
        90deg,
        transparent 0%,
        var(--tw-skeleton-shine) 45%,
        var(--tw-skeleton-shine) 55%,
        transparent 100%
    );
    background-size: 220% 100%;
    background-repeat: no-repeat;
    animation: tw-shimmer 1.25s var(--tw-ease) infinite;
}

/* Un icono dentro de la nota se vería flotando sobre la barra */
.tw-dash.is-loading [data-kpi-note] i {
    visibility: hidden;
}

/* La barra hereda el ancho del texto que tapa, y un «0» mide siete pixeles:
   una mota que no se lee como carga. Las cifras chicas piden un mínimo. Son
   todas hijas de un contenedor flex, así que basta el `min-width`. */
.tw-dash.is-loading .tw-agg-value,
.tw-dash.is-loading .tw-dash-row-num,
.tw-dash.is-loading .tw-queue-value,
.tw-dash.is-loading [data-mod] {
    min-width: 3ch;
}

/* La bandeja mide su barra por el texto que tapa, y `.tw-queue-value` es un
   hijo de columna: sin esto la barra se estiraría a todo el ancho de la
   tarjeta y se leería como un bloque, no como una cifra. */
.tw-dash.is-loading .tw-queue-value,
.tw-dash.is-loading [data-queue-note] {
    align-self: flex-start;
}

/* Las listas ya no se atenúan: ahora traen su propia silueta (el parcial
   `_esqueleto-tablero.ejs`). Bajarles la opacidad apagaría el brillo justo
   en lo que se supone que está latiendo.

   La silueta se dibuja con las clases de la fila real, y ahí `.tw-dash-row-main`
   separa dos renglones de texto por su interlínea. Entre dos barras sin texto
   esa separación no existe, así que se repone a mano para que la fila mida lo
   mismo antes y después del dato. */
.tw-dash-row.is-esqueleto .tw-dash-row-main {
    gap: 7px;
}

/* En una columna de cifras la barra va pegada a la derecha, donde va el número:
   `.tw-skeleton` es un bloque y no lo alinea el `text-align` de la celda. */
.tw-dash-table .tw-num .tw-skeleton {
    margin-left: auto;
}

/* =====================================================================
   17. Selector de apariencia
   ---------------------------------------------------------------------
   Vive dentro del menú de la cuenta. Tres opciones y no un interruptor de
   dos: «Sistema» no es un estado intermedio entre claro y oscuro, es
   delegar la decisión, y un interruptor no sabe decir eso.
   ===================================================================== */

.tw-theme-switch {
    padding: 6px 12px 8px;
}

.tw-theme-switch__label {
    display: block;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-tertiary);
    margin-bottom: 6px;
}

.tw-theme-switch__options {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2px;
    padding: 2px;
    background-color: var(--tw-bg-tertiary);
    border-radius: var(--tw-radius-sm);
}

.tw-theme-option {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 5px 4px;
    border: 0;
    border-radius: var(--tw-radius-xs);
    background: transparent;
    color: var(--tw-font-secondary);
    font-family: var(--tw-font-family);
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-medium);
    line-height: 1;
    cursor: pointer;
    transition:
        background-color var(--tw-duration-fast) var(--tw-ease),
        color var(--tw-duration-fast) var(--tw-ease);
}

.tw-theme-option i {
    font-size: var(--tw-icon-size-sm);
    line-height: 1;
}

.tw-theme-option:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

/* La opción vigente se levanta como una pastilla sobre el carril. */
.tw-theme-option[aria-pressed='true'] {
    background-color: var(--tw-bg-primary);
    color: var(--tw-font-primary);
    box-shadow: var(--tw-shadow-light);
}

.tw-theme-option:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: -2px;
}

@media (prefers-reduced-motion: reduce) {
    .tw-theme-option {
        transition: none;
    }
}

/* ══════════════════════════════════════════════════════════════════════════
   Inventario · modos de vista, filtros y agrupado

   La pantalla responde dos preguntas distintas —«cómo lo miro» y «qué miro»— y
   antes las mezclaba en una fila de selectores que desbordaba. El modo es un
   carril de pastillas en la barra de vista; los filtros, una fila debajo con la
   misma altura y el mismo margen de 12 px que el resto de las barras.
   ══════════════════════════════════════════════════════════════════════════ */

/* Carril de modos: la misma pastilla del selector de tema, en horizontal. */
.tw-inv-modes {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
    overflow-x: auto;
    scrollbar-width: none;
}

.tw-inv-modes::-webkit-scrollbar {
    display: none;
}

.tw-inv-mode {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 26px;
    padding: 0 10px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: transparent;
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    transition:
        background-color var(--tw-transition),
        color var(--tw-transition);
}

.tw-inv-mode i {
    font-size: var(--tw-icon-size-sm);
    line-height: 1;
}

.tw-inv-mode:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.tw-inv-mode.is-active {
    background-color: var(--tw-bg-primary);
    color: var(--tw-font-primary);
    box-shadow: var(--tw-shadow-light);
}

.tw-inv-mode:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: -2px;
}

/* --- Barra de filtros de un listado ---
   Nació en el inventario y ahora la usa cualquier tabla —números de parte la
   estrenó— porque filtrar se aprende una vez: los desplegables en una fila
   bajo la barra de vista, y debajo las fichas de lo que está puesto.
   Fila de filtros: misma altura y sangría que la barra de vista de arriba. */
.tw-filters {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    min-height: 40px;
    padding: 6px 12px;
    border-bottom: 1px solid var(--tw-border-light);
    flex: 0 0 auto;
}

/* Anchos fijos: con `auto` cada selector medía según su opción más larga y la
   fila bailaba al filtrar. */
.tw-filters .form-select,
.tw-filters .ts-wrapper {
    width: 13rem;
    min-width: 13rem;
    flex: 0 0 auto;
}

.tw-filters .form-check {
    margin-inline-start: 4px;
}

/* Fichas de filtro activo, en la misma fila: una segunda línea sólo cuando hay
   filtros, y sin otro borde que parta la pantalla en tres. */
.tw-filter-chips {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    padding: 0 12px 8px;
    border-bottom: 1px solid var(--tw-border-light);
    flex: 0 0 auto;
}

.tw-filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 24px;
    padding: 0 6px 0 10px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-accent-secondary);
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-sm);
    line-height: 1;
    cursor: pointer;
    transition: border-color var(--tw-transition);
}

.tw-filter-chip i {
    font-size: var(--tw-icon-size-sm);
    color: var(--tw-font-light);
}

.tw-filter-chip:hover {
    border-color: var(--tw-accent);
}

.tw-filter-chip:hover i {
    color: var(--tw-font-primary);
}

/* ---------- Agrupado: fichas en rejilla, no renglones ---------- */

.tw-inv-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(19rem, 100%), 1fr));
    gap: 8px;
    padding: 12px;
    align-content: start;
    overflow-y: auto;
}

.tw-inv-group {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    cursor: pointer;
    text-align: start;
    transition:
        border-color var(--tw-transition),
        box-shadow var(--tw-transition);
}

.tw-inv-group:hover {
    border-color: var(--tw-accent);
    box-shadow: var(--tw-shadow-light);
}

.tw-inv-group:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 2px;
}

/* Lo que hay que reponer se ve antes de leerlo. */
.tw-inv-group.is-low {
    border-color: var(--tw-tag-red-fg);
}

.tw-inv-group-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
    min-height: 32px;
}

.tw-inv-group-title {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    line-height: 1.3;
}

.tw-inv-group-detail {
    margin-top: 2px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    line-height: 1.3;
}

.tw-inv-group-total {
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    line-height: 1;
    color: var(--tw-font-primary);
    font-variant-numeric: tabular-nums;
}

/* Barra de composición: consumible, esperando a Calidad, retenido y el resto. */
.tw-inv-bar-stack {
    display: flex;
    height: 4px;
    border-radius: var(--tw-radius-pill);
    overflow: hidden;
    background-color: var(--tw-bg-transparent-light);
}

.tw-inv-bar-stack span {
    display: block;
    height: 100%;
}

.tw-inv-seg--ok {
    background-color: var(--tw-tag-green-fg);
}

.tw-inv-seg--wait {
    background-color: var(--tw-tag-yellow-fg);
}

.tw-inv-seg--held {
    background-color: var(--tw-tag-orange-fg);
}

/* El retal, morado como su etiqueta de tipo: acero que existe y no es lámina entera. */
.tw-inv-seg--remnant {
    background-color: var(--tw-tag-purple-fg);
}

.tw-inv-seg--rest {
    background-color: var(--tw-border-light);
}

.tw-inv-group-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 12px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    line-height: 1.4;
}

.tw-inv-group-legend b {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
}

/* La invitación a entrar aparece al pasar por encima y no ocupa sitio antes:
   reservarle una línea a un texto que casi nunca se lee alarga cada ficha. */
.tw-inv-group-go {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-accent);
    opacity: 0;
    transition: opacity var(--tw-transition);
}

.tw-inv-group-go i {
    font-size: var(--tw-icon-size-sm);
}

.tw-inv-group:hover .tw-inv-group-go,
.tw-inv-group:focus-visible .tw-inv-group-go {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .tw-inv-mode,
    .tw-inv-group,
    .tw-filter-chip,
    .tw-inv-group-go {
        transition: none;
    }
}

/* Opción de desplegable con un dato secundario a la derecha —el conteo de una
   colada, por ejemplo—. Va en la lista y nunca en lo ya elegido: como texto del
   valor seleccionado partiría el control en dos renglones. */
.tw-select-option {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.tw-select-option-meta {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    white-space: nowrap;
}

/* ══════════════════════════════════════════════════════════════════════════
   Plano de planta y torre de control

   El plano es un dibujo de arquitecto y no un componente de interfaz: el fondo
   se pinta en un `canvas` y las zonas van en una capa SVG encima. Todo lo de
   aquí viste esa capa, así que los colores salen de los tokens y no de valores
   sueltos —el plano se mira igual de noche que de día, y en el tema oscuro un
   relleno fijo se convierte en una mancha.
   ══════════════════════════════════════════════════════════════════════════ */

.plano-viewport {
    position: relative;
    overflow: hidden;
    flex: 1 1 auto;
    min-height: 0;
    background: var(--tw-bg-secondary);
    background-image:
        linear-gradient(var(--tw-border-light) 1px, transparent 1px),
        linear-gradient(90deg, var(--tw-border-light) 1px, transparent 1px);
    background-size: 24px 24px;
    cursor: grab;
    touch-action: none;
    user-select: none;
}

/* Colocando una terminal: el cursor dice qué hace el próximo clic. Sin esto,
   la mano de arrastrar sigue puesta y el modo activo solo se nota mirando la
   barra. */
.plano-viewport.colocando,
.plano-viewport.colocando .plano-capa {
    cursor: crosshair;
}

.plano-viewport.is-panning {
    cursor: grabbing;
}

/* La nave mientras baja su dibujo.

   Ver «Esqueletos de carga»: se enseña la forma, no un hueco ni un texto de
   espera. Aquí la forma es el rectángulo del plano, centrado y algo apagado
   —tiene el tamaño de la pantalla y a plena intensidad sería lo único que se
   ve—. Sordo al ratón: debajo ya hay zonas pintadas y pulsables desde antes de
   que llegue el PDF.

   Lleva borde porque el relleno del esqueleto está pensado para tarjetas
   blancas y el visor no lo es: sobre `--tw-bg-secondary` la diferencia es de
   ocho niveles de gris y el bloque se desvanece. El borde dibuja el canto, que
   es lo que se reconoce. */
.plano-cargando {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 4%;
    pointer-events: none;
}

.plano-cargando .tw-skeleton {
    inline-size: min(900px, 100%);
    block-size: min(600px, 100%);
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-md);
}

/* No hay hoja, ni en claro ni en oscuro: queda el trazo flotando sobre el fondo
   de la aplicación.

   La hoja estaba imitando el papel que la gente tiene en la mano, y de cerca es
   bonito; de lejos es un rectángulo blanco con sombra que parte la pantalla en
   dos —lo de dentro es «el plano» y lo de fuera es «la aplicación»— cuando lo
   que se está mirando es una sola cosa. Sin ella, la cuadrícula del visor cruza
   el dibujo entero y el plano se lee como lo que es: el fondo de la nave, no un
   documento pegado encima.

   El relleno de verdad se quita al pintar el PDF —ver `#repintarFondo`—: pdf.js
   rellena el lienzo de blanco antes de dibujar, y sin aquello esto no serviría
   de nada. */
.plano-lienzo {
    position: absolute;
    top: 0;
    left: 0;
    transform-origin: 0 0;
    background: transparent;
    box-shadow: none;
}

.plano-fondo,
.plano-capa {
    position: absolute;
    top: 0;
    left: 0;
    display: block;
}

.plano-capa {
    width: 100%;
    height: 100%;
    overflow: visible;
}

/* El plano viene como lo dibujó el arquitecto: tinta oscura pensada para papel.
   Sin hoja debajo hay que invertirlo en el tema oscuro, o el trazo negro se
   pierde contra un fondo negro.
   
   `invert(1)` solo bastaría para un plano en blanco y negro, pero este lleva
   anotaciones de color —el azul del acomodo de placas, el rojo del área de
   limpieza, el amarillo de las cotas— y la inversión sola las manda a su color
   complementario: el rojo saldría cian. `hue-rotate(180deg)` deshace ese giro y
   las devuelve a su tono, así que lo único que cambia de verdad es la claridad.
   
   El filtro va sobre el `canvas` y no sobre el lienzo entero a propósito: el
   lienzo contiene también la capa SVG, e invertirla volvería las zonas y las
   terminales de su color complementario. */
/* Y un punto por debajo del todo.

   El dibujo del arquitecto es **contexto**: sirve para saber dónde cae cada
   cosa, no para leerlo. Encima van los indicadores —anillos, cifras, pastillas,
   flechas— y a plena opacidad compiten con ellos: sobre una zona con muchas
   líneas, una pastilla de contenedor se pierde entre cotas y rótulos del plano.

   Bajarlo un poco lo deja perfectamente reconocible y manda la lectura a lo que
   se mueve. No más: por debajo de esto el plano empieza a desaparecer y con él
   la referencia de dónde está uno mirando. */
.plano-fondo {
    opacity: 0.62;
}

[data-bs-theme='dark'] .plano-fondo {
    filter: invert(1) hue-rotate(180deg);
    /* En oscuro se baja algo menos: el trazo ya llega invertido —blanco fino
       sobre negro— y pierde presencia mucho antes que la tinta sobre papel. */
    opacity: 0.7;
}

/* ── Las zonas ───────────────────────────────────────────────────────────── */

.plano-zona {
    cursor: pointer;
}

/* Un área es contexto, no el asunto de la pantalla, pero contexto que hay que
   poder ver: con el contorno a media opacidad y un píxel y cuarto se perdía
   entre las líneas del dibujo del arquitecto, y había que buscar los bordes de
   la zona para saber qué se estaba pulsando. El relleno sigue apareciendo al
   pasar el cursor, que es cuando se pregunta por esa área concreta. */
.plano-zona > .plano-forma {
    fill: transparent;
    stroke: var(--tw-accent);
    stroke-width: 2;
    stroke-opacity: 0.85;
    vector-effect: non-scaling-stroke;
    transition:
        fill var(--tw-transition),
        stroke-opacity var(--tw-transition);
}

.plano-zona:hover > .plano-forma {
    fill: var(--tw-accent-tertiary);
    stroke-opacity: 1;
    stroke-width: 2;
}

.plano-zona.is-selected > .plano-forma {
    stroke-width: 3.5;
    stroke-dasharray: none;
}

/* Una zona sin ubicación es un cartel del plano, no un punto de captura: se
   dibuja con trazo discontinuo para que se vea de un golpe lo que falta por
   enlazar sin tener que abrir la lista. */
.plano-zona.is-unlinked > .plano-forma {
    fill: transparent;
    stroke: var(--tw-font-light);
    stroke-dasharray: 6 5;
}

/* Reconocida y sin confirmar: el constructor la propuso, nadie la ha mirado. */
.plano-zona.is-pending > .plano-forma {
    stroke: var(--tw-tag-orange-fg);
    stroke-dasharray: 3 4;
}

/* El rótulo es lo que se lee para saber dónde se está mirando, así que va con
   el peso de un rótulo y no con el de un pie de figura: a 10 px y en gris
   secundario había que acercar el plano para leer el nombre de una zona. */
/* El cerco del rótulo es del color de lo que hay detrás, y detrás está el fondo
   de la aplicación: desde que el plano no trae hoja, un cerco blanco fijo dejaba
   un manchón alrededor de cada nombre. Es el mismo valor que ya usaba el tema
   oscuro por la misma razón. */
/* El rótulo va dentro de una tarjeta y ya no lleva cerco.

   El cerco —`paint-order: stroke` del color del fondo— se sostiene sobre un
   fondo liso, no sobre el plano de un arquitecto: encima de las líneas del
   dibujo dejaba un manchón con la forma de las letras. La tarjeta resuelve la
   legibilidad y de paso da al mapa una capa de profundidad que no tenía: no es
   texto flotando sobre un dibujo, son fichas encima de un plano. */
.plano-rotulo-chip {
    fill: var(--tw-bg-primary);
    fill-opacity: 0.9;
    stroke: var(--tw-border-light);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.plano-zona-nombre {
    fill: var(--tw-font-primary);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-semibold);
    text-anchor: middle;
    dominant-baseline: middle;
    pointer-events: none;
}

.plano-zona-dato {
    fill: var(--tw-font-secondary);
    font-size: 11px;
    font-weight: 500;
    text-anchor: middle;
    dominant-baseline: middle;
    paint-order: stroke;
    stroke: var(--tw-bg-secondary);
    stroke-width: 3.5px;
    stroke-linejoin: round;
    pointer-events: none;
}

/* El dato suelto —el que no va en tarjeta— sí conserva su cerco, y en oscuro
   tiene que ser del color de lo que hay detrás: el plano es trazo claro sobre
   el fondo de la aplicación. El rótulo ya no lo necesita: tiene tarjeta. */
[data-bs-theme='dark'] .plano-zona-dato {
    fill: var(--tw-font-primary);
    stroke: var(--tw-bg-secondary);
}

/* ── Foco: señalar una marca apaga las demás ─────────────────────────────────

   Un plano de nave entera es una pantalla llena de cosas a la vez, y la vista
   no sabe por dónde empezar. Al posarse sobre una zona, todo lo demás baja de
   opacidad y de saturación: la que se mira se queda sola sin haber tapado nada
   —sigue todo ahí— y la relación entre esa marca y sus flechas aparece de
   golpe. Es la misma idea del hover de una fila en las tablas del sistema,
   llevada a un dibujo.

   `:has()` y no una clase puesta por JavaScript: el estado es «hay algo bajo el
   ratón», que es exactamente lo que un selector sabe contestar, y hacerlo con
   eventos obliga a repintar en cada movimiento del cursor sobre un SVG de mil
   nodos.

   La transición sube más rápido de lo que baja —120 ms al apagar, 90 al
   volver— para que barrer el plano con el ratón no deje un rastro de zonas
   despertándose. */
.plano-lienzo svg .plano-zona,
.plano-lienzo svg .plano-flujo {
    transition:
        opacity 90ms ease-out,
        filter 90ms ease-out;
}

.plano-lienzo svg:has(.plano-zona:hover) .plano-zona:not(:hover),
.plano-lienzo svg:has(.plano-zona:hover) .plano-flujo {
    opacity: 0.35;
    filter: saturate(0.35);
    transition-duration: 120ms;
}

/* Menos la flecha del contenedor abierto: si algo está seleccionado, su camino
   es justo lo que no se puede apagar. */
.plano-lienzo svg:has(.plano-zona:hover) .plano-flujo.is-selected {
    opacity: 1;
    filter: none;
}

@media (prefers-reduced-motion: reduce) {
    .plano-lienzo svg .plano-zona,
    .plano-lienzo svg .plano-flujo {
        transition: none;
    }
}

/* ── Estado en la torre ──────────────────────────────────────────────────── */

.plano-zona.estado-vacia > .plano-forma {
    fill: color-mix(in srgb, var(--tw-font-light) 14%, transparent);
    stroke: var(--tw-font-light);
}

.plano-zona.estado-con-material > .plano-forma {
    fill: color-mix(in srgb, var(--tw-accent) 20%, transparent);
    stroke: var(--tw-accent);
}

.plano-zona.estado-alerta > .plano-forma {
    fill: color-mix(in srgb, var(--tw-solid-red) 22%, transparent);
    stroke: var(--tw-solid-red);
    stroke-width: 3;
}

.plano-zona.estado-aviso > .plano-forma {
    fill: color-mix(in srgb, var(--tw-tag-orange-fg) 26%, transparent);
    stroke: var(--tw-tag-orange-fg);
    stroke-width: 3;
}

/* La zona a ciegas no se pinta de rojo: no hay una incidencia, hay ausencia de
   lecturas. El rayado la distingue de un problema real de material. */
.plano-zona.estado-ciega > .plano-forma {
    fill: url(#plano-rayado);
    stroke: var(--tw-font-secondary);
    stroke-dasharray: 6 4;
}

@keyframes plano-latido {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0.45;
    }
}

.plano-pulso {
    animation: plano-latido 1.8s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .plano-pulso {
        animation: none;
    }
}

/* ── Lo que hay en cada sitio, dibujado ──────────────────────────────────────

   La torre nació como un mapa de cifras: se pulsaba una zona y el panel de al
   lado contaba lo que había dentro. Eso obliga a preguntar sitio por sitio, que
   es justo lo que no se puede hacer desde el pasillo. Lo de aquí abajo es la
   misma información puesta **encima del dibujo**: un anillo que reparte lo que
   hay en un punto entre lo que va en tiempo, lo parado y lo retrasado; una
   cifra que se lee a la escala de la nave entera; y las flechas de a dónde va
   cada cosa.

   Todo lo que se lee cuelga de un grupo `.plano-escalado`, que conserva su
   tamaño en pantalla cueste lo que cueste el zoom: sin eso, a la escala a la
   que cabe la nave los rótulos miden tres píxeles y el plano vuelve a ser
   cuatro puntos grises. */

/* El anillo del sitio · su vuelta repartida entre los tres estados. Hueco y no
   macizo porque debajo está la marca del puesto, que es la que dice si hay
   alguien firmado: un disco relleno la taparía. */
.plano-anillo {
    fill: none;
    stroke-width: 4;
    stroke-linecap: butt;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.plano-anillo.es-normal {
    stroke: var(--tw-accent);
    stroke-opacity: 0.75;
}

.plano-anillo.es-parado {
    stroke: var(--tw-tag-orange-fg);
}

.plano-anillo.es-tarde {
    stroke: var(--tw-solid-red);
}

/* Y late lo que pide atención. Es el arco el que late y no el anillo entero: lo
   que hay que mirar son esos dos contenedores, no el sitio.

   Dos ritmos, y la diferencia es lo que dice cuál es cuál sin leer un color:
   lo **retrasado** late rápido y llega a apagarse casi del todo —pasó un plazo
   comprometido con un cliente—, y lo **parado** respira, lento y sin
   desaparecer, porque ocho horas quietas es algo que alguien tiene que ir a ver
   y todavía no es una promesa rota.

   Lo parado late **también**, y no sólo lo retrasado, porque una planta que no
   configuró plazos por proceso no tiene nada retrasado nunca: sin esto el plano
   se queda completamente quieto justo en las plantas donde más falta hace que
   algo llame la atención. */
@keyframes plano-anillo-latido {
    0%,
    100% {
        stroke-opacity: 1;
    }
    50% {
        stroke-opacity: 0.3;
    }
}

@keyframes plano-anillo-respira {
    0%,
    100% {
        stroke-opacity: 1;
    }
    50% {
        stroke-opacity: 0.55;
    }
}

.plano-anillo.es-tarde {
    animation: plano-anillo-latido 1.6s ease-in-out infinite;
}

.plano-anillo.es-parado {
    animation: plano-anillo-respira 2.8s ease-in-out infinite;
}

/* ── La onda de un puesto trabajando ─────────────────────────────────────────

   Lo único de esta pantalla que se mueve sin que haya pasado nada malo. Una
   torre de control donde lo único que se anima son las alarmas enseña a mirarla
   con miedo; aquí late lo que está produciendo, que es lo que se quiere ver
   latir desde el pasillo. */

@keyframes plano-onda-crece {
    from {
        transform: scale(1);
        opacity: 0.55;
    }
    to {
        transform: scale(3.2);
        opacity: 0;
    }
}

.plano-onda {
    fill: var(--tw-solid-green);
    transform-box: fill-box;
    transform-origin: center;
    animation: plano-onda-crece 2.4s ease-out infinite;
    pointer-events: none;
}

/* ── La cifra de contenedores del sitio ──────────────────────────────────────

   La que contesta a la escala de la nave entera, donde no cabe ni una pastilla.
   Del color del peor de los que hay dentro: una cifra neutra junto a un anillo
   medio rojo obligaría a mirar dos veces para saber si eso de ahí urge. */

.plano-cuenta {
    stroke: var(--tw-bg-primary);
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.plano-cuenta.es-normal {
    fill: var(--tw-accent);
}

.plano-cuenta.es-parado {
    fill: var(--tw-tag-orange-fg);
}

.plano-cuenta.es-tarde {
    fill: var(--tw-solid-red);
}

/* Y la cifra late con el mismo par de ritmos que el anillo.

   Es **donde tiene que estar el latido** y estuvo en el sitio equivocado: a la
   escala a la que se mira la nave entera —que es como está esta pantalla casi
   siempre— las pastillas no se dibujan y el anillo mide seis píxeles. Lo único
   que se ve de lejos es esta insignia, así que era la única que no podía
   quedarse quieta. */
@keyframes plano-cuenta-latido {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0.35;
    }
}

@keyframes plano-cuenta-respira {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0.6;
    }
}

.plano-cuenta.es-tarde,
.plano-cuenta.es-tarde + .plano-cuenta-cifra {
    animation: plano-cuenta-latido 1.6s ease-in-out infinite;
}

.plano-cuenta.es-parado,
.plano-cuenta.es-parado + .plano-cuenta-cifra {
    animation: plano-cuenta-respira 2.8s ease-in-out infinite;
}

.plano-cuenta-cifra {
    fill: #ffffff;
    font-size: 13px;
    font-weight: 700;
    text-anchor: middle;
    dominant-baseline: middle;
    pointer-events: none;
}

/* ── Las flechas del flujo ───────────────────────────────────────────────────

   Lo que convierte el plano de un mapa de inventario en un mapa de movimiento:
   un punto con seis contenedores dice que hay seis; la flecha que sale de él
   hacia Galvanizado dice que esos seis van a caer allí, y eso es lo que deja
   ver el cuello de botella antes de que se forme.

   El trazo marcha hacia el destino. No es adorno: una línea quieta entre dos
   puntos se lee como una tubería —algo que está ahí— y lo que se quiere contar
   es que hay material yendo por ella ahora mismo. */

@keyframes plano-flujo-marcha {
    to {
        stroke-dashoffset: -18;
    }
}

/* La capa entera es sorda: una curva cruza medio plano y se comería los clics
   de todo lo que pasa por debajo. Lo único que oye es su cifra, que es lo que
   se pulsa para saber qué significa. */
.plano-flujos {
    pointer-events: none;
}

/* Y lo que va dentro también oye, **una por una**.

   `pointer-events` no se hereda como se espera: las insignias declaran `none`
   en su propia regla —se hicieron transparentes al clic a propósito, para no
   robárselo a la zona que hay debajo— y esa regla gana sobre la del envoltorio.
   El resultado era un grupo que oía y un contenido que no: el cursor cambiaba a
   mano y el clic se colaba hasta el dibujo.

   Van nombradas y no con `*` porque el envoltorio lleva también el `<title>` de
   la pista, y ése no tiene por qué recibir nada. */
.plano-pulsable,
.plano-pulsable .plano-cuenta,
.plano-pulsable .plano-cuenta-cifra,
.plano-pulsable .plano-insignia,
.plano-pulsable .plano-insignia-cifra,
.plano-pulsable .plano-punta-pastilla,
.plano-pulsable .plano-punta-texto,
.plano-pulsable .plano-flujo-cuenta,
.plano-pulsable .plano-flujo-cifra {
    pointer-events: auto;
    cursor: pointer;
}

.plano-pulsable:hover .plano-flujo-cuenta {
    fill: var(--tw-accent-tertiary);
    stroke-width: 2.5;
}

/* Las insignias del sitio viven dentro de un grupo de zona, que ya oye el ratón;
   lo único que les falta es decir que se pueden pulsar. */
.plano-pulsable:hover .plano-insignia,
.plano-pulsable:hover .plano-cuenta {
    stroke: var(--tw-accent);
    stroke-width: 3;
}

.plano-flujo-linea {
    fill: none;
    stroke: var(--tw-accent);
    stroke-opacity: 0.55;
    stroke-width: 2;
    stroke-dasharray: 10 8;
    vector-effect: non-scaling-stroke;
    animation: plano-flujo-marcha 1.1s linear infinite;
}

.plano-flujo.es-tarde .plano-flujo-linea {
    stroke: var(--tw-solid-red);
    stroke-opacity: 0.85;
}

/* El primer eslabón: acero que Almacén todavía no ha surtido, apuntando a la
   máquina que lo espera. Va en otro color porque no es lo mismo que las demás
   —ahí se mueven contenedores y aquí todavía no hay ninguno, hay un programa—,
   y el color es el que ya usa la columna «Almacén · por surtir» del tablero:
   dos pantallas que hablan de lo mismo tienen que hacerlo igual. */
.plano-flujo.es-surtido .plano-flujo-linea {
    stroke: var(--tw-tag-purple-fg, var(--tw-accent));
    stroke-opacity: 0.75;
}

.plano-flujo.es-surtido .plano-flujo-cuenta {
    stroke: var(--tw-tag-purple-fg, var(--tw-accent));
}

/* El flujo convergente: cajas de componentes que entran al puesto que arma y
   la caja de ensambles que sale. Turquesa, que es el color de la salida de la
   nave: lo que converge aquí es lo que va a salir como una sola pieza. */
.plano-flujo.es-ensamble .plano-flujo-linea {
    stroke: var(--tw-tag-turquoise-fg, var(--tw-accent));
    stroke-opacity: 0.8;
}

.plano-flujo.es-ensamble .plano-flujo-cuenta {
    stroke: var(--tw-tag-turquoise-fg, var(--tw-accent));
}

/* El tramo del contenedor abierto se enciende entero: pulsar una pastilla y ver
   iluminarse su camino es la respuesta más corta que hay a «¿y esto a dónde
   va?». */
.plano-flujo.is-selected .plano-flujo-linea {
    stroke: var(--tw-accent);
    stroke-opacity: 1;
    stroke-width: 3.5;
}

/* Las puntas viven en `defs` y no cuelgan de su flecha, así que se estilan por
   su propia clase: un selector escrito desde el grupo de la línea no las
   alcanza. */
.plano-punta {
    fill: var(--tw-accent);
    fill-opacity: 0.75;
}

.plano-punta.es-tarde {
    fill: var(--tw-solid-red);
    fill-opacity: 1;
}

.plano-punta.es-surtido {
    fill: var(--tw-tag-purple-fg, var(--tw-accent));
    fill-opacity: 1;
}

.plano-punta.es-ensamble {
    fill: var(--tw-tag-turquoise-fg, var(--tw-accent));
    fill-opacity: 1;
}

.plano-flujo-cuenta {
    fill: var(--tw-bg-primary);
    stroke: var(--tw-accent);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
}

.plano-flujo.es-tarde .plano-flujo-cuenta {
    stroke: var(--tw-solid-red);
}

.plano-flujo-cifra {
    fill: var(--tw-font-primary);
    font-size: 11px;
    font-weight: 600;
    text-anchor: middle;
    dominant-baseline: middle;
}

/* ── Los dos extremos del recorrido ──────────────────────────────────────────

   Almacén y Empaque no son procesos y lo que hay parado en ellos no son
   contenedores: son programas esperando acero y tarimas llenándose. Por eso la
   marca lleva la palabra y no sólo la cifra —un «3» suelto en Almacén se leería
   como tres contenedores— y por eso no entra en el anillo del sitio.

   Hueca y con el contorno del color: los dos extremos son contexto —de dónde
   sale el material y en qué acaba— y no un estado que haya que atender. Rellenas
   competirían con los sitios que sí piden algo. */

/* ── Lo que sale de la nave ──────────────────────────────────────────────────

   Un contenedor cuyo paso siguiente no está dibujado —PINTURA, que la hace un
   proveedor— no dejaba rastro, y la marca de Doblez enseñaba cuatro
   contenedores con tres salidas. La resta se hace mirando, así que ahí hay que
   contestarla.

   **Etiqueta y no flecha.** Se probó con una línea saliendo del plano y decía
   lo que no es: una flecha desde una marca se lee como un traslado a otro
   sitio de la nave, y aquí el material se va de la planta. Colgada del punto,
   con la puerta dibujada al principio, es una propiedad del sitio —«de aquí
   salen 1 a pintura»— y no un camino.

   Esquina viva y no redonda del todo: las pastillas de los extremos son
   píldoras porque cuentan lo que espera; ésta es una etiqueta de envío. */
.plano-salida-pastilla {
    fill: var(--tw-bg-primary);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.plano-salida-pastilla.es-externo {
    stroke: var(--tw-tag-orange-fg);
}

/* El proceso de la casa que nadie dibujó no es una salida de la planta: el
   material sigue dentro y lo que falta es el punto. Se dice en gris para que
   no compita con lo que sí se va. */
.plano-salida-pastilla.es-sin-punto {
    stroke: var(--tw-font-light);
    stroke-dasharray: 3 2;
}

.plano-salida-icono {
    fill: none;
    stroke: var(--tw-tag-orange-fg);
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.es-sin-punto ~ .plano-salida-icono {
    stroke: var(--tw-font-light);
}

.plano-salida-texto {
    fill: var(--tw-font-primary);
    font-size: 10px;
    font-weight: 600;
    text-anchor: middle;
    dominant-baseline: middle;
    pointer-events: none;
}

.plano-pulsable:hover .plano-salida-pastilla {
    fill: var(--tw-accent-tertiary);
}

.plano-pulsable .plano-salida-pastilla,
.plano-pulsable .plano-salida-texto,
.plano-pulsable .plano-salida-icono {
    pointer-events: auto;
    cursor: pointer;
}

.plano-punta-pastilla {
    fill: var(--tw-bg-primary);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.plano-punta-pastilla.es-entrada {
    stroke: var(--tw-tag-purple-fg, var(--tw-accent));
}

.plano-punta-pastilla.es-salida {
    stroke: var(--tw-tag-turquoise-fg, var(--tw-accent));
}

/* El acero ya surtido, que cuelga de la máquina y no de un extremo: azul, para
   que no se confunda con el morado de lo que Almacén todavía debe. */
.plano-punta-pastilla.es-laminas {
    stroke: var(--tw-tag-blue-fg, var(--tw-accent));
}

/* Los armados sin caja, colgados del puesto que arma. */
.plano-punta-pastilla.es-ensamble {
    stroke: var(--tw-tag-turquoise-fg, var(--tw-accent));
}

/* Y el programa preparado, que todavía no es de Almacén: gris y punteado, la
   misma gramática que el proceso que nadie dibujó. Es trabajo que existe y del
   que el piso todavía no sabe nada, y el trazo continuo lo pondría a competir
   con lo que sí está esperando acero justo encima. */
.plano-punta-pastilla.es-preparado {
    stroke: var(--tw-font-light);
    stroke-dasharray: 3 2;
}

.plano-punta-texto {
    fill: var(--tw-font-primary);
    font-size: 10px;
    font-weight: 600;
    text-anchor: middle;
    dominant-baseline: middle;
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .plano-onda,
    .plano-anillo.es-tarde,
    .plano-anillo.es-parado,
    .plano-cuenta.es-tarde,
    .plano-cuenta.es-tarde + .plano-cuenta-cifra,
    .plano-cuenta.es-parado,
    .plano-cuenta.es-parado + .plano-cuenta-cifra,
    .plano-flujo-linea {
        animation: none;
    }
}

/* ── Los puntos del plano: máquina, proceso y puesto ─────────────────────────

   Tres cosas distintas que se dibujaban iguales, con el mismo par de estados
   —«turno abierto» y «sin turno abierto»— que sobre dos de las tres no
   significa nada: nadie firma en un láser, y un proceso no es un sitio
   atendido sino un tramo del recorrido.

   Lo que las separa es **la forma**, no el color: cuadrado el equipo anclado al
   suelo, círculo el puesto de captura, rombo el sitio de trabajo. El color está
   ocupado diciendo el estado, y repartirlo entre dos significados a la vez es
   como se consigue que no se lea ninguno.

   Un punto se lee distinto de un área a propósito: relleno sólido y contorno
   claro, para que destaque sobre el dibujo sin competir con las zonas de
   material, que son las que llevan color de estado. */
.plano-zona.es-marca > .plano-forma {
    fill: var(--tw-accent);
    stroke: var(--tw-bg-primary);
    stroke-width: 2.5;
    stroke-dasharray: none;
}

.plano-zona.es-marca:hover > .plano-forma {
    fill: var(--tw-accent-hover);
}

/* Colocada y sin decir a qué corresponde: una marca anónima que hay que
   terminar de enlazar en el plano de planta. */
.plano-zona.es-marca.is-unlinked > .plano-forma {
    fill: var(--tw-bg-primary);
    stroke: var(--tw-font-light);
    stroke-dasharray: 4 3;
}

.plano-zona.es-marca.is-selected > .plano-forma {
    stroke: var(--tw-accent);
    stroke-width: 3;
}

/* ── El puesto · si hay alguien ─────────────────────────────────────────── */

/* Encendido cuando hay alguien firmado, apagado cuando no. La diferencia no es
   cosmética: dice si alguien responde por lo que se captura ahí.

   Se dice «firmado» y no «turno abierto» porque esta planta ya no maneja
   turnos: se entra identificándose con el gafete y se sale igual, y llamarlo
   turno mandaba a buscar un horario que no existe. */
.plano-zona.es-terminal.hay-firma > .plano-forma {
    fill: var(--tw-solid-green);
}

.plano-zona.es-terminal.sin-firma > .plano-forma {
    fill: var(--tw-font-light);
}

/* Un puesto cuyas propias medidas pasaron del umbral: Almacén con la cola de
   surtido acumulada, Corte con procesos retrasados. No es lo mismo que una zona
   en alerta —ahí falla el material; aquí va ahogado el puesto— y por eso se
   pinta en el ámbar de aviso y no en rojo. */
.plano-zona.es-terminal.puesto-ahogado > .plano-forma {
    fill: var(--tw-tag-orange-fg);
}

/* ── La máquina · si está trabajando ────────────────────────────────────── */

/* El verde de una máquina significa otra cosa que el de un puesto —ahí hay
   alguien firmado, aquí hay una placa dentro del equipo— y aun así es el mismo
   verde a propósito: los dos quieren decir «esto está en marcha», que es lo que
   se lee desde el pasillo. Lo que distingue una cosa de la otra es la forma,
   que ya está haciendo ese trabajo. */
.plano-zona.es-maquina.esta-corriendo > .plano-forma {
    fill: var(--tw-solid-green);
}

/* Y parada no es apagada: la máquina sigue ahí y sigue siendo a donde va el
   material. Pero tampoco es azul.

   El azul es el color del movimiento en esta pantalla —el flujo, lo que tiene
   material— y usarlo también para el reposo lo gastaba: cuatro láseres quietos
   eran cuatro puntos azules saturados compitiendo por la mirada con las flechas
   que sí dicen algo. En reposo la marca se queda en el gris de la tinta suave:
   presente, legible, y sin pedir nada. Lo mismo que ya hacía el puesto sin
   firma, que es exactamente el mismo estado contado de otra forma. */
.plano-zona.es-maquina.sin-corrida > .plano-forma {
    fill: var(--tw-font-light);
}

/* ── El proceso · ni corre ni se firma ──────────────────────────────────── */

/* Un rombo hueco. Es el único de los tres que no es un objeto de la nave sino un
   tramo del recorrido: no hay nada que encender ahí, y el relleno macizo lo
   ponía al mismo nivel que un equipo trabajando. */
/* Y el rombo del proceso, por lo mismo: el contorno azul sobre un punto que no
   tiene nada dentro prometía actividad donde no la hay. El azul se lo gana
   teniendo material —lo pone `estado-con-material`—, no por existir. */
.plano-zona.es-proceso > .plano-forma {
    fill: var(--tw-bg-primary);
    stroke: var(--tw-font-light);
    stroke-width: 2.5;
}

.plano-zona.es-proceso.con-material > .plano-forma {
    stroke: var(--tw-accent);
    stroke-width: 3;
}

.plano-zona.es-proceso:hover > .plano-forma {
    fill: var(--tw-accent-tertiary);
}

/* El rótulo de un punto va un escalón por debajo del de un área: son muchos
   más y tienen que dejar respirar el dibujo. */
.plano-zona.es-marca .plano-zona-nombre {
    font-size: var(--tw-font-size-xs);
}

/* ── La cifra del puesto, sobre su punto ─────────────────────────────────── */

/* Una insignia y no un renglón de texto: en un plano de nave entera, diez
   píxeles de texto junto a una marca se pierden entre las líneas del dibujo, y
   una torre de control se mira de pie y desde lejos. */
.plano-insignia {
    fill: var(--tw-font-primary);
    stroke: var(--tw-bg-primary);
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.plano-zona.puesto-ahogado .plano-insignia {
    fill: var(--tw-tag-orange-fg);
}

.plano-zona.sin-firma .plano-insignia {
    fill: var(--tw-font-secondary);
}

.plano-insignia-cifra {
    fill: var(--tw-bg-primary);
    font-size: 13px;
    font-weight: 600;
    text-anchor: middle;
    dominant-baseline: middle;
    pointer-events: none;
}

/* ── El globo del puesto ─────────────────────────────────────────────────── */

/* Se eleva con la sombra, no con un marco. Un contorno alrededor de algo que ya
   flota sobre el plano dibuja dos veces el mismo límite. */
/* Más ancho de lo que era.

   A 236 px cabía una cifra con su rótulo y poco más: en cuanto la medida
   empezó a decir de qué proceso es y en qué estado está, cada renglón se partía
   en tres líneas y el globo se leía como una columna de fragmentos. Con 320 el
   rótulo cabe entero al lado de su número y el detalle ocupa una línea, que es
   como se lee de un vistazo.

   `max-width` con `vw` para el teléfono: el globo flota sobre el plano y a 320
   fijos se salía de una pantalla de 360. */
.plano-globo {
    position: absolute;
    z-index: 5;
    width: 320px;
    max-width: calc(100vw - 32px);
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
    border-radius: var(--tw-radius-md);
    background: var(--tw-bg-primary);
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.16),
        0 12px 32px rgba(0, 0, 0, 0.34);
    font-size: var(--tw-font-size-sm);
}

.plano-globo-cabecera {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

/* Botón de cerrar: cuadrado, sin marco y con el icono centrado de verdad.
   Un `padding` asimétrico deja la equis descuadrada dentro de su área de clic,
   que se nota justo en el elemento más pequeño de la pantalla. */
.plano-globo-cabecera .btn {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    padding: 0;
    display: inline-grid;
    place-items: center;
    border: 0;
    background: none;
    color: var(--tw-font-tertiary);
}

.plano-globo-cabecera .btn:hover {
    background: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.plano-globo-cabecera .btn .ti {
    margin: 0;
}

.plano-globo-cifras {
    display: flex;
    flex-direction: column;
}

/* La cifra manda a la izquierda y todo lo suyo se alinea debajo del rótulo, no
   debajo del número: así la columna de números queda limpia y se recorre de un
   golpe, y el detalle no empuja al siguiente renglón. */
.plano-globo-cifra {
    display: grid;
    grid-template-columns: 2.4rem 1fr;
    align-items: baseline;
    gap: 2px 10px;
    padding: 6px 0;
}

.plano-globo-cifra + .plano-globo-cifra {
    border-top: 1px solid var(--tw-border-light);
}

.plano-globo-valor {
    grid-row: 1 / span 3;
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.plano-globo-cifra.es-alerta .plano-globo-valor {
    color: var(--tw-solid-red);
}

/* El reparto por proceso, debajo de su cifra.

   Un puesto que lleva Doblez y Galvanizado enseñaba «2 esperando» y no decía a
   cuál de los dos ir. Va en pequeño y en gris porque es el detalle de la cifra,
   no otra cifra: si compitiera en peso habría dos números discutiendo. */
.plano-globo-reparto {
    display: block;
    grid-column: 2;
    font-size: 11px;
    color: var(--tw-font-secondary);
    line-height: 1.3;
}

/* El renglón del parón va en rojo: es la parte de la cifra que pide ir. */
.plano-globo-reparto.es-alerta {
    color: var(--tw-solid-red);
}

/* Al lado de su número y con sitio para caber entero: «Detenido más de un
   turno» partido en tres líneas no se lee, se descifra. */
.plano-globo-etiqueta {
    color: var(--tw-font-secondary);
    line-height: 1.25;
}

/* Enlace, no botón enmarcado: es una acción secundaria dentro de algo que ya
   es una tarjeta flotante. */
/* Los contenedores que el globo lista. En columna y a ancho completo porque son
   destinos, no adornos: se pulsan para ir a uno. */
.plano-globo-lista {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-top: 8px;
    border-top: 1px solid var(--tw-border-light);
}

.plano-globo-lista .plano-globo-enlace {
    padding: 3px 0;
}

/* El enlace del globo puede ser un `button` desde que también abre contenedores,
   y un botón trae sus propios estilos de formulario: se le quitan para que se
   lea igual que cuando es un enlace. */
button.plano-globo-enlace {
    background: none;
    border: 0;
    width: 100%;
    text-align: left;
    font: inherit;
    cursor: pointer;
}

.plano-globo-enlace {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 6px;
    border-radius: var(--tw-radius-sm);
    color: var(--tw-accent);
    font-size: var(--tw-font-size-sm);
    text-decoration: none;
}

.plano-globo-enlace:hover {
    background: var(--tw-bg-transparent-light);
    color: var(--tw-accent-hover);
}

.plano-globo-enlace .ti {
    font-size: var(--tw-icon-size-sm) !important;
}

/* La punta apunta a la marca, y cambia de lado con el globo. */
/* La punta apunta a la marca y cambia de lado con el globo. Sin borde: es el
   mismo papel, no un recorte pegado. */
.plano-globo::before {
    content: '';
    position: absolute;
    top: 16px;
    left: -5px;
    width: 10px;
    height: 10px;
    transform: rotate(45deg);
    background: var(--tw-bg-primary);
}

.plano-globo.a-la-izquierda::before {
    left: auto;
    right: -5px;
}

/* ── Manijas de redimensionado, solo en el constructor ───────────────────── */

.plano-manija {
    fill: var(--tw-bg-primary);
    stroke: var(--tw-accent);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
    cursor: nwse-resize;
}

.plano-manija.ne,
.plano-manija.sw {
    cursor: nesw-resize;
}

/* ── Barras y paneles ────────────────────────────────────────────────────── */

/* `flex: none` y no el reparto de siempre: esta barra es hija de una columna
   flexible que recorta, y sin declararlo se encoge cuando falta alto. Encogerla
   no encoge lo que lleva dentro —las cifras miden lo que miden— así que el
   contenido se salía por abajo y se pintaba encima de la barra siguiente: era
   el amontonamiento de la torre en una ventana baja y ancha. Lo que cede es el
   mapa, que es el único que puede. */
.plano-barra {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    flex: none;
    padding: 8px 12px;
    border-bottom: 1px solid var(--tw-border-light);
    background: var(--tw-bg-primary);
}

/* El botón de modo, encendido. Tabler marca `.active` con un gris que en esta
   barra se confunde con el reposo; aquí se usa el acento, que es como el resto
   del sistema dice «esto está puesto». */
.plano-barra .btn.active {
    background: var(--tw-accent-secondary);
    border-color: var(--tw-accent);
    color: var(--tw-accent);
}

.plano-zoom {
    position: absolute;
    right: 12px;
    bottom: 12px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    z-index: 3;
}

.plano-zoom .btn {
    width: 34px;
    height: 34px;
    padding: 0;
    display: grid;
    place-items: center;
    background: var(--tw-bg-primary);
    border: 1px solid var(--tw-border-light);
}

/* Sin caja: la leyenda se apoya sobre el fondo del visor, con el mismo peso
   que un pie de tabla. Enmarcada parecía un componente aparte flotando encima
   del plano. */
/* La leyenda es lo que traduce los colores del dibujo, así que a 12 px y en
   gris terciario no cumplía su único trabajo: había que adivinar qué
   significaba cada tono, que es exactamente lo que venía a evitar. */
.plano-leyenda {
    position: absolute;
    left: 14px;
    bottom: 12px;
    z-index: 3;
    display: flex;
    flex-wrap: wrap;
    gap: 16px;

    /* ── No se toca, y no llega hasta los botones ─────────────────────────
     *
     * Las dos mitades del mismo fallo: la leyenda estaba **tapando la
     * botonera de zoom**, y con ella el botón de «Sólo el plano». Ocho
     * familias en fila miden ochocientos píxeles, así que en cuanto el mapa
     * baja de mil la leyenda cruza por debajo de la columna de botones de la
     * esquina; las dos capas llevan el mismo `z-index` y gana la última del
     * documento, que es ésta. Los botones se veían —el fondo de la leyenda es
     * translúcido— y no respondían, que es la peor forma de estar roto: el
     * mapa parecía no dejarse agrandar.
     *
     * `pointer-events: none` porque esto **no es un control**: son ocho
     * rótulos que traducen los colores del dibujo, y no hay nada que pulsar
     * en ellos. De propina, el trozo de plano que hay debajo vuelve a poder
     * arrastrarse, que antes tampoco se podía.
     *
     * Y el ancho máximo para que además no se vean encimados: dejar de robar
     * el clic no arregla que dos cosas se dibujen una sobre otra. Los 78 px
     * son la columna de botones —34 de ancho, 12 de margen— y el hueco que la
     * separa del último rótulo. */
    pointer-events: none;
    max-width: calc(100% - 78px);
    padding: 6px 10px;
    border-radius: var(--tw-radius-sm);
    background: color-mix(in srgb, var(--tw-bg-primary) 88%, transparent);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
}

.plano-leyenda-punto::before {
    width: 10px;
    height: 10px;
}

.plano-leyenda-punto {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

/* Los tonos de la leyenda salen de los tokens y no de un color escrito en la
   plantilla: el ámbar y el rojo cambian de valor entre el tema claro y el
   oscuro, y un literal en el HTML se queda con el del tema claro —ilegible
   sobre fondo negro. */
.plano-leyenda-punto--material {
    color: var(--tw-accent);
}

/* Las dos clases nuevas de marca. El color es el mismo —lo que las separa en el
   plano es la forma, porque el color ahí dice el estado— así que la leyenda
   tiene que repetir la forma o no explica nada. La forma se les da más abajo,
   después de la regla que pone redondas todas: con la misma especificidad, gana
   la última. */
/* Las dos de forma van en el gris del reposo, que es el color con el que de
   verdad salen en el dibujo mientras no pasa nada. En azul enseñaban un cuadro
   y un rombo que ninguna marca del plano tiene si no está trabajando, y el azul
   ya dice otra cosa: que ahí hay movimiento. */
.plano-leyenda-punto--maquina,
.plano-leyenda-punto--proceso {
    color: var(--tw-font-light);
}

.plano-leyenda-punto--operando {
    color: var(--tw-solid-green);
}

.plano-leyenda-punto--suelta {
    color: var(--tw-font-light);
}

.plano-leyenda-punto--aviso {
    color: var(--tw-tag-orange-fg);
}

.plano-leyenda-punto--alerta {
    color: var(--tw-solid-red);
}

.plano-leyenda-punto--ciega {
    color: var(--tw-font-secondary);
}

/* Un punto macizo, del mismo color que la marca del plano. El cuadradito con
   borde y relleno translúcido decía tres cosas del mismo estado. */
.plano-leyenda-punto::before {
    content: '';
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
}

/* Y la forma de las otras dos clases de marca, la misma con la que se dibujan
   en el plano: cuadrado el equipo anclado al suelo, rombo el sitio de trabajo. */
.plano-leyenda-punto--maquina::before {
    border-radius: 1px;
}

.plano-leyenda-punto--proceso::before {
    border-radius: 1px;
    transform: rotate(45deg);
}

/* Las dos que no son un punto de color sino una manera de dibujar. Se explican
   con su propio trazo —una raya a rayas, un borde a rayas— porque decirlas con
   un círculo de color sería explicar una cosa enseñando otra. */
.plano-leyenda-punto--flujo::before {
    width: 18px;
    height: 0;
    border-radius: 0;
    background: none;
    border-top: 2px dashed var(--tw-accent);
}

.plano-leyenda-punto--estimado::before {
    width: 14px;
    height: 9px;
    border-radius: 2px;
    background: none;
    border: 1.5px dashed var(--tw-font-secondary);
}

/* Las dos pestañas de la pastilla, con su forma y no con un punto: lo que hay
   que reconocer en el dibujo es una raya vertical pegada al canto derecho. */
.plano-leyenda-punto--trabajando::before,
.plano-leyenda-punto--entregar::before {
    width: 3px;
    height: 10px;
    border-radius: 1.5px;
}

.plano-leyenda-punto--trabajando::before {
    background: var(--tw-accent);
}

.plano-leyenda-punto--entregar::before {
    background: var(--tw-tag-turquoise-fg);
}

.plano-cuerpo {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
}

.plano-lateral {
    width: 340px;
    flex: 0 0 340px;
    border-left: 1px solid var(--tw-border-light);
    background: var(--tw-bg-primary);
    overflow-y: auto;
    padding: 14px;
}

@media (max-width: 900px) {
    .plano-cuerpo {
        flex-direction: column;
        /* Apilado, mide lo que suman el plano y su lista: sin esto se encoge
           con la pantalla y el reparto de abajo no tiene nada que repartir. */
        flex: 1 0 auto;
    }

    /* ── El plano necesita alto propio ────────────────────────────────
     *
     * Puesto en columna, `flex: 1 1 auto` significa «lo que sobre», y
     * debajo de una lista de marcas no sobra nada: medido en un teléfono,
     * al lienzo le quedaban **101 px** para un plano de 316. La pantalla
     * entera —que es un plano— se veía como una tira, y encima con el
     * mando del zoom flotando dentro.
     *
     * Con alto propio deja de depender del sobrante: el cuerpo crece, y
     * quien desplaza es `.page-wrapper`, que ya trae `overflow: auto`. */
    .plano-viewport {
        flex: 0 0 auto;
        height: 60dvh;
        min-height: 320px;
    }

    .plano-lateral {
        width: auto;
        flex: 0 0 auto;
        border-left: 0;
        border-top: 1px solid var(--tw-border-light);
        /* Y la lista deja de desplazarse por su cuenta: dentro de una página
           que ya se desplaza, un segundo desplazamiento es una trampa. */
        max-height: none;
        overflow-y: visible;
    }
}

/* Los iconos se alinean con el **primer renglón**, no con el centro del bloque:
   estas filas llevan nombre arriba y motivo debajo, y centrado verticalmente el
   icono queda flotando entre los dos sin pertenecer a ninguno. */
.plano-lista-zona {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    width: 100%;
    padding: 7px 9px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: none;
    text-align: left;
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-sm);
}

.plano-lista-zona:hover {
    background: var(--tw-bg-transparent-light);
}

/* Seleccionado se dice con el fondo, no con un contorno de color: es como el
   resto del sistema marca la fila activa. */
.plano-lista-zona.is-selected {
    background: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.plano-lista-zona > .ti {
    flex: 0 0 auto;
    /* Empuja el icono a la altura de la primera línea de texto. */
    margin-top: 1px;
}

.plano-lista-zona .plano-lista-meta {
    margin-left: auto;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-xs, 11px);
    white-space: nowrap;
}

/* La fecha de entrega al final del renglón. Se tiñe, no se explica: «vence
   hoy» escrito en palabras ocupa media línea y se lee igual de tarde. */
.plano-lista-meta.es-alerta {
    color: var(--tw-solid-red);
}

.plano-lista-meta.es-aviso {
    color: var(--tw-tag-orange-fg);
}

/* ── Las órdenes del piso ────────────────────────────────────────────────── */

/* Sin caja ni borde: es la misma lista que las alertas de arriba, solo que sus
   filas se abren. Marcar estas con un marco propio partiría la barra en dos
   componentes distintos cuando son la misma cosa vista desde otro lado. */
.plano-orden {
    display: flex;
    flex-direction: column;
}

/* Colgados del renglón de la orden y alineados con su texto —no con su icono—:
   la sangría es lo que dice que pertenecen a ella. Sin riel ni canto de color:
   en esta interfaz el estado se dice tiñendo el dato, no dibujando bordes. */
.plano-orden-nesteos {
    display: flex;
    flex-direction: column;
    margin: 2px 0 6px 26px;
}

.plano-nesteo {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 5px 7px;
    border-radius: var(--tw-radius-sm);
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-sm);
    text-decoration: none;
}

.plano-nesteo:hover {
    background: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.plano-nesteo > .ti {
    flex: 0 0 auto;
    margin-top: 1px;
}

.plano-nesteo-enlace {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 7px;
    border-radius: var(--tw-radius-sm);
    color: var(--tw-accent);
    font-size: var(--tw-font-size-sm);
    text-decoration: none;
}

.plano-nesteo-enlace:hover {
    background: var(--tw-bg-transparent-light);
    color: var(--tw-accent-hover);
}

/* ── Lo que el puesto tiene en la máquina ────────────────────────────────── */

/* Separado de las medidas por una línea porque no es una medida: las cifras de
   abajo son acumulados y esto es una placa dentro de una máquina ahora mismo. */
.plano-globo-corrida {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--tw-border-light);
}

.plano-globo-corrida-titulo {
    display: flex;
    align-items: center;
    gap: 5px;
    color: var(--tw-solid-green);
    font-size: var(--tw-font-size-xs, 11px);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Las cifras de cabecera reutilizan `.tw-agg` del sistema —etiqueta y valor en
   línea, sin caja— en lugar de tarjetas propias. Aquí solo se tiñe el valor
   cuando la cifra se sale de rango: un borde de color alrededor de la tarjeta
   grita más que el dato y no es como este sistema marca los estados. */
.plano-agg--alerta .tw-agg-value {
    color: var(--tw-solid-red);
}

.plano-agg--aviso .tw-agg-value {
    color: var(--tw-tag-orange-fg);
}

/* Y la cifra que lleva a una lista.
   Se marca con el cursor y un subrayado punteado bajo el rótulo, no con color:
   el color de esta fila ya significa otra cosa —fuera de rango— y usarlo
   también para «esto se abre» dejaría las dos lecturas mezcladas. */
.plano-agg--abre {
    cursor: pointer;
}

.plano-agg--abre .tw-agg-label {
    text-decoration: underline dotted;
    text-underline-offset: 3px;
    text-decoration-color: var(--tw-border-strong, var(--tw-font-light));
}

.plano-agg--abre:hover .tw-agg-value,
.plano-agg--abre:focus-visible .tw-agg-value {
    color: var(--tw-accent);
}

.plano-agg--abre:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 3px;
    border-radius: var(--tw-radius-sm);
}

/* =====================================================================
   Torre de control · el pulso, los avisos y el modo monitor
   ---------------------------------------------------------------------
   Todo sale de los tokens: el rojo y el ámbar cambian de valor entre el
   tema claro y el oscuro, y la torre es justo la pantalla que se deja
   puesta en oscuro durante meses.
   ===================================================================== */

/* ── El reparto vertical de la torre ─────────────────────────────────────────

   Quién cede alto cuando la ventana no da para todo. La respuesta es **sólo el
   mapa**: la franja del pulso, las cifras y los filtros miden lo que mide su
   contenido y no se encogen nunca.

   Encogerlos era lo que pasaba por omisión —en una columna flexible todo el
   mundo cede a partes iguales— y el resultado es el que se ve en una ventana
   baja y ancha: la barra se queda en 36 px con 51 px de cifras dentro, lo que
   sobra se sale por abajo, y las tres barras acaban pintadas una encima de
   otra sobre el plano. No es un problema de esta pantalla en concreto: cede
   todo lo que no diga que no cede.

   El aviso de arriba entra en el mismo trato: es el único renglón que aparece y
   desaparece solo, y aplastado dice justo lo contrario de lo que está para
   decir. */
#torre-trabajo > .tw-note {
    flex: none;
}

/* ── La franja de resultado ──────────────────────────────────────────────── */

/* Las columnas se declaran, no se calculan.

   Primero fue `auto-fit` con un mínimo, que parecía lo elegante: se reparte
   solo y sin media queries. El resultado en un monitor ancho era una fila de
   cinco y un sexto bloque solo en la de abajo con medio ancho de pantalla en
   negro al lado — un hueco que parece un sitio reservado para algo que falta.

   Son **seis** bloques y siempre los mismos, así que los repartos que no dejan
   resto son 6, 3 y 2. Se declaran esos tres y ya no hay hueco posible: el
   primero se lleva más ancho porque lleva la barra del plan del día, que es lo
   único de la franja que necesita sitio para leerse. */
/**
 * La franja del pulso · sin huecos, tenga los bloques que tenga.
 *
 * Era una rejilla de seis columnas fijas, y funcionaba mientras hubo seis
 * bloques exactos. Al entrar el séptimo —«Sin cuadrar»— la última fila se
 * quedaba con un bloque y dos celdas vacías del ancho de media pantalla, que en
 * una torre colgada de un monitor se lee como que algo se rompió al cargar.
 *
 * Con `flex-wrap` no hay celdas: los bloques de cada renglón se reparten lo que
 * hay. La base de 200 px es la que decide cuántos caben, así que la franja se
 * reacomoda sola al ancho de la ventana y añadir o quitar un bloque mañana no
 * obliga a tocar tres media queries.
 */
.torre-pulso {
    display: flex;
    flex-wrap: wrap;
    gap: 1px;
    flex: 0 0 auto;
    background: var(--tw-border-light);
    border-bottom: 1px solid var(--tw-border-light);
}

/* El separador es el hueco de la rejilla teñido, no un borde por bloque: así
   no se doblan las líneas donde dos bloques se tocan. */
.torre-pulso-bloque {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 10px 14px;
    background: var(--tw-bg-primary);
    /* Crece para llenar su renglón y se encoge hasta la base: es lo que evita
       el hueco del último renglón sin fijar un número de columnas. */
    /* La base decide cuántos caben por renglón: con 160 px los siete entran en
       una sola línea en cualquier escritorio, que es como se lee la franja de un
       vistazo desde la puerta. */
    flex: 1 1 160px;
    /* Sin esto, un pie largo —«a este paso, cierra 21:40»— empuja el bloque por
       encima de su base y desmonta el reparto del renglón. */
    min-width: 0;
}

/* El plan del día se lleva algo más de sitio: es el único que lleva barra de
   avance, y con el ancho de los demás la barra sale de juguete. */
.torre-pulso-bloque:first-child {
    flex-grow: 1.6;
    /* Sin esto, el bloque con la línea de tendencia estira toda su fila y los
       que no la llevan quedan con un vacío debajo de la cifra. */
    min-height: 0;
}

.torre-pulso-etiqueta {
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.torre-pulso-cifra {
    display: flex;
    align-items: baseline;
    gap: 6px;
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-xxl);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}

/* «de 30» acompaña a la cifra y no compite con ella: mismo renglón, peso de
   texto normal. Con el mismo tamaño, «12 de 30» se leía como dos cifras y
   había que decidir cuál era el dato. */
.torre-pulso-de,
.torre-pulso-unidad {
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular);
}

.torre-pulso-pie {
    color: var(--tw-font-tertiary);
    font-size: var(--tw-font-size-sm);
}

/* El estado se dice tiñendo el dato, nunca con un borde ni un fondo de color:
   es el mismo criterio que las cifras de la barra de abajo. */
.torre-pulso-bloque.es-alerta .torre-pulso-cifra,
.torre-pulso-bloque.es-alerta .torre-pulso-pie {
    color: var(--tw-solid-red);
}

.torre-pulso-bloque.es-aviso .torre-pulso-cifra,
.torre-pulso-bloque.es-aviso .torre-pulso-pie {
    color: var(--tw-tag-orange-fg);
}

/* ── La barra de avance ──────────────────────────────────────────────────── */

.torre-barra {
    height: 6px;
    border-radius: 3px;
    background: var(--tw-bg-transparent-light);
    overflow: hidden;
}

.torre-barra > span {
    display: block;
    height: 100%;
    background: var(--tw-accent);
    transition: width var(--tw-duration-fast, 0.15s) var(--tw-ease, ease);
}

.torre-barra--fina {
    height: 3px;
    margin-top: 5px;
}

/* ── Cada barra, con su nombre y su cuenta ───────────────────────────────── */

/* Dos rayas de tres píxeles una encima de otra no dicen cuál es cuál ni sobre
   qué está calculada cada una, y sus cifras estaban a media línea de distancia
   dentro de la ristra gris del renglón. El rótulo y la cuenta viajan con su
   barra.

   Las tres columnas son de la rejilla y no de cada renglón: con un grid por
   barra, «Placas» y «Verificadas» miden distinto y las barras arrancarían en
   sitios distintos, que es justo lo que impide compararlas de un vistazo
   estando una encima de la otra. */
.torre-medidas {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 8px;
    row-gap: 4px;
    margin-top: 6px;
}

/* Dentro de la rejilla el aire lo pone «row-gap»; el margen que la barra trae
   para ir suelta bajo el renglón la descuadraría de su propio rótulo. Y sube a
   cuatro píxeles porque aquí ya no es un adorno al pie del renglón: es el dato. */
.torre-medidas .torre-barra--fina {
    height: 4px;
    margin-top: 0;
}

.torre-medida-nombre,
.torre-medida-cifra {
    font-size: var(--tw-font-size-xs, 11px);
    line-height: 1.2;
    white-space: nowrap;
}

.torre-medida-nombre {
    color: var(--tw-font-light);
}

/* La cuenta, alineada a la derecha y en cifras de ancho fijo: «5/14» y «2/10»
   una debajo de otra sólo se comparan si caen en la misma columna. */
.torre-medida-cifra {
    justify-self: end;
    color: var(--tw-font-secondary);
    font-variant-numeric: tabular-nums;
}

/* Lo verificado no compite con las placas: va en verde y debajo, porque no es
   otro avance del mismo trabajo sino el mismo trabajo ya comprobado. Las placas
   dicen cuánto salió de la máquina y esto cuánto llegó entero al final; la
   distancia entre las dos barras es la merma que todavía puede aparecer. */
.torre-barra--verificado > span {
    background: var(--tw-solid-green);
}

.torre-barra.es-alerta > span,
.torre-pulso-bloque.es-alerta .torre-barra > span {
    background: var(--tw-solid-red);
}

.torre-barra.es-aviso > span,
.torre-pulso-bloque.es-aviso .torre-barra > span {
    background: var(--tw-tag-orange-fg);
}

/* ── La línea de las últimas horas ───────────────────────────────────────── */

/* Se estira con su hueco —`preserveAspectRatio="none"` en el SVG— porque la
   misma chispa tiene que servir en una columna de 190 px y en un monitor. La
   forma se deforma y no importa: lo que se lee es la pendiente. */
.torre-chispa {
    width: 100%;
    height: 26px;
    margin-top: auto;
    display: block;
}

.torre-chispa-linea {
    fill: none;
    stroke: var(--tw-accent);
    stroke-width: 1.5;
    vector-effect: non-scaling-stroke;
}

/* La mancha bajo la línea no es adorno: una polilínea de un píxel sobre fondo
   negro, vista a tres metros, desaparece. */
.torre-chispa-area {
    fill: var(--tw-accent);
    opacity: 0.14;
    stroke: none;
}

.torre-chispa.es-alerta .torre-chispa-linea {
    stroke: var(--tw-solid-red);
}

.torre-chispa.es-alerta .torre-chispa-area {
    fill: var(--tw-solid-red);
}

/* ── Los avisos ──────────────────────────────────────────────────────────── */

/* La sangría de 14 px la llevan **todos** los avisos, no sólo los escalados: es
   el hueco donde va el punto rojo del escalado (más abajo). Si sólo lo llevara
   la fila marcada, esa fila arrancaría 14 px a la derecha de sus vecinas y la
   lista quedaría con un escalón. */
.torre-aviso {
    position: relative;
    display: flex;
    align-items: stretch;
    gap: 4px;
    padding-left: 14px;
}

.torre-aviso > .plano-lista-zona {
    flex: 1 1 auto;
    min-width: 0;
}

/* Cuánto lleva así. Es lo que ordena la lista y lo que sube de tono solo, así
   que va al final del renglón y en cifras tabulares: en columna, un vistazo
   basta para ver cuál es el más viejo. */
.torre-aviso .plano-lista-meta {
    font-variant-numeric: tabular-nums;
}

.torre-aviso .plano-lista-meta.es-alerta,
.torre-aviso .plano-lista-meta.es-escalado {
    color: var(--tw-solid-red);
}

/* Atendido: el renglón entero baja de intensidad. Sigue ahí —el problema no se
   ha resuelto, sólo tiene dueño— pero deja de competir por la mirada con los
   que todavía no lo tienen. */
.torre-aviso.es-atendido {
    opacity: 0.65;
}

.torre-aviso-dueno {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--tw-solid-green);
    font-size: var(--tw-font-size-xs);
}

/* Sobrevivió a una vuelta a la nave sin dueño. Un punto rojo a la izquierda y
   no un fondo: el fondo de color en una lista de ocho renglones convierte la
   pantalla en un semáforo donde ya no se distingue nada.

   Y un punto de verdad, no un filo de 2 px, que es lo que había hasta el 6 de
   agosto de 2026 y lo que se quitó de todo el sistema ese día (ver `.tw-note`).
   El punto se lee como marca —igual que el de «en vivo»— y el filo se leía como
   un borde a medio pintar. */
.torre-aviso.es-escalado {
    border-radius: var(--tw-radius-sm);
}

.torre-aviso.es-escalado::before {
    content: '';
    position: absolute;
    left: 2px;
    top: 0.65em;
    width: 6px;
    height: 6px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-solid-red);
}

.torre-aviso-tomar {
    flex: 0 0 auto;
    align-self: center;
    padding: 4px 8px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    background: none;
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-xs);
    white-space: nowrap;
}

.torre-aviso-tomar:hover {
    background: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

/* ── Las firmas ──────────────────────────────────────────────────────────────

   Quién ha estado en la nave hoy. Es lo único que un puesto sabe contar: lo que
   se captura en él se dibuja en su proceso —una misma tablet lleva Doblez y
   Galvanizado, y son dos áreas distintas—, así que al punto le quedaba el color
   y nada más.

   El renglón se lee de izquierda a derecha como una frase —quién, dónde,
   cuándo— y la hora va alineada al final para poder recorrer la columna de
   horas sin leer los nombres. */

/* Las dos listas del registro tienen el mismo renglón a propósito: son la misma
   historia contada por sus dos lados —qué le pasó al material y quién estuvo— y
   dos maquetas distintas las leerían como dos pantallas.

   Con alto propio y desplazamiento por dentro: sin él, treinta movimientos
   empujan la firma más reciente fuera de la pantalla y hay que recorrer el panel
   entero para llegar a la otra lista. */
.torre-registro {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 15rem;
    overflow-y: auto;
}

.torre-firma,
.torre-evento {
    display: grid;
    grid-template-columns: 16px 1fr auto;
    align-items: start;
    gap: 8px;
    padding: 6px 8px;
    border-radius: var(--tw-radius-sm, 4px);
    font-size: 12px;
    line-height: 1.35;
}

.torre-firma:hover,
.torre-evento:hover {
    background: var(--tw-bg-tertiary, var(--tw-bg-secondary));
}

.torre-firma > .ti,
.torre-evento > .ti {
    color: var(--tw-font-light);
    margin-top: 1px;
}

/* El cuerpo del renglón puede encogerse por debajo de su contenido.

   Sin esto, la columna `1fr` de la retícula se planta en el ancho de lo más
   largo que lleve dentro —una línea de seriales, que va en un renglón y sin
   cortar— y el panel entero crece hacia la derecha: la hora se sale de la
   pantalla y aparece una barra de desplazamiento horizontal debajo de la lista.
   Es el suelo `auto` que CSS le pone a todo elemento de retícula. */
.torre-firma > span:first-of-type,
.torre-evento > span:first-of-type {
    min-width: 0;
}

.torre-evento-hora {
    color: var(--tw-font-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* El renglón que se abre · sólo la tanda de Empaque, que lleva seriales dentro.
   Es un botón y por tanto hay que devolverle la retícula y el texto alineado a
   la izquierda, que el navegador le quita a todos. Sin borde ni fondo: dentro de
   una lista, un botón dibujado como botón rompe la columna. */
button.torre-evento.es-abrible {
    width: 100%;
    border: 0;
    background: none;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

/* Los seriales de la tanda, en la tipografía del código: se reconocen por la
   forma —«KN-0042-A»— y con la proporcional se leen como texto corrido.

   En un solo renglón y cortados: tres seriales de dieciséis caracteres no caben
   en una columna de 340 px y envolvían el renglón en dos líneas más, que en una
   lista de sesenta movimientos es media pantalla gastada en repetir códigos que
   nadie compara ahí. Aquí sirven para reconocer la tanda; para leerlos se pulsa
   el renglón. */
.torre-evento-seriales {
    display: block;
    color: var(--tw-font-secondary);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* De dónde salió lo que se empacó. Va entre el contexto y los seriales porque
   es lo que hace creíble la cifra del título, y no se recorta con puntos
   suspensivos como aquéllos: una carga a medio nombre no dice de qué caja
   habla. Cuando no cabe, envuelve. */
.torre-evento-origen {
    display: block;
    margin-top: 2px;
    color: var(--tw-font-secondary);
    font-size: 11px;
    line-height: 20px;
}

/* La que sigue abierta es el estado de ahora, no el archivo del día: se marca
   con el verde de lo que está en marcha, el mismo del punto con alguien
   firmado. */
.torre-firma.es-abierta > .ti {
    color: var(--tw-solid-green);
}

.torre-firma-hora {
    color: var(--tw-font-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.torre-firma.es-abierta .torre-firma-hora {
    color: var(--tw-solid-green);
}

/* Y las mismas, colgadas del globo de su puesto. Separadas por una línea porque
   arriba hay cifras y aquí hay personas: son dos clases de dato. */
.plano-globo-firmas {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-top: 8px;
    border-top: 1px solid var(--tw-border-light);
}

.plano-globo-firmas .torre-firma {
    padding: 3px 0;
}

.torre-contador {
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-sm);
    font-variant-numeric: tabular-nums;
}

/* ── Lo que se apagó sin que nadie lo tomara ─────────────────────────────── */

/* Plegado: ya pasó, así que no puede empujar hacia abajo lo que está pasando.
   Pero tiene que estar, porque es lo que dice si la pantalla se usa. */
.torre-silencios > summary {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 9px;
    border-radius: var(--tw-radius-sm);
    color: var(--tw-font-tertiary);
    font-size: var(--tw-font-size-sm);
    cursor: pointer;
    list-style: none;
}

.torre-silencios > summary::-webkit-details-marker {
    display: none;
}

.torre-silencios > summary:hover {
    background: var(--tw-bg-transparent-light);
}

.torre-silencio {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 3px 9px 3px 24px;
    color: var(--tw-font-tertiary);
    font-size: var(--tw-font-size-sm);
}

.torre-silencio > span:first-child {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* La fecha a la que llega al ritmo que lleva, no la comprometida. */
.torre-proyeccion {
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-xs);
}

.torre-proyeccion.es-alerta {
    color: var(--tw-solid-red);
}

/* ── El reloj del monitor ────────────────────────────────────────────────── */

.torre-reloj {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.torre-reloj-hora {
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
}

.torre-reloj-turno {
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-sm);
}

/* ── Los contenedores, dentro de su zona del plano ───────────────────────────

   Una pastilla maciza por contenedor, con su código y lo que lleva ahí. Maciza
   y no un contorno más: el plano ya está lleno de líneas —las del arquitecto
   debajo y las de las zonas encima— y una etiqueta con borde se pierde entre
   ellas. Un bloque relleno es lo único que se lee de un vistazo sobre un
   dibujo técnico. */

.plano-carga {
    cursor: pointer;
}

/* Borde de dos y no de uno: sobre un plano de arquitecto, un contorno de un
   píxel es una línea más entre las suyas. */
.plano-carga-fondo {
    fill: var(--tw-bg-primary);
    stroke: var(--tw-font-secondary);
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
    transition:
        fill var(--tw-transition),
        stroke var(--tw-transition);
}

.plano-carga-texto {
    fill: var(--tw-font-primary);
    font-size: 11px;
    font-weight: var(--tw-weight-semibold);
    text-anchor: middle;
    dominant-baseline: middle;
    pointer-events: none;
}

.plano-carga:hover > .plano-carga-fondo {
    fill: var(--tw-accent-tertiary);
    stroke: var(--tw-accent);
}

.plano-carga.is-selected > .plano-carga-fondo {
    fill: var(--tw-accent-secondary);
    stroke: var(--tw-accent);
    stroke-width: 2;
}

/* El retraso va en el relleno entero, no en una marca al lado: a la escala de
   una pastilla de quince unidades no cabe nada al lado. Macizo y en blanco, que
   es lo único que se distingue de un vistazo sobre un dibujo lleno de líneas —un
   tinte claro se confunde con el resto de pastillas—. */
.plano-carga.es-tarde > .plano-carga-fondo {
    fill: var(--tw-solid-red);
    stroke: var(--tw-solid-red);
}

.plano-carga.es-tarde > .plano-carga-texto {
    fill: #ffffff;
}

/* Y late. Es el único movimiento de esta pantalla que señala un problema, así
   que se reserva para lo que ya pasó su plazo: si latiera también lo parado, la
   nave entera parpadearía a media tarde y dejaría de significar nada. */
@keyframes plano-carga-latido {
    0%,
    100% {
        fill-opacity: 1;
    }
    50% {
        fill-opacity: 0.45;
    }
}

.plano-carga.es-tarde > .plano-carga-fondo {
    animation: plano-carga-latido 1.6s ease-in-out infinite;
}

/* Parado no es tarde. Ocho horas en el mismo paso es un turno entero sin
   moverse —algo que alguien tiene que ir a ver— y todavía no es un plazo roto,
   así que va en el ámbar de aviso y respira despacio en vez de latir. */
.plano-carga.es-parado > .plano-carga-fondo {
    fill: var(--tw-tag-orange-fg);
    stroke: var(--tw-tag-orange-fg);
    animation: plano-carga-respira 2.8s ease-in-out infinite;
}

@keyframes plano-carga-respira {
    0%,
    100% {
        fill-opacity: 1;
    }
    50% {
        fill-opacity: 0.6;
    }
}

.plano-carga.es-parado > .plano-carga-texto {
    fill: #ffffff;
}

/* Colocado por deducción y no por lectura · el trazo se corta.
   «Alguien lo pasó por el lector aquí» y «su recorrido dice que le toca aquí»
   son cosas muy distintas cuando hay que ir a buscarlo, y dibujarlas iguales
   sería vender una captura que nadie hizo. */
.plano-carga.es-estimado > .plano-carga-fondo {
    stroke-dasharray: 4 3;
}

/* --- Trabajándose o esperando a que se la lleven ---------------------------
   Las dos están en el mismo punto y son dos trabajos distintos: de la primera
   se ocupa quien la tiene delante, y la segunda no se mueve hasta que el puesto
   siguiente venga por ella.

   Va en una pestaña dentro de la pastilla y no en su relleno, que ya está dicho:
   el relleno es el reloj --rojo el plazo roto, ámbar el turno sin moverse-- y un
   segundo color ahí dejaría la pastilla sin significar ninguna de las dos cosas.

   Azul la que se está trabajando, que es el color del movimiento en esta
   pantalla; turquesa la que ya terminó y espera. */
.plano-carga-marca {
    fill: var(--tw-accent);
}

.plano-carga.es-por-entregar > .plano-carga-marca {
    fill: var(--tw-tag-turquoise-fg);
}

/* Sobre el rojo y el ámbar macizos, la pestaña se pierde: ahí va en blanco y
   sigue diciendo lo mismo por el sitio que ocupa. */
.plano-carga.es-tarde > .plano-carga-marca,
.plano-carga.es-parado > .plano-carga-marca {
    fill: #ffffff;
    fill-opacity: 0.85;
}

@media (prefers-reduced-motion: reduce) {
    .plano-carga.es-tarde > .plano-carga-fondo,
    .plano-carga.es-parado > .plano-carga-fondo {
        animation: none;
    }
}

.plano-carga.is-selected > .plano-carga-texto {
    fill: var(--tw-accent);
}

.plano-carga.es-tarde.is-selected > .plano-carga-fondo {
    stroke: var(--tw-font-primary);
    stroke-width: 3;
}

/* ── La maqueta · la vista 3D de la torre ────────────────────────────────────

   El mismo cuerpo del plano con el otro lienzo puesto: la clase `vista-3d` en
   `.plano-cuerpo` decide cuál de los dos se ve. Se alterna por display y no
   por `hidden` porque los dos lienzos comparten lateral y botones de zoom, y
   el panel 3D necesita medir su hueco en cuanto aparece —un ResizeObserver lo
   está esperando—.

   Las etiquetas de la maqueta son DOM proyectado (CSS2DRenderer): la capa
   entera es sorda al ratón y cada cosa que se pulsa se declara sola, que es el
   mismo trato que la capa de flechas del SVG. */

/* El conmutador 2D/3D flota en la esquina del mapa: cambia el dibujo, no la
   pregunta, así que no le toca sitio en la barra de vistas. `plano-cuerpo` se
   vuelve su marco de referencia. */
.plano-cuerpo {
    position: relative;
}

/* ── «Sólo el plano» ─────────────────────────────────────────────────────────

   En una pantalla chica los indicadores se comen el mapa: este modo los
   esconde todos —pulso, cifras, filtros, lateral y leyenda— y deja el plano a
   pantalla del panel, con la barra de navegación de la aplicación en su
   sitio. No es la pantalla completa del navegador: es la torre quitándose el
   saco. Se enciende con el botón de la botonera de zoom y se recuerda. */

.torre-amplia .torre-pulso,
.torre-amplia .plano-barra,
.torre-amplia .torre-barra-cargas,
.torre-amplia .plano-lateral,
.torre-amplia .plano-leyenda {
    display: none;
}

.plano-modo {
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 30;
    background: var(--tw-bg-primary);
    box-shadow: var(--tw-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.2));
}

/* Las capas de la maqueta, debajo del 2D/3D y sólo cuando ella está puesta:
   en el cenital estas lecturas no existen. */
.torre-capas {
    display: none;
    position: absolute;
    top: 52px;
    left: 10px;
    z-index: 30;
    background: var(--tw-bg-primary);
    box-shadow: var(--tw-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.2));
}

.plano-cuerpo.vista-3d .torre-capas {
    display: flex;
}

/* La pista del cursor sobre la maqueta: el `title` del 2D, dicho en un
   cartelito que sigue al puntero y nunca se interpone. */
.torre-iso-pista {
    position: absolute;
    z-index: 35;
    max-width: 240px;
    padding: 4px 10px;
    border: 1px solid var(--tw-border-light);
    border-radius: 6px;
    background: var(--tw-bg-primary);
    color: var(--tw-font-secondary);
    font-size: 12px;
    pointer-events: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    box-shadow: var(--tw-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.2));
}

.torre-iso-pista strong {
    color: var(--tw-font-primary);
}

/* El nombre de una persona en el piso: gafete sobre la figurita. El de quien
   acaba de mover algo se enciende en verde y late — es la firma viva. El
   renderizador 3D reparte z-index por profundidad; el `!important` lo pisa a
   propósito: un gafete nunca se esconde detrás del rótulo de una zona, porque
   a la gente se le busca por nombre. */
.torre-iso-persona {
    z-index: 40 !important;
    padding: 2px 9px;
    border: 1px solid var(--tw-border-light);
    border-radius: 999px;
    background: var(--tw-bg-primary);
    color: var(--tw-font-secondary);
    font-size: 12px;
    font-weight: var(--tw-weight-semibold, 600);
    white-space: nowrap;
    box-shadow: 0 2px 8px rgb(0 0 0 / 0.35);
}

.torre-iso-persona.es-activa {
    z-index: 41 !important;
    border-color: var(--tw-solid-green);
    color: var(--tw-solid-green);
    font-size: 13px;
    animation: plano-latido 1.8s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .torre-iso-persona.es-activa {
        animation: none;
    }
}

.torre-iso {
    display: none;
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    position: relative;
    overflow: hidden;
    border-radius: var(--tw-radius-md, 8px);
    background: var(--tw-bg-secondary);
}

.plano-cuerpo.vista-3d .torre-iso {
    display: block;
}

.plano-cuerpo.vista-3d .plano-viewport {
    display: none;
}

.torre-iso canvas {
    display: block;
    /* Arrastrar mueve, como en el plano: la mano lo dice antes de intentarlo. */
    cursor: grab;
}

.torre-iso-etiquetas {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

/* Lo pulsable de la capa de etiquetas, declarado pieza a pieza. */
.torre-iso-etiquetas [data-tramo],
.torre-iso-etiquetas [data-cuenta],
.torre-iso-etiquetas [data-punta],
.torre-iso-etiquetas [data-persona],
.torre-iso-etiquetas [data-nota],
.torre-iso-etiquetas .plano-globo {
    pointer-events: auto;
    cursor: pointer;
}

/* El nombre de una zona, flotando sobre su volumen. El halo lo pone la sombra
   de texto y no un fondo: un cartel opaco por zona convertiría la maqueta en
   un corcho de avisos. */
/* La barra que corta el láser de tubo, encima de la máquina: el programa y
   qué barra va. Borde naranja, el tono de «en proceso» de las etiquetas. */
.torre-iso-nombre.torre-iso-corte {
    border-color: var(--tw-tag-orange-fg);
    color: var(--tw-tag-orange-fg);
    font-variant-numeric: tabular-nums;
}

.torre-iso-nombre {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: var(--tw-weight-semibold, 600);
    color: var(--tw-font-primary);
    text-shadow:
        0 0 4px var(--tw-bg-secondary),
        0 0 8px var(--tw-bg-secondary);
    white-space: nowrap;
    transition: opacity var(--tw-transition, 0.15s ease);
}

/* ── Las pastillas de la maqueta · vidrio, no papel ──────────────────────────

   En el plano 2D estas pastillas se posan sobre un dibujo plano y opacas están
   bien. En la maqueta flotan sobre una escena con luz, y una pastilla opaca ahí
   se lee como un recorte pegado encima: no pertenece al sitio del que cuelga.
   Con el fondo translúcido y desenfocado —vidrio— la escena se ve a través y la
   pastilla queda **dentro** de la maqueta.

   Y el aro de luz es lo que la ata a su haz: la cifra de un tramo brilla del
   color de ese tramo, así que la vista salta del número a la línea sin buscar. */
/* La cuenta de contenedores de la zona: la insignia del 2D, en HTML. El texto
   va en blanco literal igual que `.plano-cuenta-cifra`: sobre rojo o naranja
   sólidos, el token de tinta oscura no se lee. */
.torre-iso-cuenta {
    border: none;
    border-radius: 999px;
    min-width: 22px;
    height: 20px;
    padding: 0 6px;
    font-size: 12px;
    font-weight: var(--tw-weight-semibold, 600);
    color: #ffffff;
    background: var(--tw-accent);
    cursor: pointer;
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--tw-accent) 55%, transparent),
        0 0 12px -2px color-mix(in srgb, var(--tw-accent) 75%, transparent),
        0 1px 3px rgba(0, 0, 0, 0.35);
}

.torre-iso-cuenta.es-parado {
    background: var(--tw-tag-orange-fg);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--tw-tag-orange-fg) 55%, transparent),
        0 0 12px -2px color-mix(in srgb, var(--tw-tag-orange-fg) 75%, transparent),
        0 1px 3px rgba(0, 0, 0, 0.35);
}

.torre-iso-cuenta.es-tarde {
    background: var(--tw-solid-red);
    box-shadow:
        0 0 0 1px color-mix(in srgb, var(--tw-solid-red) 55%, transparent),
        0 0 12px -2px color-mix(in srgb, var(--tw-solid-red) 75%, transparent),
        0 1px 3px rgba(0, 0, 0, 0.35);
}

/* La cifra de un tramo de flujo: mismo lenguaje que `.plano-flujo-cuenta`, con
   el vidrio y el aro del color de su haz. */
.torre-iso-cifra {
    border: 1.5px solid var(--tw-accent);
    border-radius: 999px;
    min-width: 24px;
    height: 22px;
    padding: 0 6px;
    font-size: 11px;
    font-weight: var(--tw-weight-semibold, 600);
    color: var(--tw-font-primary);
    background: color-mix(in srgb, var(--tw-bg-primary) 72%, transparent);
    backdrop-filter: blur(9px) saturate(1.4);
    -webkit-backdrop-filter: blur(9px) saturate(1.4);
    box-shadow: 0 0 14px -3px color-mix(in srgb, var(--tw-accent) 85%, transparent);
    cursor: pointer;
}

.torre-iso-cifra.es-tarde {
    border-color: var(--tw-solid-red);
    box-shadow: 0 0 14px -3px color-mix(in srgb, var(--tw-solid-red) 85%, transparent);
}

.torre-iso-cifra.es-surtido {
    border-color: var(--tw-tag-purple-fg, var(--tw-accent));
    box-shadow: 0 0 14px -3px color-mix(in srgb, var(--tw-tag-purple-fg, var(--tw-accent)) 85%, transparent);
}

.torre-iso-cifra.es-ensamble {
    border-color: var(--tw-tag-turquoise-fg, var(--tw-accent));
    box-shadow: 0 0 14px -3px color-mix(in srgb, var(--tw-tag-turquoise-fg, var(--tw-accent)) 85%, transparent);
}

/* La salida de la nave, colgada del rótulo de su marca en la maqueta: la misma
   etiqueta que en el plano 2D y por la misma razón —no es un camino, es una
   propiedad del sitio—. Ámbar cuando el material se va de la planta; gris
   cuando sólo es un proceso que nadie dibujó. */
.torre-iso-salida {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    margin-left: 4px;
    padding: 1px 7px;
    border: 1.5px solid var(--tw-tag-orange-fg, var(--tw-accent));
    border-radius: 4px;
    background: color-mix(in srgb, var(--tw-bg-primary) 72%, transparent);
    backdrop-filter: blur(9px) saturate(1.4);
    -webkit-backdrop-filter: blur(9px) saturate(1.4);
    box-shadow: 0 0 12px -4px color-mix(in srgb, var(--tw-tag-orange-fg, var(--tw-accent)) 85%, transparent);
    color: var(--tw-font-primary);
    font-size: 10px;
    font-weight: var(--tw-weight-semibold, 600);
    cursor: pointer;
}

.torre-iso-salida.es-sin-punto {
    border-style: dashed;
    border-color: var(--tw-font-light);
}

.torre-iso-salida:hover {
    background: var(--tw-accent-tertiary);
}

.torre-iso-cifra:hover {
    background: var(--tw-accent-tertiary);
}

/* Los extremos del recorrido, con los colores de sus pastillas del 2D. */
.torre-iso-punta {
    border-radius: 999px;
    padding: 2px 10px;
    font-size: 11px;
    font-weight: var(--tw-weight-semibold, 600);
    color: var(--tw-font-primary);
    background: color-mix(in srgb, var(--tw-bg-primary) 72%, transparent);
    backdrop-filter: blur(9px) saturate(1.4);
    -webkit-backdrop-filter: blur(9px) saturate(1.4);
    border: 1.5px solid var(--tw-tag-purple-fg, var(--tw-accent));
    box-shadow: 0 0 14px -3px color-mix(in srgb, var(--tw-tag-purple-fg, var(--tw-accent)) 85%, transparent);
    cursor: pointer;
    white-space: nowrap;
}

.torre-iso-punta.es-salida {
    border-color: var(--tw-tag-turquoise-fg, var(--tw-accent));
    box-shadow: 0 0 14px -3px color-mix(in srgb, var(--tw-tag-turquoise-fg, var(--tw-accent)) 85%, transparent);
}

.torre-iso-punta.es-laminas {
    border-color: var(--tw-tag-blue-fg, var(--tw-accent));
    box-shadow: 0 0 14px -3px color-mix(in srgb, var(--tw-tag-blue-fg, var(--tw-accent)) 85%, transparent);
}

/* Lo que la oficina no ha soltado: gris, sin halo. En la maqueta el halo es lo
   que dice «esto está vivo», y un paquete sin soltar no lo está todavía. */
.torre-iso-punta.es-preparado {
    border-color: var(--tw-border-strong, var(--tw-font-light));
    border-style: dashed;
    box-shadow: none;
    color: var(--tw-font-secondary);
}

/* Y la puerta: ámbar cuando hay acero descargado que nadie puede escanear,
   gris cuando sólo está entrando. */
.torre-iso-punta.es-recibiendo {
    border-color: var(--tw-tag-orange-fg, var(--tw-accent));
    box-shadow: 0 0 14px -3px color-mix(in srgb, var(--tw-tag-orange-fg, var(--tw-accent)) 85%, transparent);
}

.torre-iso-punta.es-entrando {
    border-color: var(--tw-border-strong, var(--tw-font-light));
    box-shadow: none;
    color: var(--tw-font-secondary);
}

/* ── El desglose de contenedores en un modal ─────────────────────────────────

   La cifra de una zona o de un tramo se abre a la lista completa: un renglón
   por contenedor con qué lleva, en qué paso va y de quién es. Antes eran ocho
   códigos centrados y un «y 4 más»: para saber qué había, a abrirlos uno por
   uno. El renglón entero es el botón —abre la ficha— y la lista se desplaza
   por dentro, así el modal no crece con la planta. */

.torre-desglose {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 12px;
    max-height: 45vh;
    overflow-y: auto;
    padding-right: 2px;
}

.torre-desglose-fila {
    display: flex;
    flex-direction: column;
    gap: 3px;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 1px solid var(--tw-border-light);
    border-radius: 8px;
    padding: 8px 12px;
    cursor: pointer;
    transition:
        background var(--tw-transition),
        border-color var(--tw-transition);
}

.torre-desglose-fila:hover {
    background: var(--tw-accent-tertiary);
    border-color: var(--tw-accent);
}

/* Como enlace de página conserva la pinta de renglón, no de liga azul. */
a.torre-desglose-fila {
    color: inherit;
    text-decoration: none;
}

/* Rótulo y valor en la misma línea, para las fichas de puros datos: siete
   renglones apilados en dos líneas cada uno no caben en la ventana y obligan a
   desplazarse dentro del diálogo para leer siete palabras. La caja, el borde,
   el hover y la regla de `es-dato` se quedan como están: lo único que cambia es
   que el valor va a la derecha en vez de debajo. */
.torre-desglose-fila.es-linea {
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.torre-desglose-fila.es-linea .torre-desglose-cab {
    text-align: right;
}

/* El renglón que no abre nada no lo aparenta: sin mano y sin hover. Es la
   regla de estos desgloses — prometer un clic que no existe es peor que no
   tener clic. */
.torre-desglose-fila.es-dato {
    cursor: default;
}

.torre-desglose-fila.es-dato:hover {
    background: transparent;
    border-color: var(--tw-border-light);
}

.torre-desglose-cab {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: var(--tw-weight-semibold, 600);
    color: var(--tw-font-primary);
}

.torre-desglose-cab .ti {
    color: var(--tw-font-secondary);
}

/* Colocado por deducción: el borde cortado del plano, hecho pastilla. */
.torre-desglose-nota {
    font-size: 11px;
    font-weight: var(--tw-weight-regular, 400);
    color: var(--tw-font-light);
    border: 1px dashed var(--tw-font-light);
    border-radius: 999px;
    padding: 0 8px;
    white-space: nowrap;
}

.torre-desglose-tags {
    margin-left: auto;
    display: flex;
    gap: 4px;
    flex-wrap: wrap;
    justify-content: flex-end;
}

.torre-desglose-dato {
    font-size: var(--tw-font-size-sm, 13px);
    color: var(--tw-font-secondary);
}

.torre-desglose-pie {
    font-size: 12px;
    color: var(--tw-font-light);
}

/* La nota clavada: el punto naranja del cenital y el gafete del 3D. */
.plano-nota {
    fill: var(--tw-tag-orange-fg);
    stroke: var(--tw-bg-primary);
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
}

.torre-iso-nota {
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 1px 8px;
    border: 1px solid var(--tw-tag-orange-fg);
    border-radius: 999px;
    background: var(--tw-bg-primary);
    color: var(--tw-tag-orange-fg);
    font-size: 11px;
    font-weight: var(--tw-weight-semibold, 600);
    cursor: pointer;
}

/* El minimapa: la nave en la esquina con el cono de la cámara. */
.torre-iso-minimapa {
    position: absolute;
    left: 10px;
    bottom: 10px;
    z-index: 25;
    border: 1px solid var(--tw-border-light);
    border-radius: 6px;
    background: var(--tw-bg-primary);
    opacity: 0.92;
    cursor: pointer;
}

/* ── La máquina del tiempo ────────────────────────────────────────────────────

   Vive al pie del mapa: el deslizador rebobina el piso con los pasos de cada
   carga y el botón de repetir anima los traslados del registro. Cuando se está
   mirando el pasado, la barra entera lo dice en ámbar: una pantalla que enseña
   ayer sin avisar es una trampa. */

.torre-tiempo {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    z-index: 30;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: 999px;
    background: var(--tw-bg-primary);
    box-shadow: var(--tw-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.2));
}

.torre-tiempo[hidden] {
    display: none;
}

.torre-tiempo input[type='range'] {
    width: 220px;
}

.torre-tiempo-rotulo {
    min-width: 12rem;
    text-align: center;
    font-size: 12px;
    font-weight: var(--tw-weight-semibold, 600);
    color: var(--tw-font-secondary);
}

.torre-tiempo.es-pasado {
    border-color: var(--tw-tag-orange-fg);
}

.torre-tiempo.es-pasado .torre-tiempo-rotulo {
    color: var(--tw-tag-orange-fg);
}

/* ── El recorrido de una pieza ────────────────────────────────────────────────

   La misma barra al pie del mapa que la máquina del tiempo, y por lo mismo: las
   dos dicen que lo que se está mirando no es ahora. Va siempre en ámbar —no hay
   un recorrido «del presente»— y crece a dos líneas porque tiene que caber lo
   que se viene a leer aquí: la hora real, qué pasó y quién lo firmó.

   Ancho acotado y no elástico: pegada a los dos bordes taparía las marcas de las
   esquinas, que son justo las que la reproducción está señalando. */

.torre-repro {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    z-index: 30;
    display: flex;
    align-items: center;
    gap: 12px;
    width: min(560px, calc(100% - 32px));
    padding: 8px 12px;
    border: 1px solid var(--tw-tag-orange-fg);
    border-radius: 12px;
    background: var(--tw-bg-primary);
    box-shadow: var(--tw-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.2));
}

.torre-repro[hidden] {
    display: none;
}

.torre-repro-mandos {
    display: flex;
    align-items: center;
    gap: 2px;
}

.torre-repro-cuerpo {
    flex: 1 1 auto;
    min-width: 0;
}

.torre-repro-linea {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

.torre-repro-serial {
    font-size: 12px;
    font-weight: var(--tw-weight-semibold, 600);
    color: var(--tw-font-secondary);
    text-decoration: none;
}

.torre-repro-serial:hover {
    color: var(--tw-accent);
    text-decoration: underline;
}

/* La hora real, que es la mitad de la respuesta: «¿qué pasó a las 2:30?». */
.torre-repro-hora {
    font-size: 12px;
    font-weight: var(--tw-weight-semibold, 600);
    color: var(--tw-tag-orange-fg);
    white-space: nowrap;
}

.torre-repro-titulo {
    font-size: var(--tw-font-size-md, 15px);
    font-weight: var(--tw-weight-semibold, 600);
    color: var(--tw-font-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Quién, dónde y cuánto falta. Se recorta antes que el título: de las dos
   líneas, la que contesta la pregunta es la de arriba. */
.torre-repro-pie {
    font-size: 12px;
    color: var(--tw-font-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.torre-repro-cuerpo input[type='range'] {
    display: block;
    width: 100%;
    margin-top: 6px;
}

/* Una parada que no ocurrió en la nave —un certificado validado, un proceso de
   un proveedor—: la cámara no vuela a ningún sitio, y el rótulo lo dice en vez
   de dejar creer que la reproducción se colgó. */
.torre-repro.es-fuera .torre-repro-titulo {
    color: var(--tw-font-secondary);
    font-style: italic;
}

/* De pie y a tres metros los rótulos del escritorio no existen. */
body.torre-monitor .torre-iso-nombre {
    font-size: var(--tw-font-size-md, 15px);
}

body.torre-monitor .torre-iso-cuenta,
body.torre-monitor .torre-iso-cifra {
    min-width: 28px;
    height: 26px;
    font-size: 14px;
}

/* ── Los contenedores, uno por uno ───────────────────────────────────────────

   La otra lectura de la torre: en vez de zonas del plano con lo que hay dentro,
   un renglón por contenedor con su ruta desplegada y el reloj de cada etapa.

   Ocupa el mismo hueco que `.plano-cuerpo` y se turna con él, así que hereda su
   contrato: crece con lo que sobra y se desplaza por dentro. Sin `min-height:
   0` la lista empujaría el alto de la pantalla y la barra de desplazamiento
   saldría en el documento en vez de en la lista, que es lo que rompe el modo
   monitor —ahí no hay ratón con el que arrastrarla—. */

.torre-flujo {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
}

/* ── El tablero ──────────────────────────────────────────────────────────────

   Los mismos contenedores repartidos por proceso. Reutiliza `.tw-board` tal
   cual —columnas de 284 px, tarjeta con título y renglones— y sólo añade lo que
   el tablero de una planta necesita y un tablero de tareas no: el avance por la
   ruta y la marca de retraso. */

.torre-tablero {
    position: relative;
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
}

/* Flota sobre el tablero en vez de ocupar un renglón propio: aparece y
   desaparece solo, y un hueco que se abre y se cierra empujaría las columnas
   arriba y abajo cada vez que alguien mueve una. */
.torre-tablero-estado {
    position: absolute;
    right: 14px;
    bottom: 12px;
    z-index: 3;
    padding: 5px 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-pill);
    background: var(--tw-bg-primary);
    box-shadow: var(--tw-shadow-light);
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
}

.torre-tablero-estado.es-hecho {
    border-color: var(--tw-tag-green-fg);
    color: var(--tw-tag-green-fg);
}

/* El orden de las columnas es el orden del flujo de la planta y se arrastra.
   El cursor lo dice antes de tocarlas: sin él, que una columna se pueda mover
   es una función que sólo conoce quien la programó. */
.tw-board-column.es-movible > .tw-board-column-header {
    cursor: grab;
}

.tw-board-column.es-arrastrada {
    opacity: 0.4;
}

/* El destino se marca en el borde de entrada, no rellenando la columna: lo que
   se está eligiendo es un **sitio entre dos**, no una columna. */
.tw-board-column.es-destino {
    box-shadow: inset 3px 0 0 var(--tw-accent);
}

/* El retraso, con el mismo rojo que la lista y el plano: es el mismo hecho
   contado en tres sitios y tiene que verse igual en los tres. */
.torre-tarjeta.es-tarde {
    border-color: var(--tw-solid-red);
    background: var(--tw-tag-red-bg);
}

.torre-tarjeta .torre-carga-marca {
    margin-left: auto;
}

/* La marca de las tarjetas de los extremos —«cortando ahora», «abierta»— no es
   un retraso, así que no lleva el rojo: es el estado en el que están, dicho con
   palabras porque a esa anchura no cabe una leyenda. */
.torre-tarjeta-marca {
    margin-left: auto;
    flex: 0 0 auto;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular);
}

.torre-tarjeta.es-tarde .torre-tarjeta-marca {
    color: var(--tw-solid-red);
    font-weight: var(--tw-weight-medium);
}

/* El programa por surtir no tiene contenedor todavía, así que su título es el
   del nesteo y conviene que no se lea como un código de carga. */
.torre-tarjeta--surtir .tw-board-card-title {
    font-variant-numeric: tabular-nums;
}

/* «paso 2 de 5» obliga a dividir de cabeza en cada tarjeta, y en un tablero de
   treinta no se hace en ninguna. La barra se lee sin leer. */
.torre-tarjeta-avance {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
}

.torre-tarjeta-avance .torre-barra {
    flex: 1 1 auto;
}

.torre-tarjeta-pasos {
    flex: 0 0 auto;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-sm);
    font-variant-numeric: tabular-nums;
}

/* Encendido pero mudo: el navegador no deja sonar hasta que alguien toca la
   página. No es un error —no hay nada roto— así que se dice con el ámbar del
   aviso y no con el rojo de la alerta. */
#torre-sonido.es-bloqueado {
    border-color: var(--tw-tag-orange-fg);
    color: var(--tw-tag-orange-fg);
}

/* Buscar y acotar, encima de las tres vistas. Va fuera de la lista porque quien
   mira el plano persigue el mismo contenedor que quien mira los renglones. */
.torre-barra-cargas {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    /* Lo mismo que `.plano-barra`: mide lo suyo y no se deja encoger. */
    flex: none;
    padding: 10px 14px;
    border-bottom: 1px solid var(--tw-border-light);
    background: var(--tw-bg-primary);
}

.torre-barra-cuenta {
    margin-left: auto;
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-md);
    font-variant-numeric: tabular-nums;
}

/* ── El buscador, con los seriales debajo ────────────────────────────────────

   La caja mide lo que medía —22 rem— pero lo mide el envoltorio, que es de
   donde cuelga la lista: la lista flota, y dentro del `input-icon` heredaría su
   recorte. Anclarla ya no es cosa del envoltorio —se coloca contra la pantalla,
   ver `.torre-seriales`— pero sigue siendo quien la contiene, y de eso vive el
   «pulsar fuera cierra» de `torre.js`. */

.torre-buscador {
    position: relative;
    width: 22rem;
    max-width: 100%;
}

/* Los seriales que coinciden. Flota sobre el mapa —de ahí el z-index alto, por
   encima del conmutador 2D/3D y de las capas— y se acota en alto: son seis
   renglones y una respuesta, no un listado que se recorre.

   `fixed` y medido a mano, como el panel de filtros: colgada del envoltorio y
   en absoluto, la lista la recortaban dos ancestros —`.tw-panel`, que esconde
   lo que se le sale, y la propia barra, que en pantallas bajas se desplaza de
   lado (`overflow-x: auto`, que arrastra el vertical con él)—. Lo que se veía
   era la lista naciendo dentro de la barra y empujando una barra de
   desplazamiento, en vez de flotar por encima del plano. La medida la calcula
   `colocarSeriales()` contra la caja de texto, con vuelta arriba cuando abajo
   no cabe. */
.torre-seriales {
    position: fixed;
    z-index: 1060;
    max-height: 17rem;
    overflow-y: auto;
    padding: 6px;
    border: 1px solid var(--tw-border-light);
    border-radius: 8px;
    background: var(--tw-bg-primary);
    box-shadow: var(--tw-shadow-md, 0 4px 12px rgba(0, 0, 0, 0.25));
}

.torre-seriales[hidden] {
    display: none;
}

.torre-seriales-titulo {
    padding: 4px 8px 6px;
    color: var(--tw-font-tertiary);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.torre-serial {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 6px 8px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--tw-font-primary);
    text-align: left;
}

.torre-serial:hover,
.torre-serial:focus-visible {
    background: var(--tw-bg-tertiary, var(--tw-accent-tertiary));
}

.torre-serial-codigo {
    font-weight: var(--tw-weight-semibold, 600);
    white-space: nowrap;
}

/* El número de parte cede primero: la identidad es el serial. */
.torre-serial-parte {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--tw-font-secondary);
    font-size: 12px;
}

.torre-flujo-lista {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* El renglón. Sin barra de color a la izquierda: el retraso se dice con el
   borde entero y el fondo, como el resto del sistema. */
.torre-carga {
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background: var(--tw-bg-primary);
    padding: 10px 12px;
}

/* El retraso se dice con el borde entero y un tono de fondo, no con una barra
   al lado. Borde de dos píxeles y no de uno: al lado de cinco renglones
   normales, uno solo tiene que saltar sin tener que compararlo con el vecino.

   En ROJO y no en ámbar, y el mismo rojo en la lista, en el paso y en la
   pastilla del plano. El ámbar ya significa «puesto ahogado» —un aviso sobre el
   puesto— y esto es otra cosa: material que se pasó de su plazo. Con el mismo
   tono para las dos, la leyenda del plano no podría distinguirlas. */
.torre-carga.es-tarde {
    border-color: var(--tw-solid-red);
    border-width: 2px;
    background: var(--tw-tag-red-bg);
}

.torre-carga-cabecera {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.torre-carga-id {
    display: flex;
    align-items: baseline;
    gap: 10px;
    min-width: 0;
}

.torre-carga-codigo {
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    letter-spacing: -0.01em;
}

.torre-carga-que {
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-md);
}

/* La palabra, no sólo el color: en una captura, impresa, o al lado de otro
   ámbar, un fondo teñido deja de significar nada y esto sigue diciéndolo. */
.torre-carga-marca {
    display: inline-flex;
    align-items: center;
    padding: 1px 7px;
    border-radius: var(--tw-radius-sm);
    background: var(--tw-solid-red);
    color: #ffffff;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-semibold);
    letter-spacing: 0.04em;
}

/* Dónde está y a dónde va, en su propio renglón y grande: es literalmente la
   pregunta que esta vista vino a contestar, y metida entre el código y el reloj
   se leía como un pie de foto. */
.torre-carga-viaje {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    min-width: 0;
    margin: 8px 0;
    font-size: var(--tw-font-size-lg);
}

.torre-carga-lugar,
.torre-carga-destino {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

.torre-carga-flecha {
    color: var(--tw-font-light);
    font-size: 1.15em;
}

.torre-carga-reloj {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    margin-left: auto;
}

.torre-carga-tiempo {
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}

.torre-carga.es-tarde .torre-carga-tiempo {
    color: var(--tw-solid-red);
}

.torre-carga-etiqueta {
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-md);
}

/* Las acciones y la cadena comparten renglón: lo que se hace a la izquierda,
   de dónde viene a la derecha. */
.torre-carga-pie {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--tw-border-light);
}

.torre-carga-acciones {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.torre-carga-acciones .btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.torre-carga-cadena {
    margin: 0;
    font-size: var(--tw-font-size-sm);
}

/* «Qué trae» · el botón que abre el contenido de la caja.

   La flecha gira al abrir y no cambia la palabra: con el rótulo alternando
   entre «Qué trae» y «Ocultar», la botonera cambia de ancho cada vez que se
   pulsa y los dos botones de al lado se mueven de sitio. */
.torre-carga-abrir-flecha {
    transition: transform 0.15s ease;
}

.torre-carga-abrir.es-abierto {
    background: var(--tw-bg-tertiary);
}

.torre-carga-abrir.es-abierto .torre-carga-abrir-flecha {
    transform: rotate(180deg);
}

/* El desglose · qué trae dentro, por número de parte.

   Colgado del renglón y con su propio borde superior, como el pie: es una
   sección más de la misma tarjeta y no una tarjeta dentro de otra. */
.torre-carga-contenido {
    display: block;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--tw-border-light);
}

/* El atributo manda sobre el display de arriba: sin esto, `hidden` no esconde
   nada en cuanto la clase declara su propio display. */
.torre-carga-contenido[hidden] {
    display: none;
}

.torre-contenido {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--tw-font-size-md);
}

.torre-contenido th {
    padding: 0 8px 6px;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    text-align: left;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom: 1px solid var(--tw-border-light);
    white-space: nowrap;
}

.torre-contenido td {
    padding: 7px 8px;
    border-bottom: 1px solid var(--tw-border-light);
    color: var(--tw-font-secondary);
    vertical-align: top;
}

.torre-contenido tbody tr:last-child td {
    border-bottom: 0;
}

/* La identidad del renglón, en el peso del código de la caja: la columna que
   se recorre con la vista es ésta y no las tres de la derecha. */
.torre-contenido-parte {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

.torre-contenido-nota {
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-sm);
}

/* Las cifras se comparan de arriba abajo, así que van alineadas por la derecha
   y con cifras de ancho fijo: sin eso, 9 y 351 no se pueden leer en columna. */
.torre-contenido-cifra {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.torre-contenido tbody .torre-contenido-cifra {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

/* La suma, separada del resto: es el mismo número que la cabecera del renglón
   y estando los dos a la vista se ve si el desglose cuadra con lo que anuncia. */
.torre-contenido tfoot td {
    padding-top: 8px;
    border-top: 1px solid var(--tw-border-medium);
    border-bottom: 0;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-sm);
}

.torre-contenido tfoot .torre-contenido-cifra {
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
}

/* La ruta · una tira de etapas con su reloj.

   Se desplaza en horizontal por dentro y no se parte en varias líneas: leída
   como una tira, el orden de los pasos se ve sin leer los números, y con
   `flex-wrap` una ruta de siete etapas se convierte en dos renglones donde el
   quinto paso queda encima del segundo. */
.torre-ruta {
    display: flex;
    align-items: stretch;
    gap: 6px;
    margin: 0;
    padding: 0 0 2px;
    list-style: none;
    overflow-x: auto;
}

/* Se reparten el ancho, pero con tope: una ruta de tres etapas estirada a lo
   ancho de la pantalla deja tarjetas de medio metro con cuatro palabras dentro,
   y la tira deja de leerse como una secuencia. Con el tope, tres pasos ocupan
   lo que necesitan y siete se siguen repartiendo el hueco. */
.torre-paso {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    gap: 1px;
    min-width: 8.5rem;
    max-width: 16rem;
    padding: 6px 8px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    background: var(--tw-bg-secondary);
}

/* El estado, escrito. Antes se decía sólo con el tono de la tarjeta, y un azul
   claro contra un gris claro obliga a comparar dos etapas para saber cuál es
   cuál; escrito se sabe mirando una sola. */
.torre-paso-rotulo {
    color: var(--tw-font-light);
    font-size: 0.6875rem; /* 11px */
    font-weight: var(--tw-weight-semibold);
    letter-spacing: 0.06em;
}

.torre-paso-nombre {
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.torre-paso-tiempo {
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
    line-height: 1.15;
}

.torre-paso-detalle {
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-sm);
}

/* Lo hecho se apaga; donde está el trabajo ahora se levanta. Es el mismo
   criterio del plano: el estado va en el fondo y el borde, no en un adorno. */
.torre-paso.es-cerrado {
    background: var(--tw-bg-primary);
}

.torre-paso.es-cerrado .torre-paso-nombre,
.torre-paso.es-cerrado .torre-paso-tiempo {
    color: var(--tw-font-secondary);
    font-weight: var(--tw-weight-regular);
}

.torre-paso.es-cerrado .torre-paso-rotulo {
    color: var(--tw-tag-green-fg);
}

/* La etapa donde está el trabajo ahora se rellena de acento y va en blanco: es
   el único bloque macizo del renglón, así que se encuentra sin leer nada. */
.torre-paso.es-actual {
    border-color: var(--tw-accent);
    background: var(--tw-accent);
}

.torre-paso.es-actual .torre-paso-rotulo,
.torre-paso.es-actual .torre-paso-nombre,
.torre-paso.es-actual .torre-paso-tiempo,
.torre-paso.es-actual .torre-paso-detalle {
    color: #ffffff;
}

.torre-paso.es-actual .torre-paso-detalle {
    opacity: 0.85;
}

.torre-paso.es-actual.es-tarde {
    border-color: var(--tw-solid-red);
    background: var(--tw-solid-red);
}

.torre-paso.es-pendiente,
.torre-paso.es-saltado {
    background: none;
    border-style: dashed;
}

.torre-paso.es-pendiente .torre-paso-nombre,
.torre-paso.es-saltado .torre-paso-nombre,
.torre-paso.es-pendiente .torre-paso-tiempo,
.torre-paso.es-saltado .torre-paso-tiempo {
    color: var(--tw-font-light);
    font-weight: var(--tw-weight-regular);
}

.torre-paso.es-saltado .torre-paso-nombre {
    text-decoration: line-through;
}

/* ── Modo monitor ────────────────────────────────────────────────────────── */

/* Lo que cambia colgada en la nave no es el contenido, es el reparto: fuera la
   navegación, la tipografía crece y el plano deja de ocupar tres cuartos de la
   pantalla para enseñar ocho puntos.

   Todo cuelga de una clase en el `body` que pone el servidor, así que la barra
   lateral no llega a pintarse ni un fotograma al abrir. */
body.torre-monitor .navbar-vertical,
body.torre-monitor .torre-reloj[hidden] {
    display: none !important;
}

body.torre-monitor .page-wrapper {
    margin-left: 0 !important;
    padding-left: 0 !important;
}

/* El reparto invertido. Antes el plano se llevaba el 75 % de los píxeles para
   contestar «dónde», que se contesta rápido; el panel de la derecha —que es lo
   que dice qué hacer ahora— vivía en 340 px con letra de 13. */
body.torre-monitor .plano-lateral {
    width: 40%;
    flex: 0 0 40%;
    font-size: var(--tw-font-size-lg);
    padding: 18px;
}

body.torre-monitor .plano-lista-zona,
body.torre-monitor .plano-nesteo {
    font-size: var(--tw-font-size-lg);
    padding: 10px 12px;
}

body.torre-monitor .plano-lista-zona small,
body.torre-monitor .torre-silencio,
body.torre-monitor .torre-silencios > summary {
    font-size: var(--tw-font-size-md);
}

body.torre-monitor .tw-view-name {
    font-size: var(--tw-font-size-xl);
}

body.torre-monitor .torre-pulso-cifra {
    font-size: 2.4rem;
}

body.torre-monitor .torre-pulso-etiqueta,
body.torre-monitor .torre-pulso-pie {
    font-size: var(--tw-font-size-md);
}

body.torre-monitor .torre-chispa {
    height: 40px;
}

body.torre-monitor .tw-agg {
    font-size: var(--tw-font-size-lg);
}

/* Sin ratón delante no hay nada que pulsar: los controles de zoom y el botón
   de atender ocupan sitio y no sirven a nadie. El de atender se queda —el
   supervisor sí se acerca a la pantalla— pero crece para el dedo. */
body.torre-monitor .plano-zoom {
    display: none;
}

body.torre-monitor .torre-aviso-tomar {
    padding: 8px 14px;
    font-size: var(--tw-font-size-md);
}

/* En un monitor vertical o en una tableta el plano se va arriba y las listas
   abajo, igual que en un teléfono, pero sin recortar el alto del panel: aquí
   lo que se mira es la lista. */
@media (max-width: 1200px) {
    body.torre-monitor .plano-lateral {
        width: auto;
        flex: 1 1 auto;
        max-height: none;
    }
}

/* La lista de contenedores, colgada. Un buscador sin teclado delante no es una
   herramienta, es un hueco: lo que se mira aquí son los que llevan más tiempo
   parados, que ya vienen arriba. Los filtros se quedan —se pulsan con el dedo
   al pasar por delante— y crecen para eso. */
body.torre-monitor .torre-barra-cargas .input-icon {
    display: none;
}

body.torre-monitor .torre-barra-cargas .tw-seg > button {
    padding: 7px 14px;
    font-size: var(--tw-font-size-lg);
}

body.torre-monitor .torre-carga-codigo,
body.torre-monitor .torre-carga-tiempo {
    font-size: 1.6rem;
}

body.torre-monitor .torre-carga-marca,
body.torre-monitor .torre-paso-rotulo {
    font-size: var(--tw-font-size-md);
}

body.torre-monitor .torre-carga-que,
body.torre-monitor .torre-carga-viaje,
body.torre-monitor .torre-carga-etiqueta,
body.torre-monitor .torre-paso-nombre,
body.torre-monitor .torre-paso-detalle {
    font-size: var(--tw-font-size-lg);
}

body.torre-monitor .torre-paso-tiempo {
    font-size: var(--tw-font-size-xl);
}

body.torre-monitor .torre-paso {
    min-width: 11rem;
}

/* La cadena y el pie son de consulta —se leen sentado, con el ratón encima—;
   a tres metros ocupan un tercio del renglón y no se llegan a leer. */
body.torre-monitor .torre-carga-cadena,
body.torre-monitor .torre-carga-pie {
    display: none;
}

/* =====================================================================
   Buscador global · la caja que abre ⌘K
   ---------------------------------------------------------------------
   Todo sale de los tokens de arriba, así que el tema oscuro no necesita
   una sola regla propia: los tokens ya cambian de valor bajo
   [data-bs-theme='dark'].
   ===================================================================== */

/* El buscador no se va con la lista.
   `#sidebar-menu` es lo que se desplaza —arriba, en «La barra, repartida
   en tres»—, así que el envoltorio se pega a su borde y los veinticinco
   renglones del menú corren por debajo. Con el menú entero abierto, la
   caja quedaba a media pantalla de distancia: quien busca desde abajo del
   todo tenía que subir a mano hasta ella o acordarse del atajo.

   El fondo va aquí y no en el botón porque el botón se ilumina al pasar
   el ratón, y ese resalte es semitransparente: si el fondo fuera suyo,
   los renglones se verían pasar por detrás justo mientras se apunta.
   El relleno de arriba es el margen que traía el botón, ahora en el
   envoltorio para que la franja opaca llegue hasta el borde. */
.tw-nav-search-fija {
    position: sticky;
    top: 0;
    z-index: 2;
    padding-top: 6px;
    background-color: var(--tw-bg-secondary);
}

.tw-nav-search-fija > .tw-nav-search {
    margin-top: 0;
}

/* En el cajón del teléfono el relleno de arriba es suyo: ver el `padding: 0`
   del panel, más abajo. */
@media (max-width: 991.98px) {
    .tw-nav-search-fija {
        padding-top: 8px;
    }
}

/* El disparador de la barra lateral.
   Comparte la geometría exacta de `.nav-link` —mismo margen, mismo
   relleno, misma caja de icono— para que el texto «Buscar» arranque en la
   misma columna que «Inicio» y las dos filas se lean como una sola lista.
   Sin borde por la misma razón: en esta barra nada más lo lleva. */
.tw-nav-search {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 6px 6px 2px;
    padding: 4px 8px;
    min-height: 30px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: transparent;
    color: var(--tw-font-tertiary);
    font-family: var(--tw-font-family);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    text-align: left;
    cursor: pointer;
    transition: background-color 0.1s ease;
}

.tw-nav-search:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

/* Misma caja fija que `.nav-link-icon`: es lo que alinea la columna. */
.tw-nav-search__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 var(--tw-icon-size);
    width: var(--tw-icon-size);
    height: var(--tw-icon-size);
    color: var(--tw-font-tertiary);
}

.tw-nav-search__icon .ti {
    font-size: var(--tw-icon-size);
    line-height: 1;
}

.tw-nav-search__label {
    flex: 1;
    line-height: 20px;
}

.tw-nav-search__kbd {
    flex: 0 0 auto;
    font-family: var(--tw-font-family);
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
    background: transparent;
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-xs);
    padding: 0 4px;
    line-height: 1.5;
}

/* ── Secciones plegables del menú ──────────────────────────────────── */

/* El encabezado de sección dejó de ser un rótulo y es un botón: se pliega.
   Conserva la geometría de `.nav-link` —por eso la misma especificidad que
   Tabler— y sólo apaga lo que el navegador le pone por ser un botón. */
.navbar-vertical.navbar-expand-lg .navbar-nav .tw-nav-section__toggle {
    width: calc(100% - 12px);
    margin: 12px 6px 0;
    border: 0;
    background: transparent;
    font-family: var(--tw-font-family);
    text-align: left;
    cursor: pointer;
}

.tw-nav-section__toggle .tw-nav-section-title {
    flex: 1;
}

/* La flecha aparece con el puntero encima, con el foco o cuando la sección
   está cerrada. Siempre visible serían seis flechas compitiendo con los
   iconos de las pantallas, que es lo que de verdad se busca en el menú. */
.navbar-vertical .navbar-nav .tw-nav-section__chevron {
    flex: 0 0 auto;
    font-size: var(--tw-font-size-md) !important;
    color: var(--tw-font-light);
    opacity: 0;
    transition:
        transform 0.15s ease,
        opacity 0.1s ease;
}

.tw-nav-section__toggle:hover .tw-nav-section__chevron,
.tw-nav-section__toggle:focus-visible .tw-nav-section__chevron,
.tw-nav-section.is-collapsed .tw-nav-section__chevron {
    opacity: 1;
}

.tw-nav-section.is-collapsed .tw-nav-section__chevron {
    transform: rotate(-90deg);
}

.tw-nav-section__toggle:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: -2px;
    border-radius: var(--tw-radius-sm);
}

.tw-nav-section__items {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin: 0;
    padding: 0;
}

.tw-nav-section.is-collapsed .tw-nav-section__items > li {
    display: none;
}

/* Cerrada, la sección sigue mostrando la pantalla abierta: quedarse sin
   saber dónde estás cuesta más que el renglón que se ahorra.
 *
 * Iba por `a.current`, que es la clase que ponía el guion del armazón. Al
 * pasar el marcado al servidor y a `aria-current` esta regla dejó de casar, y
 * plegar el grupo de la pantalla en la que estabas la hacía desaparecer del
 * menú: era el único sitio donde la marca vieja hacía algo más que pintar. */
.tw-nav-section.is-collapsed .tw-nav-section__items > li:has(> a.current),
.tw-nav-section.is-collapsed .tw-nav-section__items > li:has(> a[aria-current='page']) {
    display: block;
}

/* ── Las dos barras laterales ──────────────────────────────────────────

   El panel y Ajustes conviven en el HTML y sólo una se ve; la otra se
   queda para que el buscador global siga encontrando sus pantallas. El
   atajo `hidden` del navegador no basta: `navbar-nav` trae `display:
   flex`, que le gana por especificidad. */
.navbar-vertical .navbar-nav[hidden] {
    display: none !important;
}

/* La salida de Ajustes. Es un enlace más, pero en gris de segunda fila:
   compite con el renglón de la pantalla que se vino a abrir, y perder esa
   competencia es lo correcto. */
.navbar-vertical .navbar-nav .nav-link.tw-nav-back {
    color: var(--tw-font-tertiary);
    margin-bottom: 4px;
}

/* En Ajustes el encabezado de grupo no se pliega: es un rótulo, no un
   botón, así que no debe reaccionar al puntero como si lo fuera. */
.navbar-vertical .navbar-nav div.nav-link.tw-nav-section__toggle {
    margin-top: 12px;
    cursor: default;
}

/* ── Cuántas cosas esperan ─────────────────────────────────────────────

   El número de las bandejas de «En vivo». Es la misma etiqueta roja suave
   que el resto de la aplicación usa para lo que va mal —no un punto rojo
   sólido—: en una barra que se mira todo el día, un aviso que grita se
   deja de ver a los dos días. `hidden` lo pone el script; el cero no se
   pinta, porque una bandeja vacía no tiene nada que decir. */
.tw-nav-badge {
    flex: 0 0 auto;
    margin-left: auto;
    /* El renglón mide 236px y «Mostrador de impresión» ocupa casi todo: con la
       insignia por delante se quedaba a un pelo de caber y partía el texto en
       dos líneas. El −3px se lo come al relleno derecho del enlace, que la
       píldora ya trae dentro. */
    margin-right: -3px;
    min-width: 16px;
    padding: 0 4px;
    border-radius: var(--tw-radius-xs);
    background-color: var(--tw-tag-red-bg);
    color: var(--tw-tag-red-fg);
    font-family: var(--tw-font-family);
    font-size: var(--tw-font-size-xxs);
    font-weight: var(--tw-weight-semibold);
    line-height: 16px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.tw-nav-badge[hidden] {
    display: none;
}

/* La insignia que lleva a otro sitio que el renglón —Recepciones va a la lista
   entera, su número va a los certificados que faltan—. El aro al pasar por
   encima es lo único que lo anuncia: sin él, tocarla parecía tocar el renglón.
   Ver el manejador de clic en components/nav-badges.js. */
.tw-nav-badge[data-nav-badge-href] {
    cursor: pointer;
    box-shadow: 0 0 0 0 transparent;
    transition: box-shadow 0.1s ease;
}

.tw-nav-badge[data-nav-badge-href]:hover {
    box-shadow: 0 0 0 2px var(--tw-tag-red-bg);
}

.tw-nav-badge--espera[data-nav-badge-href]:hover {
    box-shadow: 0 0 0 2px var(--tw-bg-quaternary);
}

/* Dos insignias en el mismo renglón —Recorridos lleva «esperando» y «fuera de
   plazo»— tienen que distinguirse sin leer el número: la de esperar es neutra
   porque se resuelve trabajando, y el rojo se queda para lo que ya se pasó de
   tiempo y no se arregla solo. */
.tw-nav-badge--espera {
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-secondary);
}

/* Recepciones lleva dos: los certificados sin papel, en rojo, y las coladas sin
   país de fundido, en naranja. Las dos son faltas, pero de distinta clase: el
   papel depende del proveedor; el país está en un papel que ya se tiene. */
.tw-nav-badge--pais {
    background-color: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
}

.tw-nav-badge--pais[data-nav-badge-href]:hover {
    box-shadow: 0 0 0 2px var(--tw-tag-orange-bg);
}

/* ── Saltar al menú ────────────────────────────────────────────────────────
 *
 * Escondido hasta que recibe el foco. No con `display: none` ni con
 * `visibility: hidden`, que lo sacarían también del recorrido del tabulador y
 * lo dejarían inservible: se aparta fuera de la pantalla y vuelve al recibirlo.
 *
 * `:focus` y no `:focus-visible`: aquí sólo se llega con teclado, y el segundo
 * deja fuera al foco puesto por un guion o por un lector de pantalla. */
.tw-skip {
    position: fixed;
    top: 8px;
    left: -9999px;
    z-index: 1050;
    padding: 8px 14px;
    border-radius: var(--tw-radius-sm);
    background: var(--tw-bg-secondary);
    color: var(--tw-font-primary);
    box-shadow: var(--tw-shadow-md, 0 4px 16px rgb(0 0 0 / 25%));
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    text-decoration: none;
}

.tw-skip:focus {
    left: 8px;
}

/* Al saltar, el contenido recibe el foco para que el siguiente tabulador siga
   dentro de la pantalla y no vuelva al principio del menú. Ese foco no se
   dibuja: no es un control, es el sitio al que se acaba de llegar. */
.page-wrapper:focus {
    outline: none;
}

/* ── Anclar una pantalla arriba ────────────────────────────────────────────
 *
 * El botón vive encima del renglón, en el sitio de la insignia, y sólo mientras
 * el ratón está en el renglón —o el propio botón tiene el foco, que es como se
 * llega con teclado—. Es el mismo trato que se le da a lo que no es contenido:
 * aparece cuando se busca y no compite el resto del tiempo.
 *
 * Se superpone al número a propósito. El renglón mide 236 px y «Mostrador de
 * impresión» ya se los come casi todos: reservarle sitio propio partiría en dos
 * líneas la mitad de los rótulos por un botón que se usa una vez al mes. Y
 * mientras el ratón está encima, el número no es lo que se está mirando.
 *
 * Ver components/nav-anclados.js. */
.navbar-vertical .navbar-nav li.nav-item {
    position: relative;
}

.tw-nav-anclar {
    position: absolute;
    top: 50%;
    right: 9px;
    transform: translateY(-50%);
    display: none;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: var(--tw-radius-xs);
    background: transparent;
    color: var(--tw-font-tertiary);
    line-height: 1;
}

.tw-nav-anclar .ti {
    font-size: 14px !important;
    margin: 0 !important;
}

li.nav-item:hover > .tw-nav-anclar,
.tw-nav-anclar:focus-visible,
/* Puesto se queda a la vista: es lo que explica por qué ese renglón está
   arriba, y es la única forma de quitarlo sin adivinar dónde pulsar. */
.tw-nav-anclar.is-anclado {
    display: flex;
}

.tw-nav-anclar:hover {
    background: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.tw-nav-anclar.is-anclado {
    color: var(--tw-font-secondary);
    transform: translateY(-50%) rotate(45deg);
}

/* Con el botón encima, el número de debajo estorba: son dos cosas peleando por
   los mismos veinte píxeles. Se va mientras el ratón está en el renglón. */
li.nav-item:hover > a.nav-link > .tw-nav-badge {
    visibility: hidden;
}

/* En el riel no hay dónde ponerlo —el renglón son 56 px y todo es el icono— y
 * tampoco hace falta: quien plegó el menú ya decidió que quiere lo mínimo.
 *
 * ACOTADO A 992, que es donde el riel existe: `data-nav="plegada"` es una
 * preferencia guardada en el `<html>` y no se borra al estrechar la ventana, así
 * que por debajo de ese ancho —donde el menú es un cajón que se abre entero, con
 * sus rótulos, y hasta el botón de plegar está escondido— el atributo sigue
 * puesto y esto dejaba el pin sin dibujar. Sin hover en un teléfono, eso es no
 * poder anclar nada. */
@media (min-width: 992px) {
    html[data-nav='plegada'] .tw-nav-anclar {
        display: none !important;
    }

    html[data-nav='plegada'] li.nav-item:hover > a.nav-link > .tw-nav-badge {
        visibility: visible;
    }

    /* Y el renglón vuelve a ser un bloque: en una tablet apaisada —1024 px,
       puntero grueso— el riel sí existe, y ahí el `display: flex` que el
       bloque táctil le pone al `li` para hacerle sitio al pin descentraría el
       icono en una columna donde no hay pin ninguno. */
    html[data-nav='plegada'] .navbar-vertical .navbar-nav li.nav-item {
        display: block;
    }
}

/* La sección de los anclados no se pliega: es corta por definición —seis como
   mucho— y plegar el atajo que uno mismo se puso arriba no ahorra nada. */
.tw-nav-section--anclados .tw-nav-section__toggle {
    cursor: default;
}

/* En el riel sí está, y es donde más sirve: son treinta y dos iconos sin rótulo
 * en una columna que no cabe de una vez, así que tener los propios en la primera
 * pausa ahorra el paseo. Estuvo escondida el rato que los anclados fueron
 * copias —el mismo icono dos veces a cuatro renglones de distancia no es un
 * atajo, es una duda—; moviéndolos, cada dibujo vuelve a salir una sola vez. */

/* ── La bandeja con algo que se quedó atrás ────────────────────────────────
 *
 * La insignia se invierte —fondo lleno del color del texto— cuando lo más viejo
 * de la bandeja lleva más de una semana. Es el mismo dato de siempre, dicho más
 * fuerte: no aparece un número nuevo, cambia el que ya estaba, porque el
 * renglón no tiene ancho para dos y «Mostrador de impresión» ya lo demostró.
 *
 * La frase completa —«12 solicitudes en el mostrador · la más vieja lleva 23
 * días»— va en el `title` y en el `aria-label`, que es donde cabe. Ver
 * `is-vieja` en components/nav-badges.js.
 *
 * Se distingue del rojo normal sin depender del color, que es la condición para
 * que sirva: el relleno sólido se ve en una nave con luz de nave, y quien no
 * separa bien los rojos sigue viendo que ese renglón pesa más que los otros. */
.tw-nav-badge.is-vieja {
    background-color: var(--tw-tag-red-fg);
    color: var(--tw-tag-red-bg);
}

/* La de esperar y la propia son neutras a propósito —se resuelven trabajando—,
   así que al envejecer no pueden ponerse rojas: eso diría que algo está mal
   cuando lo que hay es una cola larga. Suben un escalón de contraste y ya. */
.tw-nav-badge--espera.is-vieja,
.tw-nav-badge--propio.is-vieja {
    background-color: var(--tw-font-tertiary);
    color: var(--tw-bg-secondary);
}

/* Lo mío, en Inicio · neutro y no rojo.
 *
 * Las seis bandejas son rojas porque son trabajo parado de la planta: alguien
 * tiene que ir. Ésta es la lista de lo que uno mismo dejó a medias, y no
 * significa que nada esté mal —vaciarla del todo no es un objetivo—, así que se
 * lee como el «esperando» de Recorridos: un dato, no una alarma. */
.tw-nav-badge--propio {
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-secondary);
}

/* La suma de la sección, sólo cuando está plegada: con el grupo abierto
   sería el mismo dato dos veces, y el encabezado no es donde se mira. El
   `margin-left: auto` lo pone el título, que ya crece; aquí sobra. */
.tw-nav-badge--total {
    margin-left: 6px;
}

.tw-nav-section:not(.is-collapsed) .tw-nav-badge--total {
    display: none;
}

/* El punto del cajón, en teléfono y tablet. El menú va cerrado, así que
   el número no existe hasta que alguien lo abre: esto dice «hay algo». */
.navbar-vertical .navbar-toggler {
    position: relative;
}

.tw-nav-dot {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: var(--tw-solid-red);
    /* El aro del color de la barra despega el punto del icono de debajo. */
    box-shadow: 0 0 0 2px var(--tw-bg-secondary);
}

.tw-nav-dot[hidden] {
    display: none;
}

/* ── La caja ───────────────────────────────────────────────────────── */

.tw-palette {
    position: fixed;
    inset: 0;
    z-index: 1080; /* por encima del modal de Bootstrap (1055) */
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 12vh 16px 16px;
}

.tw-palette[hidden] {
    display: none;
}

.tw-palette__backdrop {
    position: absolute;
    inset: 0;
    background: var(--tw-gray-100);
    opacity: 0.28;
}

.tw-palette__box {
    position: relative;
    width: min(640px, 100%);
    max-height: 68vh;
    display: flex;
    flex-direction: column;
    background: var(--tw-bg-primary);
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-lg);
    box-shadow: var(--tw-shadow-strong);
    overflow: hidden;
    /* Sube tres píxeles al abrirse. Es el único movimiento: la caja tiene
       que estar lista para escribir, no terminando una animación. */
    animation: tw-palette-in 0.12s ease-out;
}

@keyframes tw-palette-in {
    from {
        opacity: 0;
        transform: translateY(-3px);
    }
}

@media (prefers-reduced-motion: reduce) {
    .tw-palette__box {
        animation: none;
    }
}

.tw-palette__field {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 12px;
    height: 48px;
    border-bottom: 1px solid var(--tw-border-light);
    flex: 0 0 auto;
}

.tw-palette__field-icon {
    color: var(--tw-font-light);
    font-size: 18px;
}

.tw-palette__input {
    flex: 1;
    border: 0;
    outline: 0;
    background: transparent;
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-lg);
    font-family: var(--tw-font-family);
    padding: 0;
}

.tw-palette__input::placeholder {
    color: var(--tw-font-light);
}

.tw-palette__kbd,
.tw-palette__footer kbd {
    font-family: var(--tw-font-family);
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-light);
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-xs);
    padding: 1px 5px;
    background: transparent;
}

.tw-palette__spinner {
    width: 13px;
    height: 13px;
    border: 2px solid var(--tw-border-strong);
    border-top-color: var(--tw-accent);
    border-radius: 50%;
    animation: tw-palette-spin 0.6s linear infinite;
}

.tw-palette__spinner[hidden] {
    display: none;
}

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

/* ── Resultados ────────────────────────────────────────────────────── */

.tw-palette__results {
    overflow-y: auto;
    padding: 6px;
    flex: 1 1 auto;
}

.tw-palette__group {
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-light);
    padding: 8px 8px 4px;
}

.tw-palette__row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 8px;
    height: 36px;
    border-radius: var(--tw-radius-sm);
    color: var(--tw-font-primary);
    text-decoration: none;
    cursor: pointer;
}

.tw-palette__row:hover {
    color: var(--tw-font-primary);
}

/* Un solo estilo de resaltado, y lo mueve el teclado. El ratón sólo
   traslada el resaltado a la fila que toca, para que no haya nunca dos
   filas compitiendo por parecer la elegida. */
.tw-palette__row.is-active {
    background: var(--tw-bg-tertiary);
}

.tw-palette__row > i {
    color: var(--tw-font-tertiary);
    font-size: 16px;
    flex: 0 0 auto;
}

.tw-palette__text {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
    flex: 1;
}

/* El título ocupa lo que necesite y sólo cede cuando no cabe; el subtítulo
   es el que se recorta primero. Con un ancho fijo, un folio corto dejaba un
   hueco muerto y uno largo se cortaba teniendo sitio de sobra. */
.tw-palette__title {
    flex: 0 1 auto;
    min-width: 0;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tw-palette__sub {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tw-palette__row mark {
    background: transparent;
    color: inherit;
    font-weight: var(--tw-weight-semibold);
    padding: 0;
}

.tw-palette__state {
    flex: 0 0 auto;
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-light);
}

.tw-palette__empty {
    margin: 0;
    padding: 20px 12px;
    text-align: center;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-tertiary);
}

.tw-palette__footer {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 7px 12px;
    border-top: 1px solid var(--tw-border-light);
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-light);
    flex: 0 0 auto;
}

.tw-palette__footer kbd + kbd {
    margin-left: 2px;
}

.tw-palette__footer-hint {
    margin-left: auto;
}

/* El fondo no se desplaza mientras la caja está abierta. */
body.tw-palette-open {
    overflow: hidden;
}

/* =====================================================================
   El mostrador de Calidad · la cola de impresión
   ---------------------------------------------------------------------
   Una pantalla que se mira de lejos. Quien la atiende está de pie, con
   alguien al otro lado del mostrador diciendo un folio en voz alta, así
   que el folio manda sobre todo lo demás: tipografía tabular, tamaño de
   cartel y espacio propio.

   LAS REIMPRESIONES SE VEN DISTINTAS, no marcadas. Un distintivo
   pequeño se lee después de haber impreso, y para entonces la etiqueta
   duplicada ya está pegada en un contenedor. Por eso `--reprint`
   cambia el fondo y el borde de la tarjeta entera.
   ===================================================================== */

/* ── El pulso del mostrador ───────────────────────────────────────────
 *
 * Cuatro cifras en una línea, separadas por la misma hairline que usa el
 * resto del sistema. Sin caja, sin esquinas redondeadas y sin tarjetas: son
 * un encabezado de la tabla que viene debajo, no cuatro objetos aparte.
 */
.tw-counter-health {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 32px;
    padding: 10px var(--tw-screen-gutter, 12px);
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-counter-vital {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.tw-counter-vital-label {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

.tw-counter-vital-value {
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    font-variant-numeric: tabular-nums;
}

.tw-counter-vital-value.is-alert {
    color: var(--tw-tag-red-fg);
}

/* Sin dato no se pinta un guion suelto: se dice qué falta para que lo haya. */
.tw-counter-vital-value.is-empty {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
}

/* ── El mostrador ─────────────────────────────────────────────────────
 *
 * La fila es una **tabla del sistema**, no tarjetas: mismo `table
 * table-vcenter` que Terminales o Usuarios. Se probó con tarjetas y con una
 * franja de color a la izquierda por estado, y las dos cosas estaban mal:
 *
 *   · la rejilla perdía el orden, que en un mostrador es el dato principal, y
 *     con una sola solicitud dejaba una tarjeta flotando en la pantalla;
 *   · la franja de color no es de este sistema. El estado se dice con
 *     `tw-tag`, igual que en todas las demás pantallas.
 *
 * Aquí abajo sólo queda lo que la tabla no cubre: el folio, que se dicta en
 * voz alta y necesita ser monoespaciado y grande, y la espera, que se lee de
 * un vistazo bajando por la columna.
 */
/* La tabla llena el panel: sin esto su barra horizontal se queda justo debajo
   del último renglón, a media pantalla, en vez de al pie del área. */
#cola-lista > .table-responsive {
    height: 100%;
}

/* El folio se dicta en voz alta y se lee desde el otro lado del mostrador.
   Tabular para que no baile al refrescarse la cola. */
.tw-counter-folio {
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    letter-spacing: 0.06em;
    font-variant-numeric: tabular-nums;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    color: var(--tw-font-primary);
    white-space: nowrap;
}

.tw-counter-wait {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--tw-font-secondary);
}

.tw-counter-wait.is-late {
    color: var(--tw-tag-red-fg);
    font-weight: var(--tw-weight-semibold);
}

.tw-counter-actions {
    display: flex;
    gap: 6px;
    justify-content: flex-end;
    white-space: nowrap;
}

/* El motivo de la reimpresión, delante y no en un tooltip: es lo que
   alguien tiene que leer ANTES de autorizar. */
.tw-counter-reason {
    font-size: var(--tw-font-size-sm);
    padding: 8px 10px;
    border-radius: var(--tw-radius-sm, 6px);
    background: var(--tw-bg-primary);
    border: 1px solid var(--tw-border-light);
    color: var(--tw-font-secondary);
}

.tw-counter-reason b {
    color: var(--tw-font-primary);
}

/* =====================================================================
   Documentos y fotos · el mismo bloque en todas las fichas
   ---------------------------------------------------------------------
   Rejilla de miniaturas, no lista de enlaces. Lo que se adjunta en una
   planta son casi siempre fotos —el golpe en la pieza, la placa con su
   colada, el papel de Lantek— y una lista de nombres obliga a abrir
   cinco para encontrar la que se busca.

   La miniatura es la propia URL firmada, que caduca a los cinco
   minutos. Es correcto: preferimos que una ficha abierta media hora
   deje de cargar la imagen a tener enlaces de larga vida a fotos de la
   operación de un cliente.
   ===================================================================== */

.tw-docs-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Envuelve en cuanto no cabe, como el pie del asistente de la estación.
       Sin esto, en una ficha con el panel estrecho —una ventana de 1000 px con
       la barra lateral abierta deja 350 px al panel derecho— el título y los
       botones de subir se empujaban fuera de la tarjeta, que recorta: el botón
       de adjuntar quedaba invisible y no había barra para llegar a él. */
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 10px;
}

.tw-docs-actions {
    display: flex;
    gap: 6px;
}

.tw-docs-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(160px, 100%), 1fr));
    gap: 10px;
}

.tw-doc {
    position: relative;
    margin: 0;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    overflow: hidden;
    background: var(--tw-bg-secondary);
}

.tw-doc-face {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 4 / 3;
    background: var(--tw-bg-tertiary);
    color: var(--tw-font-light);
    text-decoration: none;
}

.tw-doc-face img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.tw-doc-face .ti {
    font-size: 2rem;
}

/* Sin enlace firmado la cara no se puede abrir, y se dice con el cursor
   en vez de dejar un enlace que no lleva a ninguna parte. */
.tw-doc-face[data-sin-enlace] {
    cursor: not-allowed;
    opacity: 0.6;
}

.tw-doc-meta {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 10px;
    font-size: var(--tw-font-size-xs);
}

.tw-doc-name {
    color: var(--tw-font-primary);
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-doc-del {
    position: absolute;
    top: 6px;
    right: 6px;
    border: none;
    border-radius: var(--tw-radius-sm);
    padding: 4px 6px;
    line-height: 1;
    background: var(--tw-bg-primary);
    color: var(--tw-tag-red-fg);
    opacity: 0;
    transition: opacity 120ms ease;
}

.tw-doc:hover .tw-doc-del,
.tw-doc-del:focus-visible {
    opacity: 1;
}

/* En táctil no hay hover, así que el botón vive visible. Ocultarlo ahí
   lo haría inalcanzable, que es peor que un icono de más. */
@media (hover: none) {
    .tw-doc-del {
        opacity: 1;
    }
}

/* =====================================================================
   El armazón en un teléfono
   ---------------------------------------------------------------------
   El kiosco de piso ya es táctil por diseño —`station.css` se escribió
   para guantes y lectores—, pero el panel no era usable en un teléfono:
   la barra lateral empujaba el contenido hacia abajo al abrirse, las
   barras superiores no se apilaban y una tabla de nueve columnas era
   una tira horizontal.

   Se resuelve **una vez aquí**, sobre las clases que ya usan las
   veinte pantallas, y no pantalla por pantalla. Es la condición para
   que la pantalla que se escriba mañana también funcione: nadie se va
   a acordar de hacerla responsiva.

   Tres cortes y cada uno responde a algo distinto:

     · 991.98 px — donde Tabler colapsa la barra lateral. Aquí pasa de
       plegado a cajón.
     · 767.98 px — donde una tabla deja de caber. Filas a tarjetas y
       formularios a una columna.
     · (pointer: coarse) — no es un ancho: es un dedo. Alturas de 44 px
       y diálogos como hoja inferior.
   ===================================================================== */

/* ── El cajón ────────────────────────────────────────────────────────
 *
 * Tabler colapsa el menú **en el flujo**: al abrirlo, el contenido de la
 * página baja doscientos píxeles y hay que cerrarlo para volver a ver
 * dónde se estaba. En un teléfono eso convierte cada navegación en dos
 * desplazamientos.
 *
 * Aquí el mismo `.collapse.show` se saca del flujo y se convierte en un
 * panel que entra por la izquierda sobre el contenido. Sin JavaScript
 * nuevo: el botón, el estado y la accesibilidad los sigue llevando el
 * `data-bs-toggle="collapse"` que ya está en la plantilla. */
@media (max-width: 991.98px) {
    /* Lo primero, y es lo que de verdad rompía la pantalla: el sidebar
       conserva sus 236 px y `.page-wrapper` su margen izquierdo del
       mismo tamaño **también plegado**. En 375 px eso deja 139 px de
       contenido —la tabla queda en una columna de dos dedos— y no es un
       defecto de Tabler sino de la regla que corrige su desfase de 4 px.
       Plegado, la barra es sólo su cabecera: ni ocupa ancho ni desplaza. */
    .navbar-vertical.navbar-expand-lg {
        width: 100%;
    }

    .navbar-vertical.navbar-expand-lg ~ .page-wrapper,
    .navbar-vertical.navbar-expand-lg + .page-wrapper {
        margin-left: 0;
    }

    .navbar-vertical.navbar-expand-lg > .container-fluid > .navbar-collapse {
        position: fixed;
        inset: 3rem auto 0 0;
        width: min(19rem, 85vw);
        max-width: 85vw;
        z-index: 1035;
        overflow-y: auto;
        overscroll-behavior: contain;
        background: var(--tw-bg-secondary);
        border-right: 1px solid var(--tw-border-light);
        box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
        /* Sin aire arriba: lo pone el buscador, que aquí también se queda
           pegado al borde. Con el relleno del cajón, esos ocho píxeles son
           parte de lo que se desplaza y un renglón se asomaba por encima del
           buscador antes de meterse debajo. */
        padding: 0 8px 24px;
    }

    /* El velo. Sale del propio panel abierto —`::after` sobre un
       elemento fijo— para no depender de un nodo que habría que crear y
       destruir desde JavaScript. Cierra al tocarlo porque el `collapse`
       de Bootstrap escucha clics sobre cualquier disparador, y el propio
       menú lleva enlaces que ya lo cierran al navegar. */
    .navbar-vertical.navbar-expand-lg > .container-fluid > .navbar-collapse.show::after {
        content: '';
        position: fixed;
        inset: 0 0 0 min(19rem, 85vw);
        background: rgb(0 0 0 / 35%);
        z-index: -1;
    }

    /* La barra superior de cada pantalla se apila. Con el título y tres
       filtros en la misma línea, en 390 px el buscador acaba de sesenta
       píxeles y el botón primario se sale. */
    .tw-topbar {
        flex-wrap: wrap;
        row-gap: 8px;
    }

    .tw-topbar > * {
        min-width: 0;
    }
}

@media (max-width: 767.98px) {
    /* Los campos de la barra superior dejan de llevar ancho fijo: casi
       todos lo traen puesto en el `style` —`width: 22rem`— y en 390 px
       eso desborda. `!important` porque compite con un atributo.
       No basta con el ancho: el campo y el botón viven en un `d-flex`
       que no envuelve, así que hay que dejarlo envolver y que el campo
       ocupe su propio renglón. Si no, «100 %» son los 164 px que le
       tocan al repartir, y el icono de la lupa acaba encima del texto. */
    .tw-topbar > div {
        width: 100%;
        flex-wrap: wrap;
    }

    .tw-topbar .input-icon {
        flex: 1 1 100%;
    }

    .tw-topbar input[type='text'],
    .tw-topbar .form-control,
    .tw-topbar .form-select,
    .tw-topbar .input-icon {
        width: 100% !important;
    }

    /* Y el valor de la celda puede partirse. Un número de parte largo o
       una razón social entera no caben en 375 px al lado de su rótulo, y
       sin esto se recortan sin decirlo. */
    table.dataTable tbody td,
    table.tw-table-cards tbody td {
        overflow-wrap: anywhere;
        text-align: right;
    }

    table.dataTable tbody td::before,
    table.tw-table-cards tbody td::before {
        text-align: left;
    }

    .tw-page-title {
        font-size: 1.05rem;
    }

    /* Con el botón de plegar delante, el rastro de migas comparte renglón
       con él en vez de bajarse entero.

       La barra envuelve, y un título que mide lo que mide su texto —dos
       líneas de breadcrumb más la etiqueta de estado— no cabe al lado de
       nada: se iba al renglón de abajo y el botón se quedaba solo arriba,
       ocupando una franja de veintiocho píxeles para un icono. Con la base
       en cero comparte la línea y es el texto el que parte, que es como se
       lee un breadcrumb largo en un teléfono. */
    .tw-ficha-plegar + .tw-page-title {
        flex: 1 1 0;
    }

    /* Los botones de la barra superior, según lo que sean.
     *
     * **Con texto** —«Nuevo nesteo», «Registrar recepción»— ocupan el
     * ancho. Es la acción principal de la pantalla y en un teléfono se
     * toca con el pulgar; alineada a la derecha y de 110 px queda
     * colgando en una esquina y parece un resto de la maquetación de
     * escritorio.
     *
     * **Sólo icono** —refrescar— se quedan pegados al borde y no
     * ocupan el ancho: un botón de 44 px estirado a 375 no dice qué
     * hace, y su sitio natural es la esquina donde ya estaba. */
    .tw-topbar > div > .btn {
        flex: 1 1 100%;
        justify-content: center;
    }

    .tw-topbar > div > .btn[aria-label]:has(> .ti:only-child),
    .tw-topbar > div > .btn-icon {
        flex: 0 0 auto;
        margin-left: auto;
    }

    /* ── Una barra de atajos, no de acciones ─────────────────────────
     *
     * La regla de arriba da el ancho al botón porque en la mayoría de las
     * pantallas hay uno y es *la* acción: «Nuevo nesteo», «Registrar
     * recepción».
     *
     * Las pantallas de consulta no tienen acción principal: lo que llevan
     * arriba son atajos —«Ver la orden de cliente», «Ver en inventario»,
     * «Imprimir»— y son dos o tres. A ancho completo se comen ciento
     * cincuenta píxeles de la primera pantalla para ofrecer irse a otro
     * sitio, justo antes de enseñar aquello por lo que se entró.
     * Compartiendo renglón siguen siendo tocables y ocupan un tercio.
     *
     * Se pide desde la plantilla: la diferencia entre un atajo y una
     * acción la sabe la pantalla. */
     * Repartir el renglón a la mitad **no** funciona: «Ver la orden de
     * cliente» en 168 px se parte en dos líneas y los dos botones quedan
     * amontonados, que es peor que apilados. En un renglón de 343 px sólo
     * cabe un rótulo entero.
     *
     * Así que cabe uno: el atajo que hay que poder leer se queda con el
     * ancho que sobra, y el que se reconoce por su icono —imprimir— baja a
     * icono solo envolviendo su rótulo en `tw-btn-label`. Cuál es cuál lo
     * decide la plantilla, botón por botón. */
    .tw-topbar > div.tw-actions-inline {
        flex-wrap: nowrap;
    }

    .tw-topbar > div.tw-actions-inline > .btn {
        flex: 1 1 auto;
        min-width: 0;
        justify-content: center;
        padding-inline: 10px;
        overflow: hidden;
        white-space: nowrap;
    }

    /* El de icono solo: cuadrado, del tamaño del dedo y sin estirarse. */
    .tw-topbar > div.tw-actions-inline > .btn:has(> .tw-btn-label) {
        flex: 0 0 auto;
        width: 44px;
        padding-inline: 0;
    }

    .tw-topbar > div.tw-actions-inline > .btn > .tw-btn-label {
        display: none;
    }

    /* El icono no se encoge cuando el rótulo se queda sin sitio. */
    .tw-topbar > div.tw-actions-inline > .btn > .ti {
        flex: 0 0 auto;
    }

    /* El texto del campo no pasa por debajo de la lupa. Al forzar el
       ancho al 100 % se pierde el hueco que Tabler le reserva, y el
       marcador de posición acaba tapado por el icono. */
    .tw-topbar .input-icon .form-control,
    .tw-topbar .input-icon input[type='text'] {
        /* `!important` por lo mismo que el ancho de aquí arriba: compite
           con el relleno que Tabler pone en el propio `.form-control`, y
           sin él el marcador de posición pasa por debajo de la lupa. */
        padding-right: 2.5rem !important;
    }

    /* Y «actualizado hace 16 s» se va. Dice lo mismo que el distintivo
       «En vivo» que tiene al lado, y entre los dos no dejan sitio en
       375 px para nada más. En escritorio caben y los dos aportan: uno
       es el estado y el otro el detalle. */
    #dash-updated {
        display: none;
    }

    /* ── Cada fila, una tarjeta ──────────────────────────────────────
     *
     * El rótulo sale de `data-label`, que estampa `table-defaults.js` en
     * cada celda al dibujar. Es lo que permite que esto funcione en las
     * veinte tablas sin tocar ninguna.
     *
     * El encabezado se oculta: sus textos ya viajan dentro de cada
     * celda, y repetirlos arriba sería leerlos dos veces. */
    .dataTables_scrollHead,
    .dt-scroll-head {
        display: none;
    }

    table.dataTable,
    table.dataTable thead,
    table.dataTable tbody,
    table.dataTable tr,
    table.dataTable td,
    table.tw-table-cards,
    table.tw-table-cards thead,
    table.tw-table-cards tbody,
    table.tw-table-cards tr,
    table.tw-table-cards td {
        display: block;
        width: auto !important;
    }

    table.dataTable thead,
    table.tw-table-cards thead {
        display: none;
    }

    table.dataTable tbody tr,
    table.tw-table-cards tbody tr {
        /* Sin margen lateral: la tarjeta se alinea con el título de su
           sección y con todo lo demás del cuerpo. Metida ocho píxeles
           —como estuvo un rato— el título «A qué clientes fue a parar»
           quedaba desalineado del borde de la tarjeta que encabeza, y en
           una pantalla estrecha ese desfase de ocho píxeles se lee como
           un descuido. El aire lateral lo pone el cuerpo, que es de quien
           es. */
        margin: 0 0 8px;
        padding: 10px 12px;
        border: 1px solid var(--tw-border-light);
        border-radius: var(--tw-radius-md);
        background: var(--tw-bg-secondary);
    }

    table.dataTable tbody td,
    table.tw-table-cards tbody td {
        /* El alto de fila de la tabla —32 px, pensado para una rejilla—
           sigue puesto y recorta cualquier control más alto que él. En
           tarjeta la celda mide lo que mide su contenido. */
        height: auto !important;
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        /* El hueco entre rótulo y valor, y **casi ninguno** entre las dos
           líneas de un mismo valor: `gap` a secas los pone iguales, y esos
           dieciséis píxeles separaban «KM50001335H02» de «KONE INDUSTRIAL»
           más que del renglón siguiente, que es leerlo al revés. */
        column-gap: 16px;
        row-gap: 2px;
        border: none !important;
        /* Seis y no tres. Una tarjeta de listado son ocho o diez renglones,
           y a tres píxeles quedaban a diecinueve de altura cada uno: un
           bloque de texto sin respiración en el que hay que buscar el dato
           en vez de leerlo. Con seis, el renglón mide veinticinco y la
           tarjeta se recorre de un vistazo. */
        padding: 6px 0 !important;
        white-space: normal !important;

        /* Y la celda deja de pintar su propio fondo.
         *
         * Tabler pinta **cada celda** —`background-color: var(--tblr-table-bg)`
         * más un `box-shadow` interior de 9999 px para el rayado y el
         * apuntado—. En una tabla eso es invisible: la celda llena su
         * columna. En tarjeta la celda mide lo que mide su contenido y sin
         * relleno lateral, así que ese fondo se dibujaba como un rectángulo
         * gris más claro **dentro** de la tarjeta, con el texto pegado a sus
         * dos cantos. El relleno de la tarjeta quedaba fuera de él y no
         * separaba nada.
         *
         * Quien pone el fondo es la fila, que es la tarjeta. */
        background-color: transparent !important;
        box-shadow: none !important;
    }

    table.dataTable tbody td[data-label]::before,
    table.tw-table-cards tbody td[data-label]::before {
        content: attr(data-label);
        flex: 0 0 auto;
        color: var(--tw-font-light);
        font-size: var(--tw-font-size-xs);
        text-transform: uppercase;
        letter-spacing: 0.04em;
    }

    /* El segundo renglón de un valor sigue siendo ese valor.
     *
     * Muchas celdas traen dos líneas: el número de parte y su cliente, el
     * folio y su contenido, la placa y su colada. Como el rótulo y el valor
     * son dos elementos de un `flex` que envuelve, esa segunda línea caía a
     * un renglón propio **alineada a la izquierda**, justo debajo del
     * rótulo de arriba: «KONE INDUSTRIAL» se leía como un dato suelto sin
     * rótulo, y no como de quién es el número de parte de la línea
     * anterior.
     *
     * Ocupando el renglón entero y alineada a la derecha se lee donde le
     * toca: bajo su valor, en la columna de los valores.
     *
     * Menos en la primera celda, que es el título de la tarjeta: ahí la
     * segunda línea es su subtítulo, se apila a la izquierda y no tiene
     * ninguna columna de valores con la que alinearse. */
    table.dataTable tbody td:not(:first-child) > .tw-meta,
    table.tw-table-cards tbody td:not(:first-child) > .tw-meta,
    table.dataTable tbody td:not(:first-child) > .small,
    table.tw-table-cards tbody td:not(:first-child) > .small {
        flex: 1 0 100%;
        text-align: right;
    }

    /* Y lo mismo cuando esa línea de más no es un `.small`.
     *
     * La regla de arriba sólo alcanzaba a las líneas secundarias, y hay
     * celdas que traen **dos líneas principales**: quién recibió un paso y
     * quién lo cerró son dos nombres, cada uno con su hora debajo. El
     * segundo caía a la izquierda, pegado al rótulo de la celda, y se leía
     * como si fuera el dato de otro renglón.
     *
     * El primer `div` no entra: ése es el que se alinea con el rótulo, que
     * es justo el par que forma la tarjeta. */
    table.dataTable tbody td:not(:first-child) > div:not(:first-child),
    table.tw-table-cards tbody td:not(:first-child) > div:not(:first-child) {
        flex: 1 0 100%;
        text-align: right;
    }

    /* La primera celda es el nombre de la fila: se lee como título de la
       tarjeta, sin rótulo delante y con más peso. Y con una línea debajo:
       es el único renglón de la tarjeta que no es un par de rótulo y
       valor, así que separarlo del resto es lo que lo convierte en título
       en vez de en el primero de diez renglones iguales. */
    table.dataTable tbody td:first-child,
    table.tw-table-cards tbody td:first-child {
        font-weight: 600;
        font-size: var(--tw-font-size-md, 0.875rem);
        padding-bottom: 8px !important;
        margin-bottom: 4px;
        border-bottom: 1px solid var(--tw-border-light) !important;
    }

    table.dataTable tbody td:first-child::before,
    table.tw-table-cards tbody td:first-child::before {
        display: none;
    }

    /* Y lo que traiga dentro se apila.
     *
     * La celda de identidad suele llevar dos renglones —el nombre y su
     * descripción, «Corte láser» y «cuenta al recibir · registra salida»—
     * escritos como dos `<div>`. Al convertir la celda en un `flex` de
     * rótulo y valor, esos dos `<div>` se ponían **uno al lado del otro**
     * repartidos a los extremos, y el segundo se salía de la tarjeta por
     * la derecha. Son dos líneas de un título, no un rótulo y su valor. */
    table.dataTable tbody td:first-child,
    table.tw-table-cards tbody td:first-child {
        flex-direction: column;
        align-items: flex-start;
        text-align: left;
    }

    /* La celda de acciones ocupa el renglón entero y los botones se
       reparten: dos botones alineados a la derecha en 375 px dejan el
       texto en dos letras. */
    table.tw-table-cards tbody td[data-acciones] {
        /* `baseline` es lo correcto para rótulo y valor —dos textos— y lo
           equivocado para un botón: con la altura mínima táctil de 38 px,
           alinearlo por su línea base lo saca de la caja de la celda y el
           texto queda cortado por el borde de la tarjeta. */
        align-items: center;
        justify-content: stretch;
        gap: 6px;
        padding-top: 8px !important;
        border-top: 1px solid var(--tw-border-light) !important;
        margin-top: 4px;
    }

    table.tw-table-cards tbody td[data-acciones] .btn {
        flex: 1 1 auto;
    }

    /* Una celda cuyo valor son dos renglones —número de parte y cliente,
       folio y contenido— no cabe al lado de su rótulo. Se deja envolver:
       el rótulo arriba y el valor debajo, en vez de recortar los dos. */
    table.dataTable tbody td,
    table.tw-table-cards tbody td {
        flex-wrap: wrap;
    }

    table.dataTable tbody td > *,
    table.tw-table-cards tbody td > * {
        min-width: 0;
        max-width: 100%;
    }

    /* El envoltorio de scroll horizontal sobra cuando ya no hay tabla que
       desplazar, y con él la tarjeta quedaba cortada dentro de un segundo
       scroll de 322 px que nadie espera encontrar. */
    .table-responsive:has(> table.tw-table-cards),
    .tw-table-scroll:has(> table.tw-table-cards),
    .table-responsive:has(> table.dataTable) {
        overflow: visible;
    }

    /* El alto calculado de DataTables sobra: las tarjetas miden lo que
       miden y el cuerpo scrollea con la página. */
    .dataTables_scrollBody,
    .dt-scroll-body {
        max-height: none !important;
        height: auto !important;
    }

    /* El relleno del cuerpo de una tarjeta, a la mitad.
     *
     * `p-4` son 24 px por lado, escritos para 1280 px de ancho. En 375 px
     * son 48 px de los 359 que mide la tarjeta: uno de cada siete píxeles
     * de la pantalla, gastado en margen dos veces —el de la tarjeta contra
     * el filo y el del cuerpo contra la tarjeta—. Doce basta para que el
     * texto no se lea contra el canto.
     *
     * `!important` porque `p-4` es una utilidad de Bootstrap y lo lleva. */
    .tw-panel-body.p-4,
    .tw-record-content {
        --tw-record-content-gutter: 12px;
        padding-inline: 12px !important;
    }

    /* Y el cuerpo que no lleva relleno ninguno, sí lo lleva.
     *
     * El cuerpo pelado es el que envuelve una tabla: en escritorio no debe
     * llevar relleno porque la tabla trae el suyo y quien recorta es él.
     * Pero por debajo de 768 px esa tabla es una pila de tarjetas, y sin
     * relleno cada tarjeta quedaba con su borde justo encima del borde de
     * la tarjeta grande: dos líneas pegadas y ni un píxel de aire. */
    .tw-panel-body:not(.overflow-auto) {
        padding-inline: 8px;
    }

    /* Aire bajo el título de sección.
     *
     * Nace con `margin: 0` porque en escritorio lo que va debajo es el
     * encabezado de una tabla, que ya separa. En tarjeta no hay encabezado,
     * así que el título quedaba pegado al borde de la primera tarjeta y las
     * dos cosas se leían como un solo bloque. */
    .tw-section-title {
        margin-bottom: 8px;
    }

    /* El estado vacío deja de ocupar una pantalla.
     *
     * 64 px arriba y abajo centran el mensaje en el hueco de una tabla de
     * escritorio. En un teléfono no hay hueco que centrar: son 128 px de
     * aire alrededor de tres renglones, y en el escáner —donde el estado
     * vacío es lo que hay debajo del visor hasta que se lee algo— empujaba
     * fuera de cuadro lo poco que quedaba. */
    .tw-empty-state {
        padding: 28px 12px;
    }

    /* Formularios a una columna. Las rejillas de dos y tres campos por
       fila son legibles en 1280 px y ridículas en 390. */
    .tw-field-row,
    .tw-field-row--three,
    .st-cols--2,
    .st-cols--5-7 {
        grid-template-columns: 1fr;
    }

    .tw-counter-grid,
    .tw-docs-grid {
        grid-template-columns: 1fr;
    }

    /* ── Lo que no cabe y no debe partirse ───────────────────────────
     *
     * Dos casos distintos y la misma solución: se desplazan **dentro de
     * su tarjeta** en vez de estirar la página entera. Un desplazamiento
     * lateral de toda la pantalla es lo que hace que un teléfono se
     * sienta roto, aunque el contenido esté ahí. */

    /* La tabla del tablero es una vista previa densa —ocho columnas de
       números— y en tarjeta no se leería mejor: se lee moviéndola.
       Una que sí pide tarjeta lo dice con `tw-table-cards`, y entonces
       esto se aparta: las dos reglas se contradicen —desplazar de lado
       frente a partir en tarjetas— y la de aquí gana por venir después. */
    .tw-dash-wide .tw-panel-body,
    .tw-dash-scroll,
    .tw-dash-table:not(.tw-table-cards) {
        overflow-x: auto;
    }

    .tw-dash-table:not(.tw-table-cards) {
        display: block;
        white-space: nowrap;
    }

    /* La fila de agregados —«Pallets abiertos · Cerrados · Liberados ·
       Embarcados»— envuelve. Sus renglones no tienen relación entre sí,
       así que apilarlos no pierde nada; desplazarlos, en cambio,
       escondería los últimos sin señal de que están. */
    .tw-agg-list {
        flex-wrap: wrap;
        gap: 6px 14px;
    }

    /* ── Las barras de herramientas de dentro ────────────────────────
     *
     * Las fichas llevan filas de acciones montadas con `d-flex` y sin
     * envolver, con un campo de ancho fijo dentro —«Buscar No. Parte» a
     * 250 px— junto a dos o tres botones. En escritorio caben; en 375 px
     * la fila entera se sale por la derecha.
     *
     * Se acota a los cuerpos de ficha y a las barras de vista, y no a
     * `.d-flex` en general: esa clase la usan cientos de sitios, muchos
     * de ellos filas de dos elementos donde envolver no arregla nada y
     * sí desordena. */
    .tw-record-main .d-flex,
    .tw-panel-body > .d-flex,
    .tw-view-bar,
    .tw-view-bar .d-flex {
        flex-wrap: wrap;
    }

    /* Y los campos que traen el ancho puesto en el `style`, igual que en
       la barra superior: el atributo gana a la hoja, así que hace falta
       `!important` para que quepan. */
    .tw-record-main .input-icon,
    .tw-panel-body .input-icon,
    .tw-view-bar .input-icon {
        width: 100% !important;
    }

    .tw-record-main .input-icon .form-control,
    .tw-panel-body .input-icon .form-control,
    .tw-view-bar .input-icon .form-control {
        width: 100% !important;
        padding-right: 2.5rem !important;
    }

    /* Un diálogo a pantalla completa por abajo: es donde llega el pulgar.
       Centrado, un modal pequeño queda a la altura de la ceja y hay que
       cambiar de mano para tocar el botón. */
    .modal-dialog {
        margin: 0;
        position: absolute;
        inset: auto 0 0 0;
        max-width: none;
    }

    .modal-dialog .modal-content {
        border-radius: var(--tw-radius-md) var(--tw-radius-md) 0 0;
        max-height: 92vh;
    }

    .modal-dialog .modal-body {
        overflow-y: auto;
    }

    /* Los botones del pie, a lo ancho y en el orden de importancia: el
       primario arriba, donde no hay que estirar el dedo. */
    .modal-footer {
        flex-direction: column-reverse;
        align-items: stretch;
        gap: 6px;
    }

    .modal-footer > .btn {
        width: 100%;
        margin: 0;
    }
}

/* ── El dedo ─────────────────────────────────────────────────────────
 *
 * No es cuestión de ancho sino de puntero: una tablet de 1024 px en
 * horizontal se toca igual que un teléfono, y una ventana de navegador
 * estrecha en un portátil se usa con ratón. 44 px es el mínimo de las
 * guías de accesibilidad de las dos plataformas.
 *
 * `hover: none` acompaña a `pointer: coarse` para no alcanzar a los
 * lápices ni a los mandos, que apuntan fino. */
@media (pointer: coarse) and (hover: none) {
    .btn,
    .form-control,
    .form-select,
    .nav-link {
        min-height: 44px;
    }

    /* ── Anclar, con el dedo ───────────────────────────────────────────
     *
     * Aquí **no hay hover**: es la condición de este bloque. El botón que
     * en un escritorio aparece al pasar por encima del renglón no aparece
     * nunca, así que en la tablet apaisada con la que se usa el panel no
     * había forma de subirse una pantalla ni de bajarla.
     *
     * Se queda puesto, encima del renglón como siempre —sacarlo al flujo
     * obliga a convertir el `li` en una caja flexible y a pelearse con el
     * `display` que Bootstrap le da al renglón del menú, para ganar veinte
     * píxeles que no hacían falta—. Lo que se mueve es el número, que sin
     * hover no puede apartarse solo: se corre a la izquierda del botón y
     * los dos se leen a la vez. Las seis insignias son lo único que
     * comparte ese rincón; los otros veintiséis renglones ni se enteran. */
    .tw-nav-anclar {
        display: flex;
        /* Treinta pines puestos a la vez compiten con los rótulos, que es lo
           que hay que leer. Tenues se encuentran igual cuando se buscan —el
           dedo va al renglón, no al pin— y desaparecen cuando no. Los puestos
           van al color de siempre: ésos sí explican algo. */
        opacity: 0.35;
    }

    .tw-nav-anclar.is-anclado {
        opacity: 1;
    }

    .navbar-vertical .navbar-nav .nav-link .tw-nav-badge {
        margin-right: 18px;
    }


    /* ── Y el de una barra, 38 ────────────────────────────────────────
     *
     * Una barra de vista mide 40 px de alto y una barra de pantalla los
     * mide clavados: un botón de 44 dentro no cabe, así que ahí el dedo
     * se conforma con 38, que sigue siendo tocable.
     *
     * LO DECIDE DÓNDE ESTÁ Y NO LA CLASE QUE LLEVE. Antes esto era
     * `.btn-sm` a secas, y como en el escritorio esa clase no cambiaba
     * nada, nadie se acordaba de escribirla: una barra con un `btn-sm` al
     * lado de uno sin él se veía igual en el monitor y salía escalonada
     * —38 contra 44— sólo en la tablet, que es donde no se está mirando
     * al escribirlo. Pasó en ocho barras. Ahora el sitio manda; la clase
     * se respeta donde esté escrita, pero ya no hace falta. */
    .tw-view-bar .btn,
    .tw-topbar .btn,
    .tw-scope .btn,
    .btn-sm {
        min-height: 38px;
    }

    /* Un botón que sólo lleva icono tiene que ser **cuadrado**.
       `min-height` a secas lo estira a lo alto y le deja el ancho del
       icono: 32 × 44, un dominó al lado de un texto de doce píxeles. Es
       lo que se veía en el botón de refrescar del tablero.
     *
     * SE RECONOCE POR `aria-label` Y NO POR LA FORMA DEL DOM. La vía
     * aparentemente obvia —`:has(> .ti:only-child)`— es una trampa:
     * `:only-child` mira **elementos** y no cuenta los nodos de texto,
     * así que «+ Nuevo nesteo», que es un icono seguido de una palabra,
     * también casa. Un botón sin texto está obligado a llevar
     * `aria-label` para tener nombre accesible, así que la marca ya
     * existe, es obligatoria y significa exactamente esto. */
    .btn[aria-label]:has(> .ti:only-child),
    .btn-icon {
        min-width: 44px;
        padding-left: 0;
        padding-right: 0;
    }

    .tw-view-bar .btn[aria-label]:has(> .ti:only-child),
    .tw-view-bar .btn-icon,
    .tw-topbar .btn[aria-label]:has(> .ti:only-child),
    .tw-topbar .btn-icon,
    .tw-scope .btn[aria-label]:has(> .ti:only-child),
    .tw-scope .btn-icon,
    .btn-sm[aria-label]:has(> .ti:only-child),
    .btn-sm.btn-icon {
        min-width: 38px;
    }

    /* ── Los 16 píxeles de iOS ───────────────────────────────────────
     *
     * Safari acerca la pantalla al enfocar un campo que mida menos de 16
     * px, y al salir del campo **no la devuelve**: el resto de la
     * pantalla se queda a medio salir del encuadre y hay que pellizcar
     * para volver. No es una preferencia de tamaño, es el número exacto a
     * partir del cual eso deja de pasar.
     *
     * ESTO YA ESTABA Y NO SERVÍA, por dos motivos que se arreglan aquí:
     *
     *   · **Perdía la pelea.** La regla de formularios de más arriba pone
     *     `font-size: var(--tw-font-size-md) !important` —13 px— sobre
     *     `.form-control`, y una declaración normal no gana a una
     *     `!important` por mucho que vaya en una consulta de medios y por
     *     mucho que esté después. Así que ésta también lo lleva.
     *   · **Nombraba tres tipos de campo.** `text`, `number` y `password`,
     *     y quedaban fuera el correo del acceso, los teléfonos, las
     *     búsquedas de las tablas, las fechas y —sobre todo— la casilla de
     *     escritura de **todos los desplegables**, que desde que ninguno
     *     es nativo son un `<input>` dentro de `.ts-control`. Ahora se
     *     nombra lo que se escribe y se excluye lo que no: una casilla de
     *     verificación o un color no tienen texto que agrandar.
     *
     * Los campos del acceso —`.login-control input`, con su tamaño puesto
     * en la propia pantalla— entran por el selector de `input`, que aquí
     * gana por `!important`. */
    input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='file']):not([type='color']):not([type='submit']):not([type='button']):not([type='reset']),
    select,
    textarea,
    .form-control,
    .form-select,
    .st-input,
    .st-select,
    .ts-control,
    .ts-control input,
    .ts-dropdown,
    .ts-dropdown .option,
    .ts-dropdown .create,
    .ts-dropdown .no-results {
        font-size: 16px !important;
    }
}

/* =====================================================================
   El escáner, a un pulgar · botón flotante en móvil y tablet
   ---------------------------------------------------------------------
   En una computadora el escáner es una entrada del menú, que está
   siempre a la vista. En un teléfono el menú es un cajón: dos toques
   para llegar a lo que se hace con las manos ocupadas —una etiqueta en
   una mano, el teléfono en la otra—.

   Va abajo a la derecha porque ahí llega el pulgar de quien sostiene el
   teléfono con una mano, y respeta la franja segura de los teléfonos
   con gesto de inicio: sin `safe-area-inset-bottom` el botón queda
   debajo de la barra del sistema y se toca la barra en vez del botón.
   ===================================================================== */

.tw-fab-scan {
    display: none;
}

@media (max-width: 991.98px) {
    .tw-fab-scan {
        position: fixed;
        right: 16px;
        bottom: calc(16px + env(safe-area-inset-bottom, 0px));
        z-index: 1030;

        display: flex;
        align-items: center;
        justify-content: center;
        width: 56px;
        height: 56px;
        border-radius: 50%;

        /* Sólido y de alto contraste: es lo único de la pantalla que se
           busca sin mirar, y sobre una tabla llena de texto un botón
           translúcido desaparece. */
        background: var(--tw-font-primary);
        color: var(--tw-bg-primary);
        box-shadow: 0 6px 18px rgb(0 0 0 / 30%);
        text-decoration: none;
    }

    .tw-fab-scan .ti {
        font-size: 1.6rem;
    }

    .tw-fab-scan:active {
        transform: scale(0.94);
    }

    /* Con un diálogo abierto se aparta: flota por encima de todo y sobre
       una hoja inferior taparía su botón de confirmar, que es
       exactamente el que hay que tocar. */
    body.modal-open .tw-fab-scan {
        display: none;
    }

    /* Y le hace sitio al final del recorrido.
     *
     * Flota sobre el contenido, así que al llegar al final de la página se
     * queda encima del último renglón: en la trazabilidad tapaba el
     * embarque de la última pieza de la lista, que es un dato, no un
     * margen. Con el hueco reservado, el desplazamiento llega un poco más
     * abajo y nada acaba debajo del botón.
     *
     * `:has()` para no reservarlo donde el botón no sale —el escáner, o
     * quien no tiene permiso de consulta—: ahí serían 72 px de nada. */
    body:has(.tw-fab-scan) .page-wrapper {
        padding-bottom: calc(72px + env(safe-area-inset-bottom, 0px));
    }
}

/* El bloque de documentos dentro de un panel a pantalla completa.
 *
 * `.tw-panel-body` no lleva relleno a propósito —lo normal ahí dentro es una
 * tabla, que trae el suyo—, así que el bloque de documentos quedaba pegado a
 * los dos bordes: el título contra el filo izquierdo y los botones contra el
 * derecho. Se le da el mismo relleno que `.tw-record-content`, que es donde ya
 * vivía en las fichas que lo tenían, para que se vea igual en las dos. */
.tw-panel-body .tw-docs {
    padding: 16px 24px 24px;
}

/* El estado vacío ocupa la rejilla entera.
 *
 * Caía en **una celda** de `auto-fill minmax(160px, 1fr)`, así que «Nada adjunto
 * todavía» se partía en tres renglones de doscientos píxeles en una pestaña de
 * mil seiscientos. No era el texto: era que ocupaba el hueco de una miniatura.
 *
 * Lo demás —icono, título, texto centrado— lo pone `tw-empty-state`, que es la
 * forma del sistema para esto. Esta clase ya no trae relleno propio: el que
 * tenía pisaba el de aquélla y dejaba el estado vacío pegado al título. */
.tw-docs-empty {
    grid-column: 1 / -1;
}

/*
   Casillas en columna, cada una con su explicación debajo.

   En rejilla de dos y con la pista sólo en algunas, las filas quedaban de altos
   distintos y las que no tenían pista parecían de otra categoría. Cuando las
   casillas son decisiones del mismo tipo, se leen de corrido.
*/
.tw-check-list {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.tw-check-list .form-check {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: start;
    column-gap: 10px;
    row-gap: 2px;
    margin: 0;
    padding: 0;
}

.tw-check-list .form-check-input {
    margin: 2px 0 0;
}

/* La explicación arranca alineada con la etiqueta, no con la casilla. */
.tw-check-list .form-hint {
    grid-column: 2;
    margin: 0;
}

/* Dos listas de casillas lado a lado cuando hay sitio.
 *
 * Nueve casillas con su explicación son setecientos píxeles de alto, y el
 * formulario que las lleva —el del proceso— tiene debajo lo que verá el
 * operador, que es justo lo que hay que mirar mientras se marcan. Bajan a una
 * sola columna por debajo de dos de 18rem, y con una lista escondida entera
 * —las que sólo valen para un proceso interno— la que queda toma el ancho. */
.tw-check-cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: 14px 24px;
}

/* ── Elegir qué se embarca ─────────────────────────────────────────────
 *
 * Una lista para escoger de entre muchos, no una tabla para leer. La tabla que
 * había —una fila por renglón disponible, quinientas de ellas— obligaba a
 * recorrerla entera para encontrar un número de parte que aparecía once veces.
 * Aquí manda el número de parte: el grupo es la unidad, y sus órdenes se abren
 * sólo cuando hay más de una que repartir.
 *
 * Vive en `picker.js` y se usa en el alta del embarque y en su ficha.
 */

.tw-pick {
    display: flex;
    flex-direction: column;
    min-height: 0;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    overflow: hidden;
}

.tw-pick-bar {
    display: flex;
    align-items: center;
    /* Envuelve porque lleva tres cosas —buscar, cómo se agrupa y la cuenta— y
       en la ficha el selector vive en una columna, no en un modal ancho. Lo que
       cae al segundo renglón es la cuenta, que es lo único que no se toca. */
    flex-wrap: wrap;
    gap: 12px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-secondary);
    flex: 0 0 auto;
}

.tw-pick-bar .tw-meta {
    flex: 0 0 auto;
    white-space: nowrap;
}

/* El conmutador de la vista —por orden de compra o por número de parte— no se
   encoge: sus dos palabras son el ancho que necesita, y quien cede sitio es el
   buscador, que crece con lo que sobra. */
.tw-pick-bar .tw-seg {
    flex: 0 0 auto;
}

/* La altura la pone el sitio donde se monta —el modal o la ficha— y aquí sólo
   se dice que scrollea: una lista de cincuenta grupos no cabe en ningún sitio. */
.tw-pick-list {
    flex: 1 1 auto;
    min-height: 12rem;
    /* Contra la ventana y no sólo contra un número fijo: con 28rem clavados, en
       un portátil el pie del selector —lo que llevas marcado, el botón de
       quitarlo— quedaba fuera de cuadro y había que scrollear el diálogo para
       comprobar la cuenta. */
    max-height: min(28rem, 42vh);
    overflow-y: auto;
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* ── Las columnas se nombran una vez, arriba ────────────────────────────
 *
 * Cada renglón llevaba su rótulo pegado a su cifra —«20 verificada(s)», «20 por
 * embarcar»— y al lado un campo con otro 20 sin rotular. La lista se leía como
 * una sopa de números repetidos en la que el único que se teclea era el único
 * sin nombre.
 *
 * La cabecera no scrollea con la lista: el nombre de la columna tiene que
 * seguir estando cuando se va por el renglón treinta. El sangrado de 18 px son
 * los 6 del acolchado de la lista más el borde del grupo, el del renglón y sus
 * 10 de acolchado: así el rótulo cae justo encima de su columna. */
.tw-pick-cols {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 18px;
    border-bottom: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-secondary);
    flex: 0 0 auto;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

.tw-pick-cols[hidden] {
    display: none;
}

.tw-pick-col--part {
    flex: 1 1 auto;
    min-width: 0;
}

.tw-pick-col--labels {
    flex: 0 0 11rem;
}

.tw-pick-col--balance {
    flex: 0 0 7rem;
    text-align: end;
}

.tw-pick-col--qty {
    flex: 0 0 6rem;
    text-align: end;
}

/* Los huecos que guardan la columna de la casilla y la de la flecha, para que
   la cabecera y los renglones sin flecha caigan donde caen los demás. */
.tw-pick-space {
    flex: 0 0 1.125rem;
}

.tw-pick-space--chev {
    flex: 0 0 1rem;
}

.tw-pick-check {
    flex: 0 0 1.125rem;
    width: 1.125rem;
    height: 1.125rem;
    margin: 0;
}

.tw-pick-group {
    border: 1px solid transparent;
    border-radius: var(--tw-radius-sm);
    transition: border-color var(--tw-transition), background-color var(--tw-transition);
}

.tw-pick-group:hover {
    background-color: var(--tw-bg-transparent-light);
}

.tw-pick-group.is-open {
    border-color: var(--tw-border-light);
    background-color: var(--tw-bg-secondary);
}

/* Elegido se ve desde lejos, y por el borde entero: la barra de color a un lado
   es justo lo que este sistema no usa. */
.tw-pick-group.is-picked,
.tw-pick-row.is-picked {
    border-color: var(--tw-accent);
    background-color: var(--tw-accent-secondary);
}

/* La cabecera del grupo es un renglón más, con su casilla en la misma columna
   que las de los renglones sueltos. Antes era un `<button>` entero, y por eso
   no podía llevar casilla dentro: el grupo con varias órdenes se quedaba sin
   forma de marcarse sin abrirlo primero. */
.tw-pick-row--head {
    padding: 8px 10px;
    cursor: pointer;
}

.tw-pick-toggle {
    flex: 0 0 1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--tw-font-light);
}

.tw-pick-chevron {
    font-size: 1rem;
    transition: transform var(--tw-transition);
}

.tw-pick-group.is-open .tw-pick-chevron {
    transform: rotate(90deg);
}

/* Los renglones de un grupo abierto van sangrados por la izquierda y pegados a
   la derecha: sangrar por los dos lados desalineaba sus cifras de las del grupo
   que las resume, que es contra lo que se leen. */
.tw-pick-rows {
    padding: 0 0 8px 34px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.tw-pick-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    border: 1px solid transparent;
    border-radius: var(--tw-radius-sm);
}

.tw-pick-row:hover {
    background-color: var(--tw-bg-transparent-light);
}

.tw-pick-row--solo {
    padding: 8px 10px;
}

/* El número de parte es la identidad de la fila: manda en tamaño y se queda con
   el espacio que sobra. Lo demás son cifras, y las cifras se alinean.

   ── Y por dentro es un bloque, no una columna ───────────────────────────────

   Era `flex-direction: column`, y en una columna flex **todo hijo es un renglón**:
   también un texto suelto y también un `<span>` declarado `display: inline`. Así
   que «Rev. A» —escrita para ir pegada al número de parte, que para eso lleva su
   `margin-left`— caía debajo, y cada fila del selector gastaba tres renglones
   para decir dos cosas. Como bloque, lo inline fluye en la misma línea y lo que
   tiene que bajar lo dice siendo `display: block`. */
.tw-pick-part,
.tw-pick-order {
    flex: 1 1 auto;
    min-width: 0;
    display: block;
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

.tw-pick-order {
    font-variant-numeric: tabular-nums;
}

.tw-pick-rev {
    display: inline;
    margin-left: 6px;
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
}

/* La segunda línea: de quién es, cuándo entrega, cuántos lleva. Es bloque
   porque es lo único que baja de renglón. */
.tw-pick-when {
    display: block;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
}

.tw-pick-labels {
    flex: 0 0 11rem;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    font-variant-numeric: tabular-nums;
}

/* La cifra de la columna, y debajo sólo lo que hace falta explicar: piezas que
   no son etiquetas, o etiquetas que todavía no han pasado por el escáner. */
.tw-pick-num {
    display: block;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
}

.tw-pick-sub {
    display: block;
    color: var(--tw-font-light);
}

/* Lo que no se puede llevar aunque haya saldo. Se dice pegado a las etiquetas
   porque son ellas el tope, y es lo accionable: se imprimen y deja de serlo. */
.tw-pick-cap {
    display: block;
    color: var(--tw-tag-yellow-fg);
}

.tw-pick-balance {
    flex: 0 0 7rem;
    text-align: end;
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-primary);
}

/* El campo y su tope, en la misma columna. El tope va debajo del campo y no en
   la columna de las etiquetas: el número que manda sobre lo que se teclea tiene
   que estar pegado a donde se teclea. Con «7 por embarcar» en negritas a un lado
   y el tope real —5— a dos columnas de distancia, se escribía 7 y el embarque
   se llevaba 5 sin decir nada. */
.tw-pick-qty-cell {
    flex: 0 0 6rem;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
}

.tw-pick-qty {
    width: 100%;
    text-align: end;
    font-variant-numeric: tabular-nums;
}

.tw-pick-max {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    text-align: end;
    font-variant-numeric: tabular-nums;
}

/* Mientras la cifra tecleada no exista. No se recorta a media tecla —escribir
   «70» se quedaría en «7»—, se dice; y al soltar el campo se queda en el tope. */
.tw-pick-qty.is-over {
    border-color: var(--tw-tag-red-fg);
    color: var(--tw-tag-red-fg);
}

/* Lo que se lleva un grupo: la suma de lo marcado en sus órdenes, en la misma
   columna en la que los renglones tienen su campo. Sin esto la columna se
   quedaba en blanco en la mitad de los renglones y parecía rota. */
.tw-pick-sum {
    flex: 0 0 6rem;
    text-align: end;
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-primary);
}

/* Acotar a lo listo es un interruptor, no una opción del conmutador de al lado:
   el conmutador cambia por dónde se entra —y las dos puertas enseñan lo mismo—,
   esto **deja fuera** parte de la lista. Encendido se ve con el acento, que es
   como el resto del sistema dice «esto está puesto». */
.tw-pick-bar [data-solo-listo] {
    flex: 0 0 auto;
}

.tw-pick-bar [data-solo-listo].active {
    background-color: var(--tw-accent-secondary);
    border-color: var(--tw-accent);
    color: var(--tw-accent);
}

/* ── El cumplimiento de la orden · tres tramos y un porcentaje ──────────
 *
 * Va dentro de la celda de identidad y no en una columna propia: la retícula del
 * selector ya lleva cuatro columnas de cifras y una quinta las estrecharía en la
 * ficha, donde el selector vive en una columna y no en un modal ancho. Aquí
 * además cae debajo de lo que explica —la orden de compra—, que es donde se lee.
 *
 * Los tres tramos son tres cosas distintas y por eso son tres colores:
 * entregado —lo único que el cliente tiene—, apartado en un embarque vivo que
 * todavía no sale, y lo que se está marcando ahora. El último usa el color de
 * selección del propio selector: es el mismo gesto, visto en la barra. */
.tw-pick-fill {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 4px;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
    font-variant-numeric: tabular-nums;
}

.tw-pick-fill-bar {
    flex: 0 0 7rem;
    display: flex;
    height: 6px;
    border-radius: 3px;
    background-color: var(--tw-bg-tertiary);
    overflow: hidden;
}

.tw-pick-fill-done {
    background-color: var(--tw-tag-green-fg);
}

.tw-pick-fill-held {
    background-color: var(--tw-tag-blue-fg);
}

/* Lo que se está marcando ahora. Rayado y no liso: todavía no es un hecho, y
   distinguirlo de lo que ya está apartado es justo la pregunta que la barra
   viene a contestar. */
.tw-pick-fill-now {
    background-image: repeating-linear-gradient(
        45deg,
        var(--tw-accent),
        var(--tw-accent) 3px,
        transparent 3px,
        transparent 6px
    );
    background-color: var(--tw-accent-secondary);
}

.tw-pick-fill-num {
    flex: 0 0 auto;
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
}

.tw-pick-fill-next {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* El empaque estándar del número de parte, pegado al número de parte: es suyo,
   no de la orden ni de la cantidad. Inline, para que fluya en la misma línea que
   la revisión. */
.tw-pick-pack {
    display: inline;
    margin-left: 6px;
    padding: 1px 6px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-light);
    white-space: nowrap;
}

/* Las cajas que salen de lo tecleado, debajo del campo. Mismo sitio que el tope
   —lo que manda sobre el campo va pegado al campo— y el mismo tamaño: es lo que
   se acepta al crear el embarque. */
.tw-pick-boxes {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    text-align: end;
    font-variant-numeric: tabular-nums;
}

/* ── El segundo paso de la búsqueda · dentro del grupo abierto ──────────
 *
 * Vive fuera de `.tw-pick-rows` porque teclear repinta sólo los renglones: si el
 * campo estuviera dentro, cada tecla lo reescribiría y perdería el foco. El
 * sangrado es el mismo que el de los renglones, para que caiga sobre ellos. */
.tw-pick-find {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 10px 8px 34px;
}

.tw-pick-find .input-icon {
    flex: 1 1 auto;
    max-width: 24rem;
}

.tw-pick-find .tw-meta {
    flex: 0 0 auto;
    white-space: nowrap;
}

/* El pie lee dos cosas: lo que se lleva y las cajas que se van a abrir. En
   columna porque la segunda es una casilla con su texto, y en línea con la
   primera se leía como parte de la cuenta. */
.tw-pick-foot-read {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

.tw-pick-foot-read [hidden] {
    display: none;
}

.tw-pick-accept {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

.tw-pick-accept .form-check-input {
    margin: 0;
    flex: 0 0 auto;
}

/* Lo que se propuso por encima de lo etiquetado. No es un error —proponer es el
   gesto— pero es el trabajo que queda antes de poder cerrarlo. */
.tw-pick-warn {
    color: var(--tw-tag-yellow-fg);
}

.tw-pick-more {
    margin: 4px 6px 2px;
    padding: 8px;
    border: 1px dashed var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    background: none;
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-sm);
}

.tw-pick-more:hover {
    border-color: var(--tw-accent);
    color: var(--tw-font-primary);
}

.tw-pick-empty {
    padding: 32px 12px;
    text-align: center;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-sm);
}

/* El pie no se va con el scroll: es donde se lee cuánto llevas elegido y donde
   está el botón que cierra el trámite. */
.tw-pick-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 12px;
    border-top: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-secondary);
    flex: 0 0 auto;
}

.tw-pick-foot-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
}

.tw-pick-foot-actions [hidden] {
    display: none;
}

/* ── El cajón de agregar partidas, dentro de la ficha ──────────────────
 *
 * No es un modal: agregar contenido es trabajar sobre el embarque, y taparlo
 * con una ventana obliga a cerrarla para comprobar qué llevaba ya.
 */

.tw-add-drawer {
    padding: 12px;
    border-bottom: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-secondary);
}

.tw-add-drawer[hidden] {
    display: none;
}

/* ── Lo que impide avanzar ─────────────────────────────────────────────
 *
 * Eran píldoras `tw-tag` con una frase entera dentro, puestas una junto a otra:
 * a `tw-tag` se le pide una palabra —«Listo», «3 nesteos»— y con una oración se
 * convierte en un renglón de texto sin principio ni final visible. Aquí cada
 * aviso es un bloque con su icono, y lo que bloquea va debajo, que es lo
 * accionable: «no se puede liberar» dice qué botón no va a funcionar.
 */

.tw-blockers {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

.tw-blocker {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-md);
    line-height: 1.35;
}

.tw-blocker .ti {
    flex: 0 0 auto;
    font-size: 18px;
    color: var(--tw-font-light);
}

/* Lo que bloquea, en su propia línea y con el peso: es la consecuencia, y es
   lo que se busca cuando el botón de liberar no responde. */
.tw-blocker b {
    display: block;
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

/* El tono va en el borde entero y en el fondo, nunca en una barra al costado. */
.tw-blocker--red {
    border-color: var(--tw-tag-red-fg);
    background-color: var(--tw-tag-red-bg);
    color: var(--tw-tag-red-fg);
}

.tw-blocker--red .ti,
.tw-blocker--red b {
    color: var(--tw-tag-red-fg);
}

.tw-blocker--yellow {
    border-color: var(--tw-tag-yellow-fg);
    background-color: var(--tw-tag-yellow-bg);
    color: var(--tw-tag-yellow-fg);
}

.tw-blocker--yellow .ti,
.tw-blocker--yellow b {
    color: var(--tw-tag-yellow-fg);
}

/*
 * El aviso que además dice **cuáles**.
 *
 * Un aviso de una línea se centra con su icono; uno que lleva dentro la lista de
 * coladas no, porque el icono acabaría a media altura de la lista en vez de
 * junto a la frase que lo explica. Ocupa el renglón entero para que las píldoras
 * quepan sin partirse por la mitad.
 */
.tw-blocker--stacked {
    align-items: flex-start;
    width: 100%;
}

.tw-blocker-list {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
}

/* ===========================================================================
   Asistente por pasos, dentro de un modal
   ===========================================================================

   Las estaciones ya tienen el suyo —`.st-steps`— y no se reutiliza aquí: aquel
   está dimensionado para una terminal de piso, con círculos de 32 px y tipos
   grandes, y metido en un modal del panel se come media pantalla. Es el mismo
   patrón a la escala de esta superficie.

   La barra es lectura, no navegación hacia adelante: dice en cuál de los pasos
   se está y cuántos faltan. Volver atrás sí se puede, y lo resuelve el módulo
   que la monta. */
.tw-steps {
    display: flex;
    align-items: stretch;
    gap: 8px;
    margin-bottom: 16px;
}

.tw-step {
    flex: 1 1 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    row-gap: 2px;
    min-width: 0;
    padding: 8px 10px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    color: var(--tw-font-light);
    text-align: left;
    font: inherit;
}

/* El número y no un icono: dice cuántos son y en cuál va sin leer nada. */
.tw-step-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 1px solid var(--tw-border-strong);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
}

.tw-step-title {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Qué se decide en ese paso, debajo de su nombre. El rótulo solo nombra
   —«Programas», «Órdenes»— y eso basta para quien ya conoce el asistente;
   nadie lo conoce la primera vez. Se sangra 30 px —los 22 del número más los
   8 de hueco— para que caiga bajo el rótulo y no bajo el círculo. */
.tw-step-what {
    flex-basis: 100%;
    padding-left: 30px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

.tw-step.is-now {
    border-color: var(--tw-accent);
    background-color: var(--tw-accent-tertiary);
    color: var(--tw-font-primary);
}

.tw-step.is-now .tw-step-num {
    border-color: var(--tw-accent);
    background-color: var(--tw-accent);
    color: #ffffff;
}

/* Hecho es **antes que éste**, no «visitado alguna vez»: quien vuelve al paso 1
   a cambiar el archivo no puede seguir viendo los otros dos en verde. */
.tw-step.is-done {
    border-color: var(--tw-tag-green-fg);
    color: var(--tw-font-primary);
    cursor: pointer;
}

.tw-step.is-done .tw-step-num {
    border-color: var(--tw-tag-green-fg);
    background-color: var(--tw-tag-green-bg);
    color: var(--tw-tag-green-fg);
}

@media (max-width: 767.98px) {
    .tw-step-title,
    .tw-step-what {
        display: none;
    }

    .tw-step {
        justify-content: center;
    }
}

/* La zona en la que se suelta el archivo.

   Es un `<label>` envolviendo el `<input type="file">`, así que el clic ya
   funciona sin JavaScript: arrastrar es el atajo, no el único camino. */
.tw-dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    padding: 32px 20px;
    border: 1px dashed var(--tw-border-strong);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
    color: var(--tw-font-light);
    text-align: center;
    cursor: pointer;
}

.tw-dropzone:hover,
.tw-dropzone.is-over {
    border-color: var(--tw-accent);
    background-color: var(--tw-accent-tertiary);
}

.tw-dropzone .ti {
    font-size: 28px;
}

/* La tarjeta que además recibe un archivo · soltar el `.LXD` sobre su programa.
   No se dibuja una zona aparte: la tarjeta ya lleva borde y ya dice de cuál se
   trata, así que mientras el archivo está encima se tiñe con el mismo acento que
   la zona de arriba. Sólo color: cambiar el borde de grosor movería la lista
   entera un píxel al pasar por encima de cada tarjeta. */
.tw-card.is-over {
    border-color: var(--tw-accent);
    background-color: var(--tw-accent-tertiary);
}

.tw-dropzone-title {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

/* La hoja del nesteador · la cabecera de cada programa en el asistente.

   Es la misma hoja que Lantek imprime y que viaja en papel hasta la máquina
   —Ref, CNC, máquina, material, peso, aprovechamiento y la tabla de piezas—,
   dibujada con la misma retícula de casillas para que quien la tiene en la
   mano reconozca en pantalla lo que está leyendo en papel. La retícula son
   celdas sobre un fondo de un píxel que hace de línea, como el impreso. */
.tw-hoja {
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-md);
    overflow: hidden;
}

.tw-hoja-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
    /* Corrido un píxel para que el borde derecho e inferior de la última
       casilla queden debajo del borde del contenedor: con la rejilla pintada
       por detrás, un renglón a medio llenar dejaba un hueco del color de la
       línea donde no había casilla. */
    margin: 0 -1px -1px 0;
}

.tw-hoja-dato {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 6px 10px;
    background-color: var(--tw-bg-primary);
    border-right: 1px solid var(--tw-border-light);
    border-bottom: 1px solid var(--tw-border-light);
    min-width: 0;
}

/* La casilla que ocupa el renglón entero: la Ref, que es el nombre largo. */
.tw-hoja-dato--ancha {
    grid-column: 1 / -1;
}

.tw-hoja-rotulo {
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-tertiary);
}

.tw-hoja-valor {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
    overflow-wrap: anywhere;
}

.tw-hoja-valor.tw-empty-value {
    font-weight: var(--tw-weight-regular);
}

/* El acomodo en miniatura · las cajas del `.NIF` de ProNest.
   Lantek no exporta coordenadas y ahí no se dibuja nada. */
.tw-hoja-acomodo {
    display: block;
    width: 100%;
    /* Alto fijo y no proporción libre: una lámina de 4×10 a lo ancho de un
       modal XL saldría de media pantalla. El dibujo se encoge dentro y el
       fondo es el de la tarjeta, para que la banda no pese más que la tabla. */
    height: 8rem;
    background-color: var(--tw-bg-primary);
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-hoja-acomodo .tw-hoja-lamina {
    fill: var(--tw-bg-secondary);
    stroke: var(--tw-border-strong);
}

.tw-hoja-acomodo .tw-hoja-pieza {
    fill: var(--tw-accent-secondary);
    stroke: var(--tw-accent);
    stroke-opacity: 0.55;
}

/* Armar el corte lineal · la puerta «Corte lineal» del asistente de nesteo.
   Qué se corta a lo ancho; debajo, con qué barras y cómo quedan, en dos
   columnas que se apilan cuando el panel se queda corto (`auto-fit`, como
   `.tw-cols`: el ancho lo manda el panel, no la ventana). Las tablas se
   desplazan dentro de su tarjeta. */
.ac-perfil {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.ac-perfil > .form-select,
.ac-perfil > .ts-wrapper {
    flex: 1 1 22rem;
    max-width: 34rem;
}

/* La cabecera de cada tarjeta: el título con su cuenta debajo, y los
   botones a la derecha, que bajan enteros a su renglón si no caben. */
#lk-armar .tw-card-header {
    flex-wrap: wrap;
    align-items: flex-start;
}

.ac-cab {
    flex: 1 1 12rem;
    min-width: 0;
}

/* Lo que se sabe del perfil baja a su renglón antes que cortarse. */
#ac-existencias {
    flex: 1 1 14rem;
    min-width: 0;
    white-space: normal;
}

/* Los números se leen enteros: la tabla se desplaza dentro de su tarjeta
   antes que angostar un campo, y sin las flechitas que se comen dos dígitos. */
.ac-tabla > .table {
    min-width: 40rem;
}

.ac-tabla input[type='number'],
.ac-medida input[type='number'] {
    min-width: 4.75rem;
    padding-inline: 6px;
    -moz-appearance: textfield;
    appearance: textfield;
}

.ac-tabla input[type='number']::-webkit-inner-spin-button,
.ac-tabla input[type='number']::-webkit-outer-spin-button,
.ac-medida input[type='number']::-webkit-inner-spin-button,
.ac-medida input[type='number']::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.ac-tabla {
    max-height: 22rem;
    overflow: auto;
    border-top: 1px solid var(--tw-border-light);
}

.ac-tabla thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background-color: var(--tw-bg-primary);
}

.ac-cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
    gap: 12px;
    align-items: start;
}

/* Cada medida con lo que significa debajo: nadie tiene por qué saber qué es
   un despunte. En rejilla, dos o cuatro por renglón según quepan. */
.ac-medidas {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: 12px 16px;
    padding: 12px;
    border-top: 1px solid var(--tw-border-light);
}

.ac-medida {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin: 0;
    min-width: 0;
}

.ac-medida > input {
    max-width: 9rem;
}

.ac-medida .form-hint {
    margin: 0;
    white-space: normal;
}

.ac-medida-nombre {
    font-weight: var(--tw-weight-medium);
}

/* Un paso a la vez · su pregunta arriba y lo que hace falta saber. */
.ac-paso-cab {
    margin-bottom: 12px;
}

.ac-paso-cab h4 {
    margin: 0 0 4px;
}

.ac-paso-cab p {
    margin: 0;
    max-width: 48rem;
}

/* La barra de muestra: qué es cada medida, dibujado. */
.ac-anatomia {
    padding: 4px 0 8px;
    border-top: 1px solid var(--tw-border-light);
}

.ac-anatomia .lk-barra {
    height: 2.25rem;
}

.ac-anatomia .lk-barra-corte.ac-anatomia-corte {
    flex: 0 0 3px;
    background-color: var(--tw-solid-red);
}

.ac-anatomia-rotulos {
    display: flex;
    margin: 0 12px;
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-light);
}

/* Los pasos del corte en el riel · el número a la izquierda y el texto a su
   lado, siempre en renglón aunque el riel se angoste. El paso de delante se
   dice con el número relleno y el fondo, sin flecha: dos marcas juntas se
   amontonaban. Un respiro entre renglones para que el fondo no pise la
   raya del siguiente. */
/* Aire contra la raya de la identidad: el paso de delante lleva fondo, y
   pegado a la raya se leía encimado. */
.ac-riel {
    padding-top: 8px;
}

.tw-inbox-row.ac-riel-paso {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 2px;
    cursor: pointer;
}

.tw-inbox-row.ac-riel-paso.is-now {
    border-bottom-color: transparent;
}

.tw-inbox-row.ac-riel-paso[aria-disabled='true'] {
    cursor: default;
}

/* La vitrina del paso 4 · el programa elegido cortándose en 3D, a lo ancho
   de la tarjeta. Cada barra de la lista la cambia; la elegida se marca con
   el fondo de selección, como en el riel. */
.ac-vitrina {
    border-top: 1px solid var(--tw-border-light);
}

.ac-vitrina-lienzo {
    position: relative;
    height: clamp(240px, 38vh, 380px);
    background-color: var(--tw-bg-secondary);
    overflow: hidden;
}

.ac-vitrina-lienzo canvas {
    display: block;
}

.ac-vitrina-pie {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    padding: 8px 12px;
    border-top: 1px solid var(--tw-border-light);
}

.ac-patron {
    cursor: pointer;
    border-radius: var(--tw-radius-sm);
}

.ac-patron:hover {
    background-color: var(--tw-bg-transparent-light);
}

.ac-patron.is-now {
    background-color: var(--tw-accent-secondary);
}

/* El número del paso en el riel: palomita cuando ya se decidió. */
.ac-riel-numero {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.35rem;
    height: 1.35rem;
    margin-top: 1px;
    border-radius: 50%;
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-medium);
    border: 1px solid var(--tw-border-strong);
    color: var(--tw-font-secondary);
}

.ac-riel-numero.is-aqui {
    border-color: var(--tw-accent);
    background-color: var(--tw-accent);
    color: #ffffff;
}

.ac-riel-numero.is-hecho {
    border-color: transparent;
    background-color: var(--tw-tag-green-bg);
    color: var(--tw-tag-green-fg);
}

.ac-lista {
    max-height: 26rem;
    overflow: auto;
    padding: 4px 0 8px;
    border-top: 1px solid var(--tw-border-light);
}

.ac-pie {
    display: flex;
    align-items: flex-end;
    gap: 16px;
    flex-wrap: wrap;
}

.ac-maquina {
    width: 20rem;
    max-width: 100%;
}

.ac-maquina > select {
    max-width: none;
}

/* Lo que no entra al corte se ve, pero apagado: desmarcar no es borrar. */
.cl-apagada > td {
    opacity: 0.5;
}

.cl-apagada > td.tw-cell-control {
    opacity: 1;
}

/* Las cifras del corte · la pestaña «Estadísticas» de Real Cut, a la vista. */
.cl-cifras {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 24px;
    padding: 0 12px 10px;
}

.cl-cifras:empty {
    display: none;
}

.cl-cifra {
    display: flex;
    flex-direction: column;
}

.cl-patron + .cl-patron {
    border-top: 1px solid var(--tw-border-light);
}

.cl-patron {
    padding: 6px 0 2px;
}

.cl-patron-cab {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 0 12px;
}

/* La barra en 3D · «Ver en 3D» del corte lineal (nesting/barra3d.js).
   El lienzo ocupa el cuerpo del diálogo; los rótulos de cada pieza son HTML
   encima del lienzo (CSS2DRenderer) y no atrapan el ratón, para que girar la
   barra funcione aunque el puntero pase por un rótulo. */
.b3d-lienzo {
    position: relative;
    height: min(62vh, 560px);
    background-color: var(--tw-bg-secondary);
    overflow: hidden;
}

.b3d-lienzo canvas {
    display: block;
}

.b3d-etiquetas {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.b3d-rotulo {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 2px 6px;
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-primary);
    border: 1px solid var(--tw-border-light);
    color: var(--tw-font-primary);
    font-size: var(--tw-font-size-xs);
    line-height: 1.2;
    white-space: nowrap;
}

.b3d-rotulo span {
    color: var(--tw-font-light);
}

/* La cuenta de una pila de la tarima: la cifra que se presume. */
.b3d-rotulo--pila strong {
    font-size: var(--tw-font-size-lg);
}

/* En pantalla completa el lienzo se come el alto que deja el pie. */
#b3d-modal .modal-content:fullscreen {
    display: flex;
    flex-direction: column;
    border-radius: 0;
}

#b3d-modal .modal-content:fullscreen .b3d-lienzo {
    flex: 1 1 auto;
    height: auto;
}

.b3d-rotulo--retal strong {
    color: var(--tw-tag-purple-fg);
}

.b3d-pie {
    gap: 12px;
    flex-wrap: wrap;
}

.b3d-leyenda {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 12px;
}

.b3d-muestra {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

.b3d-muestra i {
    width: 10px;
    height: 10px;
    border-radius: 2px;
}

.b3d-muestra i.b3d-retal {
    background-color: var(--tw-tag-purple-fg);
}

.b3d-muestra i.b3d-chatarra {
    background-color: var(--tw-font-light);
    opacity: 0.5;
}

/* La barra del corte lineal · Real Cut 1D.
   Las piezas en su orden y a escala, como la columna «Piezas» de Real Cut:
   el largo arriba y el número de parte abajo. Lo que sobra al final va en
   morado si es retal —el tono de los retales en todo el sistema— y rayado si
   es chatarra. El despunte, en gris liso: es acero que nadie usa. */
.lk-barra {
    display: flex;
    align-items: stretch;
    height: 3rem;
    margin: 8px 12px;
    border: 1px solid var(--tw-border-strong);
    border-radius: 2px;
    background-color: var(--tw-bg-secondary);
    overflow: hidden;
}

.lk-barra-pieza,
.lk-barra-sobra {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    font-size: var(--tw-font-size-xs);
    line-height: 1.2;
}

.lk-barra-pieza {
    background-color: var(--tw-accent-secondary);
    container-type: inline-size;
}

/* La pieza corta sólo dice su largo: el número de parte cortado a tres
   letras no se lee, y lo trae el `title`. */
@container (max-width: 5.5rem) {
    .lk-barra-parte {
        display: none;
    }
}

.lk-barra-largo {
    font-weight: var(--tw-weight-medium);
}

.lk-barra-parte {
    color: var(--tw-font-light);
    text-overflow: ellipsis;
    overflow: hidden;
    max-width: 100%;
    padding: 0 4px;
}

.lk-barra-corte {
    flex: 0 0 1px;
    background-color: var(--tw-border-strong);
}

.lk-barra-despunte {
    flex: 0 0 auto;
    background-color: var(--tw-bg-quaternary);
    border-right: 1px solid var(--tw-border-strong);
}

.lk-barra-sobra {
    color: var(--tw-font-light);
    border-left: 1px solid var(--tw-border-strong);
    background-image: repeating-linear-gradient(
        -45deg,
        transparent 0 4px,
        var(--tw-bg-transparent-light) 4px 8px
    );
}

.lk-barra-sobra--retal {
    color: var(--tw-tag-purple-fg);
    background-color: var(--tw-tag-purple-bg);
    background-image: none;
}

/* La tabla de piezas de la hoja, dentro de la misma tarjeta: sin el borde
   redondeado propio —lo pone `.tw-hoja`— y con la primera columna angosta,
   que es el «#» del impreso. */
.tw-hoja .table {
    margin-bottom: 0;
}

/* Donde el impreso lleva el dibujo y el archivo no lo trae: se dice, para que
   nadie lo busque donde no puede estar. */
.tw-hoja-nota {
    padding: 6px 10px;
    border-bottom: 1px solid var(--tw-border-light);
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

/* El buscador dentro de la hoja o de la lista de órdenes: chico, porque acota
   una tabla y no la pantalla. */
.tw-hoja-buscar {
    padding: 6px 10px;
    border-bottom: 1px solid var(--tw-border-light);
}

/* La vista del asistente del nesteador · el riel y el pie del recorrido. */

.tw-rail-controles {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px 12px 8px;
}

.tw-rail-pie {
    flex: 0 0 auto;
    padding: 10px 12px;
    border-top: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-primary);
}

/* El renglón del programa que se tiene delante: el riel dice dónde se está
   parado sin que haya que acordarse. Sólo color, como toda selección aquí. */
.tw-inbox-row.is-now {
    background-color: var(--tw-accent-secondary);
    border-radius: var(--tw-radius-sm);
}

/* El hueco de la casilla en un renglón agrupado · la casilla es la del bloque.
   Se guarda el sitio para que los renglones del grupo se lean en la misma
   columna que los sueltos de arriba y de abajo. */
.lk-sin-casilla {
    flex: 0 0 auto;
    width: 1rem;
}

/* Con qué programas reparte éste · el distintivo del renglón, que es el control.

   Sin grupo va callado —es el estado de casi todos— y con grupo toma el tono de
   la etiqueta, que es lo que deja barrer la lista y ver de un vistazo quién está
   con quién. Las clases de etiqueta las pone el guion; aquí sólo se le quita al
   botón el aire que traería de serie, para que no crezca el renglón. */
.lk-grupo-boton {
    padding: 1px 6px;
    font-size: var(--tw-font-size-xs, 11px);
    line-height: 1.5;
}

/* Y a dónde puede ir · la lista, en línea debajo de su renglón.

   En línea y no flotando encima: el riel es una columna angosta con scroll, y
   una ventanita tapa justo los renglones contra los que se está decidiendo con
   cuáles juntarlo. Es la misma razón del cajón de espera del reparto. */
.tw-rail-menu {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid var(--tw-border-light);
}

.tw-rail-menu .btn {
    padding: 2px 8px;
}

/* El grupo en el que ya está: se enseña puesto y no se ofrece como novedad. */
.tw-rail-menu .btn.is-picked {
    border-color: var(--tw-accent);
    color: var(--tw-accent);
}

/* Los programas que reparten juntos · un bloque, no una etiqueta por renglón.

   EL PROBLEMA QUE RESUELVE. Agrupar cambia el recorrido —las hojas de sus
   programas seguidas y detrás un solo reparto— y con el grupo dicho sólo en un
   distintivo pequeño de cada renglón no se veía qué había pasado: los miembros
   seguían salteados entre los demás, cada uno con su etiqueta, y había que
   recorrer el riel para reconstruir quién estaba con quién. El bloque los
   reúne, los nombra una vez arriba y lleva ahí mismo su deshacer. */
.tw-rail-grupo {
    margin: 6px 0;
    padding: 6px 4px 4px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-secondary);
}

/* Cuando el recorrido está en su reparto se señala el bloque ENTERO y no un
   renglón: la matriz es de todos a la vez, y marcar sólo al primero haría
   creer que es suya. */
.tw-rail-grupo.is-now {
    border-color: var(--tw-accent);
    background-color: var(--tw-accent-secondary);
}

/* El rótulo del grupo, una sola vez: cuántos son, cuánto sacan entre todos y
   el botón de deshacerlo. */
.tw-rail-grupo-cab {
    display: flex;
    align-items: center;
    gap: 2px 6px;
    flex-wrap: wrap;
    padding: 0 6px 6px;
}

/* El pie del recorrido: el estado de lo que se tiene delante y los botones
   que lo caminan. No se desplaza con el contenido: los botones de avanzar no
   pueden vivir al final de una lista de dos pantallas de alto. */
.tw-flow-footer {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 24px;
    border-top: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-primary);
}

/* Las cifras que deciden · la información que no puede leerse de reojo.
   Cuánto le falta a una orden y cuánto le cubre este programa iban en la
   línea gris de los detalles; el tamaño y el tono son la jerarquía: la cifra
   en grande, su explicación en meta debajo. Los tonos son los de los tags,
   que ya significan lo mismo en todo el sistema. */
.tw-cifra {
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    line-height: 1.25;
    white-space: nowrap;
}

.tw-cifra--green {
    color: var(--tw-tag-green-fg);
}

.tw-cifra--yellow {
    color: var(--tw-tag-yellow-fg);
}

/* Cuántas láminas corre el programa · la píldora que acompaña al CNC en el
   riel, la hoja, el reparto y el resumen. Es EL número físico del programa
   —cuántas veces se corre— y andaba disuelto entre los datos grises. */
.tw-laminas {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 10px;
    border-radius: 999px;
    background-color: var(--tw-tag-blue-bg);
    color: var(--tw-tag-blue-fg);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    white-space: nowrap;
}

/* La tabla de órdenes del reparto · la caja anclada y las candidatas aparte.
   Ancho fijo para que las dos tablas —la de la caja y la de candidatas—
   alineen sus columnas: comparten el mismo colgroup. */
.tw-ordenes-tabla {
    table-layout: fixed;
    width: 100%;
    /* Por debajo de esto se desplaza en horizontal en vez de recortar: es la
       misma regla que las tablas de listado. Bajó a 44 rem al compactar el
       avance y las acciones: un monitor de ~900 px ya no ve barra. */
    min-width: 44rem;
}

.tw-ordenes-tabla td {
    vertical-align: middle;
    overflow-wrap: anywhere;
}

/* Lo que ya está en la caja se distingue por el fondo, no por otra lista. */
.tw-ordenes-tabla tr.is-en-caja > td {
    background-color: var(--tw-accent-tertiary);
}

/* La fila candidata entera agrega: el cursor lo promete y el fondo contesta. */
.tw-ordenes-tabla tr.lk-agregar-orden {
    cursor: pointer;
}

.tw-ordenes-tabla tr.lk-agregar-orden:hover > td {
    background-color: var(--tw-bg-transparent-light);
}

/* Las candidatas se desplazan en su propio alto: quince órdenes no pueden
   empujar el reparto de los renglones fuera de la pantalla. El hueco de la
   barra se reserva siempre —sin esto, la barra aparecía encima de la última
   columna y el botón de Agregar salía cortado—. */
.lk-scroll-ordenes {
    max-height: 20rem;
    overflow-y: auto;
    scrollbar-gutter: stable;
}

/* Los retales del insumo, para elegir sobre cuál se nesteó: se desplazan en su
   propio alto para que la tabla de piezas de la hoja no quede sepultada. El
   aire de 2 px es para que el borde de la opción elegida no se recorte. */
.lk-retales {
    max-height: min(40vh, 20rem);
    overflow-y: auto;
    scrollbar-gutter: stable;
    padding: 2px;
    margin-inline: -2px;
}

/* La que no se puede elegir —ya es de otro programa, o ya están todas las
   láminas cubiertas— se enseña apagada: sigue diciendo que existe. */
.tw-radio-option:has(> input:disabled) {
    cursor: not-allowed;
    opacity: 0.55;
}

/* El botón de la última columna no envuelve ni se corta: la columna mide lo
   que mide el botón. */
.tw-ordenes-tabla td.tw-cell-control {
    white-space: nowrap;
}

/* La matriz del reparto · filas = lo que saca la lámina, columnas = las
   órdenes de la caja. El campo mide lo que mide un número de tres cifras y va
   pegado a la derecha, alineado con su columna. */
.lk-reparto-tabla {
    min-width: 40rem;
    /* Con `collapse` (lo que trae .table), Chrome pinta el fondo de una celda
       pegajosa en la capa de la tabla y no con la celda: al desplazar en
       horizontal, las columnas que pasan por debajo se asoman en las costuras
       de los bordes. Con `separate` cada celda carga su fondo y su borde. */
    border-collapse: separate;
    border-spacing: 0;
}

/* Con muchas órdenes la matriz se desplaza en horizontal, y la identidad no
   puede irse con el scroll: «Número de parte» y «Saca» quedan congeladas,
   como en una hoja de cálculo. El fondo es opaco a propósito —sin él, las
   columnas de órdenes se leen a través al pasar por debajo— y la sombra de
   borde dice dónde acaba lo fijo. Los anchos van fijados aquí porque el
   `left` de la segunda columna depende del ancho de la primera. */
.lk-reparto-tabla th:nth-child(1),
.lk-reparto-tabla td:nth-child(1) {
    position: sticky;
    left: 0;
    z-index: 2;
    min-width: 13rem;
    max-width: 13rem;
    background-color: var(--tw-bg-primary);
}

.lk-reparto-tabla th:nth-child(2),
.lk-reparto-tabla td:nth-child(2) {
    position: sticky;
    left: 13rem;
    z-index: 2;
    min-width: 6rem;
    background-color: var(--tw-bg-primary);
    box-shadow: inset -1px 0 0 var(--tw-border-medium);
}

/* Las columnas de órdenes con un ancho parejo: nueve rem por orden hacen la
   aritmética del scroll predecible —veinte órdenes son ~180 rem, y se navega
   con la identidad a la vista—. */
.lk-reparto-tabla th.lk-col-orden {
    min-width: 9.5rem;
}

/* Con el panel angosto las columnas congeladas se comían la de la orden: la
   identidad (13 + 6 rem) y «Sin orden» a la derecha dejaban a la orden sin
   sitio, con el folio y el campo cortados debajo de lo fijo. Ahí se
   adelgaza la identidad para que al menos una orden se vea entera. */
.lk-reparto-scroll {
    container-type: inline-size;
}

@container (max-width: 46rem) {
    .lk-reparto-tabla th:nth-child(1),
    .lk-reparto-tabla td:nth-child(1) {
        min-width: 9.5rem;
        max-width: 9.5rem;
    }

    .lk-reparto-tabla th:nth-child(2),
    .lk-reparto-tabla td:nth-child(2) {
        left: 9.5rem;
        min-width: 3.75rem;
    }

    .lk-reparto-tabla {
        min-width: 0;
    }
}

/* Las barras del XML de Real Cut: el perfil se parte en renglones antes que
   empujar «Lo que sobra» fuera de la tarjeta. */
.lk-lineal-tabla td:first-child {
    white-space: normal;
    min-width: 10rem;
}

/* La vitrina del resumen · el lienzo sólo ocupa sitio mientras corre: los
   programas que no la tienen enseñan su pie con el botón para traerla. */
.lk-vitrina {
    margin: 8px 0 0;
    border-top: 1px solid var(--tw-border-light);
}

.lk-vitrina-lienzo {
    position: relative;
    height: 0;
    overflow: hidden;
    background-color: var(--tw-bg-secondary);
}

.lk-vitrina.is-viva .lk-vitrina-lienzo {
    height: clamp(220px, 34vh, 340px);
}

.lk-vitrina-lienzo canvas {
    display: block;
}

.lk-vitrina-pie {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    padding: 8px 12px;
}

/* El sobrante corto de la barra dibujada: sólo su color, el número va en el
   title. Cortado a dos cifras en la orilla se leía como otro dato. */
.lk-barra-sobra {
    container-type: inline-size;
}

@container (max-width: 2.25rem) {
    .lk-barra-sobra .lk-barra-largo {
        display: none;
    }
}

/* El resumen del pie en dos renglones como mucho: con nombres de programa
   largos crecía a tres y empujaba los botones. Entero en el `title`. */
#lk-resumen {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* La cabecera del reparto —de qué programa se trata— y su tarjeta no van
   pegadas: con 8 px la tarjeta parecía montada sobre el título. */
#lk-reparto-cab {
    margin-bottom: 16px;
}

.lk-reparto-tabla input.lk-celda {
    width: 5rem;
    margin-left: auto;
}

.lk-reparto-tabla td {
    vertical-align: top;
}

.lk-reparto-tabla tfoot td {
    border-top: 2px solid var(--tw-border-medium);
}

/* «Sin orden» congelada a la DERECHA: es el dato que dice qué falta por
   asignar, y con varias columnas de órdenes era justo el que el scroll
   escondía. Mismo truco que la identidad de la izquierda: fondo opaco y una
   sombra de borde que dice dónde empieza lo fijo. */
.lk-reparto-tabla th:last-child,
.lk-reparto-tabla td:last-child {
    position: sticky;
    right: 0;
    z-index: 2;
    min-width: 5.5rem;
    background-color: var(--tw-bg-primary);
    box-shadow: inset 1px 0 0 var(--tw-border-medium);
}

/* El desglose de «Pide de este archivo»: número de parte a la izquierda,
   piezas alineadas a la derecha — cantidades comparables de un vistazo, no un
   párrafo con «· 52 pza» incrustado. */
.lk-pide {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 10px;
    row-gap: 2px;
    align-items: baseline;
}

.lk-pide-parte {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lk-pide-pza {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.lk-pide .lk-ver-pide {
    grid-column: 1 / -1;
    justify-self: start;
}

/* El cajón de las órdenes que esperan: en la caja, sin llevar piezas. Es lo
   que acota el ancho de la matriz —la columna es de quien lleva piezas— y por
   eso vive pegado a ella, en línea y no en un modal: darle piezas a una es
   bajarle a otra, y hay que estar viendo la tabla para decidir a cuál. */
.lk-espera {
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
    padding: 4px 8px;
}

.lk-espera .lk-abrir-espera {
    width: 100%;
    text-align: left;
    justify-content: flex-start;
    border: 0;
    background: transparent;
    box-shadow: none;
    color: var(--tw-font-secondary);
    /* La frase entera envuelve: cortarla escondía justo el «cómo». */
    white-space: normal;
    height: auto;
}

.lk-espera .table-responsive {
    padding: 0 4px 4px;
}

/* La tabla del cajón es angosta a propósito: hereda el vestido de
   `tw-ordenes-tabla` pero no su ancho mínimo — aquí «Darle piezas» tiene que
   quedar a la vista sin desplazar en horizontal. */
.lk-espera .tw-ordenes-tabla {
    min-width: 0;
    table-layout: auto;
}

/* La hoja de cada programa dentro del resumen · para cotejarla contra el
   papel que salió de Lantek. Una tras otra, con aire entre ellas. */
.lk-hoja-resumen {
    margin-top: 20px;
}

.lk-hoja-resumen .lk-impresa-cab {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 6px;
}

/* El paseo guiado (driver.js), vestido con el tema.
   Su hoja de estilos trae blanco fijo —en oscuro, un flashazo— y además carga
   DESPUÉS que ésta (va con la vista, no con el layout), así que a igual
   especificidad ganaba ella. Todos los selectores van doblados
   (`.driver-popover.driver-popover`) para ganar sin importar el orden, y todo
   sale de los tokens: el globo se ve del tema que esté puesto, flechas
   incluidas —son bordes del color del fondo del globo—. */
.driver-popover.driver-popover {
    background-color: var(--tw-bg-primary);
    color: var(--tw-font-primary);
    font-family: inherit;
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-md);
    box-shadow: var(--tw-shadow-strong);
    padding: 14px 16px;
    max-width: 21rem;
}

.driver-popover .driver-popover-title {
    font: inherit;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
}

.driver-popover .driver-popover-description {
    font: inherit;
    font-size: var(--tw-font-size-md);
    line-height: 1.45;
    color: var(--tw-font-secondary);
}

.driver-popover .driver-popover-progress-text {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* La tache de cerrar, como los cierres del tema: gris que se afirma al pasar. */
.driver-popover .driver-popover-close-btn {
    color: var(--tw-font-light);
}

.driver-popover .driver-popover-close-btn:hover {
    color: var(--tw-font-primary);
}

/* Los botones, calcados de `.btn`: mismo tamaño, mismo borde, mismo hover. */
.driver-popover .driver-popover-navigation-btns button {
    font: inherit;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    padding: 4px 10px;
    color: var(--tw-font-primary);
    background-color: var(--tw-bg-primary);
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-sm);
    text-shadow: none;
}

.driver-popover .driver-popover-navigation-btns button:hover {
    background-color: var(--tw-bg-tertiary);
    color: var(--tw-font-primary);
}

.driver-popover .driver-popover-navigation-btns button:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 1px;
}

.driver-popover .driver-popover-arrow-side-top.driver-popover-arrow {
    border-top-color: var(--tw-bg-primary);
}

.driver-popover .driver-popover-arrow-side-bottom.driver-popover-arrow {
    border-bottom-color: var(--tw-bg-primary);
}

.driver-popover .driver-popover-arrow-side-left.driver-popover-arrow {
    border-left-color: var(--tw-bg-primary);
}

.driver-popover .driver-popover-arrow-side-right.driver-popover-arrow {
    border-right-color: var(--tw-bg-primary);
}

/* ===========================================================================
   Planeación del corte · lo que falta por nestear
   ===========================================================================

   Es una tabla con renglones de encabezado: la orden de compra —o el número de
   parte, según cómo se agrupe— abre una fila que ocupa el ancho entero, y
   debajo cuelgan sus líneas de corte. Con una tabla por grupo las columnas no
   se alinearían entre grupos, y comparar «cuántas faltan» de una orden con las
   de la siguiente es justo lo que se viene a hacer aquí.

   El encabezado del grupo no es una fila más: lleva el avance, la resta y la
   entrega, así que se lee entero aunque el grupo esté plegado. */

.tw-plan-body {
    /* La tarjeta recorta con `overflow: hidden`; el desplazamiento es de esta
       lista y no de la ventana. Ver el resto de pantallas: aquí nunca hay
       scroll de página.

       En horizontal por lo mismo que las tablas de listado llevan `scrollX`:
       son ocho columnas y en una ventana estrecha apretarlas deja «Sin
       insumo» en «Sin insu…». Antes se estrecha la ventana que el dato. */
    overflow: auto;
}

.tw-plan-table {
    /* Fijo para que los números no bailen de ancho al filtrar: con `auto`, una
       orden con cuatro cifras y otra con seis mueven la columna de sitio en
       cada redibujado. */
    table-layout: fixed;
    width: 100%;
    /* Por debajo de esto se desplaza en horizontal en vez de recortar. Subió de
       77 a 84 rem al entrar «Por reponer»: una columna más de cifras con una
       causa escrita debajo no cabe en el mismo ancho sin apretar el insumo. */
    min-width: 84rem;
}

/* Once columnas: identidad, entrega, insumo, las seis cifras, el avance y los
   programas. Los porcentajes salen de lo más largo que cabe en cada una —el
   rótulo «Por reponer», un código de insumo como «LANEG A-36 C.16 4X10», una
   razón social— y suman 100 para que no quede una columna colgando.

   La sexta cifra —«Por reponer»— entró el 24 de agosto de 2026 y lleva más
   ancho que sus vecinas porque debajo del número dice de qué se perdió: «no
   salieron de la placa» es la causa más larga y la que peor se lee cortada. */
.tw-plan-table th:nth-child(1) { width: 18%; }
.tw-plan-table th:nth-child(2) { width: 6.5%; }
.tw-plan-table th:nth-child(3) { width: 11.5%; }
.tw-plan-table th:nth-child(4) { width: 6.5%; }
.tw-plan-table th:nth-child(5) { width: 7.5%; }
.tw-plan-table th:nth-child(6) { width: 6.5%; }
.tw-plan-table th:nth-child(7) { width: 9.5%; }
.tw-plan-table th:nth-child(8) { width: 7%; }
.tw-plan-table th:nth-child(9) { width: 8.5%; }
.tw-plan-table th:nth-child(10) { width: 9%; }

/* La barra, más corta que la del resto de la aplicación: aquí comparte celda
   con el porcentaje y la columna vive entre cuatro de cifras. Con los 84 px de
   siempre no cabían los dos y el «0 %» salía cortado. */
.tw-plan-table .tw-progress {
    width: 56px;
}

/* Con `table-layout: fixed` una descripcion larga no ensancha la columna: se
   sale por encima de la siguiente. Se recorta aqui y no en el guion porque
   cortar por numero de caracteres depende del ancho de la ventana. */
.tw-plan-table > tbody > tr.tw-plan-row > td {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-plan-table > tbody > tr.tw-plan-group .tw-meta {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* El renglón entra debajo de su grupo, alineado con el título y no con la
   flecha: sin la sangría, la orden de compra y sus números de parte arrancan en
   la misma columna y la jerarquía sólo la sostiene el color de fondo. */
.tw-plan-table > tbody > tr.tw-plan-row > td:first-child {
    padding-left: 36px !important;
}

.tw-plan-table > tbody > tr.tw-plan-row .tw-meta {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* El encabezado se queda: una orden con cuarenta números de parte deja las
   columnas fuera de pantalla, y a media lista «8 400» no dice si son pedidas,
   nesteadas o cortadas. */
.tw-plan-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
}

.tw-plan-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Lo que falta es el número por el que se abre esta pantalla. */

/* --- Por reponer: lo que hay que cortar otra vez ---

   Era una nota en letra pequeña colgando de «Faltan» —«+8 por merma»— y tenía
   dos fallos: no sumaba en el encabezado del grupo, así que ochenta piezas
   muertas repartidas en treinta renglones no las decía nadie, y no se podía
   ordenar por ella. Desde el 24 de agosto de 2026 es una columna.

   Ámbar y no rojo: no es un error, es trabajo. La planta ya sabe que cortar de
   más cubre el desperdicio; lo que no sabía es cuánto. */
.tw-plan-recut {
    color: var(--tw-tag-orange-fg);
    font-weight: var(--tw-weight-semibold);
}

/* Y es un botón, porque abre el desglose. Se dibuja como la cifra que era: lo
   que invita a pulsarla es el subrayado al apuntar, no un borde — vive en una
   columna de números y un botón con marco ahí dentro se lee como un control
   suelto y no como el dato. */
button.tw-plan-recut {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-tag-orange-fg);
    cursor: pointer;
}

button.tw-plan-recut:hover {
    text-decoration: underline;
}

button.tw-plan-recut:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 2px;
    border-radius: var(--tw-radius-xs);
}

/* Y de qué se perdió, debajo del número.

   Porque el siguiente paso no es el mismo: la chatarra se repone cortando y una
   caja apartada sin resolver se repone **decidiendo**, y puede volver entera.
   Cortar acero por ella sin mirar es cortar de más sin saberlo. Va en pequeño y
   apagado: es de qué, no cuánto. El desglose completo está en el `title`. */
.tw-plan-causa {
    display: block;
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-regular, 400);
    color: var(--tw-font-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* La que no se pudo repartir entre los componentes de un ensamble: informa, no
   corrige —no manda cortar nada— y por eso va apagada en vez de en ámbar. */
.tw-plan-recut.es-sin-aplicar,
button.tw-plan-recut.es-sin-aplicar {
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-regular, 400);
    color: var(--tw-font-secondary);
}

/* ── El desglose de una merma ──────────────────────────────────────────
 *
 * Vive dentro del diálogo, así que no hereda nada de `.tw-plan-table`: aquí no
 * hay grupos que plegar ni columnas que cuadrar con las de otra tabla. Lo único
 * que comparte es la alineación de las cifras.
 */
.tw-merma-table {
    width: 100%;
    font-size: var(--tw-font-size-md);
}

.tw-merma-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background-color: var(--tw-bg-primary);
}

/* La fecha no se parte, y el motivo sí: es la única columna con una frase
   entera dentro y la que se viene a leer. */
.tw-merma-when {
    white-space: nowrap;
    color: var(--tw-font-secondary);
}

.tw-merma-why {
    color: var(--tw-font-secondary);
}

/* Lo que vuelve al flujo es lo único que resta en esta lista. En verde y con su
   signo, porque sin distinguirlo el desglose parece sumar más de lo que dice la
   cifra que lo abrió. */
.tw-merma-vuelve {
    color: var(--tw-tag-green-fg);
}

.tw-plan-num--falta {
    font-weight: var(--tw-weight-semibold);
}

/* --- El renglón de grupo --- */

.tw-plan-table > tbody > tr.tw-plan-group > td {
    padding: 0 8px !important;
    overflow: hidden;
    background-color: var(--tw-bg-secondary);
    border-bottom: 1px solid var(--tw-border-light);
    cursor: pointer;
}

.tw-plan-table > tbody > tr.tw-plan-group:hover > td {
    background-color: var(--tw-bg-quaternary);
}

/* Dos renglones, como los de detalle: identidad arriba y quién es debajo. En
   una sola línea, el número de la orden, la fecha y la razón social se
   disputaban una celda de 280 px y el cliente salía siempre cortado. */
.tw-plan-head {
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 40px;
    min-width: 0;
}

.tw-plan-id {
    min-width: 0;
    flex: 1 1 auto;
}

/* El hueco es de 12 y no de 8: la insignia de la fecha tiene su propio fondo
   y pegada al número de la orden los dos se leen como una sola cosa. */
.tw-plan-line {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

/* Los totales del grupo pesan más que los renglones que resumen. */
.tw-plan-table > tbody > tr.tw-plan-group .tw-plan-num {
    font-weight: var(--tw-weight-semibold);
}

.tw-plan-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    align-self: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 0;
    border-radius: var(--tw-radius-xs);
    background: transparent;
    color: var(--tw-font-tertiary);
}

.tw-plan-title {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    text-decoration: none;
    white-space: nowrap;
}

.tw-plan-title:hover {
    color: var(--tw-font-primary);
    text-decoration: underline;
}

.tw-plan-title {
    flex: 0 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* La fecha, con aire por los dos lados.

   Dos veces mal antes de quedar así. Pegada al número de la orden con un hueco
   fijo, la insignia —que trae su propio fondo— se leía como parte del número:
   un solo bloque. Empujada al extremo con `margin-left: auto` se separó del
   número y todas las fechas cayeron en la misma vertical, que es lo bueno, pero
   quedó a ocho píxeles del borde de la columna, encima de la raya que la separa
   del insumo: cambió de vecino, no de problema.

   El margen derecho es lo que faltaba. Deja 24 px libres hasta el contenido de
   la columna siguiente —más de los 16 que hay entre dos columnas de cifras— y
   la insignia queda suelta en su hueco, que es como tiene que verse algo que se
   lee de un vistazo y no se compara con lo de al lado. */
.tw-plan-when {
    flex: 0 0 auto;
    margin-left: auto;
    margin-right: 16px;
    padding-left: 12px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    white-space: nowrap;
}

/* El conteo de la planeación no se parte en tres renglones: la barra ya
   envuelve sus filtros, y un contador roto en mitad de una cifra se lee peor
   que un contador que empuja la barra un renglón más. */
#plan-conteo {
    white-space: nowrap;
}

/* ===========================================================================
   Qué placa armar ahora · la tira de sugerencias
   ===========================================================================

   La tabla de abajo contesta «qué falta». Esto contesta lo siguiente, que es lo
   que se pregunta con Lantek ya abierto: **con qué acero empiezo**. Va por
   insumo y no por orden de compra porque un programa es una lámina, y de una de
   calibre 16 sólo salen piezas de calibre 16.

   Vive entre la barra de vista y el cuerpo desplazable, y por eso `flex: 0 0
   auto`: es la cabecera de la decisión, no el primer renglón de la lista, y no
   se va con el desplazamiento.

   Tres tarjetas como mucho. Con diez deja de ser una sugerencia y vuelve a ser
   la tabla, que ya está debajo ordenada por lo mismo. */
.tw-plan-sug {
    display: flex;
    align-items: stretch;
    gap: 6px;
    flex: 0 0 auto;
    padding: 10px 12px;
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-plan-sug[hidden] {
    display: none;
}

/* Dos palabras, no una frase: nombra la tira y se calla. Lo que hay que saber
   lo dicen las tarjetas. */
.tw-plan-sug-label {
    display: flex;
    align-items: center;
    padding-right: 4px;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-tertiary);
    white-space: nowrap;
}

/* Es un botón porque hace algo: pone la pantalla en ese acero. Se dibuja como
   una tarjeta y no como un botón porque lo que lleva dentro son datos —código,
   cuántos números de parte, cuántas piezas— y no un rótulo. */
.tw-plan-sug-card {
    display: flex;
    align-items: center;
    /* Los huecos son de 8 y 6 y no de 10 y 12: con los tres cabiendo justo en un
       portátil de 1 512, esos diez píxeles son la diferencia entre leer «4 820
       piezas» y leer «4 820 piez…». */
    gap: 8px;
    /* Se reparten el renglón en vez de envolverse.

       Con `flex-wrap` y el ancho que pedía su contenido, la tercera tarjeta
       caía a un segundo renglón en cuanto la ventana bajaba de 1 600 px y la
       tira pasaba a medir 132 px de alto: una sugerencia no puede costar el
       quince por ciento de la pantalla. Repartidas, lo que se estrecha es el
       texto —que ya recorta— y no la altura.

       El tope es para el caso de una sola: estirada de lado a lado dejaría un
       código de insumo solo en medio de un metro de tarjeta. */
    flex: 1 1 240px;
    min-width: 0;
    max-width: 30rem;
    padding: 6px 10px 6px 6px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
    text-align: left;
    cursor: pointer;
}

.tw-plan-sug-card:hover {
    border-color: var(--tw-border-medium);
    background-color: var(--tw-bg-tertiary);
}

.tw-plan-sug-card:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 2px;
}

/* El número dice orden de verdad —primero éste, después aquél— y por eso lo
   llevan las tres y no una lista larga: un «1» sin «2» no ordena nada. */
.tw-plan-sug-rank {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-secondary);
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
}

.tw-plan-sug-body {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
}

.tw-plan-sug-title {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tw-plan-sug-meta {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Por qué se sugiere, y nada más: dos insignias como mucho. Con cuatro la
   tarjeta deja de leerse de un vistazo, que es lo único que tiene que hacer. */
.tw-plan-sug-tags {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    margin-left: auto;
}

/* ---------------------------------------------------------------------
   Planeación de embarques · la misma tabla, nueve columnas y casillas
   ---------------------------------------------------------------------

   Comparte todo con la planeación del corte —el grupo que se pliega, las
   cifras alineadas con las de sus renglones, el encabezado pegado— y cambia
   en dos cosas: son nueve columnas en vez de diez, y de aquí se marca.

   Los porcentajes salen de lo más largo que cabe en cada una: el rótulo «En
   embarque», una razón social, la lista de folios. Suman 100 para que no
   quede una columna colgando. */
/* Una columna menos que la del corte y cifras más cortas —piezas, no piezas por
   lámina—, así que cabe en menos. Con las 82rem heredadas, un portátil de 1280
   ya nacía con desplazamiento horizontal y la columna de folios quedaba fuera
   de pantalla sin que nada lo dijera. */
.tw-plan-table--emb {
    min-width: 62rem;
}

.tw-plan-table--emb th:nth-child(1) { width: 24%; }
/* La fecha, un punto más ancha que en la del corte: la insignia trae su propio
   fondo y con el 7 % justo se le comía la última letra con puntos suspensivos
   —«07 ago …»— en una ventana de portátil. */
.tw-plan-table--emb th:nth-child(2) { width: 8%; }
.tw-plan-table--emb th:nth-child(3) { width: 7.5%; }
.tw-plan-table--emb th:nth-child(4) { width: 9%; }
.tw-plan-table--emb th:nth-child(5) { width: 9.5%; }
.tw-plan-table--emb th:nth-child(6) { width: 9%; }
.tw-plan-table--emb th:nth-child(7) { width: 7.5%; }
.tw-plan-table--emb th:nth-child(8) { width: 9.5%; }
/* La novena la heredaba de la tabla del corte, y aquélla pasó a tener once
   columnas con otro reparto. Se declara aquí con el valor que ya tenía, para
   que añadir cifras a la otra pantalla no mueva ésta. */
.tw-plan-table--emb th:nth-child(9) { width: 9.5%; }

/* La casilla y la identidad, en la misma celda y sin que la casilla se
   encoja: con `flex` a secas, un número de parte largo la comprime hasta
   dejarla ovalada. */
.tw-plan-pick-cell {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.tw-plan-pick-cell .tw-plan-id {
    min-width: 0;
    overflow: hidden;
}

/* Ancho fijo aunque no haya casilla: el hueco lo ocupa un `<span>` vacío para
   que los renglones que sí se pueden marcar y los que no arranquen en la misma
   vertical. Sin esto, la columna se lee como dos listas mal alineadas.

   `flex-shrink: 0` no es adorno: dentro de un flex, una casilla de Bootstrap
   con un número de parte largo al lado se comprime hasta quedar ovalada. */
.tw-plan-pick {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    margin: 0;
}

input.tw-plan-pick {
    cursor: pointer;
}

/* El hueco entre la flecha, la casilla y el título del grupo.

   Con los 6 px que hereda de la planeación del corte —que ahí sólo separa la
   flecha del título— los tres elementos se leían como un solo bloque
   apelmazado, y la casilla parecía pegada a la flecha en vez de ser lo que es:
   la que marca el grupo entero. */
.tw-plan-table--emb .tw-plan-head {
    gap: 10px;
}

/**
 * El renglón cuelga del título de su grupo, no de su casilla.
 *
 * Con la sangría de 36 px que hereda, la casilla del renglón caía **en la misma
 * vertical** que la del grupo: una sola columna de casillas amontonadas donde
 * no se distinguía cuál marcaba una partida y cuál la orden entera. Los 58 px
 * la meten debajo del título del grupo, que es donde el ojo espera lo que
 * cuelga de él.
 */
.tw-plan-table--emb > tbody > tr.tw-plan-row > td:first-child {
    padding-left: 58px !important;
}

/* Lo que se puede subir hoy es el número por el que se abre esta pantalla. */
.tw-plan-num--listas {
    font-weight: var(--tw-weight-semibold);
}

/* Lo marcado se ve marcado, y desde lejos: al plegar los grupos hay que poder
   distinguir cuáles llevan algo elegido dentro sin abrirlos. El mismo tono que
   usa el selector del alta, que es donde acaba lo que aquí se marca. */
.tw-plan-table > tbody > tr.tw-plan-row.is-picked > td,
.tw-plan-table > tbody > tr.tw-plan-group.is-picked > td {
    background-color: var(--tw-accent-secondary);
}

/* ═══════════════════════════════════════════════════════════════════════
   El diálogo del avance · lo que hay debajo del porcentaje
   ═══════════════════════════════════════════════════════════════════════

   Lo abre la barra de la columna «Avance» de la planeación. La columna dice
   cuánto; el diálogo dice de dónde sale: dónde están las piezas ahora, en qué
   programas se cortaron, si Empaque ya las verificó, quién más espera el mismo
   número de parte y qué se perdió por el camino.

   Ver `public/scripts/nesting/plan-avance.js`. */

/* La barra es un botón, y se dibuja como la barra que era: lo que invita a
   pulsarla es que se ilumina al apuntar, no un marco. Un botón con borde
   dentro de una columna de cifras se lee como un control suelto y no como el
   dato — la misma decisión que tomó «Por reponer». */
.tw-plan-avance {
    display: inline-flex;
    align-items: center;
    padding: 2px 4px;
    margin-left: -4px;
    border: 0;
    border-radius: var(--tw-radius-xs);
    background: none;
    font: inherit;
    color: inherit;
    white-space: nowrap;
    cursor: pointer;
}

.tw-plan-avance:hover {
    background-color: var(--tw-bg-quaternary);
}

.tw-plan-avance:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 1px;
}

/* La misma barra cuando debajo lleva una palabra --la planeación del embarque
   dice «Sin embarcar» o «Todo embarcado» bajo la cifra--. Se apila en vez de
   ponerse en fila para que la palabra caiga bajo el porcentaje que explica, y
   no a su lado, donde parece otra columna. */
.tw-plan-avance--apilado {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    width: 100%;
}

/* --- Las cifras de arriba --- */

.tw-avance-cabecera {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-avance-barra {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Aquí la barra manda: es lo que se pulsó para llegar, y en un diálogo hay
   sitio de sobra para que se lea el tramo y no sólo el color. */
.tw-progress--ancha {
    width: 160px;
    height: 6px;
}

.tw-avance-pct {
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-primary);
}

/* Seis cifras en una rejilla y no en una tira que se arrastra: se comparan
   entre ellas —pedidas contra nesteadas contra cortadas— y para comparar hay
   que verlas a la vez. Por debajo de 720 px pasan a tres y luego a dos. */
.tw-avance-cifras {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 8px;
}

.tw-avance-cifras--cuatro {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 991.98px) {
    .tw-avance-cifras,
    .tw-avance-cifras--cuatro {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (max-width: 575.98px) {
    .tw-avance-cifras,
    .tw-avance-cifras--cuatro {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.tw-avance-cifra {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-secondary);
    min-width: 0;
}

.tw-avance-cifra-rotulo {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

.tw-avance-cifra-valor {
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    color: var(--tw-font-primary);
}

/* Qué significa la cifra, debajo. Es la mitad del valor de esta rejilla: seis
   números con su rótulo se confunden entre sí, y «lo que reparten los
   programas» no se confunde con «lo que declaró la máquina». */
.tw-avance-cifra-pie {
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-light);
    line-height: 1.3;
}

/* El tono va en el número y no en un borde de color a la izquierda: lo que
   distingue a estas dos es cuánto valen, no de qué tipo son. */
.tw-avance-cifra.es-falta .tw-avance-cifra-valor {
    color: var(--tw-tag-red-fg);
}

.tw-avance-cifra.es-reponer .tw-avance-cifra-valor {
    color: var(--tw-tag-orange-fg);
}

/* Verde y naranja son los dos tramos de la barra, y el azul es lo accionable:
   lo que se puede marcar y subir hoy desde esta misma pantalla. Lo pedido, lo
   impreso sin verificar y lo que falta por producir van sin color: no se pintan
   ni se tocan desde aquí. */
.tw-avance-cifra.es-embarcado .tw-avance-cifra-valor {
    color: var(--tw-tag-green-fg);
}

.tw-avance-cifra.es-cargado .tw-avance-cifra-valor {
    color: var(--tw-tag-orange-fg);
}

.tw-avance-cifra.es-listo .tw-avance-cifra-valor {
    color: var(--tw-accent);
}

/* --- Las secciones --- */

.tw-avance-seccion {
    padding-top: 16px;
    margin-top: 16px;
    border-top: 1px solid var(--tw-border-light);
}

.tw-avance-seccion:first-of-type {
    border-top: 0;
}

.tw-avance-seccion > .tw-section-title {
    margin-bottom: 10px;
}

/* El recorrido dentro del diálogo: sin el borde inferior de la ficha —aquí lo
   pone la sección— y con el redondeo de una caja suelta. */
.tw-avance-flow {
    margin-bottom: 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
}

.tw-flow.tw-avance-flow:last-of-type {
    padding-bottom: 14px;
    border-bottom: 1px solid var(--tw-border-light);
}

/* Las tablas del diálogo comparten alineación de cifras con el plan y nada
   más: aquí no hay grupos que plegar ni columnas que cuadrar con las de otra
   tabla. Se acotan a lo alto para que ninguna sección se coma el diálogo
   entero — con doscientos contenedores, llegar a la sección de abajo sería
   arrastrar durante un rato. */
.tw-avance-tabla {
    width: 100%;
    font-size: var(--tw-font-size-md);
}

.tw-avance-tabla thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background-color: var(--tw-bg-primary);
}

.tw-avance-seccion .table-responsive {
    max-height: 340px;
    overflow-y: auto;
}

/* La razón de una cancelación es una frase de oficina de dos renglones, y sin
   recortarla ensanchaba la tabla hasta obligar a arrastrarla de lado para
   llegar a las columnas de cifras. Entera, en el `title`. */
.tw-avance-razon {
    max-width: 22rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Un programa cancelado sale en la lista —esconderlo deja el renglón
   pareciendo un error del sistema— pero no cubre nada, y por eso se lee
   apagado: está para explicar, no para contar. */
.tw-avance-anulado > td {
    color: var(--tw-font-tertiary);
}

.tw-avance-anulado .tw-record-link {
    text-decoration: line-through;
}

/* La línea que se está mirando, dentro de la fila de espera de su pieza. */
.tw-avance-mia > td {
    background-color: var(--tw-accent-secondary);
}

.tw-avance-nota {
    margin-bottom: 10px;
}

/* --- Lo que se perdió --- */

.tw-avance-merma {
    display: flex;
    flex-direction: column;
    gap: 12px;
    align-items: flex-start;
}

.tw-avance-merma-total {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.tw-avance-merma-total .tw-avance-cifra-valor {
    color: var(--tw-tag-orange-fg);
}

/* De qué y dónde, en dos columnas: son las dos mitades de la misma pregunta y
   el siguiente paso depende de las dos. La chatarra se repone cortando; una
   caja apartada se repone decidiendo. */
.tw-avance-merma-listas {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px 24px;
    width: 100%;
}

@media (max-width: 575.98px) {
    .tw-avance-merma-listas {
        grid-template-columns: 1fr;
    }
}

.tw-avance-merma-linea {
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-secondary);
    line-height: 1.6;
}

.tw-avance-merma-linea strong {
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-primary);
}

/* ---------------------------------------------------------------------
   Barra de vista · el buscador y los filtros que no se amontonan
   ---------------------------------------------------------------------

   Dos arreglos que se veían en la pantalla de Nesteos y valen para toda
   barra que los use. */

/* El icono de la lupa está colocado en absoluto sobre el extremo derecho del
   campo, y el campo sólo reservaba 8 px de margen: un texto de sugerencia
   largo pasaba **por debajo** del icono. Se le deja el hueco del icono. */
.tw-search > .form-control {
    padding-right: 34px;
}

/* Los filtros van en su propia caja para que la barra parta —cuando tenga que
   partir— entre el bloque de filtros y el de acciones, y no a media hilera de
   controles. */
.tw-view-filters {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    flex-wrap: wrap;
}

/* Con seis controles no siempre cabe todo en 40 px de alto; la barra crece en
   vez de recortar, y el hueco entre renglones lo pone el `row-gap`. */
.tw-view-bar--wrap {
    flex-wrap: wrap;
    padding-top: 6px;
    padding-bottom: 6px;
    row-gap: 6px;
}

/* La barra sin su relleno lateral · para la que NO vive pegada al filo de una
   tarjeta.

   Los 12 px de `.tw-view-bar` son los de la tarjeta que la contiene. Dentro del
   cuerpo de una ficha —que ya pone sus 24 px— se suman a los de abajo: la
   cabecera del estimado salía 12 px a la derecha del primer número de parte de
   la lista que encabeza, y su buscador acababa 12 px antes que las barras. Aquí
   los pone el contenido, una sola vez, y la cabecera cae sobre su lista. */
.tw-view-bar--flush {
    padding-inline: 0;
}

/* ── Checklist del certificado ─────────────────────────────────────────
 *
 * Doce decisiones del mismo tipo, una por renglón. Antes cada punto llevaba
 * una botonera de Bootstrap con su verde y su rojo a todo volumen: treinta y
 * seis botones gritando en un panel que se lee dos veces al día. Aquí manda
 * el carril de `.tw-seg` —gris, con la opción activa levantada— y el color
 * aparece sólo en la opción ya decidida, que es la única noticia que hay.
 */

.mtc-check-rows {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    overflow: hidden;
}

.mtc-check-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 7px 12px;
    background-color: var(--tw-bg-primary);
    transition: background-color var(--tw-transition);
}

.mtc-check-row + .mtc-check-row {
    border-top: 1px solid var(--tw-border-light);
}

.mtc-check-row:hover {
    background-color: var(--tw-bg-secondary);
}

.mtc-check-row-label {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-primary);
}

/* Lo pendiente se ve pendiente: el texto baja de tono hasta que se decide. */
.mtc-check-row--pending .mtc-check-row-label {
    color: var(--tw-font-tertiary);
}

/* El icono dice el estado sin leer la botonera: palomita, tache o guion. */
.mtc-check-row-label .ti {
    flex: none;
}

.mtc-check-row--ok .mtc-check-row-label .ti {
    color: var(--tw-tag-green-fg);
}

.mtc-check-row--not_ok .mtc-check-row-label .ti {
    color: var(--tw-tag-red-fg);
}

.mtc-check-row--not_applicable .mtc-check-row-label .ti,
.mtc-check-row--pending .mtc-check-row-label .ti {
    color: var(--tw-font-tertiary);
}

/* El carril con tono: la opción activa se pinta según lo que significa. */
.mtc-check-row .tw-seg {
    flex: none;
}

.mtc-check-row .tw-seg > button[data-result='ok'].active {
    background-color: var(--tw-tag-green-bg);
    border-color: transparent;
    color: var(--tw-tag-green-fg);
}

.mtc-check-row .tw-seg > button[data-result='not_ok'].active {
    background-color: var(--tw-tag-red-bg);
    border-color: transparent;
    color: var(--tw-tag-red-fg);
}

/* La cabecera del checklist: título, avance y el atajo de marcar todo. */
.mtc-check-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

.mtc-check-head .tw-field-label {
    flex: none;
}

/* ── Visor de archivos ─────────────────────────────────────────────────
 *
 * El velo que abre «Ver certificado» sin irse de la pantalla. Las imágenes
 * las lleva fslightbox; esto es para el PDF, con el visor del navegador
 * dentro de un marco que respeta el tema.
 */

.tw-visor {
    position: fixed;
    inset: 0;
    z-index: 1090; /* por encima del modal de Bootstrap y del propio fslightbox */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background-color: rgba(0, 0, 0, 0.6);
}

.tw-visor-marco {
    display: flex;
    flex-direction: column;
    width: min(1100px, 100%);
    height: 100%;
    border-radius: var(--tw-radius-lg);
    background-color: var(--tw-bg-primary);
    box-shadow: var(--tw-shadow-strong);
    overflow: hidden;
}

.tw-visor-cabecera {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 8px 8px 16px;
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-visor-titulo {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

.tw-visor-titulo span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-visor-acciones {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: none;
}

.tw-visor-cuerpo {
    flex: 1;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--tw-bg-secondary);
}

.tw-visor-cuerpo iframe {
    width: 100%;
    height: 100%;
    border: 0;
}

/* El PDF dibujado con pdf.js: las páginas van una debajo de otra y el cuerpo
   desplaza. Sustituye al `<iframe>` porque el visor incrustado del navegador no
   está en todas partes —Safari de iPad se queda en la primera página— y ahí lo
   que se veía era un velo en blanco sin ningún error. */
.tw-visor-pdf {
    width: 100%;
    height: 100%;
    overflow: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    padding: 16px;
    -webkit-overflow-scrolling: touch;
}

.tw-visor-pdf iframe {
    width: 100%;
    height: 100%;
    border: 0;
}

.tw-visor-pagina {
    max-width: 100%;
    height: auto;
    /* Fondo blanco propio: un PDF con transparencias sobre el gris del velo se
       lee peor que sobre el papel que imita. */
    background-color: #fff;
    box-shadow: var(--tw-shadow-light);
}

.tw-visor-cargando {
    margin: auto;
    color: var(--tw-font-secondary);
}

.tw-visor-cuerpo img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

/* =====================================================================
   14. La tabla de una ficha, en tarjetas antes de tiempo.

   La forma de tarjeta existe porque ocho columnas no caben en un teléfono
   —ver el bloque de 767.98 px—, y el ancho que decide eso no es el de la
   ventana: es el de **la columna donde vive la tabla**. En una ficha
   (`.tw-record`) el panel de campos se lleva cuatrocientos píxeles fijos
   mientras la ventana pase de 992 px, así que a 1100 px de pantalla el
   contenido tiene seiscientos y pico: ancho de teléfono con aspecto de
   escritorio.

   Lo que pasaba ahí era exactamente lo que la forma de tarjeta existe para
   evitar: la tabla del recorrido se salía del panel, `.table-responsive`
   la convertía en una tira que se arrastra de lado y lo que quedaba fuera
   —a la izquierda— era la primera columna, el nombre del paso. Se leía
   «entrada · registra salida» sin saber de qué paso.

   Por eso el corte de esta tabla es 1200 y no 768. Las reglas son las
   mismas del bloque de teléfono; van escritas otra vez porque dos
   condiciones distintas no pueden compartir un `@media`.

   No alcanza a las tablas de listado (`table.dataTable`): ésas ocupan la
   pantalla entera y a 1100 px les sobra sitio.
   ===================================================================== */
@media (min-width: 768px) and (max-width: 1199.98px) {
    /* Y lo mismo dentro de un diálogo (`.modal-content`), por la misma razón:
       un diálogo mide ochocientos píxeles con la ventana en mil, así que sus
       tablas llegan a ancho de teléfono mucho antes que la pantalla. Arrastrar
       de lado una tabla de decisiones —la de liberar programas— esconde la
       primera columna, que es de qué programa se está decidiendo. */

    .tw-record table.tw-table-cards,
    .modal-content table.tw-table-cards,
    .tw-record table.tw-table-cards thead,
    .modal-content table.tw-table-cards thead,
    .tw-record table.tw-table-cards tbody,
    .modal-content table.tw-table-cards tbody,
    .tw-record table.tw-table-cards tr,
    .modal-content table.tw-table-cards tr,
    .tw-record table.tw-table-cards td,
    .modal-content table.tw-table-cards td {
        display: block;
        width: auto !important;
    }

    .tw-record table.tw-table-cards thead,
    .modal-content table.tw-table-cards thead {
        display: none;
    }

    .tw-record table.tw-table-cards tbody tr,
    .modal-content table.tw-table-cards tbody tr {
        margin: 0 0 8px;
        padding: 10px 12px;
        border: 1px solid var(--tw-border-light);
        border-radius: var(--tw-radius-md);
        background: var(--tw-bg-secondary);
    }

    .tw-record table.tw-table-cards tbody td,
    .modal-content table.tw-table-cards tbody td {
        height: auto !important;
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        justify-content: space-between;
        column-gap: 16px;
        row-gap: 2px;
        border: none !important;
        padding: 6px 0 !important;
        white-space: normal !important;
        overflow-wrap: anywhere;
        text-align: right;
        background-color: transparent !important;
        box-shadow: none !important;
    }

    .tw-record table.tw-table-cards tbody td > *,
    .modal-content table.tw-table-cards tbody td > * {
        min-width: 0;
        max-width: 100%;
    }

    .tw-record table.tw-table-cards tbody td[data-label]::before,
    .modal-content table.tw-table-cards tbody td[data-label]::before {
        content: attr(data-label);
        flex: 0 0 auto;
        color: var(--tw-font-light);
        font-size: var(--tw-font-size-xs);
        text-transform: uppercase;
        letter-spacing: 0.04em;
        text-align: left;
    }

    /* Toda línea que no sea la primera ocupa su renglón, bajo la columna de
       los valores: si no, cae a la izquierda pegada al rótulo de arriba y se
       lee como el dato de otro renglón. */
    .tw-record table.tw-table-cards tbody td:not(:first-child) > .tw-meta,
    .modal-content table.tw-table-cards tbody td:not(:first-child) > .tw-meta,
    .tw-record table.tw-table-cards tbody td:not(:first-child) > .small,
    .modal-content table.tw-table-cards tbody td:not(:first-child) > .small,
    .tw-record table.tw-table-cards tbody td:not(:first-child) > div:not(:first-child),
    .modal-content table.tw-table-cards tbody td:not(:first-child) > div:not(:first-child) {
        flex: 1 0 100%;
        text-align: right;
    }

    /* La primera celda es el título de la tarjeta: sus dos líneas se apilan a
       la izquierda y no llevan rótulo delante. */
    .tw-record table.tw-table-cards tbody td:first-child,
    .modal-content table.tw-table-cards tbody td:first-child {
        flex-direction: column;
        align-items: flex-start;
        text-align: left;
        font-weight: 600;
        font-size: var(--tw-font-size-md, 0.875rem);
        padding-bottom: 8px !important;
        margin-bottom: 4px;
        border-bottom: 1px solid var(--tw-border-light) !important;
    }

    .tw-record table.tw-table-cards tbody td:first-child::before,
    .modal-content table.tw-table-cards tbody td:first-child::before {
        display: none;
    }

    /* Sin tabla que desplazar, el envoltorio de scroll sobra: con él la
       tarjeta queda cortada dentro de un segundo scroll que nadie espera. */
    .tw-record .table-responsive:has(> table.tw-table-cards),
    .modal-content .table-responsive:has(> table.tw-table-cards),
    .modal-content .tw-table-scroll:has(> table.tw-table-cards) {
        overflow: visible;
    }
}

/* ---------------------------------------------------------------------
   El reparto de columnas del recorrido.

   Ocho columnas con `table-layout: auto` y el navegador reparte por
   contenido: «Estado» se quedaba con doscientos píxeles para una etiqueta de
   noventa mientras «SANTIAGO ESTEBAN RUIZ CARRILLO» se partía en tres
   renglones al lado. La tabla cabía —no había scroll— y aun así se leía
   apretada, que es el peor de los dos fallos porque no se ve en una medición.

   Así que se reparte a mano. Las seis columnas de dato corto —estado y los
   cuatro conteos— piden `width: 1%` y no envuelven: con `auto`, un porcentaje
   por debajo del contenido se lee como «lo mínimo», así que cada una se queda
   con lo suyo y ni un píxel más. Lo que sobra va a las tres que llevan texto,
   en la proporción en que se leen.

   Sólo donde hay tabla: por debajo de 1200 px esto son tarjetas y no hay
   columnas que repartir.
   --------------------------------------------------------------------- */
@media (min-width: 1200px) {
/* ── El renglón del programa, antes del primer paso ──────────────────────────

   No es una etapa: es de dónde salió el material. Se dibuja apagado y sobre el
   fondo secundario para que se lea como el encabezado de la cadena y no compita
   con los pasos, que son los que alguien firmó. Sin esa diferencia, un lector
   rápido cuenta tres pasos donde hay dos. */
.tw-steps-origen > td {
    background: var(--tw-bg-secondary);
    color: var(--tw-font-secondary);
}

.tw-steps-origen .fw-medium {
    font-weight: var(--tw-weight-regular, 400);
}

/* ── El desglose por número de parte, debajo de su paso ─────────────────────

   Iba dentro de las columnas de cifras, que miden cuatro caracteres: con seis
   números de parte cada celda se partía en una columna de texto y el total
   —que es lo que se viene a leer— quedaba enterrado encima. Aquí es un renglón
   ancho, pegado al paso del que habla: sin borde arriba, para que se lea como
   su continuación y no como una etapa más. */
.tw-steps-desglose > td {
    padding-top: 0;
    border-top: 0;
    background: var(--tw-bg-secondary);
}

    .tw-steps-table th:nth-child(1) {
        width: 20%;
    }

    .tw-steps-table th:nth-child(2),
    .tw-steps-table th:nth-child(3),
    .tw-steps-table th:nth-child(4),
    .tw-steps-table th:nth-child(5),
    .tw-steps-table th:nth-child(6) {
        width: 1%;
        white-space: nowrap;
    }

    .tw-steps-table th:nth-child(7) {
        width: 14%;
    }

    .tw-steps-table th:nth-child(8) {
        width: 26%;
    }
}

/* =====================================================================
   Pantallas bajas · la tablet en horizontal y la portátil de trece
   ---------------------------------------------------------------------
   Un iPad en horizontal mide 820 px de alto; un iPad de nueve pulgadas,
   768; una portátil de trece con el navegador puesto anda por los 800.
   Todo lo de esta hoja se escribió mirando un monitor de escritorio, que
   pasa de mil, y la diferencia no es de gusto: son doscientos píxeles
   que en un monitor sobran y aquí **no existen**.

   Se corta por ALTO y no por ancho, que es lo que hace que estas reglas
   valgan para las dos máquinas. Un iPad en horizontal es tan ancho como
   una portátil —1180 contra 1280— y por ancho ninguno de los dos se
   distingue de un escritorio; lo que los separa es que a los dos les
   falta el alto, y el alto es justo lo que se están comiendo las barras.

   La regla de toda esta sección es que **no se esconde nada**. Lo que
   cambia es la densidad: las mismas siete cifras, en un renglón en vez
   de tres, porque miden menos y no porque sobren cuatro. Lo único que se
   pliega son dos rótulos de botón que ya llevan su icono y su `title`.
   ===================================================================== */

@media (min-width: 901px) and (max-height: 920px) {
    /* ── La barra de título, en un renglón ────────────────────────────
     *
     * Con los rótulos puestos, los controles de la torre suman unos
     * 975 px sobre 944 disponibles: se pasaban por treinta píxeles y la
     * barra se partía en dos renglones de a 32. Plegando los dos
     * rótulos que sólo dicen lo que ya dice su icono, entra.
     *
     * El del sonido NO se pliega, y es a propósito: ese botón no dice
     * lo que hace sino en qué estado está, y «Toca para oír» —el estado
     * en el que el navegador todavía no deja sonar— es precisamente el
     * que hay que leer sin pasar el dedo por encima. */
    .torre-monitor-texto,
    #torre-refrescar .torre-btn-texto {
        display: none;
    }

    /* ── El pulso, en un renglón ──────────────────────────────────────
     *
     * Los siete bloques piden 160 px de base y envuelven: en 926 caben
     * cinco, así que salían tres renglones y 223 px de alto. La base
     * baja a 128 —lo que mide «2,644 piezas» con su rótulo encima— y
     * los siete entran en uno solo, de 74.
     *
     * `nowrap` con desplazamiento horizontal y no `wrap`: si en una
     * pantalla más estrecha no cupieran, la salida es arrastrarlos de
     * lado, que sigue enseñando los siete. Envolviendo volveríamos al
     * problema de partida, y encogiéndolos sin fondo la cifra se parte
     * a la mitad. La barra de desplazamiento se quita porque en una
     * tablet no se usa —se empuja con el dedo— y en el ratón el propio
     * corte de la última cifra dice que hay más. */
    .torre-pulso {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
    }

    .torre-pulso::-webkit-scrollbar {
        display: none;
    }

    .torre-pulso-bloque {
        flex: 1 0 128px;
        padding: 8px 12px;
    }

    /* La cifra grande NO se toca, y es lo único de esta sección que se
       midió y se dejó como estaba. Bajarla un escalón devolvía seis
       píxeles —de los doscientos setenta que hay que recuperar, seis— y
       a cambio la franja dejaba de leerse de pie desde la puerta, que es
       literalmente para lo que está puesta. Un renglón en vez de tres ya
       era todo el ahorro; encoger además el número habría sido apretar
       por apretar. */

    /* El pie no puede empujar el alto del renglón: «a este paso, cierra
       21:40» son dos líneas en una columna de 128 px, y las dos líneas
       las pagan los siete bloques. Se corta, y el bloque entero se abre
       de todas formas al pulsarlo. */
    .torre-pulso-pie {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* ── Las cifras y los filtros, en un renglón cada uno ─────────────
     *
     * Las dos barras envolvían por lo mismo y se arreglan igual: se
     * dejan de partir y lo que no quepa se arrastra de lado. Entre las
     * dos devuelven 106 px. */
    .plano-barra,
    .torre-barra-cargas {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
        padding-top: 7px;
        padding-bottom: 7px;
    }

    .plano-barra::-webkit-scrollbar,
    .torre-barra-cargas::-webkit-scrollbar {
        display: none;
    }

    /* Con `nowrap` alrededor, lo que no se declara encoge hasta
       desaparecer: el buscador se quedaría en un cuadrito y las
       pastillas de acotar se partirían por la mitad. */
    .torre-buscador {
        /* El mínimo es la mitad del trabajo: con `flex-shrink` suelto y sin
           suelo, la caja se encogía hasta el icono de la lupa —un cuadrito de
           treinta píxeles donde no cabe ni «KONE»— y el buscador de la torre
           dejaba de existir en la pantalla donde más falta hace. */
        flex: 0 1 18rem;
        min-width: 13rem;
    }

    .torre-barra-cargas > .tw-seg {
        flex: 0 0 auto;
    }

    .torre-barra-cuenta {
        flex: 0 0 auto;
        white-space: nowrap;
    }

    /* `.plano-barra` es el único sitio donde esta lista lleva el
       desplazamiento fuera, en su barra: aquí sólo hace falta que sus
       renglones dejen de partirse. */
    .plano-barra .tw-agg-list {
        flex-wrap: nowrap;
    }

    .plano-barra .tw-agg {
        flex: 0 0 auto;
        white-space: nowrap;
    }

    /* ── Y el mapa se queda con lo que devuelven ──────────────────────
     *
     * Sumado: 16 de la barra de título, 150 del pulso y 104 de las dos
     * barras. Doscientos setenta píxeles que no cambian de sitio, se
     * dejan de gastar, y van todos al único elemento de esta pantalla
     * que crece con lo que sobra. Medido en un iPad en horizontal
     * —1180 × 820, con el menú plegado— el mapa pasa de 586 × 330 a
     * 818 × 610: de la quinta parte de la pantalla a más de la mitad, y
     * dos veces y media de superficie.
     *
     * No lleva mínimo, y se probó a ponérselo. `.tw-panel` recorta con
     * `overflow: hidden`, así que un `min-height` aquí no empuja nada
     * hacia abajo: lo que sobresale se corta, y lo que sobresale es el
     * canto inferior del mapa, justo donde viven la botonera de zoom y
     * la leyenda. En una ventana de 500 px de alto se habría perdido la
     * única forma de acercar el plano. Repartir lo que hay es lo
     * correcto: al mapa le toca lo que sobre, y ahora sobra casi todo. */

    /* La leyenda, más apretada. En un mapa de 586 px de ancho sus ocho
       familias con 16 px de hueco medían 572: tapaban el dibujo de lado
       a lado. Con 10 caben en dos renglones cortos. */
    .plano-leyenda {
        gap: 10px;
        padding: 5px 8px;
    }
}

/* ── El lateral de la torre, en una pantalla que no es un monitor ─────
 *
 * 340 px fijos son el 17 % de un monitor de 1920 y el 37 % del panel de
 * un iPad: la misma columna que en un escritorio acompaña al mapa, aquí
 * competía con él. Baja a 288, que es lo que mide su renglón más largo
 * —«KONE INDUSTRIAL S.A. DE C.V.» con su cifra al lado— sin partirlo.
 *
 * Va por ancho y en su propia consulta porque esto no es un problema de
 * alto: una portátil ancha y baja quiere el lateral entero, y un monitor
 * vertical estrecho lo quiere corto aunque le sobre pantalla. */
@media (min-width: 901px) and (max-width: 1400px) {
    .plano-lateral {
        width: 288px;
        flex: 0 0 288px;
        padding: 12px;
    }
}

/* =====================================================================
   Pantallas estrechas de escritorio · la tablet y la portátil, por ancho
   ---------------------------------------------------------------------
   El mismo caso de arriba visto por el otro lado. La barra lateral de
   navegación se lleva 236 px fijos vaya donde vaya, y el resto de la
   hoja reparte lo que queda contando con que queda mucho: en un monitor
   de 1920 sobran 1668 y cualquier reparto sale bien; en un iPad en
   horizontal quedan 928 y los paneles fijos empiezan a pesar más que lo
   que enmarcan.

   Aquí no se recoloca nada: se ajustan las tres medidas fijas que se
   calcularon mirando un monitor. Nada de esto alcanza a las pantallas de
   planta —el asistente de las estaciones reparte en `fr` y ya nació para
   una tablet—, ni a los teléfonos, que tienen sus propias reglas por
   debajo de 992.
   ===================================================================== */

@media (min-width: 992px) and (max-width: 1400px) {
    /* La columna de campos de una ficha.
     *
     * 400 px de 928 son el 43 % del panel: la mitad de la pantalla para
     * quince rótulos cortos, y la otra mitad —donde viven los números de
     * parte, las órdenes y los embarques— peleando por 528. Con la
     * columna en 340 los rótulos siguen cabiendo enteros (el más largo,
     * «Lector de órdenes», mide 118) y las pestañas ganan sesenta
     * píxeles, que es justo lo que separa a la barra de pestañas de
     * tener que arrastrarse. */
    :root {
        --tw-record-panel-width: 340px;
    }

    /* El diseñador de etiquetas es de los pocos sitios con DOS rieles
       fijos: 268 + 268 dejaban 392 para el lienzo, que es menos que la
       etiqueta que se está diseñando. */
    .tw-designer-side,
    .tw-designer-inspector {
        flex: 0 0 232px;
        width: 232px;
    }
}

/* =====================================================================
   El menú plegado · el riel de iconos
   ---------------------------------------------------------------------
   La barra lateral mide 236 px y no los negocia: es el mismo ancho en un
   monitor de 1920 —donde es el 12 % y no se nota— que en un iPad en
   horizontal, donde es el 20 % y se nota mucho. Plegada mide 56, y esos
   180 px van enteros a la pantalla, porque el contenido empieza
   exactamente donde acaba el menú.

   TODO EL PLEGADO SALE DE UNA LÍNEA: `--tw-sidebar-width`. El ancho del
   aside y el margen del contenido ya se leían de ese testigo, así que
   cambiarlo mueve las dos cosas a la vez y no hay forma de que se
   desemparejen. Lo demás de esta sección es sólo lo que va dentro:
   apagar rótulos y centrar iconos.

   Quién decide si está plegada: `theme-boot.js`, antes de pintar. Qué
   pasa al pulsar el botón: `components/nav-plegar.js`. El nombre de cada
   icono llega ahí, como `title`, copiado del rótulo que se apaga.

   Sólo de 992 para arriba. Por debajo la barra no es una barra sino un
   cajón —`navbar-expand-lg` de Bootstrap—, no ocupa ancho y no hay nada
   que devolver.
   ===================================================================== */

/* ── El botón, al pie ─────────────────────────────────────────────────
 *
 * Un pie de verdad, no algo pegado con `sticky`.
 *
 * Pegado era lo primero que se probó y estaba mal: el aside se desplaza
 * —veinticinco renglones no caben en una pantalla baja— y un botón
 * pegado al canto de abajo se queda **encima** del renglón del menú que
 * en ese momento esté ahí. Se veía «Plegar el menú» y «Recepciones»
 * escritos uno sobre otro, y el renglón tapado no se podía pulsar.
 *
 * La forma correcta es la de siempre para una barra con pie: el aside es
 * una columna, la lista de pantallas se desplaza dentro de ella y el pie
 * se queda fuera de ese desplazamiento. Así el botón no tapa nada y el
 * último renglón del menú se alcanza igual. El reparto está más abajo,
 * en la consulta de 992.
 *
 * Fuera del riel también sirve, y por eso no está escondido en una
 * consulta de medios: en un monitor ancho hay quien prefiere el riel de
 * todas formas. */
.tw-nav-plegar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
    width: calc(100% - 12px);
    margin: 8px 6px;
    padding: 4px 8px;
    min-height: 30px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: var(--tw-bg-secondary);
    color: var(--tw-font-light);
    font-family: var(--tw-font-family);
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    text-align: left;
    cursor: pointer;
    transition: background-color 0.1s ease;
}

/* La línea que separa el pie de la lista. Va en el hueco de arriba y no
   como borde del botón: el botón tiene margen a los lados —es un renglón
   más del menú, con la misma caja que los demás— y un borde suyo habría
   sido una raya corta y descentrada en vez del canto de una franja. */
.tw-nav-plegar::before {
    content: '';
    position: absolute;
    left: -6px;
    right: -6px;
    top: -8px;
    height: 1px;
    background: var(--tw-border-light);
}

.tw-nav-plegar {
    position: relative;
}

.tw-nav-plegar:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

/* La misma caja fija de icono que `.nav-link-icon` y `.tw-nav-search__icon`:
   es lo que alinea la columna, y plegado es lo que centra el riel. */
.tw-nav-plegar__icono {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 var(--tw-icon-size);
    width: var(--tw-icon-size);
    height: var(--tw-icon-size);
}

.tw-nav-plegar__icono .ti {
    font-size: var(--tw-icon-size);
    line-height: 1;
}

/* Los dos iconos están puestos y se enseña el que toca. Lo decide la hoja
   —no el guion— para que no haya un fotograma con la flecha al revés: el
   estado ya está en el `<html>` desde antes de pintar. */
[data-icono-plegada],
html[data-nav='plegada'] [data-icono-abierta] {
    display: none;
}

html[data-nav='plegada'] [data-icono-plegada] {
    display: inline-block;
}

/* En un cajón de teléfono el menú no ocupa ancho: no hay nada que plegar,
   y el botón sería una opción que no hace nada. Y ahí quien se desplaza
   vuelve a ser el aside entero, que es como estaba antes de todo esto. */
@media (max-width: 991.98px) {
    .tw-nav-plegar {
        display: none;
    }

    .navbar-vertical.navbar-expand-lg {
        overflow-y: auto;
        /* El píxel que traía el estilo en línea del aside: deja respirar a la
           barra de desplazamiento contra el borde. Arriba de 992 se lo lleva
           `#sidebar-menu`, que es quien se desplaza allí. */
        padding-right: 1px;
    }
}

/* ── La barra, repartida en tres ──────────────────────────────────────
 *
 * Arriba la identidad, en medio la lista y abajo el pie. Sólo la de en
 * medio se desplaza.
 *
 * Antes se desplazaba el aside entero, y con eso el pie no tenía dónde
 * ponerse: iba dentro del mismo bloque desplazable que la lista, así que
 * o se iba con ella —y había que bajar veinticinco renglones para
 * encontrar el botón— o se quedaba pegado por encima de un renglón,
 * tapándolo. Repartiendo, las dos cosas se arreglan a la vez y ninguna
 * pelea con la otra.
 *
 * `min-height: 0` en la del medio es lo que hace que se desplace: sin
 * él, una caja flexible mide lo que mide su contenido y crece por debajo
 * del pie en vez de recortarse.
 *
 * Sólo de 992 para arriba. Por debajo esto es un cajón que Bootstrap
 * abre y cierra con `display`, y meterle un reparto de columna encima
 * sería discutirle su propio componente. */
@media (min-width: 992px) {
    .navbar-vertical.navbar-expand-lg {
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }

    .navbar-vertical.navbar-expand-lg > .container-fluid {
        display: flex;
        flex-direction: column;
        flex: 1 1 auto;
        min-height: 0;
    }

    .navbar-vertical.navbar-expand-lg > .container-fluid > #sidebar-menu {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        /* El desplazamiento es de esta caja, así que la barra sale aquí
           dentro: pegada al borde de la lista y no al del aside. */
        padding-right: 1px;
    }

    /* La cabecera dejó de necesitar el `sticky` que traía en la plantilla
       —ya no se desplaza nada por debajo de ella— pero se queda puesto:
       es inofensivo y quitarlo desde aquí sería pelear con un estilo en
       línea. Lo que sí hace falta es que no encoja. */
    .navbar-vertical.navbar-expand-lg > .container-fluid > .navbar-brand {
        flex: 0 0 auto;
    }
}

@media (min-width: 992px) {
    /* Aquí está todo el plegado. */
    html[data-nav='plegada'] {
        --tw-sidebar-width: 56px;
    }

    /* ── Y se mueve ───────────────────────────────────────────────────
     *
     * Ciento ochenta píxeles que aparecen de un fotograma al siguiente se
     * leen como un fallo de pintado, no como una respuesta: la pantalla
     * entera da un salto lateral y hay que volver a buscar dónde estaba
     * lo que se estaba mirando. Con la transición, el contenido acompaña
     * al menú y el ojo lo sigue sin perderlo.
     *
     * Se anima **la medida y nada más** —el ancho del aside y el margen
     * del contenido—; los rótulos de dentro se apagan de golpe, porque
     * un texto desvaneciéndose dentro de una caja que se encoge se ve
     * apretujarse antes de irse.
     *
     * La duración y la curva son las de la casa: esto es lo mismo que
     * hace cualquier otro panel que se abre, y una animación con
     * personalidad propia en el marco de la aplicación es exactamente lo
     * que hace que una pantalla parezca dos. */
    .navbar-vertical.navbar-expand-lg,
    .navbar-vertical.navbar-expand-lg ~ .page-wrapper,
    .navbar-vertical.navbar-expand-lg + .page-wrapper {
        transition:
            width var(--tw-duration-base) var(--tw-ease),
            margin-left var(--tw-duration-base) var(--tw-ease);
    }

    /* ── Lo que se apaga ──────────────────────────────────────────────
     *
     * Los rótulos, el atajo del buscador y el nombre de quien tiene la
     * sesión. Ninguno desaparece de verdad: el del renglón viaja al
     * `title` y el de la sesión sigue a un clic en las iniciales, que es
     * lo que abre su menú. */
    html[data-nav='plegada'] .navbar-vertical .nav-link-title,
    html[data-nav='plegada'] .tw-nav-search__label,
    html[data-nav='plegada'] .tw-nav-search__kbd,
    html[data-nav='plegada'] .tw-nav-plegar__texto,
    html[data-nav='plegada'] .navbar-brand .chevron {
        display: none;
    }

    /* El nombre de quien tiene la sesión lleva `d-flex` de Bootstrap, que es
       `display: flex !important`: sin repetir la marca, esto no apagaba nada y
       el nombre se salía del riel empujando las iniciales fuera de la
       pantalla. Es el único sitio de la sección donde hace falta. */
    html[data-nav='plegada'] .navbar-brand .flex-fill {
        display: none !important;
    }

    /* ── Los encabezados de sección ───────────────────────────────────
     *
     * Se van enteros y en su sitio queda una línea. Un encabezado de
     * sección plegado a un icono sería un icono que no lleva a ninguna
     * pantalla, y sin él la lista necesita igual que se vea dónde acaba
     * un grupo y empieza el siguiente.
     *
     * Y las secciones se abren todas. Es la contrapartida obligada: sin
     * encabezado no hay dónde pulsar para abrirlas, así que una sección
     * que alguien dejó cerrada se volvería inalcanzable —sus pantallas
     * seguirían en el HTML y no habría manera de llegar—. Se deja como
     * está: al desplegar el menú, cada grupo vuelve a como lo dejaron. */
    /* La especificidad va completa —hasta `.navbar-expand-lg`— porque el
       encabezado ES un `.nav-link`, y la regla que le da geometría a los
       renglones del menú lleva cuatro clases y `display: flex`. Con el
       selector corto, el botón se quedaba puesto y de él sólo se veía el
       galón: el riel salía con una punta de flecha suelta flotando entre
       dos grupos de iconos, que no era ni un icono ni un separador. */
    html[data-nav='plegada'] .navbar-vertical.navbar-expand-lg .navbar-nav .tw-nav-section__toggle {
        display: none;
    }

    /* ── Y lo que separa los grupos ───────────────────────────────────
     *
     * Una raya de canto a canto de 56 px es un tajo: parte el riel en
     * cachos y pesa más que los iconos que está separando, que son lo
     * que hay que mirar. La marca es corta y centrada —24 px, del mismo
     * gris que cualquier otro borde— y se lee como una pausa.
     *
     * Va delante de **cada** grupo y no sólo entre dos: el primero viene
     * detrás de «Inicio», que no es un grupo, y sin la marca ahí el
     * arranque del riel se leía como un bloque de tres cosas sueltas. */
    html[data-nav='plegada'] .tw-nav-section {
        position: relative;
        margin-top: 9px;
        padding-top: 9px;
    }

    html[data-nav='plegada'] .tw-nav-section::before {
        content: '';
        position: absolute;
        top: 0;
        left: 50%;
        width: 24px;
        height: 1px;
        transform: translateX(-50%);
        background: var(--tw-border-light);
    }

    html[data-nav='plegada'] .tw-nav-section.is-collapsed .tw-nav-section__items > li {
        display: block;
    }

    /* ── Lo que se centra ─────────────────────────────────────────────
     *
     * En 56 px el renglón deja de ser «icono y texto» y pasa a ser un
     * icono: se centra, y el hueco entre los dos —que ya no separa
     * nada— se quita. El `me-2` de Tabler va dentro del icono y también
     * sobra: sin apagarlo, el icono queda ocho píxeles a la izquierda
     * del centro y la columna se ve torcida. */
    html[data-nav='plegada'] .navbar-vertical.navbar-expand-lg .navbar-nav .nav-link,
    html[data-nav='plegada'] .tw-nav-search,
    html[data-nav='plegada'] .tw-nav-plegar {
        justify-content: center;
        gap: 0;
        padding-left: 0 !important;
        padding-right: 0 !important;
    }

    html[data-nav='plegada'] .navbar-vertical .nav-link-icon .ti {
        margin-right: 0 !important;
    }

    /* ── Las insignias ────────────────────────────────────────────────
     *
     * Sin renglón donde ponerse a la derecha, el número se monta en la
     * esquina del icono. Sigue siendo el número y no un punto: «12
     * solicitudes en el mostrador» y «una» piden cosas distintas, y el
     * riel es precisamente donde no se puede entrar a mirar.
     *
     * La suma de la sección no: su encabezado no existe aquí, y el
     * número de cada bandeja ya está puesto en su propio icono. */
    html[data-nav='plegada'] .navbar-vertical .nav-link {
        position: relative;
    }

    html[data-nav='plegada'] .tw-nav-badge {
        position: absolute;
        /* Anclada al icono y no al canto de arriba: el renglón mide 28 px
           con ratón y 44 con el dedo —la regla del puntero grueso—, y con
           `top` fijo la píldora quedaba encima del icono en un caso y
           flotando muy por encima en el otro. Medido desde el centro,
           cae en la esquina del icono en los dos. */
        top: calc(50% - 15px);
        right: 4px;
        margin: 0;
        /* El aro es del color de la barra: separa la píldora del icono que
           tiene debajo. Sin él, un «12» sobre un trazo se lee como parte
           del dibujo. */
        box-shadow: 0 0 0 2px var(--tw-bg-secondary);
    }

    /* Los dos números de «Recorridos» —lo que espera y lo que ya se pasó de
       plazo— son dos cosas distintas y en el riel se pisaban: las dos
       píldoras caían exactamente en la misma esquina y sólo se veía la de
       encima. Se reparten las esquinas de la derecha. Siguen siendo dos
       cifras y no una suma: sumarlas diría que hay quince cosas que
       atender cuando doce se resuelven trabajando y tres ya no. */
    html[data-nav='plegada'] .tw-nav-badge--espera {
        top: auto;
        bottom: calc(50% - 15px);
    }

    html[data-nav='plegada'] .tw-nav-badge--total {
        display: none;
    }

    /* ── La cabecera ──────────────────────────────────────────────────
     *
     * Quedan las iniciales, centradas, y siguen abriendo el menú de la
     * cuenta. El desplegable se ancla a la izquierda de la pantalla con
     * medidas fijas —está escrito en la plantilla—, así que plegado se
     * sale del riel y no pasa nada: flota por encima, que es lo que hace
     * un desplegable. */
    html[data-nav='plegada'] .navbar-brand {
        padding: 0 !important;
    }

    html[data-nav='plegada'] .navbar-brand .dropdown {
        width: 100% !important;
    }

    html[data-nav='plegada'] .navbar-brand .brand-initials {
        margin-right: 0 !important;
    }

    html[data-nav='plegada'] .navbar-brand [data-bs-toggle='dropdown'] {
        justify-content: center;
    }
}

@media (prefers-reduced-motion: reduce) {
    .navbar-vertical.navbar-expand-lg,
    .navbar-vertical.navbar-expand-lg ~ .page-wrapper,
    .navbar-vertical.navbar-expand-lg + .page-wrapper {
        transition: none;
    }
}

/* =====================================================================
   Filtros de una lista · el panel de estados
   =====================================================================

   Lo que acota la lista vive detrás del botón «Filtros» de la barra de vista,
   y se abre **anclado a ese botón**, no en medio de la pantalla. Un modal para
   marcar cinco casillas tapa la tabla que se está intentando limpiar: la
   decisión se toma mirando lo que hay debajo, y para eso hay que seguir
   viéndolo.

   Va colgado de `<body>` y posicionado a mano —`position: fixed`— porque
   `.tw-panel` recorta su contenido (`overflow: hidden`) y dentro de la barra el
   panel se cortaría por el borde de la tarjeta. La medida la calcula
   `filtro-estados.js` contra el botón, con vuelta arriba cuando abajo no cabe.

   Las formas son las del menú del sistema: el mismo borde, la misma sombra, el
   mismo radio y la misma entrada de `.dropdown-menu`, para que se lea como una
   pieza más y no como una ventana prestada.
   ===================================================================== */

.tw-filtro-panel {
    position: fixed;
    z-index: 1065; /* sobre el panel y la barra, bajo el diálogo (1055 + backdrop) */
    display: flex;
    flex-direction: column;
    width: 248px;
    background-color: var(--tw-bg-primary);
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    box-shadow: var(--tw-shadow-strong);
    overflow: hidden;
    animation: tw-menu-in var(--tw-duration-fast) var(--tw-ease) both;
}

/* `display: flex` de arriba gana a `[hidden]`, que es sólo `display: none`. */
.tw-filtro-panel[hidden] {
    display: none;
}

/* El encabezado dice de qué se está filtrando y ofrece el gesto de todo o nada,
   que es el que se busca cuando lo que se quiere es volver a verlo todo. */
.tw-filtro-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 10px 4px;
}

.tw-filtro-title {
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--tw-font-tertiary);
}

.tw-filtro-todos {
    padding: 2px 6px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: none;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    transition:
        background-color var(--tw-duration-fast) var(--tw-ease),
        color var(--tw-duration-fast) var(--tw-ease);
}

.tw-filtro-todos:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

/* Se acota por alto y no por número de opciones: doce estados de una orden de
   producción caben desplazándose, y un panel que crece hasta el borde de la
   pantalla deja el botón «Aplicar» fuera de la vista. */
.tw-filtro-lista {
    display: flex;
    flex-direction: column;
    padding: 0 4px 4px;
    max-height: 288px;
    overflow-y: auto;
}

/* El renglón entero es la etiqueta: se marca pulsando en cualquier parte, que
   es lo único que funciona con el dedo en la tablet. 32 px, como la fila de
   tabla del sistema. */
.tw-filtro-op {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 32px;
    margin: 0;
    padding: 2px 6px;
    border-radius: var(--tw-radius-sm);
    cursor: pointer;
    transition: background-color var(--tw-duration-fast) var(--tw-ease);
}

.tw-filtro-op:hover,
.tw-filtro-op:focus-within {
    background-color: var(--tw-bg-transparent-light);
}

.tw-filtro-op .form-check-input {
    flex: 0 0 auto;
    margin: 0;
}

/* El pie dice cuántos estados quedan dentro —la cuenta que el botón resume— y
   lleva la única acción que cambia la pantalla. */
.tw-filtro-pie {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px;
    border-top: 1px solid var(--tw-border-light);
}

.tw-filtro-cuenta {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

@media (prefers-reduced-motion: reduce) {
    .tw-filtro-panel {
        animation: none;
    }
}


/* ══ La bitácora · la planta por sus ejes de trabajo ═════════════════════
   Ver `tower-logbook.ts`. Un carril por persona, programa o número de parte,
   con sus cifras arriba y lo que se marcó dentro. */

.torre-bitacora {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1 1 auto;
    /* El canal es de la vista y no de cada renglón.
     *
     * Sin él la tabla se apoyaba en el borde del panel: la última columna —la
     * hora— quedaba pegada al canto mientras todo lo de arriba —la barra de
     * filtros, el buscador, las cifras— respiraba sus 12 px. Puesto aquí, las
     * filas se quedan con un margen corto propio y la suma da el mismo canal
     * que el resto de la pantalla, que es lo que hace que las cuatro vistas se
     * vean de la misma familia. */
    padding: 12px 12px 0;
}

.torre-bitacora-barra {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 12px;
    flex: 0 0 auto;
    flex-wrap: wrap;
}

/* La lista se desplaza por dentro: el alto lo pone el panel, como en el
   tablero, para que la barra de ejes no se vaya con el desplazamiento. */
.torre-bitacora-lista {
    display: flex;
    flex-direction: column;
    gap: 0;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-bottom: 12px;
}

/* ── El carril · un renglón de tabla ──────────────────────────────────
 *
 * Eran tarjetas apiladas y en un monitor grande cada una gastaba 1450 px de
 * ancho para usar 400. Y lo que se viene a hacer aquí es comparar —qué programa
 * rinde menos, qué parte rechaza más—, que con los números cayendo en un sitio
 * distinto de cada tarjeta obliga a buscarlos uno por uno. En columnas, comparar
 * es bajar la vista.
 *
 * El reparto lo declara `COLUMNAS_BITACORA` en el guion y **tiene que casar con
 * esto**: la cabecera se pinta desde allí, y desemparejados los rótulos dejan de
 * caer sobre sus cifras. Las tres primeras son texto y crecen; las de números
 * miden lo suyo y no se mueven, que es lo que mantiene la columna leíble. */

.torre-grupo-titulos,
.torre-grupo-fila {
    /* La misma caja en los dos, dicho a mano.
     *
     * La cabecera es un `div` y el renglón un `button`, y les tocaban cajas
     * distintas: la primera medía por contenido y el segundo por borde, así que
     * con el mismo `padding` de 12 la cabecera salía **24 px más ancha**. El
     * efecto es el que no perdona en una tabla: los rótulos dejan de caer encima
     * de sus cifras —cada uno corrido hacia la derecha— y de paso la lista
     * desbordaba a lo ancho. Dos rejillas que tienen que casar no pueden
     * heredar su caja de sitios distintos. */
    box-sizing: border-box;
    display: grid;
    grid-template-columns:
        18px
        minmax(180px, 2.4fr)
        /* Mínimo propio, y no cero: con `minmax(0, …)` esta columna cedía todo
           su ancho al nombre y la pastilla del turno se cortaba a media palabra
           —«desde 10:»—, que se lee como algo roto y no como algo recortado. */
        minmax(96px, 1fr)
        72px 84px 96px 76px
        72px 96px 64px;
    align-items: center;
    gap: 10px;
    width: 100%;
}

/* La cabecera. Pegajosa: con treinta renglones, saber qué columna se está
   mirando no puede depender de no haberse desplazado. */
.torre-grupo-titulos {
    position: sticky;
    top: 0;
    z-index: 2;
    /* El mismo margen lateral que el renglón, o las columnas dejan de casar. */
    padding: 4px 4px 6px;
    background: var(--tw-bg-primary);
    border-bottom: 1px solid var(--tw-border-light);
    color: var(--tw-font-light);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.torre-grupo-titulos > span {
    text-align: right;
}

.torre-grupo-titulos > span:nth-child(-n + 3) {
    text-align: left;
}

.torre-grupo {
    position: relative;
    border-bottom: 1px solid var(--tw-border-light);
}

.torre-grupo:hover {
    background: var(--tw-bg-secondary);
    border-radius: var(--tw-radius-sm, 4px);
}

/* Todo el renglón abre: un triangulito de 12 px es un blanco que en una tablet
   se falla la mitad de las veces. */
.torre-grupo-fila {
    padding: 7px 4px;
    border: 0;
    background: none;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.torre-grupo-fila[disabled] {
    cursor: default;
}

/* La flecha ocupa su hueco aunque no exista · si no, la columna de nombres
   baila renglón sí, renglón no. Es celda de la retícula, no un margen. */
.torre-grupo-flecha {
    display: inline-block;
    width: 18px;
    color: var(--tw-font-light);
    transition: transform var(--tw-transition);
}

.torre-grupo.es-abierto .torre-grupo-flecha {
    transform: rotate(90deg);
}

.torre-grupo-quien {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 1px;
}

.torre-grupo-nombre {
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.torre-grupo-pie {
    color: var(--tw-font-secondary);
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.torre-grupo-estado {
    min-width: 0;
    overflow: hidden;
}

/* Las cifras · alineadas a la derecha y con cifras de ancho fijo, que es lo
   que deja comparar una columna sin leerla número a número. */
.torre-grupo-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.torre-grupo-num.es-buena {
    color: var(--tw-solid-green);
}

.torre-grupo-num.es-mala {
    color: var(--tw-solid-red);
}

/* La raya que separa el corte de los procesos · son las mismas piezas contadas
   un paso después y no se suman, así que tampoco se tocan. */
.torre-grupo-num.es-procesos,
.torre-grupo-titulos > span:nth-child(8) {
    border-left: 1px solid var(--tw-border-light);
    padding-left: 10px;
    margin-left: -4px;
}

.torre-grupo-ultima {
    color: var(--tw-font-secondary);
}

/* El cuerpo arranca donde arranca el nombre, no en el borde: así se lee como
   lo que cuelga de ese renglón y no como una sección nueva. */
.torre-grupo-cuerpo {
    display: none;
    padding: 2px 4px 12px 32px;
}

.torre-grupo.es-abierto .torre-grupo-cuerpo {
    display: block;
}

.torre-grupo.es-abierto {
    background: var(--tw-bg-secondary);
}

/* Ir a la ficha, sin robarle el clic a abrir el carril. */
.torre-grupo-ir {
    position: absolute;
    top: 8px;
    right: 4px;
    color: var(--tw-font-light);
    opacity: 0;
    transition: opacity var(--tw-transition);
}

.torre-grupo:hover .torre-grupo-ir,
.torre-grupo-ir:focus-visible {
    opacity: 1;
}

/* ── Y el mismo carril en corto, para el lateral del plano ────────────
 *
 * 340 px no dan para una tabla, así que ahí se sigue dibujando en pastillas:
 * es el mismo dato y la misma función, y lo que cambia es cuánto sitio hay. */

.torre-grupo.es-compacto {
    display: block;
    width: 100%;
    text-align: left;
    font: inherit;
    cursor: pointer;
    padding: 8px 10px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    text-decoration: none;
    color: inherit;
}

.torre-grupo.es-compacto:hover {
    border-color: var(--tw-border-strong);
}

.torre-grupo.es-compacto .torre-grupo-pie {
    font-size: var(--tw-font-size-sm, 12px);
}

.torre-grupo.es-compacto .torre-grupo-cab {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

.torre-grupo-tasa {
    color: var(--tw-font-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.torre-grupo-tasa.es-alerta {
    color: var(--tw-solid-red);
}

.torre-grupo-cifras {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 6px;
}

.torre-grupo-cifras:empty {
    display: none;
}

/* «en procesos» · lo que separa dos cifras que se llaman igual.

   Un carril puede traer «2 rechazadas» del corte y «2 rechazadas» de los pasos
   cerrados: son hechos distintos sobre piezas distintas, y pegados parecen el
   mismo número repetido. */
.torre-grupo-rotulo {
    color: var(--tw-font-light);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    align-self: center;
    margin-left: 6px;
}

/* ── El desglose por el otro eje ─────────────────────────────────────── */

.torre-grupo-desglose {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    padding-bottom: 8px;
    font-size: 12px;
}

.torre-grupo-parte {
    display: flex;
    gap: 6px;
    color: var(--tw-font-secondary);
}

.torre-grupo-parte-cifra {
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-primary);
}

.torre-grupo-parte-cifra em {
    color: var(--tw-solid-red);
    font-style: normal;
}

/* ── Un acto · qué hizo y qué números marcó ──────────────────────────── */

.torre-grupo-actos {
    display: flex;
    flex-direction: column;
    gap: 2px;
    border-top: 1px solid var(--tw-border-light);
    padding-top: 8px;
}

.torre-acto {
    display: grid;
    grid-template-columns: auto 16px 1fr auto;
    align-items: start;
    gap: 8px;
    padding: 4px 6px;
    border-radius: var(--tw-radius-sm, 4px);
    font-size: 12px;
    line-height: 1.35;
}

.torre-acto:hover {
    background: var(--tw-bg-tertiary, var(--tw-bg-secondary));
}

/* La hora va **delante** y no al final, al revés que en el registro: aquí los
   renglones son de una sola persona y lo que se recorre es su día en orden. */
.torre-acto-hora {
    color: var(--tw-font-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.torre-acto > .ti {
    color: var(--tw-font-light);
    margin-top: 1px;
}

/* Suelo cero: sin esto una nota de cierre larga estira la retícula y se lleva
   la hora fuera de la pantalla. Es el mismo suelo que necesita el registro. */
.torre-acto-cuerpo {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.torre-acto-titulo {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
}

/* Los números marcados van en pastillas y no dentro de la frase: en una columna
   de treinta renglones, «108» metido en el texto se lee al mismo ritmo que el
   texto, y suelto se encuentra de un vistazo. */
.torre-acto-cifras {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.torre-acto-ir {
    border: 0;
    background: none;
    color: var(--tw-font-light);
    cursor: pointer;
    padding: 0;
}

.torre-acto-ir:hover {
    color: var(--tw-font-primary);
}

/* ── Y el mismo carril, en corto, junto al mapa ──────────────────────── */

.torre-lateral-ejes {
    display: flex;
    gap: 6px;
    margin-bottom: 10px;
    flex-wrap: wrap;
}

.torre-grupo.es-compacto {
    display: block;
    width: 100%;
    text-align: left;
    font: inherit;
    cursor: pointer;
    padding: 8px 10px;
    text-decoration: none;
    color: inherit;
}

.torre-grupo.es-compacto:hover {
    border-color: var(--tw-border-strong);
}

.torre-lateral-mas {
    border: 0;
    background: none;
    color: var(--tw-font-secondary);
    font-size: 12px;
    text-align: left;
    padding: 4px 2px;
    cursor: pointer;
}

.torre-lateral-mas:hover {
    color: var(--tw-font-primary);
}

/* ══ Las etiquetas · el registro serial por serial ═══════════════════════
   Ver `print-logs/print-feed.ts`. Un renglón por etiqueta impresa, sin agrupar:
   es la única vista de la torre cuya unidad es una etiqueta y no un sitio, un
   contenedor ni una persona.

   Hereda la caja de la bitácora —el canal lateral, la barra que no se va con el
   desplazamiento, la lista que se desplaza por dentro— porque son dos lecturas
   de la misma pantalla y deben verse de la misma familia. Lo que cambia es el
   reparto de columnas, que aquí lo manda `COLUMNAS_ETQ`. */

.torre-etq {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1 1 auto;
    padding: 12px 12px 0;
}

.torre-etq-barra {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 12px;
    flex: 0 0 auto;
    flex-wrap: wrap;
}

/* El buscador crece con la barra hasta un tope: pegado a los conmutadores se
   lee como parte de ellos, y a lo ancho de un monitor de cincuenta pulgadas un
   campo de texto de mil píxeles para teclear un serial es absurdo. */
.torre-etq-buscar {
    flex: 1 1 220px;
    max-width: 26rem;
    min-width: 180px;
}

/* Y el desplazamiento horizontal va **en la lista**, que aquí es la tabla, y
   nunca en el panel: el panel lleva encima la barra de puestos y el buscador, y
   arrastrarlos a un lado los deja leyéndose cortados por la izquierda.
   Es la red de seguridad para el hueco que deja el corte de 1200 px: entre ése
   y 1400, con el menú lateral abierto a mano, las siete columnas no caben. El
   `min-content` lo llevan también los rótulos, o la cabecera se quedaría quieta
   mientras los renglones se van. */
.torre-etq-lista {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: auto;
    padding-bottom: 12px;
}

/* La misma caja en la cabecera y en el renglón, dicho a mano: uno es un `div` y
   el otro un `a`, y con cajas distintas los rótulos dejan de caer sobre sus
   datos. Es el mismo cuidado que en `.torre-grupo-titulos`.

   Y NINGUNA COLUMNA MIDE POR CONTENIDO. Cada renglón es su propia retícula
   —sesenta enlaces hermanos, no una tabla—, así que una sola pista en `auto` o
   `max-content` se resuelve distinta en cada uno: basta con que a un renglón le
   toquen dos pastillas de estado en vez de una para que su columna crezca, le
   robe el sitio a las de `fr` y ese renglón salga corrido respecto a los otros
   cincuenta y nueve. Se vio en la primera prueba con datos variados. Todas las
   pistas son fijas o `minmax(px, fr)`, que se resuelven igual en los sesenta. */
.torre-etq-titulos,
.torre-etq-fila {
    box-sizing: border-box;
    display: grid;
    grid-template-columns:
        /* El serial mide lo que mide y no crece: dieciséis caracteres caben en
           su mínimo, y el sobrante hace falta donde sí falta, que es el nombre
           de quien firmó —«Felisa Del Carmen Morales (puesto)» se cortaba—. */
        minmax(220px, 1.1fr)
        minmax(120px, 1fr)
        minmax(150px, 1.2fr)
        minmax(130px, 1.6fr)
        124px
        /* Caben las dos pastillas en línea: «Sin confirmar» junto a
           «Verificada» es la pareja más ancha que se puede dar —179 px de
           pastillas más el hueco— y partida en dos renglones desiguala el alto
           de la fila en una lista que se lee por barrido. Se mide, no se
           estima: con 176 se partía por tres píxeles. */
        minmax(200px, 0.9fr)
        52px;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-width: min-content;
}

.torre-etq-titulos {
    position: sticky;
    top: 0;
    z-index: 2;
    padding: 4px 4px 6px;
    background: var(--tw-bg-primary);
    border-bottom: 1px solid var(--tw-border-light);
    color: var(--tw-font-light);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* La hora, a la derecha del todo: es lo que se compara entre renglones. */
.torre-etq-titulos > span:last-child,
.torre-etq-fila > .torre-etq-hora {
    text-align: right;
}

/* Todo el renglón lleva al pasaporte del serial, que es la pregunta siguiente
   —«¿y por dónde pasó esta pieza?»— y no un enlace escondido en una columna. */
.torre-etq-fila {
    padding: 7px 4px;
    border-bottom: 1px solid var(--tw-border-light);
    color: inherit;
    text-decoration: none;
    font-size: 13px;
}

.torre-etq-fila:hover {
    background: var(--tw-bg-secondary);
    color: inherit;
}

.torre-etq-serial,
.torre-etq-orden {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 1px;
}

/* El serial y su apostilla —las piezas que ampara la etiqueta— en la misma
   línea. Se envuelve en vez de recortarse porque un serial cortado a media
   palabra no se puede buscar, y el ancho da de sobra para los dos: es la
   salida de emergencia de un cliente con seriales más largos, no el caso. */
.torre-etq-serial {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.torre-etq-parte,
.torre-etq-quien {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.torre-etq-orden small {
    display: block;
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.torre-etq-donde {
    display: flex;
    align-items: center;
    gap: 5px;
    color: var(--tw-font-secondary);
    font-size: 12px;
    white-space: nowrap;
}

/* Casi siempre vacía, y es lo que se busca: sólo se pinta lo que se sale de lo
   esperado. Con `printed` en pastilla verde, noventa y ocho renglones iguales
   esconderían los dos que no lo son. */
.torre-etq-estado {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
}

.torre-etq-hora {
    color: var(--tw-font-secondary);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Colgada en la nave la letra crece: a tres metros un renglón de 13 px no se
   lee, y esta lista existe justo para leerse de lejos mientras alguien imprime. */
body.torre-monitor .torre-etq-fila {
    font-size: 16px;
    padding: 10px 4px;
}

/* Estrecha, cede «De dónde» y nada más.
 *
 * Es la única columna que se puede recuperar sin cambiar de pantalla —el
 * conmutador de arriba acota por puesto— así que es la que se va. Las otras seis
 * son la pregunta: qué serial, de qué parte, de qué orden, quién lo firmó, en
 * qué quedó y a qué hora. */
@media (max-width: 1200px) {
    .torre-etq-titulos,
    .torre-etq-fila {
        grid-template-columns:
            /* Los mismos 220 que a lo ancho, y no menos: es lo que mide el
               serial más la pastilla de las piezas que ampara, medido. Por
               debajo, esa apostilla cae a un segundo renglón y esa fila queda
               más alta que las cincuenta y nueve de al lado. */
            minmax(220px, 1.1fr)
            minmax(110px, 1fr)
            minmax(130px, 1.2fr)
            minmax(120px, 1.4fr)
            minmax(200px, 0.9fr)
            52px;
    }

    .torre-etq-titulos > span:nth-child(5),
    .torre-etq-fila > .torre-etq-donde {
        display: none;
    }
}

/* ══ La cabecera podada ══════════════════════════════════════════════════ */

/* Un bloque de la franja sin nada que decir · no se esconde, se baja de tono.
   Un cero es un dato —«nadie está capturando» es de los importantes— pero no
   tiene por qué competir por la mirada con lo que sí pasó. */
.torre-pulso-bloque.es-apagado {
    opacity: 0.45;
}

.torre-pulso-bloque.es-apagado .torre-pulso-cifra {
    font-size: var(--tw-font-size-lg, 1rem);
}

/* Las cifras del día, detrás de su botón. */
.torre-kpis-mas {
    border: 0;
    background: none;
    font: inherit;
    color: var(--tw-font-secondary);
    cursor: pointer;
    gap: 4px;
}

.torre-kpis-mas:hover {
    color: var(--tw-font-primary);
}

.torre-kpis-mas .ti {
    transition: transform var(--tw-transition);
}

.torre-kpis-mas.es-abierto .ti {
    transform: rotate(180deg);
}

/* Contenedor de presentación: reparte igual que la lista que lo rodea en vez
   de meter una caja intermedia que rompa el reflujo de las pastillas. */
.torre-kpis-dia {
    display: contents;
}


/* ══ El lateral de la torre en una pantalla grande ═══════════════════════
 *
 * ── EL PROBLEMA ─────────────────────────────────────────────────────────
 *
 * Había reglas para 900, para 1400 y para el modo monitor, y ninguna para lo
 * que hay encima. En un monitor de 1920 —o de 2560— el lateral se quedaba en
 * los mismos 340 px y **todo el sobrante se lo llevaba el plano**, que no sabe
 * qué hacer con él: el dibujo no crece con el hueco, sólo se aleja, así que la
 * pantalla ganaba margen negro mientras el registro y las firmas seguían
 * cayendo bajo el pliegue de una columna de 340 que hay que desplazar.
 *
 * Es el peor reparto posible: la parte que **no** aprovecha el espacio se lleva
 * todo el que sobra, y la que sí lo aprovecharía se queda como en una portátil.
 *
 * ── LO QUE HACE ─────────────────────────────────────────────────────────
 *
 * A partir de 1600 el lateral crece, y a partir de 1900 se parte en dos
 * columnas que se desplazan por separado. Lo que se gana no es sitio: es que
 * dejan de existir el pliegue y el desplazamiento. Los avisos y el registro se
 * ven **a la vez**, que es como se leen —«esto se detuvo, ¿qué pasó?»— y era
 * justo lo que una sola columna hacía imposible.
 *
 * Por ancho y no por alto, como las otras dos consultas de esta pantalla: una
 * portátil ancha y baja quiere el reparto en dos, y un monitor vertical no lo
 * quiere aunque le sobre pantalla. */

/* Apiladas por defecto: en pantallas normales el lateral es la columna de
   siempre y esto no cambia nada. */
.plano-lateral-col {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

@media (min-width: 1600px) {
    .plano-lateral {
        width: 400px;
        flex: 0 0 400px;
    }
}

@media (min-width: 1900px) {
    .plano-lateral {
        width: 760px;
        flex: 0 0 760px;
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 0 18px;
        align-items: start;
        /* El que se desplaza pasa a ser cada columna, no el lateral entero:
           con un solo desplazamiento, bajar a ver una firma se lleva también
           los avisos fuera de la pantalla, que es la mitad del problema. */
        overflow-y: hidden;
    }

    .plano-lateral-col {
        min-height: 0;
        max-height: 100%;
        overflow-y: auto;
        padding-right: 4px;
    }

    /* Y las listas de dentro dejan de tener su propio tope: el alto ya lo pone
       la columna, y dos desplazamientos anidados sobre la misma lista es una
       trampa —la rueda mueve el de dentro y parece que la página se atascó—. */
    .plano-lateral-col .torre-registro {
        max-height: none;
    }
}

@media (min-width: 2400px) {
    /* En un monitor de sala el lateral aguanta la mitad de la pantalla sin
       apretar: el plano a esa anchura ya sólo estaba añadiendo negro. */
    .plano-lateral {
        width: 900px;
        flex: 0 0 900px;
    }
}

/* El modo monitor ya reparte por porcentaje —40 %— y a partir de ahí ese 40 %
   da de sobra para dos columnas. Se le da la misma rejilla sin tocarle el
   ancho, que está calculado para leerse a tres metros. */
@media (min-width: 1900px) {
    body.torre-monitor .plano-lateral {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 0 18px;
        align-items: start;
        overflow-y: hidden;
    }
}


/* El folio de la orden, al lado del cliente y en pequeño.

   Iba en grande y con el cliente debajo en gris, y es al revés de como se
   busca: «OP-2026-0024» no le dice nada a nadie fuera de producción, y quien
   mira esta lista persigue un pedido de KONE. El folio no se va —es como se
   nombra la orden al abrirla— pero pasa a identificar en vez de a leerse. */
.plano-orden-folio {
    margin-left: 6px;
    color: var(--tw-font-light);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}


/* Y con qué prisa la pidió el cliente · al lado del nombre y del folio.

   Va dentro de la misma celda de texto y no en una pista propia del renglón:
   cada fila de esta lista es su propia retícula y una columna de más las
   desalinea entre sí. El tono lo pone `.tw-flag`; esto sólo separa.

   La regla se fue a `.tw-prisa`, arriba, cuando los listados del panel
   estrenaron la misma forma: la marca no puede verse de dos maneras según la
   pantalla. Este nombre se queda como alias mientras sobreviva algún renglón
   escrito a mano con él. */
.plano-orden-prisa {
    display: inline-block;
    margin-left: 6px;
    font-size: 11px;
    white-space: nowrap;
}


/* La tabla, estrechada · **sin esconder ni una celda**.
 *
 * Quitar columnas con `display: none` recoloca la rejilla —los que sobreviven
 * se corren a los huecos de los que se fueron y la hora acaba bajo «pasos»—, así
 * que lo que se estrecha son las pistas y la letra. Todas las cifras siguen ahí:
 * lo que cambia es cuánto ocupan.
 *
 * Es el caso de una portátil o de la torre con el menú desplegado, donde al
 * cuerpo le quedan unos 900 px. */
@media (max-width: 1200px) {
    .torre-grupo-titulos,
    .torre-grupo-fila {
        grid-template-columns:
            16px
            minmax(140px, 2fr)
            minmax(88px, 1fr)
            56px 64px 84px 64px
            56px 76px 52px;
        /* Ocho y no seis: con seis, «RECHAZADAS» y «RECHAZO» se tocaban en la
           cabecera y se leían como una sola palabra. */
        gap: 8px;
    }

    .torre-grupo-num,
    .torre-grupo-titulos {
        font-size: var(--tw-font-size-sm);
    }
}


/* ══ El diálogo de la torre se desplaza por dentro ═══════════════════════
 *
 * El carril de una persona trae hasta cuarenta actos, y el diálogo crecía con
 * ellos: en una pantalla de portátil el pie se salía por abajo y no había cómo
 * llegar a los últimos. La ventana no puede medir lo que quepa dentro; lo que
 * se desplaza es el cuerpo.
 *
 * Sólo aquí y no en los diálogos de la aplicación —el móvil ya tiene su regla
 * propia—, y con tope en `vh` para que en un monitor grande el diálogo siga
 * siendo un diálogo y no una segunda pantalla.
 *
 * `.torre-desglose` conserva su propio tope de 45vh y no se estorban: al
 * acotarse él, el cuerpo casi nunca llega a desbordar, así que no se acaba con
 * dos barras de desplazamiento sobre la misma lista —que es la trampa de
 * siempre: la rueda mueve la de dentro y parece que la ventana se atascó—. */
#torre-info-modal .modal-content {
    max-height: min(84vh, 900px);
}

#torre-info-modal .modal-body {
    overflow-y: auto;
    /* Sin suelo, el cuerpo se encoge a nada cuando la cabecera es larga. */
    min-height: 0;
}

/* ── Las preferencias de avisos · la lista de Avisos › Preferencias ────────

   Vivía en Mi cuenta como «Avisos por correo»; desde el 10 de octubre de 2026
   está en la página de Avisos con una segunda columna, la de la campana
   (`.tw-aviso--pref`, más abajo).

   Doce renglones que dicen qué se avisa y ofrecen tres estados: apagado, al
   momento y resumen diario. No es una tabla —no hay nada que comparar entre
   renglones— ni una lista de casillas, porque la elección no es «sí o no» sino
   «cómo». De ahí la pastilla de la derecha, que es el mismo `.tw-seg` del resto
   del sistema.

   El renglón separa con una línea arriba y no con tarjetas: doce tarjetas
   apiladas dentro de otra tarjeta es un marco dentro de un marco, y lo que se
   lee es el nombre del aviso, no su recuadro. */
.tw-aviso {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 0;
    border-top: 1px solid var(--tw-border-light);
}

/* El primero de cada grupo ya tiene el título del grupo encima. */
.tw-aviso:first-child {
    border-top: 0;
    padding-top: 2px;
}

.tw-aviso-icon {
    flex: 0 0 auto;
    padding-top: 2px;
    color: var(--tw-font-tertiary);
}

.tw-aviso-body {
    flex: 1 1 auto;
    min-width: 0;
}

.tw-aviso-name {
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

.tw-aviso-desc {
    font-size: var(--tw-font-size-md);
    line-height: 1.5;
    color: var(--tw-font-secondary);
}

/* La condición exacta, en el gris más tenue: es la letra pequeña del renglón
   —«más de 48 horas»— y quien ya sabe qué es el aviso no la vuelve a leer. */
.tw-aviso-when {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

.tw-aviso-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
}

/* Apagado, el botón de prueba no pinta nada: mandaría un correo de algo que no
   se recibe. Se esconde sin quitarlo del sitio para que el renglón no salte al
   encender. */
.tw-aviso[data-modo=''] .tw-aviso-probar {
    visibility: hidden;
}

/* En pantalla estrecha la pastilla baja a su propio renglón, alineada con el
   texto y no con el icono: así las doce quedan en la misma vertical. */
@media (max-width: 820px) {
    .tw-aviso {
        flex-wrap: wrap;
    }

    .tw-aviso-actions {
        width: 100%;
        justify-content: flex-start;
        padding-left: 32px;
    }
}

/* ── La campana de la oficina (10-oct-2026) ────────────────────────────────

   La de las estaciones (`.st-campana` en station.css) llevada al panel: sólo
   existe con algo que contar, el número toma el tono del peor —naranja si algo
   espera a que lo resuelvas, azul si sólo pasó algo— y late cuando sube. Vive
   en la cabecera de la barra lateral, a la derecha de quien tiene la sesión:
   es lo único que está en todas las pantallas. El tono va en el número y en el
   icono de cada aviso, nunca en un borde. */

.tw-campana[hidden] {
    display: none !important;
}

.navbar-vertical .navbar-brand:has(> .tw-campana:not([hidden])) {
    display: flex;
    align-items: center;
    gap: 2px;
}

/* El nombre cede el ancho que la campana necesita; el `width: 90%` de la
   plantilla está en línea y por eso lleva la marca. */
.navbar-vertical .navbar-brand:has(> .tw-campana:not([hidden])) > .dropdown:first-child {
    flex: 1 1 auto;
    min-width: 0;
    width: auto !important;
}

.tw-campana {
    flex: 0 0 auto;
}

.tw-campana-boton {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 28px;
    padding: 0 6px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: transparent;
    color: var(--tw-font-secondary);
    cursor: pointer;
    transition: background-color var(--tw-transition);
}

.tw-campana-boton:hover,
.tw-campana-boton[aria-expanded='true'] {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.tw-campana-boton > .ti {
    font-size: var(--tw-icon-size);
}

/* El número, con la medida de las insignias del menú de al lado. */
.tw-campana-n {
    min-width: 16px;
    padding: 0 4px;
    border-radius: var(--tw-radius-xs);
    background-color: var(--tw-tag-blue-bg);
    color: var(--tw-tag-blue-fg);
    font-size: var(--tw-font-size-xxs);
    font-weight: var(--tw-weight-semibold);
    line-height: 16px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.tw-campana-n[data-tono='naranja'] {
    background-color: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
}

.tw-campana-boton--nueva > .ti-bell {
    animation: tw-campana-late 0.9s ease-in-out 2;
}

@keyframes tw-campana-late {
    0%,
    100% {
        transform: rotate(0);
    }
    20% {
        transform: rotate(-16deg);
    }
    40% {
        transform: rotate(14deg);
    }
    60% {
        transform: rotate(-10deg);
    }
    80% {
        transform: rotate(6deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    .tw-campana-boton--nueva > .ti-bell {
        animation: none;
    }
}

/* El panel · flota a la derecha de la barra, a toda la altura que haga falta
   y con su propio desplazamiento. Fijo y no anclado al botón: la barra lateral
   mide 236 px y un panel colgado de ella se saldría por la izquierda. */
#avisos-oficina .tw-campana-panel {
    position: fixed !important;
    inset: 8px auto auto calc(var(--tw-sidebar-width) + 8px) !important;
    transform: none !important;
    margin: 0 !important;
    width: min(400px, calc(100vw - var(--tw-sidebar-width) - 16px));
    max-height: calc(100dvh - 16px);
    overflow-y: auto;
    padding: 0;
    z-index: 1060;
    /* Cuelga de la barra lateral, que pone sus renglones en medio: aquí no. */
    font-weight: var(--tw-weight-regular);
    text-align: left;
}

@media (max-width: 991.98px) {
    /* En teléfono y tablet la barra es la de arriba: el panel cae debajo, a lo ancho. */
    #avisos-oficina .tw-campana-panel {
        inset: 56px 8px auto 8px !important;
        width: auto;
        max-height: calc(100dvh - 64px);
    }
}

.tw-campana-cab {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 12px;
    background-color: var(--tw-bg-primary);
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-campana-titulo {
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
}

.tw-campana-todos {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

.tw-campana-todos:hover {
    color: var(--tw-font-primary);
}

.tw-campana-cuerpo {
    padding-bottom: 6px;
}

.tw-campana-seccion + .tw-campana-seccion {
    border-top: 1px solid var(--tw-border-light);
}

.tw-campana-seccion-titulo {
    margin: 12px 12px 0;
    font-size: var(--tw-font-size-xs);
    font-weight: var(--tw-weight-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--tw-font-tertiary);
}

.tw-campana-grupo {
    padding: 8px 12px 10px;
}

.tw-campana-grupo + .tw-campana-grupo {
    border-top: 1px solid var(--tw-border-light);
}

.tw-campana-grupo-cab {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 24px;
}

.tw-campana-grupo-cab > .ti {
    flex: 0 0 auto;
    font-size: var(--tw-icon-size);
    color: var(--tw-font-tertiary);
}

/* Lo que espera a que lo resuelvas lleva el tono en el icono. */
.tw-campana-grupo[data-clase='espera'] .tw-campana-grupo-cab > .ti,
.tw-avisos-grupo[data-clase='espera'] > .ti {
    color: var(--tw-tag-orange-fg);
}

.tw-campana-grupo-nombre {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

.tw-campana-grupo-n {
    flex: 0 0 auto;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Cada renglón, alineado con el nombre del aviso (icono 16 + hueco 8). */
.tw-campana-fila {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin: 2px 0 0 16px;
    padding: 5px 8px;
    border-radius: var(--tw-radius-sm);
    color: inherit;
    text-decoration: none;
}

a.tw-campana-fila:hover {
    background-color: var(--tw-bg-transparent-light);
}

.tw-campana-fila-linea {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
}

.tw-campana-fila-titulo,
.tw-campana-fila-meta {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-campana-fila-titulo {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--tw-font-primary);
}

/* Desde cuándo: nunca se recorta, es lo que dice si es de hoy o de hace un mes. */
.tw-campana-fila-hace {
    flex: 0 0 auto;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    white-space: nowrap;
}

.tw-campana-fila-meta {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* «y 2 más», al final de la fila de botones: lleva a la página, donde están todos. */
.tw-campana-mas {
    margin-left: auto;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    white-space: nowrap;
}

.tw-campana-mas:hover {
    color: var(--tw-font-primary);
}

.tw-campana-acciones {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin: 6px 0 0 24px;
}

/* En el riel de 56 px la campana va debajo de las iniciales. */
@media (min-width: 992px) {
    html[data-nav='plegada'] .navbar-vertical .navbar-brand:has(> .tw-campana:not([hidden])) {
        flex-direction: column;
        justify-content: center;
        gap: 2px;
        height: auto !important;
        padding: 6px 0 !important;
    }

    html[data-nav='plegada'] .navbar-vertical .navbar-brand:has(> .tw-campana:not([hidden])) > .dropdown:first-child {
        flex: 0 0 auto;
        width: 100% !important;
    }

    html[data-nav='plegada'] .tw-campana-boton {
        padding: 0 4px;
    }
}

/* ── La página de Avisos (`/notifications`) ─────────────────────────────── */

/* Ahora · el encabezado de cada aviso lleva sus dos salidas a la derecha. */
.tw-avisos-grupo > .ti {
    color: var(--tw-font-tertiary);
}

.tw-avisos-grupo-acciones {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
}

/* Historial · anchos fijos: los títulos de los avisos son largos y, con la
   tabla en ancho automático, «Desde» y «Estado» se salían por la derecha. El
   detalle se queda con lo que sobre y lo que no cabe se lee en el `title`. */
.tw-avisos-historial {
    table-layout: fixed;
}

.tw-avisos-col-que {
    width: 30%;
}

.tw-avisos-col-aviso {
    width: 15rem;
}

.tw-avisos-col-desde {
    width: 9.5rem;
}

.tw-avisos-col-estado {
    width: 10rem;
}

.tw-avisos-historial td {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-avisos-tipo {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    white-space: nowrap;
}

.tw-avisos-tipo > .ti {
    flex: 0 0 auto;
    color: var(--tw-font-tertiary);
}

.tw-avisos-tipo-nombre {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tw-avisos-detalle {
    color: var(--tw-font-secondary);
}

/* Preferencias · una retícula: el aviso, la campana y el correo, cada uno en
   su columna para que las pastillas de los diecinueve caigan en la misma
   vertical. Medidas fijas: «Al momento | Resumen diario» no cabe en menos. */
.tw-avisos-prefs {
    --tw-avisos-cols: 16px minmax(0, 1fr) 176px 336px;
    padding: 0 24px 24px;
}

.tw-avisos-prefs-cab {
    position: sticky;
    top: 0;
    z-index: 1;
    display: grid;
    grid-template-columns: var(--tw-avisos-cols);
    column-gap: 16px;
    padding: 10px 0;
    background-color: var(--tw-bg-primary);
    border-bottom: 1px solid var(--tw-border-light);
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

.tw-avisos-prefs-cab > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-avisos-prefs-grupo {
    margin: 18px 0 2px;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
}

.tw-aviso--pref {
    display: grid;
    grid-template-columns: var(--tw-avisos-cols);
    column-gap: 16px;
    align-items: start;
}

.tw-avisos-prefs-grupo + .tw-aviso--pref {
    border-top: 0;
    padding-top: 6px;
}

.tw-aviso-col {
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 28px;
}

/* La espera no se silencia: no hay control que tocar, y se dice por qué. */
.tw-aviso-fijo {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--tw-font-tertiary);
}

@media (max-width: 1100px) {
    .tw-avisos-prefs-cab {
        display: none;
    }

    .tw-aviso--pref {
        grid-template-columns: 16px minmax(0, 1fr);
        row-gap: 8px;
    }

    .tw-aviso--pref > .tw-aviso-col {
        grid-column: 2;
    }
}

/* Cabecera de grupo dentro de una tabla · «Nesteo 002711» sobre sus renglones.

   Es el corte de arriba de una tabla que ya agrupa por dentro: el contenido de
   una caja se lee primero por programa y después por número de parte. Va con
   fondo propio y sin borde inferior para que se lea como el rótulo de lo que
   viene debajo y no como una fila más que compite con ellas —que es lo que pasa
   con una fila en negrita sin fondo—. El total del grupo va a la derecha, en la
   misma columna que las cifras que resume: así se comprueba la suma en vertical
   sin buscarla. */
.tw-rowgroup > td {
    background-color: var(--tw-bg-secondary);
    border-bottom: 0;
}

.tw-rowgroup + tr > td {
    border-top: 1px solid var(--tw-border-light);
}

/* ── Nueva salida · cuánto sale de cada número de parte ─────────────────────
   Las tarjetas de «Nueva salida a proceso externo» (`salida-nueva.js`).

   Tres reglas de alineación, las tres pedidas a la vista de la primera
   versión, donde cada campo bailaba según lo largo de su «de N»:
   · la tabla arranca y acaba en el borde del contenido de la tarjeta, no 8 px
     más adentro que el código de la caja;
   · las columnas tienen el mismo ancho en todas las tarjetas (`fixed`), así
     que la descripción no empieza en otro sitio de una caja a la siguiente;
   · el «de N» tiene ancho propio y el total de la caja cae debajo de los
     campos con el mismo relleno que el número de dentro de un campo. */
.tw-salida-cajas {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.tw-salida-cajas > .tw-card + .tw-card {
    margin-top: 0;
}

.tw-salida-partes {
    table-layout: fixed;
    width: 100%;
    margin-top: 8px;
}

/* `!important` porque el armazón (`layouts/main.ejs`) fuerza 1rem y 1.1rem
   de relleno lateral en toda `.table`, también con `!important`: sin ganarle,
   el encabezado y las celdas quedaban a dos márgenes distintos. */
.tw-salida-partes > :is(thead, tbody, tfoot) > tr > :is(th, td) {
    vertical-align: middle;
    padding-left: 8px !important;
    padding-right: 8px !important;
}

.tw-salida-partes > :is(thead, tbody, tfoot) > tr > :is(th, td):first-child {
    padding-left: 0 !important;
}

.tw-salida-partes > :is(thead, tbody, tfoot) > tr > :is(th, td):last-child {
    padding-right: 0 !important;
}

.tw-salida-partes > tfoot > tr > td {
    border-bottom: 0;
}

.tw-salida-sale {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.tw-salida-sale > .form-control {
    flex: 0 0 5.5rem;
    width: 5.5rem;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.tw-salida-de {
    flex: 0 0 3.5rem;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.tw-salida-total {
    flex: 0 0 5.5rem;
    box-sizing: border-box;
    padding-right: var(--tw-salida-campo-relleno, 9px);
    text-align: right;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* El rótulo de la columna, en la misma vertical que los números. */
.tw-salida-total--rotulo {
    font-weight: inherit;
}

/* Lo que no sale de un número de parte · ¿se queda o falta?
   Un renglón debajo del suyo y pegado a él: sin la raya de en medio se lee como
   la segunda línea de la misma pieza, no como otra pieza.

   Empieza donde la descripción y no a la derecha. Estuvo a la derecha, bajo el
   campo que lo provocó, y con una caja de varios números de parte ese lado se
   volvía un amontonamiento: el campo, su «de 118», el «No salen 18» y la
   pastilla, los cuatro apilados contra el mismo borde mientras la mitad
   izquierda del renglón quedaba vacía. La columna de la derecha es la de las
   cifras, y esto no es una cifra: es una frase. Por eso la primera celda va
   vacía y el resto del renglón arranca en la columna del texto.

   Y con aire por debajo: la pastilla mide más que un renglón de tabla chica, y
   sin ese hueco quedaba pegada a la raya y al número de parte siguiente.
   Envuelve en la tablet antes que cortar la pastilla. */
.tw-salida-partes > tbody > tr:has(+ .tw-salida-resto-fila:not([hidden])) > td {
    border-bottom: 0;
}

.tw-salida-partes > tbody > .tw-salida-resto-fila > td {
    padding-top: 2px;
    padding-bottom: 10px;
}

.tw-salida-resto {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}

/* =====================================================================
   30. Distribuir en cajas · el asistente del embarque
   -------------------------------------------------------------------
   Salvador, 20 de septiembre de 2026: «la distribución por estándar pack
   me gustaría más estilo wizard ya DENTRO del embarque». Es la anatomía
   de siempre —riel a la izquierda, lo que se decide a la derecha, pie
   fijo— y ocupa el hueco del contenido como una pantalla más: se entra y
   se sale sin perder dónde se estaba.

   Se usa también en el iPad en horizontal, así que todo lo de aquí se
   mide a 1180 × 820: el pie no se va con el scroll, los campos son del
   tamaño del dedo y la vista previa envuelve en vez de encoger.
   ===================================================================== */

/* La pantalla del asistente · dentro del contenido, no encima de todo.

   Salvador, 20 de septiembre de 2026: *«no me gusta que el wizard de
   creación de cajas es de full screen, o sea no está embebido en el
   content»*.

   Nació `position: fixed; inset: 0` sobre el viewport entero, y al
   abrirlo desaparecían el menú y el marco de la aplicación: se leía como
   otro programa y no como un paso del embarque, y el único camino de
   vuelta era su botón «Cerrar». Ahora no es una capa: es una `.tw-screen`
   como la de la ficha, hermana suya dentro de `.page-wrapper`, y por eso
   no necesita ni posición ni z-index propios —`.page-wrapper > *` ya le
   da el alto de la pantalla—. Sólo hace falta que `hidden` pueda con el
   `display: flex` que hereda de `.tw-screen`. */
.tw-flow-screen[hidden] {
    display: none;
}

/* Y mientras está abierto, la ficha se retira. El hueco del contenido es
   de una sola pantalla: dos hermanas con `flex: 1 1 auto` se lo partirían
   a medias, y quedaría además el desplazamiento de una debajo del otro,
   que es la trampa de siempre.

   Lo decide el propio `hidden` del asistente y no una clase en el
   `<body>`: un estado escrito en dos sitios es un estado que tarde o
   temprano se queda a medias en uno de ellos. */
.page-wrapper:has(> .tw-flow-screen:not([hidden])) > .tw-screen:not(.tw-flow-screen) {
    display: none;
}

/* ── El riel · una partida por renglón ────────────────────────────── */
.tw-dist-rail {
    display: flex;
    flex-direction: column;
    gap: 4px;
    width: 100%;
    padding: 10px 12px;
    border: 0;
    border-bottom: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
    background-color: transparent;
    text-align: left;
}

.tw-dist-rail:hover {
    background-color: var(--tw-bg-secondary);
}

/* Dónde se está parado, sólo con color, como toda selección aquí. */
.tw-dist-rail.is-now {
    background-color: var(--tw-accent-secondary);
}

/* La partida cuyo reparto no cabe en lo que le queda sin caja. El tono va
   en el borde entero y el fondo, nunca en una barra a la izquierda. */
.tw-dist-rail.is-over {
    border: 1px solid var(--tw-tag-red-fg);
    background-color: var(--tw-tag-red-bg);
}

/* Dos renglones y no tres: la identidad con su resultado arriba, las cuentas
   debajo. El resultado se va al extremo derecho —`margin-left: auto`— porque es
   la respuesta a lo que se está haciendo en el panel, no parte del nombre. */
.tw-dist-rail-top,
.tw-dist-rail-meta {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
}

.tw-dist-rail-part {
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
    word-break: break-word;
}

/* El resultado del reparto, como texto. En este riel la píldora con fondo se
   reservaría para un estado y esto es una cuenta; en rojo sólo cuando el
   reparto se pasa de lo que la partida tiene sin caja, que es lo único de aquí
   que detiene el botón de crear. */
.tw-dist-rail-plan {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    white-space: nowrap;
}

.tw-dist-rail-plan.is-over {
    color: var(--tw-tag-red-fg);
    font-weight: var(--tw-weight-medium);
}

/* ── El panel · la partida que se está repartiendo ─────────────────── */
.tw-dist-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

/* Los dos controles, uno al lado del otro: son dos formas de decir lo
   mismo y verlas juntas es lo que hace elegir una. En una tablet vertical
   se apilan solas. */
.tw-dist-controles {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
}

.tw-dist-control {
    flex: 1 1 18rem;
    min-width: 0;
    padding: 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
}

/* El tope va pegado al campo —«de 300»—, no en una nota debajo. */
.tw-dist-campo {
    display: flex;
    align-items: center;
    gap: 8px;
}

.tw-dist-campo .form-control {
    width: 7rem;
    flex: 0 0 auto;
}

.tw-dist-total {
    margin-top: 10px;
    color: var(--tw-font-primary);
}

/* Lo que sobra o falta, dicho junto al total. Sin regla propia se leía
   igual que el resto y la advertencia no advertía de nada. */
.tw-pick-warn {
    color: var(--tw-tag-red-fg);
}

/* ── Las cajas · un cuadro por caja, y el cuadro es el campo ────────────

   Eran dos representaciones —una lista de renglones editables y, debajo, los
   mismos cuadros en sólo lectura— y decían lo mismo. Con diecisiete cajas la
   lista medía setecientos píxeles y no cabía en el iPad horizontal; los cuadros
   se acomodan en tres filas y además enseñan la forma del reparto.

   Así que el cuadro se quedó y se llevó dentro lo que la lista tenía: la
   cantidad en un campo y el aspa para quitarlo. Al final, el cuadro punteado
   que añade una caja más. */
.tw-dist-previa {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.tw-dist-caja {
    display: flex;
    flex-direction: column;
    gap: 4px;
    width: 8.5rem;
    padding: 8px 10px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
}

/* La que no lleva justo el empaque estándar de un número de parte que no
   admite flexibilidad. Tono en el borde entero y en el fondo. */
.tw-dist-caja.is-fuera {
    border-color: var(--tw-tag-orange-fg);
    background-color: var(--tw-tag-orange-bg);
}

/* El nombre y su aspa, en el mismo renglón: el aspa pegada a lo que quita. */
.tw-dist-caja-cab {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 4px;
    min-height: 1.5rem;
}

.tw-dist-caja-nombre {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

.tw-dist-caja.is-fuera .tw-dist-caja-nombre {
    color: var(--tw-tag-orange-fg);
}

/* El campo ocupa el cuadro entero: la cifra **es** el control, no una etiqueta
   con un control al lado. */
.tw-dist-caja .tw-dist-cantidad {
    width: 100%;
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    text-align: center;
}

/* El aspa sólo se ve al pasar por encima o con el foco puesto: diecisiete
   aspas siempre encendidas convierten el reparto en una fila de botones de
   borrar. Con el dedo no hay «encima», así que en pantalla táctil se queda. */
.tw-dist-caja .tw-dist-quitar {
    opacity: 0;
    color: var(--tw-font-light);
    transition: opacity var(--tw-duration-fast) var(--tw-ease);
}

.tw-dist-caja:hover .tw-dist-quitar,
.tw-dist-caja:focus-within .tw-dist-quitar {
    opacity: 1;
}

@media (hover: none) {
    .tw-dist-caja .tw-dist-quitar {
        opacity: 1;
    }
}

/* Y el cuadro que añade: punteado, del mismo tamaño, al final de la fila. Un
   botón suelto debajo de los cuadros se leía como el pie del asistente. */
.tw-dist-caja--nueva {
    align-items: center;
    justify-content: center;
    gap: 4px;
    border-style: dashed;
    background-color: transparent;
    color: var(--tw-font-secondary);
    font: inherit;
    font-size: var(--tw-font-size-sm);
    cursor: pointer;
}

.tw-dist-caja--nueva:hover {
    border-color: var(--tw-font-light);
    background-color: var(--tw-bg-secondary);
    color: var(--tw-font-primary);
}

/* --- La banda de avance del embarque ---------------------------------------
   Pegada bajo el título y por encima de la ficha: el avance de empaquetado es
   lo primero que se pregunta al abrir un embarque vivo, y es lo único de esta
   pantalla que se mueve solo mientras se mira. Dentro de una pestaña habría
   que ir a buscarlo.

   Una sola línea de alto. La ficha ya compite por altura con el panel de
   campos, el carril de secciones y una tabla de nueve columnas; en el iPad
   horizontal —1180 × 820— una tira de tarjetas se habría comido un cuarto de
   la pantalla para decir cuatro números.

   ── Y UN RENGLÓN QUIERE DECIR UN RENGLÓN ─────────────────────────────────

   Estaba en `wrap`, y a 1440 se partía: el embudo arriba, las cuentas de
   bultos debajo. Es el alto que la banda existe para no gastar, y además la
   segunda línea aterrizaba pegada al borde derecho, donde no la busca nadie.

   Ahora no envuelve. Lo que hay es un **orden de cesión**: cuando el ancho
   aprieta, las cuentas marcadas con `data-cede` desaparecen de mayor a menor
   —primero las tarimas, después las cajas— y lo que no lleva marca se queda
   siempre. Lo rojo no lleva marca. Es la misma regla que la cabecera de
   subpanel, donde el que cede sitio es el título y nunca el botón. */
.tw-avance-embarque {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 8px 16px;
    padding: 6px 12px;
    background-color: var(--tw-bg-secondary);
    border-bottom: 1px solid var(--tw-border-light);
    flex: 0 0 auto;
    overflow: hidden;
}

/* El orden de cesión. Las medidas son las del contenido, no las del catálogo:
   por debajo de 1280 el embudo y las dos cuentas ya no caben juntos, y por
   debajo de 1100 tampoco cabe la de cajas. */
@media (max-width: 1280px) {
    .tw-avance-embarque .tw-flag[data-cede='1'] {
        display: none;
    }
}

@media (max-width: 1100px) {
    .tw-avance-embarque .tw-flag[data-cede='2'] {
        display: none;
    }
}

.tw-avance-embarque-barra {
    display: flex;
    align-items: center;
    gap: 2px;
    flex: 0 0 auto;
}

/* El embudo · cuatro cifras encadenadas y no cuatro tarjetas sueltas. Son
   trozos del camino de la misma pieza, así que se leen seguidas.

   Tampoco envuelve: era el otro sitio por donde la banda se partía en dos. */
.tw-avance-embudo {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 2px;
    min-width: 0;
}

/* Cada cifra lleva a la pestaña que la explica. Botón sin marco, como «Por
   reponer» y como el avance de la planeación: un botón con borde dentro de una
   fila de cifras se lee como un control suelto y no como el dato. */
.tw-avance-paso {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    padding: 2px 6px;
    border: 0;
    border-radius: var(--tw-radius-xs);
    background: none;
    font: inherit;
    white-space: nowrap;
    cursor: pointer;
}

.tw-avance-paso:hover {
    background-color: var(--tw-bg-quaternary);
}

.tw-avance-paso:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 1px;
}

.tw-avance-paso-valor {
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    /* Tabular, porque la cifra cambia sola mientras se mira: sin esto el
       renglón entero se desplaza un pixel cada vez que Empaque escanea. */
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-primary);
}

.tw-avance-paso-rotulo {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

.tw-avance-paso-flecha {
    font-size: 0.9rem;
    color: var(--tw-font-light);
    flex: 0 0 auto;
}

/* Las cuentas de bultos se van al otro extremo: son la otra pregunta —cuántas
   cajas quedan por armar— y pegadas al embudo se leerían como su quinta cifra. */
.tw-avance-embarque-bultos {
    margin-left: auto;
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-secondary);
    white-space: nowrap;
    flex: 0 0 auto;
}

/* Lo empacado de un renglón, dentro de su celda · la misma barra, más corta.
   La columna mide 10 rem y ya lleva la cantidad: con los 84 px de la barra
   normal, el número caía a la línea de abajo y la fila crecía al doble. */
.tw-progress--mini {
    width: 44px;
    height: 3px;
    margin-right: 6px;
}

/* La cifra que acaba de cambiar sube un pixel y vuelve · el mismo gesto que los
   indicadores de Inicio. Es lo único que se mueve en esta pantalla, y su razón
   de ser es que se note que alguien acaba de escanear en la mesa. */
.tw-avance-paso-valor.is-bumped {
    animation: tw-bump 0.45s ease-out;
}

/* El hueco de la mini barra del renglón: existe aunque esté vacío, para que el
   refresco tenga dónde escribir sin rehacer la celda. */
.tw-avance-renglon {
    display: inline;
}

/* =====================================================================
   31. Liberar el embarque · el diálogo de la conciliación
   -------------------------------------------------------------------
   Salvador, 20 de septiembre de 2026: «al liberar embarque indicar qué
   cajas se cumplieron […] y que admin pueda aceptar así la liberación
   con una conciliación». Ocupa el hueco del contenido con la ficha
   retirada, igual que «Distribuir en cajas», y reusa su
   `.tw-flow-screen`: es la misma anatomía con otra pregunta.

   Se mide contra el iPad en horizontal —1180 × 820— como todo lo que se
   usa con el camión delante: el pie no se va con el desplazamiento y los
   campos son del tamaño del dedo.
   ===================================================================== */

.tw-lib-grupo {
    display: block;
    margin-bottom: 20px;
}

.tw-lib-grupo > summary {
    cursor: pointer;
    list-style: none;
}

/* El triángulo de `details` se retira: la cabecera entera es el tirador y
   dos indicadores para lo mismo se leen como dos controles. */
.tw-lib-grupo > summary::-webkit-details-marker {
    display: none;
}

.tw-lib-grupo-cabecera {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 6px 0;
    border-bottom: 1px solid var(--tw-border-light);
    margin-bottom: 10px;
}

.tw-lib-grupo-titulo {
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
}

/* ── La caja · una tarjeta por bulto ──────────────────────────────── */
.tw-lib-caja {
    padding: 10px 12px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
}

.tw-lib-caja + .tw-lib-caja {
    margin-top: 8px;
}

/* La ya decidida. El tono va en el borde entero y el fondo, nunca en una
   barra a la izquierda. */
.tw-lib-caja.is-decidida {
    border-color: var(--tw-border-strong, var(--tw-border-light));
    background-color: var(--tw-accent-secondary);
}

.tw-lib-cabecera {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.tw-lib-codigo {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
}

/* ── El renglón · qué número de parte y cuánto ────────────────────── */
.tw-lib-linea {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding-top: 8px;
}

.tw-lib-parte {
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

/* El tope va pegado al campo donde se teclea, debajo y en pequeño: lo que
   no tiene etiqueta no se puede dar por embarcado, y eso se dice ahí. */
.tw-lib-campo {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 7.5rem;
}

.tw-lib-campo .form-hint {
    margin: 0;
}

.tw-lib-motivo {
    display: block;
    margin-top: 10px;
}

.tw-lib-motivo .form-label {
    margin-bottom: 4px;
}

/* ── La barra por etapas · dónde está hoy lo que todavía no se fue ─────────

   QUÉ RESUELVE. El estimado de embarque propone por encima de lo etiquetado, y
   sin esto la diferencia se decía con una sola palabra: «faltan 400 por
   etiquetar». Cuatrocientas piezas esperando en la mesa de Empaque y
   cuatrocientas que ni siquiera tienen programa se leían igual, y no son el
   mismo compromiso ni de lejos.

   UN SOLO COMPONENTE PARA LAS TRES BARRAS de este flujo —el cumplimiento de la
   orden en el riel, el saldo de la partida en el panel y la cadena entera de un
   renglón ya firmado— porque son la misma figura: un total repartido en tramos
   que suman el total. Tres componentes distintos serían tres sitios donde el
   verde acaba significando cosas distintas.

   EL COLOR DICE LA ETAPA, Y SIEMPRE LA MISMA. La rampa va de frío a cálido
   según lo avanzado, y el verde es sólo el final —lo que ya no depende de
   nadie—. Sale de los tonos de las etiquetas, que ya significan esto mismo en
   todo el sistema y tienen su pareja en tema oscuro. Qué tramo lleva qué tono
   se decide una sola vez, en `ETAPAS` de `public/scripts/shipments/etapas.js`;
   aquí sólo viven los tonos.

   LA LEYENDA SE NOMBRA UNA VEZ, ARRIBA. Un chip por etapa en cada renglón
   convierte una lista de quince partidas en cuatro columnas de pastillas de
   colores: los nombres van en la cabecera de la lista y el renglón sólo lleva
   su barra, que es lo que se compara de un vistazo. */
.tw-etapas {
    display: flex;
    width: 100%;
    height: 8px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-quaternary);
    overflow: hidden;
}

/* Sin radio propio: entre dos tramos redondeados aparece una muesca que se lee
   como un hueco. La pastilla la pone el contenedor con su `overflow`. */
.tw-etapas-seg {
    height: 100%;
    border-radius: 0;
    background-color: var(--tw-bg-quaternary);
    transition: width var(--tw-transition, 160ms ease-out);
}

.tw-etapas-seg--green {
    background-color: var(--tw-tag-green-fg);
}

.tw-etapas-seg--orange {
    background-color: var(--tw-tag-orange-fg);
}

.tw-etapas-seg--turquoise {
    background-color: var(--tw-tag-turquoise-fg);
}

.tw-etapas-seg--blue {
    background-color: var(--tw-tag-blue-fg);
}

.tw-etapas-seg--purple {
    background-color: var(--tw-tag-purple-fg);
}

.tw-etapas-seg--pink {
    background-color: var(--tw-tag-pink-fg);
}

.tw-etapas-seg--accent {
    background-color: var(--tw-accent);
}

/* La leyenda · los nombres de los tramos, una vez y arriba de la lista.
   Envuelve en vez de recortarse: a 1180 px de ancho las seis etapas no caben en
   una línea, y una leyenda con elipsis no nombra nada. */
.tw-etapas-leyenda {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 2px 14px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

.tw-etapas-leyenda > span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

/* El cuadrito del tono. `currentColor` no vale: el texto de la leyenda es gris
   y lo que tiene que pintarse es el tono del tramo. */
.tw-etapas-punto {
    width: 8px;
    height: 8px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-quaternary);
    flex: 0 0 auto;
}

/* ── El estimado de embarque · el riel y el panel ─────────────────────────

   La anatomía es la del alta de la orden de producción y no se repite aquí: el
   riel es la fuente —las órdenes de compra con saldo— y lo grande es lo que se
   está decidiendo. Esto es sólo lo que esa pantalla no tenía.

   El resumen del estimado va PEGADO AL RIEL y no en el pie: el pie dice lo que
   se firma —tres cifras— y esto dice de qué está hecho lo propuesto, que es lo
   que se mira mientras se decide. Separado del riel por su línea, como la
   identidad de una ficha. */
.tw-est-resumen {
    flex: 0 0 auto;
    padding: 12px;
    border-bottom: 1px solid var(--tw-border-light);
}

/* La cifra grande del resumen y su rótulo, sin que el rótulo se despegue. */
.tw-est-resumen .tw-cifra {
    display: block;
}

/* El renglón de una partida · la identidad, el campo y la barra.

   Rejilla y no flex: cada renglón tiene que alinear sus columnas con los de
   arriba y abajo, y con flex el número de parte largo empuja el campo de una
   fila y no de la siguiente. Cada renglón es su propia retícula con las MISMAS
   pistas, que es lo que las deja alineadas sin una tabla. */
/* Las tres pistas · la identidad no se recorta y la barra absorbe el sobrante.

   DOS AJUSTES QUE SE VIERON EN LA CAPTURA. Dentro de la ficha del embarque esta
   rejilla vive en un panel que ya cedió 400 px al de campos, así que quedan unos
   550: con la barra fija a 14 rem la primera columna bajaba a 182 px y
   «KM51948441V000 Rev. C» salía con puntos suspensivos. Y al revés, con la ficha
   plegada sobraban 400 px que se quedaban como hueco muerto entre el número de
   parte y su cifra, que es lo que separa un dato de su renglón.

   Las dos columnas flexibles lo resuelven solo: la identidad pide algo más que
   la barra —1.2 contra 1— y el campo de en medio no se mueve, porque en él se
   teclea y un campo que cambia de ancho al abrir el panel es un campo que se
   falla. */
.tw-est-fila {
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) 10rem minmax(8rem, 1fr);
    align-items: center;
    gap: 8px 16px;
    padding: 10px 0;
    border-bottom: 1px solid var(--tw-border-light);
}

/* La leyenda de la barra del riel, encima de la lista de órdenes y sólo una
   vez. Va dentro del propio riel para que se desplace con él. */
.tw-est-riel-leyenda {
    padding: 8px 12px;
    border-bottom: 1px solid var(--tw-border-light);
}

/* La lectura de la barra de una orden · las cifras con su nombre, debajo.

   POR QUÉ EL RENGLÓN CRECE UNA LÍNEA. La barra sola contesta «¿cuánto?» y no
   «¿cuánto de qué?», y sus cifras vivían únicamente en el `title` de cada
   tramo: en el iPad —que es donde se arma un embarque de pie— no hay ratón que
   pasar por encima, así que lo que no está escrito no está. Se paga con alto de
   renglón y se cobra en que las veinte órdenes del riel se leen sin tocarlas.

   Gris del detalle y un punto más chico que el de la cabecera: lo primero que
   se lee sigue siendo el número de la orden y su entrega. */
.tw-est-lectura {
    margin-top: 6px;
}

.tw-est-lectura .tw-etapas-leyenda {
    gap: 0 10px;
    font-size: var(--tw-font-size-xs);
}

/* ── El desglose de una orden · el modal que abre su barra ────────────────

   LA FIGURA ES UNA LISTA DE LISTAS y no una tabla: cada sección es un tramo de
   la barra, dentro van los embarques y dentro de cada uno sus números de parte.
   Una tabla de cuatro columnas obligaría a repetir el folio en cada renglón
   —que es justo lo que se está agrupando— y a inventar una columna «tramo» que
   sólo diría lo que ya dice el encabezado de su sección.

   Las cifras van a la derecha en su propia pista, alineadas entre sí: son lo
   que se compara de un vistazo y con ellas pegadas al texto no hay columna que
   seguir con la mirada. */
.tw-desglose-cab {
    padding-bottom: 12px;
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-desglose-seccion {
    margin-top: 16px;
}

/* El título lleva el punto de su tramo: es el hilo entre la barra de arriba y
   lo que se está listando debajo, sin repetir la leyenda entera. */
.tw-desglose-titulo {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 6px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

.tw-desglose-cifra {
    margin-left: auto;
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-primary);
}

/* Un embarque · su cabecera y sus renglones, en una caja que se lee como una
   unidad. Sin ella, cinco embarques seguidos son treinta renglones planos en
   los que no se ve dónde acaba uno. */
.tw-desglose-emb {
    border: 1px solid var(--tw-border-light);
    border-radius: 6px;
    padding: 8px 12px;
    margin-bottom: 8px;
}

.tw-desglose-emb-cab {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--tw-border-light);
    margin-bottom: 4px;
}

.tw-desglose-renglon {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 4px 0;
}

/* El saldo lleva además dónde está hoy · la barra y su lectura, debajo de la
   cifra y a todo el ancho: en la misma línea, con cinco etapas nombradas, la
   identidad de la parte se quedaba sin sitio. */
.tw-desglose-renglon--saldo {
    flex-wrap: wrap;
    padding: 8px 0;
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-desglose-renglon--saldo:last-child {
    border-bottom: 0;
}

.tw-desglose-etapas {
    flex: 1 0 100%;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.tw-desglose-etapas .tw-etapas-leyenda {
    gap: 0 10px;
    font-size: var(--tw-font-size-xs);
}

.tw-desglose-vacio {
    padding: 8px 0;
}

/* El programa que corta lo que falta, y sus cajas · dentro del renglón del
   saldo y sangrado, porque cuelgan de ese número de parte y no de la sección.

   Sin el sangrado y la línea de la izquierda, un renglón con dos programas y
   seis cajas se lee como diez renglones hermanos y se pierde de quién es cada
   uno. La línea es el único adorno: una caja dentro de otra caja dentro del
   modal serían tres bordes para decir una jerarquía. */
.tw-desglose-prog {
    margin-top: 8px;
    padding-left: 12px;
    border-left: 2px solid var(--tw-border-light);
}

.tw-desglose-prog-cab,
.tw-desglose-caja {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    font-size: var(--tw-font-size-sm);
}

.tw-desglose-caja {
    padding-top: 4px;
    padding-left: 12px;
}

/* La cifra de la caja no compite con la del saldo: la del renglón de arriba es
   la que se compara entre partes, y ésta sólo acompaña a su código. */
.tw-desglose-caja .tw-desglose-cifra {
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

.tw-est-fila:last-child {
    border-bottom: 0;
}

/* El buscador de dentro de la orden, en la cabecera del panel.

   Ancho fijo y no elástico: la barra envuelve, y con el campo creciendo hasta
   el hueco que quedara, los tres atajos de la derecha cambiaban de sitio cada
   vez que el número de orden era más largo. A la medida de un número de parte
   —«KM51948441V000»— que es lo que se teclea aquí. */
.tw-est-buscar {
    width: 18rem;
    max-width: 100%;
}

/* El filtro no dejó ningún renglón · el aviso y su salida, en el sitio de la
   lista. No es un cartel: es una línea y un botón que lo deshace. */
.tw-est-sin-coincidir {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 12px;
    padding: 28px 12px;
}

/* La cabecera de la lista: los mismos tres huecos, para que los rótulos caigan
   encima de lo que nombran. Se nombran aquí y ni una vez más. */
.tw-est-cab {
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) 10rem minmax(8rem, 1fr);
    align-items: end;
    gap: 8px 16px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--tw-border-light);
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
}

/* El campo de la cantidad con su tope **al lado** · el tope se dice donde se
   teclea y no en una columna aparte.

   Estaba debajo, y entre eso y la prisa en su propia línea cada partida medía
   tres renglones: con cuarenta partidas, teclear cuarenta números eran cinco
   pantallas de desplazamiento. Al lado sigue igual de pegado al campo y la fila
   baja a uno. */
.tw-est-campo {
    display: flex;
    align-items: center;
    gap: 6px;
}

.tw-est-campo .form-control {
    width: 5.5rem;
    flex: 0 0 auto;
}

.tw-est-campo .form-hint {
    margin: 0;
    white-space: nowrap;
}

/* Por debajo de la tablet en horizontal la rejilla de tres se parte: el campo y
   la barra bajan a su propia línea en vez de encogerse hasta no poder teclear
   ni leer. */
@media (max-width: 1100px) {
    .tw-est-fila,
    .tw-est-cab {
        grid-template-columns: minmax(0, 1fr) 10rem;
    }

    .tw-est-fila > .tw-est-barra,
    .tw-est-cab > .tw-est-barra {
        grid-column: 1 / -1;
    }
}

/* --- La receta del ensamble, dibujada ---------------------------------------

   QUÉ RESUELVE. «De qué se compone» era un formulario de dos desplegables con su
   botón, y debajo una lista de tarjetas iguales. Tres cosas se perdían ahí:

     · la receta no se veía como receta. Un ensamble de nueve componentes eran
       nueve renglones idénticos de setenta píxeles, sin descripción y sin más
       jerarquía que el orden de captura;
     · lo que no cuadraba se decía lejos de donde estaba. «UI-PN-COMP-C no tiene
       ruta» salía como un aviso apilado bajo el formulario, con el número de
       parte escrito otra vez, mientras su renglón —dos dedos más abajo— no decía
       nada;
     · y la regla de armado ocupaba trescientos veinte píxeles para contestar dos
       preguntas que caben en una frase.

   Así que la receta se dibuja, con la misma idea que la mesa de armado de la
   estación: el ensamble arriba en una banda y un CARRIL —aquí una RAMA— por
   componente, con su cantidad grande. Lo que cambia es el vestido: esto es el
   panel, y va con los tokens de aquí. Ni una clase de `station.css` se toca.

   Lo que se ve sin leer: cuántos lleva cada componente, cuál no cuadra —teñido
   entero, borde y fondo, nunca una barra al lado— y dónde termina la receta,
   porque la rama de añadir va punteada al final del árbol. */

.tw-receta {
    --tw-receta-hueco: 6px;
    /* El eje del árbol: la vertical de las ramas cae por aquí, y el codo de cada
       una sale de este mismo sitio. En un solo sitio para que la línea y el codo
       no se separen medio píxel al cambiar el ancho de la columna. */
    --tw-receta-eje: 11px;
    display: flex;
    flex-direction: column;
}

/* ── La banda · qué se arma y cómo ─────────────────────────────────────────

   No repite el número de parte: lo dice el título de la ficha dos dedos más
   arriba, y escribirlo otra vez era el primer dato dicho dos veces de esta
   pantalla. Lo que la banda dice es lo único que no está en ningún otro sitio:
   dónde nace el ensamble y qué coladas admite. */
.tw-receta-banda {
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
}

.tw-receta-banda-fila {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 10px;
    padding: 8px 10px;
}

/* El disco de la banda. Lleno y con icono, como las paradas fijas del
   recorrido: dice «esto no es un renglón más» antes de leer nada. */
.tw-receta-hito {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-quaternary);
    color: var(--tw-font-secondary);
}

.tw-receta-hito .ti {
    font-size: 0.875rem !important;
}

/* La regla, dicha con forma y pulsable · «Se arma en Empaque · una sola colada».

   Es la frase **y** el botón que la cambia, no una frase con un botón al lado:
   dos elementos para el mismo dato es la forma más fácil de decirlo dos veces.
   Va sin relleno de botón para que se lea como lo que es —una afirmación— y con
   el lápiz detrás para que se vea que se puede tocar. */
.tw-receta-regla {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 1 1 auto;
    min-width: 0;
    padding: 2px 6px;
    margin: 0;
    border: 1px solid transparent;
    border-radius: var(--tw-radius-sm);
    background-color: transparent;
    font-size: var(--tw-font-size-md);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
    text-align: left;
    cursor: pointer;
    transition:
        background-color var(--tw-transition),
        border-color var(--tw-transition);
}

.tw-receta-regla:hover,
.tw-receta-regla[aria-expanded='true'] {
    border-color: var(--tw-border-medium);
    background-color: var(--tw-bg-primary);
}

.tw-receta-regla > span {
    min-width: 0;
    overflow-wrap: anywhere;
}

.tw-receta-regla > .ti {
    flex: 0 0 auto;
    font-size: var(--tw-icon-size) !important;
    color: var(--tw-font-light);
}

/* Sin permiso de escritura no hay nada que pulsar: la frase se queda quieta. */
.tw-receta-regla:disabled {
    cursor: default;
    opacity: 1;
}

.tw-receta-regla:disabled > .ti {
    display: none;
}

/* Lo que no cuadra entre la regla y las rutas, en la banda y no en un cartel.

   Sólo llega aquí lo que es del ensamble entero —«la ruta del ensamble está
   vacía»—; lo que es de un componente baja a su rama, que es donde se arregla.
   Texto naranja y no recuadro: el recuadro convierte un estado en un anuncio, y
   tres anuncios seguidos se aprenden a no leer. */
.tw-receta-avisos {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 0 10px 8px 44px;
}

.tw-receta-aviso {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-tag-orange-fg);
}

.tw-receta-aviso > .ti {
    flex: 0 0 auto;
    font-size: var(--tw-font-size-md) !important;
    margin-top: 1px;
}

/* El formulario de la regla, desplegado desde la frase. Los dos desplegables de
   siempre, con sus pistas pegadas a su campo. */
.tw-receta-editor {
    border-top: 1px solid var(--tw-border-light);
    padding: 12px;
    background-color: var(--tw-bg-primary);
}

/* ── El árbol ──────────────────────────────────────────────────────────────

   La vertical baja desde la banda y cada rama sale con su codo. Se recorta en la
   última para que el árbol acabe en una rama y no en el aire, igual que el
   recorrido de «Por dónde pasa» acaba en una parada. */
.tw-receta-ramas {
    display: flex;
    flex-direction: column;
    gap: var(--tw-receta-hueco);
    padding-top: var(--tw-receta-hueco);
}

/* La columna de la cantidad se nombra **una vez**, aquí arriba, y no dentro de
   cada rama: nueve «por ensamble» seguidos son ruido, y uno solo sobre la
   columna dice exactamente lo mismo. Las sangrías son las de la rama —la marca
   y el borde de la tarjeta— para que caiga sobre los números y no al lado. */
.tw-receta-cab {
    position: relative;
    display: flex;
    padding-left: calc(22px + 8px + 10px);
    padding-top: 8px;
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-extra-light);
}

/* El rótulo se mete entre la banda y la primera rama, y la vertical tiene que
   atravesarlo: sin este tramo el árbol arrancaba veinte píxeles por debajo de
   la banda, colgando de nada. */
.tw-receta-cab::before {
    content: '';
    position: absolute;
    left: var(--tw-receta-eje);
    top: 0;
    bottom: 0;
    width: 1px;
    background-color: var(--tw-border-strong);
}

/* No se parte en dos renglones: es un rótulo de columna, y si sobresale un poco
   por la derecha cae sobre el número de parte, que es texto y no un control. */
.tw-receta-cab > span {
    width: 4.5rem;
    white-space: nowrap;
}

.tw-receta-rama {
    display: flex;
    align-items: stretch;
    gap: 8px;
}

.tw-receta-marca {
    position: relative;
    flex: 0 0 auto;
    width: 22px;
}

/* La vertical. Se sale por arriba del hueco entre ramas para tocar a la de
   encima —y, en la primera, para salir de debajo de la banda. */
.tw-receta-marca::before {
    content: '';
    position: absolute;
    left: var(--tw-receta-eje);
    top: calc(var(--tw-receta-hueco) * -1);
    bottom: 0;
    width: 1px;
    background-color: var(--tw-border-strong);
}

/* El codo, hasta el borde de la tarjeta. */
.tw-receta-marca::after {
    content: '';
    position: absolute;
    left: var(--tw-receta-eje);
    top: 22px;
    width: calc(22px - var(--tw-receta-eje) + 8px);
    height: 1px;
    background-color: var(--tw-border-strong);
}

.tw-receta-ramas > :last-child .tw-receta-marca::before {
    bottom: calc(100% - 22px);
}

.tw-receta-rama > .tw-card,
.tw-receta-rama > .tw-receta-nueva {
    flex: 1 1 auto;
    min-width: 0;
}

/* ── Una rama · un componente ──────────────────────────────────────────────

   Tres columnas, y la primera **fija**: la cantidad de todas las ramas cae en la
   misma vertical. Con una pista `auto` cada rama se mediría a sí misma y un
   ×120 desplazaría su número de parte respecto al de la rama de arriba, que es
   el desajuste pequeño y constante que se mira diez veces sin saber qué es. */
.tw-receta-cuerpo {
    display: grid;
    grid-template-columns: 4.5rem minmax(0, 1fr) auto;
    grid-template-areas:
        'cant ident acciones'
        'cant meta  acciones';
    align-items: center;
    gap: 2px 10px;
    padding: 8px 10px;
}

.tw-receta-cant {
    grid-area: cant;
    position: relative;
    display: flex;
    align-items: baseline;
    gap: 2px;
}

/* El «×» un punto por debajo del número y en gris: lo que se lee es cuántos
   lleva, y el signo sólo dice que es una multiplicación. Pero no tan pequeño
   que se pierda: «×2» se lee como una sola cosa, igual que en la mesa de
   armado. */
.tw-receta-por {
    font-size: var(--tw-font-size-lg);
    color: var(--tw-font-light);
}

.tw-receta-num {
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-medium);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
    color: var(--tw-font-primary);
}

/* Y cuando se puede editar, el número grande **es** el campo. No hay un número
   pintado y un campo debajo: eso sería el mismo dato dos veces, y además obliga
   a mirar cuál de los dos manda. */
.tw-receta-cant .form-control {
    /* Justo lo que mide un número de cuatro cifras a este cuerpo. Más ancho y el
       «×» se quedaba solo a la izquierda con el número pegado al otro borde, que
       es lo contrario de una multiplicación. */
    width: 3.5rem;
    height: auto;
    padding: 2px 4px;
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-medium);
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    text-align: right;
    /* Sin las flechas del campo numérico: Chrome les reserva el sitio aunque no
       se vean, y con ellas un «×120» salía como «12(», cortado por su propia
       caja. La cantidad se teclea; para subir de uno en uno está el teclado. */
    -moz-appearance: textfield;
    appearance: textfield;
}

.tw-receta-cant .form-control::-webkit-inner-spin-button,
.tw-receta-cant .form-control::-webkit-outer-spin-button {
    -webkit-appearance: none;
    appearance: none;
    margin: 0;
}

/* Guardando la cantidad · el mismo aro que un botón trabajando, sobre el campo.
   Trabajando no es lo mismo que deshabilitado: el campo se apaga mientras el
   viaje dura, pero lo que se ve es que está girando. */
.tw-receta-cant.is-busy .form-control {
    color: transparent;
}

.tw-receta-cant.is-busy::after {
    content: '';
    position: absolute;
    inset: 0;
    margin: auto;
    width: 13px;
    height: 13px;
    border: 2px solid var(--tw-font-secondary);
    border-top-color: transparent;
    border-radius: 50%;
    animation: tw-spin 0.6s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
    .tw-receta-cant.is-busy::after {
        animation-duration: 2.4s;
    }
}

.tw-receta-ident {
    grid-area: ident;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 8px;
    min-width: 0;
}

/* El número de parte cede el ancho y se parte antes que empujar a la píldora
   fuera de la tarjeta: lo que se recorta de un código es justo lo que lo
   distingue de otro. */
.tw-receta-pn {
    flex: 0 1 auto;
    min-width: 0;
    font-weight: var(--tw-weight-medium);
    overflow-wrap: anywhere;
}

/* La píldora de la rama es **una**: lo que no cuadra. Todo lo demás —la
   descripción, la posición, la revisión fijada— es texto de la línea de abajo. */
.tw-receta-ident > .tw-tag {
    margin-left: auto;
}

/* Envuelve, y lo que baja es lo de la derecha.

   La descripción es lo único que contesta «¿y éste cuál es?», así que tiene un
   suelo: por debajo de siete letras deja de decir nada y da igual que quepa. Sin
   ese suelo era el único elemento que encogía —los demás van a su tamaño— y en
   la columna estrecha de la ficha un componente con subconjunto se quedaba en
   «Sub…». Cuando no cabe todo, lo que baja de línea es la seña y el botón de
   abrir, que siguen leyéndose igual un renglón más abajo. */
.tw-receta-meta {
    grid-area: meta;
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 8px;
    min-width: 0;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* Una línea y una sola: la descripción de un número de parte puede medir lo que
   quiera, y dos renglones por rama son veinte píxeles por componente.

   Crece desde su suelo y no desde lo que mide: con la base en `auto` una
   descripción larga pedía primero su ancho entero, no cabía, y lo que bajaba de
   renglón era la seña de la etiqueta —una rama de tres renglones entre ramas de
   dos, en el ensamble de nueve—. Con la base en el suelo, la línea se reparte:
   la seña se queda en su sitio y lo que se recorta es la descripción, que es lo
   único de la línea que sobrevive a un recorte. */
.tw-receta-dice {
    flex: 1 1 7rem;
    min-width: 7rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Si lleva etiqueta propia · la frase **es** el interruptor.

   Antes eran dos cosas: una píldora que lo decía y un botón de icono que lo
   cambiaba, o sea el mismo dato dicho dos veces y con la píldora robándole el
   sitio a la que dice si la rama cuadra. */
.tw-receta-sena {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    margin-left: auto;
    padding: 0;
    border: 0;
    background: none;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
    white-space: nowrap;
}

button.tw-receta-sena {
    cursor: pointer;
}

button.tw-receta-sena:hover {
    color: var(--tw-font-primary);
    text-decoration: underline;
}

.tw-receta-sena > .ti {
    font-size: var(--tw-font-size-md) !important;
}

.tw-receta-acciones {
    grid-area: acciones;
    display: flex;
    align-items: center;
    gap: 2px;
}

/* La rama que no cuadra · teñida entera, borde y fondo. Nunca una barra al
   lado: a esa distancia no se ve, y de cerca se lee como un adorno. */
.tw-receta-rama.is-off > .tw-card {
    border-color: var(--tw-tag-orange-fg);
    background-color: var(--tw-tag-orange-bg);
}

/* Y su píldora se vuelve maciza: la del mismo tono sobre el fondo del mismo
   tono se quedaba en texto naranja suelto, y la píldora es el estado. */
.tw-receta-rama.is-off .tw-tag--orange {
    background-color: var(--tw-tag-orange-fg);
    color: var(--tw-bg-primary);
}

/* ── Un nivel más, plegado ─────────────────────────────────────────────────

   Un componente que además es ensamble enseña de qué se compone **él**, pero
   sólo un escalón y cerrado: el árbol entero de una torre de cuatro niveles no
   cabe en esta pestaña, y para eso está su propia ficha, a un clic del número de
   parte. */
.tw-receta-abrir {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    padding: 0;
    border: 0;
    background: none;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    cursor: pointer;
    white-space: nowrap;
}

.tw-receta-abrir:hover {
    color: var(--tw-font-primary);
    text-decoration: underline;
}

.tw-receta-abrir > .ti {
    font-size: var(--tw-font-size-md) !important;
}

.tw-receta-hijos {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px dashed var(--tw-border-light);
}

.tw-receta-hijo {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* La cantidad del nieto cae en su propia vertical, como la de las ramas. */
.tw-receta-hijo > .tw-receta-hijo-cant {
    flex: 0 0 2.75rem;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--tw-font-secondary);
}

.tw-receta-hijo > a {
    flex: 0 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* ── La rama punteada del final ────────────────────────────────────────────

   Agregar va **dentro** del árbol y no en la barra de arriba: ahí arriba era una
   acción más entre las de la pestaña, y aquí es el sitio donde la receta se
   acaba, que es justo donde se le añade el siguiente. */
.tw-receta-nueva {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 10px;
    border: 1px dashed var(--tw-border-strong);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-transparent-lighter);
    font-size: var(--tw-font-size-md);
    color: var(--tw-font-secondary);
    text-align: left;
    cursor: pointer;
    transition: background-color var(--tw-transition);
}

.tw-receta-nueva:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

/* El hueco de un ensamble sin componentes. Del tamaño de lo que falta y con el
   mismo trazo discontinuo que el hueco del recorrido. */
.tw-receta-hueco {
    flex: 1 1 auto;
    min-width: 0;
    padding: 16px;
    border: 1px dashed var(--tw-border-strong);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-transparent-lighter);
}

.tw-receta-hueco-titulo {
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
}

/* ── Se usa en · el camino de vuelta ───────────────────────────────────────

   En qué ensambles entra este número de parte. Una línea y no una lista de
   tarjetas: son tres datos por ensamble —cuál, cuántos y a dónde ir— y en
   tarjeta costaban sesenta píxeles cada uno. */
.tw-receta-usado {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 8px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

.tw-receta-usado-que {
    flex: 0 0 auto;
}

.tw-receta-usado a {
    color: var(--tw-font-secondary);
    white-space: nowrap;
}

.tw-receta-usado a:hover {
    color: var(--tw-accent);
}

.tw-receta-usado a > b {
    font-weight: var(--tw-weight-medium);
    font-variant-numeric: tabular-nums;
}

/* En el iPad en horizontal la ficha se pliega y la pestaña se queda con mil
   píxeles: cabe todo. Por debajo de eso la columna de acciones y la de la
   cantidad se comen la del número de parte, así que la línea de abajo baja
   entera y la cantidad se queda arriba con la identidad. */
@media (max-width: 720px) {
    .tw-receta-cuerpo {
        grid-template-columns: 3.5rem minmax(0, 1fr) auto;
    }

    .tw-receta-cab > span {
        width: 3.5rem;
    }

    .tw-receta-cant .form-control {
        width: 3.25rem;
        font-size: var(--tw-font-size-lg);
    }

    .tw-receta-meta {
        flex-wrap: wrap;
    }

    .tw-receta-sena {
        margin-left: 0;
    }
}

/* ══ EL ACOMODO DEL CAMIÓN · 22 de septiembre de 2026 ══════════════════════

   QUÉ RESUELVE. «Cajas» contesta qué lleva cada caja y «Tarimas» cómo va
   estibado, y las dos lo contestan **leyendo**: renglones, pastillas y una
   cuenta de «fuera de tarima» que hay que ir a buscar. La pregunta que faltaba
   —«¿cómo acomodo esto?»— no se contesta leyendo, se contesta moviendo.

   Salvador, ese día: *«haz de manera intuitiva y gráfica una manera fácil de
   entender cómo acomodar cajas y tarimas»*.

   LA FORMA ES LA DEL CAMIÓN. Cada tarima es una zona con sus cajas dentro, las
   que viajan sueltas caen en su propia zona, y a la izquierda está el andén: lo
   que el piso armó y todavía no va en ningún embarque. Se arrastra de una zona
   a otra, y lo que no se puede arrastrar —una tablet, un ratón que se resiste—
   se hace con el botón de cada tarjeta. Es el mismo plano que la estación pinta
   en «Empacar el embarque», con los mismos tonos, para que la oficina y el piso
   estén mirando el mismo dibujo.  */

.tw-acomodo {
    display: grid;
    grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

/* El andén · lo que espera fuera del camión. Se pega arriba al desplazar: es el
   origen del arrastre y tiene que seguir a la vista mientras se busca destino. */
.tw-acomodo-anden {
    position: sticky;
    top: 8px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    /* El mismo relleno que una zona del camión · 22 de septiembre de 2026: con
       12 px aquí y 12 px + borde allá, las dos columnas empezaban en sitios
       distintos y el andén parecía metido hacia dentro. */
    padding: 12px 12px 14px;
    border: 1px dashed var(--tw-border-medium);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
}

/* Y su cabecera y su nota, a ras del borde interior: la tarjeta de abajo no
   lleva sangría propia, así que cualquier margen aquí las descuadra. */
.tw-acomodo-anden > .tw-acomodo-zona-head,
.tw-acomodo-anden > .tw-acomodo-nota {
    margin: 0;
}

.tw-acomodo-camion {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Una zona es un bulto que contiene otros: la tarima, y la zona de las sueltas.
   El borde punteado dice «aquí se suelta»; el sólido, que ya está firmada. */
.tw-acomodo-zona {
    border: 1px dashed var(--tw-border-medium);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-secondary);
    padding: 12px 12px 14px;
}

.tw-acomodo-zona--cerrada {
    border-style: solid;
    border-color: var(--tw-border-light);
}

/* Encima de una zona mientras se arrastra: el destino se enciende entero, no
   con una línea. Ver el mismo criterio en `.st-card--sirve`. */
.tw-acomodo-zona.is-destino {
    border-color: var(--tw-accent);
    border-style: solid;
    background-color: var(--tw-accent-secondary);
}

.tw-acomodo-zona-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 10px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
}

.tw-acomodo-zona-head .tw-code {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
}

.tw-acomodo-zona-acciones {
    margin-left: auto;
    display: flex;
    gap: 6px;
}

.tw-acomodo-cards {
    display: grid;
    /* Cajas de ancho parejo y pegadas a la izquierda, como se apilan de verdad
       sobre la tarima. Con `1fr` dos cajas se estiraban a media pantalla cada
       una y dejaban de parecer cajas; con `auto-fill` y sin estirar, la
       plataforma que asoma a la derecha es justo el sitio que queda libre. */
    grid-template-columns: repeat(auto-fill, minmax(190px, 220px));
    justify-content: start;
    gap: 8px;
    min-height: 52px;
}

/* El andén es una columna: sus tarjetas se leen de arriba abajo y no compiten
   con el ancho del camión. */
.tw-acomodo-anden .tw-acomodo-cards {
    grid-template-columns: minmax(0, 1fr);
}

.tw-acomodo-caja {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 12px;
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-sm);
    background-color: var(--tw-bg-primary);
    cursor: grab;
}

.tw-acomodo-caja:active {
    cursor: grabbing;
}

/* La que se está arrastrando se apaga en su sitio: deja ver que salió de ahí
   sin quitarla, que es lo que permite arrepentirse soltándola donde estaba. */
.tw-acomodo-caja.is-arrastrando {
    opacity: 0.4;
}

/* Armada y firmada: el verde del sistema, en el borde y no en un relleno que
   competiría con el resalte del destino. */
.tw-acomodo-caja--armada {
    border-color: var(--tw-tag-green-fg);
}

/* La que el piso ya metió en su tarima no se mueve desde aquí: se dice con el
   cursor antes de intentarlo, y el servidor lo repite con su motivo. */
.tw-acomodo-caja--fija {
    cursor: not-allowed;
}

.tw-acomodo-caja-top {
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 20px;
}

.tw-acomodo-caja-top .tw-code {
    font-size: var(--tw-font-size-sm);
}

.tw-acomodo-mover {
    margin-left: auto;
    border: 0;
    background: none;
    color: var(--tw-font-light);
    padding: 2px 4px;
    line-height: 1;
    cursor: pointer;
}

.tw-acomodo-mover:hover {
    color: var(--tw-font-primary);
}

.tw-acomodo-vacio {
    grid-column: 1 / -1;
    padding: 12px 10px;
    text-align: center;
    color: var(--tw-font-light);
    font-size: var(--tw-font-size-sm);
    border: 1px dashed var(--tw-border-light);
    border-radius: var(--tw-radius-sm);
}

@media (max-width: 992px) {
    .tw-acomodo {
        grid-template-columns: minmax(0, 1fr);
    }

    /* En tablet el andén deja de estar pegado: con una sola columna, quedarse
       fijo arriba se comería media pantalla del camión. */
    .tw-acomodo-anden {
        position: static;
    }
}

/* ── La tarima, dibujada · 22 de septiembre de 2026 ───────────────────────

   La misma figura que el plano del piso (`.st-tarima-base` en `station.css`) y
   por el mismo motivo: *«las tarimas son pallets, ¿podrías ser más interactivo
   y dinámico para representar esto?»*. La zona se apoya en su plataforma —tres
   tablas y dos tacos, dibujados con CSS— y las cajas van encima. Que la oficina
   y el piso miren el mismo dibujo es lo que permite señalar una tarima por
   radio sin leer su código. */

.tw-acomodo-zona--tarima .tw-acomodo-cards {
    padding: 0 4px 2px;
}

.tw-pallet-base {
    /* Alineada con las cajas que lleva encima: las dos usan la misma sangría,
       o la madera sobresale por un lado y se lee como una barra suelta. */
    margin: 4px 4px 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
}

/* El tablero · tres tablas con su junta. */
.tw-pallet-tablas {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
}

.tw-pallet-tablas > span {
    height: 9px;
    border-radius: 2px;
    background-image: linear-gradient(
        180deg,
        var(--tw-border-strong) 0%,
        var(--tw-border-strong) 55%,
        var(--tw-border-medium) 100%
    );
    transition: background-color var(--tw-transition);
}

/* Los tacos · cortos, oscuros y metidos hacia dentro.

   Eran tan anchos como las tablas y con el mismo tono, así que la plataforma se
   leía como dos filas de barras —o peor, como una barra de progreso a medias—.
   Un taco es lo que queda en sombra debajo del tablero: estrecho, más oscuro y
   sin llegar a los extremos. */
.tw-pallet-tacos {
    display: flex;
    justify-content: space-between;
    padding: 0 6%;
}

.tw-pallet-tacos > span,
.tw-pallet-tacos::before {
    content: '';
    width: 11%;
    height: 6px;
    border-radius: 0 0 2px 2px;
    background-color: var(--tw-border-light);
}

/* Estibada y firmada: el mismo verde que su chip, sólo en el tablero. */
.tw-acomodo-zona--cerrada .tw-pallet-tablas > span {
    background-image: none;
    background-color: var(--tw-tag-green-fg);
}

/* Y con una caja encima a punto de soltarse, la plataforma se enciende: la
   señal va donde va a caer la caja, no sólo en el borde de la zona. */
.tw-acomodo-zona.is-destino .tw-pallet-tablas > span {
    background-image: none;
    background-color: var(--tw-accent);
}

/* La carga se levanta un punto al pasar por encima: dice que se puede agarrar
   sin decirlo con palabras. */
.tw-acomodo-caja:hover {
    border-color: var(--tw-border-strong);
    transform: translateY(-1px);
}

.tw-acomodo-caja {
    transition:
        transform var(--tw-transition),
        border-color var(--tw-transition);
}

.tw-acomodo-caja--fija:hover {
    transform: none;
}

/* ── Fuera del camión y dentro · 22 de septiembre de 2026 ────────────────

   Lo que está en el andén **no va en el embarque todavía**, y dos columnas de
   tarjetas iguales no dicen eso. Lo dicen el rótulo de cada mitad, la nota del
   andén y el punteado frente al sólido. */

.tw-acomodo-titulo {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
    padding-bottom: 2px;
}

.tw-acomodo-nota {
    font-size: var(--tw-font-size-xs);
    line-height: 1.45;
    color: var(--tw-font-light);
}

/* El camión se dibuja sólido: lo de dentro está decidido. El andén sigue
   punteado, que es como se dibuja lo que todavía no entró. */
.tw-acomodo-camion .tw-acomodo-zona {
    border-style: solid;
}

/* ── El camión de la ficha del embarque · 23 de septiembre de 2026 ─────────

   Lo mismo que el piso («Empacar el embarque») con los tamaños de la oficina:
   tarimas en mosaico, al abrir una sus cajas en casillas por número de parte,
   y al abrir una casilla la tarjeta de la caja. Ver `camion.js`. En el piso
   la casilla mide 32 px porque se toca con el dedo; aquí 16, que es lo que un
   ratón acierta y lo que deja 160 cajas en tres renglones. */

.tw-camion-resumen {
    margin-bottom: 12px;
}

.tw-camion-mosaicos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px;
}

.tw-camion-mosaico {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    padding: 12px 12px 8px;
    text-align: left;
    font: inherit;
    color: var(--tw-font-primary);
    border: 1px solid var(--tw-border-medium);
    border-radius: var(--tw-radius-md);
    background-color: var(--tw-bg-primary);
    cursor: pointer;
    transition:
        border-color var(--tw-duration-fast) var(--tw-ease),
        background-color var(--tw-duration-fast) var(--tw-ease);
}

.tw-camion-mosaico:hover {
    border-color: var(--tw-border-strong);
    background-color: var(--tw-bg-secondary);
}

.tw-camion-mosaico:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 2px;
}

.tw-camion-mosaico--armada {
    border-color: var(--tw-tag-green-fg);
}

.tw-camion-mosaico--armada .tw-pallet-tablas > span {
    background-image: none;
    background-color: var(--tw-tag-green-fg);
}

.tw-camion-tipo {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-tertiary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.tw-camion-codigo {
    font-weight: var(--tw-weight-semibold);
}

.tw-camion-nota {
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-secondary);
}

.tw-camion-estado {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.tw-camion-cifra strong {
    font-size: var(--tw-font-size-lg);
    font-weight: var(--tw-weight-semibold);
    margin-right: 2px;
}

.tw-camion-barra {
    height: 4px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-tertiary);
    overflow: hidden;
}

.tw-camion-barra > span {
    display: block;
    height: 100%;
    background-color: var(--tw-tag-green-fg);
}

.tw-camion-mosaico .tw-pallet-base {
    margin-top: auto;
    padding-top: 4px;
}

/* Las migas · dónde estás y cómo se vuelve. La primera lleva la flecha. */
.tw-camion-migas {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 12px;
}

.tw-camion-miga {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 6px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: none;
    font: inherit;
    color: var(--tw-font-tertiary);
    cursor: pointer;
}

button.tw-camion-miga:hover {
    background-color: var(--tw-bg-transparent-light);
    color: var(--tw-font-primary);
}

.tw-camion-miga--aqui {
    color: var(--tw-font-primary);
    font-weight: var(--tw-weight-medium);
    cursor: default;
}

.tw-camion-sep {
    font-size: var(--tw-icon-size-sm);
    color: var(--tw-font-light);
}

.tw-camion-bloque > [data-tarima-card] {
    margin-bottom: 16px !important;
}

/* Un grupo por número de parte: su renglón y sus casillas. */
.tw-camion-grupo {
    padding: 10px 0;
    border-top: 1px solid var(--tw-border-light);
}

.tw-camion-grupo-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 8px;
}

.tw-camion-parte {
    font-weight: var(--tw-weight-medium);
}

.tw-casillas {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.tw-casilla {
    width: 16px;
    height: 16px;
    padding: 0;
    border: 1px solid var(--tw-border-strong);
    border-radius: var(--tw-radius-xs);
    background-color: var(--tw-bg-secondary);
    cursor: pointer;
    transition: transform var(--tw-duration-fast) var(--tw-ease);
}

.tw-casilla:hover {
    transform: scale(1.35);
    border-color: var(--tw-accent);
}

.tw-casilla--subida {
    border-color: var(--tw-tag-green-fg);
    background-color: var(--tw-tag-green-fg);
}

.tw-casilla--armada {
    border: 2px solid var(--tw-tag-green-fg);
    background-color: var(--tw-tag-green-bg);
}

.tw-casilla--rechazada {
    border-color: var(--tw-tag-red-fg);
    background-color: var(--tw-tag-red-bg);
}

.tw-casilla--vacia {
    border-style: dashed;
    background-color: transparent;
}

.tw-casilla--muestra {
    display: inline-block;
    width: 10px;
    height: 10px;
    vertical-align: -1px;
    cursor: default;
}

.tw-casilla--muestra:hover {
    transform: none;
}

.tw-camion-leyenda {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--tw-border-light);
}

.tw-camion-leyenda > span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.tw-camion-tarjeta {
    margin-top: 4px;
}

::view-transition-group(camion-nivel) {
    animation-duration: var(--tw-duration-slow, 320ms);
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* ── El acomodo se mide contra su panel · 23 de septiembre de 2026 ─────────

   Salvador, con la ficha de EMB-2026-0010 abierta: *«esto se ve mal; cuida los
   paddings, la alineación de las cards a las laterales, más el overflow»*. El
   corte a una columna miraba la **ventana** (`max-width: 992px`), y el panel de
   una ficha mide 570 px a 1440 de pantalla: el andén se quedaba sus 300 y al
   camión le tocaban 206, con tarjetas de 190 más 24 de relleno. Se salían por
   la derecha. Es la trampa de `otv-tablas-homologadas`: el ancho lo manda el
   panel, no la ventana.

   Así que el contenedor es el panel (container query) y por debajo de 760 px
   el andén va arriba, a lo ancho y compacto, y el camión debajo con todo el
   ancho. Y las tarjetas llenan la fila (`1fr`): con ancho fijo y pegadas a la
   izquierda quedaba un hueco desigual a la derecha y ninguna columna cuadraba
   con el borde de su zona. */

.tw-record-content:has(> .tw-acomodo) {
    container-type: inline-size;
    container-name: acomodo;
}

.tw-acomodo-cards {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 176px), 1fr));
}

/* La plataforma a ras de las tarjetas: las dos con la misma sangría, o la
   madera asoma por un lado. */
.tw-acomodo-zona--tarima .tw-acomodo-cards {
    padding: 0;
}

.tw-acomodo-pallet-base,
.tw-acomodo-zona .tw-pallet-base {
    margin: 8px 0 0;
}

@container acomodo (max-width: 760px) {
    .tw-acomodo {
        grid-template-columns: minmax(0, 1fr);
    }

    .tw-acomodo-anden {
        position: static;
    }

    .tw-acomodo-anden .tw-acomodo-cards {
        grid-template-columns: repeat(auto-fill, minmax(min(100%, 176px), 1fr));
    }
}

/* Doce a la vista y el resto detrás del botón. */
.tw-acomodo-zona:not(.is-completa) .tw-acomodo-caja--resto {
    display: none;
}

.tw-acomodo-mas {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 32px;
    border: 1px dashed var(--tw-border-medium);
    border-radius: var(--tw-radius-sm);
    background: none;
    font: inherit;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    cursor: pointer;
}

.tw-acomodo-mas:hover {
    border-color: var(--tw-border-strong);
    color: var(--tw-font-primary);
}

.tw-acomodo-zona.is-completa .tw-acomodo-mas .ti {
    transform: rotate(180deg);
}

/* ── Tarimas, cajas y acomodo: el contenido a ras de su barra ─────────────

   Salvador: *«mira cómo las cards no se alinean con Acomodo y el botón de
   Nueva tarima de manera horizontal»*. La barra de la vista lleva 12 px de
   relleno y `.tw-record-content` 24: el título y el botón empezaban y acababan
   12 px más afuera que las tarjetas de debajo. En estas tres subpestañas lo que
   hay debajo son tarjetas y zonas, no una tabla que llegue al filo, así que el
   contenido toma la sangría de su barra y los dos bordes cuadran. */
#tab-bultos .tw-record-content {
    padding-left: 12px;
    padding-right: 12px;
}

/* ── Las barras de tarimas, cajas y acomodo no se amontonan ──────────────

   Salvador, con «Cajas sin tarima» abierta en un panel de 570 px: *«cuidado
   con estos temas de amontonamiento»*. La barra no envuelve —es de una línea
   por diseño— y a ese ancho el título se partía en dos renglones, el buscador
   se aplastaba hasta enseñar una «E» y los tres botones se empujaban. Se mide
   la barra (container query), no la ventana:

     · el título nunca se parte, y la ayuda que lleva al lado —«Arrastra para
       mover…»— cede primero, con puntos suspensivos;
     · el buscador guarda un mínimo en el que se lee lo que se escribe;
     · por debajo de 640 px los botones se quedan en su icono, con su nombre en
       el `title` y el `aria-label`. */
#tab-bultos .tw-view-bar {
    container-type: inline-size;
    container-name: barra-bultos;
}

#tab-bultos .tw-view-name {
    flex: 0 1 auto;
    min-width: 0;
    white-space: nowrap;
}

#tab-bultos .tw-view-name .tw-meta {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#tab-bultos .tw-view-actions {
    flex: 0 1 auto;
    min-width: 0;
    justify-content: flex-end;
}

#tab-bultos .tw-view-actions .input-icon {
    flex: 1 1 12rem;
    min-width: 9rem;
}

#tab-bultos .tw-view-actions .btn,
#tab-bultos .tw-view-actions .tw-tag {
    flex-shrink: 0;
    white-space: nowrap;
}

@container barra-bultos (max-width: 640px) {
    #tab-bultos .tw-view-actions .btn {
        font-size: 0;
        gap: 0 !important;
        padding-inline: 8px;
    }

    #tab-bultos .tw-view-actions .btn .ti {
        font-size: var(--tw-icon-size);
        margin: 0 !important;
    }

    #tab-bultos .tw-view-name .tw-meta {
        display: none;
    }

    /* Un botón solo sí cabe con su nombre: quitárselo es esconder la única
       acción de la barra detrás de un icono. */
    #tab-bultos .tw-view-actions .btn:only-of-type {
        font-size: var(--tw-font-size-md);
        gap: 8px !important;
        padding-inline: 12px;
    }

    #tab-bultos .tw-view-actions .btn:only-of-type .ti {
        font-size: var(--tw-icon-size-sm);
    }
}

/* --- Detalle de una verificación (po_single/verifications.js) --------------
   Qué se escaneó, cuándo y con qué ritmo, y cuánto de cada número de parte
   queda cubierto. A la izquierda los números de parte con su barra de
   verificadas contra impresas —se elige uno y todo lo de la derecha se
   acota—; a la derecha los indicadores, la actividad en columnas y la tabla.
   Todo sale de tokens: el diálogo se ve igual en claro y en oscuro. */
.tw-ver {
    display: grid;
    grid-template-columns: 320px minmax(0, 1fr);
    height: 100%;
    min-height: 0;
}

.tw-ver-aside {
    display: flex;
    flex-direction: column;
    min-height: 0;
    border-right: 1px solid var(--tw-border-light);
    background-color: var(--tw-bg-secondary);
}

.tw-ver-aside-search {
    padding: 12px;
    border-bottom: 1px solid var(--tw-border-light);
}

.tw-ver-parts {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 4px;
}

.tw-ver-part {
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    border-radius: var(--tw-radius-sm);
    background: transparent;
    color: var(--tw-font-primary);
    text-align: left;
}

.tw-ver-part:hover {
    background-color: var(--tw-bg-transparent-light);
}

.tw-ver-part.is-active {
    background-color: var(--tw-bg-transparent-lighter, var(--tw-bg-transparent-light));
    box-shadow: inset 0 0 0 1px var(--tw-border-medium);
}

.tw-ver-part-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    font-variant-numeric: tabular-nums;
}

.tw-ver-part-name {
    font-weight: var(--tw-weight-medium);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-ver-part-count {
    font-weight: var(--tw-weight-semibold);
}

.tw-ver-part-foot {
    display: flex;
    align-items: center;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-tertiary);
    font-variant-numeric: tabular-nums;
}

.tw-ver-part-foot .tw-progress {
    flex: 1 1 auto;
    width: auto;
}

/* El resumen se queda quieto arriba y sólo la tabla se desplaza: al bajar por
   cientos de seriales, los indicadores y la actividad siguen a la vista. Para
   revisar muchos renglones se oculta con el botón de la barra de la tabla. */
.tw-ver-main {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}

.tw-ver-main.is-sin-resumen .tw-ver-summary {
    display: none;
}

.tw-ver-summary {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px 16px;
}

/* Indicadores a media altura: aquí acompañan, no son el tablero. */
.tw-ver-summary .tw-kpi {
    min-height: 0;
    padding: 8px 12px;
}

.tw-ver-summary .tw-kpi-value {
    font-size: var(--tw-font-size-xl);
}

/* Actividad y avance, lado a lado: la actividad dice cuándo, el avance dice
   cuánto falta. */
.tw-ver-row {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(260px, 1fr);
    gap: 8px;
}

@media (max-width: 1199.98px) {
    .tw-ver-row {
        grid-template-columns: minmax(0, 1fr);
    }
}

.tw-ver-row > .tw-card + .tw-card {
    margin-top: 0;
}

/* Impresas contra verificadas: una barra apilada sobre lo generado. Verde lo
   verificado, naranja lo impreso sin verificar, gris lo que falta imprimir. */
.tw-ver-stack {
    display: flex;
    height: 8px;
    border-radius: var(--tw-radius-pill);
    overflow: hidden;
    background-color: var(--tw-bg-quaternary);
}

.tw-ver-stack--thin {
    height: 4px;
}

.tw-ver-stack > span {
    display: block;
    height: 100%;
}

.tw-ver-seg--verified {
    background-color: var(--tw-tag-green-fg);
}

.tw-ver-seg--printed {
    background-color: var(--tw-tag-orange-fg);
}

.tw-ver-progress {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 4px 12px 12px;
}

.tw-ver-progress-head {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font-variant-numeric: tabular-nums;
}

.tw-ver-progress-pct {
    font-size: var(--tw-font-size-xl);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-primary);
}

.tw-ver-legend {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-secondary);
    font-variant-numeric: tabular-nums;
}

.tw-ver-legend > div {
    display: flex;
    align-items: center;
    gap: 6px;
}

.tw-ver-legend strong {
    margin-left: auto;
    font-weight: var(--tw-weight-medium);
    color: var(--tw-font-primary);
}

.tw-ver-dot {
    width: 8px;
    height: 8px;
    border-radius: var(--tw-radius-pill);
    background-color: var(--tw-bg-quaternary);
}

.tw-ver-dot.tw-ver-seg--verified {
    background-color: var(--tw-tag-green-fg);
}

.tw-ver-dot.tw-ver-seg--printed {
    background-color: var(--tw-tag-orange-fg);
}

/* Escaneadas / Faltan en la barra de la tabla: las pestañas chicas del tema,
   pegadas al borde inferior para que el subrayado caiga sobre la línea. */
.tw-ver-tabs {
    display: flex;
    align-items: flex-end;
    align-self: stretch;
    gap: 16px;
}

.tw-ver-tabs .tw-scope-tab {
    min-height: 40px;
}

.tw-ver-inline-bar {
    display: block;
    width: 140px;
    flex: 0 0 auto;
}

/* La actividad: una columna por día (u hora, si fue un solo día). La parte del
   número de parte elegido va en el acento y el resto en su tono claro, para
   que se vea cuándo se escaneó eso dentro del total. */
.tw-ver-activity {
    padding: 8px 12px 10px;
}

.tw-ver-bars {
    display: flex;
    align-items: stretch;
    gap: 4px;
    height: 84px;
    border-bottom: 1px solid var(--tw-border-medium);
}

/* La columna no lleva fondo: un día sin escaneos tiene que verse vacío, no
   como una barra gris llena. La cifra va encima de lo que mide. */
.tw-ver-bar {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    align-items: stretch;
    min-width: 4px;
}

.tw-ver-bar-value {
    margin-bottom: 2px;
    font-size: var(--tw-font-size-xs);
    line-height: 14px;
    color: var(--tw-font-tertiary);
    text-align: center;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.tw-ver-bar-fill {
    display: flex;
    flex-direction: column-reverse;
    min-height: 0;
    border-radius: var(--tw-radius-xs) var(--tw-radius-xs) 0 0;
    overflow: hidden;
}

.tw-ver-bar-sel,
.tw-ver-bar-rest {
    display: block;
    width: 100%;
}

.tw-ver-bar-sel {
    background-color: var(--tw-accent);
}

.tw-ver-bar-rest {
    flex: 1 1 auto;
    background-color: var(--tw-bg-quaternary);
}

.tw-ver-bar-labels {
    display: flex;
    gap: 3px;
    margin-top: 4px;
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-light);
    font-variant-numeric: tabular-nums;
}

.tw-ver-bar-labels > span {
    flex: 1 1 0;
    min-width: 3px;
    overflow: hidden;
    text-align: center;
    white-space: nowrap;
}

.tw-ver-table-wrap {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    border-top: 1px solid var(--tw-border-light);
}

.tw-ver-table-wrap .tw-view-bar {
    position: sticky;
    top: 0;
    z-index: 2;
    background-color: var(--tw-bg-primary);
}

.tw-ver-table thead th {
    position: sticky;
    top: 40px;
    z-index: 1;
    background-color: var(--tw-bg-primary);
}

/* El corte de día: un subtítulo de la tabla, en el tono de la tarjeta y no en
   el fondo de página (en oscuro ese fondo es negro y partía la tabla). */
.tw-ver-day td {
    background-color: var(--tw-bg-transparent-light);
    font-size: var(--tw-font-size-sm);
    font-weight: var(--tw-weight-semibold);
    color: var(--tw-font-secondary);
}

.tw-ver-gap td {
    padding-top: 2px;
    padding-bottom: 2px;
    font-size: var(--tw-font-size-sm);
    color: var(--tw-font-light);
}

/* El serial se lee por su final: el consecutivo va en negrita y el prefijo,
   igual en toda la orden, se apaga. */
.tw-ver-serial {
    font-variant-numeric: tabular-nums;
}

.tw-ver-serial-pre {
    color: var(--tw-font-light);
}

.tw-ver-serial-num {
    font-weight: var(--tw-weight-semibold);
}

@media (max-width: 991.98px) {
    .tw-ver {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto minmax(0, 1fr);
    }

    .tw-ver-aside {
        max-height: 220px;
        border-right: 0;
        border-bottom: 1px solid var(--tw-border-light);
    }
}

/* La caja del acomodo, abierta: a lo ancho de su fila, con lo que se le
   declaró dentro. Ver `verQueLleva` en `detail.js`. */
.tw-acomodo-caja[role='button'] {
    cursor: pointer;
}

.tw-acomodo-caja:focus-visible {
    outline: 2px solid var(--tw-accent);
    outline-offset: 2px;
}

.tw-acomodo-caja.is-abierta {
    grid-column: 1 / -1;
    border-color: var(--tw-border-strong);
    transform: none;
}

.tw-acomodo-dentro {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--tw-border-light);
    cursor: default;
}

.tw-acomodo-dentro-tabla {
    width: 100%;
    font-size: var(--tw-font-size-sm);
    border-collapse: collapse;
}

.tw-acomodo-dentro-tabla th {
    font-weight: var(--tw-weight-regular);
    color: var(--tw-font-tertiary);
    text-align: left;
    padding: 0 8px 4px 0;
}

.tw-acomodo-dentro-tabla td {
    padding: 4px 8px 4px 0;
    border-top: 1px solid var(--tw-border-light);
    color: var(--tw-font-primary);
}

/* La cabecera de la caja abierta: las migas ceden y el «3 de 160» con sus dos
   flechas va siempre junto, a la derecha. Se partía y la flecha de «siguiente»
   caía sola al renglón de abajo. */
.tw-camion-cabeza {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
}

.tw-camion-cabeza .tw-camion-migas {
    flex: 1 1 auto;
    min-width: 0;
    margin-bottom: 0;
}

.tw-camion-paso {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    white-space: nowrap;
}

/* ── Lo que el piso tiene listo, en fantasma · 23 de septiembre de 2026 ─────

   Fuera del embarque todavía: borde punteado y fondo transparente, igual que
   el andén del acomodo, para que no se lea como una tarima o una caja más del
   camión. El botón es la invitación. Ver `pintarFantasmasDelPiso`. */
.tw-fantasmas {
    margin-top: 16px;
}

.tw-fantasmas:first-child {
    margin-top: 0;
    margin-bottom: 16px;
}

.tw-fantasmas-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 8px;
}

.tw-fantasmas-lista {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px;
}

.tw-fantasma {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    padding: 12px;
    border: 1px dashed var(--tw-border-strong);
    border-radius: var(--tw-radius-md);
    background-color: transparent;
    color: var(--tw-font-secondary);
    transition: border-color var(--tw-duration-fast) var(--tw-ease);
}

.tw-fantasma:hover {
    border-color: var(--tw-accent);
}

.tw-fantasma .tw-camion-codigo {
    color: var(--tw-font-secondary);
}

.tw-fantasma .btn {
    margin-top: 4px;
}

.tw-fantasma-tipo {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--tw-font-size-xs);
    color: var(--tw-font-tertiary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* La flecha de «Tarimas» y los separadores de la ruta, al tamaño de un icono
   dentro de un botón (14 px). Heredaban los 16 de los iconos de fila y la
   flecha se veía más grande que el texto que acompaña. */
.tw-camion-miga .ti,
.tw-camion-sep {
    font-size: var(--tw-icon-size-sm) !important;
    width: var(--tw-icon-size-sm);
    height: var(--tw-icon-size-sm);
    line-height: 1;
}

/* ══════════════════════════════════════════════════════════════════════════
   El modo práctica · como el modo de prueba de Stripe
   ══════════════════════════════════════════════════════════════════════════
   Un marco naranja alrededor de toda la ventana y una pestaña colgando arriba
   al centro (`views/partials/_practica.ejs`). Encima de todo —también de la
   barra lateral fija, que va a 1030— y sin recibir clics salvo el botón, así
   que no desplaza ni tapa el trabajo. El texto usa el fondo del naranja y el
   fondo el texto: contrasta igual en claro y en oscuro. */
.tw-practica-marco {
    position: fixed;
    inset: 0;
    border: 3px solid var(--tw-tag-orange-fg);
    pointer-events: none;
    z-index: 2000;
}

.tw-practica-pestana {
    position: fixed;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    z-index: 2001;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 10px 4px;
    border-radius: 0 0 6px 6px;
    background: var(--tw-tag-orange-fg);
    color: var(--tw-tag-orange-bg);
    font-size: 12px;
    line-height: 18px;
    white-space: nowrap;
}

.tw-practica-pestana .ti {
    font-size: 14px;
}

.tw-practica-salir {
    margin-left: 4px;
    padding: 0 8px;
    border: 1px solid currentColor;
    border-radius: 4px;
    background: transparent;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

.tw-practica-salir:hover {
    background: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
}

/* El interruptor del menú de la cuenta. */
.tw-practica-switch {
    margin-left: auto;
    width: 28px;
    height: 16px;
    border-radius: 8px;
    background: var(--tw-gray-40, #d6d6d6);
    position: relative;
    flex: none;
}

.tw-practica-switch::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #fff;
    transition: left 120ms ease;
}

.tw-practica-switch[data-encendido='si'] {
    background: var(--tw-tag-orange-fg);
}

.tw-practica-switch[data-encendido='si']::after {
    left: 14px;
}

/* La hoja de una práctica: los códigos con su QR, para escanear de la
   pantalla o del papel. Al imprimir sólo sale la hoja. */
.tw-practica-codigos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
    gap: 12px;
}

.tw-practica-codigo {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px;
    border: 1px solid var(--tw-border-light);
    border-radius: var(--tw-radius-md);
}

.tw-practica-qr {
    width: 72px;
    height: 72px;
    flex: none;
    background: #fff;
    border-radius: 4px;
    padding: 4px;
}

.tw-practica-qr svg {
    width: 100%;
    height: 100%;
}

@media print {
    .navbar-vertical,
    .tw-no-imprimir,
    .tw-practica-marco,
    .tw-practica-pestana {
        display: none !important;
    }

    .page-wrapper {
        margin: 0 !important;
    }

    .tw-practica-corrida .tw-cols {
        display: block;
        overflow: visible !important;
    }
}

/* «Tu mesa» de práctica (`practica/mesa.js`): las etiquetas que la persona
   tendría delante —canastillas, cajas, lo recién impreso—, dibujadas como las
   de verdad para escanearlas de la pantalla con el escáner. Abajo a la
   derecha; la guía va a la izquierda. Por debajo de los modales (1050), que
   la tapaban; mientras hay globo de la guía, por encima de su velo (10000):
   un Data Matrix oscurecido se lee peor de la pantalla. */
.tw-mesa {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 1040;
    width: min(340px, calc(100vw - 32px));
    border: 2px solid var(--tw-tag-orange-fg);
    border-radius: var(--tw-radius-md, 8px);
    background: var(--tw-bg-primary);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
    overflow: hidden;
}

.tw-mesa__cabeza {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 8px 12px;
    border: 0;
    background: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
    font-weight: 600;
    font-size: 13px;
    cursor: pointer;
}

.tw-mesa__cuenta {
    padding: 0 6px;
    border-radius: 8px;
    background: var(--tw-tag-orange-fg);
    color: var(--tw-tag-orange-bg);
    font-size: 12px;
}

.tw-mesa__cuerpo {
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-height: min(62vh, 560px);
    padding: 10px;
    overflow-y: auto;
}

.tw-mesa[data-plegada='si'] .tw-mesa__cuerpo {
    display: none;
}

.tw-mesa[data-plegada='si'] .tw-mesa__cabeza .ti-chevron-down {
    transform: rotate(180deg);
}

.tw-mesa__titulo {
    margin-bottom: 6px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--tw-font-light);
}

.tw-mesa__lista {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* Una etiqueta como se vería pegada: blanca, con borde, código grande y su
   símbolo. Siempre en claro, también en tema oscuro: es papel. */
.tw-etiqueta-practica {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 10px;
    border: 2px solid #111;
    border-radius: 4px;
    background: #fff;
    color: #111;
}

.tw-etiqueta-practica__simbolo {
    width: 92px;
    flex: none;
}

.tw-etiqueta-practica__simbolo svg {
    display: block;
    width: 100%;
    height: auto;
}

.tw-etiqueta-practica__datos {
    min-width: 0;
}

.tw-etiqueta-practica__clase {
    font-size: 11px;
    color: #555;
}

.tw-etiqueta-practica__codigo {
    font-size: 16px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    word-break: break-all;
}

.tw-etiqueta-practica__texto {
    font-size: 11px;
    color: #333;
    word-break: break-all;
}

/* driver.js apaga los clics de toda la página mientras hay un globo
   (`.driver-active * { pointer-events: none }`): la mesa tiene que seguir
   respondiendo, porque apuntarle a una etiqueta es justo lo que pide el globo. */
.driver-active .tw-mesa,
.driver-active .tw-mesa * {
    pointer-events: auto;
}

.driver-active .tw-mesa {
    z-index: 10001;
}

/* «Arma tu práctica» (`/practica`): la tarjeta que combina va primero y se
   distingue; en el modal, cada partida es un recuadro con su cuenta abajo. */
.tw-practica-a-la-medida {
    border-color: var(--tw-tag-orange-fg);
}

.tw-practica-receta__titulo {
    margin-bottom: 6px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--tw-font-light);
}

/* Sin fondo propio: `--tw-bg-secondary` en oscuro es el negro de la página y
   dentro del modal la partida se leía como un hueco. El borde basta. */
.tw-practica-partida {
    padding: 12px;
    border: 1px solid var(--tw-border-medium, var(--tw-border-light));
    border-radius: var(--tw-radius-md, 8px);
}

.tw-practica-partida__cuenta {
    margin-top: 8px;
    font-size: 12px;
    color: var(--tw-font-secondary, inherit);
}

.tw-mesa__pista {
    font-size: 12px;
    color: var(--tw-font-light);
}

/* Ir difuminando según el avance (`practica/mesa.js`, `ponerAvance`): la que
   toca escanear va nítida y grande; las que siguen, las ya hechas y las que no
   son de este paso, difuminadas. Se difumina el contenido y no la etiqueta
   entera, para que la palomita de «lista» se lea. Igual en el globo de la
   guía (`.tw-guia-etiquetas`). */
.tw-mesa .tw-etiqueta-practica {
    cursor: pointer;
}

.tw-etiqueta-practica {
    position: relative;
    transition: transform 0.15s ease;
}

.tw-etiqueta-practica > * {
    transition: filter 0.15s ease, opacity 0.15s ease;
}

.tw-mesa[data-enfoque='si'] .tw-etiqueta-practica:not(.is-enfocada) > *,
.tw-guia-etiquetas[data-enfoque='si'] .tw-etiqueta-practica:not(.is-enfocada) > * {
    filter: blur(4px);
    opacity: 0.4;
}

.tw-etiqueta-practica.is-fuera > * {
    filter: blur(3px);
    opacity: 0.3;
}

.tw-etiqueta-practica.is-leida:not(.is-enfocada) > * {
    filter: blur(3px);
    opacity: 0.35;
}

.tw-etiqueta-practica.is-enfocada {
    outline: 3px solid var(--tw-tag-orange-fg);
    outline-offset: 2px;
}

.tw-etiqueta-practica.is-enfocada .tw-etiqueta-practica__simbolo {
    width: 150px;
}

.tw-etiqueta-practica.is-leida::after {
    content: '✓ lista';
    position: absolute;
    top: 50%;
    right: 10px;
    transform: translateY(-50%);
    padding: 2px 8px;
    border-radius: 6px;
    background: #1f7a3d;
    color: #fff;
    font-size: 12px;
    font-weight: 600;
}

/* Dentro del globo de la guía: la etiqueta que hay que escanear ahora. */
.tw-guia-etiquetas {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 10px;
}

.tw-guia__pendiente {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 8px 0;
    border-top: 1px solid var(--tw-border-light);
}

/* La guía de práctica: la tarjeta de «en qué paso vas», abajo a la
   izquierda (la Zebra de práctica va a la derecha), y el globo de driver.js
   con letra de tablet. */
.tw-guia {
    position: fixed;
    left: 16px;
    bottom: 16px;
    z-index: 1040;
    width: min(380px, calc(100vw - 32px));
    border: 2px solid var(--tw-tag-orange-fg);
    border-radius: var(--tw-radius-md, 8px);
    background: var(--tw-bg-primary, #fff);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
    overflow: hidden;
}

.tw-guia__cabeza {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    background: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
    font-weight: 600;
    font-size: 13px;
}

.tw-guia__cabeza > span {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-guia__plegar {
    border: 0;
    background: transparent;
    color: inherit;
    padding: 0 2px;
}

.tw-guia__cuerpo {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px 12px 12px;
}

.tw-guia[data-plegada='si'] .tw-guia__cuerpo {
    display: none;
}

.tw-guia[data-plegada='si'] .tw-guia__plegar .ti {
    transform: rotate(180deg);
}

/* Se aparta de lo que hay que tocar (`apartarDe` en guia.js): sube arriba —el
   `top` lo pone el guion, debajo de la cabecera— o se pliega a su cabecera. */
.tw-guia[data-lado='arriba'] {
    bottom: auto;
}

.tw-guia[data-apartada='si'] .tw-guia__cuerpo {
    display: none;
}

.tw-guia__paso {
    font-size: 15px;
}

.tw-guia__porque {
    font-size: 13px;
    color: var(--tw-font-secondary);
}

.tw-guia__acciones {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.driver-popover.tw-guia-globo {
    max-width: 380px;
    font-size: 15px;
}

.driver-popover.tw-guia-globo .driver-popover-title {
    font-size: 17px;
}

.driver-popover.tw-guia-globo .driver-popover-description {
    font-size: 15px;
    line-height: 1.45;
}

/* ── El juego de la práctica (2 de octubre de 2026) ───────────────────────

   Salvador: «más gamificado, más fácil de usar, más noble». La tarjeta de la
   guía es una misión: una barra con un tramo por paso, los puntos que lleva, el
   paso en una línea y un solo botón grande que dice qué hacer. Al cumplir un
   paso, confeti y «+100». Al terminar, estrellas. Todo con el naranja de la
   práctica y sin movimiento si el equipo pide reducirlo. */

.tw-mision__nombre {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tw-guia__cabeza > .tw-mision__puntos {
    flex: none;
}

.tw-mision__puntos {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--tw-tag-orange-fg);
    color: var(--tw-tag-orange-bg);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.tw-mision__barra {
    display: flex;
    gap: 4px;
}

.tw-mision__tramo {
    flex: 1;
    height: 8px;
    border-radius: 999px;
    background: var(--tw-border-medium, var(--tw-border-light));
}

.tw-mision__tramo.is-hecho {
    background: var(--tw-tag-green-fg);
}

.tw-mision__tramo.is-actual {
    background: var(--tw-tag-orange-fg);
    animation: tw-mision-late 1.6s ease-in-out infinite;
}

@keyframes tw-mision-late {
    50% {
        opacity: 0.45;
    }
}

.tw-mision__contador {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--tw-font-light);
}

.tw-mision__paso {
    font-size: 17px;
    font-weight: 600;
    line-height: 1.3;
}

.tw-mision__porque {
    font-size: 13px;
    color: var(--tw-font-secondary);
}

.tw-mision__porque summary {
    cursor: pointer;
    color: var(--tw-tag-orange-fg);
    font-weight: 600;
}

/* Lo que salió distinto de lo pedido y no deja avanzar (`PasoGuiado.aviso`). */
.tw-mision__aviso {
    padding: 6px 10px;
    border-radius: var(--tw-radius-sm, 6px);
    background: var(--tw-tag-red-bg);
    color: var(--tw-tag-red-fg);
    font-size: 13px;
    font-weight: 600;
}

.tw-mision__atorado {
    padding: 6px 10px;
    border-radius: var(--tw-radius-sm, 6px);
    background: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
    font-size: 13px;
    font-weight: 600;
}

.tw-mision__principal {
    width: 100%;
    min-height: 44px;
    justify-content: center;
    font-size: 15px;
}

.tw-mision__otras {
    display: flex;
    gap: 6px;
    width: 100%;
}

.tw-mision__otras .btn {
    flex: 1;
}

.tw-guia.is-atorado {
    animation: tw-mision-llama 1.2s ease-in-out 3;
}

@keyframes tw-mision-llama {
    50% {
        box-shadow: 0 0 0 6px color-mix(in srgb, var(--tw-tag-orange-fg) 35%, transparent);
    }
}

/* Lo que la guía ilumina, latiendo: el dedo de «toca aquí». */
.driver-active .driver-active-element {
    animation: tw-guia-pulso 1.6s ease-out infinite;
}

@keyframes tw-guia-pulso {
    0% {
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--tw-tag-orange-fg) 55%, transparent);
    }
    100% {
        box-shadow: 0 0 0 14px transparent;
    }
}

/* La etiqueta que toca, brillando cuando se escaneó otra. */
.tw-etiqueta-practica.is-brilla {
    animation: tw-etiqueta-brilla 0.5s ease-in-out 3;
}

@keyframes tw-etiqueta-brilla {
    50% {
        box-shadow: 0 0 0 6px var(--tw-tag-orange-fg);
    }
}

/* El festejo: confeti y los puntos que suben. */
.tw-confeti {
    position: fixed;
    z-index: 2000000000;
    width: 8px;
    height: 12px;
    border-radius: 2px;
    pointer-events: none;
    animation: tw-confeti 1.2s cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}

@keyframes tw-confeti {
    to {
        transform: translate(var(--dx), calc(var(--dy) + 140px)) rotate(var(--giro));
        opacity: 0;
    }
}

.tw-festejo-puntos {
    position: fixed;
    z-index: 2000000000;
    transform: translate(-50%, -50%);
    padding: 4px 12px;
    border-radius: 999px;
    background: var(--tw-tag-green-fg);
    color: #fff;
    font-size: 18px;
    font-weight: 700;
    pointer-events: none;
    animation: tw-festejo-sube 1.5s ease-out forwards;
}

@keyframes tw-festejo-sube {
    to {
        transform: translate(-50%, -160%);
        opacity: 0;
    }
}

/* Las estrellas. */
.tw-estrellas {
    display: inline-flex;
    gap: 2px;
    line-height: 1;
}

.tw-estrella {
    color: var(--tw-border-medium, #c8c8c8);
}

.tw-estrella.is-llena {
    color: #f59f00;
}

.tw-estrellas--grandes {
    gap: 10px;
    font-size: 54px;
}

.tw-estrellas--grandes .tw-estrella {
    animation: tw-estrella-entra 0.45s cubic-bezier(0.3, 1.6, 0.5, 1) both;
    animation-delay: calc(var(--i) * 0.25s + 0.15s);
}

@keyframes tw-estrella-entra {
    from {
        transform: scale(0.2) rotate(-40deg);
        opacity: 0;
    }
}

/* La pantalla de resultado. */
.tw-resultado {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    text-align: center;
}

.tw-resultado__titulo {
    font-size: 26px;
    font-weight: 700;
}

.tw-resultado__frase {
    max-width: 420px;
    color: var(--tw-font-secondary);
    font-size: 15px;
}

.tw-resultado__datos {
    display: flex;
    gap: 28px;
    justify-content: center;
}

.tw-resultado__datos > div {
    display: flex;
    flex-direction: column;
    align-items: center;
    font-size: 12px;
    color: var(--tw-font-light);
}

.tw-resultado__cifra {
    font-size: 28px;
    font-weight: 700;
    color: var(--tw-font-primary);
    font-variant-numeric: tabular-nums;
}

.tw-resultado__grupo {
    width: 100%;
    text-align: left;
}

.tw-resultado__subtitulo {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
    font-weight: 600;
    cursor: default;
}

summary.tw-resultado__subtitulo {
    cursor: pointer;
}

.tw-resultado__grupo ul {
    margin: 0;
    padding-left: 20px;
}

.tw-resultado__grupo li span {
    display: block;
    color: var(--tw-font-secondary);
    font-size: 13px;
}

.tw-resultado__acciones {
    flex-wrap: wrap;
    justify-content: center;
}

/* Lo que sigue, arriba y a todo lo ancho: es lo que se toca con el pulgar. */
.tw-resultado__acciones .btn-primary {
    order: -1;
    flex-basis: 100%;
}

.tw-resultado__acciones .btn:not(.btn-primary) {
    flex: 1;
}

.tw-insignia {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 14px;
    border: 2px solid #f59f00;
    border-radius: var(--tw-radius-md, 8px);
    text-align: left;
}

.tw-insignia > .ti {
    font-size: 30px;
    color: #f59f00;
}

.tw-insignia__nombre {
    font-weight: 700;
}

.tw-insignia__texto {
    font-size: 13px;
    color: var(--tw-font-secondary);
}

/* El camino de retos. */
.tw-retos__resumen {
    margin-top: 2px;
    font-size: 13px;
    color: var(--tw-font-secondary);
}

.tw-retos {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.tw-reto {
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 14px;
    border: 1px solid var(--tw-border-medium, var(--tw-border-light));
    border-radius: var(--tw-radius-md, 8px);
}

/* El camino: una línea que une los niveles. */
.tw-reto + .tw-reto::before {
    content: '';
    position: absolute;
    top: -11px;
    left: 32px;
    width: 2px;
    height: 10px;
    background: var(--tw-border-medium, var(--tw-border-light));
}

.tw-reto.is-toca {
    border: 2px solid var(--tw-tag-orange-fg);
    box-shadow: 0 6px 18px color-mix(in srgb, var(--tw-tag-orange-fg) 20%, transparent);
}

.tw-reto.is-apagado {
    opacity: 0.55;
}

.tw-reto__nivel {
    display: grid;
    flex: none;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: var(--tw-bg-secondary);
    font-size: 16px;
    font-weight: 700;
}

.tw-reto.is-toca .tw-reto__nivel {
    background: var(--tw-tag-orange-fg);
    color: #fff;
}

.tw-reto.is-ganado .tw-reto__nivel {
    background: var(--tw-tag-green-fg);
    color: #fff;
}

.tw-reto__cuerpo {
    flex: 1;
    min-width: 0;
}

.tw-reto__cabeza {
    display: flex;
    align-items: center;
    gap: 8px;
}

.tw-reto__titulo {
    font-size: 15px;
    font-weight: 600;
}

.tw-reto__toca {
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--tw-tag-orange-bg);
    color: var(--tw-tag-orange-fg);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
}

.tw-reto__texto {
    margin-top: 2px;
    font-size: 13px;
    color: var(--tw-font-secondary);
}

.tw-reto__pie {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin-top: 6px;
    font-size: 12px;
    color: var(--tw-font-light);
}

.tw-reto__pie .tw-estrellas {
    font-size: 16px;
}

.tw-reto__insignia {
    opacity: 0.6;
}

.tw-reto__insignia.is-ganada {
    color: #f59f00;
    font-weight: 600;
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .tw-mision__tramo.is-actual,
    .tw-guia.is-atorado,
    .driver-active .driver-active-element,
    .tw-etiqueta-practica.is-brilla,
    .tw-estrellas--grandes .tw-estrella {
        animation: none;
    }
}
