/* =============================================================================
   Taller Industrial — Responsive (teléfonos y tablets)
   =============================================================================
   Plan: docs/plan_responsive.md — Fases F0 (cimientos) y F1 (shell móvil).

   REGLA DE ORO DE ESTE ARCHIVO
   ---------------------------
   El ESCRITORIO NO SE TOCA. Todo lo que cambia geometría vive dentro de un
   `@media`. Fuera de media queries solo hay:
     · tokens NUEVOS (nadie los consumía antes), y
     · clases NUEVAS (.tabbar, .drawer-backdrop, .sheet-bar, .topbar__drawer…)
       que hoy solo existen en el markup móvil y arrancan ocultas.
   Consecuencia: a >= 1024px con mouse, este archivo es un no-op.

   BREAKPOINTS (única fuente; las media queries no aceptan var(), por eso van
   como literales y se documentan acá — ver también tokens.css):
     · teléfono   <= 639px
     · tablet     640px – 1023px
     · escritorio >= 1024px   (idéntico a hoy)
     · «móvil»    <= 1023px   (teléfono + tablet: drawer + barra inferior)
   El corte del sidebar pasó de 900 a 1024 (decisión D2): iPad vertical (810px)
   es móvil; iPad horizontal (1080px) y notebooks son escritorio.

   EJE INDEPENDIENTE — PUNTERO (decisión D6):
     `@media (pointer: coarse)` sube los objetivos táctiles a 44px y los inputs a
     16px en CUALQUIER pantalla táctil (también el iPad horizontal, que por ancho
     es «escritorio»), y NO aplica en escritorio con mouse: la densidad
     «compacta» que se elige en la app queda intacta en la demo.

   ANTI-CDN (CLAUDE.md): CSS propio, tokens propios. Cero librerías externas.
   Colores: solo var(--color-*) → las 4 paletas y claro/oscuro siguen intactos.
============================================================================= */

/* ---------------------------------------------------------------------------
   1. Tokens nuevos (neutros: no los consumía nada antes de F1)
--------------------------------------------------------------------------- */
:root {
    --tap-min: 44px;              /* objetivo táctil mínimo */
    --tabbar-h: 56px;             /* alto de la barra inferior (sin el safe-area) */
    --sheet-radius: 12px;
    /* Muescas/recortes de pantalla (requiere viewport-fit=cover en base.html). */
    --safe-b: env(safe-area-inset-bottom, 0px);
    --safe-t: env(safe-area-inset-top, 0px);
    /* Espacio que hay que dejar libre abajo para no quedar tapado por la barra. */
    --hueco-inferior: calc(var(--tabbar-h) + var(--safe-b));
}

/* ---------------------------------------------------------------------------
   2. Componentes móviles: ocultos por defecto (o sea, en escritorio)
--------------------------------------------------------------------------- */
.tabbar { display: none; }
.drawer-backdrop { display: none; }
.sheet-bar { display: none; }
.topbar__drawer { display: none; }
.drawer-foot { display: none; }
/* El <details> que pliega los filtros y la fila de pie del banner de ayuda los
   CREA app.js, y solo en teléfono: en escritorio no existen. Se declaran igual
   por si el ancho cambia (rotar la tablet, agrandar la ventana) con el elemento
   ya en el DOM y antes de que corra el desarmado. */
.filtros-movil > summary,
.ayuda-banner__pie { display: none; }

/* Contenedor único de tablas (plan §3.3). Los cuatro modos viven en la sección 5
   y SOLO dentro de media queries: en escritorio `.tbl` es un div neutro.
     · .tbl--cards  cada fila se vuelve una tarjeta «etiqueta: valor»
     · .tbl--scroll la tabla sigue siendo tabla, scrollea con la 1ª columna fija
     · .tbl--kv     tabla de 2 columnas que deja de forzar el ancho y envuelve
     · .tbl--form   formsets: cada fila del formulario, apilada con sus labels
   El etiquetador que copia <th> -> data-label (decisión D5) está en app.js. */
.tbl { max-width: 100%; }

/* `.tbl--scroll` ARRASTRA SIEMPRE, a cualquier ancho — no solo en móvil.
   Esta regla vivía adentro del `@media (max-width: 1023px)` de F5, y eso dejaba
   un agujero en TODO el rango que va de 1024px al ancho real de un escritorio:
   ahí la tabla no scrolleaba y, como su tarjeta es `overflow: hidden`
   (base.css, para recortar las esquinas redondeadas), lo que sobraba no se
   escondía «un poco»: se RECORTABA, sin ninguna forma de llegar. Medido en iPad
   acostado (1080px): `/usuarios/admin/` dejaba **11 controles inalcanzables**,
   con la tabla desbordando 110px su contenedor. Le pasa igual a una notebook
   angosta o a una ventana a medio maximizar: que una tabla recorte en vez de
   arrastrarse es un defecto a cualquier ancho.
   Sacarla del @media es seguro POR CONSTRUCCIÓN: `overflow-x: auto` no dibuja
   nada mientras el contenido entre, así que a 1440px no cambia un píxel (0
   diferencias en la línea base de 116 pantallas). De las 73 tablas `--scroll`
   del sistema, 50 ya traían `overflow-x:auto` INLINE —o sea que ya se
   arrastraban siempre— y el cambio alcanza a las otras 23.
   La primera columna fija y la sombra del borde NO se sacan de acá: se extienden
   a `pointer: coarse` (sección 5.1), porque pegar una columna con su sombra sí
   se ve aunque no haya desborde, y el escritorio con mouse no se toca. */
.tbl--scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
}

/* Texto que SOLO se ve en el teléfono. Sirve para lo que hoy vive escondido en
   un `title=` (413 en el repo) y que en una pantalla táctil no existe, porque no
   hay hover: «Bloqueada: completá primero X», el motivo de un rechazo de QC, la
   pista de un ícono. En escritorio no se pinta y el `title` sigue haciendo su
   trabajo — o sea que esta clase, a >= 640px, es un no-op. */
.solo-movil { display: none; }

/* «Ver más» de los banners de ayuda y el ▾ del acordeón del detalle de OT: los
   inyecta app.js solo en teléfono, pero se declaran ocultos acá por si el ancho
   cambia (rotar la tablet, agrandar la ventana) con el elemento ya en el DOM.
   Sin esto, volver a escritorio dejaba un ▾ suelto en cada encabezado. */
.ayuda-banner__mas,
.pleg-chevron { display: none; }

/* Reordenar con ▲▼ (F6, decisión D10). El grupo lo inyecta app.js solamente
   cuando el puntero es grueso, así que en escritorio con mouse no existe: no
   hace falta esconderlo, alcanza con darle forma para cuando aparece. */
.st-mover {
    display: inline-flex;
    gap: 4px;
}
.st-mover__btn {
    padding: 0 var(--space-2);
    line-height: 1;
}
/* En la tablet la fila sigue siendo una fila de tabla y los ▲▼ viven en la
   columna angosta del «⠿»: apilados, la columna no se ensancha. En el teléfono
   el grupo viaja al pie de acciones y ahí van uno al lado del otro. */
td.st-handle .st-mover {
    flex-direction: column;
    gap: 2px;
}

/* ---------------------------------------------------------------------------
   3. MÓVIL (<= 1023px): shell de una columna, drawer y barra inferior
--------------------------------------------------------------------------- */
@media (max-width: 1023px) {

    /* --- 3.1 Shell a una columna ---
       `minmax(0, 1fr)` en vez de `1fr`: sin eso, una tabla ancha adentro de
       .app-main estira la pista del grid, el documento se hace más ancho que la
       pantalla y el navegador responde alejando la página (hallazgo H2). */
    .app-shell,
    .app-shell[data-sidebar="collapsed"] {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas:
            "topbar"
            "main";
    }

    /* .app-main contiene el desborde en vez de propagarlo al documento. En F2 en
       adelante cada tabla se hace cargo de lo suyo (.tbl--scroll / --cards). */
    .app-main {
        min-width: 0;
        overflow-x: auto;
        padding: var(--space-4);
        /* Que la barra inferior no tape el final del contenido. */
        padding-bottom: calc(var(--space-6) + var(--hueco-inferior));
    }

    /* --- 3.2 Sidebar = drawer ---
       No se duplica markup: es el MISMO partials/sidebar.html, sacado del flujo
       y deslizado desde la izquierda. Arranca invisible para lectores de
       pantalla y para el Tab (visibility:hidden), no solo transparente. */
    .app-sidebar {
        display: block;
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        width: min(86vw, var(--sidebar-width));
        z-index: 1036;
        transform: translateX(-100%);
        visibility: hidden;
        box-shadow: var(--shadow-lg);
        padding-bottom: calc(var(--space-6) + var(--safe-b));
        transition: transform var(--transition-base), visibility var(--transition-base);
    }
    .app-sidebar--open {
        transform: translateX(0);
        visibility: visible;
    }
    .drawer-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 1035;
        /* `cursor:pointer` NO es cosmético acá: Safari iOS solo sintetiza el
           evento `click` al tocar un elemento que considere «clickeable», y un
           <div> pelado no lo es aunque tenga un listener puesto por JS. Sin
           esto, tocar el fondo NO cerraba el drawer en iPhone (verificado con
           WebKit: el tap no generaba ningún click). app.js además escucha
           `touchend` como refuerzo. */
        cursor: pointer;
        background: rgba(0, 0, 0, .45);
        opacity: 0;
        visibility: hidden;
        transition: opacity var(--transition-base), visibility var(--transition-base);
    }
    .drawer-backdrop.is-open { opacity: 1; visibility: visible; }

    /* Con el drawer abierto, el fondo no scrollea (mismo criterio que
       html.has-modal en base.css). La clase la pone app.js. */
    html.has-drawer,
    html.has-drawer body { overflow: hidden; }
    html.has-drawer .app-main { overflow: hidden; }

    /* En drawer NO existe el modo «rail colapsado» (64px): se ignora la cookie
       ti_sidebar (data-sidebar) y el menú se ve siempre completo. */
    .app-shell[data-sidebar="collapsed"] .app-sidebar { overflow-x: auto; }
    .app-shell[data-sidebar="collapsed"] .brand__texts,
    .app-shell[data-sidebar="collapsed"] .nav-section { display: block; }
    .app-shell[data-sidebar="collapsed"] .nav-item__label { display: block; flex: 1; min-width: 0; }
    .app-shell[data-sidebar="collapsed"] .brand__caret { display: none; }
    .app-shell[data-sidebar="collapsed"] .brand {
        padding: var(--space-2) var(--space-5) var(--space-3);
        justify-content: flex-start;
    }
    .app-shell[data-sidebar="collapsed"] .nav-item {
        justify-content: flex-start;
        padding: var(--space-1) var(--space-5);
        gap: var(--space-3);
    }
    .app-shell[data-sidebar="collapsed"] .nav-item__icon {
        width: 18px;
        height: 18px;
        font-size: inherit;
    }
    /* El botón «Colapsar / expandir» del sidebar no hace nada útil en drawer:
       se convierte en cabecera del panel (no clickeable a la vista). */
    .app-sidebar .brand { cursor: default; }

    /* Pie del drawer: usuario, tema, preferencias, Mi cuenta y Salir. Son los
       controles que se sacan del topbar para matar los ~460px fijos (H2). */
    .drawer-foot {
        display: block;
        margin-top: var(--space-4);
        padding: var(--space-3) var(--space-4) var(--space-2);
        border-top: 1px solid var(--color-border);
    }
    .drawer-foot__user {
        display: flex;
        align-items: center;
        gap: var(--space-3);
        padding: var(--space-2) 0 var(--space-3);
        color: var(--color-sidebar-fg-active);
    }
    .drawer-foot__acciones {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: var(--space-2);
    }
    .drawer-foot__acciones .btn { width: 100%; }

    /* --- 3.3 Topbar compacto --- */
    .app-topbar {
        padding: 0 var(--space-3);
        gap: var(--space-2);
        min-width: 0;
        overflow: hidden;
    }
    .topbar__title {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .topbar__actions {
        gap: var(--space-1);
        flex-shrink: 0;
    }
    .topbar__drawer { display: inline-flex; flex-shrink: 0; }
    /* El cargo debajo del nombre no aporta en pantalla chica y suma ancho. */
    .topbar__cargo { display: none; }

    /* --- 3.4 Barra inferior por rol (decisión D1) ---
       Muestra los 4-5 destinos del rol en vez de esconderlos detrás de un
       hamburguesa; «Más» abre el drawer con el menú completo. */
    .tabbar {
        display: flex;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        /* Por debajo de los modales (ModalStack arranca en 1000) y del botón
           «↑ ir arriba» (990); por encima del contenido. */
        z-index: 985;
        height: calc(var(--tabbar-h) + var(--safe-b));
        padding-bottom: var(--safe-b);
        background: var(--color-surface);
        border-top: 1px solid var(--color-border);
        box-shadow: 0 -2px 10px rgba(0, 0, 0, .10);
    }
    .tabbar__item {
        flex: 1 1 0;
        min-width: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        min-height: var(--tap-min);
        padding: 4px 2px;
        border: 0;
        background: transparent;
        color: var(--color-fg-muted);
        font-family: inherit;
        font-size: .68rem;
        line-height: 1.1;
        text-decoration: none;
        cursor: pointer;
        touch-action: manipulation;
        -webkit-tap-highlight-color: transparent;
    }
    .tabbar__item:hover { text-decoration: none; color: var(--color-fg); }
    .tabbar__item--active { color: var(--color-primary); font-weight: var(--font-weight-semibold); }
    .tabbar__icon { font-size: 1.15rem; line-height: 1; }
    .tabbar__label {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    /* Puntito de «tenés notificaciones» sobre el ícono de la pestaña Notifs. */
    .tabbar__item .icon-btn__badge { top: 0; right: 22%; }
    /* Con un modal abierto manda el modal (mismo criterio que .scroll-top). */
    html.has-modal .tabbar { display: none; }

    /* --- 3.5 Flotantes por encima de la barra inferior --- */
    .scroll-top {
        right: var(--space-3);
        bottom: calc(var(--hueco-inferior) + var(--space-3));
    }
    #asistente-root .asis-fab {
        right: var(--space-3);
        bottom: calc(var(--hueco-inferior) + var(--space-3) + 52px);
    }
    #asistente-root .asis-panel {
        bottom: calc(var(--hueco-inferior) + var(--space-2));
    }
    /* Banderín «DESARROLLO» de base.html (solo con DEBUG=True): su <style> vive
       en el <body>, o sea DESPUÉS de este archivo → hace falta el id para ganarle. */
    #dev-toast { bottom: calc(var(--hueco-inferior) + var(--space-3)); }

    /* --- 3.6 (RETIRADO EN F5) la red `.card { overflow-x: auto }` ---
       F1 y F2 dejaron acá una red mínima: `.card` es `overflow:hidden`
       (base.css:296, para recortar las esquinas redondeadas), así que una tabla
       ancha adentro no se «escondía un poco», se RECORTABA. Medido en /notifs/
       en iPhone: «Ver» y «Marcar leída» quedaban en x=467..611 sobre 390px de
       pantalla, sin ninguna forma de llegar a ellos. La red los volvía
       alcanzables arrastrando la tarjeta entera.
       Se retira acá porque su propio comentario decía que salía cuando F5
       cerrara, y F5 cerró: las 92 tablas del sistema están hoy adentro de un
       `.tbl` (tarjetas en el piso, `--scroll` en las de escritorio, `--kv` en
       los detalles, `--form` en los formsets), o sea que cada tabla se hace
       cargo de su propio ancho. Dejarla puesta tenía un costo real: la tarjeta
       ENTERA se arrastraba (título, filtros y paginación incluidos) en vez de
       moverse solo la tabla, y una tarjeta que scrollea de más se lleva puesto
       el gesto de scroll vertical de la página.
       Si alguna vez vuelve a aparecer contenido recortado dentro de una
       tarjeta, la respuesta NO es reponer esta regla: es envolver ese
       contenido en su propio `.tbl--scroll`. */

    /* --- 3.7 Grillas de 2 columnas que no entran (inicio) --- */
    /* El inline `grid-template-columns:1fr 1fr` de core/home.html le gana a
       cualquier clase; por eso el !important, encerrado en la media query. */
    .u-grid-2 { grid-template-columns: 1fr !important; }
}

/* ---------------------------------------------------------------------------
   4. TELÉFONO (<= 639px)
--------------------------------------------------------------------------- */
@media (max-width: 639px) {

    /* --- 4.1 Densidad: en el teléfono se ignora «compacta» (decisión D6) --- */
    [data-density="compacta"] {
        --density-row-h: 48px;
        --density-input-h: var(--tap-min);
        --density-button-h: var(--tap-min);
        --density-padding-y: var(--space-3);
        --density-padding-x: var(--space-4);
        --font-size-md: 1rem;
    }

    /* Nada de texto microscópico: los chips/badges suben a 0.8rem. */
    .badge { font-size: max(var(--font-size-xs), .8rem); }

    /* --- 4.2 Topbar mínimo: ☰ + título + campanita ---
       CAUSA RAÍZ de H2: el bloque de usuario + tema + 3 links de topbar.html
       medían ~460px FIJOS y hacían que el navegador cargara todo con zoom-out.
       Se van al pie del drawer (.drawer-foot).
       El !important es porque .topbar__user trae `display:flex` INLINE en el
       template; se deja el inline como está para no tocar el escritorio. */
    .topbar__user,
    .topbar__link,
    .topbar__theme { display: none !important; }
    .app-main { padding: var(--space-3); padding-bottom: calc(var(--space-6) + var(--hueco-inferior)); }

    /* --- 4.3 Desplegable de notificaciones y popover de tema: a lo ancho --- */
    #notif-dropdown,
    .theme-popover {
        position: fixed !important;
        left: var(--space-2) !important;
        right: var(--space-2) !important;
        width: auto !important;
        top: calc(var(--topbar-height) + var(--space-1)) !important;
        max-height: calc(100dvh - var(--topbar-height) - var(--hueco-inferior) - var(--space-6));
        overflow-y: auto;
        overscroll-behavior: contain;
    }
    .theme-options { grid-template-columns: repeat(3, 1fr); }

    /* --- 4.4 Toasts a lo ancho, debajo del topbar --- */
    #toast-container {
        left: var(--space-3) !important;
        right: var(--space-3) !important;
        max-width: none !important;
        top: calc(var(--topbar-height) + var(--space-2)) !important;
    }

    /* --- 4.5 Modales = hojas a pantalla completa ---
       Los ~88 modales fijan `width` INLINE (400-1100px), así que acá el
       !important es inevitable. Queda encerrado en esta media query y la lista
       de selectores es cerrada: el backdrop y su tarjeta. */
    .modal-backdrop:not(.modal-libre) {
        align-items: stretch !important;
        justify-content: stretch !important;
        padding: 0 !important;
    }
    /* El :not(script):not(style):not(template) es una guarda: `display:flex`
       sobre un <script> lo haría VISIBLE (imprimiría el código en pantalla).
       Hoy ningún modal tiene un script como hijo directo, pero el selector es
       global sobre 88 modales y el día que alguien meta uno, no explota. */
    .modal-backdrop:not(.modal-libre) > *:not(script):not(style):not(template) {
        width: 100% !important;
        max-width: none !important;
        height: 100dvh !important;
        max-height: none !important;
        margin: 0 !important;
        border-radius: 0 !important;
        border-width: 0 !important;
        display: flex !important;
        flex-direction: column !important;
    }
    /* El cuerpo scrollea; el encabezado queda fijo arriba. */
    .modal-backdrop:not(.modal-libre) > * > .card__header { flex-shrink: 0; position: sticky; top: 0; z-index: 2; }
    .modal-backdrop:not(.modal-libre) > * > .card__body,
    .modal-backdrop:not(.modal-libre) .card__body {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        overscroll-behavior: contain;
    }
    /* «‹ Volver»: en pantalla completa no hay fondo para tocar ni tecla Escape.
       La inyecta app.js como PRIMER hijo de la hoja (solo en teléfono). */
    .sheet-bar {
        display: flex;
        align-items: center;
        flex-shrink: 0;
        gap: var(--space-2);
        padding: var(--space-2) var(--space-3);
        background: var(--color-surface-2);
        border-bottom: 1px solid var(--color-border);
    }
    .sheet-bar__back {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        min-height: var(--tap-min);
        padding: 0 var(--space-3);
        border: 1px solid var(--color-border);
        border-radius: var(--radius-md);
        background: var(--color-surface);
        color: var(--color-fg);
        font-family: inherit;
        font-size: var(--font-size-sm);
        cursor: pointer;
        touch-action: manipulation;
    }

    /* --- 4.6 Asistente: panel a pantalla (asistente.css corta en 480px) --- */
    #asistente-root .asis-panel {
        left: var(--space-2);
        right: var(--space-2);
        width: auto;
        max-width: none;
        bottom: calc(var(--hueco-inferior) + var(--space-2));
        height: calc(100dvh - var(--topbar-height) - var(--hueco-inferior) - var(--space-6));
        max-height: none;
    }

    /* --- 4.7 Login (no usa el shell) --- */
    .login-page { padding: var(--space-4); align-items: flex-start; padding-top: var(--space-8); }
    .login-card { max-width: none; }

    /* --- 4.8 Barras de filtros: en columna, cada control a lo ancho ---
       Marcadas con .filters en el template (F2 en adelante las va poniendo). */
    .filters {
        flex-direction: column !important;
        align-items: stretch !important;
        flex-wrap: wrap !important;
    }
    .filters > * { width: 100% !important; min-width: 0 !important; }
    /* Los desplegables de opciones múltiples («Estado» en /ot/) traen
       `min-width:210px` y su panel flotante `min-width:100%`. */
    .filters .ms,
    .filters .form-input,
    .filters select,
    .filters input { width: 100% !important; min-width: 0 !important; }
    .filters .ms-panel { max-height: 50vh; }
    /* Los pares de botones «Filtrar / Limpiar» viven en un div propio: que
       queden lado a lado y no uno arriba del otro. */
    .filters > div > .btn { flex: 1 1 0; justify-content: center; }

    /* --- 4.9 Grillas de formulario de 2 columnas ---
       Variante de .u-grid-2 que colapsa SOLO en el teléfono: en una tablet de
       810px dos columnas de campos entran bien y se pierde menos scroll. */
    .u-grid-2-sm { grid-template-columns: 1fr !important; }

    /* --- 4.10 MENOS CROMO ANTES DE LA PRIMERA FILA ---
       Medido en iPhone 13 antes de esto: para ver la primera subtarea de «Para
       tomar» había que bajar 843px (1,53 pantallas); en «Tratamientos», 1.113px
       (2,02). El trabajo estaba SIEMPRE debajo del pliegue. Se ataca por cuatro
       lados, todos acotados al teléfono. */

    /* (a) Los filtros se pliegan. El <details> lo arma app.js —acá solo va su
       aspecto— y arranca abierto si hay algún filtro puesto, para que nadie se
       quede mirando una lista corta sin saber por qué. */
    .filtros-movil { margin-bottom: var(--space-3); }
    .filtros-movil > summary {
        display: flex;
        align-items: center;
        gap: var(--space-2);
        min-height: var(--tap-min);
        padding: 0 var(--space-3);
        border: 1px solid var(--color-border);
        border-radius: var(--radius-md);
        background: var(--color-surface);
        color: var(--color-fg);
        font-size: var(--font-size-sm);
        font-weight: var(--font-weight-semibold);
        cursor: pointer;
        touch-action: manipulation;
        /* Los dos son para lo mismo: sacar el triangulito nativo, que en cada
           navegador se dibuja distinto y desalinea el texto. El ▾ propio, con
           `::after`, gira al abrir y queda del lado del pulgar. */
        list-style: none;
    }
    .filtros-movil > summary::-webkit-details-marker { display: none; }
    .filtros-movil > summary::after {
        content: "▾";
        margin-left: auto;
        color: var(--color-fg-muted);
        transition: transform var(--transition-base);
    }
    .filtros-movil[open] > summary::after { transform: rotate(180deg); }
    .filtros-movil[open] > summary { margin-bottom: var(--space-2); }
    .filtros-movil__n { color: var(--color-primary); }
    /* La tarjeta ya está adentro del <details>, que aporta el margen de abajo. */
    .filtros-movil > .card,
    .filtros-movil > form.card { margin-bottom: 0 !important; }

    /* (b) La bajada explicativa del encabezado. En «Para tomar» son 4 renglones
       que dicen EXACTAMENTE lo mismo que el banner de ayuda que está tres
       centímetros más abajo («es una recomendación, no un candado…»), y el
       tutorial de la pantalla lo repite entero. En una pantalla de 390px eso es
       leer tres veces lo mismo antes de ver una sola subtarea. En escritorio se
       queda donde está: allá entra al lado del título y no cuesta nada. */
    .pantalla-bajada { display: none !important; }

    /* (c) Un h1 de 1,5rem en 390px ocupa más de lo que informa (el título de la
       pantalla ya está en el topbar, que desde este arreglo dice la verdad). */
    .app-main h1 { font-size: var(--font-size-xl); }

    /* (d) Pie del banner de ayuda: «Ver más ▾» y «✕ Entendido» comparten un
       renglón de 44px en vez de ocupar uno cada uno. La fila la arma app.js
       moviendo los dos controles que ya existían; no se agrega ningún control
       nuevo ni se saca ninguno. */
    .ayuda-banner__pie {
        display: flex;
        align-items: center;
        gap: var(--space-3);
        padding: 0 var(--space-4) var(--space-3);
    }
    .ayuda-banner__pie .ayuda-banner__mas { margin-top: 0; flex: 0 0 auto; }
    .ayuda-banner__pie .ayuda-banner__cerrar {
        margin-left: auto;
        padding: 0;
        flex: 0 0 auto;
    }
    .ayuda-banner__pie .ayuda-banner__cerrar .btn { width: auto; }
}

/* ---------------------------------------------------------------------------
   5. TABLAS — el patrón único (plan §3.3). Fases F2 y F3.
   ---------------------------------------------------------------------------
   Las 92 tablas del sistema están pensadas para 1440px. En un teléfono hay dos
   respuestas posibles y NO son intercambiables:

     · Si la fila tiene una ACCIÓN (Tomar, Terminar, Aprobar, Crear remito), la
       tabla se vuelve TARJETAS: nadie va a scrollear 700px a la derecha con una
       mano para encontrar el botón. `.tbl--cards`.
     · Si la tabla es de CONSULTA (histórico, reporte, auditoría), se mantiene
       como tabla y scrollea con la primera columna fija, que es la que dice de
       qué fila estamos hablando. `.tbl--scroll`.

   Las etiquetas de las tarjetas NO se escriben celda por celda (decisión D5):
   app.js copia el texto del <th> al atributo `data-label` de cada <td>, así un
   template se convierte cambiando UNA línea (la clase del contenedor). En el
   <th> se pueden marcar además:
     · data-prio="2"  columna que se esconde en tablet y teléfono
     · data-prio="3"  columna que se esconde solo en teléfono
     · data-tit       la celda que hace de TÍTULO de la tarjeta (sin etiqueta)
   y app.js los propaga a los <td> de esa misma columna, contando colspan.
--------------------------------------------------------------------------- */

/* --- 5.1 Tablet y teléfono (<= 1023px) --- */
@media (max-width: 1023px) {

    /* Prioridad 2: la columna se esconde a partir de la tablet. */
    .tbl [data-prio="2"] { display: none !important; }
}

/* Modo scroll contenido (F5): las ~55 pantallas de escritorio (reportes,
   costeo, auditoría, matriz de permisos, ABMs) NO se convierten a tarjetas —son
   de consulta, no de acción, y una fila de 11 columnas apilada en 11 renglones
   se lee peor que la tabla—, así que se arrastran dentro de su caja con la
   primera columna quieta, para no perder la referencia de qué fila se mira.
   El `overflow-x` vive ARRIBA, fuera de todo @media (ver la sección 2). Acá
   queda lo que sí se ve aunque no haya desborde —la columna pegada y su
   sombra—, y por eso va condicionado.
   La condición es un OR de dos ejes: por ANCHO (teléfono y tablet vertical) o
   por PUNTERO (cualquier pantalla táctil, a cualquier ancho). El segundo es el
   que cubre el iPad ACOSTADO, que mide 1080px y por ancho cuenta como
   «escritorio» aunque se opere con el dedo. En un escritorio con mouse ninguna
   de las dos aplica y no cambia nada. */
@media (max-width: 1023px), (pointer: coarse) {

    /* El camino completo `> table > thead > tr >` NO es cosmético: hay tablas
       ADENTRO de tablas (el «Ver diff» del registro de auditoría, dentro de un
       <details> en una celda). Con el selector suelto `.tbl--scroll table
       td:first-child`, las celdas de la tabla interna también se pegaban al
       borde izquierdo del contenedor de AFUERA: el diff se leía encimado sobre
       la primera columna. Cada tabla se pega a su propio contenedor y a
       ninguno más. */
    .tbl--scroll > table > thead > tr > th:first-child,
    .tbl--scroll > table > tbody > tr > th:first-child,
    .tbl--scroll > table > tbody > tr > td:first-child,
    .tbl--scroll > table > tfoot > tr > td:first-child {
        position: sticky;
        left: 0;
        z-index: 1;
        background: var(--color-surface);
        /* La sombra es la señal de «hay más tabla debajo de esta columna»: sin
           ella el texto scrollea POR DEBAJO y parece un error de pintado. */
        box-shadow: 2px 0 4px -2px rgba(0, 0, 0, .18);
    }
    .tbl--scroll > table > thead > tr > th:first-child {
        background: var(--color-surface-2);
        z-index: 2;
    }
}

/* --- 5.2 Teléfono (<= 639px) --- */
@media (max-width: 639px) {

    /* Prioridad 3: columnas de contexto que en el teléfono sobran. */
    .tbl [data-prio="3"] { display: none !important; }

    /* ---- Modo tarjetas (P-A) y formsets apilados (P-F) ----
       Los `!important` de acá son inevitables y están acotados: las tablas
       traen `width`, `white-space:nowrap`, `text-align` y `height` INLINE
       (3.581 `style=` en el repo) y ninguna clase le gana a un inline. Todo
       vive dentro de esta media query, así que a >= 640px no existe. */
    .tbl--cards,
    .tbl--form {
        /* Los contenedores suelen ser `.card__body` con `padding:0` inline. */
        padding: var(--space-3) !important;
        overflow-x: visible !important;
        --tbl-etiqueta-w: 5.625rem;
    }
    .tbl--cards table,
    .tbl--form table {
        display: block;
        width: 100% !important;
        min-width: 0 !important;
    }
    .tbl--cards thead,
    .tbl--form thead { display: none; }
    .tbl--cards tbody,
    .tbl--form tbody { display: block; }

    .tbl--cards tbody > tr,
    .tbl--form tbody > tr {
        display: block;
        margin: 0 0 var(--space-3);
        padding: var(--space-3);
        border: 1px solid var(--color-border);
        border-radius: var(--radius-md);
        background: var(--color-surface);
    }
    .tbl--cards tbody > tr:last-child,
    .tbl--form tbody > tr:last-child { margin-bottom: 0; }
    /* El `:hover` de las filas (base.css) en touch se queda pegado después de
       tocar: la tarjeta quedaría de otro color hasta tocar en otro lado. */
    .tbl--cards tbody > tr:hover,
    .tbl--form tbody > tr:hover { background: var(--color-surface); }

    /* La celda pasa a bloque, no a flex: adentro hay de todo (badges, <form>,
       <div>, varios nodos de texto sueltos) y en un contenedor flex cada nodo
       suelto se vuelve un ítem y el contenido se desparrama. Con `block` la
       etiqueta es un inline-block y el valor fluye al lado, como se lee. */
    .tbl--cards tbody > tr > td,
    .tbl--form tbody > tr > td {
        display: block;
        width: auto !important;
        max-width: none !important;
        min-width: 0 !important;
        height: auto !important;
        padding: 4px 0 !important;
        border: 0 !important;
        text-align: left !important;
        white-space: normal !important;
    }
    .tbl--cards tbody > tr > td::before,
    .tbl--form tbody > tr > td::before {
        content: attr(data-label);
        display: inline-block;
        /* Ancho de la columna de etiquetas. Es una VARIABLE porque el arreglo de
           las líneas que envuelven (más abajo) tiene que usar exactamente el
           mismo número: si se tocan por separado, el valor y su segunda línea
           quedan desalineados por unos píxeles y se nota.
           5.625rem = los 7.5em de antes a --font-size-xs (0.75rem). Como todo el
           proyecto es `box-sizing: border-box`, el `padding-right` va ADENTRO de
           esos 90px: el ancho total de la etiqueta es 5.625rem, no 5.625+0.5. */
        min-width: var(--tbl-etiqueta-w);
        padding-right: var(--space-2);
        color: var(--color-text-muted);
        font-size: var(--font-size-xs);
        font-weight: var(--font-weight-semibold);
        vertical-align: top;
    }
    .tbl--cards tbody > tr > td:not([data-label])::before,
    .tbl--cards tbody > tr > td[data-label=""]::before,
    .tbl--cards tbody > tr > td[colspan]::before,
    .tbl--form tbody > tr > td:not([data-label])::before,
    .tbl--form tbody > tr > td[data-label=""]::before { content: none; }

    /* VALOR LARGO: la segunda línea alinea con la columna del valor, no con el
       margen izquierdo de la tarjeta.
       Era el único defecto que se VEÍA roto en el recorrido (captura
       v06_despacho_s1.png): «Descripción | [PILOTO] Rack de tuberías piso 2 —»
       y abajo, pegado al borde, «tramo A», como si «tramo A» fuera otro campo.
       Pasa porque la etiqueta es un `::before` inline-block y el texto que sigue
       FLUYE: al envolver, la línea nueva arranca donde arranca el bloque.
       El arreglo es el par clásico `padding-left` + `text-indent` negativo: el
       bloque entero se corre a la derecha el ancho de la etiqueta y solo la
       PRIMERA línea se vuelve a correr a la izquierda, que es justo donde se
       dibuja el `::before`. Resultado: etiqueta a la izquierda, valor en su
       columna, y todo lo que envuelve queda debajo del valor.
       6.125rem = 5.625rem (el `min-width:7.5em` del ::before, que corre a
       --font-size-xs = 0.75rem) + 0.5rem (su `padding-right`, --space-2).
       Se excluyen las celdas que NO llevan etiqueta a la izquierda: el título,
       el pie de acciones, el «#» de orden, los pares de media línea (`tbl-par`,
       con la etiqueta angosta) y los formsets (ahí el `::before` es `display:
       block`, o sea que el valor ya arranca en su propio renglón). */
    .tbl--cards tbody > tr > td[data-label]:not([data-label=""]):not(.tbl-tit):not(.tbl-actions):not(.tbl-par):not(.st-pos) {
        padding-left: var(--tbl-etiqueta-w) !important;
        text-indent: calc(-1 * var(--tbl-etiqueta-w));
    }
    /* `text-indent` SE HEREDA, y lo obedece cualquier contenedor de bloque —un
       `inline-block` lo es—. Sin estos dos resets:
         · la ETIQUETA misma se corría otros 98px a la izquierda y se salía de la
           tarjeta: quedaban los valores en su columna y ninguna etiqueta a la
           vista (verificado en iPhone antes de agregarlo);
         · un <div> adentro de la celda (el panel de «↻ Intentos») se iba para
           el mismo lado. */
    .tbl--cards tbody > tr > td[data-label]:not([data-label=""])::before { text-indent: 0; }
    .tbl--cards tbody > tr > td[data-label]:not([data-label=""]) > * { text-indent: 0; }

    /* Celda sin nada adentro: con la etiqueta puesta sería una línea que dice
       «Equipo:» y nada más. La marca app.js (`tbl-vacia`), que desde F6 cuenta
       también como «vacía» a la celda cuya única letra es la rayita de «sin
       dato» (— – -). En una tabla de escritorio esa rayita mantiene la columna
       alineada y hay que dibujarla; en una tarjeta, «Asignado a: —» y
       «Depende de: —» son dos renglones que no dicen nada y que, medidos en el
       detalle de OT, eran el 25% del alto de la tarjeta de subtarea. */
    .tbl--cards tbody > tr > td.tbl-vacia,
    .tbl--form tbody > tr > td.tbl-vacia { display: none !important; }

    /* Pares que se leen de un vistazo y no merecen un renglón cada uno («T. est»
       y «T. real», «Estimado» y «Real»…). Se marcan con `data-par` en el <th> y
       app.js lo propaga a los <td> de esa columna. Dos por línea; si por
       prioridad de columna se esconde uno, el otro se queda con la línea. */
    .tbl--cards tbody > tr > td.tbl-par {
        display: inline-block !important;
        width: 49% !important;
        vertical-align: top;
    }
    .tbl--cards tbody > tr > td.tbl-par::before { min-width: 0; }

    /* Celdas que son una tira de chips separados por <br> (el «Estado» de cada
       subtarea: estado + chip de plan + «predecesora reabierta» + «rechazada
       por QC»). En una tabla de 11 columnas apilarlos es lo único que entra; en
       una tarjeta que ocupa el ancho de la pantalla, cada <br> es un renglón
       regalado. Se marca con `data-flow` en el <th> y los chips fluyen y
       envuelven solos cuando de verdad no entran. */
    .tbl--cards tbody > tr > td.tbl-flow br { display: none; }
    .tbl--cards tbody > tr > td.tbl-flow .badge { margin-top: 0 !important; }
    .tbl--cards tbody > tr > td.tbl-flow > .badge + .badge { margin-left: 4px; }

    /* Fila de estado vacío («No hay resultados»): ocupa la tarjeta entera. */
    .tbl--cards tbody > tr > td[colspan] { text-align: center !important; }

    /* Pie de tabla (totales de los formsets): con la tabla en `display:block`,
       un `table-footer-group` queda descolgado; se lo trata como una fila más. */
    .tbl--cards tfoot,
    .tbl--form tfoot { display: block; }
    .tbl--cards tfoot tr,
    .tbl--form tfoot tr {
        display: flex;
        justify-content: space-between;
        gap: var(--space-3);
        padding: var(--space-3) var(--space-1) 0;
        border-top: 1px solid var(--color-border);
    }
    .tbl--cards tfoot td,
    .tbl--form tfoot td {
        display: block;
        height: auto !important;
        padding: 0 !important;
        border: 0 !important;
        text-align: left !important;
    }

    /* Título de la tarjeta: la columna marcada con `data-tit` en el <th>.
       Vale igual para los formsets: en «Confirmar tiempos» el nombre de la
       subtarea encabeza su bloque de campos en vez de ser un par etiqueta:valor. */
    .tbl--cards tbody > tr > td.tbl-tit,
    .tbl--form tbody > tr > td.tbl-tit {
        font-size: var(--font-size-md);
        font-weight: var(--font-weight-semibold);
        padding-bottom: var(--space-2) !important;
    }
    .tbl--cards tbody > tr > td.tbl-tit::before,
    .tbl--form tbody > tr > td.tbl-tit::before { content: none; }

    /* Pie de acciones: botones de 44px de alto al pie de la tarjeta. Es la
       razón de ser del modo tarjetas.
       F6 — CÓMO SE REPARTE EL ANCHO. Hasta F3 iban todos apilados, uno por
       línea. En el detalle de OT eso daba tarjetas de subtarea con CUATRO
       renglones de botón («✓ Aprobar», «✗ Rechazar», «✎», «🗑») = ~200px de
       pie por subtarea. Ahora es una fila que envuelve y app.js clasifica cada
       acción por lo que contiene (`etiquetarTablas`):
         · `tbl-act--icono` (texto de 1-2 caracteres: ✎ 🗑 ⚙ ▲ ▼) → ancho
           natural, van todos juntos en una línea.
         · `tbl-act--ancho` (trae un <select>/<input>: el combo «cuadrilla +
           Tomar») → línea entera, porque adentro apila dos controles.
         · el resto de los BOTONES → `flex: 1 1 45%`: si hay uno solo crece y
           ocupa la línea completa (queda igual que antes); si hay dos, entran
           de a dos.
         · lo que no es botón (los mensajes `.solo-movil`) → línea entera.
       Ninguna acción se pierde ni cambia de orden: cambia cuánto ocupan. */
    .tbl--cards tbody > tr > td.tbl-actions {
        display: flex !important;
        flex-flow: row wrap;
        align-items: stretch;
        gap: var(--space-2);
        margin-top: var(--space-2);
        padding-top: var(--space-3) !important;
        border-top: 1px solid var(--color-border) !important;
    }
    /* Por defecto, línea entera: en el pie no hay solo botones — están también
       los mensajes que en escritorio viven en un `title` y en el teléfono se
       muestran como texto («Completá primero «Corte»», «Esperando Calidad»).
       Partirlos al 45% los dejaría en una columna de 150px al lado del botón. */
    .tbl--cards td.tbl-actions > * { flex: 1 1 100%; }
    /* Los botones sí se reparten: uno solo crece y ocupa la línea (queda igual
       que antes de F6); dos entran de a dos. */
    .tbl--cards td.tbl-actions > .btn,
    .tbl--cards td.tbl-actions > button,
    .tbl--cards td.tbl-actions > a,
    .tbl--cards td.tbl-actions > form { flex: 1 1 45%; }
    .tbl--cards td.tbl-actions > .tbl-act--ancho { flex: 1 1 100%; }
    .tbl--cards td.tbl-actions > .tbl-act--icono {
        flex: 0 0 auto;
        min-width: var(--tap-min);
    }
    /* Un botón de ícono NO se estira a lo ancho (la regla general de más abajo
       pone `width:100%` en todo lo que cuelga del pie). */
    .tbl--cards td.tbl-actions > .tbl-act--icono,
    .tbl--cards td.tbl-actions > .tbl-act--icono > .btn,
    .tbl--cards td.tbl-actions > .tbl-act--icono > button {
        width: auto !important;
    }

    /* Un control con `data-al-pie="X"` cambia su texto por «X» en el teléfono
       («↻ Intentos» → «↻»), sin tocar el DOM: el texto se apaga con font-size:0
       y el glifo lo pinta un pseudo-elemento con el valor del atributo. Se hace
       así y no con un <span> adentro del botón porque un `.btn` es inline-flex y
       un elemento adentro parte el texto en dos ítems, ensancha el botón y
       mueve la fila entera en ESCRITORIO (medido: 1,07% de píxeles distintos en
       el detalle de OT). Un atributo no dibuja nada. */
    .tbl--cards td.tbl-actions > [data-al-pie]:not([data-al-pie=""]) {
        font-size: 0 !important;
    }
    .tbl--cards td.tbl-actions > [data-al-pie]:not([data-al-pie=""])::before {
        content: attr(data-al-pie);
        font-size: var(--font-size-md);
        line-height: 1;
    }
    .tbl--cards tbody > tr > td.tbl-actions::before { content: none; }
    /* Hay listas con DOS columnas de acción (notifs: «Ver» y «Marcar leída»).
       El pie es uno solo: la segunda no repite el borde ni el espacio. */
    .tbl--cards tbody > tr > td.tbl-actions + td.tbl-actions {
        margin-top: 0;
        padding-top: 0 !important;
        border-top: 0 !important;
    }
    .tbl--cards td.tbl-actions form {
        display: flex !important;
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-2);
        width: 100%;
        margin: 0;
    }
    .tbl--cards td.tbl-actions .btn,
    .tbl--cards td.tbl-actions button,
    .tbl--cards td.tbl-actions > a {
        width: 100%;
        justify-content: center;
        margin-left: 0 !important;
    }
    /* El combo «select de cuadrilla + Tomar» se apila: el select medía 33x28px
       con fuente de 10.8px, o sea intocable con un dedo. */
    .tbl--cards td.tbl-actions select,
    .tbl--cards td.tbl-actions input,
    .tbl--cards td.tbl-actions .form-input {
        width: 100% !important;
        max-width: none !important;
        height: auto !important;
        min-height: var(--tap-min);
        font-size: max(16px, var(--font-size-sm)) !important;
    }

    /* Reordenar arrastrando no existe en touch (decisión D10): el «⠿» se va. */
    .tbl--cards td.st-handle { display: none !important; }
    .tbl--cards tbody > tr[draggable] { -webkit-user-drag: none; }

    /* Fila clickeable (16 listas abren el detalle con hx-get): la tarjeta
       entera sigue siendo el disparador y se nota al tocarla (decisión D3). */
    .tbl--cards tbody > tr[hx-get] { cursor: pointer; }
    .tbl--cards tbody > tr[hx-get]:active { background: var(--color-surface-2); }

    /* ---- Formsets (P-F): el valor de cada fila es un campo ---- */
    .tbl--form td input:not([type="checkbox"]):not([type="radio"]),
    .tbl--form td select,
    .tbl--form td textarea {
        width: 100% !important;
        max-width: none !important;
    }
    .tbl--form tbody > tr > td::before { display: block; min-width: 0; }

    /* ---- Modo clave-valor (P-D): tablas de 2 columnas ----
       No se vuelven tarjetas (sería el doble de alto para nada): siguen siendo
       tabla, pero dejan de exigir un ancho mínimo y envuelven. */
    .tbl--kv table { table-layout: fixed; width: 100% !important; }
    .tbl--kv th,
    .tbl--kv td {
        /* Los `width:200px` inline de las etiquetas dejaban 158px para el valor
           en una pantalla de 390: con `table-layout:fixed` y ancho automático
           las dos columnas se reparten la mitad cada una. */
        width: auto !important;
        white-space: normal !important;
        overflow-wrap: anywhere;
        padding: var(--space-2) !important;
        height: auto !important;
    }

    /* El complemento de `.solo-movil`: lo que se ve en tablet y escritorio pero
       NO en el teléfono. Se usa para lo que no es del piso y cuesta alto en una
       tarjeta — hoy, el historial de reprogramaciones de cada subtarea (ver la
       nota larga en ot/partials/subtareas_tabla.html). No es «esconder por las
       dudas»: es un panel de planificación que se opera desde una PC y que al
       pie de la tarjeta costaba 52px por subtarea. */
    .solo-grande { display: none !important; }

    /* Lo que en escritorio vive en un `title=` y en touch sería invisible. */
    .solo-movil {
        display: block;
        margin-top: 4px;
        color: var(--color-text-muted);
        font-size: var(--font-size-xs);
        line-height: 1.35;
        white-space: normal;
    }

    /* ---- Paginación de las listas ---- */
    .paginacion {
        flex-direction: column;
        align-items: stretch !important;
        gap: var(--space-2);
    }
    .paginacion .btn { width: 100%; justify-content: center; }
    /* «‹‹ primera / última ››» no valen una fila entera en un teléfono. */
    .paginacion .paginacion__extremo { display: none; }
}

/* ---------------------------------------------------------------------------
   5.3 DETALLE DE OT y compañía (F2): el hero, el acordeón y las pestañas.
   ---------------------------------------------------------------------------
   SOBRE EL ALTO, para que nadie se sorprenda: el detalle NO queda más corto que
   antes, queda más LARGO. Medido en iPhone 13 sobre una OT de 6 subtareas:
   2.294px (3,8 pantallas) antes de F2, 4.142px (6,2) después. La razón es que
   las 6 subtareas pasaron de ser una tabla de 920px recortada dentro de un
   contenedor de 436px —con los botones «Tomar/Terminar» directamente
   inalcanzables— a 6 tarjetas de ~350px con su acción de 44px al pie. Esa es la
   operación que el teléfono tiene que permitir; el largo es el precio y se paga
   con scroll, que sí existe.
   Lo que sí se recorta es todo lo demás: el acordeón deja «Calidad y
   materiales» en 59px en vez de ~600, e igual con esperas, prioridad, fechas,
   urgencia y tratamientos.
   F6 — LA TARJETA DE SUBTAREA, COMPACTADA. Era la peor deuda que dejó F2:
   ~350px por subtarea, o sea que una OT de 6 subtareas ocupaba media pantalla
   solo en aire. Se atacó sin sacar ni una acción, por cuatro lados:
     1. el «#» de orden deja de ser un renglón propio y encabeza la tarjeta
        junto al nombre («#3 Soldadura de refuerzos»);
     2. «Asignado a: —» y «Depende de: —» desaparecen cuando el dato no existe
        (la rayita cuenta como celda vacía, sección 5.2);
     3. «T. est» y «T. real» comparten un renglón (`data-par`);
     4. el pie de botones envuelve en vez de apilar: «✓ Aprobar / ✗ Rechazar»
        en una línea y «✎ 🗑 ↻ ▲▼» en otra, en vez de cinco renglones.
   Medido en iPhone 13 sobre la misma OT de 6 subtareas que se midió en F2:
   la tarjeta pasa de 348px a 220-246px (-29%) y el detalle completo de 4.142px
   a 3.502px (-15%), sin perder ni una acción. Las 6 subtareas se recorrían en
   6,2 pantallas y ahora entran en 5,3.
--------------------------------------------------------------------------- */
@media (max-width: 639px) {

    /* --- Tarjeta de subtarea: el número de orden encabeza, no ocupa renglón ---
       `.st-handle` (el ⠿ de arrastrar) ya está escondido; `.st-pos` pasa a ser
       un prefijo del título. El selector hermano funciona igual con el ⠿ en
       medio: `display:none` no rompe la adyacencia en CSS. */
    .tbl--cards tbody > tr > td.st-pos {
        display: inline-block !important;
        width: auto !important;
        padding: 0 6px 0 0 !important;
        font-size: var(--font-size-md);
        font-weight: var(--font-weight-semibold);
        color: var(--color-text-muted);
    }
    .tbl--cards tbody > tr > td.st-pos::before {
        content: "#";
        min-width: 0;
        padding: 0;
        font-size: inherit;
        color: inherit;
    }
    .tbl--cards tbody > tr > td.st-pos + td.tbl-tit {
        display: inline-block !important;
        max-width: calc(100% - 2.5em);
    }

    /* --- Encabezado de la OT --- */
    /* El bloque de estado/acciones deja de pegarse a la derecha (en una columna
       de 390px, «alineado a la derecha» se lee como un error de maquetado). */
    .ot-hero__lado {
        align-items: flex-start !important;
        width: 100%;
    }
    .ot-hero__lado > div { width: 100%; }
    .ot-hero__lado .btn { flex: 1 1 auto; justify-content: center; }
    /* La tira de fechas (necesidad, aceptada, inicio real, cierre real, pintura,
       visita) en una sola línea no entra; en dos columnas se lee de un vistazo. */
    .ot-hero__fechas {
        display: grid !important;
        grid-template-columns: 1fr 1fr;
        gap: var(--space-3) var(--space-4) !important;
    }

    /* --- Acordeón (las clases las pone app.js; sin JS no cambia nada) --- */
    .card.es-plegable > .card__header {
        cursor: pointer;
        gap: var(--space-2);
        align-items: center;
        min-height: var(--tap-min);
        /* Varios encabezados traen `justify-content:space-between` inline (para
           empujar su botón a la derecha). Con el ▾ metido adelante, eso dejaba
           el ▾ pegado a la izquierda y el TÍTULO contra el borde derecho, como
           si estuviera desalineado. Acá el título va pegado a su flecha. */
        justify-content: flex-start !important;
        flex-wrap: wrap;
    }
    /* El botón propio del encabezado («+ Nuevo envío») vuelve a la derecha. */
    .card.es-plegable > .card__header > .btn:last-child { margin-left: auto; }
    .card.es-plegable > .card__header .pleg-chevron {
        display: inline-block;
        flex-shrink: 0;
        color: var(--color-text-muted);
        transition: transform var(--transition-base);
    }
    .card.es-plegable.esta-plegado > .card__header .pleg-chevron { transform: rotate(-90deg); }
    .card.es-plegable.esta-plegado > .card__body,
    .card.es-plegable.esta-plegado > .card__footer { display: none !important; }
    .card.es-plegable.esta-plegado { margin-bottom: var(--space-2) !important; }

    /* --- Cabecera de «Subtareas»: la fila de acciones ENVUELVE, no se recorta ---
       El `<div>` que agrupa los botones ya traía `flex-wrap:wrap`, así que
       «⧉ Dependencias», «⏱ Confirmar tiempos» y «⛌ Detectar duplicados» caían
       solos. El que no envolvía era el <form> de aplicar template, que es UN
       hijo indivisible de ese wrapper: select de `min-width:180px` + la etiqueta
       «+ fase materiales» (con `white-space:nowrap`) + «Agregar», todo en fila,
       411px dentro de una cabecera de 364. Con `.card { overflow:hidden }` eso no
       es «se ve feo»: el botón «Agregar» quedaba en x=365..411 sobre una pantalla
       de 390 y NO había forma de alcanzarlo — ni scrolleando, porque no había
       scroll. Igual dentro de la hoja modal (right=419).
       Se arregla con el mismo criterio que el resto de la rama: envolver, no
       recortar. El select toma el renglón entero (su `min-width` inline se
       neutraliza) y la etiqueta con el botón comparten el de abajo. */
    .card__header .st-tpl-form {
        flex-wrap: wrap;
        width: 100%;
        row-gap: var(--space-2) !important;
    }
    .card__header .st-tpl-form > select {
        flex: 1 1 100%;
        min-width: 0 !important;
    }
    .card__header .st-tpl-form > label { flex: 1 1 auto; }
    .card__header .st-tpl-form > .btn { flex: 0 0 auto; }

    /* --- Pestañas «Calidad y materiales»: tira que se arrastra --- */
    .cym-tabs {
        flex-wrap: nowrap !important;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior-x: contain;
        padding-bottom: var(--space-2);
    }
    .cym-tabs > .btn { flex: 0 0 auto; white-space: nowrap; }
    /* «Abrir en pantalla completa» tiene `margin-left:auto` inline: en una tira
       que scrollea eso lo manda a la luna. */
    .cym-tabs > #cym-full { margin-left: 0 !important; }

    /* --- Pestañas de la bandeja de Calidad --- */
    .qc-tabs { flex-direction: column; align-items: stretch; }
    /* La pestaña inactiva es un `.btn--ghost` (sin borde): apiladas a lo ancho,
       la activa se ve como un bloque azul y la otra como texto suelto que no
       parece tocable. El borde las vuelve a leer como dos pestañas. */
    .qc-tabs > .btn {
        width: 100%;
        justify-content: center;
        border: 1px solid var(--color-border);
    }

    /* --- Calendario de planificación: contención, no F4 (decisión D4) ---
       En teléfono el calendario es SOLO LECTURA. Las 68 «+ insertar día» que
       viven adentro de cada celda quedaban recortadas por el `overflow:hidden`
       de `.daycell` (medido: 68 botones en x=392..487 sobre una pantalla de
       390px, o sea inalcanzables). Como son acciones de escritura sobre una
       pantalla que en el teléfono no se opera, se esconden: desaparece el
       problema sin cambiar nada en tablet ni en escritorio. El calendario
       táctil de verdad (elegir + colocar, backlog plegable, celdas de 44px) es
       F4 y no está en el alcance de F2/F3. */
    .d-actions { display: none !important; }

    /* --- Links a tutoriales --- */
    /* Tres links de 44px separados por `gap:var(--space-4)` y envueltos por
       flex-wrap ocupaban ~400px antes de que se viera el contenido de la
       pantalla. Apilados y pegados, ocupan un tercio. */
    .tutorial-links {
        flex-direction: column;
        align-items: flex-start !important;
        gap: 0 !important;
    }

    /* --- Banner de ayuda (hallazgo H15) --- */
    /* El `padding-right:7rem` reservaba lugar para el botón absoluto: en 390px
       eso deja el texto en una columna de 200px. El botón se va al pie. */
    .ayuda-banner__cuerpo { padding-right: var(--space-4) !important; }
    .ayuda-banner__cerrar {
        position: static !important;
        display: block;
        padding: 0 var(--space-4) var(--space-3);
    }
    .ayuda-banner__cerrar .btn { width: 100%; justify-content: center; }
    /* Recortado a ~5 líneas hasta que se toque «Ver más» (lo agrega app.js solo
       si hay texto debajo del corte). */
    .ayuda-banner.esta-recortado .ayuda-banner__texto {
        max-height: 7.5em;
        overflow: hidden;
        -webkit-mask-image: linear-gradient(to bottom, #000 60%, transparent);
        mask-image: linear-gradient(to bottom, #000 60%, transparent);
    }
    .ayuda-banner__mas {
        display: block;
        margin-top: var(--space-2);
        min-height: var(--tap-min);
        padding: 0;
        border: 0;
        background: none;
        color: var(--color-primary);
        font-family: inherit;
        font-size: var(--font-size-sm);
        font-weight: var(--font-weight-semibold);
        cursor: pointer;
        touch-action: manipulation;
    }

    /* --- Desplegable de notificaciones --- */
    /* El mensaje venía con `white-space:nowrap` + elipsis y el texto completo
       solo en el `title`: en una pantalla táctil eso es información perdida. */
    .notif-msg { white-space: normal !important; }

    /* --- Chat de la OT y de solicitudes --- */
    /* La burbuja viene con `max-width:78%` inline, pensado para una columna de
       800px. En 358px eso son 279px y un mensaje de dos renglones se parte en
       cinco. 88% deja ver de quién es cada burbuja y aprovecha el ancho. */
    .chat-msg { max-width: 88% !important; }
    /* En un teléfono, 360px de alto máximo se comen media pantalla y encima
       el teclado tapa el resto: el hilo se achica y el campo queda a la vista. */
    #ot-chat-container { max-height: 45vh !important; }
    /* El placeholder («Escribí un mensaje… (Enter para enviar, Shift+Enter salto
       de línea)») ocupa dos renglones a 16px y quedaba cortado al medio dentro
       de los 44px del campo. */
    #ot-chat-input,
    #chat-input { min-height: 56px !important; }
}

/* ---------------------------------------------------------------------------
   5.4 PANTALLAS DE ESCRITORIO EN UNA PANTALLA CHICA (F5)
   ---------------------------------------------------------------------------
   Decisión D11: planificación, costeo, reportes y administración se usan en
   escritorio y tablet. El objetivo acá NO es que se operen con el pulgar: es
   que si alguien las abre desde una tablet o un teléfono, NO estén rotas y se
   puedan leer. Todas sus tablas están en `.tbl--scroll` (sección 5.1); acá va
   lo puntual que el scroll por sí solo no resuelve, que resultó ser una sola
   pantalla: la matriz de permisos, la única con la primera columna anclada por
   su propio CSS y 11 columnas de rol atrás.
--------------------------------------------------------------------------- */
@media (max-width: 1023px) {

    /* La matriz de permisos tiene 11 columnas de rol y una primera columna
       («Acción») de 220px fijos. Pegada a la izquierda en una tablet de 810px
       eso todavía funciona, pero es la columna que más ancho puede ceder. */
    .perm-matriz th:first-child { min-width: 150px !important; }
}

@media (max-width: 639px) {

    /* Una columna pegada de 220px sobre 390px de pantalla deja 170px para las
       otras once: en el teléfono la matriz se lee mejor sin columna fija que
       con una que se come la mitad. */
    .perm-matriz th:first-child,
    .perm-matriz td:first-child {
        position: static !important;
        min-width: 120px !important;
        box-shadow: none !important;
    }

    /* --- Bloques <pre> que en el teléfono quedaban cortados ---
       Los cuatro traen `white-space:pre` (o el default) desde un <style> de su
       propia página, así que necesitan `!important` para que este archivo —que
       se carga ANTES— les gane. Queda encerrado en la media query del teléfono:
       en escritorio siguen en una línea, que es como se leen bien.
         · .man-chuleta__code   la chuleta de HTML del ABM de manuales
                                (manuales/_chuleta.html + estilos en abm_form.html);
         · .diff-block          el diff entre versiones de un tutorial;
         · .ancla-preview__src  el fragmento de código que muestra un ancla;
         · .pre-envuelve        el JSON del snapshot de gantt (clase agregada
                                al <pre> de gantt/detalle_snapshot.html).
       `overflow-wrap: anywhere` es lo que hace que corte también DENTRO de una
       palabra larga sin espacios: un payload JSON o una ruta de archivo no
       tienen dónde partirse y sin esto seguirían desbordando. */
    .man-chuleta__code,
    .diff-block,
    .ancla-preview__src,
    .pre-envuelve {
        white-space: pre-wrap !important;
        overflow-wrap: anywhere;
        word-break: break-word;
    }
}

/* ---------------------------------------------------------------------------
   5.5 TABLET VERTICAL: QUE SE VEA QUE LA TABLA SE DESLIZA
   ---------------------------------------------------------------------------
   En iPad vertical (810px) las tablas del piso NO se vuelven tarjetas —eso pasa
   a <= 639px— y se arrastran adentro de su caja. Medido: 168px escondidos a la
   derecha. El problema NO es el gesto (168px es un tirón corto): es que **nada
   anuncia que haya algo ahí**. La tabla se ve entera, la columna «Acciones»
   entra completa, y iOS no dibuja barra de scroll hasta que ya empezaste a
   arrastrar. Y cuando arrastrás, se van el «#» y el «Nombre»: se toca
   «Terminar» sin ver de qué subtarea es.
   NO se mueve el corte de `.tbl--cards` a 1023 (decisión del dueño: toca ~17
   plantillas). Tres respuestas, todas acotadas a <= 1023px o a `pointer:coarse`,
   o sea que el escritorio con mouse no se entera:
     (a) sombra en el borde, que aparece SOLO si hay contenido escondido de ese
         lado (app.js pone `hay-mas-der` / `hay-mas-izq` al scrollear);
     (b) barra de scroll PERSISTENTE en táctil (en iOS hay que pedirla con
         `-webkit-appearance:none`, si no se esconde sola);
     (c) columnas fijas hasta la del TÍTULO inclusive. Fijar solo la primera
         —lo que ya hace `.tbl--scroll` desde F5— no alcanza en estas tablas:
         su primera columna es el chip de plan o el «⠿», y lo que no hay que
         perder de vista es el nombre. app.js calcula los offsets y pone
         `.tbl-fija`; la guarda del 55% del ancho está allá.
--------------------------------------------------------------------------- */
/* El mismo OR que la columna fija: por ancho, o por puntero grueso a cualquier
   ancho (el iPad acostado mide 1080px y se opera con el dedo). Con mouse no
   aplica ninguna de las dos y el escritorio no se entera. */
@media (max-width: 1023px), (pointer: coarse) {

    /* (a) La sombra es `inset`: se pinta sobre el borde del CONTENEDOR, no del
       contenido, así que no se va con el scroll. Las celdas de la tabla son
       transparentes, así que se ve a todo lo alto.
       `--sombra-propia` NO es adorno: `box-shadow` REEMPLAZA, no suma. Varios de
       estos contenedores SON la tarjeta (`<div class="card tbl tbl--cards">` en
       «Para tomar»), así que poner la sombra del borde a secas les borraba su
       sombra de tarjeta y la tarjeta se aplanaba al arrastrarla. */
    .tbl--scroll,
    .tbl--cards { --sombra-propia: 0 0 transparent; }
    .card.tbl--scroll,
    .card.tbl--cards { --sombra-propia: var(--shadow-card); }

    .tbl--scroll.hay-mas-der,
    .tbl--cards.hay-mas-der {
        box-shadow: var(--sombra-propia), inset -18px 0 14px -14px rgba(0, 0, 0, .45);
    }
    .tbl--scroll.hay-mas-izq,
    .tbl--cards.hay-mas-izq {
        box-shadow: var(--sombra-propia), inset 18px 0 14px -14px rgba(0, 0, 0, .45);
    }
    .tbl--scroll.hay-mas-izq.hay-mas-der,
    .tbl--cards.hay-mas-izq.hay-mas-der {
        box-shadow: var(--sombra-propia),
                    inset 18px 0 14px -14px rgba(0, 0, 0, .45),
                    inset -18px 0 14px -14px rgba(0, 0, 0, .45);
    }
}

@media (min-width: 640px) and (max-width: 1023px) {

    /* En la tablet la tabla del piso sigue siendo tabla: se arrastra adentro de
       su caja en vez de desbordarla (y de quedar recortada por el
       `overflow:hidden` de `.card`, que es lo que pasaba con `#st-refresh`). */
    .tbl--cards {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior-x: contain;
    }

    /* (c) Columnas fijas. `--fija-x` lo calcula app.js sumando el ancho de las
       columnas de antes; `.tbl-fija--ultima` es la del título y lleva la sombra
       que avisa «esto está pegado, lo de atrás pasa por debajo». */
    .tbl--cards .tbl-fija {
        position: sticky;
        left: var(--fija-x, 0px);
        z-index: 1;
        background: var(--color-surface);
    }
    .tbl--cards thead .tbl-fija {
        background: var(--color-surface-2);
        z-index: 2;
    }
    .tbl--cards .tbl-fija--ultima { box-shadow: 2px 0 4px -2px rgba(0, 0, 0, .18); }
}

/* (b) Barra de scroll persistente en cualquier pantalla táctil. En un teléfono
   `.tbl--cards` ya es una pila de tarjetas y no scrollea, así que esto se ve
   donde hace falta: tablet y iPad horizontal. En escritorio con mouse no
   aplica y la barra sigue siendo la del sistema. */
@media (pointer: coarse) {
    .tbl--scroll::-webkit-scrollbar,
    .tbl--cards::-webkit-scrollbar {
        -webkit-appearance: none;
        height: 8px;
    }
    .tbl--scroll::-webkit-scrollbar-track,
    .tbl--cards::-webkit-scrollbar-track { background: var(--color-surface-2); }
    .tbl--scroll::-webkit-scrollbar-thumb,
    .tbl--cards::-webkit-scrollbar-thumb {
        border-radius: 4px;
        background: var(--color-fg-subtle);
    }
    .tbl--scroll,
    .tbl--cards { scrollbar-width: thin; }
}

/* ---------------------------------------------------------------------------
   6. ERGONOMÍA TÁCTIL (pointer: coarse) — decisión D6
   Aplica en teléfono, tablet y iPad horizontal. NO aplica con mouse.
--------------------------------------------------------------------------- */
@media (pointer: coarse) {

    /* Los tokens de densidad se redeclaran DESPUÉS de [data-density="compacta"]
       (tokens.css se carga antes que este archivo) → ganan por orden, con la
       misma especificidad. Todo lo que ya consume estos tokens (.btn, inputs,
       filas de tabla) crece solo, sin tocar base.css. */
    :root,
    [data-density="compacta"] {
        --density-button-h: var(--tap-min);
        --density-input-h: var(--tap-min);
        --density-row-h: 48px;
    }

    /* Sin retraso de 300ms ni flash azul feo al tocar. */
    .btn, .icon-btn, a.nav-item, .tabbar__item, button, [role="button"], summary {
        touch-action: manipulation;
        -webkit-tap-highlight-color: var(--color-primary-soft);
    }

    /* Botones chicos: crecen en ALTO sin cambiar la tipografía (para no
       reventar el ancho de las tablas densas). `min-height`/`min-width` se
       eligieron a propósito en vez de `height`: acotan el tamaño POR ENCIMA de
       un `height` inline (hay decenas de botones con `style="height:24px"`),
       cosa que `height` no haría, y solo tocan a los que hoy quedan por debajo
       de 44px. */
    .btn, .btn--sm { min-width: var(--tap-min); min-height: var(--tap-min); }
    .icon-btn { width: var(--tap-min); height: var(--tap-min); }
    .input--sm, select.input--sm, input.input--sm { min-height: var(--tap-min); }
    /* Link/botón «📖 Cómo se hace» del tag {% tutorial %}: viene con padding:0
       inline y queda de 17px de alto. */
    .tutorial-link { min-height: var(--tap-min); }
    /* Links de TEXTO que son un destino de verdad y no un botón —hoy
       «¿Olvidaste tu contraseña?» en las dos logins y en la pantalla de cambio—.
       Al ser `inline` miden el alto de su renglón (~17px en iPad) y la auditoría
       los cuenta, con razón: hay que acertarles con el dedo. Se los cajea solo
       acá adentro, así en escritorio siguen siendo un link común y corriente. */
    .link-tap {
        display: inline-flex;
        align-items: center;
        min-height: var(--tap-min);
    }
    /* El ✓ «marcar leída» del desplegable de notificaciones: el alto se lo da
       `align-self:stretch`, pero de ancho quedaba en ~30px. */
    .notif-check { min-width: var(--tap-min); }
    /* Las opciones del multi-select («Estado», «Severidad»…) son <label> con
       una casilla adentro: se tocan por el label, así que el label es el que
       tiene que medir 44px. */
    .ms-opt { min-height: var(--tap-min); }
    /* El clip 📎 de adjuntar en el chat de solicitudes es un <label> (para
       disparar el input file oculto), o sea que no lo agarra ninguna de las
       reglas de botones: quedaba en ~35px. */
    #chat-clip {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: var(--tap-min);
        min-height: var(--tap-min);
    }
    /* Casillas y radios: se tocan por su label, no por el cuadradito. Se los
       agranda a un tamaño cómodo (no a 44px: quedaría enorme y feo). Están en
       la lista blanca de la auditoría por ese motivo. */
    input[type="checkbox"], input[type="radio"] { width: 22px; height: 22px; }

    /* iOS Safari hace ZOOM al enfocar un input de menos de 16px, y de ese zoom
       no se vuelve solo: es la diferencia entre poder cargar un dato en el piso
       y no poder. Por eso acá sí va !important: hay campos con `font-size`
       INLINE (p. ej. el selector de cuadrilla de ot/para_tomar.html: 0.75rem y
       height:28px) y ninguna clase le gana a un inline. Encerrado en
       `pointer: coarse` → en escritorio con mouse no cambia ni un píxel.
       El `:not(...)` no es decorativo: sube la especificidad por encima de los
       `input[type="text"]` de base.css:392-397 y de paso deja afuera casillas,
       radios y sliders, que no llevan tipografía ni alto de 44px. */
    input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="range"]),
    select, textarea,
    .form-input, .form-select, .input {
        font-size: max(16px, var(--font-size-sm)) !important;
        min-height: var(--tap-min);
        /* Un <select> de 37px de ancho apretado en una celda de tabla es
           intocable con un dedo (pasa en ot/para_tomar.html). Solo crecen los
           que hoy quedan por debajo de 44. */
        min-width: var(--tap-min);
    }

    /* `<summary>`: el título de un <details> es un control (abre y cierra) y en
       el repo hay varios de una línea — «Ver diff» del registro de auditoría,
       «Traceback / stack» del visor de errores — que medían entre 19 y 22px de
       alto. Se le da alto mínimo, no `display:flex`: en Chrome eso último
       borraría el triangulito ▸ que es la única pista de que se puede abrir. */
    summary {
        min-height: var(--tap-min);
        padding-top: var(--space-2);
        padding-bottom: var(--space-2);
    }

    /* Ítems del menú y opciones de tema, tocables. */
    .nav-item { min-height: var(--tap-min); padding-top: var(--space-2); padding-bottom: var(--space-2); }
    .theme-option { min-height: var(--tap-min); }
    .theme-swatch__dot { height: var(--tap-min); }
    .theme-swatch__picker { height: var(--tap-min); }

    /* Enlaces sueltos dentro de tablas: área tocable sin cambiar el diseño. */
    .table td a:not(.btn):not(.icon-btn) { display: inline-block; padding: 8px 0; }

    /* --- Sin ayudas hover-only ---
       En touch no hay hover: el globito del «?» se abre TOCÁNDOLO (app.js pone
       .is-open). Se agranda el punto y se le da un área tocable de 44px con un
       pseudo-elemento, para no deformar el diseño con un círculo gigante. */
    .help-tip { width: 22px; height: 22px; font-size: 13px; cursor: pointer; }
    .help-tip::after {
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        width: var(--tap-min);
        height: var(--tap-min);
        transform: translate(-50%, -50%);
    }
    .help-tip.is-open .help-tip__bub { opacity: 1; visibility: visible; }

    /* Mismo quirk que el fondo del drawer: para que tocar el fondo de un modal
       lo cierre en Safari iOS, el fondo tiene que ser «clickeable». La tarjeta
       de adentro vuelve al cursor normal para no contagiar la mano al contenido. */
    .modal-backdrop { cursor: pointer; }
    .modal-backdrop > * { cursor: auto; }

    /* Foco visible también en táctil (teclados externos, accesibilidad). */
    .btn:focus-visible,
    .icon-btn:focus-visible,
    .tabbar__item:focus-visible,
    .nav-item:focus-visible {
        outline: 2px solid var(--color-primary);
        outline-offset: 2px;
    }
}

/* ---------------------------------------------------------------------------
   7. Impresión: nada del shell móvil se imprime
--------------------------------------------------------------------------- */
@media print {
    .tabbar,
    .drawer-backdrop,
    .sheet-bar,
    .topbar__drawer { display: none !important; }
}
