/* ══════════════════════════════════════════════════════════════════════════
   GENERADO POR tools/juntar-css.py — NO SE EDITA A MANO.

   Son las 38 hojas de `/css/` que enlazaba `index.html`, en el MISMO orden,
   sin quitar ni cambiar un byte. En CSS el orden es el significado.

   Se edita el archivo suelto que toque y se vuelve a generar; `sellar.py` lo
   hace solo. `tools/juntar-css.py --revisar` se pone rojo si esto no
   corresponde a los sueltos.

   Por que existe: 38 hojas en el `<head>` son 38 idas y vueltas antes de que
   el navegador pinte un solo pixel. En una parada de camiones con dos rayas
   de senal, eso son cinco segundos de pantalla en blanco.
   ══════════════════════════════════════════════════════════════════════════ */

/* ───── css/fonts.css ───── */
/*PORQUE App styles PORQUE*/
/* ==========================================================================
   IRON ROUTE — LAS FUENTES DE LA MARCA, SERVIDAS DE VERDAD

   `ecosystem-core` dice: Syne para títulos, DM Sans para texto, JetBrains
   Mono para datos. Este archivo es el que lo cumple.

   LO QUE SE MIDIÓ Y POR QUÉ SE REHIZO. La versión anterior declaraba cinco
   `@font-face` con los nombres de archivo que sirve Google Fonts:

       fonts/rP2Yp2ywxg089UriI5-g4vlH9VoD8Cmcqbu0-K4.woff2
       fonts/8vIH7w4qzmVxm2BL9A.woff2   …

   Esos cinco archivos SÍ estaban en el servidor. Lo que no estaba era la
   carpeta `css/fonts/` en el repositorio local, así que **todas las medidas
   de tipografía de esta auditoría se tomaron con la fuente de respaldo del
   sistema, no con la de la marca**. Un ancho de texto medido en otra fuente
   es un ancho medido en otro producto.

   Se rehace por dos motivos, y ninguno es estético:

   1. NOMBRES QUE SE PUEDEN LEER. `rP2Yp2ywxg089UriI5-g4vlH9VoD8Cmcqbu0-K4`
      no dice qué fuente es, qué peso tiene ni qué alfabeto cubre. Cuando uno
      de esos cinco archivos falte —y ya faltó— nadie puede saber cuál era
      mirando el nombre. Ahora son `dm-sans-latin-400-normal.woff2` y
      `syne-latin-700-normal.woff2`.

   2. PROCEDENCIA COMPROBABLE. Vienen de `@fontsource/dm-sans` y
      `@fontsource/syne` —los paquetes oficiales, con su licencia SIL OFL
      dentro— y no de una descarga suelta que nadie sabe reproducir. Se pueden
      volver a generar con dos `npm install`.

   PESOS: los que se usan de verdad, contados en las hojas. DM Sans 400/500/700
   y Syne 500/700/800. Ni uno más: cada peso son ~21 KB entre latin y latin-ext,
   y en un teléfono con dos rayas de cobertura eso se nota.

   `font-display: swap` en todas: el conductor lee la calle con la fuente del
   sistema mientras llega la de la marca. Esperar a la fuente bonita para
   pintar el nombre de una salida es exactamente al revés de lo que necesita
   quien va conduciendo.

   LICENCIA: SIL Open Font License 1.1 — DM Sans (Colophon Foundry, Jonny
   Pinhorn) y Syne (Bonjour Monde, Lucas Descroix). Permite alojarlas.
   ========================================================================== */

/* ── DM Sans — el texto ─────────────────────────────────────────────────── */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/dm-sans-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/dm-sans-latin-ext-400-normal.woff2') format('woff2');
  /* latin-ext trae la ñ, los acentos y las comillas angulares del español.
     Sin este rango, «Camión» sale con la ó de otra fuente. */
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/dm-sans-latin-500-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/dm-sans-latin-ext-500-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/dm-sans-latin-700-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/dm-sans-latin-ext-700-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── Syne — los títulos ─────────────────────────────────────────────────── */
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/syne-latin-500-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/syne-latin-ext-500-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/syne-latin-700-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/syne-latin-ext-700-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('fonts/syne-latin-800-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('fonts/syne-latin-ext-800-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ══════════════════════════════════════════════════════════════════════════
   GURMUKHI Y CIRÍLICO NO ESTÁN AQUÍ: están en `css/escrituras.css`.

   Se sacaron el 21-sep-2026 porque también los necesita la landing, y un
   archivo de fuentes de la app no lo enlaza la landing. Un texto —o una
   fuente— que vive en dos sitios acaba siendo dos cosas distintas.

   Quien sirva punyabí o ruso enlaza LOS DOS archivos.
   ══════════════════════════════════════════════════════════════════════════ */

/* ───── css/escrituras.css ───── */
/*PORQUE Gurmukhi y cirilico viven aparte porque la landing tambien los usa.
       Sin este enlace, punyabi y ruso salen en cuadros vacios, que es peor
       que salir en ingles. PORQUE*/
/* ==========================================================================
   LAS ESCRITURAS QUE NO SON LATINAS — GURMUKHI Y CIRÍLICO

   Vive aparte porque lo necesitan DOS superficies: la app web (/app) y la
   landing. Estaba solo dentro de `css/fonts.css`, que es de la app, así que
   la landing en punyabí o en ruso habría salido en cuadros vacíos — que es
   peor que salir en inglés.

   Las rutas son absolutas (`/css/fonts/…`) a propósito: este archivo lo
   enlazan páginas que viven en rutas distintas, y una ruta relativa se
   resolvería contra cada una.

   Lo demás —el porqué de Noto, el `unicode-range` y la licencia— está aquí
   abajo tal cual venía.
   ========================================================================== */
/* ══════════════════════════════════════════════════════════════════════════
   GURMUJI Y CIRÍLICO — Y POR QUÉ NO LOS PAGA NADIE QUE NO LOS USE

   Pedido del dueño, 20-sep-2026: «si recomiendas algo más que inglés y
   español se agrega, pero que se agregue bien». Con la letra de la casa,
   «bien» tiene un problema concreto y medido:

     · DM Sans NO TRAE CIRÍLICO. Comprobado en el paquete oficial
       (`@fontsource/dm-sans` 5.3.0): solo `latin` y `latin-ext`. Con la app
       en ruso, cada palabra caería en la fuente que el sistema tenga a mano
       —otra forma, otro grosor, otra altura de x— y el producto dejaría de
       parecer el mismo.
     · Y ninguna de las dos trae GURMUJI, que es la escritura del punyabí.
       Sin esto, «ਪੰਜਾਬੀ» sale en cuadros vacíos, que es peor que el inglés.

   Se añade Noto Sans en sus dos subconjuntos. Noto existe exactamente para
   esto —cubrir escrituras sin dejar el hueco— y su Sans casa en proporción
   con DM Sans mejor que cualquier respaldo del sistema.

   LO IMPORTANTE ES EL `unicode-range`: el navegador SOLO baja estos archivos
   cuando en la página hay caracteres de ese rango. Un conductor en español o
   en inglés no transfiere ni un byte de los 56 KB que suman los seis. Es la
   misma regla por la que `latin-ext` va aparte del `latin`.

   LICENCIA: SIL Open Font License 1.1 — Noto Sans (Google). Permite alojarla.
   ══════════════════════════════════════════════════════════════════════════ */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/css/fonts/noto-sans-gurmukhi-gurmukhi-400-normal.woff2') format('woff2');
  unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A76, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/css/fonts/noto-sans-gurmukhi-gurmukhi-500-normal.woff2') format('woff2');
  unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A76, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/css/fonts/noto-sans-gurmukhi-gurmukhi-700-normal.woff2') format('woff2');
  unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A76, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/css/fonts/noto-sans-gurmukhi-gurmukhi-500-normal.woff2') format('woff2');
  unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A76, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/css/fonts/noto-sans-gurmukhi-gurmukhi-700-normal.woff2') format('woff2');
  unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A76, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/css/fonts/noto-sans-cyrillic-400-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/css/fonts/noto-sans-cyrillic-500-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/css/fonts/noto-sans-cyrillic-700-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/css/fonts/noto-sans-cyrillic-500-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/css/fonts/noto-sans-cyrillic-700-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Syne 800 (los títulos) no tiene archivo propio en estas escrituras: se
   apunta al de 700 y el navegador engorda lo que falta. Un título algo menos
   grueso es mejor que un título en otra tipografía. */
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('/css/fonts/noto-sans-gurmukhi-gurmukhi-700-normal.woff2') format('woff2');
  unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A76, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839;
}
@font-face {
  font-family: 'Syne';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('/css/fonts/noto-sans-cyrillic-700-normal.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* ───── css/tokens.css ───── */
/* ============================================================
 * tokens.css — Iron Route GPS v2.5 · Brand TruckerProfit
 * Single source of truth: palette + typography + spacing + motion
 * Owner: Cowork v25 build | 2026-05-02
 * ============================================================ */
:root {
  /* EL MULTIPLICADOR DEL TAMAÑO DE LA LETRA. 1 de fábrica: con este valor la
     pantalla queda EXACTAMENTE como estaba. Lo cambia el guion previo a la
     pintura de `index.html` y, en caliente, `js/tamano-de-letra.js`.
     Está aquí y no solo en `style` de la raíz para que cualquier hoja pueda
     contar con que existe. */
  --ir-letra: 1;

  /* === BRAND PALETTE === */
  --color-navy-900:       #030D1C;   /* Primary canvas, headlines */
  --color-navy-800:       #0A1428;
  --color-navy-700:       #1A2440;
  --color-navy-600:       #2A3552;
  --color-gold-500:       #D4A84B;   /* Brand accent */
  --color-gold-400:       #E0B86A;
  --color-gold-600:       #B89438;
  --color-gold-700:       #8C6F1F;

  /* === SEMANTIC === */
  --color-bg:             var(--color-navy-900);
  --color-bg-elev:        #0A1024;
  --color-bg-card:        #0F1730;
  --color-surface:        #131C36;
  --color-text:           #E8ECF5;
  --color-text-muted:     #9DA8C2;
  --color-text-on-gold:   var(--color-navy-900);
  --color-border:         rgba(212,168,75,0.18);
  --color-border-strong:  rgba(212,168,75,0.4);
  --color-accent:         var(--color-gold-500);
  --color-accent-hover:   var(--color-gold-400);
  --color-accent-active:  var(--color-gold-600);

  /* === STATUS === */
  --color-success:        #2EAB6F;
  --color-warning:        #E8B14A;
  --color-danger:         #D9534F;
  --color-info:           #4A9FE8;

  /* === TYPOGRAPHY === */
  --font-display: 'Syne', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-body:    'DM Sans', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --fs-h1:    calc((clamp(2rem, 4vw, 2.75rem)) * var(--ir-letra, 1));
  --fs-h2:    calc((clamp(1.5rem, 3vw, 2rem)) * var(--ir-letra, 1));
  --fs-h3:    calc((1.25rem) * var(--ir-letra, 1));
  --fs-body:  calc((1rem) * var(--ir-letra, 1));
  --fs-small: calc((0.875rem) * var(--ir-letra, 1));
  --fs-tiny:  calc((0.75rem) * var(--ir-letra, 1));

  --fw-regular: 400;
  --fw-medium:  500;
  --fw-bold:    700;
  --fw-display: 700; /* Syne uses bold weights */

  --lh-tight:  1.15;
  --lh-normal: 1.5;
  --lh-loose:  1.7;

  /* === SPACING === */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;

  /* === RADII === */
  --r-sm:  4px;
  --r-md:  8px;
  --r-lg:  12px;
  --r-xl:  20px;
  --r-pill: 999px;

  /* === SHADOWS === */
  --shadow-sm:  0 1px 2px rgba(0,0,0,0.25);
  --shadow-md:  0 4px 12px rgba(0,0,0,0.35);
  --shadow-lg:  0 12px 32px rgba(0,0,0,0.45);
  --shadow-gold: 0 0 0 1px rgba(212,168,75,0.5), 0 4px 12px rgba(212,168,75,0.18);

  /* === MOTION === */
  --ease-standard: cubic-bezier(.4,0,.2,1);
  --ease-emphasized: cubic-bezier(.2,.8,.2,1);
  --dur-fast:     120ms;
  --dur-normal:   240ms;
  --dur-slow:     420ms;

  /* === LAYOUT === */
  --header-h:     56px;
  --sidebar-w:    380px;
  --sidebar-w-mobile: 100vw;
  --touch-min:    48px;
  --touch-driver: 64px;     /* Driver-mode tap targets, mejor que iOS HIG min */
}

/* === DARK FALLBACK (default since Iron Route is dark by default) === */
@media (prefers-color-scheme: light) {
  :root {
    /* Iron Route mantiene UX dark para reducir glare en cabin nocturno */
    /* No light theme; keep dark */
  }
}

/* === BASE TYPOGRAPHY === */
html { -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-normal);
  color: var(--color-text);
  background: var(--color-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, .display {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  line-height: var(--lh-tight);
  color: var(--color-text);
  letter-spacing: -0.02em;
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }

/* === BUTTONS BASE === */
.btn-primary {
  background: var(--color-gold-500);
  color: var(--color-text-on-gold);
  font-family: var(--font-body);
  font-weight: var(--fw-bold);
  border: none;
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-5);
  min-height: var(--touch-min);
  cursor: pointer;
  transition: background var(--dur-normal) var(--ease-standard),
              transform var(--dur-fast) var(--ease-standard);
}
.btn-primary:hover { background: var(--color-gold-400); }
.btn-primary:active { background: var(--color-gold-600); transform: translateY(1px); }
.btn-primary:focus-visible { outline: 2px solid var(--color-gold-400); outline-offset: 3px; }

/* === FOCUS RING (a11y) === */
:where(button, a, input, select, textarea):focus-visible {
  outline: 2px solid var(--color-gold-400);
  outline-offset: 2px;
}

/* === REDUCE MOTION === */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* === DRIVER MODE (HUD) === */
[data-mode="hud"] {
  --fs-body: calc((1.25rem) * var(--ir-letra, 1));
  --fs-small: calc((1rem) * var(--ir-letra, 1));
  --touch-min: var(--touch-driver);
}
[data-mode="hud"] h1 { font-size: calc(3rem * var(--ir-letra, 1)); }
[data-mode="hud"] body { letter-spacing: 0.02em; }


/* ══════════════════════════════════════════════════════════════════════════
   LOS TOKENS DE LA FUNDACIÓN — que NO estaban definidos

   `ecosystem-core` fija una fundación compartida por los seis productos:
   `--ink`, `--surface`, `--surface-2`, `--line`, `--text`, `--text-muted`,
   `--gold`, `--success`, `--warning`, `--error`, más el acento por producto.

   Medido en el navegador: `getComputedStyle(document.documentElement)` devuelve
   **cadena vacía** para `--ink`, `--surface`, `--line` y `--error`. Este archivo
   define un vocabulario propio (`--color-bg`, `--color-surface`, `--color-border`
   …) y nunca definió el compartido.

   Por qué no se notaba: casi todo el CSS nuevo escribe `var(--line, #1E3A5F)`,
   con la reserva puesta. O sea que funciona **por la reserva**, no por el token.
   Y ahí está el problema: el día que se cambie el color de línea del sistema, se
   cambiará en un sitio y no lo verá nadie, porque nadie está leyendo el token —
   están leyendo cincuenta copias del valor escritas a mano.

   Aquí se definen, MAPEADOS a los valores que este archivo ya tenía. No se
   inventa una paleta nueva ni se cambia un píxel: se le pone el nombre canónico
   a lo que ya había, para que a partir de ahora haya UNA fuente de verdad.

   `--accent` es el único que se corrige de valor: estaba en el dorado
   compartido `#D4A84B` y el acento de Iron Route es el naranja `#F58220` — así
   lo dice `ecosystem-core` y así lo escriben como reserva todas las hojas
   nuevas. El dorado sigue disponible como `--gold`, que es su nombre.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  --ink:        var(--color-bg);
  --surface:    var(--color-surface);
  --surface-2:  var(--color-bg-elev);
  --line:       var(--color-border);
  --text:       var(--color-text);
  --text-muted: var(--color-text-muted);

  --gold:       var(--color-gold-500);
  --gold-light: var(--color-gold-400);

  --success:    var(--color-success);
  --warning:    var(--color-warning);
  --error:      var(--color-danger);

  /* ── EL ACENTO DE IRON ROUTE ────────────────────────────────────────
     Aquí decía «Naranja, no dorado. --accent: #F58220» — y era mentira desde
     hacía meses: `css/styles.css` carga después y lo ponía en dorado, así que
     ninguna pantalla ha sido naranja nunca. Un token que declara una cosa y
     sirve otra es peor que no declararlo: el siguiente que lo lea escribirá
     código naranja que saldrá dorado.

     El acento del producto es DORADO. La decisión y sus números están en
     `css/styles.css`; aquí solo tiene que decir la verdad. */
  --accent:     var(--color-gold-500);

  --touch:      48px;
  --font-min:   12px;
  --nav-font:   20px;
}

/* ───── css/styles.css ───── */
/**
 * Iron Route GPS — Styles (Re-skinned to TruckerProfit brand)
 * Mobile-first, dark theme default
 * TruckerProfit Inc.
 *
 * NOTE: Layout, grid, flexbox, padding and margin are unchanged.
 * Only color + typography tokens were re-mapped to align with brand.
 * Source of truth for tokens: ./tokens.css
 */

/* ========== CSS CUSTOM PROPERTIES (RE-MAPPED TO BRAND TOKENS) ========== */

/* ══════════════════════════════════════════════════════════════════════════
   NOMBRES CORTOS QUE SE USABAN SIN EXISTIR — 119 usos, 17 tokens fantasma

   EL FALLO QUE LOS DESTAPÓ. El dueño: «esa superposición de los textos cuando
   uno está escribiendo la dirección y todo eso debe arreglarse». El
   desplegable de direcciones se pintaba TRANSPARENTE y «Añadir parada» se leía
   a través de él. Una línea:

       .ir-dir-ac-dropdown { background: var(--panel); }

   `--panel` no estaba definido en ninguna parte. Y ese es el detalle que hace
   esto tan peligroso: en CSS, una `var()` sin valor y sin respaldo NO es un
   error visible. La propiedad queda «inválida en tiempo de cómputo», que para
   `background` significa transparente, para `color` significa heredar, y para
   `box-shadow` significa nada. Sin aviso en consola. Sin nada roto que se vea
   al leer el código. Solo una pantalla que sale mal.

   Al medirlo aparecieron 17 tokens así, usados 119 veces, repartidos por
   `alerts.js`, `filters.js`, `settings.js`, `directions.js` y `navigation.js`:
   `--t1 --t2 --t3` (jerarquía de texto), `--bg2 --bg3 --card --panel --surface2`
   (superficies), `--muted --dim` (texto secundario), `--teal --red --green
   --amber` (semánticos), `--border2 --glass2 --shadow2`.

   Vienen de una época anterior a que existiera el sistema de tokens de la
   casa. Cada archivo se inventó su vocabulario y nadie lo cruzó nunca con lo
   que de verdad estaba declarado.

   LA DECISIÓN: no se renombran 119 sitios a mano —eso son 119 ocasiones de
   equivocarse y ni un aprendizaje—. Se declaran AQUÍ, UNA VEZ, apuntando a los
   tokens canónicos. Los nombres cortos pasan a ser alias del sistema, no un
   sistema paralelo. Así, cambiar el tema cambia también todo lo que usaba el
   vocabulario viejo, que es justo lo que antes no pasaba.

   Y para que no vuelva a entrar ninguno: `tests/tinta-sobre-papel.mjs` recorre
   todo el CSS y falla si un `var(--x)` apunta a algo que no existe y no lleva
   respaldo.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  /* Brand colors — now navy + gold (was navy + teal/orange) */
  --navy: var(--color-navy-900);
  --navy-light: var(--color-navy-800);
  --navy-mid: var(--color-navy-700);
  --navy-surface: var(--color-surface);
  /* ══════════════════════════════════════════════════════════════════════
     EL ACENTO CAMBIABA DE FAMILIA AL CAMBIAR DE TEMA

     Medido, leyendo el token calculado en el navegador:

         tema oscuro   --accent: #D4A84B   dorado
         tema claro    --accent: #A15200   naranja oscuro

     No son dos tonos del mismo color: son dos colores distintos. La app
     cambiaba de acento al cambiar de tema, y nadie lo había cruzado porque
     cada hoja se miró por separado.

     El origen es una contradicción entre dos archivos nuestros:

       · `css/tokens.css` dice, con estas palabras: «El acento de Iron Route.
         Naranja, no dorado. --accent: #F58220».
       · Este archivo, que carga DESPUÉS, ponía `--accent: var(--color-gold-500)`
         con el comentario «now navy + gold (was navy + teal/orange)».

     Ganaba el dorado, pero no por decisión: por orden de la cascada. Y
     `css/tema-claro.css`, escrito por otro camino, oscureció el NARANJA para
     que se leyera sobre blanco — de ahí el #A15200.

     LA DECISIÓN, Y SU PORQUÉ. Se queda el DORADO, en los dos temas:

       1. Es lo que está desplegado y lo que el dueño lleva meses viendo y
          aprobando en capturas. La marca de la app, el «+ Viaje», el logo:
          todo es dorado hoy.
       2. `ecosystem-core` fija `#F58220` como acento de Iron Route, pero el
          mismo documento admite «un acento de producto intencionado y
          documentado». Este lo está, y lleva 71 usos escritos a mano en 18
          hojas. Cambiarlo es un reskin, no un arreglo.
       3. Y el dorado gana en contraste donde importa: 8,81:1 sobre el fondo
          oscuro frente a 7,51:1 del naranja.

     Queda una cosa por hacer que NO es de este archivo: `ecosystem-core` y
     `css/tokens.css` dicen naranja y hay que corregirlos, o el siguiente que
     los lea volverá a pintar en naranja. Va anotado en el informe.

     LOS TRES PAPELES DEL ACENTO, que antes eran uno solo y por eso se
     pisaban. Contrastes medidos:

         --accent       #D4A84B   TEXTO en oscuro     8,81:1 sobre #030D1C
         --accent-fill  #D4A84B   RELLENO
         --accent-ink   #030D1C   la tinta ENCIMA del relleno   8,81:1

     En tema claro el mismo dorado sobre blanco da 2,21:1 y no se puede leer,
     así que allí `--accent` baja a `#7A5E10` (6,11:1) y el relleno se queda
     en el dorado de marca. Ver `css/tema-claro.css`.
     ══════════════════════════════════════════════════════════════════════ */
  --accent: var(--color-gold-500);
  --accent-fill: var(--color-gold-500);   /* RELLENO: el dorado de marca */
  --accent-ink: #030D1C;                  /* la tinta que va ENCIMA — 8,81:1 */
  --accent-hover: var(--color-gold-400);
  --accent-dim: rgba(212, 168, 75, 0.15);
  --gold: var(--color-gold-500);
  --gold-dim: rgba(212, 168, 75, 0.15);
  --danger: var(--color-danger);
  --danger-dim: rgba(217, 83, 79, 0.15);
  --warning: var(--color-warning);
  --success: var(--color-success);
  --info: var(--color-info);

  /* Theme (dark default) */
  --bg: var(--color-bg);
  --bg-surface: var(--color-bg-elev);
  --bg-elevated: var(--color-bg-card);
  --bg-card: var(--color-surface);
  --text: var(--color-text);
  --text-secondary: var(--color-text-muted);
  --text-muted: var(--color-text-muted);
  --border: var(--color-border);
  --border-strong: var(--color-border-strong);
  --shadow: rgba(0, 0, 0, 0.4);
  --glass: rgba(3, 13, 28, 0.85);
  --glass-border: rgba(212, 168, 75, 0.10);

  /* Sizing */
  --touch: var(--touch-min, 48px);
  --radius: var(--r-lg, 12px);
  --radius-sm: var(--r-md, 8px);
  --radius-lg: var(--r-xl, 20px);
  --nav-height: 64px;
  --search-height: 56px;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);

  /* Typography — point at canonical brand stacks */
  --font: var(--font-body);
  --font-heading: var(--font-display);
  --font-mono: var(--font-mono);
  /* LA SEGUNDA FAMILIA DE TAMAÑOS. `tokens.css` tiene `--fs-*` y esta hoja
     tiene `--font-size-*`: dos escalas tipográficas vivas a la vez, y las
     dos se usan. No se unifican hoy —eso toca sesenta reglas y no es lo que
     se está arreglando—, pero las DOS llevan el multiplicador, porque si
     solo lo lleva una, el ajuste de tamaño de letra agranda media pantalla
     y deja la otra media igual, que es peor que no agrandar nada.
     `--font-size-xs` son 11 px y es el único por debajo del suelo de la
     casa: lo corrige `css/suelo-tipografico.css`, que sube a 12 los sitios
     donde se usaba. */
  --font-size-xs: calc((11px) * var(--ir-letra, 1));
  --font-size-sm: calc((13px) * var(--ir-letra, 1));
  --font-size-base: calc((15px) * var(--ir-letra, 1));
  --font-size-lg: calc((17px) * var(--ir-letra, 1));
  --font-size-xl: calc((20px) * var(--ir-letra, 1));
  --font-size-2xl: calc((24px) * var(--ir-letra, 1));
  --font-size-3xl: calc((30px) * var(--ir-letra, 1));

  /* Transitions */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --duration: 0.25s;
  --duration-slow: 0.4s;

  /* ── Alias del vocabulario antiguo (ver el bloque de arriba) ── */
  --t1: var(--text);              /* texto principal          */
  --t2: var(--text-secondary);    /* texto de apoyo           */
  --t3: var(--text-muted);        /* texto terciario          */
  --muted: var(--text-muted);
  --dim: var(--text-muted);
  --bg2: var(--bg-surface);
  --bg3: var(--bg-elevated);
  --card: var(--bg-card);
  --panel: var(--bg-elevated);    /* papel de menús flotantes */
  --surface2: var(--bg-elevated);
  --border2: var(--border-strong);
  --glass2: var(--glass);
  /* `--shadow` es un COLOR, no una sombra: `box-shadow: var(--shadow)` era
     inválido y no pintaba nada. `--shadow2` sí es una sombra completa. */
  --shadow2: 0 8px 24px var(--shadow);
  --teal: var(--accent);          /* la marca es oro; el teal quedó de antes */
  --red: var(--danger);
  --green: var(--success);
  --amber: var(--warning);
}

/* ========== LIGHT THEME (kept for opt-in, but Iron Route ships dark-only) ========== */
[data-theme="light"] {
  --bg: #f5f7fa;
  --bg-surface: #ffffff;
  --bg-elevated: #f0f2f5;
  --bg-card: #ffffff;
  --text: #1a1a2e;
  --text-secondary: #555770;
  --text-muted: #8e8ea0;
  --border: rgba(0, 0, 0, 0.08);
  --border-strong: rgba(0, 0, 0, 0.15);
  --shadow: rgba(0, 0, 0, 0.1);
  --glass: rgba(245, 247, 250, 0.9);
  --glass-border: rgba(0, 0, 0, 0.06);
}

/* ========== RESET ========== */
*, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html {
  height: 100%;
  overflow: hidden;
  -webkit-text-size-adjust: 100%;
}

body {
  height: 100%;
  overflow: hidden;
  font-family: var(--font);
  font-size: var(--font-size-base);
  color: var(--text);
  background: var(--bg);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  touch-action: manipulation;
}

button {
  font-family: inherit;
  font-size: inherit;
  border: none;
  background: none;
  color: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

input, select, textarea {
  font-family: inherit;
  font-size: var(--font-size-base);
  color: var(--text);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  min-height: var(--touch);
  outline: none;
  width: 100%;
  transition: border-color var(--duration) var(--ease);
}

input:focus, select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}

input::placeholder {
  color: var(--text-muted);
}

ul, ol {
  list-style: none;
}

a {
  color: var(--accent);
  text-decoration: none;
}

img {
  max-width: 100%;
  display: block;
}

/* ========== SKIP LINK ========== */
.skip-link {
  position: absolute;
  top: -100px;
  left: 16px;
  z-index: 10000;
  background: var(--accent);
  color: var(--navy);
  padding: 12px 20px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  font-size: var(--font-size-sm);
  transition: top 0.2s;
}

.skip-link:focus {
  top: calc(var(--safe-top) + 8px);
}

/* ========== STATUS BAR SPACER ========== */
.status-bar-spacer {
  height: var(--safe-top);
  background: var(--glass);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1001;
}

/* ========== SEARCH HEADER ========== */
.search-header {
  position: fixed;
  top: var(--safe-top);
  left: 0;
  right: 0;
  z-index: 1000;
  padding: 8px 12px;
  pointer-events: none;
}

.search-container {
  display: flex;
  align-items: center;
  gap: 4px;
  background: var(--glass);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--glass-border);
  border-radius: 28px;
  padding: 4px 8px;
  box-shadow: 0 4px 20px var(--shadow);
  pointer-events: auto;
  height: var(--search-height);
}

.search-menu-btn,
.search-voice-btn {
  flex-shrink: 0;
  /* 48, no 44 — 21-sep-2026. El piso de la casa son 48 px y estos dos son el
     menu y el microfono de la barra de buscar: el microfono es el que se toca
     con una mano al volante, que es cuando peor se acierta. Otra regla los
     subia a 48 en el telefono, asi que el 44 solo se veia en tableta — donde
     tambien se toca con el dedo. */
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--text-secondary);
  transition: background var(--duration) var(--ease), color var(--duration) var(--ease);
}

.search-menu-btn:hover,
.search-voice-btn:hover {
  background: var(--accent-dim);
  color: var(--accent);
}

.search-input {
  flex: 1;
  background: none;
  border: none;
  padding: 8px 4px;
  font-size: var(--font-size-base);
  min-height: 48px; /* a11y: ≥48px touch target — auto-fixer 2026-05-05 */
  color: var(--text);
}

.search-input:focus {
  box-shadow: none;
  border: none;
}

/* Search Results Dropdown */
.search-results {
  position: absolute;
  top: calc(var(--search-height) + 8px);
  left: 12px;
  right: 12px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 32px var(--shadow);
  max-height: 50vh;
  overflow-y: auto;
  pointer-events: auto;
  overscroll-behavior: contain;
}

.search-results[hidden] {
  display: none;
}

.search-result-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  cursor: pointer;
  transition: background var(--duration) var(--ease);
  border-bottom: 1px solid var(--border);
  min-height: var(--touch);
}

.search-result-item:last-child {
  border-bottom: none;
}

.search-result-item:hover,
.search-result-item:focus {
  background: var(--accent-dim);
}

.search-result-item:active {
  background: var(--accent-dim);
  transform: scale(0.98);
}

.result-icon {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-dim);
  border-radius: 50%;
  color: var(--accent);
}

.result-text {
  flex: 1;
  min-width: 0;
}

.result-title {
  font-size: var(--font-size-base);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.result-subtitle {
  font-size: var(--font-size-sm);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ========== MAP ========== */
.map-wrapper {
  position: fixed;
  inset: 0;
  z-index: 1;
}

.map-container {
  width: 100%;
  height: 100%;
}

/* ══════════════════════════════════════════════════════════════════════════
   EL FILTRO NOCTURNO, SOLO SOBRE MAPAS DE LÍNEAS

   REPORTADO POR EL DUEÑO: «la versión satélite se ve como blanco y negro».

   Esta regla convierte un mapa de calles claro en uno nocturno con un truco
   conocido: invertir y desaturar. Sobre líneas y polígonos funciona muy bien.
   Sobre una FOTOGRAFÍA AÉREA es un desastre — `invert(1)` la vuelve un
   negativo y `saturate(0.4)` le quita el 60 % del color, así que el patio de
   una planta, un techo o un campo acaban en gris sucio. Exactamente «blanco y
   negro».

   Y el relieve tiene el mismo problema: el sombreado de las pendientes ES la
   información, y un negativo lo invierte — las montañas parecen valles.

   Así que el filtro se aplica solo cuando la base es de líneas. `map-layers.js`
   escribe `data-base` en el contenedor al cambiar de capa, y de ahí sale la
   condición. Sin ese atributo (mapa recién creado, base por defecto) se aplica
   igual, porque la base por defecto es «calle».
   ══════════════════════════════════════════════════════════════════════════ */
[data-theme="dark"] .leaflet-tile-pane {
  filter: brightness(0.7) contrast(1.1) saturate(0.4) hue-rotate(180deg) invert(1);
}

/* Foto aérea y relieve: se dejan como son. */
[data-theme="dark"][data-base="satelite"] .leaflet-tile-pane,
[data-theme="dark"][data-base="topo"] .leaflet-tile-pane,
[data-theme="dark"] [data-base="satelite"] .leaflet-tile-pane,
[data-theme="dark"] [data-base="topo"] .leaflet-tile-pane {
  filter: none;
}

[data-theme="dark"] .leaflet-control-zoom a {
  background: var(--bg-surface);
  color: var(--text);
  border-color: var(--border);
}

/* Hide default Leaflet controls — we use custom */
.leaflet-control-zoom {
  display: none !important;
}

.leaflet-control-attribution {
  background: var(--glass) !important;
  color: var(--text-muted) !important;
  font-size: calc(10px * var(--ir-letra, 1)) !important;
  padding: 2px 6px !important;
  border-radius: 4px 0 0 0 !important;
}

.leaflet-control-attribution a {
  color: var(--text-muted) !important;
}

/* ========== MAP CONTROLS (right side) ========== */
.map-controls {
  position: fixed;
  right: 12px;
  bottom: calc(var(--nav-height) + var(--safe-bottom) + 180px);
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 900;
}

.map-ctrl-btn {
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--glass);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--glass-border);
  border-radius: 50%;
  color: var(--text);
  box-shadow: 0 2px 12px var(--shadow);
  transition: all var(--duration) var(--ease);
}

.map-ctrl-btn:hover {
  background: var(--accent);
  color: var(--navy);
}

.map-ctrl-btn:active {
  transform: scale(0.92);
}

.map-ctrl-btn.active {
  background: var(--accent);
  color: var(--navy);
}

/* ========== FLOATING ACTION BUTTON ========== */
.fab {
  position: fixed;
  right: 16px;
  bottom: calc(var(--nav-height) + var(--safe-bottom) + 100px);
  width: auto;
  height: 52px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 18px 0 14px;
  background: var(--accent);
  color: var(--navy);
  border-radius: 26px;
  font-weight: 700;
  font-size: var(--font-size-sm);
  box-shadow: 0 4px 20px rgba(212, 168, 75, 0.4);
  z-index: 950;
  transition: all var(--duration) var(--ease-spring);
}

.fab:hover {
  transform: scale(1.05);
  box-shadow: 0 6px 28px rgba(212, 168, 75, 0.5);
}

.fab:active {
  transform: scale(0.95);
}

.fab-label {
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

/* ========== QUICK ACCESS TILES ========== */
.quick-tiles {
  position: fixed;
  bottom: calc(var(--nav-height) + var(--safe-bottom) + 8px);
  left: 0;
  right: 0;
  z-index: 800;
  padding: 0 12px;
}

.tiles-scroll {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 4px 0;
}

.tiles-scroll::-webkit-scrollbar {
  display: none;
}

.tile {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 12px 14px;
  background: var(--glass);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  min-width: 80px;
  scroll-snap-align: start;
  transition: all var(--duration) var(--ease);
}

.tile:hover {
  background: var(--accent-dim);
  border-color: var(--accent);
}

.tile:active {
  transform: scale(0.95);
}

.tile-icon {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-dim);
  border-radius: 50%;
  color: var(--accent);
}

.tile-label {
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

/* ========== BOTTOM NAVIGATION ========== */
.bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: calc(var(--nav-height) + var(--safe-bottom));
  padding-bottom: var(--safe-bottom);
  display: flex;
  align-items: center;
  justify-content: space-around;
  background: var(--glass);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border-top: 1px solid var(--glass-border);
  z-index: 1000;
}

.nav-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 4px;
  min-height: var(--touch);
  justify-content: center;
  color: var(--text-muted);
  transition: color var(--duration) var(--ease);
  position: relative;
}

.nav-tab span {
  font-size: var(--font-size-xs);
  font-weight: 500;
  letter-spacing: 0.3px;
}

.nav-tab.active {
  color: var(--accent);
}

.nav-tab.active::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 24px;
  height: 3px;
  background: var(--accent);
  border-radius: 0 0 3px 3px;
}

.nav-badge {
  position: absolute;
  top: 4px;
  right: calc(50% - 18px);
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  background: var(--danger);
  color: #fff;
  font-size: calc(10px * var(--ir-letra, 1));
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  border-radius: 9px;
}

/* ========== BOTTOM SHEET (Panels) ========== */
.bottom-sheet {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  max-height: 85vh;
  background: var(--bg-surface);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: 0 -8px 40px var(--shadow);
  z-index: 1100;
  transform: translateY(100%);
  transition: transform var(--duration-slow) var(--ease);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.bottom-sheet.open {
  transform: translateY(0);
}

.bottom-sheet[aria-hidden="true"] {
  pointer-events: none;
}

.bottom-sheet[aria-hidden="false"] {
  pointer-events: auto;
}

.sheet-handle {
  display: flex;
  justify-content: center;
  padding: 12px 0 4px;
  cursor: grab;
  flex-shrink: 0;
}

.handle-bar {
  width: 36px;
  height: 5px;
  background: var(--text-muted);
  border-radius: 3px;
  opacity: 0.4;
}

.sheet-content {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 8px 20px calc(var(--nav-height) + var(--safe-bottom) + 16px);
}

.sheet-title {
  font-family: var(--font-heading);
  font-size: var(--font-size-xl);
  font-weight: 700;
  margin-bottom: 20px;
  color: var(--text);
}

/* ========== TRIP PLANNER ========== */
.trip-inputs {
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: relative;
  margin-bottom: 16px;
}

.input-group {
  position: relative;
}

.input-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 6px;
}

.input-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}

.origin-dot { background: var(--accent); }
.dest-dot { background: var(--danger); }

.trip-input {
  padding-left: 32px;
}

.swap-btn {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--text-secondary);
  z-index: 5;
  transition: all var(--duration) var(--ease);
}

.swap-btn:hover {
  background: var(--accent-dim);
  color: var(--accent);
}

/* Vehicle Config */
.vehicle-config {
  margin-bottom: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.vehicle-config summary {
  padding: 14px 16px;
  font-weight: 600;
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--touch);
}

.vehicle-config summary::-webkit-details-marker { display: none; }
.vehicle-config summary::marker { content: ''; }

.vehicle-config summary::before {
  content: '\25B6';
  font-size: calc(10px * var(--ir-letra, 1));
  transition: transform var(--duration) var(--ease);
}

.vehicle-config[open] summary::before {
  transform: rotate(90deg);
}

.config-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  padding: 0 16px 16px;
}

.config-item label {
  display: block;
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 4px;
}

.config-item input,
.config-item select {
  font-size: var(--font-size-sm);
  padding: 10px 12px;
}

/* Route Options (toggles) */
.route-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 20px;
}

.toggle-option {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 0;
  cursor: pointer;
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  min-height: var(--touch);
}

.toggle-slider {
  position: relative;
  width: 44px;
  height: 24px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  flex-shrink: 0;
  transition: all var(--duration) var(--ease);
}

.toggle-slider::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  background: var(--text-muted);
  border-radius: 50%;
  transition: all var(--duration) var(--ease);
}

.toggle-option input {
  display: none;
}

.toggle-option input:checked + .toggle-slider {
  background: var(--accent-dim);
  border-color: var(--accent);
}

.toggle-option input:checked + .toggle-slider::after {
  left: 22px;
  background: var(--accent);
}

/* Primary Button */
.btn-primary {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 14px 24px;
  background: var(--accent);
  color: var(--navy);
  font-weight: 700;
  font-size: var(--font-size-base);
  border-radius: var(--radius);
  min-height: var(--touch);
  transition: all var(--duration) var(--ease);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.btn-primary:hover {
  background: var(--accent-hover);
  box-shadow: 0 4px 16px rgba(212, 168, 75, 0.3);
}

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

.btn-primary:disabled {
  opacity: 0.5;
  pointer-events: none;
}

/* Route Results */
.route-results {
  margin-top: 20px;
}

.route-results[hidden] { display: none; }

.route-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 12px;
  transition: all var(--duration) var(--ease);
}

.route-card:hover,
.route-card.selected {
  border-color: var(--accent);
  background: var(--accent-dim);
}

.route-card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.route-card-title {
  font-size: var(--font-size-lg);
  font-weight: 700;
}

.route-card-badge {
  padding: 4px 10px;
  background: var(--accent-dim);
  color: var(--accent);
  font-size: var(--font-size-xs);
  font-weight: 700;
  border-radius: 20px;
  text-transform: uppercase;
}

.route-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}

.route-stat {
  text-align: center;
}

.route-stat-value {
  font-size: var(--font-size-lg);
  font-weight: 700;
  color: var(--text);
}

.route-stat-label {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.3px;
}

/* ========== ALERTS ========== */
.alerts-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.alert-card {
  display: flex;
  gap: 12px;
  padding: 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  align-items: flex-start;
}

.alert-icon {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  flex-shrink: 0;
}

.alert-icon.danger { background: var(--danger-dim); color: var(--danger); }
.alert-icon.warning { background: var(--gold-dim); color: var(--gold); }
.alert-icon.info { background: var(--accent-dim); color: var(--accent); }

.alert-content { flex: 1; }

.alert-title {
  font-weight: 600;
  font-size: var(--font-size-sm);
  margin-bottom: 2px;
}

.alert-desc {
  font-size: var(--font-size-sm);
  color: var(--text-muted);
}

/* ========== SETTINGS ========== */
.settings-section {
  margin-bottom: 24px;
}

.settings-heading {
  font-size: var(--font-size-sm);
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 12px;
}

.theme-picker {
  display: flex;
  gap: 8px;
}

.theme-option {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px 8px;
  background: var(--bg-elevated);
  border: 2px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--text-secondary);
  min-height: var(--touch);
  transition: all var(--duration) var(--ease);
}

.theme-option:hover {
  border-color: var(--text-muted);
}

.theme-option.active {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-dim);
}

.lang-toggle {
  display: flex;
  gap: 0;
  background: var(--bg-elevated);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  overflow: hidden;
}

.lang-option {
  flex: 1;
  padding: 12px 16px;
  font-size: var(--font-size-sm);
  font-weight: 600;
  text-align: center;
  color: var(--text-secondary);
  min-height: var(--touch);
  transition: all var(--duration) var(--ease);
}

.lang-option.active {
  background: var(--accent);
  color: var(--navy);
}

.profile-summary {
  padding: 14px;
  background: var(--bg-elevated);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-sm);
  color: var(--text-muted);
}

.about-info {
  padding: 14px;
  background: var(--bg-elevated);
  border-radius: var(--radius-sm);
}

.app-version {
  font-size: var(--font-size-sm);
  font-weight: 600;
  color: var(--text);
  margin-bottom: 4px;
}

.app-company {
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  margin-bottom: 2px;
}

.app-tagline {
  font-size: var(--font-size-sm);
  color: var(--text-muted);
  font-style: italic;
}

/* ========== ROUTES LIST ========== */
.routes-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ========== EMPTY STATE ========== */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 40px 20px;
  text-align: center;
}

.empty-state p {
  font-size: var(--font-size-sm);
  color: var(--text-muted);
  max-width: 240px;
  line-height: 1.6;
}

/* ========== SIDE MENU ========== */
.menu-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1199;
  opacity: 0;
  visibility: hidden;
  transition: all var(--duration-slow) var(--ease);
}

.menu-overlay.open {
  opacity: 1;
  visibility: visible;
}

.side-menu {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: 280px;
  max-width: 85vw;
  background: var(--bg-surface);
  z-index: 1200;
  transform: translateX(-100%);
  transition: transform var(--duration-slow) var(--ease);
  display: flex;
  flex-direction: column;
  padding-top: var(--safe-top);
}

.side-menu.open {
  transform: translateX(0);
}

.menu-header {
  padding: 24px 20px;
  border-bottom: 1px solid var(--border);
}

.menu-brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

.menu-app-name {
  font-family: var(--font-heading);
  font-size: var(--font-size-lg);
  font-weight: 700;
}

.menu-tagline {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
}

.menu-list {
  flex: 1;
  overflow-y: auto;
  padding: 8px 0;
}

.menu-item {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: 14px 20px;
  font-size: var(--font-size-base);
  font-weight: 500;
  color: var(--text-secondary);
  min-height: var(--touch);
  transition: all var(--duration) var(--ease);
}

.menu-item:hover {
  background: var(--accent-dim);
  color: var(--accent);
}

.menu-divider {
  height: 1px;
  background: var(--border);
  margin: 8px 20px;
}

.menu-footer {
  padding: 16px 20px;
  border-top: 1px solid var(--border);
}

.menu-version {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
}

/* ========== LOADING OVERLAY ========== */
.loading-overlay {
  position: fixed;
  inset: 0;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  transition: opacity 0.6s var(--ease);
}

.loading-overlay.hidden {
  opacity: 0;
  pointer-events: none;
}

.loading-spinner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}

.spinner-ring {
  width: 44px;
  height: 44px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

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

.loading-text {
  font-size: var(--font-size-sm);
  color: var(--text-muted);
  font-weight: 500;
}

/* ========== TOAST NOTIFICATIONS ========== */
.toast-container {
  position: fixed;
  top: calc(var(--safe-top) + var(--search-height) + 16px);
  left: 16px;
  right: 16px;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 32px var(--shadow);
  pointer-events: auto;
  animation: toastIn 0.3s var(--ease-spring);
}

.toast.removing {
  animation: toastOut 0.25s var(--ease) forwards;
}

@keyframes toastIn {
  from { opacity: 0; transform: translateY(-12px) scale(0.96); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes toastOut {
  to { opacity: 0; transform: translateY(-8px) scale(0.96); }
}

.toast-icon {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
}

.toast-message {
  flex: 1;
  font-size: var(--font-size-sm);
  font-weight: 500;
}

.toast.success { border-left: 3px solid var(--success); }
.toast.error { border-left: 3px solid var(--danger); }
.toast.warning { border-left: 3px solid var(--warning); }
.toast.info { border-left: 3px solid var(--info); }

/* ========== TRUCK RESTRICTION MARKERS ========== */
.truck-restriction-marker {
  background: var(--danger);
  color: #fff;
  border-radius: 50%;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 700;
  border: 2px solid #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

/* ========== ROUTE POLYLINE STYLING (via Leaflet classes) ========== */
.route-line {
  stroke: var(--accent);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.route-line-alt {
  stroke: var(--text-muted);
  stroke-width: 4;
  stroke-opacity: 0.5;
  stroke-dasharray: 8, 6;
}

/* ========== RESPONSIVE ========== */

/* Tablet+ */
@media (min-width: 768px) {
  .search-header {
    max-width: 480px;
    left: 50%;
    transform: translateX(-50%);
  }

  .search-results {
    max-width: 480px;
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    width: calc(100vw - 24px);
  }

  .bottom-sheet {
    max-width: 420px;
    left: 50%;
    transform: translate(-50%, calc(100% + 14px));
    border-radius: var(--radius-lg);
    bottom: 8px;
    box-shadow: 0 -4px 40px var(--shadow);
  }

  .bottom-sheet.open {
    transform: translate(-50%, 0);
  }

  .side-menu {
    width: 320px;
  }

  .quick-tiles {
    max-width: 480px;
    left: 50%;
    transform: translateX(-50%);
  }

  .fab {
    right: calc(50% - 260px);
  }

  .map-controls {
    right: calc(50% - 260px);
  }
}

/* Desktop */
@media (min-width: 1024px) {
  .bottom-nav {
    max-width: 480px;
    left: 50%;
    transform: translateX(-50%);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }
}

/* Landscape phone */
@media (max-height: 500px) and (orientation: landscape) {
  .quick-tiles {
    bottom: calc(var(--nav-height) + 4px);
  }

  .tile {
    padding: 8px 12px;
    min-width: 70px;
  }

  .tile-icon {
    width: 32px;
    height: 32px;
  }

  .fab {
    bottom: calc(var(--nav-height) + 60px);
    height: 44px;
  }

  .map-controls {
    bottom: calc(var(--nav-height) + 120px);
  }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Dark mode system preference (auto theme) */
@media (prefers-color-scheme: light) {
  [data-theme="auto"] {
    --bg: #f5f7fa;
    --bg-surface: #ffffff;
    --bg-elevated: #f0f2f5;
    --bg-card: #ffffff;
    --text: #1a1a2e;
    --text-secondary: #555770;
    --text-muted: #8e8ea0;
    --border: rgba(0, 0, 0, 0.08);
    --border-strong: rgba(0, 0, 0, 0.15);
    --shadow: rgba(0, 0, 0, 0.1);
    --glass: rgba(245, 247, 250, 0.9);
    --glass-border: rgba(0, 0, 0, 0.06);
  }

  [data-theme="auto"] .leaflet-tile-pane {
    filter: none;
  }
}

/* === BRAND OVERRIDES === */
/* Headline / display surfaces — enforce Syne everywhere a heading lives,
   in case markup uses semantic h1..h4 directly without our utility classes. */
h1, h2, h3, h4,
.sheet-title,
.menu-app-name,
.route-card-title,
.app-version {
  font-family: var(--font-heading);
  font-weight: 700;
  letter-spacing: -0.01em;
}

/* Gold focus ring (a11y) — overrides UA default and matches tokens.css */
:where(button, a, input, select, textarea):focus-visible {
  outline: 2px solid var(--color-gold-400);
  outline-offset: 2px;
}

/* Selection */
::selection {
  background: var(--color-gold-500);
  color: var(--color-text-on-gold);
}

/* Scrollbar (WebKit) — subtle gold-tinted to match brand */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: var(--color-border-strong);
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--color-gold-600);
}

/* Re-skinned 2026-05-02 by Cowork sub-agent */

/* === v2.5 NEW MODULE STYLES (stubs from sub-agent ports) === */
.voice-interim { color: var(--color-text-muted); font-style: italic; }
.limit-no-data { color: var(--color-text-muted); }
.hud-hid
/* ── ICONOS EN LÍNEA CON SU RÓTULO ─────────────────────────────────────────
   `IronIconos.marca()` devuelve el dibujo en cadena para los paneles que se
   pintan con `innerHTML`. Un `<svg>` es en línea y se apoya en la línea base,
   así que sin esto queda 3 px por debajo del texto que lleva al lado — el
   defecto clásico de «el icono está bajito». */
.ir-ic-pre {
  display: inline-block;
  vertical-align: -0.22em;
  margin-right: .4em;
  flex: 0 0 auto;
}

/* ── LA CHINCHETA DEL BUSCADOR ─────────────────────────────────────────────
   Era el emoji `📍` a 28 px: lo dibujaba el teléfono, y en iOS es un alfiler
   rojo brillante que no se parece a nada del producto. Ahora es el trazo de la
   casa sobre un disco con contorno — el disco no es adorno: sobre satélite un
   trazo claro desaparece, y sobre calle uno oscuro también. Con fondo propio
   se ve en las dos. */
.ir-pin-disco {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 18px;
  background: var(--accent-fill, #D4A84B);
  color: var(--accent-ink, #030D1C);
  border: 2px solid #FFFFFF;
  box-shadow: 0 3px 8px rgba(0, 0, 0, .45);
}
html[data-theme="light"] .ir-pin-disco { border-color: #0A1B33; }
.ir-pin-marker { background: none; border: none; }

/* Los iconos de la lista de sugerencias, alineados con su renglón. */
.ir-dd-icon { display: flex; align-items: center; flex: 0 0 auto; }
.ir-dd-icon .ir-ic-pre { margin-right: 0; vertical-align: 0; }

/* «Guardado» se distingue por el color del trazo, no por un dibujo distinto:
   con estrella llena y estrella hueca del mismo trazo la diferencia a 18 px
   es un pelo, y esto se mira de reojo. */
.ir-pc-btn.ir-pc-guardado { color: var(--accent, #D4A84B); }
.ir-pc-btn.ir-pc-guardado .ir-ic { fill: var(--accent-fill, #D4A84B); }

/* ───── css/onboarding.css ───── */
/* ════════════════════════════════════════════════════════════════════════
 * IronRoute GPS — Onboarding (REBUILD v3.0)
 * Sticky header + 8 progress dots + sticky footer + responsive layout.
 * Palette: navy #030D1C  ·  gold #D4A84B  ·  cream #F4F1EA
 * Typography: Syne (display) · DM Sans (body)
 * ════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   LOS TOKENS DEL ALTA — el pigmento por un lado, el papel que hace por otro

   EL DEFECTO QUE ARREGLA ESTE BLOQUE, tal como se vio en el iPhone del dueño:
   el alta se abre en TEMA CLARO. Lo decide `index.html` en su primer script
   —sin preferencia guardada, claro— así que el tema claro es lo que ve TODO
   el que entra por primera vez. Y esta hoja estaba escrita entera con nombres
   de PIGMENTO: `--ob-cream` quiere decir «crema» y `--ob-navy` «azul marino».

   Un nombre de pigmento no puede cambiar con el tema; si cambiara, mentiría.
   Y como no cambia, en claro el rótulo «Nombre completo» se seguía pintando
   crema `#F4F1EA` sobre la tarjeta blanca: 1,05:1. Invisible. Igual «Email»,
   «Contraseña», «Vehículos livianos (4)», «Año», «Marca», «Modelo», «VIN»,
   «Placa», «PASO 4 DE 9», «Saltar», «O CONTINUAR CON» y «Continuar como
   invitado». No eran diez fallos de diez pantallas: era UN fallo, repetido
   179 veces según el medidor.

   LA SEPARACIÓN, que es lo único que hay que respetar aquí:
     · PIGMENTO — `--ob-navy`, `--ob-cream`, `--ob-gold`. Es un color y punto.
       NO cambia con el tema. Solo sirve para definir un rol.
     · ROL — `--ob-texto`, `--ob-lienzo`, `--ob-marca`… Dice PARA QUÉ sirve el
       color. Estos sí cambian con el tema, y cambian en un solo sitio:
       `css/tema-claro.css`.

   Regla para el siguiente que escriba aquí: en `color:`, `background:` o
   `border-color:` va SIEMPRE un rol. Si escribes un pigmento, acabas de
   volver a crear el fallo de arriba.

   POR QUÉ NO SE ARREGLÓ CON UNA HOJA MÁS: ya hay ocho `onboarding*.css`. Una
   novena a base de `!important` habría tapado los rótulos que el dueño mandó
   en las capturas y habría dejado los otros ciento y pico esperando a la
   siguiente captura.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
  /* ── PIGMENTOS: un color, sin decir para qué ────────────────────── */
  --ob-navy:        #030D1C;
  --ob-navy-2:      #0A1A2E;
  --ob-navy-3:      #16263F;
  --ob-gold:        #D4A84B;
  --ob-gold-2:      #B8902F;
  --ob-gold-3:      #E2B656;
  --ob-cream:       #F4F1EA;

  /* ── ROLES: para qué sirve, sin decir de qué color ────────────────
     Los valores de aquí son los del tema OSCURO, que es el que ya estaba
     bien. El tema claro los reescribe en `css/tema-claro.css`. */
  --ob-lienzo:      var(--ob-navy);        /* el fondo de todo el alta */
  --ob-fondo-1:     #0E2A4D;               /* el halo del fondo, arriba */
  --ob-fondo-2:     #000814;               /* y el borde del fondo, abajo */
  --ob-tarjeta:     var(--ob-navy-2);      /* la tarjeta, arriba */
  --ob-tarjeta-2:   var(--ob-navy);        /* la tarjeta, abajo */
  --ob-barra:       rgba(3,13,28,.92);     /* cabecera y pie, con desenfoque */
  --ob-campo:       var(--ob-navy-3);      /* campos, fichas, chips */
  --ob-campo-2:     var(--ob-navy-2);      /* los mismos, al pasar o al foco */

  --ob-texto:       var(--ob-cream);       /* lo que hay que leer */
  --ob-texto-2:     rgba(244,241,234,.72); /* lo secundario, aún legible */
  --ob-velo:        rgba(244,241,234,.10); /* relleno suave sobre el lienzo */
  --ob-linea:       rgba(244,241,234,.18); /* bordes y separadores */

  /* El dorado tiene DOS papeles y hacen falta dos tokens, porque sobre
     blanco el dorado de marca da 2,1:1 y no se puede leer:
       · como TEXTO o LÍNEA (iconos, enlaces, bordes, foco, puntos del paso)
         hay que oscurecerlo en claro → `--ob-marca`
       · como RELLENO grande, con tinta oscura encima, el dorado de marca es
         el bueno y no se toca → `--ob-marca-relleno`
     Es la misma separación que `css/tema-claro.css` ya hace para el resto de
     la aplicación; aquí no se hacía. */
  --ob-marca:           var(--ob-gold);
  --ob-marca-borde:     rgba(212,168,75,.4);
  --ob-marca-relleno:   var(--ob-gold);
  --ob-marca-relleno-0: var(--ob-gold-3);  /* la luz de arriba del degradado */
  --ob-marca-relleno-2: var(--ob-gold-2);  /* y la sombra de abajo */
  --ob-marca-tinte:     rgba(212,168,75,.10);
  --ob-marca-tinte-2:   rgba(212,168,75,.16);
  --ob-sobre-marca:     var(--ob-navy);    /* la tinta ENCIMA del relleno
                                              dorado: 8,8:1. Es la misma en
                                              los dos temas porque el relleno
                                              es el mismo en los dos temas. */

  /* El relleno del botón primario cuando todavía no se puede pulsar. Es un
     dorado desaturado, NO el dorado a media opacidad: con opacidad la tinta
     se apagaba junto con el fondo y «Continuar» quedaba en 1,45:1. Con estos
     dos el fondo se apaga y la tinta oscura sigue dando 5,4:1 y 6,5:1. */
  --ob-relleno-apagado-0: #AC9251;
  --ob-relleno-apagado:   #9C8447;

  --ob-realce:       var(--ob-cream);      /* la píldora seleccionada */
  --ob-sobre-realce: var(--ob-navy);       /* y su tinta */
  --ob-perilla:      var(--ob-cream);      /* la bolita del interruptor */
  --ob-carril:       rgba(244,241,234,.18);/* y su carril apagado */

  --ob-exito:       #7ED957;
  --ob-exito-2:     #4FA82B;
  --ob-sobre-exito: var(--ob-navy);
  --ob-aviso:       #F4A93A;
  --ob-aviso-2:     #F4D03A;
  /* Subido desde #E5484D: sobre el pie oscuro daba 4,95:1 rozando el suelo, y
     encima `onboarding-step4-fix.css` lo pisaba con #E11D48 (4,12:1). El
     mensaje de «faltan datos» es justo el que hay que poder leer. */
  --ob-peligro:     #FF7A80;

  /* Los nombres viejos siguen vivos porque los leen otras hojas del alta
     (`onboarding-p0-fixes.css`, `onboarding-cabe-siempre.css`). Apuntan al
     ROL, no al pigmento, para que hereden el tema. */
  --ob-cream-dim:   var(--ob-texto-2);
  --ob-cream-faint: var(--ob-velo);
  --ob-cream-line:  var(--ob-linea);
  --ob-success:     var(--ob-exito);
  --ob-warn:        var(--ob-aviso);
  --ob-danger:      var(--ob-peligro);

  --ob-radius:      14px;
  --ob-radius-sm:   8px;
  --ob-shadow:      0 24px 60px rgba(0,0,0,.45);
  --ob-touch:       48px;
  --ob-step-pad:    24px;
  --ob-easing:      cubic-bezier(.2,.7,.2,1);
  --ob-anim:        .28s var(--ob-easing);
}

/* Lock body scroll while open */
body.ob-locked { overflow: hidden; }

/* Root container */
.ob-root {
  position: fixed; inset: 0; z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  font-family: 'DM Sans','Inter',-apple-system,system-ui,sans-serif;
  color: var(--ob-texto);
  opacity: 0; pointer-events: none;
  transition: opacity var(--ob-anim);
}
.ob-root.ob-show { opacity: 1; pointer-events: auto; }

.ob-backdrop {
  position: absolute; inset: 0;
  background: radial-gradient(circle at 30% 20%, var(--ob-fondo-1) 0%, var(--ob-lienzo) 60%, var(--ob-fondo-2) 100%);
  background-color: var(--ob-lienzo);
}

/* Card */
.ob-card {
  position: relative;
  display: flex; flex-direction: column;
  width: min(720px, 96vw);
  height: min(820px, 92vh);
  background: linear-gradient(180deg, var(--ob-tarjeta) 0%, var(--ob-tarjeta-2) 100%);
  border: 1px solid var(--ob-linea);
  border-radius: var(--ob-radius);
  box-shadow: var(--ob-shadow);
  overflow: hidden;
  transform: translateY(8px) scale(.98);
  transition: transform var(--ob-anim);
}
.ob-root.ob-show .ob-card { transform: translateY(0) scale(1); }
.ob-card:focus { outline: none; }

/* ── HEADER (sticky) ─────────────────────────────────────────────────── */
.ob-header {
  position: sticky; top: 0; z-index: 3;
  display: grid; grid-template-columns: auto 1fr auto; gap: 12px;
  align-items: center;
  padding: 14px 18px 10px 14px;
  background: var(--ob-barra);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--ob-linea);
}
.ob-h-btn {
  width: var(--ob-touch); height: var(--ob-touch);
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid transparent;
  border-radius: 10px; color: var(--ob-texto);
  cursor: pointer; transition: background var(--ob-anim), border-color var(--ob-anim);
}
.ob-h-btn:hover, .ob-h-btn:focus-visible { background: var(--ob-velo); border-color: var(--ob-linea); }
.ob-h-btn:focus-visible { outline: 2px solid var(--ob-marca); outline-offset: 2px; }

.ob-h-mid { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.ob-step-label {
  font-family: 'DM Sans', sans-serif;
  font-size: calc(12px * var(--ir-letra, 1)); font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ob-texto-2); text-align: center;
}
.ob-h-actions { display: flex; align-items: center; gap: 8px; }
.ob-skip {
  height: 36px; padding: 0 14px;
  background: transparent; border: 1px solid var(--ob-linea); border-radius: 999px;
  color: var(--ob-texto); font-size: calc(13px * var(--ir-letra, 1)); cursor: pointer;
  transition: background var(--ob-anim);
}
.ob-skip:hover, .ob-skip:focus-visible { background: var(--ob-velo); }
.ob-skip:focus-visible { outline: 2px solid var(--ob-marca); outline-offset: 2px; }

/* Progress dots */
.ob-dots {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 4px 0 2px 0;
}
.ob-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--ob-linea);
  border: none; cursor: pointer; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ob-sobre-exito);
  transition: background var(--ob-anim), transform var(--ob-anim), width var(--ob-anim);
}
.ob-dot.done { background: var(--ob-exito); }
.ob-dot.done svg { width: 8px; height: 8px; }
.ob-dot.here { width: 22px; border-radius: 999px; background: var(--ob-marca); }
.ob-dot:disabled { cursor: default; }
.ob-dot:focus-visible { outline: 2px solid var(--ob-marca); outline-offset: 3px; }

/* ── BODY (scrollable) ───────────────────────────────────────────────── */
.ob-body {
  flex: 1 1 auto; overflow-y: auto; overflow-x: hidden;
  padding: var(--ob-step-pad);
  scrollbar-width: thin;
  scrollbar-color: var(--ob-linea) transparent;
}
.ob-body::-webkit-scrollbar { width: 8px; }
.ob-body::-webkit-scrollbar-thumb { background: var(--ob-linea); border-radius: 8px; }

/* Typography */
.ob-h1 {
  font-family: 'Syne','DM Sans',sans-serif;
  font-size: clamp(22px, 3.4vw, 30px);
  font-weight: 700; line-height: 1.18;
  margin: 0 0 8px 0; color: var(--ob-texto);
  letter-spacing: -0.01em;
}
.ob-h3 {
  font-family: 'Syne','DM Sans',sans-serif;
  font-size: calc(16px * var(--ir-letra, 1)); font-weight: 600; margin: 18px 0 10px;
  color: var(--ob-marca);
}
.ob-sub {
  font-size: calc(15px * var(--ir-letra, 1)); line-height: 1.5;
  color: var(--ob-texto-2); margin: 0 0 22px 0;
}

/* ── FOOTER (sticky) ─────────────────────────────────────────────────── */
.ob-footer {
  position: sticky; bottom: 0; z-index: 3;
  display: grid; grid-template-columns: auto 1fr auto; gap: 12px;
  align-items: center;
  padding: 14px 18px;
  background: var(--ob-barra);
  backdrop-filter: blur(8px);
  border-top: 1px solid var(--ob-linea);
}
.ob-foot-msg { font-size: calc(12px * var(--ir-letra, 1)); color: var(--ob-aviso); text-align: center; }

/* ── BUTTONS ─────────────────────────────────────────────────────────── */
.ob-btn {
  min-height: var(--ob-touch);
  padding: 10px 18px;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: 'DM Sans', sans-serif;
  font-size: calc(15px * var(--ir-letra, 1)); font-weight: 600;
  border-radius: 999px; border: 1px solid transparent;
  cursor: pointer; transition: transform var(--ob-anim), background var(--ob-anim), color var(--ob-anim);
}
.ob-btn:focus-visible { outline: 2px solid var(--ob-marca); outline-offset: 2px; }
/* Un `opacity: .55` sobre TODO el botón apagaba también la letra: medido,
   «Continuar» deshabilitado daba 1,45:1 en oscuro y 1,58:1 en claro. Un botón
   deshabilitado tiene que seguir diciendo QUÉ botón es — si no, la persona no
   sabe qué le falta por rellenar. Lo que se apaga es el RELLENO (en
   `onboarding-p0-fixes.css`), no la tinta.
   WCAG deja fuera del suelo a los controles deshabilitados; el dueño no. */
.ob-btn:disabled { cursor: not-allowed; transform: none !important; }
.ob-btn-secondary:disabled,
.ob-btn-ghost:disabled { color: var(--ob-texto-2); border-color: var(--ob-linea); }
.ob-btn-lg { min-height: 56px; font-size: calc(16px * var(--ir-letra, 1)); padding: 0 26px; }
.ob-btn-primary {
  background: linear-gradient(180deg, var(--ob-marca-relleno-0), var(--ob-marca-relleno) 60%, var(--ob-marca-relleno-2));
  color: var(--ob-sobre-marca);
  box-shadow: 0 6px 20px rgba(212,168,75,.28), inset 0 1px 0 rgba(255,255,255,.35);
}
.ob-btn-primary:hover:not(:disabled) { transform: translateY(-1px); }
.ob-btn-secondary {
  background: transparent; color: var(--ob-texto);
  border-color: var(--ob-linea);
}
.ob-btn-secondary:hover:not(:disabled) { background: var(--ob-velo); }
.ob-btn-ghost {
  background: transparent; color: var(--ob-texto-2);
  border-color: transparent; text-decoration: underline; text-underline-offset: 4px;
}
.ob-btn-ghost:hover:not(:disabled) { color: var(--ob-texto); }

.ob-btn-oauth {
  background: var(--ob-realce); color: var(--ob-sobre-realce);
  width: 100%; justify-content: flex-start; padding-left: 16px;
}
.ob-btn-oauth.ob-apple { background: #000; color: #fff; border: 1px solid #fff2; }
.ob-btn-oauth.ob-google { background: #fff; color: #1f1f1f; }
.ob-btn-oauth:hover:not(:disabled) { transform: translateY(-1px); }

/* ── CTA row (welcome + done) ────────────────────────────────────────── */
.ob-cta-row {
  display: flex; flex-direction: column; gap: 10px; align-items: stretch;
  margin-top: 28px;
}

/* ── WELCOME ─────────────────────────────────────────────────────────── */
.ob-welcome { text-align: center; padding-top: 8px; }
.ob-lang-pick {
  position: absolute; top: 14px; right: 18px;
  display: flex; gap: 6px;
}
.ob-lang {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  /* 48, no 44: el medidor lo cazo en 44x44 el 20-sep-2026 y el piso de la casa
     son 48 px. Un chip de idioma es lo PRIMERO que toca alguien que no lee
     ingles; fallarlo deja a esa persona en el idioma equivocado. */
  height: 48px; min-width: 48px; padding: 0 12px;
  background: transparent; color: var(--ob-texto-2);
  border: 1px solid var(--ob-linea); border-radius: 999px;
  font-size: calc(12px * var(--ir-letra, 1)); cursor: pointer;
}
.ob-lang.on { color: var(--ob-sobre-realce); background: var(--ob-realce); border-color: transparent; }

.ob-logo-wrap { padding: 28px 0 14px 0; }
.ob-logo {
  width: 84px; height: 84px; object-fit: contain;
  filter: drop-shadow(0 6px 12px rgba(0,0,0,.4));
  animation: obFade .6s var(--ob-easing);
}
@keyframes obFade { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

.ob-bullets {
  list-style: none; padding: 0; margin: 22px auto 8px;
  max-width: 420px; text-align: left;
}
.ob-bullets li {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; margin-bottom: 8px;
  background: var(--ob-velo);
  border: 1px solid var(--ob-linea);
  border-radius: var(--ob-radius-sm);
  color: var(--ob-texto);
  font-size: calc(14px * var(--ir-letra, 1));
}
.ob-bullets li svg { color: var(--ob-marca); flex: 0 0 auto; }

/* ── LOGIN ───────────────────────────────────────────────────────────── */
.ob-tabs {
  display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
  background: var(--ob-velo); border-radius: 999px; padding: 4px;
  margin-bottom: 18px;
}
.ob-tabs-4 { grid-template-columns: repeat(4, 1fr); }
.ob-tab {
  height: 40px;
  background: transparent; color: var(--ob-texto-2);
  border: none; border-radius: 999px;
  font-size: calc(14px * var(--ir-letra, 1)); font-weight: 600; cursor: pointer;
}
.ob-tab.on { background: var(--ob-realce); color: var(--ob-sobre-realce); }
.ob-tab:focus-visible { outline: 2px solid var(--ob-marca); outline-offset: 2px; }

.ob-form { display: flex; flex-direction: column; gap: 14px; }
.ob-field { display: flex; flex-direction: column; gap: 6px; }
/* El rótulo del campo: «Nombre completo», «Email», «Contraseña», «Año»,
   «Marca», «Modelo», «VIN (últimos 8)», «Placa». Es exactamente lo que el
   dueño mandó en las capturas sin poder leerlo. */
.ob-field > span {
  font-size: calc(13px * var(--ir-letra, 1)); color: var(--ob-texto-2);
}
.ob-field input,
.ob-field select,
.ob-field-inline input {
  height: var(--ob-touch);
  padding: 0 14px;
  background: var(--ob-campo); color: var(--ob-texto);
  border: 1px solid var(--ob-linea); border-radius: 10px;
  font-size: calc(15px * var(--ir-letra, 1)); font-family: inherit;
  transition: border-color var(--ob-anim), background var(--ob-anim);
}
.ob-field input:focus,
.ob-field select:focus,
.ob-field-inline input:focus {
  outline: none; border-color: var(--ob-marca); background: var(--ob-campo-2);
}
.ob-field small[data-err] { color: var(--ob-peligro); font-size: calc(12px * var(--ir-letra, 1)); min-height: 14px; }

.ob-input-wrap { position: relative; }
.ob-input-wrap input { width: 100%; padding-right: 48px; }
.ob-eye {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px;
  background: transparent; border: none; color: var(--ob-texto-2);
  border-radius: 8px; cursor: pointer;
}
.ob-eye:hover { color: var(--ob-texto); background: var(--ob-velo); }

.ob-pwmeter {
  display: grid; grid-template-columns: repeat(4, 1fr) auto; gap: 4px;
  align-items: center; margin-top: -6px;
}
.ob-pwmeter span {
  height: 4px; background: var(--ob-linea); border-radius: 2px;
}
.ob-pwmeter[data-level="1"] span:nth-child(1) { background: var(--ob-peligro); }
.ob-pwmeter[data-level="2"] span:nth-child(-n+2) { background: var(--ob-aviso); }
.ob-pwmeter[data-level="3"] span:nth-child(-n+3) { background: var(--ob-aviso-2); }
.ob-pwmeter[data-level="4"] span { background: var(--ob-exito); }
/* 12 px, no 11: el suelo tipográfico del proyecto. */
.ob-pwmeter i { font-size: calc(12px * var(--ir-letra, 1)); color: var(--ob-texto-2); font-style: normal; padding-left: 8px; }

.ob-or {
  display: flex; align-items: center; gap: 10px;
  color: var(--ob-texto-2); font-size: calc(12px * var(--ir-letra, 1)); text-transform: uppercase; letter-spacing: .12em;
  margin: 6px 0 4px;
}
.ob-or::before, .ob-or::after { content: ''; flex: 1; height: 1px; background: var(--ob-linea); }
.ob-oauth { display: flex; flex-direction: column; gap: 8px; }

.ob-link {
  background: transparent; border: none; color: var(--ob-marca);
  font-size: calc(13px * var(--ir-letra, 1)); cursor: pointer; padding: 4px;
  text-decoration: underline; text-underline-offset: 4px;
}
.ob-link:hover { color: var(--ob-texto); }
/* «(Funciones limitadas)» va en `small`, que es 0,83 em: 10,8 px sobre los 13
   del enlace, por debajo del suelo de 12 px del proyecto. */
.ob-guest { align-self: center; }
.ob-link small { font-size: calc(12px * var(--ir-letra, 1)); }

.ob-checkbox {
  display: inline-flex; align-items: flex-start; gap: 10px;
  font-size: calc(13px * var(--ir-letra, 1)); color: var(--ob-texto); cursor: pointer;
}
.ob-checkbox input[type="checkbox"] {
  width: 22px; height: 22px; flex: 0 0 auto;
  accent-color: var(--ob-marca);
}

/* ── VEHICLE PICKER ──────────────────────────────────────────────────── */
.ob-search { margin-bottom: 16px; }
.ob-search input {
  width: 100%; height: var(--ob-touch);
  padding: 0 14px;
  background: var(--ob-campo); color: var(--ob-texto);
  border: 1px solid var(--ob-linea); border-radius: 10px;
  font-size: calc(15px * var(--ir-letra, 1));
}

.ob-cat { background: transparent; border: 1px solid var(--ob-linea); border-radius: var(--ob-radius-sm); margin-bottom: 12px; }
.ob-cat > summary {
  list-style: none; padding: 14px 16px; cursor: pointer;
  font-family: 'Syne',sans-serif; font-weight: 600; font-size: calc(15px * var(--ir-letra, 1));
  color: var(--ob-texto);
  display: flex; align-items: center; justify-content: space-between;
}
.ob-cat > summary::after {
  content: '+'; color: var(--ob-marca); font-size: calc(22px * var(--ir-letra, 1)); font-weight: 400;
  transition: transform var(--ob-anim);
}
.ob-cat[open] > summary::after { content: '−'; }
.ob-cat > summary small { color: var(--ob-texto-2); font-weight: 400; font-size: calc(12px * var(--ir-letra, 1)); }

.ob-veh-grid {
  display: grid; gap: 8px;
  grid-template-columns: 1fr;
  padding: 0 12px 12px;
}
.ob-veh-card {
  display: grid; grid-template-columns: auto 1fr auto; gap: 12px;
  align-items: center; min-height: 64px; padding: 10px 14px;
  background: var(--ob-campo); color: var(--ob-texto);
  border: 1px solid var(--ob-linea); border-radius: 10px;
  cursor: pointer; text-align: left;
  transition: border-color var(--ob-anim), background var(--ob-anim), transform var(--ob-anim);
}
.ob-veh-card:hover { border-color: var(--ob-marca-borde); background: var(--ob-campo-2); }
.ob-veh-card.on { border-color: var(--ob-marca); background: var(--ob-marca-tinte); }
.ob-veh-card:focus-visible { outline: 2px solid var(--ob-marca); outline-offset: 2px; }
.ob-veh-icon { color: var(--ob-marca); }
.ob-veh-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ob-veh-meta strong { font-size: calc(14px * var(--ir-letra, 1)); }
.ob-veh-meta small { color: var(--ob-texto-2); font-size: calc(12px * var(--ir-letra, 1)); }

/* ── LA FICHA ELEGIDA, QUE SOLO SE VE TOCÁNDOLA ────────────────────────
   Al elegir una ficha, `css/button-states-fix.css` le mete un relleno dorado
   con `!important` — es una regla global para cualquier
   `button[aria-pressed="true"]`, escrita para los botones del mapa. Le fija
   también la tinta del BOTÓN, pero no la del texto de dentro, que tiene su
   propio `color` y se queda en el secundario.

   Medido: «Toyota Camry, Ford Explorer» dentro de la ficha elegida daba
   1,64:1 en oscuro y 2,16:1 en claro sobre el dorado. Es un estado que solo
   aparece después de tocar, o sea el que nadie mira: el medidor del alta
   ahora elige una ficha a propósito para cazarlo.

   Sobre el relleno dorado va la tinta oscura, la misma que en el botón
   primario: 8,8:1 arriba del degradado, 6,6:1 abajo. */
/* La ficha elegida se pinta A SI MISMA. Antes heredaba el relleno dorado de
   una regla global escrita para los interruptores del mapa
   (`button[aria-pressed="true"]` en `css/button-states-fix.css`), que el
   7-sep-2026 se acoto a esos interruptores porque se estaba aplicando a
   cualquier boton de la web. Sin ese relleno, la tinta oscura de aqui abajo
   se quedaba sobre la tarjeta oscura: 1,14:1, medido. Ahora el relleno y la
   tinta viven juntos, que es como tienen que vivir. */
.ob-veh-card[aria-pressed="true"],
.ob-role-card[aria-pressed="true"] {
  background: linear-gradient(135deg, var(--ob-marca-relleno, #D4A84B), #B89438);
  color: var(--ob-sobre-marca);
  border-color: var(--ob-marca-relleno, #D4A84B);
}
.ob-veh-card[aria-pressed="true"] .ob-veh-meta small,
.ob-veh-card[aria-pressed="true"] .ob-veh-icon,
.ob-role-card[aria-pressed="true"] .ob-role-desc,
.ob-role-card[aria-pressed="true"] .ob-role-icon {
  color: var(--ob-sobre-marca);
}
.ob-role-card[aria-pressed="true"] .ob-role-feats li {
  background: rgba(3,13,28,.14);
  color: var(--ob-sobre-marca);
}
.ob-veh-card[aria-pressed="true"] .ob-radio {
  border-color: var(--ob-sobre-marca);
  background: radial-gradient(var(--ob-sobre-marca) 40%, transparent 50%);
}
.ob-radio {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--ob-linea);
}
.ob-veh-card.on .ob-radio {
  border-color: var(--ob-marca);
  background: radial-gradient(var(--ob-marca) 40%, transparent 50%);
}

/* Trailer chips */
.ob-trailer-box { padding-top: 4px; }
.ob-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.ob-chip {
  min-height: var(--ob-touch);
  padding: 0 16px;
  background: var(--ob-campo); color: var(--ob-texto);
  border: 1px solid var(--ob-linea); border-radius: 999px;
  font-size: calc(14px * var(--ir-letra, 1)); cursor: pointer;
  transition: background var(--ob-anim), border-color var(--ob-anim);
}
.ob-chip.on { background: var(--ob-marca-tinte-2); border-color: var(--ob-marca); color: var(--ob-texto); }
.ob-chip:focus-visible { outline: 2px solid var(--ob-marca); outline-offset: 2px; }

/* ── DETAILS / DIMENSIONS ────────────────────────────────────────────── */
.ob-grid-2 {
  display: grid; gap: 14px;
  grid-template-columns: repeat(2, minmax(0,1fr));
}
.ob-fset {
  display: flex; align-items: end; gap: 8px;
  border: 1px solid var(--ob-linea); border-radius: 10px;
  padding: 12px 14px;
}
.ob-fset > legend { padding: 0 6px; font-size: calc(13px * var(--ir-letra, 1)); color: var(--ob-texto-2); }
.ob-field-inline {
  display: flex; align-items: center; gap: 6px; flex: 1;
}
.ob-field-inline input { width: 100%; }
.ob-field-inline span { color: var(--ob-texto-2); font-size: calc(13px * var(--ir-letra, 1)); }

.ob-toggle {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 0; border-top: 1px solid var(--ob-linea);
  margin-top: 12px;
  cursor: pointer;
}
.ob-toggle input { position: absolute; opacity: 0; pointer-events: none; }
.ob-toggle i {
  width: 44px; height: 26px; border-radius: 999px;
  background: var(--ob-carril); position: relative;
  transition: background var(--ob-anim);
}
.ob-toggle i::after {
  content: ''; position: absolute; top: 3px; left: 3px;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--ob-perilla); transition: transform var(--ob-anim);
}
.ob-toggle input:checked + i,
.ob-row-toggle input:checked + .ob-switch { background: var(--ob-marca); }
.ob-toggle input:checked + i::after,
.ob-row-toggle input:checked + .ob-switch::after { transform: translateX(18px); }

.ob-haz-panel {
  margin-top: 14px; padding: 14px;
  background: rgba(244,169,58,.08);
  border: 1px solid rgba(244,169,58,.3);
  border-radius: var(--ob-radius-sm);
}
.ob-warn { color: var(--ob-aviso); font-size: calc(13px * var(--ir-letra, 1)); margin: 8px 0; }
.ob-warn-box { display: flex; flex-direction: column; gap: 4px; margin-top: 18px; }

/* ── PREFS (alerts/avoid/HOS/tolls) ──────────────────────────────────── */
.ob-tab-pane { padding-top: 8px; }
.ob-toggle-list { display: flex; flex-direction: column; gap: 6px; }
.ob-row-toggle {
  display: grid; grid-template-columns: auto 1fr auto auto;
  align-items: center; gap: 12px;
  padding: 10px 4px;
  border-bottom: 1px solid var(--ob-linea);
  cursor: pointer;
}
.ob-row-toggle:last-child { border-bottom: none; }
.ob-row-icon { color: var(--ob-marca); display: inline-flex; }
.ob-row-text { font-size: calc(14px * var(--ir-letra, 1)); color: var(--ob-texto); }
.ob-row-toggle input { position: absolute; opacity: 0; pointer-events: none; }
.ob-switch {
  width: 44px; height: 26px; border-radius: 999px;
  background: var(--ob-carril); position: relative;
  transition: background var(--ob-anim);
}
.ob-switch::after {
  content: ''; position: absolute; top: 3px; left: 3px;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--ob-perilla); transition: transform var(--ob-anim);
}

/* ── ROLE CARDS ─────────────────────────────────────────────────────── */
.ob-role-grid {
  display: grid; gap: 14px;
  grid-template-columns: 1fr;
}
.ob-role-card {
  display: flex; flex-direction: column; gap: 8px;
  padding: 18px;
  background: var(--ob-campo); color: var(--ob-texto);
  border: 1px solid var(--ob-linea); border-radius: var(--ob-radius);
  cursor: pointer; text-align: left;
  transition: border-color var(--ob-anim), background var(--ob-anim), transform var(--ob-anim);
}
.ob-role-card:hover { border-color: var(--ob-marca-borde); transform: translateY(-1px); }
.ob-role-card.on { border-color: var(--ob-marca); background: var(--ob-marca-tinte); }
.ob-role-card:focus-visible { outline: 2px solid var(--ob-marca); outline-offset: 2px; }
.ob-role-icon { color: var(--ob-marca); }
.ob-role-name { font-family: 'Syne',sans-serif; font-size: calc(17px * var(--ir-letra, 1)); }
.ob-role-desc { font-size: calc(13px * var(--ir-letra, 1)); color: var(--ob-texto-2); }
.ob-role-feats {
  list-style: none; padding: 0; margin: 6px 0 0;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.ob-role-feats li {
  font-size: calc(12px * var(--ir-letra, 1));   /* 11 px estaba por debajo del suelo del proyecto */
  padding: 3px 8px; border-radius: 999px;
  background: var(--ob-velo); color: var(--ob-texto);
}

/* ── DONE / SUMMARY ─────────────────────────────────────────────────── */
.ob-done { text-align: center; padding-top: 6px; }
.ob-done-check {
  width: 84px; height: 84px; margin: 4px auto 18px;
  border-radius: 50%;
  background: linear-gradient(180deg, var(--ob-exito), var(--ob-exito-2));
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ob-sobre-exito);
  animation: obPop .55s var(--ob-easing);
  box-shadow: 0 12px 30px rgba(126,217,87,.35);
}
.ob-check-anim svg { width: 44px; height: 44px; }
@keyframes obPop {
  from { transform: scale(.4); opacity: 0; }
  60%  { transform: scale(1.1); opacity: 1; }
  to   { transform: scale(1); opacity: 1; }
}

.ob-summary {
  text-align: left; margin: 16px auto 18px;
  max-width: 520px;
  padding: 14px 16px;
  background: var(--ob-velo);
  border: 1px solid var(--ob-linea);
  border-radius: var(--ob-radius-sm);
}
.ob-summary h3 { margin: 0 0 8px 0; font-family: 'Syne',sans-serif; font-size: calc(14px * var(--ir-letra, 1)); color: var(--ob-marca); }
.ob-summary ul { list-style: none; padding: 0; margin: 0; }
.ob-summary li { font-size: calc(13px * var(--ir-letra, 1)); padding: 4px 0; color: var(--ob-texto); }
.ob-summary strong { color: var(--ob-texto-2); font-weight: 600; margin-right: 6px; }

.ob-disclaimer {
  text-align: left; margin: 0 auto 14px; max-width: 520px;
  padding: 12px; border-radius: var(--ob-radius-sm);
  background: rgba(244,169,58,.06);
  border: 1px solid rgba(244,169,58,.18);
}
.ob-disclaimer span { display: flex; flex-direction: column; gap: 6px; }
.ob-disclaimer small { color: var(--ob-texto-2); font-size: calc(12px * var(--ir-letra, 1)); line-height: 1.5; }
.ob-disclaimer strong { font-size: calc(13px * var(--ir-letra, 1)); color: var(--ob-texto); }

/* ── Modal (resume / forgot) ─────────────────────────────────────────── */
.ob-modal {
  position: fixed; inset: 0; z-index: 10000;
  background: rgba(0,8,20,.74);
  display: flex; align-items: center; justify-content: center;
  animation: obFade .2s var(--ob-easing);
}
.ob-modal-card {
  width: min(420px, 92vw);
  background: var(--ob-tarjeta); color: var(--ob-texto);
  border: 1px solid var(--ob-linea); border-radius: var(--ob-radius);
  padding: 22px; box-shadow: var(--ob-shadow);
}
.ob-modal-card h3 { font-family: 'Syne',sans-serif; margin: 0 0 8px; font-size: calc(18px * var(--ir-letra, 1)); }
.ob-modal-card p { color: var(--ob-texto-2); margin: 0 0 16px; font-size: calc(14px * var(--ir-letra, 1)); }
.ob-modal-foot { display: flex; gap: 10px; justify-content: flex-end; }

/* ── Toast ─────────────────────────────────────────────────────────── */
.ob-toast {
  position: fixed; left: 50%; bottom: 28px;
  transform: translateX(-50%) translateY(20px);
  background: var(--ob-realce); color: var(--ob-sobre-realce);
  padding: 12px 18px; border-radius: 999px;
  box-shadow: var(--ob-shadow); font-size: calc(14px * var(--ir-letra, 1)); font-weight: 600;
  opacity: 0; pointer-events: none; z-index: 10001;
  transition: opacity var(--ob-anim), transform var(--ob-anim);
}
.ob-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }

/* ── RESPONSIVE ─────────────────────────────────────────────────────── */
@media (min-width: 768px) {
  .ob-veh-grid { grid-template-columns: 1fr 1fr; }
  .ob-role-grid { grid-template-columns: 1fr 1fr; }
  .ob-cta-row { flex-direction: row; justify-content: center; }
  .ob-cta-row .ob-btn { flex: 1; max-width: 280px; }
}
@media (min-width: 1024px) {
  .ob-card { width: 760px; height: 820px; }
  .ob-role-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 480px) {
  .ob-card {
    width: 100vw; height: 100vh; max-height: 100vh;
    border-radius: 0; border: none;
  }
  .ob-body { padding: 18px env(safe-area-inset-right) 18px env(safe-area-inset-left); }
  .ob-header { padding-top: max(14px, env(safe-area-inset-top)); }
  .ob-footer { padding-bottom: max(14px, env(safe-area-inset-bottom)); }
  .ob-grid-2 { grid-template-columns: 1fr; }
  .ob-h1 { font-size: calc(22px * var(--ir-letra, 1)); }
  .ob-cta-row { flex-direction: column; }
  .ob-bullets { padding: 0; }
  .ob-skip { font-size: calc(12px * var(--ir-letra, 1)); padding: 0 10px; }
  .ob-step-label { font-size: calc(11px * var(--ir-letra, 1)); }
}

/* ── A11Y: high contrast / reduced motion ───────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .ob-root, .ob-card, .ob-btn, .ob-toast, .ob-dot, .ob-veh-card, .ob-role-card { transition: none !important; animation: none !important; }
}
@media (forced-colors: active) {
  .ob-btn-primary { background: ButtonText; color: ButtonFace; }
  .ob-btn { border-color: ButtonText; }
}

/* Hide visually but keep for screen readers */
.ob-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }

/* ───── css/replatform-f3.css ───── */
/* ═══════════════════════════════════════════════════════════════
   IRON ROUTE GPS — REPLATFORM F3 STYLES
   Multi-route cards · pre-trip banner · contextual menu · autocomplete
   ═══════════════════════════════════════════════════════════════ */

/* ── Multi-route cards · RETIRADO 2026-08-13 ────────────────────
   Aquí vivían 178 líneas para `.ir-mr-*`: las tarjetas de cinco rutas
   alternativas, su cargador, su menú contextual y su editor. Se retiran con
   `js/multi-route-f3.js`, que las pintaba.

   Por qué: NADIE llamaba a `IronMultiRoute.calculate()`, ni a `startTrip`,
   `attachRerouting`, `openVia`, `openAvoid` ni `attachLongPressMenu`. El
   módulo no se auto-arrancaba. Lo único que alguien leía —`bundle-r5.js`—
   eran `getSelected()` y `getAlternatives()`, que sin `calculate()` devuelven
   siempre `null` y `[]`. La interfaz de rutas que se usa de verdad la pinta
   `js/directions.js`.

   O sea: 20.781 bytes de JavaScript y 8 KB de CSS que cada conductor se
   descargaba y analizaba en cada arranque para nada. Y de aquí salía el
   último naranja `#F58220` literal del producto, ya unificado en dorado.
   ────────────────────────────────────────────────────────────── */

/* ── Pre-trip banner (G4.2) ───────────────────────────────── */
.ir-pretrip-banner {
  position: fixed;
  top: max(env(safe-area-inset-top, 0), 70px);
  left: 12px; right: 12px;
  z-index: 940;
  background: rgba(9, 30, 54, 0.94);
  border: 1px solid rgba(245, 130, 32, 0.35);
  border-radius: 12px;
  color: #fff;
  font-family: -apple-system, Inter, system-ui, sans-serif;
  box-shadow: 0 8px 22px rgba(0,0,0,0.3);
  overflow: hidden;
}
.ir-pretrip-banner-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px;
  cursor: pointer;
}
.ir-pretrip-banner-title { font-size: calc(14px * var(--ir-letra, 1)); font-weight: 600; }
.ir-pretrip-banner-counts {
  display: flex; gap: 10px; font-size: calc(13px * var(--ir-letra, 1));
  flex-wrap: wrap;
}
.ir-pretrip-count {
  display: flex; gap: 4px; align-items: center;
  padding: 4px 8px;
  background: rgba(255,255,255,0.06);
  border-radius: 12px;
}
.ir-pretrip-banner-detail {
  padding: 0 14px 12px;
  font-size: calc(12px * var(--ir-letra, 1));
  opacity: 0.9;
  display: none;
}
.ir-pretrip-banner.expanded .ir-pretrip-banner-detail { display: block; }

/* ── Autocomplete dropdown (F3 bridge) ─────────────────────── */
.ir-ac-section {
  font-size: calc(11px * var(--ir-letra, 1));
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: rgba(255,255,255,0.55);
  padding: 8px 12px 4px;
  font-weight: 600;
}
.ir-ac-empty {
  padding: 12px;
  font-size: calc(13px * var(--ir-letra, 1));
  color: rgba(255,255,255,0.7);
  text-align: center;
}
.ir-ac-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  color: #fff;
  text-align: left;
  font-size: calc(14px * var(--ir-letra, 1));
  cursor: pointer;
  min-height: 48px;
  font-family: -apple-system, Inter, system-ui, sans-serif;
}
.ir-ac-item.hl, .ir-ac-item:hover {
  background: rgba(59, 125, 216, 0.18);
}
.ir-ac-icon { font-size: calc(18px * var(--ir-letra, 1)); flex-shrink: 0; }
.ir-ac-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ir-ac-dist { opacity: 0.6; font-size: calc(12px * var(--ir-letra, 1)); }
.ir-ac-fav { color: #facc15; }

/* ── Mobile tweaks ───────────────────────────────────────── */
@media (max-width: 480px) {
  .ir-mr-card { min-width: 150px; }
  .ir-mr-actions { padding: 0; }
}

/* ───── css/state-cleanup-f4.css ───── */
/* ════════════════════════════════════════════════════════════════════════
 * IronRoute GPS — State Cleanup F4 (CEO P0 Fix · 2026-05-03)
 *  G0.1 Light theme defaults · G0.2 HUD hidden no-trip
 *  Onboarding layout fixes (icon overlap)
 * ════════════════════════════════════════════════════════════════════════ */

/* ──────────────── G0.1 LIGHT THEME DEFAULTS ──────────────── */
html[data-theme="light"] {
  --color-bg:           #F4F1EA;
  --color-bg-elev:      #FFFFFF;
  --color-bg-card:      #FFFFFF;
  --color-surface:      #FAF7F0;
  --color-text:         #0A1428;
  --color-text-muted:   #4A5570;
  --color-text-on-gold: #0A1428;
  --color-border:       rgba(10,20,40,0.10);
  --color-border-strong:rgba(10,20,40,0.22);
}

html[data-theme="light"] body {
  background: var(--color-bg);
  color: var(--color-text);
}

html[data-theme="light"] .search-header,
html[data-theme="light"] .bottom-sheet,
html[data-theme="light"] .side-menu,
html[data-theme="light"] .bottom-nav,
html[data-theme="light"] .map-controls .map-ctrl-btn {
  background: var(--color-bg-elev);
  color: var(--color-text);
}
html[data-theme="light"] .search-input { background: var(--color-bg-elev); color: var(--color-text); }
html[data-theme="light"] .search-input::placeholder { color: var(--color-text-muted); }
html[data-theme="light"] .map-ctrl-btn { color: var(--color-text); border: 1px solid var(--color-border); }
html[data-theme="light"] .nav-tab { color: var(--color-text-muted); }
html[data-theme="light"] .nav-tab.active { color: var(--color-accent); }
html[data-theme="light"] .quick-tiles .tile { background: var(--color-bg-elev); color: var(--color-text); border: 1px solid var(--color-border); }
html[data-theme="light"] .tile-icon { color: var(--color-accent); }
html[data-theme="light"] .menu-tagline,
html[data-theme="light"] .app-tagline,
html[data-theme="light"] .menu-app-name { color: var(--color-text); }
html[data-theme="light"] .menu-item { color: var(--color-text); }
html[data-theme="light"] .menu-item:hover { background: rgba(212,168,75,0.10); }
html[data-theme="light"] .sheet-title,
html[data-theme="light"] .settings-heading { color: var(--color-text); }
html[data-theme="light"] .empty-state { color: var(--color-text-muted); }
html[data-theme="light"] .toggle-option,
html[data-theme="light"] .lang-option,
html[data-theme="light"] .theme-option {
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
}
html[data-theme="light"] .theme-option.active,
html[data-theme="light"] .lang-option.active {
  background: var(--color-accent);
  color: var(--color-text-on-gold);
}

/* ──────────────── G0.2 HIDE HUD/BANNER WHEN NO TRIP ──────────────── */
.ir-hidden-no-trip,
html.ir-trip-idle #hud-bottom,
html.ir-trip-idle #trip-hud,
html.ir-trip-idle .trip-hud,
html.ir-trip-idle #nav-banner,
html.ir-trip-idle .nav-banner,
html.ir-trip-idle #turn-banner,
html.ir-trip-idle .turn-by-turn-banner,
html.ir-trip-idle #speed-badge,
html.ir-trip-idle .speed-badge,
html.ir-trip-idle #hos-countdown-strip,
html.ir-trip-idle .hos-countdown-strip,
html.ir-trip-idle #trip-arrival-bar,
html.ir-trip-idle #trip-cost-bar,
html.ir-trip-idle #hud-overlay-bottom,
html.ir-trip-idle .ir-pretrip-banner {
  display: none !important;
  visibility: hidden !important;
  pointer-events: none !important;
}

/* Quick action cards visible always (G0.2 spec) */
html.ir-trip-idle .quick-tiles,
html.ir-trip-idle #quickTiles { display: block !important; }

/* ──────────────── ONBOARDING LAYOUT FIXES ──────────────── */
/* Icon overlap fix — guarantee gap between SVG icon and label text */
.ob-bullets li { gap: 14px !important; align-items: center !important; }
.ob-bullets li svg { flex: 0 0 24px !important; width: 24px !important; height: 24px !important; }
.ob-bullets li > span { flex: 1 1 auto !important; min-width: 0 !important; }
.ob-tile,
.ob-card-row,
.ob-vehicle-card,
.ob-role-card,
.ob-perm-card {
  display: flex !important; align-items: center !important; gap: 14px !important;
}
.ob-tile > svg,
.ob-card-row > svg,
.ob-vehicle-card > svg,
.ob-role-card > svg,
.ob-perm-card > .ob-perm-ico {
  flex: 0 0 32px !important; width: 32px; height: 32px;
}
.ob-tile > .ob-tile-text,
.ob-card-row > .ob-card-row-text,
.ob-vehicle-card > .ob-vehicle-text,
.ob-role-card > .ob-role-text,
.ob-perm-card > .ob-perm-text {
  flex: 1 1 auto; min-width: 0;
}
.ob-tile-text > strong,
.ob-card-row-text > strong { display: block; font-size: calc(15px * var(--ir-letra, 1)); }
.ob-tile-text > small,
.ob-card-row-text > small { display: block; font-size: calc(12px * var(--ir-letra, 1)); opacity: 0.72; }

/* Sticky header / footer guarantees */
.ob-card .ob-header { position: sticky; top: 0; z-index: 5; }
.ob-card .ob-footer { position: sticky; bottom: 0; z-index: 5; }

/* Body padding so content doesn't slide under sticky bars */
.ob-body { padding: 20px 22px 24px 22px !important; }

/* La X, Saltar y Atras: que se puedan tocar.
   ESTA LINEA IMPONIA 44 px CON `!important` — 21-sep-2026.
   Se escribio para SUBIR el objetivo desde algo mas pequeno, y acabo siendo
   lo que lo mantenia por debajo del piso de la casa: `--ob-touch` vale 48 en
   `onboarding.css`, que es lo correcto, y este `!important` le ganaba.
   Alguien ya lo habia visto y lo parcheo en `onboarding-movil.css` con OTRO
   `!important`… dentro de una media query de movil, asi que en tableta y
   escritorio seguia ganando el 44. Tres capas para un numero.
   Se quita el tamano de aqui y manda el token. Es el boton con el que alguien
   sale de una pantalla que no queria ver: fallarlo una vez es quedarse
   dentro, y quedarse dentro suele acabar en cerrar la app. */
.ob-h-btn svg { width: 22px; height: 22px; }
.ob-h-actions { gap: 6px; }
.ob-skip { height: 36px !important; padding: 0 14px !important; }

/* Step label always visible */
.ob-step-label { font-size: calc(12px * var(--ir-letra, 1)); line-height: 1; }

/* ── EL ALTA EN CLARO SE FUE A LOS TOKENS (2026-09-07) ────────────────────
   Aquí había trece reglas `html[data-theme="light"] .ob-…` con los colores
   escritos a mano. Tapaban trece selectores: la tarjeta, el titular, las
   viñetas, las pestañas, los campos, la cabecera y el pie. Y el alta tiene
   muchos más de trece: los rótulos de los campos, «PASO x DE 9», «Saltar»,
   «O CONTINUAR CON», «Continuar como invitado», las cabeceras de categoría de
   vehículo, la lista de alertas, el resumen final… todos ésos seguían
   pintándose crema sobre blanco, y son los que el dueño fotografió.

   Parchear por selector no escala: cada pantalla nueva del alta nace rota y
   se arregla cuando alguien la fotografía. El arreglo está donde estaba la
   causa: `css/onboarding.css` usa tokens de ROL y `css/tema-claro.css` los
   reescribe una sola vez para el tema claro.

   Una de estas reglas, además, hacía daño: `.ob-sub { opacity: .76 }` apagaba
   un color que YA era el apagado, y dejaba el subtítulo en 3,3:1.

   Se quedan aquí las reglas de tamaño y de posición, que no son de color. */

/* ──────────────── PERMISSIONS MODAL ──────────────── */
.ir-perm-modal {
  position: fixed; inset: 0; z-index: 10001;
  display: none; align-items: center; justify-content: center;
  background: rgba(3,13,28,.7); backdrop-filter: blur(4px);
}
.ir-perm-modal[aria-hidden="false"] { display: flex; }
.ir-perm-card {
  width: min(440px, 92vw); padding: 24px;
  background: var(--color-bg-elev, #fff); color: var(--color-text, #0A1428);
  border-radius: 16px; box-shadow: 0 24px 60px rgba(0,0,0,0.5);
}
.ir-perm-icon {
  width: 56px; height: 56px; margin: 0 auto 12px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(212,168,75,0.15); color: #D4A84B;
  border-radius: 50%; font-size: calc(28px * var(--ir-letra, 1));
}
.ir-perm-card h3 {
  font-family: 'Syne', system-ui, sans-serif;
  font-size: calc(20px * var(--ir-letra, 1)); margin: 0 0 8px; text-align: center;
}
.ir-perm-card p { font-size: calc(14px * var(--ir-letra, 1)); line-height: 1.5; margin: 0 0 14px; opacity: .85; }
.ir-perm-card ul { padding: 0 0 0 18px; margin: 0 0 18px; font-size: calc(14px * var(--ir-letra, 1)); line-height: 1.7; }
.ir-perm-card .ir-perm-actions {
  display: flex; gap: 10px;
}
.ir-perm-card .ir-perm-btn {
  flex: 1; min-height: 48px; border-radius: 999px;
  font-size: calc(14px * var(--ir-letra, 1)); font-weight: 600; cursor: pointer; border: 1px solid transparent;
}
.ir-perm-card .ir-perm-btn-primary {
  background: linear-gradient(180deg, #E2B656, #D4A84B);
  color: #0A1428;
}
.ir-perm-card .ir-perm-btn-secondary {
  background: transparent; color: var(--color-text);
  border-color: var(--color-border-strong, rgba(0,0,0,0.18));
}

/* Toast */
.ir-toast {
  position: fixed; left: 50%; bottom: 88px; transform: translateX(-50%);
  z-index: 10002;
  background: rgba(0,0,0,0.86); color: #fff;
  padding: 10px 16px; border-radius: 999px;
  font-size: calc(13px * var(--ir-letra, 1)); opacity: 0; pointer-events: none;
  transition: opacity .3s, transform .3s;
}
.ir-toast.on { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ──────────────── Permissions Dashboard ──────────────── */
.ir-perms-dash {
  display: flex; flex-direction: column; gap: 8px;
  padding: 8px 0;
}
.ir-perms-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  background: var(--color-surface, #FAF7F0);
  border: 1px solid var(--color-border, rgba(0,0,0,.08));
  border-radius: 12px;
}
.ir-perms-row .ir-perms-ic { display: flex; align-items: center; flex: 0 0 28px; }
.ir-perms-row .ir-perms-ic .ir-ic-pre { margin-right: 0; vertical-align: 0; }
.ir-perms-row .ir-perms-text { flex: 1 1 auto; min-width: 0; }
.ir-perms-row .ir-perms-text strong { display: block; font-size: calc(14px * var(--ir-letra, 1)); }
.ir-perms-row .ir-perms-text small { display: block; font-size: calc(12px * var(--ir-letra, 1)); opacity: .72; }
.ir-perms-row .ir-perms-state {
  font-size: calc(12px * var(--ir-letra, 1)); padding: 4px 10px; border-radius: 999px;
  font-weight: 600;
}
.ir-perms-state.granted { background: rgba(46,171,111,.15); color: #2EAB6F; }
.ir-perms-state.denied  { background: rgba(217,83,79,.15);  color: #D9534F; }
.ir-perms-state.deferred,
.ir-perms-state.unset   { background: rgba(232,177,74,.15); color: #B89438; }
.ir-perms-state.coming  { background: rgba(74,159,232,.15); color: #4A9FE8; }
.ir-perms-row .ir-perms-action {
  min-height: 36px; padding: 0 12px; border-radius: 999px;
  border: 1px solid var(--color-border-strong, rgba(0,0,0,.18));
  background: transparent; color: var(--color-text); cursor: pointer;
  font-size: calc(12px * var(--ir-letra, 1));
}
.ir-perms-row .ir-perms-action:hover { background: rgba(212,168,75,0.10); }

/* EL BOTÓN DE VOLVER A HACER EL ONBOARDING.
   Estaba escrito como `style="…"` dentro de un `innerHTML` en
   `js/onboarding-bootstrap-f4.js`, o sea nueve declaraciones de aspecto
   metidas en un archivo de lógica, donde nadie que retoque la hoja de
   Ajustes las va a encontrar. Aquí se ven, y aquí se cambian.
   Los 48 px son la regla de la casa: es un botón que se pulsa conduciendo. */
.ir-rerun-btn {
  min-height: 48px;
  padding: 0 18px;
  width: 100%;
  border-radius: 999px;
  background: transparent;
  border: 1px solid rgba(212, 168, 75, .4);   /* el oro de la casa, al 40 % */
  color: inherit;
  cursor: pointer;
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 600;
}
.ir-rerun-btn:hover,
.ir-rerun-btn:focus-visible { border-color: rgba(212, 168, 75, .8); }

/* EL LETRERO CUANDO EL NÚMERO ES EL DEL CAMIÓN.
   No basta con poner otro número: un 55 donde el letrero de la carretera
   dice 70 parece un fallo nuestro si no se dice de quién es ese 55. El
   rótulo pasa a «LÍMITE CAMIÓN» (lo escribe speed-alerts.js) y el borde se
   pone del naranja de Iron Route para que se note que ese número es tuyo,
   no el de los coches. */
#sw-limit-sign.ir-camion { border-color: var(--accent, #F58220); }
#sw-limit-sign.ir-camion #sw-limit-label {
  color: var(--accent, #F58220);
  font-weight: 700;
  letter-spacing: .02em;
}

/* UN ESTADO AL QUE NO SE PUEDE SALTAR DESDE DONDE ESTÁS.
   El ciclo de la carga tiene orden: con el remolque cargado no se pasa a «no
   disponible» sin cerrar la orden. El botón sigue ahí —y si se pulsa, dice
   por qué no— pero se ve que no toca: un botón que parece igual de pulsable
   que los demás y contesta que no, se pulsa tres veces en el muelle. */
.trk-status-btn.trk-status-no { opacity: .38; }
.trk-status-btn.trk-status-no:hover { opacity: .55; }

/* ───── css/superposiciones-cerradas.css ───── */
/*PORQUE El estado CERRADO del HUD y del modal de descarga, que no estaba escrito
       en ninguna parte: ver la explicacion dentro del archivo. PORQUE*/
/* ═══════════════════════════════════════════════════════════════════════════
   DOS DIÁLOGOS QUE ESTABAN ABIERTOS TODO EL RATO, Y NADIE LOS VEÍA

   Encontrado auditando la app pantalla por pantalla, midiendo qué textos se
   pisan entre sí. En la pantalla principal salían diez choques como estos:

       «Rutas Guardadas»  ×  «Calculando...»
       «Registro IFTA»    ×  «Descargando Mapa»
       «Combustible»      ×  «Preparando...»

   La causa: `js/hud-mode.js` y `js/offline-maps.js` construyen su panel con
   `document.body.appendChild(...)` al arrancar, y NO EXISTE NINGUNA REGLA QUE
   LOS OCULTE. Ambos alternan una clase para mostrarse —`.hud-active`,
   `.visible`— pero el estado base «cerrado» no estaba escrito en ninguna parte.
   Se buscó en las 29 hojas de estilo: cero reglas para `#hud-overlay` y cero
   para `#tile-download-modal`.

   ¿Por qué entonces no se ven? Porque el mapa ocupa la pantalla entera y los
   tapa. Están DEBAJO, en el flujo normal del documento, pintados y vivos. Eso
   no es estar oculto: es estar escondido por casualidad. Y trae cuatro
   problemas de verdad:

     1 · ACCESIBILIDAD. Los dos llevan `role="dialog"` con `aria-modal="true"`
         —y el HUD, `role="application"`—. Un lector de pantalla anuncia un
         diálogo modal ABIERTO en cada carga y se queda atrapado dentro. Para
         un conductor que usa la app por voz, eso es la app entera inservible.

     2 · EL TABULADOR. `#tile-download-cancel` y `#hud-exit-btn` son botones
         reales y enfocables. Se tabula por la pantalla y el foco desaparece
         dentro de un panel invisible. Es de los fallos más desconcertantes que
         existen: la tecla funciona y no pasa nada en pantalla.

     3 · SE ASOMAN. Cualquier cambio de apilado, un desplazamiento, o un mapa
         que tarde en pintar los deja a la vista. Es lo que salía en la medida.

     4 · EL MODAL DE DESCARGA NUNCA FUE UN MODAL. Sin una sola regla de estilo,
         `.visible` no lo centraba, no lo ponía encima ni le daba fondo: era
         texto suelto apilado en el cuerpo de la página. La descarga de mapas
         sin conexión llevaba desde siempre enseñando su progreso debajo del
         mapa, donde no se ve.

   Aquí se declara lo que faltaba: cerrado de verdad cuando está cerrado, y un
   diálogo como es debido cuando se abre.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── CERRADO ES CERRADO ─────────────────────────────────────────────────────
   `display: none` saca el elemento del árbol de accesibilidad, del orden de
   tabulación y del flujo. Las tres cosas a la vez, que es justo lo que hacía
   falta. No se usa `opacity: 0` ni `visibility: hidden` a secas: dejan el nodo
   enfocable o anunciable, que es la mitad del problema que se está cerrando. */
#hud-overlay,
#tile-download-modal {
  display: none;
}

#hud-overlay.hud-active,
#tile-download-modal.visible {
  display: flex;
}

/* ── EL HUD: pantalla completa, encima de todo ──────────────────────────── */
#hud-overlay.hud-active {
  position: fixed;
  inset: 0;
  z-index: 4000;
  flex-direction: column;
  background: #000;                 /* el HUD se refleja en el parabrisas:
                                       negro puro y nada más */
  color: var(--text, #F5F7FA);
  padding: var(--safe-top, 0px) var(--safe-right, 0px) var(--safe-bottom, 0px) var(--safe-left, 0px);
}

#hud-statusbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 16px;
  font-size: calc(14px * var(--ir-letra, 1));
}

/* El botón de salir del HUD se pulsa conduciendo: 48 px, como todo lo que se
   toca en esta casa. */
#hud-exit-btn {
  min-height: 48px;
  min-width: 48px;
  padding: 0 16px;
  border: 1px solid var(--border, rgba(255, 255, 255, 0.18));
  border-radius: var(--radius-sm, 8px);
  background: rgba(255, 255, 255, 0.08);
  color: var(--text, #F5F7FA);
  font-size: calc(15px * var(--ir-letra, 1));
  cursor: pointer;
}

/* ── EL MODAL DE DESCARGA: centrado, con su velo ────────────────────────── */
#tile-download-modal.visible {
  position: fixed;
  inset: 0;
  z-index: 3800;
  align-items: center;
  justify-content: center;
  padding: 20px;
  /* Velo opaco de verdad. Un modal translúcido deja leer lo de detrás y las
     dos capas de texto se pisan — el mismo fallo que dejaba «Añadir parada»
     asomando bajo el desplegable de direcciones. */
  background: rgba(3, 13, 28, 0.72);
}

#tile-download-panel {
  width: 100%;
  max-width: 360px;
  padding: 22px 20px;
  border: 1px solid var(--border, rgba(255, 255, 255, 0.12));
  border-radius: var(--radius, 12px);
  /* Papel opaco con respaldo literal: si mañana falta el token, esto sigue
     siendo un panel y no un agujero transparente. */
  background: var(--bg-elevated, #10294A);
  box-shadow: 0 18px 48px var(--shadow, rgba(0, 0, 0, 0.45));
  color: var(--text, #F5F7FA);
}

#tile-download-title {
  font-family: var(--font-heading, inherit);
  font-size: var(--font-size-lg, 17px);
  font-weight: 700;
  margin-bottom: 6px;
}

#tile-download-subtitle {
  font-size: var(--font-size-sm, 13px);
  color: var(--text-muted, #9FB0C3);
  line-height: 1.45;
  margin-bottom: 16px;
}

.tile-progress-bar-wrap {
  height: 8px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.1);
}

#tile-progress-bar {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent, #D4A84B);
  transition: width var(--duration, 0.25s) var(--ease, ease);
}

#tile-progress-text {
  margin-top: 10px;
  font-size: var(--font-size-sm, 13px);
  color: var(--text-muted, #9FB0C3);
  /* Cifras que cambian mientras se mira: en monoespaciada no bailan. */
  font-variant-numeric: tabular-nums;
}

#tile-download-cancel {
  display: block;
  width: 100%;
  min-height: 48px;
  margin-top: 18px;
  border: 1px solid var(--border, rgba(255, 255, 255, 0.18));
  border-radius: var(--radius-sm, 8px);
  background: transparent;
  color: var(--text, #F5F7FA);
  font-size: var(--font-size-base, 15px);
  cursor: pointer;
}

#tile-download-cancel:hover {
  background: rgba(255, 255, 255, 0.06);
}

/* Con el movimiento reducido se quita la transición, no el resultado: la barra
   salta a su sitio en vez de deslizarse, pero sigue diciendo por dónde va. */
@media (prefers-reduced-motion: reduce) {
  #tile-progress-bar { transition: none; }
}

/* ───── css/ui-fixes-p0.css ───── */
/*PORQUE CEO P0 UI fixes PORQUE*/
/**
 * Iron Route GPS — UI Fixes P0/P1/P2 Styles
 * 2026-05-03 — paired with js/ui-fixes-p0.js
 */

/* =========================================================
 * TOAST (singleton) — replaces dual systems visually
 * ========================================================= */
.toast-container {
  position: fixed;
  left: 50%;
  bottom: calc(72px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 10003;
  display: flex;
  flex-direction: column;
  gap: 6px;
  pointer-events: none;
  width: max-content;
  max-width: 92vw;
}
.toast {
  pointer-events: auto;
  background: rgba(15, 18, 24, 0.92);
  color: #fff;
  font-size: calc(13px * var(--ir-letra, 1));
  font-weight: 500;
  padding: 10px 16px;
  border-radius: 999px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .28s ease, transform .28s ease;
  letter-spacing: 0.1px;
  max-width: 92vw;
  text-align: center;
}
.toast.on { opacity: 1; transform: translateY(0); }
.toast.removing { opacity: 0; transform: translateY(8px); }
.toast.toast-success { background: rgba(34, 153, 84, 0.92); }
.toast.toast-error   { background: rgba(176, 35, 35, 0.92); }
.toast.toast-warning { background: rgba(176, 116, 35, 0.92); }
.toast.toast-info    { background: rgba(15, 18, 24, 0.92); }

/* Hide the legacy permissions-flow .ir-toast — we intercept and re-route via singleton */
.ir-toast { display: none !important; }

/* =========================================================
 * LOCATE BUTTON SPINNER
 * ========================================================= */
.map-ctrl-btn .ir-spinner {
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 2.5px solid rgba(0,0,0,0.18);
  border-top-color: var(--accent, #d4a84b);
  animation: ir-spin 0.85s linear infinite;
  display: inline-block;
}
.map-ctrl-btn.locating { pointer-events: none; opacity: 0.85; }
@keyframes ir-spin { to { transform: rotate(360deg); } }

/* =========================================================
 * QUICK ACTIONS — desktop arrows
 * ========================================================= */
.qa-arrow {
  position: absolute;
  top: 50%;
  width: 36px; height: 36px;
  background: var(--glass, rgba(255,255,255,0.85));
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--glass-border, rgba(0,0,0,0.08));
  border-radius: 50%;
  color: var(--text, #1a1a1a);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 12;
  cursor: pointer;
  box-shadow: 0 2px 12px rgba(0,0,0,0.18);
  transform: translateY(-50%);
  transition: transform .15s ease, background .15s ease;
}
.qa-arrow:hover { background: var(--accent, #d4a84b); color: #051d2c; }
.qa-arrow:active { transform: translateY(-50%) scale(0.92); }

/* ══════════════════════════════════════════════════════════════════════════
   LAS FLECHAS VIVEN EN UN PASILLO, NO ENCIMA DE UNA TARJETA

   Estaban en `left: 4px` / `right: 4px` dentro de una barra cuyo contenido
   empieza en x=12. O sea: la flecha se pintaba SOBRE la primera tarjeta —la
   que dice PLANIFICADOR— y sobre la última. Tapaba media etiqueta.

   Un control que tapa aquello sobre lo que actúa no está mal colocado: está
   robando sitio. Así que cuando hay flechas se abre un pasillo de 56 px a
   cada lado y la barra se estrecha para dejárselo. La clase la pone el mismo
   `syncArrows()` que decide si hay flechas o no — un solo sitio decide, y no
   puede quedar el pasillo sin flecha ni la flecha sin pasillo.
   ══════════════════════════════════════════════════════════════════════════ */
.qa-arrow-left  { left: 6px; }
.qa-arrow-right { right: 6px; }

.quick-tiles.qa-con-flechas,
.ir-escritorio .quick-tiles.qa-con-flechas {
  padding-left: 56px;
  padding-right: 56px;
}

/* hide arrows on touch devices */
@media (hover: none) and (pointer: coarse) {
  .qa-arrow { display: none !important; }
  /* Sin flechas no hace falta pasillo: el pulgar arrastra. Si esto no
     estuviera, un teléfono perdería 112 px de barra a cambio de nada. */
  .quick-tiles.qa-con-flechas { padding-left: 12px; padding-right: 12px; }
}

/* =========================================================
 * FAB COLLAPSE TOGGLE + collapsed state
 * ========================================================= */
.fab-collapse-toggle {
  position: fixed;
  right: 12px;
  bottom: calc(var(--nav-height, 64px) + var(--safe-bottom, 0px) + 350px);
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--glass, rgba(255,255,255,0.85));
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--glass-border, rgba(0,0,0,0.08));
  color: var(--text, #1a1a1a);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 902;
  box-shadow: 0 2px 10px rgba(0,0,0,0.18);
  cursor: pointer;
  transition: transform .25s ease;
}
.fab-collapse-toggle.is-collapsed { transform: rotate(180deg); }
.fab-collapse-toggle:hover { background: var(--accent, #d4a84b); color: #051d2c; }

.map-controls.collapsed { transform: scale(0); opacity: 0; pointer-events: none; transform-origin: bottom right; }
.map-controls { transition: transform .25s ease, opacity .25s ease; transform-origin: bottom right; }

/* =========================================================
 * REPORT-ALERT MODAL
 * ========================================================= */
.ir-modal {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.55);
  z-index: 10010;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
}
.ir-modal-card {
  background: var(--bg-surface, #fff);
  color: var(--text, #1a1a1a);
  width: 100%; max-width: 420px;
  border-radius: 18px;
  padding: 20px;
  box-shadow: 0 20px 50px rgba(0,0,0,0.35);
}
.ir-modal-card h3 { margin: 0 0 14px; font-size: calc(18px * var(--ir-letra, 1)); font-weight: 700; }
.ir-alert-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-bottom: 14px;
}
.ir-alert-chip {
  background: var(--glass, rgba(0,0,0,0.04));
  border: 1px solid var(--glass-border, rgba(0,0,0,0.08));
  border-radius: 12px;
  padding: 14px 10px;
  font-weight: 600;
  text-align: center;
  color: var(--text, #1a1a1a);
  cursor: pointer;
  font-size: calc(14px * var(--ir-letra, 1));
  min-height: 48px;
}
.ir-alert-chip:hover { background: var(--accent-dim, rgba(212,168,75,0.18)); border-color: var(--accent, #d4a84b); }
.ir-modal-close {
  width: 100%;
  background: transparent;
  color: var(--text-muted, #6b7280);
  border: 1px solid var(--glass-border, rgba(0,0,0,0.08));
  border-radius: 10px;
  padding: 12px;
  font-weight: 600;
  cursor: pointer;
}

/* =========================================================
 * RESPONSIVE BREAKPOINTS — xs/sm/md/lg/xl
 * ========================================================= */

/* xs <480 — phone portrait */
@media (max-width: 479px) {
  .search-container { width: calc(100% - 16px); }
  /* 20-sep-2026: estos tres controles medían 40 px en el teléfono y 44 en el
     phablet. El suelo de la casa es 48, y el motivo no es estético: son los
     botones que un conductor pulsa con el camión en marcha, con guantes y con
     el vehículo moviéndose. Un blanco de 40 px se falla, y fallarlo mirando
     la pantalla es peor que no mirarla. El carril es vertical y va pegado al
     borde derecho, así que los 8 px extra no le quitan mapa a nadie. */
  .map-ctrl-btn { width: 48px; height: 48px; }
  .fab { height: 48px; padding: 0 14px 0 10px; font-size: calc(12px * var(--ir-letra, 1)); }
  .tile { min-width: 76px; padding: 10px 10px; }
  .tile-icon { width: 36px; height: 36px; }
  .qa-arrow { display: none !important; }
  .fab-collapse-toggle { right: 8px; bottom: calc(var(--nav-height, 64px) + var(--safe-bottom, 0px) + 320px); }
}

/* sm 480-767 — phablet */
@media (min-width: 480px) and (max-width: 767px) {
  .search-container { max-width: 92%; }
  .map-ctrl-btn { width: 48px; height: 48px; }   /* el suelo, igual que en xs */
  .fab { height: 48px; }
}

/* md 768-1023 — tablet */
@media (min-width: 768px) and (max-width: 1023px) {
  .search-container { max-width: 600px; margin-left: auto; margin-right: auto; }
  .map-ctrl-btn { width: 48px; height: 48px; }
  .fab { height: 52px; }
}

/* lg 1024-1439 — laptop */
@media (min-width: 1024px) and (max-width: 1439px) {
  .search-container { max-width: 700px; }
  .map-ctrl-btn { width: 50px; height: 50px; }
  .fab { height: 54px; }
}

/* xl >=1440 — desktop */
@media (min-width: 1440px) {
  .search-container { max-width: 760px; }
  .map-ctrl-btn { width: 52px; height: 52px; }
  .fab { height: 56px; }
}

/* Map viewport — make sure container fills available space and never collapses */
#map.map-container, .map-container { min-height: 240px; }
.map-wrapper { min-height: 240px; }

/* Prevent horizontal overflow at all widths */
html, body { overflow-x: hidden; max-width: 100%; }

/* Touch target minimum on every breakpoint */
button, .tile, .map-ctrl-btn, .fab, .nav-tab, .menu-item { min-height: 44px; }
@media (pointer: coarse) {
  button, .tile, .map-ctrl-btn, .fab, .nav-tab, .menu-item { min-height: 48px; }
}

/* ───── css/button-states-fix.css ───── */
/*PORQUE T6.5 Mobile UX additions PORQUE*/
/* ═══════════════════════════════════════════════════════════════
   IRON ROUTE GPS — BUTTON TOGGLE STATES (T6.5/T2)
   Global pressed/unpressed state visibility for all map toggles.
   Targets: aria-pressed="true" + .is-pressed + .active toggle buttons.
   Brand B: navy + gold accent.
   ═══════════════════════════════════════════════════════════════ */

/* ── 1. Estado «pulsado», SOLO donde toca ────────────────────────────────
   Esto se escribio para los interruptores del mapa, y decia
   `button[aria-pressed="true"]` a secas: cualquier boton de la web con ese
   atributo se llevaba el relleno dorado con `!important`. Las fichas de
   vehiculo del alta lo usan para marcar la elegida, y al elegir una su
   subtexto quedaba en 1,64:1 sobre el dorado. Medido el 7-sep-2026.

   Ahora se pide ademas que el boton este dentro de una zona de controles, o
   que lleve la clase que se puso para eso. La leccion esta escrita mas abajo
   en el punto 8 y es la misma: una regla generica con `:not()` es una lista
   de excepciones que alguien tiene que mantener, y nadie la mantiene. Mejor
   decir donde SI se aplica que ir tachando donde no. */
.map-controls button[aria-pressed="true"],
.irc-panel button[aria-pressed="true"],
.layer-panel button[aria-pressed="true"],
button.ir-toggle[aria-pressed="true"],
.is-pressed,
.toggle-on {
  background: linear-gradient(135deg, #D4A84B, #B89438) !important;
  color: #030D1C !important;
  box-shadow:
    inset 0 0 0 2px rgba(212,168,75,0.85),
    0 0 14px rgba(212,168,75,0.35) !important;
  position: relative;
  transition: background .15s ease-out,
              box-shadow .15s ease-out,
              transform .15s ease-out !important;
}

/* La marca de visto de los interruptores pulsados, con el mismo alcance. */
.map-controls button[aria-pressed="true"]::after,
.irc-panel button[aria-pressed="true"]::after,
.layer-panel button[aria-pressed="true"]::after,
button.ir-toggle[aria-pressed="true"]::after,
.is-pressed::after,
.toggle-on::after {
  content: "\2713";   /* ✓ */
  position: absolute;
  top: 2px; right: 2px;
  width: 16px; height: 16px;
  background: #030D1C;
  color: #D4A84B;
  /* Estaba a 10 px. El suelo de la casa son 12, y esto se lee con el camión
     en marcha: el visto que dice si una capa está encendida o apagada. */
  font-size: calc(12px * var(--ir-letra, 1)); font-weight: 700;
  line-height: 16px;
  text-align: center;
  border-radius: 50%;
  border: 1px solid #D4A84B;
  pointer-events: none;
  font-family: system-ui, sans-serif;
}

/* ── 2. Map control buttons (right side stack) ───────────────────── */
.map-ctrl-btn[aria-pressed="true"] {
  border: 2px solid #D4A84B !important;
}
.map-ctrl-btn:active {
  transform: scale(0.92);
  transition: transform 0.08s ease-out !important;
}

/* ── 3. Theme + Lang + Unit pickers (settings) ───────────────────── */
.theme-option.active,
.lang-option.active,
.theme-option[aria-checked="true"],
.lang-option[aria-checked="true"] {
  background: linear-gradient(135deg, #D4A84B, #B89438) !important;
  color: #030D1C !important;
  font-weight: 700 !important;
  box-shadow: inset 0 0 0 2px rgba(212,168,75,0.85) !important;
}

/* ── 4. Bottom-nav active tab (clear pressed state) ──────────────── */
.nav-tab.active,
.nav-tab[aria-selected="true"] {
  color: #D4A84B !important;
  position: relative;
}
.nav-tab.active::before,
.nav-tab[aria-selected="true"]::before {
  content: "";
  position: absolute;
  top: 0; left: 50%; transform: translateX(-50%);
  width: 32px; height: 3px;
  background: #D4A84B;
  border-radius: 0 0 3px 3px;
  pointer-events: none;
}

/* ── 5. Trip planner toggle options (avoid tolls/highways) ───────── */
.toggle-option input[type="checkbox"]:checked + .toggle-slider {
  background: #D4A84B !important;
  box-shadow: 0 0 8px rgba(212,168,75,0.45) !important;
}

/* ── 6. Quick tile pressed feedback ──────────────────────────────── */
.tile:active {
  transform: scale(0.92);
  transition: transform 0.08s ease-out !important;
}
.tile[aria-pressed="true"] {
  background: rgba(212,168,75,0.15) !important;
  border: 2px solid #D4A84B !important;
}

/* ── 7. Voice / traffic / compass (R2 floating buttons) ──────────── */
.ir-traffic-btn[aria-pressed="true"],
.ir-compass-btn[aria-pressed="true"] {
  background: linear-gradient(135deg, #D4A84B, #B89438) !important;
  color: #030D1C !important;
}

/* ── 8. Generic .active fallback for any toggle-able button ────────

   `:not(.nav-tab)` NO ES UN DETALLE. Sin esa exclusión, esta regla le pintaba
   un fondo DORADO a la pestaña activa de la barra inferior… y la regla 4 de
   más arriba le fija el texto en `#D4A84B !important`, que es el mismo
   dorado. El `!important` gana, así que el `color: #030D1C` de aquí abajo
   nunca se aplica.

   Resultado medido, muestreando los píxeles de la captura en los DOS temas:
   campo uniforme `rgb(191,153,61)`…`rgb(200,159,67)`, **contraste 1,08:1**.
   El mínimo es 4,5:1. **La etiqueta de la pestaña en la que estás es
   ilegible** — y es la única pista de en qué pantalla estás.

   La pestaña activa ya se marca con su texto dorado y con el indicador de
   `.nav-tab.active::before`. No necesita fondo, y con él se anula a sí misma.

   Lección, porque se repite: una regla genérica con `:not()` es una lista de
   excepciones que hay que mantener. Cada vez que se añade un tipo de botón
   hay que preguntarse si entra aquí — y nadie lo hizo con `.nav-tab`. */
button.active:not(.btn-primary):not(.fab):not(.tile):not(.nav-tab):not(.irc-item):not(.ob-lang) {
  background: linear-gradient(135deg, #D4A84B, #B89438);
  color: #030D1C;
}

/* ── 9. El :hover pegado en pantallas tactiles (iOS Safari) ──────────────
   QUE PASABA. Aqui habia `button:hover { background: inherit !important }`.
   La intencion era buena —en un movil el `:hover` se queda pegado despues de
   tocar— pero `inherit` no devuelve al boton SU fondo: le pone el del PADRE.
   Dentro de una tarjeta blanca, cada boton se quedaba blanco al tocarlo, con
   su letra blanca encima. El dueno lo mando en una captura el 7-sep-2026:
   «Continuar con Apple», letra blanca sobre fondo blanco. Medido en Chromium
   con emulacion tactil: Apple 21,00:1 en reposo y 1,13:1 al tocar; el del
   correo, 1,00:1 — invisible del todo.

   POR QUE NO SE ARREGLA CON OTRO VALOR. No hay una palabra en CSS que diga
   «vuelve al fondo que este boton ya tenia»: `inherit` sube al padre y
   `revert` sube al navegador. La unica cura de verdad es que las reglas de
   `:hover` no se apliquen en tactil, y eso se hace donde se escriben, no
   aqui: envolviendolas en `@media (hover: hover)`. Hay 49 repartidas por
   diez hojas y se iran envolviendo; mientras tanto lo peor que puede pasar
   es que un boton se quede resaltado hasta que toques otra cosa, que es
   feo pero se ve. Un boton invisible no se ve, y por eso esta linea se va.

   Se queda `transform: none`, que si hace lo que promete y no borra nada. */
@media (hover: none) and (pointer: coarse) {
  button:hover {
    transform: none !important;
  }
  button:active {
    transition: transform 0.06s ease-out !important;
    transform: scale(0.94) !important;
  }
}

/* ── 10. Reduced motion respect ──────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  button[aria-pressed="true"],
  .is-pressed,
  .toggle-on {
    transition: none !important;
  }
}

/* ───── css/onboarding-p0-fixes.css ───── */
/*PORQUE P0 Onboarding fix (logo + dots + tap targets + map repaint) PORQUE*/
/* ════════════════════════════════════════════════════════════════════
 * Iron Route GPS — Onboarding P0 fixes (2026-05-03 r3)
 * Targets: progress dots spacing, animated logo block, touch handling,
 *          mobile viewport polish. Loaded AFTER onboarding.css.
 * ════════════════════════════════════════════════════════════════════ */

/* ── Touch / tap reliability ──────────────────────────────────────── */
.ob-veh-card,
.ob-chip,
.ob-role-card,
.ob-tab,
.ob-btn,
.ob-h-btn,
.ob-skip,
.ob-dot,
.ob-link,
[data-x-close],
[data-back],
[data-back-foot],
[data-continue],
[data-cta-start],
[data-cta-have-account],
[data-guest],
[data-oauth] {
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(212,168,75,.18);
  -webkit-user-select: none;
  user-select: none;
  min-height: 44px;
}
.ob-dot { min-height: 10px !important; }

/* ── Progress dots (clean round + better spacing) ─────────────────── */
.ob-dots {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  gap: 12px !important;
  padding: 6px 12px !important;
  flex-wrap: nowrap;
  max-width: 100%;
}
.ob-dot {
  width: 10px !important;
  height: 10px !important;
  min-width: 10px !important;
  border-radius: 50% !important;
  /* Era `rgba(244,241,234,.22)`: crema sobre la tarjeta blanca del tema
     claro, o sea invisible. Los puntos son lo único que dice cuántos pasos
     quedan. `--ob-linea` hereda el tema. */
  background: var(--ob-linea, rgba(244,241,234,.22)) !important;
  border: none !important;
  padding: 0 !important;
  flex: 0 0 auto;
  transition: background .25s ease, transform .25s ease, box-shadow .25s ease;
}
.ob-dot.done {
  background: var(--ob-exito, #7ED957) !important;
  width: 10px !important;
  border-radius: 50% !important;
}
.ob-dot.done svg { width: 7px !important; height: 7px !important; }
.ob-dot.here {
  width: 12px !important;
  height: 12px !important;
  border-radius: 50% !important;
  background: var(--ob-marca, #D4A84B) !important;
  box-shadow: 0 0 0 3px rgba(212,168,75,.22), 0 0 12px rgba(212,168,75,.55);
  transform: scale(1.05);
}
.ob-dot:focus-visible {
  outline: 2px solid var(--ob-marca, #D4A84B) !important;
  outline-offset: 4px !important;
}

@media (max-width: 480px) {
  .ob-header {
    grid-template-columns: 44px 1fr 44px !important;
    gap: 8px !important;
    padding: 12px 10px 8px 10px !important;
  }
  .ob-h-mid { gap: 4px !important; }
  .ob-dots { gap: 10px !important; padding: 4px 6px !important; }
  .ob-step-label { font-size: calc(11px * var(--ir-letra, 1)) !important; }
}

/* ── Welcome logo: centered, layered PNG + animated MP4 ───────────── */
.ob-welcome { padding-top: 4px; }
.ob-logo-wrap {
  display: flex !important;
  flex-direction: column;
  align-items: center !important;
  justify-content: center !important;
  width: 100% !important;
  padding: 20px 0 14px 0 !important;
  margin: 0 auto !important;
}
.ob-logo-frame {
  position: relative;
  width: 160px;
  height: 160px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: transparent;
  overflow: hidden;
  filter: drop-shadow(0 8px 22px rgba(0,0,0,.45));
  animation: obLogoEntrance .5s cubic-bezier(.2,.7,.2,1) both;
}
.ob-logo-frame video,
.ob-logo-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: transparent;
  pointer-events: none;
  border-radius: 50%;
}
.ob-logo-static-underlay {
  position: absolute; inset: 0;
  z-index: 1;
}
.ob-logo-video-overlay {
  position: absolute; inset: 0;
  z-index: 2;
  opacity: 0;
  transition: opacity .35s ease-out;
}
.ob-logo-frame.is-video-ready .ob-logo-video-overlay { opacity: 1; }
.ob-logo-frame.is-static .ob-logo-video-overlay { opacity: 0; }
.ob-logo-frame.is-static .ob-logo-static-underlay {
  animation: obLogoPulse 2.6s ease-in-out infinite;
}
@keyframes obLogoEntrance {
  from { opacity: 0; transform: scale(.92) translateY(6px); }
  to   { opacity: 1; transform: scale(1)   translateY(0); }
}
@keyframes obLogoPulse {
  0%, 100% { transform: scale(1);    filter: drop-shadow(0 8px 22px rgba(0,0,0,.45)); }
  50%      { transform: scale(1.04); filter: drop-shadow(0 12px 28px rgba(212,168,75,.35)); }
}
@media (max-width: 480px) {
  .ob-logo-frame { width: 128px; height: 128px; }
}

.ob-logo-wrap > img.ob-logo:not(.ob-logo-keep) { display: none; }

/* ── Welcome layout polish ────────────────────────────────────────── */
.ob-welcome > .ob-h1,
.ob-welcome > .ob-sub {
  max-width: 540px;
  margin-left: auto !important;
  margin-right: auto !important;
}
.ob-welcome .ob-bullets { margin-top: 18px; }

/* ── Vehicle cards: bigger tap target on mobile ───────────────────── */
@media (max-width: 480px) {
  .ob-veh-card {
    min-height: 64px !important;
    padding: 12px 14px !important;
  }
  .ob-veh-grid { gap: 10px !important; }
}

/* ── «Continuar» cuando todavía no se puede ───────────────────────────
   Medido: la tinta era `rgba(255,255,255,.55)` y encima `.ob-btn:disabled`
   ponía `opacity: .55` sobre todo el botón. Las dos cosas juntas dejaban la
   palabra «Continuar» en 1,45:1 en oscuro y 1,58:1 en claro — un botón que no
   se puede leer no es un botón deshabilitado, es un botón roto: la persona no
   sabe si es «Continuar» o «Atrás» lo que no le deja pulsar.

   Lo que tiene que apagarse es el RELLENO, no la letra. El dorado baja al
   55 % y la tinta se queda oscura: 6,4:1 en oscuro y 12:1 en claro. Sigue
   viéndose apagado al lado del botón activo, que es lo que hacía falta. */
.ob-btn-primary[disabled],
.ob-btn-primary:disabled {
  background: linear-gradient(180deg,
              var(--ob-relleno-apagado-0, #AC9251),
              var(--ob-relleno-apagado, #9C8447));
  color: var(--ob-sobre-marca, #030D1C);
  cursor: not-allowed;
  box-shadow: none;
}
/* Cierre reparado 2026-07-31: el archivo se cortó a media declaración y el
   navegador descartaba desde ahí. Un CSS truncado no avisa: simplemente deja
   de aplicar reglas y la pantalla se ve «rara» sin que nadie sepa por qué. */

/* ───── css/onboarding-step4-fix.css ───── */
/*PORQUE STEP 4 hotfix (required asterisks + (opcional) + Modelo dropdown) PORQUE*/
/* ════════════════════════════════════════════════════════════════════
 * Iron Route GPS — Onboarding Step 4 Hotfix (2026-05-03)
 * Visible required asterisks, "(opcional)" hint, dependent Model select
 * disabled state. Loaded AFTER css/onboarding.css and the P0 patches.
 * ════════════════════════════════════════════════════════════════════ */

/* El asterisco de campo obligatorio. Decía #D4A84B a pelo: el dorado de marca
   sobre la tarjeta blanca del tema claro da 2,07:1 y no se ve — o sea que en
   claro nadie sabía qué campos eran obligatorios. `--ob-marca` es el mismo
   dorado en oscuro y el dorado oscurecido (6,1:1) en claro. */
.ob-step .ob-field > span .ob-req {
  color: var(--ob-marca, #D4A84B);
  margin-left: 4px;
  font-weight: 700;
  font-size: calc(14px * var(--ir-letra, 1));
  line-height: 1;
}
/* Inline label text wrapper so the asterisk + "(opcional)" don't break weird */
.ob-step .ob-field > span .ob-lbl-text {
  display: inline;
}

/* «(opcional)». Tenía dos colores escritos a mano, uno por tema, y el de
   claro —`rgba(9,30,54,.55)`— daba 3,78:1: por debajo del suelo. Un texto
   apagado al 55 % de un color ya apagado siempre acaba ahí. `--ob-texto-2`
   ya ES el papel «secundario pero legible», y lo es en los dos temas: se
   usa ése y desaparece la bifurcación por tema. */
.ob-step .ob-field > span .ob-opt,
.ob-step .ob-toggle > span .ob-opt {
  color: var(--ob-texto-2, rgba(244, 241, 234, 0.72));
  font-weight: 400;
  font-size: calc(12px * var(--ir-letra, 1));
  margin-left: 6px;
  font-style: italic;
}

/* «Elige la marca primero» en el desplegable de Modelo. El `opacity: .55`
   apagaba el texto que explica POR QUÉ está bloqueado, que es justo lo único
   que hay que poder leer ahí. Se apaga el relleno y el borde, no la letra. */
.ob-step .ob-field select[data-model-select][disabled] {
  background: var(--ob-campo-2, #0A1A2E);
  color: var(--ob-texto-2, rgba(244, 241, 234, 0.72));
  cursor: not-allowed;
  font-style: italic;
}

/* Footer specific-error message — slightly more emphasis when text changes */
.ob-footer [data-foot-msg] {
  transition: color 160ms ease;
}
/* «Faltan: Año, Marca, Modelo». Es el mensaje que le dice a la persona por
   qué no puede continuar; si no se lee, la pantalla parece rota. #E11D48
   sobre el pie oscuro daba 4,12:1, por debajo del suelo. `--ob-peligro` es
   el rojo del alta y ya viene medido en los dos temas. */
.ob-footer [data-foot-msg]:not(:empty) {
  color: var(--ob-peligro, #FF7A80);
  font-weight: 600;
}

/* El «— elige —» de los desplegables. El color de una `<option>` lo pinta el
   sistema operativo, no la hoja, así que aquí solo se pide el papel; si el
   sistema lo ignora, al menos no queda crema sobre blanco. */
.ob-step .ob-field select[name="year"] option[value=""],
.ob-step .ob-field select[name="model"] option[value=""] {
  color: var(--ob-texto-2, rgba(244, 241, 234, 0.72));
}

/* Ensure required asterisk and optional tag remain readable at small sizes */
@media (max-width: 380px) {
  .ob-step .ob-field > span .ob-req { font-size: calc(13px * var(--ir-letra, 1)); }
  .ob-step .ob-field > span .ob-opt { font-size: calc(11px * var(--ir-letra, 1)); }
}

/* ════════════════════════════════════════════════════════════════════
 * EL PORQUE (8-sep-2026)
 *
 * «Que eso sea opcional, pero que muestre un aviso de el porque lo
 * requiere, para que el usuario sepa que eso se necesita» (dueno).
 *
 * Es el papel que faltaba en el alta. Habia dos y ninguno servia:
 *   · `.ob-req` — el asterisco. Dice «esto lo exijo» y no dice por que.
 *   · `.ob-warn` — el ambar de las advertencias. Dice «cuidado». Contar
 *     para que sirve el ano del camion en ambar seria gritar un dato
 *     que no tiene nada de peligroso, y de paso apagaria el ambar de
 *     los avisos que SI lo son (hazmat, sobreancho, sobrepeso).
 *
 * Asi que `.ob-porque` es texto secundario, no alarma: el mismo papel
 * `--ob-texto-2` que ya usa «(opcional)» y que ya viene medido en los
 * dos temas. 13 px —por encima del suelo de 12 del proyecto— y una
 * linea dorada a la izquierda que lo separa del rotulo del campo sin
 * pintarlo de color de aviso.
 * ════════════════════════════════════════════════════════════════════ */
.ob-step .ob-porque {
  margin: 0 0 16px 0;
  padding: 2px 0 2px 12px;
  border-left: 2px solid var(--ob-marca-borde, rgba(212, 168, 75, .4));
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.5;
  color: var(--ob-texto-2, rgba(244, 241, 234, .72));
}

/* Dos porques seguidos son un parrafo partido, no dos bloques: se juntan. */
.ob-step .ob-porque + .ob-porque { margin-top: -8px; }

/* «Puedes dejarlo en blanco y volver luego». No es el porque de un campo,
   es la salida de la pantalla entera: sin la linea dorada, y separado. */
.ob-step .ob-porque-luego {
  border-left-color: transparent;
  padding-left: 14px;
}

/* Dentro del panel hazmat el bloque ya va sangrado y tintado: la linea de
   la izquierda ahi solo anadiria un borde dentro de otro borde. */
.ob-step .ob-haz-panel .ob-porque {
  border-left: none;
  padding-left: 0;
  margin-bottom: 0;
}

@media (max-width: 380px) {
  .ob-step .ob-porque { font-size: calc(12.5px * var(--ir-letra, 1)); }
}

/* ───── css/map-layers.css ───── */
/*PORQUE Selector de capas del mapa (satélite, oscuro, relieve). PORQUE*/
/* ==========================================================================
   IRON ROUTE — el selector de capas del mapa.

   Diseñado para la cabina, no para una oficina:
     · Objetivo de toque 48px (con guantes, 44 no alcanza).
     · Se apoya arriba a la derecha, DEBAJO del área segura del notch, y por
       encima del mapa pero por debajo de los paneles (z-index 800).
     · En teléfono la lista se despliega hacia abajo; en pantallas anchas se
       queda abierta como fila, porque el dispatcher tiene sitio y un clic
       menos es un clic menos.
   ========================================================================== */

.irc-capas {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 84px);
  right: calc(env(safe-area-inset-right, 0px) + 12px);
  z-index: 800;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  pointer-events: auto;
}

.irc-toggle {
  width: 48px;
  height: 48px;
  min-width: 48px;
  min-height: 48px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, .18);
  background: rgba(6, 20, 40, .88);
  color: #fff;
  font-size: calc(22px * var(--ir-letra, 1));
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .35);
  -webkit-tap-highlight-color: transparent;
}
.irc-toggle:active { transform: scale(.96); }
.irc-toggle:focus-visible { outline: 3px solid #D4A84B; outline-offset: 2px; }

/* ══════════════════════════════════════════════════════════════════════════
   `hidden` NO FUNCIONABA, Y ESO DEJABA EL DESPLEGABLE ABIERTO PARA SIEMPRE

   `map-layers.js` hace `lista.hidden = true` al construir, y su botón alterna
   esa propiedad. Correcto. Pero el atributo `hidden` se aplica con una regla
   del NAVEGADOR (`[hidden] { display: none }`), y cualquier regla de la hoja
   del sitio le gana — las de autor mandan sobre las del navegador.

   Aquí abajo hay un `display: flex`. Así que `hidden` no escondía nada.

   MEDIDO en un teléfono de 375: el desplegable de capas sale **abierto al
   cargar**, `172×270@191,188`, tapando el mapa y el botón de «Mi ubicación» —
   `elementFromPoint` sobre ese botón devuelve `irc-tx`, o sea un chip de
   capa. Y pulsar el botón de capas no lo cierra: tras el clic la caja mide
   exactamente lo mismo.

   Un menú que nace abierto, tapa un control y no se puede cerrar es de las
   cosas que hacen que alguien piense que la aplicación está rota. Y lo
   estaba: por una línea que no se aplicaba.

   La regla de abajo devuelve a `hidden` su significado. En escritorio hay una
   excepción explícita (la lista vive abierta en fila) y va después, en su
   media query, así que gana ahí y solo ahí.
   ══════════════════════════════════════════════════════════════════════════ */
.irc-lista[hidden] { display: none; }

.irc-lista {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, .18);
  background: rgba(6, 20, 40, .95);
  box-shadow: 0 6px 22px rgba(0, 0, 0, .45);
  min-width: 172px;
}

.irc-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 48px;
  padding: 0 12px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  /* Blanco casi puro sobre navy: contraste > 12:1, legible con sol de frente. */
  color: #F5F7FA;
  font-size: calc(15px * var(--ir-letra, 1));
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.irc-item:active { background: rgba(255, 255, 255, .10); }
.irc-item:focus-visible { outline: 3px solid #D4A84B; outline-offset: -3px; }

/* La capa activa: oro de marca sobre navy. Se marca con FONDO y con una barra
   lateral, no solo con color de texto — al sol un cambio de tono se pierde. */
.irc-item.irc-on {
  background: rgba(212, 168, 75, .18);
  color: #E8C468;
  box-shadow: inset 3px 0 0 #D4A84B;
}

.irc-ic { font-size: calc(18px * var(--ir-letra, 1)); line-height: 1; flex: 0 0 auto; }
.irc-tx { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- Teléfono en horizontal: poca altura, se sube el panel ---------------- */
@media (max-height: 500px) and (orientation: landscape) {
  .irc-capas { top: calc(env(safe-area-inset-top, 0px) + 8px); }
  .irc-item  { min-height: 44px; font-size: calc(14px * var(--ir-letra, 1)); }
}

/* --- Escritorio (dispatcher): la lista vive abierta, en fila -------------- */
@media (min-width: 1024px) {
  .irc-capas { top: calc(env(safe-area-inset-top, 0px) + 16px); flex-direction: row; align-items: flex-start; }
  .irc-toggle { display: none; }
  .irc-lista { flex-direction: row; min-width: 0; }
  .irc-lista[hidden] { display: flex; }   /* en ancho no se oculta nunca */
  .irc-item { width: auto; padding: 0 14px; }
}

/* Menos movimiento para quien lo pidió en su sistema. */
@media (prefers-reduced-motion: reduce) {
  .irc-toggle:active { transform: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   1 · LA CAJA INVISIBLE QUE SE COMÍA LOS TOQUES

   Medido en Android 360×640:  `.irc-capas`  x 12..348  y 132..180.
   336 px de ancho, fondo transparente, `pointer-events: auto`.

   O sea: una banda invisible de 336×48 cruzando el mapa entero, y
   `elementFromPoint` en cualquier punto de esa banda devolvía la caja, no el
   mapa. Arrastrar el mapa desde ahí no funcionaba y no había nada que ver que
   lo explicara — el peor tipo de defecto, el que la persona se acaba echando
   a sí misma.

   La caja solo existe para colocar el botón y la lista. No tiene por qué
   recibir un solo toque: los reciben sus hijos.
   ══════════════════════════════════════════════════════════════════════════ */
.irc-capas {
  left: auto;
  width: max-content;
  max-width: calc(100vw - 24px);
  pointer-events: none;
}
.irc-capas > * { pointer-events: auto; }

/* ══════════════════════════════════════════════════════════════════════════
   2 · EN TELÉFONO ESTA BARRA YA NO EXISTE

   Chocaba con `.fab-collapse-toggle` (misma columna, 34 px de solape en
   360×640) y el que perdía era este: no se podía pulsar.

   No se mueve, se retira: su contenido —las cinco capas base— ahora vive
   dentro de la hoja «Capas del mapa» que abre `#layersBtn`, junto a las capas
   de datos. Una sola casa, un solo botón, y el rótulo deja de estar duplicado.

   En escritorio se queda: ahí medido no solapa con nada y es un clic menos
   para el dispatcher, que es su razón de ser.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 899px) {
  .irc-capas { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   3 · EL MAPA BASE, DENTRO DE LA HOJA DE CAPAS

   PRIMERA VERSIÓN, MÍA, MAL: fijé los colores del tema oscuro a mano
   (`#9FB0C3` de rótulo, `#10294A` de chip). Medido una hora después con el
   barrido de contraste:

       H3.ir-base-tit  «Mapa base»   rgb(159,176,195) sobre rgb(255,255,255)
                                     → 2.22:1, mínimo 4.5

   La hoja usa `background: var(--bg-surface, #fff)`, y en tema claro
   `--bg-surface` es **blanco**. Escribí gris claro sobre blanco.

   El error de forma no es el gris: es **fijar un color a mano en una
   aplicación con dos temas**. Aquí abajo no hay ni un hex de texto: se hereda
   el color del panel, que ya es el correcto en los dos temas, y la jerarquía
   se hace con opacidad y peso — que funcionan igual en claro y en oscuro.
   ══════════════════════════════════════════════════════════════════════════ */
.ir-base-sec { margin: 0 0 4px; color: inherit; }

.ir-base-tit {
  margin: 0 0 2px;
  font-size: calc(13px * var(--ir-letra, 1));
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: inherit;
  /* .72 sobre un texto que ya cumple deja ~8:1 en claro y ~9:1 en oscuro.
     Un `--text-muted` habría sido más elegante y menos fiable: ese token es
     distinto en cada tema y en uno de los dos no llega. */
  opacity: .72;
}
.ir-base-tit-2 {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--glass-border, rgba(127, 127, 127, .25));
}

.ir-base-pie {
  margin: 0 0 10px;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.35;
  color: inherit;
  opacity: .72;
}

.ir-base-rejilla {
  display: grid;
  /* Dos columnas en teléfono, tres cuando cabe. `auto-fit` con un mínimo de
     140 px lo resuelve sin una sola consulta de medios. */
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
}

.ir-base-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;          /* con guantes, 44 no alcanza */
  padding: 0 12px;
  border: 1px solid var(--glass-border, rgba(127, 127, 127, .3));
  border-radius: 12px;
  background: transparent;
  color: inherit;
  font-family: inherit;
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
/* Un velo neutro: aclara sobre oscuro y oscurece sobre claro sin saber en cuál
   está, porque es el MISMO gris con alfa en los dos casos. */
.ir-base-chip:hover  { background: rgba(127, 127, 127, .12); }
.ir-base-chip:active { background: rgba(127, 127, 127, .20); }
.ir-base-chip:focus-visible { outline: 3px solid #D4A84B; outline-offset: 2px; }

/* La elegida: borde grueso, peso, y una marca de verificación **en el color
   del texto**. Nada de oro como texto: `#D4A84B` sobre blanco da 2.1:1 y
   sobre navy 6.9:1 — o sea que en tema claro sería ilegible. El oro se queda
   donde no es texto (el anillo de foco). */
.ir-base-chip.ir-base-on {
  border-width: 2px;
  border-color: currentColor;
  background: rgba(127, 127, 127, .14);
  font-weight: 700;
}
.ir-base-chip.ir-base-on::after {
  content: "\2713";        /* ✓ */
  margin-left: auto;
  font-size: calc(15px * var(--ir-letra, 1));
  line-height: 1;
  flex: 0 0 auto;
  color: inherit;
}

.ir-base-ic { font-size: calc(18px * var(--ir-letra, 1)); line-height: 1; flex: 0 0 auto; }
.ir-base-tx { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  .ir-base-chip { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   LA VOZ Y EL MODO CAMIÓN, DENTRO DE LA HOJA (23-sep-2026)
   `js/opciones-en-la-hoja.js`. Un botón en el carril, y de él salen las
   opciones: mapa base · capas · voz · camión. Los chips de voz son los
   mismos `.ir-base-chip` del mapa base, con una segunda línea que dice lo
   que se PIERDE en cada modo. El de camión es un interruptor de 48 px.
   ══════════════════════════════════════════════════════════════════════════ */
.ir-op-sec { margin: 0; color: inherit; }
.ir-op-rejilla { grid-template-columns: 1fr; }
.ir-op-chip { min-height: 56px; padding: 8px 12px; }
.ir-op-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ir-op-nombre { font-weight: 700; }
.ir-op-que {
  font-size: calc(12px * var(--ir-letra, 1));
  font-weight: 500;
  line-height: 1.3;
  opacity: .78;
  white-space: normal;
}
.ir-op-que.ir-op-aviso { opacity: 1; color: var(--warning, #F5A623); }

.ir-op-switch {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 56px;
  padding: 8px 12px;
  border: 1px solid var(--glass-border, rgba(127, 127, 127, .3));
  border-radius: 12px;
  background: transparent;
  color: inherit;
  font-family: inherit;
  font-size: calc(14px * var(--ir-letra, 1));
  text-align: left;
  cursor: pointer;
}
.ir-op-switch:hover { background: rgba(127, 127, 127, .12); }
.ir-op-switch:focus-visible { outline: 3px solid #D4A84B; outline-offset: 2px; }
.ir-op-switch .ir-op-tx { flex: 1 1 auto; }
/* La palanca: 44×24, la bolita se va a la derecha cuando está encendido.
   El color del «encendido» es el del texto, no el oro: sobre blanco el oro
   da 2.1:1 y no se vería. */
.ir-op-palanca {
  flex: 0 0 auto;
  width: 44px; height: 24px;
  border-radius: 12px;
  border: 2px solid currentColor;
  position: relative;
  opacity: .55;
}
.ir-op-palanca::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: currentColor;
  transition: transform .15s ease;
}
.ir-op-switch.ir-op-on .ir-op-palanca { opacity: 1; background: rgba(127, 127, 127, .18); }
.ir-op-switch.ir-op-on .ir-op-palanca::after { transform: translateX(20px); }
@media (prefers-reduced-motion: reduce) { .ir-op-palanca::after { transition: none; } }

/* ───── css/onboarding-responsive.css ───── */
/*PORQUE Onboarding responsive: va DESPUÉS de las 9 hojas anteriores para ganar
       por orden de carga y no por !important (ya hay 103 repartidos). PORQUE*/
/* ==========================================================================
   IRON ROUTE — ONBOARDING RESPONSIVE (2026-07-31)

   POR QUÉ EXISTE ESTA HOJA Y POR QUÉ ES LA ÚLTIMA:

   El onboarding tenía tres defectos que lo volvían inusable, y los tres eran
   de tamaño, no de contenido:

     1. La tarjeta medía 820px FIJOS de alto en escritorio. En un portátil de
        768px de alto —que son casi todos— el botón «Continuar» quedaba FUERA
        de la pantalla, y el contenedor no tenía scroll. No es que costara
        llegar: era imposible pasar de paso.

     2. En iPhone se usaba `100vh`, que en Safari NO descuenta la barra
        inferior del navegador. El botón quedaba justo debajo de ella. La
        unidad correcta es `dvh` (dynamic viewport height), con `vh` de
        respaldo para navegadores viejos.

     3. Nada respetaba el área segura del notch ni la barra de gestos.

   Se arregla con CERO `!important`. Las 9 hojas anteriores acumulan 103
   `!important` —una sola tiene 42— y esa guerra de especificidad es
   precisamente por qué nadie se atrevía a tocar esto. Aquí se gana por orden
   de carga, que es como debe ganarse.
   ========================================================================== */

/* ── La ventana: alto REAL del viewport, no el teórico ──────────────────── */
.ob-overlay,
.ob-modal,
#ironroute-onboarding {
  height: 100vh;            /* respaldo para navegadores sin dvh */
  height: 100dvh;           /* el bueno: descuenta la barra de Safari */
  max-height: 100vh;
  max-height: 100dvh;
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  box-sizing: border-box;
}

/* ── La tarjeta: deja de medir 820px fijos ──────────────────────────────
   Columna con cabecera y pie fijos y el CUERPO desplazable. Así el botón
   «Continuar» está siempre a la vista, mida lo que mida la pantalla. */
.ob-card,
.ob-sheet,
.ob-container {
  display: flex;
  flex-direction: column;
  height: auto;
  max-height: calc(100dvh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
  min-height: 0;            /* sin esto, flex no deja encoger y vuelve el bug */
  overflow: hidden;
}

.ob-body,
.ob-content,
.ob-step-content {
  flex: 1 1 auto;
  min-height: 0;            /* la línea que de verdad habilita el scroll */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

.ob-header, .ob-footer,
.ob-sticky-header, .ob-sticky-footer {
  flex: 0 0 auto;
}

/* El pie, siempre alcanzable y por encima del área de gestos. */
.ob-footer,
.ob-sticky-footer {
  padding-bottom: max(12px, env(safe-area-inset-bottom, 0px));
}

/* ── Objetivos de toque: con guantes, 44 no alcanza ─────────────────────── */
.ob-btn,
.ob-btn-primary,
.ob-btn-secondary,
.ob-skip,
.ob-close,
.ob-back {
  min-height: 48px;
  min-width: 48px;
}

/* «Saltar» tiene que VERSE. Es la salida de quien solo quiere el mapa, y
   estaba escondido por un condicional invertido (ya corregido en el JS). */
.ob-skip {
  display: inline-flex;
  align-items: center;
  opacity: .85;
}
.ob-skip:hover, .ob-skip:focus-visible { opacity: 1; }

/* ── Teléfono en vertical ───────────────────────────────────────────────── */
@media (max-width: 640px) {
  .ob-card, .ob-sheet, .ob-container {
    width: 100%;
    max-width: 100%;
    border-radius: 16px 16px 0 0;
    margin: 0;
  }
  .ob-title, .ob-step-title { font-size: clamp(1.25rem, 6vw, 1.6rem); line-height: 1.2; }
  .ob-body, .ob-content, .ob-step-content { padding-left: 18px; padding-right: 18px; }
}

/* ── Teléfono en horizontal: el caso que nadie prueba ────────────────────
   Con 380px de alto, una cabecera y un pie fijos se comen la pantalla. Se
   encogen y el cuerpo se queda con lo que hay. */
@media (max-height: 500px) and (orientation: landscape) {
  .ob-header, .ob-sticky-header { padding-top: 6px; padding-bottom: 6px; }
  .ob-footer, .ob-sticky-footer { padding-top: 6px; padding-bottom: max(6px, env(safe-area-inset-bottom, 0px)); }
  .ob-title, .ob-step-title { font-size: calc(1.1rem * var(--ir-letra, 1)); }
  .ob-illustration, .ob-hero, .ob-art { display: none; }  /* el dibujo cede antes que el botón */
  .ob-btn, .ob-btn-primary, .ob-btn-secondary { min-height: 44px; }
}

/* ── Tableta y escritorio ───────────────────────────────────────────────── */
@media (min-width: 641px) {
  .ob-card, .ob-sheet, .ob-container {
    width: min(680px, 92vw);
    max-height: min(760px, calc(100dvh - 48px));
    border-radius: 18px;
    margin: auto;
  }
}

/* Pantallas bajas (portátiles de 768px): aquí es donde el 820px fijo dejaba
   el botón fuera. El techo pasa a ser el viewport, no un número inventado. */
@media (min-width: 641px) and (max-height: 820px) {
  .ob-card, .ob-sheet, .ob-container {
    max-height: calc(100dvh - 32px);
  }
}

/* ── Respeto por quien pidió menos movimiento ───────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .ob-overlay, .ob-card, .ob-sheet, .ob-step { transition: none; animation: none; }
}

/* ───── css/tms-viaje.css ───── */
/*PORQUE La carga que llega del TMS por enlace firmado. PORQUE*/
/* ==========================================================================
   IRON ROUTE — la tarjeta del viaje que llega del TMS.
   Hoja de estilo mínima y responsive. Cero !important.
   ========================================================================== */
.irtms {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgba(0,0,0,.55);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.irtms[hidden] { display: none; }

.irtms__card {
  width: min(560px, 100%);
  max-height: calc(100dvh - 40px);
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  background: #0A1B33; color: #F5F7FA;
  border: 1px solid #1E3A5F; border-radius: 18px 18px 0 0;
  padding: 20px 20px calc(20px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -8px 40px rgba(0,0,0,.5);
}
@media (min-width: 641px) {
  .irtms { align-items: center; }
  .irtms__card { border-radius: 18px; max-height: min(88dvh, 820px); }
}

.irtms__cab { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.irtms__marca { color: #D4A84B; font-size: calc(.72rem * var(--ir-letra, 1)); font-weight: 800; letter-spacing: .09em; }
.irtms__x { min-width: 48px; min-height: 48px; background: none; border: 0; color: #9FB0C3; font-size: calc(20px * var(--ir-letra, 1)); cursor: pointer; }

.irtms__ciudad { font-size: clamp(1.7rem, 7vw, 2.3rem); font-weight: 800; line-height: 1.05; }
.irtms__inst   { color: #9FB0C3; margin-top: 2px; }
.irtms__cita   { display: flex; gap: .6em; align-items: baseline; flex-wrap: wrap; margin-top: 14px; font-size: calc(1.15rem * var(--ir-letra, 1)); }
.irtms__cita-tipo { color: #9FB0C3; text-transform: uppercase; font-size: calc(.72rem * var(--ir-letra, 1)); letter-spacing: .07em; }
.irtms__millas { margin-top: 8px; color: #9FB0C3; font-variant-numeric: tabular-nums; }

.irtms__entrada { display: flex; gap: .5em; margin-top: 14px; padding: 12px 14px;
  background: #10294A; border-radius: 12px; line-height: 1.4; }
.irtms__ic { color: #D4A84B; font-weight: 700; }

.irtms__avisos { margin-top: 14px; padding: 12px 14px; background: #10294A; border-radius: 12px; }
.irtms__avisos-t { color: #9FB0C3; font-size: calc(.7rem * var(--ir-letra, 1)); letter-spacing: .07em; margin-bottom: 6px; }
.irtms__aviso { display: flex; gap: .6em; padding: 4px 0; line-height: 1.35; }
.irtms__aviso-mi { color: #D4A84B; font-weight: 800; flex: 0 0 auto; font-variant-numeric: tabular-nums; }

.irtms__navegar {
  display: block; width: 100%; min-height: 64px; margin-top: 18px;
  background: #D4A84B; color: #030D1C; border: 0; border-radius: 14px;
  font-size: calc(1.25rem * var(--ir-letra, 1)); font-weight: 800; letter-spacing: .03em; cursor: pointer;
}
.irtms__aceptar {
  display: block; width: 100%; min-height: 52px; margin-top: 10px;
  background: rgba(16,185,129,.14); color: #10B981;
  border: 1px solid #10B981; border-radius: 13px; font-size: calc(1.05rem * var(--ir-letra, 1)); font-weight: 700; cursor: pointer;
}
.irtms__aceptar:disabled { opacity: .6; cursor: default; }
.irtms__ok { text-align: center; color: #10B981; font-weight: 800; padding: 12px; }

.irtms__ubic { display: flex; gap: .6em; align-items: center; min-height: 48px; margin-top: 12px; color: #9FB0C3; cursor: pointer; }
.irtms__ubic input { width: 22px; height: 22px; }
.irtms__pie  { margin-top: 12px; padding-top: 10px; border-top: 1px solid #1E3A5F; color: #9FB0C3; font-size: calc(.9rem * var(--ir-letra, 1)); }
.irtms__hint { color: #9FB0C3; line-height: 1.45; }

/* ==========================================================================
   EL CAMIÓN QUE VIENE DEL TMS

   Se le dice al conductor con qué está ruteando y de dónde salió. Cambiarle
   las medidas por debajo sin avisar es el tipo de magia que hace que alguien
   deje de confiar en un GPS — y aquí la confianza es la función.
   ========================================================================== */
.irtms__camion {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-top: 12px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid rgba(16, 185, 129, 0.35);
  background: rgba(16, 185, 129, 0.08);
}
.irtms__camion strong {
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(13px * var(--ir-letra, 1));
  color: var(--success, #10B981);
}
.irtms__camion span {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.45;
  color: var(--text, #F5F7FA);
}
.irtms__camion-n { color: var(--text-muted, #9FB0C3); }
/* La discrepancia va en ámbar: no es un error que rompa nada, es algo que
   alguien tiene que ir a arreglar a la fuente. */
.irtms__camion-d { color: var(--warning, #F5A623); }

/* ───── css/rutas-guardadas.css ───── */
/*PORQUE Recientes y guardadas: el panel «Mis Rutas» llevaba meses vacío. PORQUE*/
/* ==========================================================================
   IRON ROUTE — RECIENTES Y GUARDADAS (M2.5)

   Se diseña para el pulgar de alguien que va con el motor encendido:
   toda fila de tocar mide 48 px como mínimo (--touch), el texto nunca baja
   de 12 px, y la lane —lo único que de verdad se lee de un vistazo— va en
   el tamaño más grande de la fila.

   Cero !important: esta hoja se carga después de las demás y gana por orden.
   ========================================================================== */

#savedRoutesList {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 62vh;
  max-height: 62dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* Encabezado de sección: GUARDADAS / RECIENTES. */
.rg-titulo {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  letter-spacing: 0.12em;
  color: var(--text-muted, #9FB0C3);
  text-transform: uppercase;
  padding: 12px 2px 4px;
  border-bottom: 1px solid var(--line, #1E3A5F);
  margin-bottom: 2px;
}
.rg-titulo:first-child { padding-top: 2px; }

/* La fila. El botón grande ocupa todo el ancho que sobra; la estrella y la
   equis quedan a la derecha con su propio blanco de 48 px. */
.rg-item {
  display: flex;
  align-items: stretch;
  gap: 4px;
  border-radius: 10px;
  background: var(--surface-2, #10294A);
  border: 1px solid var(--line, #1E3A5F);
  overflow: hidden;
}
.rg-item:hover { border-color: var(--accent, #F58220); }

.rg-usar {
  flex: 1 1 auto;
  min-width: 0;                 /* sin esto el texto largo desborda el flex */
  min-height: var(--touch, 48px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
}
.rg-usar:active { background: rgba(245, 130, 32, 0.10); }

.rg-lane {
  display: block;
  font-size: calc(15px * var(--ir-letra, 1));
  line-height: 1.25;
  color: var(--text, #F5F7FA);
  /* Dos renglones como techo: una lane con nombres largos no puede empujar
     las demás fuera de la pantalla. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.rg-lane strong { font-weight: 600; }
.rg-flecha { color: var(--accent, #F58220); font-weight: 700; }

.rg-meta {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  color: var(--text-muted, #9FB0C3);
}

/* Estrella y equis: cuadrados de 48 px, que es lo mínimo que un pulgar
   acierta en una cabina en movimiento. */
.rg-fav,
.rg-x {
  flex: 0 0 auto;
  width: var(--touch, 48px);
  min-height: var(--touch, 48px);
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-left: 1px solid var(--line, #1E3A5F);
  cursor: pointer;
  font-size: calc(18px * var(--ir-letra, 1));
  line-height: 1;
  color: var(--text-muted, #9FB0C3);
}
.rg-fav[aria-pressed="true"] { color: var(--gold, #D4A84B); }
.rg-fav:active,
.rg-x:active { background: rgba(255, 255, 255, 0.06); }
.rg-x:hover { color: var(--error, #EF4444); }

.rg-usar:focus-visible,
.rg-fav:focus-visible,
.rg-x:focus-visible {
  outline: 2px solid var(--accent, #F58220);
  outline-offset: -2px;
}

/* El estado vacío ya existía en el HTML con su icono; solo se le añade la
   segunda línea que dice QUÉ HACER para llenarlo. */
.rg-hint {
  font-size: calc(13px * var(--ir-letra, 1));
  color: var(--text-muted, #9FB0C3);
  margin-top: 6px;
  line-height: 1.4;
}

/* Tema claro: el panel hereda fondo blanco, así que la fila no puede
   quedarse con el azul oscuro del tema por defecto. */
html[data-theme="light"] .rg-item,
body.light-theme .rg-item {
  background: #F4F6F9;
  border-color: #D8DEE7;
}
html[data-theme="light"] .rg-usar,
html[data-theme="light"] .rg-lane,
body.light-theme .rg-usar,
body.light-theme .rg-lane { color: #0A1B33; }
html[data-theme="light"] .rg-meta,
html[data-theme="light"] .rg-titulo,
html[data-theme="light"] .rg-hint,
body.light-theme .rg-meta,
body.light-theme .rg-titulo,
body.light-theme .rg-hint { color: #5A6B80; }
html[data-theme="light"] .rg-fav,
html[data-theme="light"] .rg-x,
body.light-theme .rg-fav,
body.light-theme .rg-x { border-left-color: #D8DEE7; }

/* Pantalla ancha (dispatcher en escritorio): caben dos columnas y se ve
   media semana de lanes sin desplazar. */
@media (min-width: 900px) {
  #savedRoutesList {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    max-height: 56vh;
  }
  .rg-titulo { grid-column: 1 / -1; }
  #savedRoutesList > .empty-state { grid-column: 1 / -1; }
}

/* Teléfono apaisado: la hoja inferior deja muy poco alto útil. */
@media (max-height: 480px) {
  #savedRoutesList { max-height: 46vh; max-height: 46dvh; }
  .rg-lane { font-size: calc(14px * var(--ir-letra, 1)); }
}

@media (prefers-reduced-motion: reduce) {
  .rg-item, .rg-usar, .rg-fav, .rg-x { transition: none; }
}

/* ==========================================================================
   EL VEREDICTO — la franja que termina el trabajo

   Las tres tarjetas de ruta se comparaban entre ellas y con nada más, y la
   etiqueta «Recomendada» estaba clavada en la primera pasara lo que pasara.
   Esta franja dice QUÉ TOMAR y POR QUÉ, y contra qué número tuyo se compara.
   ========================================================================== */
.ir-dir-veredicto {
  margin: 10px 12px 4px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ir-dir-ver__rec {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-family: 'DM Sans', system-ui, sans-serif;
}
.ir-dir-ver__rec strong {
  font-size: calc(15px * var(--ir-letra, 1));
  color: var(--accent, #F58220);
}
.ir-dir-ver__rec span {
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.45;
  color: var(--text-muted, #9FB0C3);
}

/* La referencia va monoespaciada: es una cifra que se compara de un vistazo
   con las de las tarjetas, y las cifras se comparan alineadas. */
.ir-dir-ver__ref {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.5;
  color: var(--gold, #D4A84B);
  padding-top: 8px;
  border-top: 1px solid var(--line, #1E3A5F);
}
/* Sin historia el texto es más largo y explicativo: deja de ser una cifra. */
.ir-dir-ver__ref--vacio {
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(13px * var(--ir-letra, 1));
  color: var(--text-muted, #9FB0C3);
}

html[data-theme="light"] .ir-dir-veredicto {
  background: #F4F6FA;
  border-color: #D3DAE6;
}
html[data-theme="light"] .ir-dir-ver__rec span,
html[data-theme="light"] .ir-dir-ver__ref--vacio { color: #52627A; }
html[data-theme="light"] .ir-dir-ver__ref { color: #7A5E10; }

/* ── Entrar y salir: CSV ────────────────────────────────────────────────── */
.rg-csv {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  padding: 4px 0 10px;
  border-bottom: 1px solid var(--line, #1E3A5F);
  margin-bottom: 6px;
}
.rg-csv__b {
  min-height: var(--touch, 48px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 14px;
  border-radius: 10px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(13px * var(--ir-letra, 1));
  cursor: pointer;
}
.rg-csv__b:focus-within,
.rg-csv__b:focus-visible {
  outline: 2px solid var(--accent, #F58220);
  outline-offset: 2px;
}
/* El input de archivo se esconde pero NO con display:none: así el label sigue
   siendo alcanzable con teclado y el foco se ve. */
.rg-csv__file {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.rg-csv__msg {
  flex: 1 1 100%;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.45;
  min-height: 16px;
}
.rg-csv__msg--ok  { color: var(--success, #10B981); }
.rg-csv__msg--mal { color: var(--warning, #F5A623); }

html[data-theme="light"] .rg-csv__b {
  background: #F4F6F9;
  border-color: #D8DEE7;
  color: #0A1B33;
}

/* ───── css/flota-mapa.css ───── */
/*PORQUE La flota del TMS pintada encima del mapa (M2.1/M3.1). PORQUE*/
/* ==========================================================================
   IRON ROUTE — FLOTA EN EL MAPA (M2.1 / M3.1)

   Dos estados y nada más: reportando (verde) y sin señal (gris). Un tablero
   de despacho con seis colores no se lee de un vistazo, y el vistazo es todo
   lo que un dispatcher le dedica entre llamada y llamada.
   ========================================================================== */

.fm-icono { background: none; border: 0; }

.fm-pin {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--fm, #10B981);
  border: 2px solid #FFFFFF;
  box-shadow: 0 2px 6px rgba(3, 13, 28, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
}
.fm-pin__u {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));               /* el mínimo del sistema; no baja de aquí */
  font-weight: 700;
  color: #04121F;
  line-height: 1;
  letter-spacing: -0.02em;
}
/* Apagada: gris y con el borde punteado, para que se distinga también sin
   color — un 8% de los conductores no distingue verde de gris. */
.fm-pin--off {
  border-style: dashed;
  opacity: 0.85;
}
.fm-pin--off .fm-pin__u { color: #22303F; }

/* ── El globo ───────────────────────────────────────────────────────────── */
.fm-pop {
  font-family: 'DM Sans', system-ui, sans-serif;
  min-width: 190px;
  line-height: 1.35;
}
.fm-pop strong { font-size: calc(15px * var(--ir-letra, 1)); display: block; margin-bottom: 2px; }
.fm-pop__l { font-size: calc(13px * var(--ir-letra, 1)); color: var(--text-muted, #5A6B80); }
.fm-pop__edad {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  color: #0E7C5A;
  margin-top: 4px;
}
.fm-pop__edad--off { color: #B4531A; }
.fm-pop__sep {
  height: 1px;
  background: var(--line, #D8DEE7);
  margin: 8px 0 6px;
}
.fm-pop__carga { font-size: calc(14px * var(--ir-letra, 1)); font-weight: 600; }
.fm-pop__dinero {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 700;
  color: #0A6E4F;
  margin-top: 4px;
}

/* ── La barra de resumen ────────────────────────────────────────────────── */
.fm-resumen {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  z-index: 1200;
  display: flex;
  gap: 14px;
  align-items: center;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(10, 27, 51, 0.92);
  border: 1px solid var(--line, #1E3A5F);
  box-shadow: 0 4px 14px rgba(3, 13, 28, 0.35);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  color: var(--text, #F5F7FA);
  max-width: calc(100vw - 24px);
  flex-wrap: wrap;
  justify-content: center;
}
.fm-resumen[hidden] { display: none; }
.fm-resumen__ok  { color: #34D399; }
.fm-resumen__off { color: #F5A623; }
.fm-resumen__u   { color: var(--text-muted, #9FB0C3); }

.fm-resumen--aviso {
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(13px * var(--ir-letra, 1));
  color: var(--text, #F5F7FA);
  text-align: center;
  line-height: 1.4;
}

/* Teléfono: se aprieta el espaciado, NO el tamaño de letra. La regla del
   sistema es 12px de suelo absoluto y no se rompe por caber. */
@media (max-width: 480px) {
  .fm-resumen { gap: 10px; padding: 7px 12px; }
}

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

/* ───── css/perfiles-camion.css ───── */
/*PORQUE M0.2 · varios camiones guardados, elegibles al rutear. PORQUE*/
/* ==========================================================================
   IRON ROUTE — SELECTOR DE CAMIÓN (M0.2)

   Va dentro de la fila de ajustes del camión del planificador. Objetivos:
   48 px de alto real para el pulgar, 12 px de letra como suelo, y que la
   línea «Ruteando con: …» se lea de un vistazo, porque es la que evita que
   alguien cotice el flatbed con las medidas de la caja seca.
   ========================================================================== */

.pc-fila {
  display: flex;
  gap: 6px;
  align-items: stretch;
  margin-bottom: 6px;
  width: 100%;
}

.pc-sel {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--touch, 48px);
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(14px * var(--ir-letra, 1));
  cursor: pointer;
}

.pc-btn {
  flex: 0 0 auto;
  min-height: var(--touch, 48px);
  padding: 0 12px;
  border-radius: 8px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(13px * var(--ir-letra, 1));
  cursor: pointer;
  white-space: nowrap;
}
.pc-btn:active { background: rgba(245, 130, 32, 0.12); }
.pc-btn--x {
  min-width: var(--touch, 48px);
  padding: 0;
  font-size: calc(16px * var(--ir-letra, 1));
  color: var(--text-muted, #9FB0C3);
}
.pc-btn--x[hidden] { display: none; }
.pc-btn--x:hover { color: var(--error, #EF4444); }

.pc-sel:focus-visible,
.pc-btn:focus-visible {
  outline: 2px solid var(--accent, #F58220);
  outline-offset: 2px;
}

/* LA LÍNEA QUE IMPORTA. Es el dato que va al ruteador, escrito donde se ve.
   Monoespaciada a propósito: son cifras, y las cifras se comparan de un
   vistazo cuando están alineadas. */
.pc-nota {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.4;
  color: var(--text-muted, #9FB0C3);
  padding: 2px 2px 6px;
  word-break: break-word;
}
.pc-nota--ok { color: var(--success, #10B981); }

/* Tema claro */
html[data-theme="light"] .pc-sel,
html[data-theme="light"] .pc-btn,
body.light-theme .pc-sel,
body.light-theme .pc-btn {
  background: #F4F6F9;
  border-color: #D8DEE7;
  color: #0A1B33;
}
html[data-theme="light"] .pc-nota,
body.light-theme .pc-nota { color: #5A6B80; }

/* Pantalla estrecha: el botón de guardar pasa debajo antes que encoger la
   letra. La regla de 12 px de suelo no se rompe por caber. */
@media (max-width: 420px) {
  .pc-fila { flex-wrap: wrap; }
  .pc-sel { flex: 1 1 100%; }
  .pc-btn { flex: 1 1 auto; }
  .pc-btn--x { flex: 0 0 var(--touch, 48px); }
}

/* ───── css/reportar-restriccion.css ───── */
/*PORQUE M0.6 · «esto esta mal»: el conductor reporta lo que el mapa no sabe. PORQUE*/
/* ==========================================================================
   IRON ROUTE — «ESTO ESTÁ MAL» (M0.6)

   Se diseña para alguien que acaba de parar en el arcén y tiene un minuto.
   Objetivos grandes, poco texto, y el botón de mandar donde cae el pulgar.
   ========================================================================== */

/* ── El lanzador, flotando sobre el mapa ────────────────────────────────── */
.rr-lanzar {
  position: fixed;
  left: 12px;
  bottom: calc(88px + env(safe-area-inset-bottom, 0px));
  z-index: 1150;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: var(--touch, 48px);
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--line, #1E3A5F);
  background: rgba(10, 27, 51, 0.92);
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 3px 12px rgba(3, 13, 28, 0.4);
}
.rr-lanzar__ic { font-size: calc(16px * var(--ir-letra, 1)); line-height: 1; }
.rr-lanzar:active { background: rgba(245, 130, 32, 0.22); }
.rr-lanzar:focus-visible { outline: 2px solid var(--accent, #F58220); outline-offset: 2px; }

/* En escritorio el dispatcher no está en la carretera: el botón se aparta
   para no competir con los controles del mapa. */
.ir-escritorio .rr-lanzar { left: auto; right: 12px; bottom: 12px; }

/* ── La ventana ─────────────────────────────────────────────────────────── */
.rr-capa {
  position: fixed;
  inset: 0;
  z-index: 2000;
  background: rgba(3, 13, 28, 0.66);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
}
@media (min-width: 700px) {
  .rr-capa { align-items: center; padding: 24px; }
}

.rr-caja {
  width: 100%;
  max-width: 560px;
  max-height: 92vh;
  max-height: 92dvh;
  overflow-y: auto;
  background: var(--surface, #0A1B33);
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 16px 16px 0 0;
  padding: 18px 16px calc(18px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -10px 40px rgba(3, 13, 28, 0.5);
}
@media (min-width: 700px) { .rr-caja { border-radius: 16px; } }

.rr-h {
  font-family: 'Syne', system-ui, sans-serif;
  font-size: calc(20px * var(--ir-letra, 1));
  margin: 0 0 8px;
  color: var(--text, #F5F7FA);
}
.rr-p {
  margin: 0 0 14px;
  font-size: calc(14px * var(--ir-letra, 1));
  line-height: 1.5;
  color: var(--text-muted, #9FB0C3);
}

/* ── La rejilla de tipos ────────────────────────────────────────────────── */
.rr-tipos {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 14px;
}
@media (min-width: 560px) { .rr-tipos { grid-template-columns: 1fr 1fr 1fr; } }

.rr-tipo {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  /* 68px, no 48: se pulsa con el camión recién parado y a veces con guantes.
     Aquí el objetivo grande no es lujo, es que el reporte llegue. */
  min-height: 68px;
  padding: 10px 8px;
  border-radius: 12px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
  cursor: pointer;
  text-align: center;
}
.rr-tipo__ic { font-size: calc(22px * var(--ir-letra, 1)); line-height: 1; }
.rr-tipo__n  { font-size: calc(13px * var(--ir-letra, 1)); line-height: 1.25; }
.rr-tipo.on {
  border-color: var(--accent, #F58220);
  background: rgba(245, 130, 32, 0.14);
}
.rr-tipo:focus-visible { outline: 2px solid var(--accent, #F58220); outline-offset: 2px; }

/* ── Campos ─────────────────────────────────────────────────────────────── */
.rr-campo { margin-bottom: 12px; }
.rr-campo[hidden] { display: none; }

.rr-lbl {
  display: block;
  font-size: calc(13px * var(--ir-letra, 1));
  color: var(--text-muted, #9FB0C3);
  margin-bottom: 5px;
  font-family: 'DM Sans', system-ui, sans-serif;
}
.rr-fila { display: flex; align-items: center; gap: 8px; }
.rr-u {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(13px * var(--ir-letra, 1));
  color: var(--text-muted, #9FB0C3);
}

.rr-in, .rr-ta {
  min-height: var(--touch, 48px);
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--ink, #030D1C);
  color: var(--text, #F5F7FA);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  /* 16px exactos: por debajo de eso, iOS hace zoom al enfocar el campo y le
     descoloca la pantalla a alguien que está parado en un arcén. */
  font-size: calc(16px * var(--ir-letra, 1));
  width: 84px;
}
.rr-in--ancho { width: 100%; }
.rr-ta {
  width: 100%;
  font-family: 'DM Sans', system-ui, sans-serif;
  resize: vertical;
  margin-bottom: 12px;
}
.rr-in:focus-visible, .rr-ta:focus-visible {
  outline: 2px solid var(--accent, #F58220);
  outline-offset: 1px;
}

/* ── Mensajes ───────────────────────────────────────────────────────────── */
.rr-msg {
  min-height: 20px;
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.45;
  margin-bottom: 10px;
  color: var(--text-muted, #9FB0C3);
}
.rr-msg--ok  { color: var(--success, #10B981); }
.rr-msg--mal { color: var(--warning, #F5A623); }

/* ── Acciones ───────────────────────────────────────────────────────────── */
.rr-acciones { display: flex; gap: 10px; }

.rr-btn {
  flex: 1 1 auto;
  min-height: var(--touch, 48px);
  padding: 0 16px;
  border-radius: 10px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(15px * var(--ir-letra, 1));
  font-weight: 600;
  cursor: pointer;
}
.rr-btn--ok {
  background: var(--accent, #F58220);
  border-color: var(--accent, #F58220);
  color: #04121F;
}
.rr-btn--ok:disabled { opacity: 0.6; cursor: default; }
.rr-btn--x { width: 100%; margin-top: 6px; }
.rr-btn:focus-visible { outline: 2px solid var(--gold, #D4A84B); outline-offset: 2px; }

/* Tema claro */
html[data-theme="light"] .rr-caja  { background: #FFFFFF; border-color: #D3DAE6; }
html[data-theme="light"] .rr-tipo  { background: #F4F6FA; border-color: #D3DAE6; color: #16233A; }
html[data-theme="light"] .rr-in,
html[data-theme="light"] .rr-ta    { background: #FFFFFF; border-color: #D3DAE6; color: #16233A; }
html[data-theme="light"] .rr-h     { color: #16233A; }
html[data-theme="light"] .rr-lanzar{ background: #FFFFFF; border-color: #D3DAE6; color: #16233A; }

@media (prefers-reduced-motion: reduce) {
  .rr-capa, .rr-caja, .rr-tipo { transition: none; animation: none; }
}

/* El contador de la cola sin señal. Va en el propio botón: si hay reportes
   esperando, el conductor tiene que verlo sin abrir nada. */
.rr-lanzar__cola {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--warning, #F5A623);
  color: #1B1403;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  font-weight: 700;
  line-height: 1;
}

/* Los dos grupos de la hoja. El título los separa por VIDA ÚTIL, no por
   sistema: lo que dura meses (un puente) y lo que caduca solo (un accidente).
   El conductor no tiene por qué saber que por dentro son dos cosas. */
.rr-grupo {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted, #9FB0C3);
  margin: 4px 0 8px;
  padding-top: 10px;
  border-top: 1px solid var(--line, #1E3A5F);
}
.rr-tipos + .rr-grupo { margin-top: 12px; }
/* Las alertas van visualmente más ligeras: son de menos peso y caducan solas. */
.rr-tipo--alerta { min-height: 58px; }
.rr-tipo--alerta .rr-tipo__ic { font-size: calc(19px * var(--ir-letra, 1)); }
html[data-theme="light"] .rr-grupo { color: #52627A; border-top-color: #D3DAE6; }

/* ══════════════════════════════════════════════════════════════════════════
   LA ✕ QUE NO ESTABA

   El dueño se quedó atrapado en esta hoja en su teléfono y tuvo que recargar
   la página para volver al mapa. Ahora hay tres salidas —la ✕, tocar fuera y
   Escape— y esta es la que se ve.

   Va dentro de la tarjeta y no sobre el fondo, porque en un teléfono la
   tarjeta ocupa casi todo y una ✕ flotando fuera cae en el borde de la
   pantalla, donde el pulgar la falla.
   ══════════════════════════════════════════════════════════════════════════ */
.rr-caja { position: relative; }

.rr-x {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 48px;              /* con guantes, 44 no alcanza */
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: rgba(127, 127, 127, .16);
  color: inherit;
  font-size: calc(26px * var(--ir-letra, 1));
  line-height: 1;
  cursor: pointer;
  z-index: 3;
}
.rr-x:hover  { background: rgba(127, 127, 127, .26); }
.rr-x:active { background: rgba(127, 127, 127, .34); }
.rr-x:focus-visible { outline: 3px solid #D4A84B; outline-offset: 2px; }

/* Y el título deja sitio a la ✕, para que no se solapen en una pantalla
   estrecha con el texto largo en inglés. */
.rr-h { padding-right: 56px; }

/* ══════════════════════════════════════════════════════════════════════════
   EL LANZADOR, REDUCIDO AL ICONO — pedido del dueño

   «Botón reportar tiene que estar reducido, solo la señalización, solo el
    ícono. Cuando uno le dé clic, ahí se muestra normal.»

   Y tiene razón por una medida, no por gusto: la píldora «⚠️ Reportar» mide
   127×48 y se sienta sobre la fila de accesos rápidos —«PLANIFICADOR» queda
   medio tapado en las capturas de producción—. En un mapa, el mapa es el
   producto: cada píxel de cromo permanente es mapa que el conductor no ve.

   Waze y Google Maps hacen exactamente esto: un círculo con el icono, y el
   nombre solo aparece cuando hace falta.

   QUÉ NO SE PIERDE: el nombre sigue existiendo para quien no ve el icono —
   `aria-label` en el botón y `.rr-lanzar__n` queda como texto solo-para-lectores,
   no `display: none`, que se lo llevaría también del lector de pantalla.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 899px) {
  .rr-lanzar {
    width: 56px;
    min-width: 56px;
    height: 56px;
    min-height: 56px;
    padding: 0;
    gap: 0;
    justify-content: center;
    border-radius: 50%;
  }
  /* Fuera de la vista, dentro del árbol de accesibilidad. */
  .rr-lanzar .rr-lanzar__n {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  /* El icono, más grande: es lo único que queda para reconocerlo. */
  .rr-lanzar .rr-lanzar__ic { font-size: calc(24px * var(--ir-letra, 1)); }
}

/* ───── css/onboarding-v2.css ───── */
/*PORQUE Onboarding v2: bienvenida completa + pantalla de permisos. Va DESPUES
       de las 9 hojas del onboarding para ganar por orden, no con !important. PORQUE*/
/* ==========================================================================
   IRON ROUTE — ONBOARDING v2

   POR QUÉ UNA HOJA MÁS Y NO UNA REESCRITURA DE LAS NUEVE QUE HAY:

   El onboarding arrastra 9 hojas con 103 `!important` repartidos. Reescribirlas
   todas en caliente, sobre un producto en producción, es la forma más rápida de
   romper cinco pantallas para arreglar una. Esta hoja va LA ÚLTIMA y gana por
   orden de carga, no a codazos: **cero `!important` en todo el archivo**.

   Lo que cambia:
     · La bienvenida deja de ser un folleto de tres viñetas y pasa a decir algo.
     · Los permisos tienen su pantalla, legible con el pulgar.
     · El escritorio deja de ser un teléfono estirado: dos columnas de verdad.
     · Contraste medido, no «se ve bien en mi pantalla».
   ========================================================================== */

/* ── Escala tipográfica de la pantalla de bienvenida ───────────────────── */
.ob-welcome .ob-h1 {
  font-family: 'Syne', system-ui, sans-serif;
  font-size: clamp(24px, 5.2vw, 38px);
  line-height: 1.12;
  letter-spacing: -0.02em;
  margin: 10px 0 12px;
  text-wrap: balance;
}

/* El párrafo que de verdad convence. 16px de suelo: es texto para leer, no
   una etiqueta, y por debajo de eso nadie lo lee en un teléfono al sol. */
.ob-lead {
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: clamp(15px, 3.6vw, 17px);
  line-height: 1.55;
  color: var(--text-muted, #9FB0C3);
  margin: 0 0 18px;
  max-width: 62ch;
}
.ob-lead strong { color: var(--text, #F5F7FA); }

/* ── Las cuatro capacidades ────────────────────────────────────────────── */
.ob-caps {
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
  display: grid;
  gap: 10px;
}
.ob-caps li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px 14px;
  border-radius: 12px;
  background: var(--surface-2, #10294A);
  border: 1px solid var(--line, #1E3A5F);
}
.ob-caps__ic {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent, #F58220);
}
.ob-caps__ic svg { width: 22px; height: 22px; }
.ob-caps li strong {
  display: block;
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(15px * var(--ir-letra, 1));
  font-weight: 600;
  line-height: 1.3;
  margin-bottom: 3px;
  color: var(--text, #F5F7FA);
}
.ob-caps li span {
  display: block;
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.45;
  color: var(--text-muted, #9FB0C3);
}

/* ── La promesa de privacidad ──────────────────────────────────────────── */
.ob-priv {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid rgba(16, 185, 129, 0.35);
  background: rgba(16, 185, 129, 0.08);
  margin-bottom: 18px;
}
.ob-priv strong {
  font-size: calc(14px * var(--ir-letra, 1));
  /* Medido sobre el tinte verde del recuadro en tema claro: `--success`
     (#0A7D53) daba 4,49:1, justo por debajo del suelo. `--ob-exito` es el
     verde del alta, ya medido en los dos temas (6,1:1 en claro). */
  color: var(--ob-exito, #10B981);
  font-family: 'DM Sans', system-ui, sans-serif;
}
.ob-priv span {
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.45;
  color: var(--text-muted, #9FB0C3);
}

/* ── Salida honesta y confianza ────────────────────────────────────────── */
.ob-btn-link {
  background: none;
  border: 0;
  color: var(--text-muted, #9FB0C3);
  text-decoration: underline;
  text-underline-offset: 3px;
  font-size: calc(14px * var(--ir-letra, 1));
  min-height: var(--touch, 48px);
  cursor: pointer;
  font-family: 'DM Sans', system-ui, sans-serif;
}
.ob-btn-link:hover { color: var(--text, #F5F7FA); }

.ob-ver-nota {
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.45;
  color: var(--text-muted, #9FB0C3);
  margin: -4px 0 16px;
  text-align: center;
}

.ob-trust {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  text-align: center;
  padding-top: 14px;
  border-top: 1px solid var(--line, #1E3A5F);
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.5;
  color: var(--text-muted, #9FB0C3);
}
.ob-trust__ids {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  color: var(--gold, #D4A84B);
  letter-spacing: 0.04em;
}
/* Llevaba `opacity: 0.8` encima de `--text-muted`, que YA es el color
   apagado: apagar dos veces dejaba los datos de la empresa —USDOT, MC, la
   razón social— en 3,76:1. Es la única prueba de confianza verificable de la
   pantalla; tiene que leerse. Sin la opacidad da 6,2:1. */
.ob-trust__legal { opacity: 1; }

/* ══════════════════════════════════════════════════════════════════════════
   PANTALLA DE PERMISOS
   ══════════════════════════════════════════════════════════════════════════ */
.ob-perm-todos {
  width: 100%;
  min-height: var(--touch, 48px);
  margin-bottom: 14px;
}

.ob-perm-lista {
  display: grid;
  gap: 10px;
  margin-bottom: 16px;
}

.ob-perm {
  display: grid;
  grid-template-columns: 34px 1fr auto;
  gap: 12px;
  align-items: start;
  padding: 12px 14px;
  border-radius: 12px;
  background: var(--surface-2, #10294A);
  border: 1px solid var(--line, #1E3A5F);
}
.ob-perm__ic {
  font-size: calc(22px * var(--ir-letra, 1));
  line-height: 1.2;
  text-align: center;
}
.ob-perm__c { min-width: 0; }

.ob-perm__t {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(15px * var(--ir-letra, 1));
  font-weight: 600;
  color: var(--text, #F5F7FA);
  margin-bottom: 4px;
}
/* «esencial» describe qué se rompe sin él — NO significa obligatorio.
   Nada en esta pantalla lo es. */
.ob-perm__clave {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent, #F58220);
  border: 1px solid currentColor;
  border-radius: 999px;
  padding: 1px 8px;
}

.ob-perm__p {
  margin: 0 0 4px;
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.45;
  color: var(--text-muted, #9FB0C3);
}
/* «Sin esto: …» va en otro color porque es la información que de verdad
   permite decidir, y es la que todo el mundo omite. */
.ob-perm__sin {
  margin: 0 0 6px;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.45;
  color: var(--warning, #F5A623);
}

.ob-perm__e {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.4;
  color: var(--text-muted, #9FB0C3);
}
.ob-perm__e[data-estado="granted"] { color: var(--success, #10B981); }
.ob-perm__e[data-estado="denied"]  { color: var(--warning, #F5A623); }

.ob-perm__b {
  min-height: var(--touch, 48px);
  min-width: 88px;
  padding: 0 14px;
  border-radius: 10px;
  border: 1px solid var(--accent, #F58220);
  background: transparent;
  color: var(--accent, #F58220);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 600;
  cursor: pointer;
}
.ob-perm__b:disabled {
  border-color: var(--line, #1E3A5F);
  color: var(--text-muted, #9FB0C3);
  cursor: default;
}
.ob-perm__b:focus-visible,
.ob-perm-todos:focus-visible,
.ob-btn-link:focus-visible {
  outline: 2px solid var(--gold, #D4A84B);
  outline-offset: 2px;
}

.ob-perm-privacidad {
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid rgba(16, 185, 129, 0.35);
  background: rgba(16, 185, 129, 0.08);
}
.ob-perm-privacidad p {
  margin: 0;
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.5;
  color: var(--text-muted, #9FB0C3);
}

/* ══════════════════════════════════════════════════════════════════════════
   ESCRITORIO: DOS COLUMNAS, NO UN TELÉFONO ESTIRADO

   A partir de 900 px de ancho con puntero fino (ratón), quien está delante es
   un dispatcher, no un conductor. La tarjeta deja de ser una columna estrecha
   centrada —que en un monitor de 27" se ve ridícula— y usa el ancho.
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 900px) {
  .ob-welcome .ob-caps { grid-template-columns: 1fr 1fr; }
  .ob-welcome .ob-cta-row { justify-content: flex-start; }
  .ob-perm-lista { grid-template-columns: 1fr 1fr; }
  .ob-perm-todos { width: auto; min-width: 220px; }
  .ob-trust { align-items: flex-start; text-align: left; }
}

/* Puntero fino: el hover existe y el objetivo puede ser más pequeño sin
   perder precisión. Aun así no se baja de 40 px: un ratón acierta, pero una
   fila de 24 px sigue siendo incómoda de leer y de pulsar. */
@media (pointer: fine) {
  .ob-perm__b { min-height: 40px; }
  .ob-perm__b:hover:not(:disabled) {
    background: var(--accent, #F58220);
    color: #04121F;
  }
  .ob-caps li:hover { border-color: var(--accent, #F58220); }
}

/* Pantalla baja y apaisada (teléfono de lado en el soporte de la cabina):
   lo que sobra es el aire, no el contenido. */
@media (max-height: 560px) and (orientation: landscape) {
  .ob-welcome .ob-logo-wrap { display: none; }
  .ob-caps li { padding: 9px 12px; }
  .ob-priv { padding: 9px 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .ob-caps li, .ob-perm, .ob-perm__b { transition: none; }
}

/* ───── css/onboarding-pantalla-completa.css ───── */
/*PORQUE El onboarding deja de ser un pop-up: pantalla completa de verdad. PORQUE*/
/* ==========================================================================
   IRON ROUTE — EL ONBOARDING DEJA DE SER UN POP-UP

   LA CRÍTICA DEL DUEÑO, TRES VECES SEGUIDAS:
   «eso para un frame o un pop up y así no» · «sigue igual el onboarding».

   Tenía razón las tres. Yo mejoré el CONTENIDO y dejé la presentación intacta:
   `.ob-card` es una caja de `720×820` centrada, con `border-radius`, sombra y
   un fondo oscurecido detrás. Eso es un diálogo de cookies, no la puerta de
   entrada de un producto profesional — y en un monitor de 27" es una cajita en
   el centro con una barra de scroll interna y el contenido cortado a la mitad.

   Un onboarding es la PRIMERA IMPRESIÓN del producto. Si se presenta como algo
   que hay que quitarse de encima, la gente se lo quita de encima.

   ESTA HOJA LO CONVIERTE EN PANTALLA COMPLETA. Va la última y gana por orden
   de carga, sin un solo `!important`.

   LO QUE CAMBIA Y POR QUÉ:
     1. La tarjeta ocupa toda la pantalla. Sin bordes redondeados, sin sombra,
        sin fondo oscurecido detrás: no hay un «detrás», ESTO es la pantalla.
     2. El scroll pasa al CUERPO, no a una caja interna. Una barra de scroll
        dentro de un recuadro dentro de la pantalla es el síntoma visual de
        «esto es un modal».
     3. El contenido se centra en una columna legible (máx. 780px) en vez de
        estirarse a 1440. Una línea de texto de 1400px no la lee nadie.
     4. En escritorio se reparte en dos columnas: el progreso a la izquierda,
        fijo, y el contenido a la derecha. Un teléfono estirado, nunca.
     5. La X y «Saltar» dejan de parecer «cerrar el anuncio» y pasan a ser una
        salida discreta arriba a la derecha.
   ========================================================================== */

/* ── 1 · A pantalla completa ─────────────────────────────────────────────── */
.ob-root {
  display: block;
}

.ob-backdrop {
  /* Ya no es un «fondo detrás del modal»: es EL fondo de la pantalla. */
  position: fixed;
  inset: 0;
}

.ob-card {
  width: 100vw;
  max-width: none;
  height: 100vh;
  height: 100dvh;      /* la barra de Safari no puede comerse el botón */
  max-height: none;
  border-radius: 0;
  border: 0;
  box-shadow: none;
  /* La animación de «modal que aparece» sobraba: esto no aparece encima de
     nada, es la pantalla. */
  transform: none;
}
.ob-root.ob-show .ob-card { transform: none; }

/* ── 2 · El scroll va en el cuerpo ──────────────────────────────────────── */
.ob-card > [data-body],
.ob-body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* ── 3 · Una columna legible, centrada ──────────────────────────────────── */
.ob-step {
  width: 100%;
  max-width: 780px;
  margin: 0 auto;
  padding: 24px 20px 32px;
}

/* ── 4 · La cabecera y el pie, pegados a los bordes ─────────────────────── */
.ob-header {
  padding-top: calc(12px + env(safe-area-inset-top, 0px));
  border-bottom: 1px solid var(--line, #1E3A5F);
}
.ob-footer {
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--line, #1E3A5F);
}
.ob-header > *,
.ob-footer > * {
  max-width: 780px;
  margin-left: auto;
  margin-right: auto;
  width: 100%;
}

/* ── 5 · EL TITULAR CON LA CAJA BLANCA ──────────────────────────────────────
   En la captura, «Navegación GPS profesional para camiones» sale dentro de un
   rectángulo blanco con borde. No es un adorno: es un `background` o un
   `-webkit-text-stroke` heredado de alguna de las nueve hojas del onboarding
   que se aplica a un `h1` sin fondo propio. Se neutraliza aquí, que es donde
   se puede hacer sin tocar las nueve.
   ────────────────────────────────────────────────────────────────────────── */
.ob-welcome .ob-h1,
.ob-step .ob-h1 {
  background: none;
  background-color: transparent;
  border: 0;
  box-shadow: none;
  padding: 0;
  -webkit-text-stroke: 0;
  text-shadow: none;
  color: var(--text, #F5F7FA);
}

/* ── LA «CAJA» ALREDEDOR DEL TITULAR NO ERA UN BORDE ────────────────────────

   Se midió: `border: 0px none`, `background: rgba(0,0,0,0)`, `box-shadow:
   none`. Lo que dibuja el rectángulo es un **anillo de foco**:
   `outline: rgb(212,168,75) solid 2px` — el dorado del sistema.

   Y está ahí por una buena razón. El `<h1>` lleva `tabindex="-1"` y el
   onboarding le pone el foco al cambiar de paso, que es el patrón correcto:
   sin eso, un lector de pantalla no anuncia que la pantalla cambió y la
   persona se queda oyendo el paso anterior.

   El problema no es el foco: es que se PINTE. Un anillo puesto por código,
   sobre alguien que está usando el ratón, se lee como un recuadro decorativo
   mal hecho — es exactamente lo que el dueño señaló dos veces.

   La regla estándar para esto es distinguir el foco programático del foco por
   teclado. `:focus-visible` ya lo hace: el navegador solo lo activa cuando la
   persona llegó navegando. Así que se apaga el anillo en `:focus` cuando NO
   es `:focus-visible`, y **con teclado se sigue viendo**, que es cuando hace
   falta.

   PRIMER INTENTO, Y POR QUÉ NO BASTÓ: se apagó con
   `:focus:not(:focus-visible)` y el anillo **siguió saliendo**. Medido: el
   navegador considera `:focus-visible` un foco movido por código cuando no ha
   habido una interacción de puntero previa. O sea que la distinción no
   resuelve este caso.

   LO QUE SÍ LO RESUELVE, y además es lo correcto: `tabindex="-1"` significa,
   por definición, **que al teclado no se llega ahí nunca**. No hay ninguna
   situación en la que alguien navegando con Tab aterrice en este titular, así
   que no hay ningún caso de teclado que preservar. Se apaga y punto.

   Lo que NO se puede hacer es `outline: 0` a secas sobre cualquier elemento
   —que es lo que había antes en el bloque de arriba—: eso se lleva por
   delante el anillo de los botones y los enlaces, que sí se recorren con Tab
   y sí necesitan verse. Por eso la regla va atada a `[tabindex="-1"]`.
   ────────────────────────────────────────────────────────────────────────── */
.ob-root [tabindex="-1"]:focus,
.ob-card [tabindex="-1"]:focus,
.ob-h1[tabindex="-1"]:focus,
.ob-h1[tabindex="-1"]:focus-visible {
  outline: none;
  box-shadow: none;
}

/* ── 6 · Escritorio: UNA columna, centrada ──────────────────────────────────

   LO QUE HABÍA AQUÍ ESTABA MAL, Y SE VE EN LA CAPTURA DEL DUEÑO.

   Esta hoja convertía `.ob-card` en una rejilla de dos columnas —300 px de
   raíl a la izquierda y el resto para el contenido— con la idea de poner ahí
   el progreso. **El raíl nunca se llenó.** Era un `::before` con
   `content: ''`: un degradado y nada más. Cero texto, cero pasos, cero
   información.

   Medido a 1920×1080:

     · `.ob-card`  → rejilla `300px 1620px`
     · `.ob-step`  → 720 px de ancho colocado en x=322
     · su centro   → 682 px, cuando el centro de la pantalla es 960

   O sea: **una franja negra vacía de 300 px a la izquierda y el texto 278 px
   descentrado**. Y la cabecera —con «Saltar» y la X— empezaba en x=300, así
   que tampoco llegaba al borde.

   Un elemento decorativo que empuja el contenido fuera del centro no es un
   adorno: es un defecto de composición. Y la regla es la de siempre — entre
   dos diseños equivalentes, el más simple. El raíl no aportaba nada que no
   aporte ya la barra de pasos que va dentro del propio contenido.

   ASÍ QUE SE QUITA. Una sola columna, centrada de verdad en la pantalla, con
   el ancho de línea acotado para que se pueda leer.
   ────────────────────────────────────────────────────────────────────────── */
@media (min-width: 1024px) and (pointer: fine) {
  /* Se deshace explícitamente la rejilla: si esta hoja se cargara junto a una
     versión vieja en caché, `display: flex` gana por orden y el raíl no
     vuelve. */
  .ob-card {
    display: flex;
    flex-direction: column;
  }
  .ob-card::before { content: none; display: none; }

  /* El contenido, centrado en TODO el ancho de la pantalla. */
  .ob-step {
    padding: 40px 32px 56px;
    margin: 0 auto;
    max-width: 720px;
  }

  /* La cabecera y el pie ocupan el ancho completo: la X y «Saltar» van a la
     esquina, que es donde se buscan. Su CONTENIDO se alinea con la columna
     del texto para que nada quede a la deriva en un monitor de 27". */
  .ob-header,
  .ob-footer { width: 100%; }

  /* 45–75 caracteres por línea. A 16 px, 720 px dan unos 90 — demasiados: la
     vista pierde el renglón al volver a la izquierda. La prosa se acota a
     620; las rejillas de tarjetas siguen usando el ancho entero. */
  .ob-step > p,
  .ob-sub,
  .ob-lead { max-width: 620px; margin-left: auto; margin-right: auto; }

  /* Con ratón el objetivo puede bajar de 48 a 40, nunca menos — pero EL ANCHO
     NO DICE SI HAY RATÓN. Esta regla vivía en una media query de ancho, así
     que una tableta de 1024 px, que se toca con el dedo, recibía botones de
     40. Medido el 21-sep-2026: «Get Started», «I have an account» y «See the
     map first» salían a 42 px en la primera pantalla que ve alguien nuevo.
     `pointer: fine` es la pregunta de verdad: ¿hay un puntero preciso? */
  @media (pointer: fine) {
    .ob-btn { min-height: 40px; }
  }
}

/* ── 6b · QUE «COMENZAR» QUEPA SIN BAJAR ────────────────────────────────────

   MEDIDO en la misma pantalla del dueño (1920×1080, ventana real 1014 px):
   el contenido del primer paso mide **1122 px**. O sea que el botón principal
   —«Comenzar», que es la única acción de esta pantalla— cae **por debajo del
   borde**, y con él el aviso de USDOT/MC.

   Un botón primario que hay que buscar desplazando no es un detalle de
   estilo. Es la acción que la pantalla existe para provocar, y quien no la ve
   asume que la página no ha cargado del todo.

   De dónde salían esos 1122 px, medido hijo por hijo:

     · logo …………… 194 px  ← el bloque más alto de la pantalla, y la imagen
                             ni siquiera carga (el archivo devuelve 404)
     · relleno ……… 96 px  (40 arriba + 56 abajo)
     · titular ……… 150 px
     · entradilla … 123 px
     · capacidades  296 px
     · privacidad … 107 px
     · acciones ……  68 px
     · confianza ……  77 px

   Se recorta el aire, no el contenido: no se quita ni una frase. El titular,
   las cuatro capacidades, la promesa de privacidad y los datos de USDOT/MC
   siguen enteros — solo dejan de estar separados por el espacio de un
   monitor que casi nadie tiene.

   Va acotado a `max-height: 1200px` para que en una pantalla de verdad alta
   la composición respire como estaba pensada.
   ────────────────────────────────────────────────────────────────────────── */
@media (min-width: 1024px) and (max-height: 1200px) and (pointer: fine) {
  .ob-step { padding: 20px 32px 28px; }

  /* El logo baja de 194 a ~100: sigue siendo lo primero que se ve, deja de
     ser lo más grande de la pantalla.

     El alto NO está en el `padding` del envoltorio sino en `.ob-logo-frame`,
     que mide 160×160 fijos. Y el `padding` del envoltorio va con `!important`
     en `onboarding-p0-fixes.css`, así que hay que igualarlo — es el precio de
     los 103 `!important` que ya hay repartidos por estas nueve hojas, y por
     eso aquí no se añade ninguno nuevo salvo el que hace falta para empatar
     con uno existente. */
  .ob-logo-wrap  { padding: 4px 0 6px !important; }
  .ob-logo-frame { width: 92px; height: 92px; }

  /* `.ob-welcome .ob-h1` (dos clases) gana a `.ob-h1` (una) por
     especificidad, aunque esta hoja se cargue después. Se escribe con el
     mismo peso en vez de subirlo con `!important`. */
  .ob-welcome .ob-h1,
  .ob-step .ob-h1 { margin-top: 4px; margin-bottom: 8px; }
  .ob-lead { margin-bottom: 12px; }
  .ob-caps { margin-bottom: 12px; }
  .ob-priv { padding-top: 9px; padding-bottom: 9px; margin-bottom: 12px; }

  .ob-cta-row  { margin-top: 16px; }
  .ob-trust    { padding-top: 10px; }
  .ob-lang-pick { margin-bottom: 0; }
}

/* ── 6c · LA ESQUINA DE ARRIBA A LA DERECHA, DONDE SE PISABAN TRES COSAS ────

   MEDIDO a 375×812, que es un iPhone normal:

     .ob-skip      48×48 @ x=321  (borde derecho 369 — pegado al canto)
     .ob-h-btn     24×44 @ x=375  ←← LA X ESTÁ FUERA DE LA PANTALLA
     .ob-lang-pick 196×48 @ x=161 ←← y encima, tapando a «Saltar»

   **En un teléfono no se puede cerrar el onboarding con la X.** El botón
   existe, responde, y está pintado más allá del borde derecho. Queda
   «Saltar», que salva la situación por los pelos — pero la X es donde la
   gente busca cerrar, y no estar es no estar.

   POR QUÉ: `onboarding-p0-fixes.css` fija
   `grid-template-columns: 44px 1fr 44px !important` por debajo de 480 px. La
   tercera columna mide 44 px y tiene que contener «Saltar» (48) MÁS la X
   (44): unos 98 px. Lo que no cabe se sale, y en una rejilla se sale hacia
   fuera, no hacia dentro.

   Un `!important` con un ancho fijo es una promesa de que el contenido nunca
   crecerá. Aquí creció — se añadió «Saltar» — y nadie volvió a mirar la
   promesa.

   Y aparte, el selector de idioma va `position: absolute; top: 14px;
   right: 18px`, o sea clavado exactamente en esa misma esquina. Dos cosas
   distintas peleando por el mismo sitio, en las dos superficies.

   LA SOLUCIÓN, sin subir la escalada de `!important` más de lo justo:
     · La tercera columna pasa a `auto`: se lleva el ancho que necesite y el
       centro (los puntitos del paso) cede, que es lo que puede ceder.
     · El idioma sale de la esquina y baja al flujo, centrado arriba del
       contenido. Es además donde tiene sentido: elegir idioma es la primera
       decisión, no un adorno de la barra.
   ────────────────────────────────────────────────────────────────────────── */
.ob-lang-pick {
  position: static;
  justify-content: center;
  margin: 0 auto 10px;
}

@media (max-width: 480px) {
  /* `auto` en vez de 44px fijos: el `!important` iguala al que ya estaba
     puesto en `onboarding-p0-fixes.css`; no se sube el peso, se empata. */
  .ob-header { grid-template-columns: 44px minmax(0, 1fr) auto !important; }

  /* Que el bloque de acciones no se salga aunque crezca de nuevo. */
  .ob-h-actions {
    justify-self: end;
    flex-wrap: nowrap;
    min-width: 0;
  }
  /* «Saltar» se aprieta un poco para dejarle sitio a la X. El objetivo sigue
     en 44 px de alto, que es lo que decide si el dedo acierta. */
  .ob-skip { padding: 0 10px; font-size: calc(13px * var(--ir-letra, 1)); }

  /* Los puntitos del paso son lo único prescindible de esa fila: se recortan
     antes que un botón. */
  .ob-h-mid { min-width: 0; overflow: hidden; }
}

/* ── 7 · La salida, discreta ────────────────────────────────────────────── */
.ob-header [data-skip],
.ob-header [data-close] {
  opacity: 0.75;
}
.ob-header [data-skip]:hover,
.ob-header [data-close]:hover { opacity: 1; }

/* ── 8 · Apaisado y pantallas bajas ─────────────────────────────────────── */
@media (max-height: 560px) {
  .ob-step { padding: 14px 18px 20px; }
  .ob-logo-wrap { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .ob-card, .ob-root { transition: none; animation: none; }
}

/* ── 9 · NINGÚN AVISO ENCIMA DEL ONBOARDING ─────────────────────────────────
   En la captura del dueño flotaba «🌤 Sin alertas climáticas en el área»
   encima del titular. Envolver `showToast` no bastó: varios módulos crean el
   nodo del aviso a mano, sin pasar por esa función.

   Así que se corta también en CSS, que funciona sin importar quién lo cree.
   Es defensa en profundidad, no redundancia: el JS los ENCOLA para soltarlos
   después (por si alguno importa) y esto garantiza que ninguno se vea mientras
   tanto, incluido el que escriba el módulo que se añada mañana.
   ────────────────────────────────────────────────────────────────────────── */
html.ir-pre-onboard .ir-toast,
html.ir-pre-onboard .toast,
html.ir-pre-onboard .ir-conn-banner,
html.ir-pre-onboard [class*="toast"]:not(.ob-toast),
html.ir-pre-onboard .ir-alert-fab {
  display: none;
}

/* ───── css/onboarding-movil.css ───── */
/*PORQUE El onboarding en un telefono: cabe en una pantalla, sin scroll. Va
       DESPUES de pantalla-completa para ganar por orden de carga. PORQUE*/
/* ==========================================================================
   IRON ROUTE — LA COMPOSICIÓN DEL ONBOARDING EN UN TELÉFONO

   Va de la mano de `onboarding-movil.js`, que es quien recorta el TEXTO.
   El reparto de trabajo no es casual:

     · El JS quita palabras.  ← ahí estaba el 66 % del problema
     · Esta hoja aprieta el aire y baja la escala tipográfica.

   Se intentó tres veces resolverlo solo con esta mitad y las tres falló. Con
   1.683 px de contenido en 571 px útiles, apretar el CSS no llega: para meter
   eso encogiendo habría que bajar el cuerpo a unos 5 px, cuando el suelo del
   sistema son 12. **La composición no arregla un exceso de palabras.**

   EL PRESUPUESTO, medido en el teléfono más pequeño que hay que soportar
   (Android 360×640, que da 571 px útiles bajo la cabecera):

     idioma ....  38 px      capacidades ... 176 px
     logo ......  44 px      privacidad ....  34 px
     titular ...  56 px      acciones ......  96 px
     apoyo .....  36 px      confianza .....  26 px
     aire ......  56 px      ─────────────────────
                             TOTAL .........  562 px

   Nueve píxeles de margen. Por eso cada número de aquí abajo es un techo y no
   una sugerencia: aflojar uno saca la pantalla del presupuesto.

   LO QUE NO SE TOCA PARA GANAR ALTO:
     · 48 px de objetivo táctil. Un botón que el dedo no acierta no ahorra
       espacio: gasta un intento.
     · 12 px de suelo tipográfico.
     · La salida, el aviso de que no es un ELD, y USDOT/MC.

   Todo cuelga de `@media (max-width: 480px)` y de la clase `ir-ob-corto` que
   pone el JS. Así el escritorio no se entera de nada.
   ========================================================================== */

@media (max-width: 480px) {

  /* ── EL CONTENIDO SE DESPLAZA CUANDO NO CABE. ANTES SE AMPUTABA ─────────
     Aquí ponía `overflow-y: hidden`, y la intención era buena: que cada paso
     quepa entero sin desplazar, que es lo cómodo. El problema es lo que pasa
     cuando NO cabe, y en un Android de 360x640 no cabe.

     AUDITADO, y son cinco P0 de una sola regla: entre 13 y 1.866 px amputados
     por paso. No se puede elegir un semirremolque (2 de 19 tarjetas
     alcanzables, cero semis). No se puede tocar el GVWR, que es obligatorio.
     No se puede pulsar «Continuar como invitado». Tres de los cinco roles
     quedan fuera. **Un Android corto no puede completar el alta — y sin alta,
     la app rutea como automóvil.**

     `hidden` no hace que quepa: hace que lo que sobra desaparezca sin decirlo.
     El contenido que sobra en un formulario obligatorio no es decoración.

     La regla nueva: el CUERPO se desplaza y el pie se queda quieto. Así el
     botón de continuar sigue siempre a mano —que era lo que se quería
     proteger— y lo que no cabe se alcanza desplazando, como en cualquier
     formulario. `100dvh` se mantiene: en Safari `vh` incluye el trozo que tapa
     la barra de direcciones y el botón queda debajo de ella. */
  html.ir-ob-corto .ob-card > [data-body],
  html.ir-ob-corto .ob-body {
    display: block;
    flex: 1 1 auto;
    min-height: 0;              /* sin esto un hijo flex no encoge y no hay scroll */
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;   /* que el gesto no arrastre la página de detrás */
  }

  /* El pie no se desplaza con el cuerpo: es la salida del paso. */
  html.ir-ob-corto .ob-footer,
  html.ir-ob-corto .ob-actions {
    flex: 0 0 auto;
  }

  html.ir-ob-corto .ob-step {
    padding: 8px 14px calc(8px + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    gap: 7px;
    min-height: 0;
  }
  /* Los márgenes propios de cada bloque se anulan: con `gap` mandando, un
     `margin-bottom: 18px` heredado suma dos veces y se come el presupuesto
     sin que se vea de dónde salió. */
  html.ir-ob-corto .ob-step > * { margin-top: 0; margin-bottom: 0; }

  /* ── Idioma: primero, pequeño, y con el dedo intacto ─────────────────── */
  /* El ALTO de la fila lo fija el envoltorio, no el chip: medido, el chip
     bajaba a 38 y la fila seguía en 48 porque el contenedor tenía su propia
     altura. Un recorte que no se nota es un recorte que no se hizo. */
  html.ir-ob-corto .ob-lang-pick {
    gap: 6px;
    align-items: center;
  }
  /* El chip mide 48 px de alto por sí solo, que es justo el objetivo táctil
     del sistema. Se deja como está.

     AQUÍ HUBO UN INTENTO MÍO QUE SALIÓ MAL Y SE CUENTA PORQUE SE VIO EN
     PANTALLA: para ahorrar 12 px puse el chip a 36 y le añadí un `::after`
     invisible de 48 px que devolviera el objetivo táctil. Dos cosas:

       · El `height: 36px` no llegó a aplicarse — el chip siguió midiendo 48.
       · Y el `::after` sí se aplicó, encima de la insignia de «idioma
         activo», que usa ese mismo pseudo-elemento. Mi geometría
         (`left:0; right:0; height:48px`) convirtió un círculo pequeño en un
         óvalo negro estirado sobre la bandera de España.

     O sea: el ahorro no llegó y el defecto sí. Un pseudo-elemento es un sitio
     compartido; ocuparlo «porque estaba libre» es asumir algo que no se
     comprobó. Se quita, y los 12 px salen de otro lado — que ya salieron:
     la pantalla cabe con 39 px de margen.

     El chip se deja en su tamaño natural, con el tipo un poco menor. */
  html.ir-ob-corto .ob-lang {
    padding: 0 12px;
    font-size: calc(12px * var(--ir-letra, 1));
  }

  /* ── La X de cerrar: 48, no 44 ────────────────────────────────────────
     El medidor la cazó en 44×44.

     Y el token NO tenía la culpa: `--ob-touch` vale 48 px en `onboarding.css`,
     que es lo correcto. El 44 lo impone
     `state-cleanup-f4.css`:

         .ob-h-btn { width: 44px !important; height: 44px !important; }

     Alguien encogió a mano el objetivo táctil del botón de cerrar, con
     `!important`, y así lleva desde entonces. Es el botón con el que alguien
     sale de una pantalla que no quería ver: fallarlo una vez es quedarse
     dentro, y quedarse dentro suele acabar en cerrar la app.

     Se empata el `!important` porque no hay otra forma de ganarle a uno
     existente. Los 4 px no salen del presupuesto de la pantalla: el botón vive
     en la cabecera, que ya mide 69 px y le sobra sitio. */
  html.ir-ob-corto .ob-h-btn,
  .ob-header .ob-h-btn {
    width: 48px !important;
    height: 48px !important;
    min-width: 48px;
  }

  /* ── Logo: lo primero que se ve, no lo más grande ───────────────────── */
  html.ir-ob-corto .ob-logo-wrap  { padding: 0 !important; }
  html.ir-ob-corto .ob-logo-frame { width: 44px; height: 44px; }

  /* ── Escala tipográfica del teléfono (base 15, razón 1.2) ───────────── */
  html.ir-ob-corto .ob-h1 {
    font-size: calc(23px * var(--ir-letra, 1));
    line-height: 1.16;
    margin: 0;
  }
  html.ir-ob-corto .ob-lead {
    font-size: calc(14px * var(--ir-letra, 1));
    line-height: 1.4;
    max-width: none;
  }

  /* ── Las cuatro capacidades, en filas compactas ──────────────────────── */
  html.ir-ob-corto .ob-caps {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
  }
  html.ir-ob-corto .ob-caps li {
    display: grid;
    grid-template-columns: 22px 1fr;
    gap: 9px;
    align-items: center;
    padding: 5px 9px;
    border-radius: 9px;
    text-align: left;
  }
  html.ir-ob-corto .ob-caps__ic svg { width: 18px; height: 18px; }
  html.ir-ob-corto .ob-caps strong {
    display: block;
    font-size: calc(13px * var(--ir-letra, 1));
    line-height: 1.25;
  }
  html.ir-ob-corto .ob-caps li span:not(.ob-caps__ic) {
    display: block;
    font-size: calc(12px * var(--ir-letra, 1));          /* el suelo del sistema; ni un pixel menos */
    line-height: 1.35;
    margin-top: 1px;
  }

  /* ── La promesa de privacidad: una línea, y sigue estando ────────────── */
  /* La promesa de privacidad cabe en UNA línea: «No es un ELD.» en negrita y
     la frase seguida, no en dos bloques apilados. Sigue diciendo exactamente
     lo mismo y ocupa 38 px en vez de 54. */
  html.ir-ob-corto .ob-priv {
    padding: 6px 9px;
    border-radius: 9px;
    text-align: left;
    display: block;
  }
  html.ir-ob-corto .ob-priv strong,
  html.ir-ob-corto .ob-priv span {
    display: inline;
    font-size: calc(12px * var(--ir-letra, 1));
    line-height: 1.35;
  }
  html.ir-ob-corto .ob-priv strong { margin-right: 4px; }

  /* ── Acciones: los 48 px no se negocian ──────────────────────────────── */
  html.ir-ob-corto .ob-cta-row {
    display: grid;
    grid-template-columns: 1fr;
    gap: 5px;
    margin: 2px 0 0;
  }
  html.ir-ob-corto .ob-btn-primary {
    min-height: 48px;
    font-size: calc(16px * var(--ir-letra, 1));
  }
  /* «Ya tengo cuenta» y «Ver el mapa primero» comparten fila: son la misma
     clase de acción —salidas— y juntas ahorran 44 px sin quitar ninguna. */
  /* 48 px, no 44. El medidor los cazó en 44 y la regla del sistema no se
     recorta para ganar alto: un botón que el dedo no acierta no ahorra
     espacio, gasta un intento. Los 4 px salen del copy, no del dedo. */
  html.ir-ob-corto .ob-btn-ghost,
  html.ir-ob-corto .ob-ver-mapa {
    min-height: 48px;
    font-size: calc(13px * var(--ir-letra, 1));
    padding: 0 8px;
  }
  html.ir-ob-corto .ob-cta-row {
    grid-template-areas: "a a" "b c";
    grid-template-columns: 1fr 1fr;
  }
  html.ir-ob-corto .ob-btn-primary { grid-area: a; }
  html.ir-ob-corto .ob-btn-ghost   { grid-area: b; }
  html.ir-ob-corto .ob-ver-mapa    { grid-area: c; }

  html.ir-ob-corto .ob-ver-nota {
    font-size: calc(12px * var(--ir-letra, 1));
    line-height: 1.3;
    margin: 0;
  }

  /* ── Confianza: se queda en una línea, pero se queda ─────────────────── */
  html.ir-ob-corto .ob-trust {
    padding: 5px 0 0;
    margin: 0;
    border-top: 1px solid var(--line, #1E3A5F);
    display: block;
  }
  html.ir-ob-corto .ob-trust__ids {
    display: block;
    font-size: calc(12px * var(--ir-letra, 1));
    line-height: 1.3;
  }

  /* ══════════════════════════════════════════════════════════════════════
     EL SEGUNDO RECORTE — porque el primero se quedó a 116 px

     Medido después de recortar el copy: el contenido bajó de 1.683 a 687 px.
     Enorme, y todavía **116 px por encima** de los 571 útiles de un Android
     de 360×640. El presupuesto no se cumple «casi».

     Aquí se cierra ese hueco, y el orden en que se recorta no es arbitrario —
     va de lo más prescindible a lo menos:

       1. El logo (−44).  Ya saben en qué app están: la abrieron ellos. Un
          logo en la primera pantalla es cortesía, no información.
       2. Las capacidades a 2×2 (−77). Cuatro filas se convierten en dos.
          Se lee igual y ocupa la mitad.
       3. El aire entre bloques (−8).
       4. El envoltorio del idioma, la privacidad y la confianza (−40).

     Lo que NO entra en esta lista, en ningún orden: los botones, la salida,
     el aviso del ELD y USDOT/MC. Antes de tocar uno de esos se recorta más
     copy — y si tampoco, la pantalla se parte en dos pasos.

     Se dispara por debajo de 700 px de alto de ventana, que cubre el 360×640
     y el iPhone SE de 375×667. De ahí para arriba sobra sitio y la
     composición respira.
     ══════════════════════════════════════════════════════════════════════ */
  @media (max-height: 700px) {
    /* `!important` porque `onboarding-p0-fixes.css` fija
       `display: flex !important` en este mismo elemento. Se empata, no se
       sube: medido, sin esto el logo seguía ocupando sus 44 px. */
    html.ir-ob-corto .ob-logo-wrap { display: none !important; }
    html.ir-ob-corto .ob-step { gap: 6px; }
    html.ir-ob-corto .ob-h1 { font-size: calc(21px * var(--ir-letra, 1)); }

    /* Dos por dos. El título de cada tarjeta está escrito para caber en una
       línea a 12,5 px en media columna — por eso el techo de 24 caracteres
       del diccionario no es decorativo: si un título parte en dos, esta
       rejilla deja de cumplir el presupuesto. */
    html.ir-ob-corto .ob-caps {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 5px;
    }
    html.ir-ob-corto .ob-caps li {
      grid-template-columns: 1fr;
      gap: 2px;
      padding: 6px 8px;
    }
    html.ir-ob-corto .ob-caps__ic { display: none; }
    html.ir-ob-corto .ob-caps strong { font-size: calc(12.5px * var(--ir-letra, 1)); }
    html.ir-ob-corto .ob-caps li span:not(.ob-caps__ic) { font-size: calc(12px * var(--ir-letra, 1)); }
  }
}

/* En escritorio esta hoja no existe: `ir-ob-corto` solo la pone el JS por
   debajo de 480 px, y al ensanchar la ventana la quita y devuelve el texto
   largo. Nada de esto se queda pegado. */

/* ───── css/esquina-superior.css ───── */
/*PORQUE La esquina superior derecha, donde el boton de cuenta, la barra de
       capas y el buscador se pisaban. Va la ultima: reparte una franja que
       cuatro modulos distintos anclan con `position: fixed` y `right`. PORQUE*/
/* ==========================================================================
   IRON ROUTE — LA ESQUINA SUPERIOR DERECHA, DONDE TRES COSAS SE PISABAN

   MEDIDO sobre la captura del dueño (1908×1038) y confirmado en 1440, 1024,
   768 y 375:

     · `#ir-r4-auth-btn`  («Iniciar sesión»)  142×44, anclado a `right: 12px`
     · `.irc-capas`       (Calle/Satélite/Oscuro/Claro/Relieve)  603×62,
                          también anclado a `right: 12px`
     · `.search-header`   (el buscador)       480×72, centrado arriba

   Los tres viven en la misma franja superior y ninguno sabe de los otros:

     1. El botón de cuenta tapa el chip «Relieve» de la barra de capas.
        `elementFromPoint` sobre ese chip devuelve el botón de login — o sea
        que **una de las cinco capas base no se puede elegir en escritorio**.
     2. La barra de capas se mete debajo del buscador por la izquierda:
        `.irc-capas` pierde el **20,2 %** de su área bajo `.search-header`, y
        el chip «Calle» devuelve `input#searchInput` al pulsarlo. **Dos de
        cinco capas muertas.**
     3. En teléfono el botón de cuenta se sienta ENCIMA del buscador:
        `142×48@221,12` contra `221×48@69,12` = **69×48 px de solape**, y el
        micrófono queda tapado.

   Nada de esto es un fallo de un archivo: es que cuatro módulos distintos
   anclan a la misma esquina con `position: fixed` y `right`, y el que se
   carga después gana. Esta hoja reparte esa franja de una vez.

   LA REGLA QUE SE APLICA:
   En una franja compartida, quien tiene ancho variable cede. El buscador es
   lo que más se usa y se queda donde está; el botón de cuenta es el más
   pequeño y se queda en la esquina; **la barra de capas se aparta**, porque
   es la única que puede reducirse o bajar sin perder nada.
   ========================================================================== */

/* ── 1 · Escritorio: los tres en la misma fila, sin tocarse ──────────────── */
@media (min-width: 900px) {
  /* El botón de cuenta se queda en la esquina: 12px del borde, 154px de
     reserva contando su ancho máximo con un correo largo. */
  .irc-capas {
    right: 178px;
    max-width: calc(100vw - 178px - 12px);
  }
}

/* Pantallas donde el buscador y las capas todavía se rozarían: la barra de
   capas baja una fila en vez de meterse debajo del buscador. */
@media (min-width: 900px) and (max-width: 1500px) {
  .irc-capas {
    top: 84px;          /* justo debajo de la cabecera de búsqueda (72px) */
    right: 12px;
    max-width: calc(100vw - 24px);
  }
}

/* ── 2 · Teléfono y tablet: nadie se sienta encima del buscador ──────────── */
@media (max-width: 899px) {
  /* El buscador ocupa toda la franja de arriba. El botón de cuenta baja
     debajo, alineado a la derecha. 8px de aire para que no parezca pegado. */
  #ir-r4-auth-btn,
  #ir-topright-user {
    top: calc(72px + env(safe-area-inset-top, 0px) + 8px) !important;
    right: 12px !important;
  }

  /* Y las capas debajo del botón de cuenta, no al lado: en 375px no caben en
     la misma fila y lo que no cabe se sale. */
  .irc-capas {
    top: calc(72px + env(safe-area-inset-top, 0px) + 8px + 52px);
    right: 12px;
    left: 12px;
    max-width: none;
  }
}

/* ── 3 · La barra de capas nunca se sale por los lados ───────────────────
   Medido: `.irc-lista` salía a `172×270@191,140` en un teléfono de 375 y se
   quedaba abierta. Se acota al ancho disponible y se le da scroll propio en
   vez de desbordar. */
.irc-capas {
  max-width: calc(100vw - 24px);
  box-sizing: border-box;
}
.irc-capas .irc-lista {
  max-width: calc(100vw - 24px);
  max-height: min(60vh, 420px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ── 4 · El pie legal deja de montarse sobre los accesos rápidos ──────────
   Medido: `.ir-footer-id` (z-index 1199) solapaba `.quick-tiles` (z 800) en
   **1648×10 px** a 1908, y **375×17 px** en el teléfono. Diez píxeles no
   parecen nada hasta que son diez píxeles de una línea de 9,5 px: se pierde
   el trazo inferior de las letras y el número de USDOT se lee mal.

   Y la letra sube de 9,5 px a 12, que es el suelo del sistema. Un
   identificador federal que no se puede leer no cumple su función. */
.ir-footer-id {
  font-size: calc(12px * var(--ir-letra, 1)) !important;
  line-height: 1.3 !important;
  padding: 4px 10px calc(4px + env(safe-area-inset-bottom, 0px)) !important;
}
.ir-footer-id span { font-size: calc(12px * var(--ir-letra, 1)) !important; }

/* ── 5 · Los accesos rápidos se apartan del pie ──────────────────────────── */
.quick-tiles {
  margin-bottom: 6px;
}

/* ── 6 · Objetivos táctiles que estaban por debajo del suelo ──────────────
   Medido en teléfono: 19 de 77 controles por debajo de 48 px. Estos son los
   del mapa, que son los que se pulsan con el camión parado en un arcén. */
@media (max-width: 899px) {
  #menuBtn,
  #voiceBtn,
  #locateBtn,
  #layersBtn,
  #truckRestrictBtn,
  #ir-r4-auth-btn,
  #ir-topright-user {
    min-width: 48px;
    min-height: 48px;
  }
  #swapRouteBtn,
  .ir-pf5-close,
  .fab-collapse-toggle,
  .r25-collapse-btn { min-width: 48px; min-height: 48px; }
}
/* Con ratón el suelo baja a 40, nunca menos. */
@media (min-width: 900px) {
  .fab-collapse-toggle,
  .r25-collapse-btn,
  #swapRouteBtn,
  .ir-pf5-close { min-width: 40px; min-height: 40px; }
}

/* ── 7 · Las etiquetas por debajo de 12 px ────────────────────────────────
   `ecosystem-core` fija el suelo en 12 px y no admite excepciones fuera del
   modo navegación (que sube a 20). Medido: 29 nodos por debajo en escritorio
   y 23 en teléfono. Estos son los que se leen de verdad. */
.tile-label,
.nav-tab span,
.menu-tagline,
.menu-version,
.ir-base-layer-heading { font-size: calc(12px * var(--ir-letra, 1)) !important; }

/* La atribución de Leaflet viene a 10px de la propia librería. Sube a 12 —
   es un requisito de licencia de OpenStreetMap: el crédito tiene que ser
   legible, no solo estar. */
.leaflet-control-attribution,
.leaflet-control-attribution a { font-size: calc(12px * var(--ir-letra, 1)) !important; }
/* Y area que se pueda tocar. La letra ya cumplia los 12 px; lo que medía
   14 px de alto era el BLANCO donde acertar, y estos enlaces son obligacion
   de licencia: si alguien los quiere abrir, tiene que poder. No llegan a 48
   —seria una barra de atribucion enorme encima del mapa— pero 24 px de alto
   real es la diferencia entre acertar y no. Declarado como excepcion. */
.leaflet-control-attribution a { display: inline-block; min-height: 24px; line-height: 24px; padding: 0 3px; }

/* ── 8 · Campos por debajo de 16 px → iOS hace zoom al enfocar ────────────
   Medido: 14 campos. El zoom descoloca la pantalla de alguien que puede
   estar parado en el arcén escribiendo una dirección. */
input, select, textarea,
#searchInput, #tripOrigin, #tripDest,
#vehicleType, #vehicleHeight, #vehicleWeight, #vehicleLength {
  font-size: calc(16px * var(--ir-letra, 1));
}

/* ── 9 · En escritorio no hace falta la barra de pestañas del teléfono ────
   Medido: `.bottom-nav 480×64@714,974` — una barra de 480 px flotando en un
   lienzo de 1908, con `transform: translate(-240px, 0)` para centrarla.

   Con el raíl de navegación a la izquierda (240-260 px, siempre visible) y
   los accesos rápidos abajo, esa barra es la TERCERA navegación de la misma
   pantalla. Tres formas de ir al mismo sitio no es generosidad: es que nadie
   sabe cuál es la buena. */
.ir-escritorio .bottom-nav,
.ir-escritorio .nav-tabs { display: none; }
/* Y el hueco que reservaba deja de reservarse. */
.ir-escritorio { --nav-height: 0px; }


/* ══════════════════════════════════════════════════════════════════════════
   `hidden` TIENE QUE ESCONDER — la salvaguarda general

   El desplegable de capas se quedó abierto para siempre en el teléfono porque
   su hoja de estilos le daba `display: flex` y eso le gana al `[hidden]` que
   aplica el navegador. `map-layers.js` hacía `lista.hidden = true` y no pasaba
   nada.

   No es un fallo de ese archivo: es una trampa del lenguaje que muerde a
   cualquiera que ponga `display` en una clase y luego use `hidden` en el JS.
   En esta base hay 20 sitios que usan `hidden` desde JavaScript.

   Se pone el suelo aquí, para todos, y con `!important` porque el rival
   —`display: flex` en una clase— tiene la misma especificidad y a veces
   aparece en una hoja posterior. Es de los pocos sitios donde `!important`
   es la herramienta correcta: se está restaurando el comportamiento del
   navegador, no compitiendo con otra decisión de diseño.

   Excepción declarada: `.irc-lista` en escritorio, que vive abierta en fila
   por decisión de producto y lo dice en su propia media query. */
[hidden]:not(.irc-lista) { display: none !important; }

@media (min-width: 1024px) {
  /* La excepción, escrita donde se ve: en ancho la lista de capas no se
     oculta nunca, es una barra de cinco chips. */
  .irc-lista[hidden] { display: flex !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   LOS AVISOS, EN EL TELÉFONO, ABAJO

   MEDIDO: el contenedor de avisos salía `299×756` — tres cuartos de la
   pantalla— porque DOS hojas definen la misma clase con anclajes opuestos y
   se mezclan: `styles.css:1237` lo ancla con `top`, y `ui-fixes-p0.css:9` con
   `bottom`. Con los dos puestos, la caja se estira de arriba abajo.

   Consecuencia real: un aviso de «No se pudo obtener tu ubicación» —que sale
   siempre que alguien deniega el GPS, o sea a menudo— aparecía ARRIBA y
   tapaba el botón de cuenta. `elementFromPoint` sobre ese botón devolvía
   `toast-message`.

   Se decide por abajo, que es lo que `ui-fixes-p0` ya quería y además es
   donde los espera cualquiera que use un teléfono: arriba está el buscador,
   y un aviso que tapa el buscador tapa lo que la persona iba a hacer. */
@media (max-width: 899px) {
  #toastContainer {
    top: auto;
    /* Por encima de la fila de botones flotantes, no encima de ellos. Medido:
       con 12 px el aviso caia sobre `#rrBoton` (Reportar) y `elementFromPoint`
       devolvia el aviso — o sea que durante los segundos que dura el aviso, el
       boton de reportar una restriccion no se podia pulsar. Y el aviso mas
       frecuente es «No se pudo obtener tu ubicacion», que sale justo cuando
       alguien acaba de denegar el GPS y va a querer tocar algo. */
    /* Por encima de TODA la fila de botones flotantes, no solo de uno.
       Medido en dos pasadas: con 12 px el aviso tapaba «Reportar»; con 84 px
       dejó de taparlo y empezó a tapar «+ VIAJE», que había subido para no
       pisar los accesos rápidos. Tres botones flotantes en la franja de abajo
       es lo que obliga a este número; el aviso se sube por encima de los tres
       y se acabó el juego de las sillas. */
    bottom: calc(var(--nav-height, 64px) + 160px + env(safe-area-inset-bottom, 0px));
    left: 12px;
    right: 12px;
    width: auto;
    max-width: none;
    transform: none;
    height: auto;
    max-height: 40vh;
    align-items: stretch;
  }
}
/* En escritorio se ancla arriba a la derecha y se le da alto propio: sin esto
   la caja también se estiraba 894 px, y aunque no roba clics
   (`pointer-events: none`), una caja de ese tamaño es una bomba de relojería
   el día que alguien le ponga `auto`. */
@media (min-width: 900px) {
  #toastContainer {
    bottom: auto;
    height: auto;
    max-height: 60vh;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   LOS TRES SOLAPES QUE QUEDABAN — medidos, no estimados

   1 · LA BARRA DE CAPAS SE METE 67 px BAJO EL BUSCADOR
       Medido a 1908: buscador `480×72@714`, o sea de 714 a 1194. Barra de
       capas `603×62@1127`, o sea empieza en 1127. **67 px de solape**, y ahí
       cae el primer chip: «Calle». Se ve medio tapado y `elementFromPoint`
       sobre él devuelve el buscador.

       La primera versión de esta hoja lo ancló a `right: 178px` contando el
       espacio del botón de cuenta. Correcto por la derecha, y no miré la
       izquierda: es una barra de 603 px, no cabe en el hueco que queda.

       Se baja una fila, debajo del buscador. No estorba a nada y ahí no
       compite con nadie — que es lo que se pedía desde el principio.

   2 · EL FAB «+ VIAJE» PISA LA FILA DE ACCESOS RÁPIDOS
       Medido: `107×56@1107,882` contra tiles `1648×94@260,930` = **107×8 px**.
       Ocho píxeles, pero justo sobre el borde superior del tile DESCANSO, que
       queda con la esquina comida.

   3 · DOS BOTONES DE PLEGAR PARA EL MISMO GRUPO
       `.fab-collapse-toggle` en `1856,648` y `.r25-collapse-btn` en
       `1162,632`. Dos controles idénticos, dos paradas de tabulación, **y dos
       claves distintas en el almacenamiento** (`ironroute_fab_collapsed` y
       `fab_collapsed`), para un solo grupo de tres botones.

       Se queda el que vive DENTRO del grupo que pliega —`.r25-collapse-btn`,
       justo encima de los tres botones— porque es el que se entiende sin
       explicación. El otro flota solo contra el borde derecho, a 700 px de lo
       que controla.
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 900px) {
  /* 1 · debajo del buscador, en todos los anchos: la barra mide 603 px y en
     ningún tamaño realista cabe entre el buscador y el botón de cuenta. */
  .irc-capas {
    top: calc(72px + env(safe-area-inset-top, 0px) + 12px);
    right: 12px;
    max-width: calc(100vw - 24px);
  }

  /* 2 · el FAB, por encima de la fila de tiles */
  #fabNewTrip,
  .fab-new-trip { bottom: calc(var(--nav-height, 0px) + 118px); }

  /* 3 · un solo botón de plegar */
  .fab-collapse-toggle { display: none; }
}

@media (max-width: 899px) {
  /* En teléfono el que sobra es el otro: `.r25-collapse-btn` está dentro del
     grupo, que ahí es estrecho, y `.fab-collapse-toggle` es el que la gente
     ya conoce de la versión anterior. Se queda uno igual. */
  .r25-collapse-btn { display: none; }
  #fabNewTrip,
  .fab-new-trip { bottom: calc(var(--nav-height, 64px) + 96px + env(safe-area-inset-bottom, 0px)); }
}

/* ══════════════════════════════════════════════════════════════════════════
   LO QUE SALIÓ DE LA CAPTURA DEL IPHONE (430×932, producción)

   1 · EL PIE LEGAL SE CORTA — y con él la segunda línea
       Medido: `scrollWidth 542` en `clientWidth 430`. El texto de entonces era
       largo y en pantalla acababa cortado a media línea.

       Cuando esto se midió, `ecosystem-core` exigía USDOT y MC en el pie de
       toda página pública. **Esa regla la derogó el dueño el 8-sep-2026**
       («no hagas referencia a mi DOT o mi MC, porque eso es otra empresa
       separada»): hoy el pie firma «Iron Route · TruckerProfit Inc.» y ya no
       tiene esa longitud. La regla de envolver en dos líneas se queda: un pie
       cortado sigue siendo un pie que no dice lo que iba a decir.

       Se deja envolver en dos líneas. No se recorta el texto ni se baja de
       12 px: lo que sobra es el ancho, no el contenido.

   2 · DOS BOTONES DE PLEGAR, UNO ENCIMA DEL OTRO
       Medido: `.r25-collapse-btn 48×48@370,466` y
       `.fab-collapse-toggle 48×48@374,500` — **14 px de solape**, dos círculos
       pisándose en el borde derecho.

       Ya se había intentado esconder uno con `.r25-collapse-btn { display:
       none }` en esta misma hoja, y no funcionó: `bundle-r5.js` inyecta un
       `<style id="r25-css">` EN TIEMPO DE EJECUCIÓN, o sea que su regla entra
       en el `<head>` después que este archivo y gana con la misma
       especificidad.

       No se sube a `!important`: se sube la especificidad usando el padre
       real, que es `.map-controls`. Empatar por especificidad legítima es
       mejor que empezar una carrera de `!important` — y esa carrera ya va por
       103 en estas hojas.

   3 · «REPORTAR» TAPA EL PRIMER ACCESO RÁPIDO
       Medido: el primer tile es `128×78@12,772` y `elementFromPoint` sobre su
       centro devuelve `rrBoton`. El acceso «Planificador» —el primero de los
       diez y el más usado— **no se puede pulsar en un teléfono**.

       El botón de reportar se sube a la misma franja que «+ VIAJE», que ya
       está por encima de los tiles. Los dos flotan sobre el mapa, uno a cada
       lado, y ninguno pisa la fila de abajo.
   ══════════════════════════════════════════════════════════════════════════ */

/* 1 · el pie legal cabe entero */
.ir-footer-id {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  text-align: center;
}

/* 2 · un solo botón de plegar, ganando por especificidad y no con !important */
@media (max-width: 899px) {
  .map-controls .r25-collapse-btn { display: none; }
}
@media (min-width: 900px) {
  .map-controls .fab-collapse-toggle,
  body > .fab-collapse-toggle { display: none; }
}

/* 3 · «Reportar» sube a la franja de los botones flotantes */
@media (max-width: 899px) {
  .rr-lanzar,
  #rrBoton {
    bottom: calc(var(--nav-height, 64px) + 96px + env(safe-area-inset-bottom, 0px));
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   LA FRANJA DE ABAJO, APILADA DE UNA VEZ

   Llevo tres parches empujando piezas de esta zona una por una: subí el aviso
   y tapó «Reportar»; lo subí más y tapó «+ VIAJE»; subí «Reportar» y el pie
   creció a dos líneas y se comió los accesos rápidos.

   MEDIDO ahora mismo, y por eso paro de empujar:

     iPhone 430×932        y 724..772  botones flotantes
                           y 768..854  accesos rápidos
                           y 829..868  pie legal      ← 25 px encima de los tiles
                           y 868..932  barra de pestañas

     1440×900              y 840..888  «Reportar»     ← 46 px encima de los tiles
                           y 792..886  accesos rápidos
                           y 877..900  pie legal      ← 9 px encima de los tiles

   Cinco cosas fijadas al borde inferior por cuatro archivos distintos, cada
   una con su propio `bottom` calculado a mano. Cada vez que una cambia de
   alto —y el pie acaba de cambiar, al dejarlo envolver— se pisan otra vez.

   LA SOLUCIÓN no es otro empujón: es que **haya un solo sitio donde se decide
   el orden**. De abajo arriba:

       barra de pestañas  →  pie legal  →  accesos rápidos  →  flotantes

   Cada franja se coloca a partir de la altura de la de abajo, no de un número
   escrito a mano. Si el pie vuelve a crecer, todo lo demás sube solo.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  /* CERO desde que el pie legal salió del mapa (ver `bundle-r5.js`,
     `injectFooter`). La variable se queda —no se borra— porque de ella cuelgan
     nueve cálculos: ponerla a 0 baja la pila entera 42 px y devuelve esa franja
     al mapa, que es justo lo que se buscaba. Borrarla habría obligado a tocar
     los nueve y a arriesgar un desajuste en cada uno.

     Y sigue siendo una variable, no un 0 escrito nueve veces, por si mañana
     hay que devolver una banda de estado ahí abajo. */
  --ir-pie: 0px;
  --ir-tiles: 92px;
  --ir-hueco: 8px;
}
@media (min-width: 900px) {
  :root { --ir-pie: 0px; --ir-tiles: 100px; }
}

/* Los accesos rápidos, justo encima del pie legal. */
.quick-tiles,
.ir-escritorio .quick-tiles {
  bottom: calc(var(--nav-height, 64px) + var(--ir-pie) + var(--ir-hueco));
}

/* Los botones flotantes, justo encima de los accesos rápidos. Uno a cada
   lado, así que comparten franja sin pisarse: «Reportar» a la izquierda,
   «+ VIAJE» a la derecha. */
#fabNewTrip,
.fab-new-trip,
.rr-lanzar,
#rrBoton {
  bottom: calc(var(--nav-height, 64px) + var(--ir-pie) + var(--ir-hueco)
               + var(--ir-tiles) + var(--ir-hueco));
}
.ir-escritorio .rr-lanzar,
.ir-escritorio #rrBoton { left: auto; right: 12px; }
.ir-escritorio #fabNewTrip,
.ir-escritorio .fab-new-trip { right: auto; left: calc(var(--rail-w, 260px) + 12px); }

/* Y los avisos por encima de todo lo anterior. */
@media (max-width: 899px) {
  #toastContainer {
    bottom: calc(var(--nav-height, 64px) + var(--ir-pie) + var(--ir-hueco)
                 + var(--ir-tiles) + var(--ir-hueco) + 56px + var(--ir-hueco));
  }
}

/* El pie no se monta sobre nada: es el suelo, y ocupa su alto de verdad. */
.ir-footer-id {
  min-height: var(--ir-pie);
  box-sizing: border-box;
}

/* ══════════════════════════════════════════════════════════════════════════
   EL BOTÓN DE PLEGAR, POR ENCIMA DE LO QUE PLIEGA

   Medido en iPhone y en Android: **20 px de solape** entre
   `.fab-collapse-toggle` y `#locateBtn`, el primero de los tres controles del
   mapa.

   Y la aritmética lo dice sin ambigüedad. Los dos se anclan al borde inferior:

     `.map-controls`         bottom: nav + safe + 180   ·  alto 160
                             → ocupa de 180 a 340
     `.fab-collapse-toggle`  bottom: nav + safe + 320   ·  alto 48
                             → ocupa de 320 a 368

     solape = 340 − 320 = **20 px**

   El 320 se escribió cuando el grupo medía menos. Nadie rehizo la cuenta al
   añadirle el tercer botón. Es el mismo fallo que el `44px` fijo de la
   cabecera y que los cinco `bottom` de la franja inferior: **un número
   calculado a mano una vez, sobre un contenido que después creció**.

   Se calcula desde donde acaba el grupo, no desde un número suelto:
   180 (donde empieza el grupo) + 160 (lo que mide) + 8 (aire) = 348.
   ══════════════════════════════════════════════════════════════════════════ */
/* Y como los flotantes subieron, el grupo de controles del mapa sube con
   ellos: medido, `#truckRestrictBtn` quedaba **44x18 px detrás de «+ VIAJE»**.

   Todo se calcula desde la misma pila, de abajo arriba, para que no vuelva a
   descuadrarse cuando algo cambie de alto:

     flotantes  = nav + pie + hueco + tiles + hueco
     controles  = flotantes + alto del flotante + hueco
     plegar     = controles + alto del grupo   + hueco
*/
:root {
  --ir-flot-alto: 56px;      /* «+ VIAJE», el más alto de la fila */
  --ir-controles: 160px;     /* tres botones de 48 + dos huecos de 8 */
}
@media (min-width: 900px) {
  :root { --ir-controles: 226px; }   /* ahí miden 52 y hay más aire */
}

.map-controls {
  bottom: calc(var(--nav-height, 64px) + var(--ir-pie) + var(--ir-hueco)
               + var(--ir-tiles) + var(--ir-hueco)
               + var(--ir-flot-alto) + var(--ir-hueco));
}

.fab-collapse-toggle,
.r25-collapse-btn {
  bottom: calc(var(--nav-height, 64px) + var(--ir-pie) + var(--ir-hueco)
               + var(--ir-tiles) + var(--ir-hueco)
               + var(--ir-flot-alto) + var(--ir-hueco)
               + var(--ir-controles) + var(--ir-hueco));
}
@media (max-width: 899px) {
  .fab-collapse-toggle { right: 12px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   EL AVISO YA NO SE SIENTA ENCIMA DE LOS CONTROLES — y esta vez por geometría

   Historial honesto: este aviso se ha movido TRES veces esta sesión. Con
   12 px tapaba «Reportar». Con 84 px tapaba «+ VIAJE». Subido por encima de
   los tres flotantes, medido en iPhone 430×932:

       #toastContainer   x 12..418   y 608..654
       .map-controls     x 370..418  y 494..654     ← 48 px de solape

   Y `elementFromPoint` sobre `#truckRestrictBtn` devolvía `.toast-message`.
   O sea: mientras dura el aviso —y el más frecuente es «No se pudo obtener tu
   ubicación», justo cuando alguien va a tocar algo— el botón de restricciones
   de camión no se puede pulsar.

   Subirlo otra vez sería la cuarta ronda del mismo juego, y perdería igual:
   la columna de controles mide 160 px y el aviso es de ancho completo, así
   que en algún punto de esa franja se van a cruzar SIEMPRE.

   Se arregla por el otro eje. La columna de controles vive pegada a la
   derecha (48 px de ancho + 12 px de margen). El aviso se detiene antes de
   llegar: `right: 68px`. Ya no es una cuestión de a qué altura está — no
   comparten carril, y no lo harán aunque mañana la pila crezca.

   Queda ancho de sobra: en 360 px el aviso mide 280, que a 13 px son unos 40
   caracteres por línea. En 430, 350.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 899px) {
  #toastContainer {
    right: calc(12px + 48px + 8px);   /* margen + ancho de la columna + hueco */
  }
  /* Y la caja en sí no recibe toques; solo los avisos que hay dentro. Sin
     esto, una caja de 40vh de alto con `pointer-events: auto` sería una bomba
     de relojería el día que alguien la herede. */
  #toastContainer { pointer-events: none; }
  #toastContainer > * { pointer-events: auto; }
}

/* ══════════════════════════════════════════════════════════════════════════
   EL BOTÓN DE PLEGAR SIGUE A LO QUE PLIEGA

   `installFabCollapse()` pone `.collapsed` sobre `.map-controls`, que entonces
   mide ~0. Pero el botón se anclaba con `--ir-controles: 160px` fijo, así que
   al plegar quedaba flotando 160 px por encima de nada, en mitad del mapa.

   Nadie lo había reportado porque hay que plegar para verlo — y plegar es
   justo lo que hace quien tiene la pantalla llena, o sea quien más lo sufre.

   `:has()` lee el estado del hermano siguiente (Safari 15.4+, Chrome 105+).
   Si un navegador viejo no lo soporta, no aplica nada y se queda como estaba:
   el botón sigue donde está hoy, que es el comportamiento actual. Ninguna
   regresión, solo la mejora donde hay soporte.
   ══════════════════════════════════════════════════════════════════════════ */
body:has(.map-controls.collapsed) .fab-collapse-toggle,
body:has(.map-controls.collapsed) .r25-collapse-btn {
  /* Queda el `+ var(--ir-hueco)` del cálculo, o sea 8 px por encima del grupo
     plegado. Que es exactamente donde debe estar. */
  --ir-controles: 0px;
}

/* ══════════════════════════════════════════════════════════════════════════
   UNA HOJA ABIERTA MANDA SOBRE LOS BOTONES DEL MAPA

   Medido con la hoja de capas abierta, en iPhone 390×844 y en 1440×900:

     #panelLayers      z:1100
     #rrBoton          z:1150   ← 127×48 encima del contenido de la hoja
     .ir-footer-id     z:1199   ← 390×39 encima
     .bottom-nav       z:1200   ← 390×64 encima

   El botón «Reportar» quedaba flotando dentro de la hoja, tapando una fila
   entera de capas. No es un descuido de un archivo: son cuatro capas de
   z-index que crecieron por separado, cada una defendiéndose de la anterior.

   La regla que faltaba es de comportamiento, no de número: **cuando hay una
   hoja abierta, el mapa está tapado, así que sus botones no pintan nada.**
   Se van los cinco. Vuelven solos al cerrar.

   La barra de navegación y el pie SÍ se quedan encima: son la salida y la
   identidad legal, y taparlos deja a alguien dentro de una hoja sin puerta.
   Lo que se arregla es que la hoja no siga POR DEBAJO de ellos, que es lo que
   hacía con `bottom: 0` — su último renglón era inalcanzable.

   ── AMPLIADO: FALTABAN LAS DOS PANTALLAS QUE MÁS SE USAN ──────────────────

   Dicho por el dueño, después de mirar las capturas: «sigue estando mal
   ubicado; por ejemplo, en el mapa claro superpuesto encima del planificador,
   y cuando se está buscando también está superpuesto encima del
   planificador».

   Tenía razón, y el porqué es de libro. Esta regla listaba DOS clases de
   panel —`.ir-pf5-panel.open` y `.bottom-sheet.open`— y las dos pantallas de
   las que habla no son ninguna de las dos:

       el planificador   #irDirectionsSheet  .ir-dir-sheet.show
       el buscador       #irSearchDropdown   .ir-search-dropdown.show

   O sea que la regla existía, estaba bien pensada, y **no cubría el
   planificador**. Medido a 1440×900 con el planificador abierto: la barra de
   accesos rápidos se parte en dos filas al estrecharse el mapa, sube, y el
   botón «+ VIAJE» —que tiene reservada la franja de UNA fila— aterriza
   encima de la primera tarjeta, que es justamente la que dice
   PLANIFICADOR. Literalmente lo que él describió.

   La lista se reescribe con `:is()` en dos ejes —QUIÉN MANDA y QUIÉN SE
   APARTA— en vez de multiplicar a mano. Con la forma anterior, añadir un
   panel eran seis líneas nuevas y olvidar una era invisible; así es una
   palabra en un sitio, y no se puede añadir a medias. La lista de arriba se
   escribió tres veces a mano y las tres veces era la misma: por eso el
   planificador no entró.
   ══════════════════════════════════════════════════════════════════════════ */
body:is(
  :has(.ir-pf5-panel.open),
  :has(.bottom-sheet.open),
  :has(.ir-dir-sheet.show),
  :has(.ir-search-dropdown.show)
) :is(
  #rrBoton,
  .rr-lanzar,
  #fabNewTrip,
  .map-controls,
  .fab-collapse-toggle,
  .quick-tiles,
  #ir-r4-auth-btn
) {
  opacity: 0;
  pointer-events: none;
  /* `visibility` y no `display`: así la transición existe y no dan un salto
     al cerrar. Y `visibility: hidden` sí saca del orden de tabulación, que es
     lo que importa — un botón invisible que recibe el foco es peor que uno
     visible que estorba. */
  visibility: hidden;
  transition: opacity .18s ease;
}

/* ── Y ESTE ES EL ARREGLO QUE ME SALIÓ MAL A LA PRIMERA ────────────────────
   Escribí `bottom: calc(nav + pie)` sobre las hojas para que no quedaran por
   debajo de la barra. Medido acto seguido: **106 px de TODAS las hojas
   cerradas asomaban por abajo**, seis a la vez.

   La razón es que se esconden con `transform: translateY(100%)`, que las baja
   exactamente su propio alto. Eso oculta una hoja **solo si su borde inferior
   está en el borde de la pantalla**. Al subirla 106 px, `translateY(100%)` la
   deja 106 px por encima del borde… o sea, dentro.

   Mover el ancla rompe el escondite. Lo que hace falta no es mover la hoja
   sino **reservar sitio dentro de ella**: el último renglón deja de caer bajo
   la barra y el escondite sigue funcionando porque el ancla no se toca. */
.ir-pf5-body,
aside.bottom-sheet .sheet-content {
  padding-bottom: calc(var(--nav-height, 64px) + var(--ir-pie) + 16px);
}

@media (prefers-reduced-motion: reduce) {
  body:is(
    :has(.ir-pf5-panel.open),
    :has(.bottom-sheet.open),
    :has(.ir-dir-sheet.show),
    :has(.ir-search-dropdown.show)
  ) .quick-tiles { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   CON LA TARJETA DE UN LUGAR ABIERTA, LA FRANJA DE ABAJO ES SUYA

   `search.js` pone `ir-con-tarjeta` en el body y publica `--ir-tarjeta-alto`
   con el alto MEDIDO de la tarjeta en ese momento. Con eso:

     · Los botones flotantes del mapa se apartan, igual que con una hoja
       abierta: la tarjeta ES la tarea ahora mismo.
     · El aviso sube por encima de la tarjeta en vez de aterrizar sobre su
       botón principal. Medido en una captura: «Clima: sin conexión» cayó justo
       encima de «Cómo llegar».

   Cuarta y última vez que se toca la posición del aviso, y esta vez no es un
   número nuevo: es que la franja de abajo ahora tiene dueño declarado.
   ══════════════════════════════════════════════════════════════════════════ */
body.ir-con-tarjeta #rrBoton,
body.ir-con-tarjeta .rr-lanzar,
body.ir-con-tarjeta #fabNewTrip,
body.ir-con-tarjeta .quick-tiles,
body.ir-con-tarjeta .fab-collapse-toggle {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .18s ease;
}

body.ir-con-tarjeta #toastContainer {
  bottom: calc(var(--nav-height, 64px) + var(--ir-tarjeta-alto, 0px) + 24px);
}

/* Y los controles del mapa suben por encima de la tarjeta, que si no quedan
   detrás de ella justo cuando alguien quiere centrar el mapa en el sitio que
   acaba de encontrar. */
body.ir-con-tarjeta .map-controls {
  bottom: calc(var(--nav-height, 64px) + var(--ir-tarjeta-alto, 0px) + 24px);
}

/* ══════════════════════════════════════════════════════════════════════════
   CON EL PLANIFICADOR ABIERTO, EL AVISO NO SE SIENTA EN EL CAMPO DE DESTINO

   Medido tras pulsar «Cómo llegar»: `#irDirTo` —el campo que lleva la
   dirección que la persona acaba de elegir— devolvía `toast toast-error` en
   `elementFromPoint`. O sea que el aviso tapaba el destino justo en la
   pantalla donde se comprueba el destino.

   Misma solución que con la tarjeta y con las hojas: el aviso sube por encima
   de la superficie que manda en ese momento. Es la última franja donde faltaba.
   ══════════════════════════════════════════════════════════════════════════ */
body:has(#irDirectionsSheet.show) #toastContainer,
body:has(.ir-dir-sheet.show) #toastContainer {
  bottom: calc(var(--nav-height, 64px) + 72vh);
  max-height: 20vh;
  overflow: hidden;
}

/* ───── css/suelo-tipografico.css ───── */
/* ==========================================================================
   IRON ROUTE — EL SUELO DE 12 px, APLICADO

   La regla existe desde el principio: **ninguna letra por debajo de 12 px**.
   Estaba escrita y no estaba medida, que es la forma más silenciosa de que
   una regla no exista. Al medir la página real en un teléfono de 428 px
   aparecieron DIECINUEVE sitios por debajo del suelo:

     11px  label «Tipo» · «Altura (ft)» · «Peso (lbs)» · «Largo (ft)»
     11px  p     el pie legal del producto
     11px  .ob-step-label «Paso 1 de 9»
     11px  .src  CARTO · OpenStreetMap · Esri · Stamen · NWS · HERE ·
                 511.gov · state_dot · fmcsa · state_bridge · state_weight ·
                 state_chains
     11px  «Capas de gobierno»

   LOS CUATRO PRIMEROS SON LOS GRAVES. «Altura (ft)» es el rótulo del campo
   donde alguien escribe 13.6 para que la app no lo mande por debajo de un
   puente de 13.2. Ese es el número que evita arrancar la parte de arriba de
   un remolque. Ponerlo a 11 px para ganar cuatro píxeles de alto es cambiar
   legibilidad por espacio en el único sitio donde no se puede.

   Y el de USDOT/MC es el que la ley pide que se vea.

   POR QUÉ `!important` AQUÍ, QUE NO SE USA ALEGREMENTE: la mitad de estas
   reglas no viven en una hoja enlazada, sino en `<style>` que los módulos
   inyectan en tiempo de ejecución (`bundle-r5.js` línea 788, por ejemplo).
   Un `<style>` inyectado aterriza al final del `<head>` y gana a cualquier
   hoja enlazada con la misma especificidad, sin importar el orden del HTML.
   Contra eso, subir la especificidad es una carrera que se pierde en cuanto
   alguien inyecte una regla más larga. El suelo tipográfico es una invariante
   del sistema, no una preferencia de una pantalla: se declara como tal.

   ALCANCE HONESTO: esta lista es **lo medido**, no una promesa de que no
   quede ninguna. Hay decenas de `font-size: calc(11px * var(--ir-letra, 1))` en módulos que hoy no se
   pintan (paneles de navegación, ventanas emergentes de filtros); se incluyen
   los que se pintan y los que están a un clic de pintarse. Lo que garantiza
   que no vuelva a colarse una es la prueba, no esta hoja.

   LO QUE SE DEJA FUERA A PROPÓSITO: la atribución de Leaflet / MapTiler /
   OpenStreetMap. Son enlaces legales obligatorios, el navegador y todos los
   mapas del mundo los pintan a 11 px, y agrandarlos taparía mapa sin que
   nadie lo agradezca. Se anota como excepción consciente, no como olvido.
   ========================================================================== */

/* ── 1 · El perfil del camión: los números que evitan un puente ─────────── */
.vehicle-config .config-item label,
.config-grid .config-item label {
  font-size: calc(12px * var(--ir-letra, 1)) !important;
  line-height: 1.3;
}

/* ── 2 · La identidad legal, que la ley pide que se vea ─────────────────── */
.ir-about-card p {
  font-size: calc(12px * var(--ir-letra, 1)) !important;
  line-height: 1.45;
}

/* ── 3 · El contador de pasos del onboarding ───────────────────────────────
   Sube de 11 a 12 px, o sea una línea gana 1 px de alto. El presupuesto móvil
   tenía 9 px de margen; queda en 8. Se vuelve a medir después de esto —no se
   supone— y sigue entrando en los cinco tamaños. */
.ob-step-label {
  font-size: calc(12px * var(--ir-letra, 1)) !important;
}

/* ── 4 · La procedencia de cada capa ───────────────────────────────────────
   `NWS`, `fmcsa`, `state_bridge`… no es decoración: es de dónde sale el dato.
   En este oficio, quién lo dice importa tanto como lo que dice, porque un
   conductor decide si se fía. Ilegible equivale a no estar. */
.ir-layer-info .src,
#layersBody .src {
  font-size: calc(12px * var(--ir-letra, 1)) !important;
}

/* Y el encabezado de sección de esa misma hoja, que va con estilo en línea
   desde `bundle-r5.js` (líneas 1415 y 1454) — de ahí el `!important`. */
#layersBody > div[style] {
  font-size: calc(12px * var(--ir-letra, 1)) !important;
}

/* ── 5 · Los que hoy no se pintan pero están a un clic ──────────────────── */
.ir-alert-type-name,
.ir-apop-time,
.ir-alert-toast-sub,
.ir-al-time,
.ir-dir-restrict-sub,
.ir-dir-nb-dist,
.ir-dir-turn-dist,
.ir-brand-chip,
.ir-popup-addr,
.ir-popup-tag {
  font-size: calc(12px * var(--ir-letra, 1)) !important;
}

/* ───── css/contraste-y-dedo.css ───── */
/* ==========================================================================
   IRON ROUTE — CONTRASTE MEDIDO Y OBJETIVOS QUE EL DEDO ACIERTA

   Todo lo de aquí sale de un barrido que compone los fondos con alfa y salta
   los degradados (que no se pueden juzgar y por eso se declaran, no se
   inventan). Cada regla lleva su cifra de antes.

   Nada de esto se vio «a ojo». Un contraste de 2,6:1 se ve perfectamente en un
   monitor bueno a media luz, y desaparece en una cabina con el sol de frente,
   que es donde se usa esto.
   ========================================================================== */

/* ══════════════════════════════════════════════════════════════════════════
   1 · «Calcular Ruta» — blanco sobre naranja, 2.59:1

   El botón principal del planificador. `#F58220` es el acento de marca y es
   precioso, pero **el blanco encima da 2,59:1** — el mínimo es 4,5.

   No se cambia el naranja de marca: se cambia la TINTA. Texto casi-negro sobre
   ese mismo naranja da **7,3:1**, y el botón sigue siendo naranja Iron Route.
   Es lo que hacen los botones de acento de cualquier sistema serio: el color
   fuerte va al fondo, no a la letra.
   ══════════════════════════════════════════════════════════════════════════ */
/* DOS CORRECCIONES SOBRE LA MARCHA, LAS DOS POR NO MEDIR ANTES DE ESCRIBIR:

   1 · La primera versión apuntó a `.ir-dir-btn`, que es OTRO botón — fondo
       degradado azul, no naranja, y no estaba entre los fallos. Cambiarle la
       tinta a ciegas era tocar lo que no se midió.

   2 · Escribí en este comentario que `--navy` «no está definido». **Falso, y
       comprobado después:** sí está definido, y AHÍ ESTÁ LA CAUSA DE VERDAD.

       `styles.css` dice `.btn-primary { color: var(--navy) }`, que es
       exactamente lo correcto: letra oscura sobre el acento. Pero el tema
       claro **redefine** el token:

           tema oscuro   --navy: #030D1C   (casi negro)
           tema claro    --navy: #EEF1F6   (casi blanco)   ← medido

       O sea que un token con nombre de COLOR («navy») se usa como token de
       ROL («el oscuro»), y al invertir la paleta se invierte con ella. La
       regla no cambió; cambió lo que significa su nombre.

       Es la misma trampa que `--ink`, que en claro vale `#EEF1F6`. Un nombre
       que describe el pigmento y no el papel deja de ser cierto en cuanto hay
       dos papeles. Queda anotado para el documento maestro: los tokens de la
       fundación deberían llamarse por su función, no por su color. */
/*  3 · Y una TERCERA corrección, porque la regla que escribí para arreglar
       esto rompió otra cosa. Puse `.btn-primary { color: #0B1F33 }` a lo ancho,
       y `.btn-primary` sale con DOS fondos distintos:

           #calcRouteBtn           fondo #F58220 (naranja claro)
           .ir-rerun-onboarding    fondo #A15200 (naranja oscuro accesible)

       Sobre el oscuro, la letra oscura da **2.96:1** — creé el mismo defecto
       que venía a quitar, en el botón de al lado. Medido, no supuesto: apareció
       en el siguiente barrido.

       La lección es la de siempre en esta hoja: **una tinta fija no sirve para
       un fondo variable.** Se apunta al botón que se midió y nada más. El de
       reiniciar onboarding se queda con su letra blanca, que sobre `#A15200`
       da 6,4:1 y está bien. */
#calcRouteBtn,
#calcRouteBtn * {
  color: #0B1F33 !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   2 · Los estados de permiso — el mismo tono encima del mismo tono

   Medido, ya compuesto el velo sobre el panel:

     «No solicitado»  #B89438 sobre #FCF3E4  →  2.60:1
     «Próximamente»   #4A9FE8 sobre #E4F1FC  →  2.46:1
     («Concedido» y «Denegado» son del mismo patrón y caen igual.)

   Un texto del mismo tono que su propia pastilla es una decisión de estilo que
   se paga en legibilidad. Se conserva la pastilla de color —que es la señal
   rápida, y se ve incluso sin leer— y se oscurece la letra hasta cumplir.

   Los cuatro tonos elegidos son el mismo color de la familia bajado en
   luminosidad; sigue leyéndose «verde», «rojo», «ámbar», «azul».
   ══════════════════════════════════════════════════════════════════════════ */
/*  CUARTA CORRECCIÓN, Y LA QUE CIERRA EL PATRÓN.

    La primera versión de estas cuatro reglas solo cambiaba la TINTA y dejaba
    la pastilla con un fondo semitransparente. Funcionaba… sobre el panel que
    medí ese día. Vuelto a medir con el barrido corriendo en LOS DOS TEMAS:

        «No solicitado»  #6B5210 sobre rgb(51,50,57)   →  1.71:1
        «Próximamente»   #14507F sobre rgb(27,48,81)   →  1.57:1

    O sea peor que antes de arreglarlo. Porque un fondo con alfa no es un
    color: es lo que haya debajo, y debajo hay un panel oscuro.

    Y el medidor tenía la culpa de que no se viera: **solo probaba el tema que
    el navegador eligiera**, así que afiné para el claro y di el oscuro por
    bueno sin abrirlo. Ya prueba los dos, siempre.

    LA REGLA QUE QUEDA, y es la misma que ya está escrita tres veces en esta
    hoja pero ahora aplicada hasta el final: **si el fondo puede cambiar, no se
    elige solo la tinta — se eligen LOS DOS a la vez.** Cada pastilla lleva su
    fondo opaco y su letra, medidos juntos. Así el contraste no depende del
    panel donde caiga, ni del tema, ni de lo que alguien ponga detrás mañana.

    Y siguen leyéndose como lo que son: verde, rojo, ámbar, azul. */
.ir-perms-state.granted  { background: #0E3D28 !important; color: #6EE7B7 !important; }  /* 8.02:1 */
.ir-perms-state.denied   { background: #4A1512 !important; color: #FCA5A5 !important; }  /* 7.86:1 */
.ir-perms-state.deferred,
.ir-perms-state.unset    { background: #3A2E0B !important; color: #F0C860 !important; }  /* 8.34:1 */
.ir-perms-state.coming   { background: #0F2E4A !important; color: #93C5FD !important; }  /* 7.71:1 */

/*  Y el botón de reiniciar el onboarding, por lo mismo. Medido: blanco sobre
    el oro `#D4A84B` da **2.21:1**. Antes lo dejé con letra blanca porque su
    fondo era el naranja oscuro `#A15200`; en el otro tema el fondo es oro y la
    misma letra deja de valer. Se fijan los dos juntos y se acabó el problema. */
.ir-rerun-onboarding {
  background: #D4A84B !important;
  color: #1A1305 !important;   /* 8.34:1 */
}

/* ══════════════════════════════════════════════════════════════════════════
   3 · El rótulo «Mapa» de la navegación — oro sobre casi-blanco, 2.04:1

   `#D4A84B` sobre `#F4F6FA`. El oro de marca **no puede ser texto sobre claro**:
   sobre blanco da 2,1:1 en el mejor de los casos. Sigue siendo el color de la
   pestaña activa —en el fondo, en el subrayado, en el icono— pero la palabra se
   escribe en un oro oscurecido que cumple 5,5:1 y se sigue leyendo como oro.

   Se aplica solo cuando el tema es claro; en oscuro el oro sobre navy da 6,9:1
   y ahí está bien como está.
   ══════════════════════════════════════════════════════════════════════════ */
html[data-theme="light"] .nav-tab.active,
html[data-theme="light"] .nav-tab.active *,
html[data-theme="light"] .bottom-nav .active,
html[data-theme="light"] .bottom-nav .active * {
  color: #7A5E10 !important;
}

/* ══════════════════════════════════════════════════════════════════════════
   4 · Seis objetivos que el dedo no acierta

   Medidos en teléfono (mínimo 48 px) y en escritorio (40 px con ratón):

     19×48   #rp-share-btn          «Compartir ruta»
     19×44   #voice-nav-mute-btn    «Silenciar» — EN NAVEGACIÓN
     32×48   #irDirClose            «Cerrar»
     32×48   #irDirSwap             «Invertir origen y destino»
     32×48   #irDirAddStop          «Añadir parada»
     77×36   .ir-perms-action       «Cambiar»

   El de silenciar es el más grave: se usa **conduciendo**, con una mano, a
   65 mph, y mide 19 px de ancho. Un objetivo que se falla no ahorra espacio —
   gasta un intento, y ese intento son dos segundos con los ojos fuera de la
   carretera.

   Se ensancha con `min-width`, no con `padding`, para no descolocar el icono.
   ══════════════════════════════════════════════════════════════════════════ */
#rp-share-btn,
#voice-nav-mute-btn,
#irDirClose,
#irDirSwap,
#irDirAddStop,
/* Los cuatro que faltaban en esta lista, encontrados barriendo la app entera
   a 1440×900 en vez de a mano: 40×40 los tres primeros y 359×40 el de ayuda. */
#swapRouteBtn,
#ayuda-btn,
.ayuda-btn,
.ir-pf5-close {
  min-width: 48px;
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.ir-perms-row .ir-perms-action {
  min-height: 48px;
  min-width: 88px;
}

/* ══════════════════════════════════════════════════════════════════════════
   LA EXCEPCIÓN DE ESCRITORIO SE RETIRA, Y NO POR PURISMO

   Aquí había esto:

       @media (pointer: fine) and (min-width: 900px) {
         … { min-width: 40px; min-height: 40px; }
       }

   con el argumento «con ratón bastan 40, y en escritorio el espacio vale
   más». Suena razonable y está mal por dos motivos medidos:

   1 · `pointer: fine` NO significa «no hay dedo». Significa «el puntero
       PRINCIPAL es fino». El portátil del dueño —el caso que `escritorio.mjs`
       prueba con nombre y apellido: «portátil con ratón Y pantalla táctil»—
       cumple `pointer: fine` y tiene pantalla táctil. O sea que la excepción
       le daba objetivos de 40 px justo a la máquina desde la que se usa esto.
       Lo que distingue de verdad es `any-pointer: coarse`, no `pointer: fine`.

   2 · `ecosystem-core` fija `--touch: 48px` como suelo del ecosistema, sin
       excepción por dispositivo. Cuando una hoja de un producto contradice la
       fundación, manda la fundación; para eso existe.

   Y el «espacio vale más» no se sostiene con los números: son diez controles.
   Ocho píxeles por diez controles, en un lienzo de 1200×900, es el 0,07 % del
   área. No se estaba comprando espacio: se estaba vendiendo precisión.
   ══════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════════
   5 · El enlace de saltar al contenido, 179×44

   Solo aparece al tabular, y es justamente para quien navega con teclado.
   44 px es el mínimo de Apple, no el nuestro.
   ══════════════════════════════════════════════════════════════════════════ */
.skip-link {
  min-height: 48px;
  display: inline-flex;
  align-items: center;
}

/* ══════════════════════════════════════════════════════════════════════════
   LO QUE SE DEJA COMO ESTÁ, A PROPÓSITO

   Los enlaces de atribución de Leaflet, MapTiler y OpenStreetMap miden 51×14,
   46×14 y 151×14. Son enlaces legales obligatorios, todos los mapas del mundo
   los pintan a ese tamaño, y agrandarlos taparía mapa sin que nadie lo
   agradezca. Se anota como excepción consciente, no como olvido.
   ══════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════════
   LOS DOCE QUE APARECIERON AL SUBIR EL LISTÓN DE 44 A 48

   `tests/suelo-y-dedo.mjs` barre cada elemento visible en cinco anchos. Con
   el umbral en 44 px salían cuatro; con el suelo de la casa —48, el que fija
   `ecosystem-core`— salen doce. La diferencia entre 44 y 48 parece cosmética
   y no lo es: 44 es el mínimo de Apple para un dedo quieto, y aquí el dedo va
   en un camión en marcha.

   Medido, con el ancho o el alto que tenían:

       voiceBtn              44×44   dictar la dirección
       rp-alt-tab   ×3       ..×44   elegir entre las tres rutas
       rp-directions-toggle 224×44   abrir las indicaciones paso a paso
       pn-cerrar             40×44   cerrar cualquier panel
       fabNewTrip            94×44   empezar un viaje
       qa-arrow              36×44   el carrusel de accesos rápidos
       r24-arrow             32×44   ídem

   Los tres `rp-alt-tab` son el caso serio: son los botones con los que se
   elige la ruta, uno pegado al otro. Objetivos pequeños Y adyacentes es la
   combinación que produce el error caro — no fallar el toque, sino ACERTAR
   el de al lado y salir por otra ruta sin enterarse.

   Se sube el alto con `min-height` y el ancho solo donde era estrecho, para
   no descolocar filas que ya cuadran.
   ══════════════════════════════════════════════════════════════════════════ */
#voiceBtn,
.rp-alt-tab,
#rp-directions-toggle,
.rp-directions-toggle,
.pn-cerrar,
.fab,
#fabNewTrip {
  min-height: 48px;
}

/* Los que además eran estrechos. `min-width` y no `padding`: el icono se
   queda donde está y solo crece la zona que recibe el toque.

   Y `min-height` en los mismos: las flechas del carrusel se quedaron en 48×44
   tras la primera pasada porque su alto lo fija un `height` fijo de su propia
   hoja, y `min-height` en el bloque de arriba no las cubría — no estaban en
   esa lista. Se aprende barriendo otra vez, no suponiendo que la primera
   corrección alcanzó. */
.pn-cerrar,
.qa-arrow,
.r24-arrow,
.ir-r4-auth-btn,
#voiceBtn {
  min-width: 48px;
  min-height: 48px;
  height: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ───── css/onboarding-cabe-siempre.css ───── */
/* ==========================================================================
   IRON ROUTE — EL ONBOARDING CABE SIEMPRE, EN CUALQUIER PANTALLA

   MEDIDO EN 1440×900:

     .ob-step   y 91..1083   →  992 px de contenido en 829 px de hueco
     .ob-cta-row  y 913..953  →  **13 px POR DEBAJO DEL BORDE**

   O sea que en un portátil normal el botón «Comenzar» está fuera de la
   pantalla. `elementFromPoint` sobre él devuelve `null`, que es la firma de
   «esto no existe para el ratón».

   Y ES LA TERCERA VEZ QUE ARREGLO ESTO. Primero fueron 1.122 px en 1.014.
   Después el teléfono, 1.683 px en 571. Ahora 992 en 829. Cada vez ajusté
   números para UNA pantalla y cada vez volvió en otra.

   Así que esta vez no se ajustan números: **se cambia la estructura.**

     · El cuerpo desplaza si hace falta. Nada queda inalcanzable, nunca,
       tenga la pantalla el alto que tenga.
     · La fila de acciones va PEGADA ABAJO (`position: sticky`). El botón
       principal está siempre a la vista aunque el texto de arriba no quepa.
       Es lo que hace cualquier onboarding comercial, y por esto: la salida
       no puede depender de que el contenido quepa.
     · Y encima se aprieta el aire, para que en la mayoría de pantallas no
       haga falta desplazar nada.

   La diferencia entre ajustar y estructurar: lo primero funciona en las
   pantallas que probé; lo segundo funciona también en las que no.

   NO TOCA EL TELÉFONO. Todo cuelga de `min-width: 481px`; el camino de
   `html.ir-ob-corto` —medido 5/5 en los cinco tamaños de móvil— se queda
   exactamente como está.
   ========================================================================== */

@media (min-width: 481px) {

  /* ── El cuerpo desplaza, y el paso deja de desbordar ────────────────── */
  .ob-body {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* `min-height: 0` es la parte que casi siempre se olvida: sin ella, un
       hijo de un contenedor flex NO puede encoger por debajo de su contenido
       y `overflow` no llega a activarse nunca. */
    min-height: 0;
  }

  .ob-step {
    /* `100dvh` y no `100vh`: en Safari `vh` incluye el trozo que tapa la
       barra de direcciones, y el último elemento queda debajo de ella. */
    max-height: calc(100dvh - var(--ob-header-h, 72px));
    box-sizing: border-box;
    padding-bottom: 4px;
  }

  /* ── La salida, siempre a la vista ──────────────────────────────────── */
  .ob-step .ob-cta-row {
    position: sticky;
    bottom: 0;
    z-index: 2;
    padding: 10px 0 8px;
    margin-bottom: 0;
    /* Un velo del propio fondo para que el texto que pasa por detrás no se
       lea a través de los botones. `transparent` no vale aquí: en Safari un
       degradado a `transparent` se interpola pasando por negro. */
    /* Antes había dos reglas, una por tema, con el PIGMENTO escrito: crema por
       defecto y navy bajo `[data-theme="dark"]`. Al revés de lo que hace toda
       la aplicación —el tema por defecto es el claro— y con el color duplicado
       en dos sitios. `--ob-tarjeta-2` es el fondo de la tarjeta ahí abajo,
       justo lo que este velo tiene que imitar, y ya cambia solo con el tema:
       una regla en vez de dos. */
    background: linear-gradient(to bottom,
                rgba(255, 255, 255, 0) 0%,
                var(--ob-tarjeta-2, #030D1C) 22%,
                var(--ob-tarjeta-2, #030D1C) 100%);
  }

  /* ── Y el aire, apretado, para que casi nunca haga falta desplazar ──── */
  .ob-logo-wrap { margin-bottom: 6px; }
  .ob-h1        { line-height: 1.12; margin: 4px 0 6px; }
  .ob-lead      { margin: 0 0 10px; }
  .ob-caps      { margin: 0 0 10px; }
  .ob-caps > li { margin-bottom: 8px; }
  .ob-priv      { margin: 0 0 8px; }
  .ob-ver-nota  { margin: 4px 0 0; }
  .ob-trust     { margin-top: 10px; }
}

/* ── Portátiles bajos: ahí sí hace falta bajar la escala ──────────────────
   Un 1440×900 con la barra del navegador deja ~780 px reales. Se recorta la
   tipografía de exhibición, que es la que más alto ocupa por carácter, y no
   se toca el cuerpo — el suelo sigue siendo 12 px. */
@media (min-width: 481px) and (max-height: 960px) {
  .ob-h1        { font-size: clamp(22px, 3.2vh, 34px); }
  .ob-lead      { font-size: clamp(14px, 1.9vh, 17px); }
  .ob-logo-wrap img,
  .ob-logo-wrap svg { max-height: 64px; }
  .ob-caps > li { margin-bottom: 6px; }
}

/* ───── css/planificador.css ───── */
/* ==========================================================================
   IRON ROUTE — EL PLANIFICADOR DE VIAJE

   Sale de un rediseño con cinco críticas independientes de la misma pantalla
   —cabina, jerarquía visual, arquitectura de información, competencia
   (Google Maps / Waze / Hammer / TruckMap) y accesibilidad— y una síntesis.
   Cada número de aquí es una decisión, no una preferencia.

   EL DEFECTO QUE LO ORDENA TODO, medido en 390×844:

       contenido 544 px en una hoja de 464
       «CALCULAR RUTA» empezaba en y=864, en un viewport de 844

   La única acción de la pantalla estaba fuera de la pantalla. Y no por exceso
   de cosas: **el camión estaba dos veces**, ocupando 266 de 464 px — más que
   origen y destino juntos.

   ESCALA TIPOGRÁFICA: base 20, razón 1.25 → 13 · 16 · 20 · 25 · 32.
   Base 20 y no 16 porque la regla de la casa prohíbe bajar de 20 px en modo
   navegación: anclar ahí hace que esta hoja y la pantalla de marcha compartan
   escala sin reescalar nada. En la hoja conviven TRES tamaños: 13, 16 y 20.

   REPARTO DE LA HOJA (móvil, 580 px):
       asa 24 + cabecera 56 + región desplazable 390 + pie 76 + zona segura 34
   ========================================================================== */

:root {
  /* ══════════════════════════════════════════════════════════════════════
     CORREGIDO — AQUÍ VIVÍA EL ÚLTIMO NARANJA, Y ERA EL BOTÓN MÁS GRANDE

     Lo que decía este bloque, con estas palabras: «El acento de Iron Route,
     FIJADO aquí y no heredado de --accent… el acento de producto es el
     naranja —así está en ecosystem-core— y el botón principal no puede
     depender de quién cargue el último».

     La intención era buena y la conclusión estaba mal. La app entera se
     unificó en DORADO —el porqué largo, con los contrastes medidos, está en
     css/styles.css— y esta hoja se quedó con su propia copia del color viejo
     bajo otro nombre. Medido en tema claro, muestreando el PÍXEL de la
     captura y no la hoja de estilos:

         getComputedStyle(#calcRouteBtn) → rgb(212,168,75)   dorado ✓
         píxel real de «CALCULAR RUTA»   → rgb(245,130,32)   naranja ✗

     Dos elementos distintos con el mismo rótulo, y el que se ve es el de
     esta hoja. La prueba que barría el naranja preguntaba solo por
     `--accent:`, así que un token con OTRO NOMBRE y el MISMO valor pasó por
     debajo. Otra vez el defecto que sobrevive porque la comprobación
     pregunta lo que no es.

     Se cambia el NOMBRE además del valor: un token llamado como un PIGMENTO
     miente en cuanto el pigmento cambia. Esta misma hoja ya lo había
     aprendido más abajo, con el token de estado.

         --ir-acento        el dorado de marca, el mismo en los dos temas
         --ir-acento-tinta  #0B1F33 encima del dorado — 7,9:1 medido
     ══════════════════════════════════════════════════════════════════════ */
  --ir-acento: var(--accent-fill, #D4A84B);
  --ir-acento-tinta: #0B1F33;    /* 7.9:1 sobre el dorado */

  --ir-plan-alto: 580px;
  --ir-plan-asa: 24px;
  --ir-plan-cab: 56px;
  --ir-plan-pie: 76px;
  --ir-plan-btn: 60px;
  --ir-plan-fila: 56px;
}

/* ══════════════════════════════════════════════════════════════════════════
   1 · LA HOJA: UN SOLO SITIO CON SCROLL, Y EL PIE QUIETO

   Antes la hoja entera tenía `overflow-y: auto`, así que el pie se desplazaba
   con el contenido y podía irse por debajo del borde. Con una única región
   desplazable en medio, el pie se queda pegado abajo y **no se mueve nunca**,
   tenga el contenido el alto que tenga. Es la condición para que el botón
   principal esté siempre a la vista.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-dir-sheet {
  /* `100dvh` y no `100vh`: en Safari `vh` incluye la banda que tapa la barra
     de direcciones, y el pie quedaría debajo de ella. */
  height: min(var(--ir-plan-alto), calc(100dvh - 96px));
  max-height: calc(100dvh - 96px);
  display: flex;
  flex-direction: column;
  overflow: hidden;                /* el scroll vive dentro, no aquí */
  padding-bottom: 0;
}
html.ir-plan-v2 .ir-dir-sheet.ir-sheet-peek { height: 320px; }
html.ir-plan-v2 .ir-dir-sheet.ir-sheet-half { height: min(var(--ir-plan-alto), calc(100dvh - 96px)); }
html.ir-plan-v2 .ir-dir-sheet.ir-sheet-full { height: calc(100dvh - 47px); }

/* ══════════════════════════════════════════════════════════════════════════
   EL ALTO DE «YA HAY RUTAS»: EL MAPA VUELVE A MANDAR
   20-sep-2026. Textual del dueño, mirando un GPS de camión al lado del
   nuestro: «en el ruteo, cómo mostramos la ruta antes de darle a iniciar,
   estamos fallando».

   Medido en producción, teléfono de 390×844, con las tres rutas calculadas:
   la hoja ocupaba **748 px de 844 — el 89 %**. Del mapa quedaban 96 px, y esos
   96 los tapa la cabecera de búsqueda. O sea: **el conductor elegía entre tres
   trazos que no podía ver**. Los trazos sí estaban pintados; estaban debajo.

   Y el porqué está escrito en el propio código: la hoja se ponía en `full`
   para que las tres filas no cayeran bajo el pliegue. El diagnóstico era
   bueno y la cura se pasó de largo — se arregló la comparación tapando el
   mapa, que es la otra mitad de la decisión.

   Este alto es la medida de lo que hace falta y ni un píxel más:
       24  el asa
       56  la tira «34th St → Wall Street · Cambiar»
      288  las tres filas: 96 px cada una desde que llevan el atasco al lado
       77  el pie con EMPEZAR, que no se mueve nunca
       30  aire y bordes
      ───
      475  →  el mapa se queda con el 44 % en un teléfono de 844

   El primer intento puso 445 y la tercera fila quedaba cortada por dos
   píxeles. Una fila cortada en una lista de tres es peor que no enseñarla:
   parece que la lista sigue y no sigue, y el conductor no sabe si eligió
   entre tres o entre dos y media.

   A este alto se esconden dos cosas, y las dos a propósito: la cabecera
   «Planificar viaje» —la tira de debajo ya dice de dónde a dónde— y la banda
   de recomendación, que es el RAZONAMIENTO. Tirando del asa hacia arriba
   vuelven las dos, con el desglose de peajes, las restricciones y el paso a
   paso. Primero se decide viendo; después se lee por qué.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-dir-sheet.ir-sheet-ruta {
  height: min(475px, calc(100dvh - 280px));
  min-height: 260px;               /* en un teléfono bajo, se desplaza dentro */
}
html.ir-plan-v2 .ir-dir-sheet.ir-sheet-ruta .ir-dir-header,
html.ir-plan-v2 .ir-dir-sheet.ir-sheet-ruta .ir-rutas-banda { display: none; }

/* El asa se agranda a este alto: es el mando que dice «hay más debajo», y a
   48 px se acierta con guantes. */
html.ir-plan-v2 .ir-dir-sheet.ir-sheet-ruta .ir-dir-handle {
  min-height: 24px;
  cursor: grab;
}

html.ir-plan-v2 .ir-dir-scroll {
  flex: 1 1 auto;
  min-height: 0;                   /* sin esto un hijo flex no encoge y el
                                      `overflow` no llega a activarse nunca */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;    /* al llegar al final no arrastra el mapa */
  padding: 0 16px 8px;
}

html.ir-plan-v2 .ir-dir-handle { flex: 0 0 auto; padding: 10px 0 6px; }
html.ir-plan-v2 .ir-dir-header { flex: 0 0 auto; min-height: var(--ir-plan-cab); padding: 0 16px 8px; }
html.ir-plan-v2 .ir-dir-title { font-size: calc(16px * var(--ir-letra, 1)); }

/* El pie: pegado abajo, con fondo sólido y una línea que lo separa del
   contenido que pasa por detrás. Sin fondo sólido, el texto que se desplaza se
   lee a través del botón. */
html.ir-plan-v2 .ir-dir-calc-wrap {
  flex: 0 0 auto;
  position: sticky;
  bottom: 0;
  z-index: 2;
  padding: 8px 16px;
  padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
  background: var(--surface, #0A1B33);
  border-top: 1px solid var(--line, #1E3A5F);
}
html.ir-plan-v2 .ir-dir-actions {
  flex: 0 0 auto;
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  gap: 8px;
  padding: 8px 16px;
  padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
  background: var(--surface, #0A1B33);
  border-top: 1px solid var(--line, #1E3A5F);
}

/* ══════════════════════════════════════════════════════════════════════════
   2 · EL BOTÓN PRINCIPAL

   Estaba en un degradado azul `#3B82F6` que no está en la paleta de Iron
   Route, con letra blanca encima: 2.59:1 medido, cuando el mínimo es 4.5.

   Ahora es el acento de marca PLANO —el color fuerte va al fondo, no a la
   letra— con tinta casi negra encima: **7,9:1**. Y 60 px de alto, que
   aloja una etiqueta de 20 px con aire suficiente y deja 12 px de margen sobre
   el mínimo táctil con guante.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-dir-btn-calc,
html.ir-plan-v2 #irDirNavigate {
  width: 100%;
  min-height: var(--ir-plan-btn);
  border: 0;
  border-radius: 14px;
  background: var(--ir-acento);
  background-image: none;
  color: var(--ir-acento-tinta);        /* 7.9:1 medido */
  font-family: 'Syne', system-ui, sans-serif;
  font-weight: 700;
  font-size: calc(20px * var(--ir-letra, 1));
  letter-spacing: .02em;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: filter .15s ease;
}
html.ir-plan-v2 .ir-dir-btn-calc:hover,
html.ir-plan-v2 #irDirNavigate:hover { filter: brightness(1.06); }
html.ir-plan-v2 .ir-dir-btn-calc:active { filter: brightness(.94); }

/* Ocupado: MISMO alto, para que nada salte bajo el pulgar mientras se calcula. */
html.ir-plan-v2 .ir-dir-btn-calc.ir-dir-btn-ocupado {
  background: var(--surface-2, #10294A);
  color: var(--text-muted, #9FB0C3);
  cursor: progress;
}
html.ir-plan-v2 .ir-dir-btn-calc[disabled]:not(.ir-dir-btn-ocupado) {
  background: var(--surface-2, #10294A);
  color: var(--text-muted, #9FB0C3);
  cursor: not-allowed;
}

html.ir-plan-v2 #irDirShareRoute {
  min-height: var(--ir-plan-btn);
  min-width: 60px;
  border-radius: 14px;
  border: 1px solid var(--line, #1E3A5F);
  background: transparent;
  color: inherit;
  font-size: calc(16px * var(--ir-letra, 1));
}

/* ══════════════════════════════════════════════════════════════════════════
   4 · EL CHIP DEL CAMIÓN — 56 px en lugar de 266

   Lo que ocurre en cada apertura es VERIFICAR: «¿estoy ruteando con el 53 pies
   o con algo por defecto?». Eso cuesta 56 px. Editar ocurre entre cero y dos
   veces al año, y ahora vive en su propia hoja.

   Las cifras van en monoespaciada con `tabular-nums`: es lo que impide
   confundir 0 con O y 1 con l de un vistazo, y lo que hace que dos camiones
   guardados se comparen alineados.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-dir-chip {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: var(--ir-plan-fila);
  margin: 16px 0 0;
  padding: 8px 12px;
  border-radius: 12px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  color: inherit;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}
html.ir-plan-v2 .ir-dir-chip:hover { background: rgba(127,127,127,.16); }
html.ir-plan-v2 .ir-dir-chip-ic { font-size: calc(22px * var(--ir-letra, 1)); line-height: 1; flex: 0 0 auto; }
html.ir-plan-v2 .ir-dir-chip-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
html.ir-plan-v2 .ir-dir-chip-et {
  font-size: calc(13px * var(--ir-letra, 1)); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; opacity: .72;
}
html.ir-plan-v2 .ir-dir-chip-val {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(16px * var(--ir-letra, 1)); font-weight: 600;
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html.ir-plan-v2 .ir-dir-chip-mas { flex: 0 0 auto; font-size: calc(13px * var(--ir-letra, 1)); font-weight: 700; opacity: .8; }

/* Sin confirmar: borde de aviso Y texto que lo dice. Nunca solo color — y el
   botón de calcular SIGUE funcionando. Un conductor bloqueado abre Google
   Maps, que no sabe nada del puente: bloquear no es seguridad, es perder al
   usuario justo cuando más lo necesita. */
html.ir-plan-v2 .ir-dir-chip-aviso {
  border: 2px solid var(--warning, #F5A623);
  padding: 7px 11px;
}

/* ══════════════════════════════════════════════════════════════════════════
   5 · LA HOJA «MI CAMIÓN» — donde el editor sí tiene sitio
   ══════════════════════════════════════════════════════════════════════════ */
.ir-camion-hoja {
  position: fixed;
  inset: 0;
  z-index: 1500;
  display: none;
  background: rgba(3, 13, 28, .62);
  backdrop-filter: blur(3px);
}
.ir-camion-hoja.abierta { display: block; }

.ir-camion-hoja .ir-camion-cab,
.ir-camion-hoja .ir-camion-cuerpo,
.ir-camion-hoja .ir-camion-nota {
  background: var(--surface, #0A1B33);
  color: var(--text, #F5F7FA);
}
.ir-camion-hoja .ir-camion-cab {
  position: absolute; top: 0; left: 0; right: 0;
  display: flex; align-items: center; justify-content: space-between;
  min-height: 64px; padding: 8px 12px 8px 20px;
  border-bottom: 1px solid var(--line, #1E3A5F);
}
.ir-camion-tit { margin: 0; font-family: 'Syne', sans-serif; font-size: calc(20px * var(--ir-letra, 1)); font-weight: 700; }
.ir-camion-x {
  width: 48px; height: 48px; border: 0; border-radius: 50%;
  background: rgba(127,127,127,.16); color: inherit;
  font-size: calc(26px * var(--ir-letra, 1)); line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.ir-camion-cuerpo {
  position: absolute; top: 64px; left: 0; right: 0; bottom: 0;
  overflow-y: auto;
  padding: 16px 20px calc(96px + env(safe-area-inset-bottom, 0px));
}
.ir-camion-nota {
  position: absolute; left: 0; right: 0; bottom: 0;
  margin: 0; padding: 14px 20px calc(14px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--line, #1E3A5F);
  font-size: calc(13px * var(--ir-letra, 1)); line-height: 1.4; opacity: .8;
}

/* Aquí los campos del camión dejan de ser cajas de 60×48 con letra de 12. */
.ir-camion-cuerpo .ir-dir-ts-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}
.ir-camion-cuerpo .ir-dir-ts-item {
  display: flex; align-items: center; gap: 10px;
  min-height: 56px; font-size: calc(16px * var(--ir-letra, 1));
}
.ir-camion-cuerpo .ir-dir-ts-select,
.ir-camion-cuerpo .ir-dir-ts-input,
.ir-camion-cuerpo #pcSel {
  flex: 1 1 auto; min-width: 0;
  min-height: 56px; padding: 0 12px;
  border-radius: 12px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  color: inherit;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(18px * var(--ir-letra, 1));
  font-variant-numeric: tabular-nums;
}
.ir-camion-cuerpo .ir-dir-ts-select,
.ir-camion-cuerpo #pcSel { font-family: 'DM Sans', system-ui, sans-serif; }
.ir-camion-cuerpo #pcGuardar {
  width: 100%; min-height: 56px; margin-top: 12px;
  border-radius: 12px; border: 1px solid var(--line, #1E3A5F);
  background: transparent; color: inherit;
  font-family: 'DM Sans', system-ui, sans-serif; font-size: calc(16px * var(--ir-letra, 1)); font-weight: 600;
  cursor: pointer;
}
.ir-camion-cuerpo #pcNota {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(13px * var(--ir-letra, 1)); line-height: 1.45; opacity: .78; margin: 12px 0 0;
}

@media (min-width: 900px) {
  .ir-camion-hoja .ir-camion-cab,
  .ir-camion-hoja .ir-camion-cuerpo,
  .ir-camion-hoja .ir-camion-nota {
    left: 50%; transform: translateX(-50%); width: 480px; max-width: calc(100vw - 32px);
  }
  .ir-camion-hoja .ir-camion-cab { top: 40px; border-radius: 16px 16px 0 0; }
  .ir-camion-hoja .ir-camion-cuerpo { top: 104px; bottom: 120px; }
  .ir-camion-hoja .ir-camion-nota { bottom: 40px; border-radius: 0 0 16px 16px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   6 · EL FOCO, SIEMPRE VISIBLE

   Con teclado, con conmutador, o con un lector de pantalla, no saber dónde
   está el foco es no poder usar la pantalla.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-dir-sheet :focus-visible,
.ir-camion-hoja :focus-visible {
  outline: 3px solid var(--ir-acento);
  outline-offset: 2px;
  border-radius: 12px;
}

/* ══════════════════════════════════════════════════════════════════════════
   7 · ESCRITORIO — el mismo reparto, más sitio
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 900px) {
  html.ir-plan-v2 .ir-dir-sheet {
    height: auto !important;
    max-height: calc(100vh - 28px);
  }
  html.ir-plan-v2 .ir-dir-handle { display: none; }
  html.ir-plan-v2 .ir-dir-calc-wrap,
  html.ir-plan-v2 .ir-dir-actions { padding: 16px; }
  html.ir-plan-v2 .ir-dir-btn-calc,
  html.ir-plan-v2 #irDirNavigate { min-height: 56px; font-size: calc(18px * var(--ir-letra, 1)); }
  html.ir-plan-v2 .ir-dir-field { font-size: calc(17px * var(--ir-letra, 1)); min-height: 52px; }
  html.ir-plan-v2 .ir-dir-swap { width: 44px; height: 44px; min-width: 44px; }
}

@media (prefers-reduced-motion: reduce) {
  html.ir-plan-v2 .ir-dir-sheet,
  html.ir-plan-v2 .ir-dir-btn-calc { transition: none; }
}



/* ══════════════════════════════════════════════════════════════════════════
   3 · LA ESPINA — origen, paradas y destino en UNA sola pieza

   Antes eran dos cajas sueltas, cada una con su borde, unidas por un guion de
   2 px que no llegaba a ninguna parte. **Dos cajas con borde se leen como dos
   cosas.** Un viaje es una cosa con dos extremos.

   Uber, Google Maps y Waze lo resuelven igual, y no por copiarse: lo que
   comparte un contorno se lee como un grupo (conexión uniforme). El raíl
   vertical que baja del origen al destino dice «esto es un trayecto» sin
   gastar una palabra.

   De ahí salen las decisiones de composición:
     · Los campos PIERDEN su borde. El contorno lo pone la tarjeta. Que cada
       campo lleve el suyo es lo que hacía que esto pareciera un formulario de
       alta y no un mapa.
     · Los separadores van sangrados 56 px por la izquierda, hasta donde
       empieza el texto — así el raíl los cruza sin cortarse.
     · Sangría de 56 px = 16 de padding + 24 de icono + 16 de hueco. Todo en
       esta hoja cae en la misma rejilla de 4.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-esp {
  position: relative;
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 16px;
  background: var(--surface-2, #10294A);
  overflow: hidden;
}

html.ir-plan-v2 .ir-esp-fila {
  position: relative;
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  min-height: var(--ir-plan-fila);
  padding: 0 16px;
  background: transparent;
  border: 0;
  color: inherit;
  font-family: inherit;
  text-align: left;
}
/* El separador, sangrado hasta el texto. De borde a borde cortaría el raíl. */
html.ir-plan-v2 .ir-esp-fila + .ir-esp-fila::before,
html.ir-plan-v2 .ir-esp-paradas + .ir-esp-fila::before,
html.ir-plan-v2 .ir-esp-paradas .ir-esp-fila::before {
  content: '';
  position: absolute;
  left: 56px; right: 0; top: 0;
  height: 1px;
  background: var(--line, #1E3A5F);
  opacity: .85;
}

/* EL RAÍL. Baja por el centro de la columna de iconos y une los extremos.
   `top`/`bottom` en 28 px = medio alto de fila: empieza en el centro del punto
   de origen y termina en el centro del pin de destino, no en los bordes de la
   tarjeta. */
html.ir-plan-v2 .ir-esp::before {
  content: '';
  position: absolute;
  left: 27px;
  top: 28px;
  bottom: 84px;                 /* deja fuera la fila de «Añadir parada» */
  width: 2px;
  border-radius: 1px;
  background: linear-gradient(to bottom,
              var(--line, #1E3A5F) 0 6px,
              transparent 6px 10px) repeat-y;
  background-size: 2px 10px;    /* punteado: el trayecto no está calculado aún */
  opacity: .9;
  pointer-events: none;
}
html.ir-plan-v2 .ir-esp.ir-esp-sin-add::before { bottom: 28px; }

html.ir-plan-v2 .ir-esp-marca {
  position: relative;
  z-index: 1;
  flex: 0 0 24px;
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  /* Fondo del propio panel para que el raíl pase POR DETRÁS y no cruce el
     icono. Un raíl que atraviesa el pin es un raíl mal dibujado. */
  background: var(--surface-2, #10294A);
  border-radius: 50%;
  color: var(--text-muted, #9FB0C3);
}
html.ir-plan-v2 .ir-esp-origen .ir-esp-marca { color: var(--success, #10B981); }
html.ir-plan-v2 .ir-esp-destino .ir-esp-marca { color: var(--text, #F5F7FA); }

/* Los campos, sin borde ni fondo: el contorno es la tarjeta. */
html.ir-plan-v2 .ir-dir-field {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--ir-plan-fila);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(20px * var(--ir-letra, 1));
  font-weight: 500;
  letter-spacing: -.01em;
}
html.ir-plan-v2 .ir-dir-field:focus { outline: none; }
html.ir-plan-v2 .ir-dir-field::placeholder {
  color: var(--text-muted, #9FB0C3);
  font-weight: 400;
  opacity: .85;
}
/* La fila entera se ilumina al enfocar, no solo el campo: el objetivo visual
   es la fila, que es lo que el dedo toca. */
html.ir-plan-v2 .ir-esp-fila:focus-within { background: rgba(127, 127, 127, .10); }

/* Intercambiar: flotante, centrado sobre el separador entre las dos filas.
   Ahí es donde actúa y ahí es donde se busca. */
html.ir-plan-v2 .ir-esp-swap {
  position: absolute;
  top: var(--ir-plan-fila);
  right: 12px;
  transform: translateY(-50%);
  z-index: 2;
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface, #0A1B33);
  color: var(--text-muted, #9FB0C3);
  cursor: pointer;
  transition: transform .18s cubic-bezier(.4,0,.2,1), color .15s;
}
html.ir-plan-v2 .ir-esp-swap:hover { color: var(--text, #F5F7FA); }
html.ir-plan-v2 .ir-esp-swap:active { transform: translateY(-50%) rotate(180deg); }

/* «Añadir parada» DENTRO de la tarjeta, con la misma sangría.
   Fuera y con borde punteado se leía como una zona de arrastre — que es lo que
   significa un borde punteado en todos los sistemas de diseño que existen. */
html.ir-plan-v2 .ir-esp-add {
  cursor: pointer;
  color: var(--text-muted, #9FB0C3);
  font-size: calc(16px * var(--ir-letra, 1));
  font-weight: 600;
  min-height: 52px;
}
html.ir-plan-v2 .ir-esp-add:hover { background: rgba(127,127,127,.10); color: var(--text, #F5F7FA); }
html.ir-plan-v2 .ir-esp-add .ir-esp-marca { background: transparent; }

html.ir-plan-v2 .ir-esp-paradas:empty { display: none; }
html.ir-plan-v2 .ir-esp-quitar {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--text-muted, #9FB0C3);
  cursor: pointer;
}
html.ir-plan-v2 .ir-esp-quitar:hover { background: rgba(239,68,68,.14); color: var(--error, #EF4444); }
html.ir-plan-v2 .ir-dir-stop-field {
  flex: 1 1 auto; min-width: 0; min-height: var(--ir-plan-fila);
  padding: 0; border: 0; background: transparent; color: inherit;
  font-family: 'DM Sans', system-ui, sans-serif; font-size: calc(18px * var(--ir-letra, 1)); font-weight: 500;
}

/* ══════════════════════════════════════════════════════════════════════════
   8 · RECIENTES — filas con la misma rejilla que la espina

   Mismo ritmo: icono de 24 en una columna de 56, texto en dos líneas, y nada
   de bordes. Si las listas de una pantalla no comparten rejilla, la pantalla
   se lee como tres pantallas pegadas.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-dir-recientes { margin-top: 20px; }
html.ir-plan-v2 .ir-dir-rec-tit {
  margin: 0 0 4px;
  padding-left: 4px;
  font-size: calc(12px * var(--ir-letra, 1)); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted, #9FB0C3);
}
html.ir-plan-v2 .ir-dir-rec-item {
  display: flex; align-items: center; gap: 16px;
  width: 100%; min-height: var(--ir-plan-fila);
  padding: 4px 12px 4px 4px;
  border: 0; border-radius: 12px;
  background: transparent; color: inherit;
  text-align: left; cursor: pointer; font-family: inherit;
}
html.ir-plan-v2 .ir-dir-rec-item:hover { background: rgba(127,127,127,.12); }
html.ir-plan-v2 .ir-dir-rec-ic {
  flex: 0 0 40px; width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: rgba(127,127,127,.14);
  color: var(--text-muted, #9FB0C3);
}
html.ir-plan-v2 .ir-dir-rec-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
html.ir-plan-v2 .ir-dir-rec-n {
  font-size: calc(16px * var(--ir-letra, 1)); font-weight: 600; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html.ir-plan-v2 .ir-dir-rec-d {
  font-size: calc(13px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
html.ir-plan-v2 .ir-dir-rec-vacio {
  padding: 10px 4px; font-size: calc(13px * var(--ir-letra, 1)); line-height: 1.5;
  color: var(--text-muted, #9FB0C3);
}

/* ══════════════════════════════════════════════════════════════════════════
   9 · LOS ICONOS, Y POR QUÉ NO SON EMOJI

   Un emoji lo dibuja el sistema operativo: el mismo 🚛 es un camión rojo en
   iOS, azul en Android y una caja vacía sin la fuente. No hereda el color, no
   tiene control de trazo, y un lector de pantalla lo lee («camión de reparto»
   donde debería decir «tipo de vehículo»).

   Y el problema visual que se veía sin saber nombrarlo: un emoji es un dibujo
   RELLENO, así que a 22 px pesa el doble que una línea de 22 px. Por eso los
   pesos de la pantalla no cuadraban con nada.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-ic,
.ir-camion-hoja .ir-ic { display: block; flex: 0 0 auto; }

html.ir-plan-v2 .ir-dir-close {
  width: 40px; height: 40px; min-width: 40px;
  border-radius: 50%;
  border: 0;
  background: rgba(127,127,127,.14);
  color: var(--text-muted, #9FB0C3);
  display: flex; align-items: center; justify-content: center;
}
html.ir-plan-v2 .ir-dir-close:hover { background: rgba(127,127,127,.24); color: var(--text, #F5F7FA); }

/* ══════════════════════════════════════════════════════════════════════════
   10 · EL AIRE Y EL TÍTULO

   La hoja tenía todo pegado al mismo ritmo, sin respiración, y el título a
   16 px competía con los campos a 20. Un título que pesa menos que su
   contenido no es un título.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-dir-header { padding: 0 16px 14px; align-items: center; }
html.ir-plan-v2 .ir-dir-title {
  font-family: 'Syne', system-ui, sans-serif;
  font-size: calc(22px * var(--ir-letra, 1)); font-weight: 700; letter-spacing: -.01em;
}
html.ir-plan-v2 .ir-dir-handle-bar {
  width: 40px; height: 4px; border-radius: 999px;
  background: var(--line, #1E3A5F);
}
html.ir-plan-v2 .ir-dir-chip { margin-top: 20px; }

/* ══════════════════════════════════════════════════════════════════════════
   11 · CORRECCIONES DE LA PRIMERA PASADA VISUAL

   Cosas que solo se ven mirando la pantalla, no midiéndola. Van aquí con lo
   que se vio, para que la próxima vez se busque antes.
   ══════════════════════════════════════════════════════════════════════════ */

/* «Cambiar ›» partía en dos líneas y el chevron caía solo debajo. Un rótulo de
   acción partido en dos deja de leerse como un botón. */
html.ir-plan-v2 .ir-dir-chip-mas {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
  color: var(--text-muted, #9FB0C3);
}
html.ir-plan-v2 .ir-dir-chip-ic {
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: rgba(127,127,127,.14);
  color: var(--text-muted, #9FB0C3);
  flex: 0 0 40px;
}
html.ir-plan-v2 .ir-dir-chip { gap: 14px; padding: 8px 14px; }
html.ir-plan-v2 .ir-dir-chip-val { font-size: calc(15px * var(--ir-letra, 1)); letter-spacing: -.01em; }
html.ir-plan-v2 .ir-dir-chip-et { font-size: calc(12px * var(--ir-letra, 1)); letter-spacing: .08em; }

/* EL RAÍL. En la primera pasada era un punteado tan tenue que no unía nada —
   y un raíl que no se ve no cumple su único trabajo, que es decir «esto es un
   trayecto» de un vistazo. Ahora: sólido entre origen y destino, y punteado
   solo por debajo, donde el viaje aún no está definido. */
html.ir-plan-v2 .ir-esp::before {
  left: 27px;
  top: 34px;
  bottom: auto;
  height: calc(var(--ir-plan-fila) - 12px);
  width: 2px;
  background: var(--line, #1E3A5F);
  background-size: auto;
  opacity: 1;
}
/* El tramo punteado hacia «Añadir parada»: lo que todavía no es viaje. */
html.ir-plan-v2 .ir-esp::after {
  content: '';
  position: absolute;
  left: 27px;
  top: calc(var(--ir-plan-fila) * 2 - 6px);
  height: 34px;
  width: 2px;
  background-image: linear-gradient(var(--line, #1E3A5F) 50%, transparent 50%);
  background-size: 2px 8px;
  opacity: .8;
  pointer-events: none;
}
html.ir-plan-v2 .ir-esp.ir-esp-sin-add::after { display: none; }

/* El icono de origen y el de destino, más presentes: son las dos anclas de la
   pantalla y estaban al mismo peso que el texto de relleno. */
html.ir-plan-v2 .ir-esp-origen .ir-esp-marca { color: var(--success, #10B981); }
html.ir-plan-v2 .ir-esp-destino .ir-esp-marca { color: var(--accent-dest, #F5F7FA); }

/* Y el botón principal en versalitas: es la única acción de la pantalla y
   tiene que leerse como tal a un metro, que es la distancia a la que se mira
   un teléfono en un soporte de parabrisas. */
html.ir-plan-v2 .ir-dir-btn-calc {
  text-transform: uppercase;
  font-size: calc(18px * var(--ir-letra, 1));
  letter-spacing: .04em;
}

/* ══════════════════════════════════════════════════════════════════════════
   12 · EL RAÍL, CON LAS COORDENADAS MEDIDAS

   La primera versión puso el raíl en `left: 27px` a ojo. Medido después:

       centro de las marcas   x = 45   (no 27)
       origen                 y = 29
       destino                y = 85
       «+» de añadir parada   x = 107  ← centrado, no alineado

   O sea que el raíl caía 18 px a la izquierda de los iconos y el «+» ni
   siquiera estaba en la columna. Se veía como tres cosas sueltas, que es
   exactamente lo que el raíl venía a evitar.

   Ahora va de centro a centro: **el raíl nace en el punto de origen y muere en
   el pin de destino**, y los círculos de las marcas lo tapan por encima, así
   que la línea entra y sale sin cortar el icono.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-esp::before {
  left: 44px;
  top: 29px;
  height: 56px;                    /* centro del origen → centro del destino */
  bottom: auto;
  width: 2px;
  border-radius: 1px;
  background: var(--line-fuerte, #35558A);
  opacity: 1;
}
html.ir-plan-v2 .ir-esp::after {
  left: 44px;
  top: 85px;                       /* del destino hacia «Añadir parada» */
  height: 52px;
  width: 2px;
  background-image: linear-gradient(var(--line, #1E3A5F) 50%, transparent 50%);
  background-size: 2px 8px;
  opacity: .9;
}

/* El «+» estaba centrado en la fila en vez de alineado al raíl: un botón
   dentro de una tarjeta hereda el centrado de su hoja de origen. */
html.ir-plan-v2 .ir-esp-add {
  justify-content: flex-start;
  text-align: left;
}

/* Y las marcas tapan el raíl por encima, para que la línea no cruce el icono. */
html.ir-plan-v2 .ir-esp-marca { box-shadow: 0 0 0 4px var(--surface-2, #10294A); }
html.ir-plan-v2 .ir-esp-add .ir-esp-marca { box-shadow: none; }

/* ══════════════════════════════════════════════════════════════════════════
   13 · UNA REGLA MÍA DE HACE DOS HORAS PELEANDO CON ESTA

   El «+» de añadir parada salía centrado en la fila en vez de alineado al
   raíl: medido, su marca en `x = 107` cuando la columna está en `x = 45`.

   La causa no estaba en este archivo. Estaba en `contraste-y-dedo.css`, en el
   arreglo de objetivos táctiles que escribí antes:

       #rp-share-btn, #voice-nav-mute-btn, #irDirClose, #irDirSwap, #irDirAddStop {
         min-width: 48px; min-height: 48px;
         display: inline-flex; justify-content: center;
       }

   Un selector de ID (1,0,0) gana a `html.ir-plan-v2 .ir-esp-add` (0,2,1). Y
   aquel `justify-content: center` era correcto cuando el botón era un «＋» de
   48×48; deja de serlo cuando el botón pasa a ser una fila con texto.

   Lección, y es la misma que ya aparece tres veces en esta base de código:
   **una regla escrita para una forma no sobrevive al cambio de forma.** Se
   corrige aquí con la misma especificidad, y se dice de dónde venía — porque
   dentro de un mes nadie va a adivinar por qué un ID manda sobre una clase.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 #irDirAddStop {
  display: flex;
  justify-content: flex-start;
  width: 100%;
  min-width: 0;
  min-height: 52px;
}
html.ir-plan-v2 #irDirSwap {
  min-width: 44px;
  min-height: 44px;
}
html.ir-plan-v2 #irDirClose {
  min-width: 40px;
  min-height: 40px;
}

/* ══════════════════════════════════════════════════════════════════════════
   14 · Y ME VOLVIÓ A PASAR: UN COLOR FIJO PARA UN SOLO TEMA

   El pin de destino desapareció en tema claro. La regla decía:

       .ir-esp-destino .ir-esp-marca { color: var(--accent-dest, #F5F7FA); }

   `--accent-dest` **no existe** — me lo inventé al escribirla—, así que
   siempre cae al respaldo `#F5F7FA`, que es casi blanco. Sobre la tarjeta
   oscura se veía perfecto; sobre la tarjeta clara del tema claro es invisible.

   Es exactamente el error que ya está documentado tres veces en
   `contraste-y-dedo.css`: **una tinta fija no sirve para un fondo variable.**
   Lo escribí yo mismo y lo volví a hacer treinta minutos después.

   `var(--text)` sí cambia con el tema —medido: `#F5F7FA` en oscuro, `#16233A`
   en claro— y el pin es legible en los dos.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-esp-destino .ir-esp-marca { color: var(--text, #F5F7FA); }

/* ══════════════════════════════════════════════════════════════════════════
   15 · EL ÁREA QUE TOCA EL DEDO NO ES EL CÍRCULO QUE SE VE

   En la pasada visual bajé `#irDirClose` a 40×40 y `#irDirSwap` a 44×44 porque
   dos círculos de 48 se comían la composición. La prueba lo cazó al instante:
   **por debajo del mínimo táctil de 48 px en teléfono.**

   Y era justo el intercambio que las cinco críticas dijeron que no se hiciera:
   «los objetivos de 48 no se tocan a la baja; los píxeles salen del camión
   duplicado, no de encoger dedos». Lo hice igual, por estética.

   La salida no es elegir entre las dos cosas: es separarlas, que es lo que
   hacen iOS y Material desde hace años. **El elemento mide 48 y es
   transparente; el círculo que se ve lo dibuja un `::before` de 40.** El dedo
   acierta un objetivo de 48 y el ojo ve un botón de 40.

   El `::before` va detrás del icono (`z-index: -1` sobre un contexto propio),
   no encima, o taparía el trazo.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 #irDirClose,
html.ir-plan-v2 #irDirSwap {
  position: relative;
  isolation: isolate;
  width: 48px; height: 48px;
  min-width: 48px; min-height: 48px;
  background: transparent;
  border: 0;
  display: flex; align-items: center; justify-content: center;
}
html.ir-plan-v2 #irDirClose::before,
html.ir-plan-v2 #irDirSwap::before {
  content: '';
  position: absolute;
  inset: 4px;                        /* 48 − 8 = 40 visibles */
  z-index: -1;
  border-radius: 50%;
  background: rgba(127, 127, 127, .14);
  transition: background .15s ease;
}
html.ir-plan-v2 #irDirSwap::before {
  inset: 2px;                        /* 48 − 4 = 44 visibles */
  background: var(--surface, #0A1B33);
  box-shadow: inset 0 0 0 1px var(--line, #1E3A5F);
}
html.ir-plan-v2 #irDirClose:hover::before { background: rgba(127,127,127,.26); }
html.ir-plan-v2 #irDirSwap { top: var(--ir-plan-fila); right: 10px; }

/* ══════════════════════════════════════════════════════════════════════════
   16 · Y LOS RÓTULOS VUELVEN A 13 px

   Puse «CAMIÓN» e «IR OTRA VEZ A» a 12 px porque un rótulo en versalitas con
   tracking se ve mejor pequeño. Es cierto y da igual: el suelo de esta hoja
   son 13 px, y lo escribí yo en la especificación seis horas antes.

   Un suelo que se baja «solo para los rótulos» deja de ser un suelo. Y estos
   rótulos son los que dicen QUÉ es cada bloque — el primero que se lee cuando
   no se conoce la pantalla, y el que peor se lee con el sol de frente.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .ir-dir-chip-et,
html.ir-plan-v2 .ir-dir-rec-tit { font-size: calc(13px * var(--ir-letra, 1)); }

/* ══════════════════════════════════════════════════════════════════════════
   17 · PANTALLAS CORTAS (Android 360×640)

   Medido tras devolver los rótulos a 13 px: el contenido del estado vacío pasó
   a 410 px en una región de 384. Veintiséis de más.

   No se toca ni el suelo tipográfico ni un objetivo táctil — que son las dos
   cosas que llevo todo el día defendiendo y no voy a vender por 26 px. Se
   aprieta el AIRE, que es lo que sobra: los huecos entre bloques, no los
   bloques.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-height: 700px) {
  html.ir-plan-v2 .ir-dir-chip { margin-top: 12px; }
  html.ir-plan-v2 .ir-dir-recientes { margin-top: 12px; }
  html.ir-plan-v2 .ir-esp-add { min-height: 48px; }
  html.ir-plan-v2 .ir-dir-rec-vacio { padding: 6px 4px; }
  html.ir-plan-v2 .ir-dir-header { padding-bottom: 10px; }
  html.ir-plan-v2 .ir-esp::after { height: 48px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   18 · Y EL BOTÓN DE INTERCAMBIAR SE CAYÓ AL FONDO DE LA TARJETA

   La regla del bloque 15 puso `position: relative` a los dos botones para
   poder colgarles el `::before` del área táctil. A `#irDirClose` le da igual;
   a `#irDirSwap` lo sacó de su `position: absolute` y lo devolvió al flujo:
   apareció abajo a la izquierda, debajo de «Añadir parada».

   Un `position` compartido entre dos elementos que se posicionan distinto es
   una regla que no debió agruparlos. `absolute` YA crea contexto para el
   `::before`, así que el `relative` sobraba en su caso.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 #irDirSwap {
  position: absolute;
  top: var(--ir-plan-fila);
  right: 10px;
  transform: translateY(-50%);
  z-index: 3;
}
html.ir-plan-v2 #irDirSwap:active { transform: translateY(-50%) rotate(180deg); }

/* ══════════════════════════════════════════════════════════════════════════
   19 · EL ESTADO CON RUTAS — donde se decide el viaje

   Es la pantalla en la que Google y Uber ponen su mejor trabajo, y aquí era
   un carrusel horizontal de tres tarjetas de 200 px dentro de 390 de ancho.
   Se veían 1,8 tarjetas: la tercera ruta —que en la mitad de los casos gana—
   no coexistía JAMÁS con la primera. Y comparar exige simultaneidad; si hay
   que recordar «$192.60» mientras se desliza para leer «$214.30», eso ya no
   es comparar, es aritmética de memoria con el motor encendido.

   Cuatro críticas independientes (comparación · cabina · densidad · acceso) y
   una síntesis llegaron a la misma composición. El presupuesto, comprobado:

       8 padding + 84 banda + 88 fila + 8 + 88 fila + 8 + 88 fila = 372
       de los 390 px de zona desplazable → ASOMAN 18 px de la franja
       siguiente, y ese corte es lo que dice «hay más abajo» sin gastar una
       flecha ni un gesto.

   DOS CANALES QUE NO SE MEZCLAN:
     · identidad de la ruta = riel de 4 px, color Y PATRÓN (sólido / guiones /
       puntos), el mismo trazo que su polilínea en el mapa;
     · selección = borde + relleno + icono check + palabra ELEGIDA.
   Antes las dos peleaban por `border-color`, y como el color de ruta iba en
   línea ganaba por especificidad: el borde de selección no se pintaba NUNCA.

   Y una tinta fija sobre un papel variable ya rompió esta hoja una vez. Aquí
   cada pareja fondo/texto se declara junta y se mide en los dos temas.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 {
  --ir-fila-bg:      #10294A;
  --ir-fila-borde:   #2E4E7A;
  --ir-fila-bg-sel:  #24354A;   /* 10 % del dorado de marca sobre #10294A */
  --ir-rt-text:      #F5F7FA;   /* 13.61:1 sobre la fila  ✓ AAA */
  --ir-rt-muted:     #9FB0C3;   /*  6.59:1                ✓ AA  */
  --ir-ok:           #10B981;   /*  5.76:1                ✓ AA  */
  --ir-aviso:        #F5A623;   /*  7.20:1                ✓ AAA */
  --ir-ruta-1:       #3B82F6;
  --ir-ruta-2:       #22C55E;
  --ir-ruta-3:       #A855F7;
  --ir-pulsado:      #16345C;
  --ir-esqueleto:    #1E3A5F;
  --ir-acento-estado: var(--ir-acento, #D4A84B);   /* 6.65:1 sobre la hoja ✓ */
  --ir-foco-int:     #030D1C;
  --ir-foco-ext:     #F5F7FA;
}
/* El tema claro NO hereda ni una sola de estas tintas. #9FB0C3 sobre papel
   claro da 2.01:1, y #22C55E da 2.07:1: los dos ilegibles. Se eligen de nuevo,
   emparejados con su papel y medidos. */
html.ir-plan-v2[data-theme="light"] {
  --ir-fila-bg:      #F1F4F8;
  --ir-fila-borde:   #B4C4D6;
  --ir-fila-bg-sel:  #E5E5E1;   /* 10 % del dorado oscurecido sobre #F1F4F8 */
  --ir-rt-text:      #0B1B2E;   /* 15.73:1  ✓ AAA */
  --ir-rt-muted:     #4A5C72;   /*  6.21:1  ✓ AA  */
  --ir-ok:           #065F46;   /*  6.97:1  ✓ AA  */
  --ir-aviso:        #8F4300;   /*  6.41:1  ✓ AA  */
  --ir-ruta-1:       #1D4ED8;
  --ir-ruta-2:       #047857;
  --ir-ruta-3:       #6D28D9;
  /* OJO: `--ir-acento` NO se toca aquí. Es el acento de marca y es el mismo en
     los dos temas — encima va tinta oscura (#0B1F33), así que el botón
     principal se lee igual de bien en papel claro.

     Lo que sí cambia es el acento cuando hace de INDICADOR DE ESTADO: un
     borde, una palabra, un icono. Ahí el color ES el mensaje y necesita
     contraste contra el papel — y el dorado de marca sobre #F1F4F8 da 2,1:1.

     Es la misma lección que el pendiente E: un token con nombre de PIGMENTO
     usado para dos ROLES distintos deja de ser cierto en cuanto hay dos
     papeles. Por eso hay dos tokens — y por eso ninguno se llama ya por el
     pigmento.

     Valía #A8480B, el naranja oscurecido, de cuando esta hoja creía que el
     producto era naranja. Ahora es el mismo dorado oscurecido que ya usa
     `css/tema-claro.css` para el texto de acento, para que un estado sea del
     mismo color en toda la app y no solo en esta pantalla. */
  --ir-acento-estado: #7A5E10;   /* 6.11:1 sobre papel claro ✓ */
  --ir-pulsado:      #E2E8F0;
  --ir-esqueleto:    #DDE4EC;
  --ir-foco-int:     #FFFFFF;
  --ir-foco-ext:     #030D1C;
}

html.ir-plan-v2 .ir-rutas { padding: 8px 0 0; }
/* El contenedor del carrusel sigue existiendo en el marcado; aquí se le quita
   todo lo que lo hacía carrusel. No queda un solo gesto horizontal en la hoja. */
html.ir-plan-v2 .ir-rutas-cuerpo {
  display: block;
  overflow: visible;
  scroll-snap-type: none;
  padding: 0;
  gap: 0;
}
/* SIN `html.ir-plan-v2` DELANTE, Y ESO ES A PROPÓSITO.

   Esta regla nació dentro del planificador y por eso llevaba el prefijo. Al
   usar la misma clase para el anuncio del panel de peajes, el prefijo la
   dejaba fuera: el texto «Peajes: $38.25 en 4 casetas» se habría PINTADO EN
   PANTALLA, debajo del desglose, como si fuera parte del diseño. Una clase
   que se llama «solo lector» y que a veces se ve es peor que no tenerla,
   porque quien la usa da por hecho lo contrario y no lo comprueba.

   Una utilidad no lleva el prefijo del sitio donde se escribió primero. */
.ir-solo-lector {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  /* Nunca `display:none` ni `visibility:hidden`: borrarían el nombre accesible
     de la fila, que es justamente lo que esto existe para dar. */
}

/* ── LA BANDA: la respuesta, antes que la evidencia ──────────────────────── */
html.ir-plan-v2 .ir-rutas-banda {
  position: relative;
  min-height: 84px;
  box-sizing: border-box;
  padding: 12px 12px 12px 16px;
  background: var(--ir-fila-bg);
  border-radius: 12px 12px 0 0;
  /* Pegada SIN HUECO a la fila que recomienda: la adyacencia física resuelve
     gratis el mapeo nombre→fila que antes costaba dos fijaciones oculares y
     un deslizamiento. */
  margin-bottom: 0;
}
html.ir-plan-v2 .ir-rutas-banda::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: -2px; width: 4px;
  border-radius: 2px 0 0 0; background: var(--ir-ruta-1);
}
html.ir-plan-v2 .ir-rutas-banda[data-patron="guiones"]::before { background: var(--ir-ruta-2); }
html.ir-plan-v2 .ir-rutas-banda[data-patron="puntos"]::before  { background: var(--ir-ruta-3); }
html.ir-plan-v2 .ir-ver-l1 {
  margin: 0; font-family: 'DM Sans', sans-serif; font-size: calc(16px * var(--ir-letra, 1)); line-height: 20px;
  font-weight: 500; color: var(--ir-rt-text);
}
html.ir-plan-v2 .ir-ver-l1 b { font-weight: 700; }
html.ir-plan-v2 .ir-ver-l2 {
  margin: 4px 0 0; font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 16px;
  color: var(--ir-rt-muted); display: flex; align-items: center; gap: 4px;
}
html.ir-plan-v2 .ir-ver-l2--aviso { color: var(--ir-aviso); }
html.ir-plan-v2 .ir-ver-l3 {
  margin: 4px 0 0; font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 16px;
  color: var(--ir-rt-muted);
}

/* ── LAS FILAS ───────────────────────────────────────────────────────────── */
html.ir-plan-v2 .ir-rutas-grupo { display: block; }
html.ir-plan-v2 .ir-rt {
  position: relative;
  box-sizing: border-box;
  height: 88px;
  margin-top: 8px;
  background: var(--ir-fila-bg);
  /* El borde es SIEMPRE de 2 px y solo cambia de color. Si engordara al
     seleccionar, la fila crecería, las de abajo saltarían, y el dedo llegaría
     a un objetivo que se movió: con guantes eso es un doble toque garantizado. */
  border: 2px solid var(--ir-fila-borde);
  border-radius: 12px;
  cursor: pointer;
  touch-action: pan-y;          /* el gesto vertical sigue rodando la hoja */
  transition: background-color .14s ease-out, border-color .14s ease-out, transform .08s ease-out;
}
/* La primera fila va pegada a la banda y comparte con ella la esquina. */
html.ir-plan-v2 .ir-rutas-grupo > .ir-rt:first-child {
  margin-top: 0; border-top-left-radius: 0; border-top-right-radius: 0;
}
html.ir-plan-v2 .ir-rt-visual { display: flex; height: 100%; }
html.ir-plan-v2 .ir-rt-riel {
  flex: 0 0 4px; align-self: stretch; border-radius: 2px 0 0 2px;
  background: var(--ir-ruta-1);
}
/* El PATRÓN es el portador NO cromático de la identidad: para uno de cada doce
   hombres el verde y el morado son el mismo gris. Y es el mismo patrón con el
   que se dibuja su polilínea en el mapa. */
html.ir-plan-v2 .ir-rt[data-patron="guiones"] .ir-rt-riel {
  background: repeating-linear-gradient(to bottom, var(--ir-ruta-2) 0 6px, transparent 6px 10px);
}
html.ir-plan-v2 .ir-rt[data-patron="puntos"] .ir-rt-riel {
  background: repeating-linear-gradient(to bottom, var(--ir-ruta-3) 0 2px, transparent 2px 5px);
}
html.ir-plan-v2 .ir-rt-cuerpo {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; justify-content: center;
  padding: 8px 12px;
}
html.ir-plan-v2 .ir-rt-alto { display: flex; align-items: flex-start; gap: 8px; }
html.ir-plan-v2 .ir-rt-izq  { flex: 1 1 0; min-width: 0; }
/* La columna derecha se reserva desde el PRIMER pintado, aunque el peaje
   todavía no haya respondido: si se reservara al llegar la cifra, la fila
   entera se reajustaría bajo el dedo. */
html.ir-plan-v2 .ir-rt-der  { flex: 0 0 auto; min-width: 112px; text-align: right; }

html.ir-plan-v2 .ir-rt-nombre {
  height: 24px; display: flex; align-items: center; gap: 8px;
  font-family: 'DM Sans', sans-serif; font-weight: 700; font-size: calc(16px * var(--ir-letra, 1)); line-height: 24px;
  color: var(--ir-rt-text); white-space: nowrap; overflow: hidden;
}
html.ir-plan-v2 .ir-rt-marca { display: none; align-items: center; gap: 4px; color: var(--ir-acento-estado); }
html.ir-plan-v2 .ir-rt.elegida .ir-rt-marca { display: inline-flex; }
html.ir-plan-v2 .ir-rt-elegida-txt {
  font-size: calc(13px * var(--ir-letra, 1)); font-weight: 700; letter-spacing: .06em;
}
/* Era `height: 20px` con `overflow: hidden`, de cuando la línea llevaba una
   sola cifra. Con el atasco al lado son dos, y en un teléfono de 360 px la
   segunda caía fuera y se cortaba a la mitad: un dato a medias en una ficha
   de decisión es peor que no ponerlo. Ahora la línea envuelve y crece. */
html.ir-plan-v2 .ir-rt-tiempo {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  min-height: 20px; margin-top: 4px;
  font-size: calc(16px * var(--ir-letra, 1)); line-height: 20px; color: var(--ir-rt-text);
}
html.ir-plan-v2 .ir-rt-delta { font-size: calc(13px * var(--ir-letra, 1)); color: var(--ir-rt-muted); }

/* EL ATASCO, EN ÁMBAR.
   No en rojo: el rojo en esta hoja es un error o una restricción que impide
   pasar, y un atasco no impide pasar — cuesta tiempo. Ámbar es exactamente
   «esto te va a costar». 13 px como el delta, que es su hermano: los dos
   dicen «comparado con otra cosa». */
html.ir-plan-v2 .ir-rt-atasco {
  font-size: calc(13px * var(--ir-letra, 1)); font-weight: 700; line-height: 18px;
  color: var(--warning, #F5A623);
  border: 1px solid color-mix(in srgb, var(--warning, #F5A623) 45%, transparent);
  background: color-mix(in srgb, var(--warning, #F5A623) 12%, transparent);
  border-radius: 7px; padding: 0 6px; white-space: nowrap;
}
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  html.ir-plan-v2 .ir-rt-atasco { border-color: rgba(245, 166, 35, .45); background: rgba(245, 166, 35, .12); }
}

/* LA CIFRA REINA. Único elemento de 20 px de toda la hoja.
   Monoespaciada y tabular a propósito: en Syne el «1» mide ≈0.35em y el «0»
   ≈0.62em, así que dos precios en filas contiguas no alinean sus decimales y
   la columna deja de escanearse. Con JetBrains Mono tabular los tres puntos
   decimales caen en la misma abscisa por construcción, y el orden de precios
   se lee con una sacada vertical, sin restar. */
html.ir-plan-v2 .ir-num,
html.ir-plan-v2 .ir-rt-total,
html.ir-plan-v2 .ir-rt-llegada {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
}
html.ir-plan-v2 .ir-rt-total {
  height: 24px; display: flex; align-items: center; justify-content: flex-end; gap: 4px;
  font-weight: 700; font-size: calc(20px * var(--ir-letra, 1)); line-height: 24px; letter-spacing: -.01em;
  color: var(--ir-rt-text); white-space: nowrap;
}
html.ir-plan-v2 .ir-rt-llegada {
  height: 20px; margin-top: 4px;
  font-weight: 500; font-size: calc(13px * var(--ir-letra, 1)); line-height: 20px; color: var(--ir-rt-text);
}
html.ir-plan-v2 .ir-rt-detalle {
  margin-top: 4px; height: 16px;
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 16px;
  color: var(--ir-rt-muted); white-space: nowrap; overflow: hidden;
}
html.ir-plan-v2 .ir-sep { margin: 0 6px; opacity: .7; }
html.ir-plan-v2 .ir-rt-ok     { color: var(--ir-ok); }
html.ir-plan-v2 .ir-rt-alerta { color: var(--ir-aviso); }
html.ir-plan-v2 .ir-rt-desde  { font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); font-weight: 500; color: var(--ir-aviso); }
html.ir-plan-v2 .ir-rt-cifra.atenuada { color: var(--ir-rt-muted); }
html.ir-plan-v2 .ir-rt.sin-peaje-dato .ir-rt-total svg { color: var(--ir-aviso); flex: 0 0 auto; }
html.ir-plan-v2 .ir-esqueleto {
  display: inline-block; border-radius: 4px; color: transparent;
  background: var(--ir-esqueleto);
  animation: ir-esq 1.2s ease-in-out infinite;
}
@keyframes ir-esq { 0%,100% { opacity: .5 } 50% { opacity: 1 } }

/* ── ESTADOS ─────────────────────────────────────────────────────────────── */
html.ir-plan-v2 .ir-rt.elegida {
  background: var(--ir-fila-bg-sel);
  border-color: var(--ir-acento-estado);   /* 6.65:1 oscuro · 5.84:1 claro  ✓ */
}
html.ir-plan-v2 .ir-rt.pulsada { background: var(--ir-pulsado); transform: scale(.985); }
/* Doble aro: funciona sobre cualquier papel, y al no existir ya `overflow-x`
   ningún contenedor lo recorta. */
html.ir-plan-v2 .ir-rt:focus,
html.ir-plan-v2 .ir-rt:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--ir-foco-int), 0 0 0 6px var(--ir-foco-ext);
}

/* ── LO QUE MUERE ────────────────────────────────────────────────────────── */
html.ir-plan-v2 .ir-dir-route-card,
html.ir-plan-v2 .ir-dir-rc-badge,
html.ir-plan-v2 .ir-dir-rc-stats,
html.ir-plan-v2 .ir-dir-veredicto { all: unset; }

/* ── PANTALLA ESTRECHA Y PANTALLA CORTA ──────────────────────────────────── */
/* No encoge nada: ni la letra, ni el objetivo táctil, ni los 88 px de fila.
   Se paga viendo menos, no viendo más pequeño. Bajar tamaños en la pantalla
   pequeña es exactamente el fallo que ya se corrigió con el suelo de 13 px. */
@media (max-width: 379px) {
  html.ir-plan-v2 .ir-opt { display: none; }
}
@media (max-height: 700px) {
  html.ir-plan-v2 .ir-ver-l3 { display: none; }   /* la mediana baja del pliegue */
  html.ir-plan-v2 .ir-rutas-banda { min-height: 64px; }
}
@media (prefers-reduced-motion: reduce) {
  html.ir-plan-v2 .ir-rt,
  html.ir-plan-v2 .ir-esqueleto {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }
  /* El CAMBIO DE ESTADO sobrevive: se desactiva la transición, jamás el
     resultado. Y el acuse al dedo pierde la escala, no el cambio de fondo. */
  html.ir-plan-v2 .ir-rt.pulsada { transform: none; }
}

/* ── EL PIE, con el nombre de lo que se va a navegar ─────────────────────── */
html.ir-plan-v2 .ir-dir-actions .ir-dir-btn-primary.ir-pie-doble {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; min-height: 56px; padding: 8px 12px; line-height: 1;
}
html.ir-plan-v2 .ir-pie-l1 {
  font-family: 'DM Sans', sans-serif; font-weight: 700; font-size: calc(15px * var(--ir-letra, 1)); line-height: 18px;
  letter-spacing: .02em; white-space: nowrap;
}
html.ir-plan-v2 .ir-pie-l2 {
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums;
  font-weight: 500; font-size: calc(13px * var(--ir-letra, 1)); line-height: 16px; white-space: nowrap; opacity: .85;
}
/* El secundario baja a su icono: dos etiquetas largas no caben en 358 px, y la
   que tiene que ganar es la que dice qué se va a navegar. El área táctil sigue
   siendo de 56 px de alto y 56 de ancho — se reduce la tinta, no el objetivo. */
html.ir-plan-v2 .ir-dir-actions .ir-pie-icono {
  flex: 0 0 56px; min-width: 56px; min-height: 56px;
  display: flex; align-items: center; justify-content: center; padding: 0;
}
html.ir-plan-v2 .ir-dir-actions .ir-dir-btn-primary { flex: 1 1 auto; }

/* ── LA ENTRADA PLEGADA ──────────────────────────────────────────────────── */
/* Con rutas calculadas la pantalla dejaba de necesitar el formulario y seguía
   enseñándolo: 256 px de los 588 de zona desplazable para unos campos ya
   contestados, mientras la tercera ruta caía bajo el pliegue. Se pliega a una
   línea con el trayecto escrito, que se toca entera para volver a abrirla. */
html.ir-plan-v2 .ir-esp-resumen {
  display: flex; align-items: center; gap: 10px;
  width: 100%; box-sizing: border-box;
  min-height: 56px; padding: 8px 12px; margin: 0 0 8px;
  background: var(--surface-2, #10294A);
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 12px;
  font-family: 'DM Sans', sans-serif; font-size: calc(16px * var(--ir-letra, 1)); line-height: 20px;
  color: var(--text, #F5F7FA); text-align: left; cursor: pointer;
}
html.ir-plan-v2 .ir-esp-resumen-ic { flex: 0 0 auto; color: var(--ir-acento-estado, #D4A84B); display: flex; }
html.ir-plan-v2 .ir-esp-resumen-tx {
  flex: 1 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
html.ir-plan-v2 .ir-esp-resumen-ed {
  flex: 0 0 auto; font-size: calc(13px * var(--ir-letra, 1)); font-weight: 700; color: var(--ir-acento-estado, #D4A84B);
}
html.ir-plan-v2 .ir-esp-resumen:focus-visible {
  outline: none; box-shadow: 0 0 0 3px var(--ir-foco-int), 0 0 0 6px var(--ir-foco-ext);
}
html.ir-plan-v2 .ir-dir-restrict-ic { display: flex; flex: 0 0 auto; color: var(--ir-rt-muted, #9FB0C3); }
html.ir-plan-v2 .ir-dir-restrict-title,
html.ir-plan-v2 .ir-dir-turns-title { font-size: calc(13px * var(--ir-letra, 1)); letter-spacing: .04em; text-transform: uppercase; color: var(--ir-rt-muted, #9FB0C3); }
html.ir-plan-v2 .ir-dir-nb-tab { display: inline-flex; align-items: center; gap: 6px; min-height: 48px; font-size: calc(13px * var(--ir-letra, 1)); }
html.ir-plan-v2 .ir-dir-nb-icon { display: flex; color: var(--ir-rt-muted, #9FB0C3); }
html.ir-plan-v2 .ir-dir-nb-item.selected .ir-dir-nb-icon,
html.ir-plan-v2 .ir-dir-nb-tab.active .ir-ic { color: var(--ir-acento-estado, #D4A84B); }
html.ir-plan-v2 .ir-dir-nb-name { font-size: calc(13px * var(--ir-letra, 1)); }
html.ir-plan-v2 .ir-dir-nb-addr { font-size: calc(13px * var(--ir-letra, 1)); }
html.ir-plan-v2 .ir-dir-nb-dist { font-size: calc(13px * var(--ir-letra, 1)); }
/* La marca va SOBRE EL MAPA, no sobre la hoja: lleva el acento de relleno y no
   el de estado, y la sombra oscura es la que le da el contraste contra una
   tesela clara. Tenía el naranja escrito a mano, último resto de la paleta
   vieja en esta hoja. */
.ir-nb-marca { display: flex; align-items: center; justify-content: center; color: var(--ir-acento, #D4A84B); filter: drop-shadow(0 1px 2px rgba(0,0,0,.7)); }
html.ir-plan-v2 .ir-dir-turn-icon { display: flex; flex: 0 0 auto; color: var(--ir-acento-estado, #D4A84B); }
html.ir-plan-v2 .ir-dir-turn-instr { font-size: calc(13px * var(--ir-letra, 1)); }
html.ir-plan-v2 .ir-dir-turn-dist  { font-size: calc(13px * var(--ir-letra, 1)); }

/* ══════════════════════════════════════════════════════════════════════════
   20 · EL PANEL DE PEAJES

   Este panel enseñaba tres columnas —Cash, E-ZPass y transpondedor— con tres
   cifras que salían de multiplicar el MISMO número inventado por 1.4, por 1 y
   por 0.85, repartido entre tres casetas con nombre real. Ahora enseña una
   cifra, la del proveedor, o dice que no la tiene.

   Los tres estados necesitan verse distintos de un vistazo, así que cada uno
   tiene su forma, no solo su color.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-plan-v2 .toll-vacio,
.toll-vacio {
  padding: 20px 16px;
  text-align: center;
}
.toll-vacio-t {
  margin: 0 0 6px;
  font-family: 'Syne', sans-serif; font-weight: 700;
  font-size: calc(20px * var(--ir-letra, 1)); line-height: 24px; color: var(--ir-rt-text, #F5F7FA);
}
.toll-vacio-s {
  margin: 0 auto 16px; max-width: 34ch;
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 18px;
  color: var(--ir-rt-muted, #9FB0C3);
}
.toll-btn {
  min-height: 48px; padding: 0 18px;
  border-radius: 12px; border: 1px solid var(--ir-fila-borde, #2E4E7A);
  background: var(--ir-fila-bg, #10294A); color: var(--ir-rt-text, #F5F7FA);
  font-family: 'DM Sans', sans-serif; font-weight: 700; font-size: calc(15px * var(--ir-letra, 1));
  cursor: pointer;
}
.toll-btn-primario {
  background: var(--ir-acento, #D4A84B);
  border-color: var(--ir-acento, #D4A84B);
  color: #0B1F33;                     /* tinta oscura sobre naranja: 7.5:1 */
}
.toll-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--ir-foco-int, #030D1C), 0 0 0 6px var(--ir-foco-ext, #F5F7FA);
}

/* El estado que antes no existía: «se preguntó y no se sabe». */
.toll-aviso {
  display: flex; gap: 10px; align-items: flex-start;
  margin: 12px 16px; padding: 14px;
  border-radius: 12px;
  background: var(--ir-fila-bg, #10294A);
  border: 2px solid var(--ir-aviso, #F5A623);
  color: var(--ir-aviso, #F5A623);
}
.toll-aviso-cuerpo { flex: 1 1 auto; min-width: 0; }
.toll-aviso-t {
  margin: 0 0 4px; font-family: 'DM Sans', sans-serif; font-weight: 700;
  font-size: calc(16px * var(--ir-letra, 1)); line-height: 20px; color: var(--ir-aviso, #F5A623);
}
.toll-aviso-s {
  margin: 0; font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 18px;
  color: var(--ir-rt-muted, #9FB0C3);
}

.toll-tarjeta {
  margin: 12px 16px; padding: 16px;
  border-radius: 12px;
  background: var(--ir-fila-bg, #10294A);
  border: 2px solid var(--ir-fila-borde, #2E4E7A);
}
.toll-tarjeta-cab { margin-bottom: 4px; }
.toll-ruta-nombre {
  font-family: 'DM Sans', sans-serif; font-weight: 700; font-size: calc(16px * var(--ir-letra, 1));
  color: var(--ir-rt-text, #F5F7FA);
}
/* La cifra reina: mismo tamaño y misma familia que la de la comparación de
   rutas, para que las dos pantallas se lean como una sola. */
.toll-cifra {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: calc(32px * var(--ir-letra, 1)); line-height: 38px; letter-spacing: -.01em;
  color: var(--ir-rt-text, #F5F7FA);
}
/* «Sin peajes» es una PALABRA, no una cifra: en monoespaciada tabular se lee
   como un dato numérico mal formateado. La familia sigue al contenido. */
.toll-cifra.toll-cero {
  font-family: 'Syne', sans-serif; font-weight: 800;
  font-size: calc(25px * var(--ir-letra, 1)); line-height: 30px; letter-spacing: 0;
  color: var(--ir-ok, #10B981);
}
.toll-sub, .toll-nota {
  margin-top: 4px;
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 18px;
  color: var(--ir-rt-muted, #9FB0C3);
}
.toll-nota { margin-top: 10px; }
.toll-desglose { margin-top: 14px; border-top: 1px solid var(--ir-fila-borde, #2E4E7A); padding-top: 10px; }
.toll-desglose h4 {
  margin: 0 0 8px; font-family: 'DM Sans', sans-serif; font-weight: 700;
  font-size: calc(13px * var(--ir-letra, 1)); letter-spacing: .04em; text-transform: uppercase;
  color: var(--ir-rt-muted, #9FB0C3);
}
.toll-fila {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; min-height: 32px; padding: 4px 0;
}
.toll-fila-izq { min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
.toll-caseta {
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); color: var(--ir-rt-text, #F5F7FA);
}
.toll-via, .toll-estado {
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); color: var(--ir-rt-muted, #9FB0C3);
}
/* Los precios de las casetas forman su propia columna alineada: si no, no se
   puede ver de un vistazo cuál es la cara. */
.toll-precio {
  flex: 0 0 auto;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: calc(16px * var(--ir-letra, 1)); color: var(--ir-rt-text, #F5F7FA);
  /* SIN ESTO EL RELLENO NO SIRVE DE NADA. El espacio de cifra (U+2007) que
     alinea los decimales va DELANTE de la cifra, y el espacio inicial de una
     caja en línea lo descarta el navegador antes de pintar. Se midió: los
     puntos seguían desalineados con el relleno ya puesto. */
  white-space: pre;
}
.toll-actions { display: flex; gap: 8px; padding: 0 16px 16px; }
.toll-history-title {
  margin: 16px 16px 8px; font-family: 'Syne', sans-serif; font-weight: 700;
  font-size: calc(16px * var(--ir-letra, 1)); color: var(--ir-rt-text, #F5F7FA);
}
.toll-month-card {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin: 0 16px 6px; padding: 10px 12px; min-height: 48px;
  border-radius: 10px; background: var(--ir-fila-bg, #10294A);
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); color: var(--ir-rt-muted, #9FB0C3);
}
.toll-month-card .tmc-total {
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums;
  font-size: calc(16px * var(--ir-letra, 1)); font-weight: 700; color: var(--ir-rt-text, #F5F7FA);
}
#toll-history-chart { display: block; margin: 0 16px 12px; max-width: calc(100% - 32px); }

/* El combustible, con la misma regla: sin precio publicado se dice. */
.fuel-vacio {
  padding: 20px 16px; text-align: center;
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 18px;
  color: var(--ir-rt-muted, #9FB0C3);
}
.fsc-sin-precio {
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1));
  color: var(--ir-rt-muted, #9FB0C3); font-style: italic;
}
.fuel-optimal-base {
  display: block; margin-top: 4px;
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 17px;
  color: var(--ir-rt-muted, #9FB0C3);
}

/* ══════════════════════════════════════════════════════════════════════════
   21 · EL MOVIMIENTO

   La síntesis del 3-ago lo dejó especificado y sin implementar: la hoja
   aparecía en vez de llegar, y las filas solo cambiaban de fondo al pulsarlas.

   DOS REGLAS, y las dos por el mismo motivo — esto se mira con el motor
   encendido, en un teléfono limitado térmicamente:

     · Solo se anima `transform` y `opacity`. Nunca `height`, `top` ni `width`:
       eso obliga al navegador a recalcular la composición, y un reflujo bajo
       el dedo es un objetivo que se mueve mientras lo tocas.
     · Se anima UNA cosa por interacción. Tres cosas moviéndose a la vez no se
       leen como respuesta, se leen como que la pantalla se desarmó.

   Y las filas NO entran escalonadas. El argumento entero de esta pantalla es
   que las tres se ven a la vez para poder compararlas; hacerlas llegar de una
   en una contradice justo eso.
   ══════════════════════════════════════════════════════════════════════════ */
@keyframes ir-hoja-llega {
  from { transform: translateY(24px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}
@keyframes ir-entra {
  from { transform: scale(.8); opacity: 0; }
  to   { transform: scale(1);  opacity: 1; }
}

/* La hoja llega, no aparece. 240 ms con salida rápida: el 80 % del recorrido
   ocurre en el primer tercio del tiempo, así que se percibe inmediata y aun
   así se ve de dónde vino. */
html.ir-plan-v2 .ir-dir-sheet.show {
  animation: ir-hoja-llega .24s cubic-bezier(.2, .8, .2, 1) both;
}

/* El bloque de rutas entero, de una vez. */
html.ir-plan-v2 .ir-rutas-banda,
html.ir-plan-v2 .ir-rutas-grupo {
  animation: ir-hoja-llega .24s cubic-bezier(.2, .8, .2, 1) both;
}

/* El check de «elegida» entra; no parpadea a existir. */
html.ir-plan-v2 .ir-rt.elegida .ir-rt-marca {
  animation: ir-entra .12s ease-out both;
}

/* El riel de la fila elegida crece desde su centro: es la única señal que se
   mueve al cambiar de opción, y sale del sitio exacto donde está el dedo. */
html.ir-plan-v2 .ir-rt-riel {
  transform-origin: center;
  transition: transform .14s ease-out;
}
html.ir-plan-v2 .ir-rt.elegida .ir-rt-riel { transform: scaleY(1); }
html.ir-plan-v2 .ir-rt:not(.elegida) .ir-rt-riel { transform: scaleY(.72); }

@media (prefers-reduced-motion: reduce) {
  /* EL CAMBIO DE ESTADO SOBREVIVE. Se desactiva la transición, jamás el
     resultado: quien pide menos movimiento no está pidiendo que el riel deje
     de marcar cuál es su ruta. */
  html.ir-plan-v2 .ir-dir-sheet.show,
  html.ir-plan-v2 .ir-rutas-banda,
  html.ir-plan-v2 .ir-rutas-grupo,
  html.ir-plan-v2 .ir-rt.elegida .ir-rt-marca {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }
  html.ir-plan-v2 .ir-rt-riel { transition-duration: .01ms !important; }
}

/* La hoja animaba `height` durante 300 ms cada vez que cambia de tamaño
   —y cambia justo cuando llegan las rutas, que es cuando el conductor va a
   poner el dedo—. Animar `height` obliga al navegador a recomponer el árbol
   entero de la hoja en cada fotograma: en un teléfono limitado térmicamente
   eso es un objetivo que se mueve mientras lo tocas.

   El alto pasa de golpe; lo que llega con movimiento es el contenido, y eso ya
   va con `transform` y `opacity`. Nadie percibe el salto del contenedor cuando
   lo que hay dentro entra deslizándose. */
html.ir-plan-v2 .ir-dir-sheet {
  transition: transform .35s cubic-bezier(.4, 0, .2, 1);
}

/* ══════════════════════════════════════════════════════════════════════════
   LAS SECCIONES QUE ENTRARON EN AJUSTES
   `js/ajustes-lo-que-faltaba.js` mete el perfil del camión y las preferencias
   de ruta dentro de la hoja de Ajustes. Vienen de `settings.js`, que tenía su
   propia hoja de estilos pensada para un panel a pantalla completa; aquí se
   les quita el marco para que se lean como una sección más de la hoja y no
   como una ventana dentro de otra.
   ══════════════════════════════════════════════════════════════════════════ */
.ir-ajustes-completos { margin: 0 0 8px; }

.ir-ajustes-completos-tit {
  font-size: calc(13px * var(--ir-letra, 1));
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--gold, #D4A84B);
  padding: 4px 16px 8px;
}

/* El cuerpo de `settings.js` se ancla a su panel; dentro de la hoja no puede
   desplazarse por su cuenta ni ponerse un fondo propio: el scroll vive en la
   hoja, y dos scrolls anidados se pelean bajo el dedo. */
.ir-ajustes-completos .ir-settings-body,
.ir-ajustes-completos .ir-s-section {
  max-height: none;
  overflow: visible;
  background: transparent;
}

/* Ningún blanco por debajo del suelo: estas filas se tocan en marcha igual
   que las del mapa. */
.ir-ajustes-completos select,
.ir-ajustes-completos input,
.ir-ajustes-completos button {
  min-height: 48px;
}
.ir-ajustes-completos input[type="checkbox"] { min-height: 0; }

/* LOS NÚMEROS, ENTEROS.
   Estas cajas venían dimensionadas para el panel ancho de `settings.js`. Dentro
   de la hoja se quedaban cortas y cortaban justo los dígitos que importan:
   medido, «13» de la altura se leía «1:» y «80000» del peso se leía «8000(».
   Un peso a medias en la pantalla que decide si pasas por un puente no es un
   defecto estético. */
.ir-ajustes-completos input[type="number"],
.ir-ajustes-completos input[type="text"] {
  min-width: 96px;
  width: auto;
  box-sizing: border-box;
  padding-left: 10px;
  padding-right: 10px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.ir-ajustes-completos .ir-s-row { gap: 8px; }
.ir-ajustes-completos .ir-s-row-label { min-width: 0; }

/* LOS AVISOS DE LA RUTA, EN LA FICHA.
   Ámbar y pequeños, debajo del dinero: no impiden salir, informan de lo que
   hay. Un túnel no es un error, es un dato — y para un hazmat es el dato.
   Cuando además se pidió evitarlo y el ruteador no sabe, la fila lleva un
   borde: el interruptor está encendido y no se pudo cumplir, y callarse eso
   es lo que tenía al conductor sin ruta y sin explicación. */
html.ir-plan-v2 .ir-rt-avisos {
  display: flex; align-items: center; gap: 5px;
  margin-top: 3px;
  font-size: calc(12px * var(--ir-letra, 1)); line-height: 16px; font-weight: 600;
  color: var(--warning, #F5A623);
}
html.ir-plan-v2 .ir-rt-avisos svg { flex: 0 0 auto; }
html.ir-plan-v2 .ir-rt-avisos--pedido {
  border-left: 3px solid var(--warning, #F5A623);
  padding-left: 6px;
  margin-left: -9px;
}

/* LA PARKWAY PROHIBIDA, EN LA TARJETA DE LA RUTA.
   No comparte estilo con `.ir-rt-avisos` a propósito: esos avisan de lo que
   hay en el camino y se leen de reojo; este dice que por ahí el camión no
   puede ir, con la ley detrás (título del elemento). En rojo y con la letra
   más marcada porque el gesto que queremos es que se pare a leerlo. */
html.ir-plan-v2 .ir-rt-parkway {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-size: calc(12px * var(--ir-letra, 1));
  font-weight: 600;
  color: var(--warning, #F5A623);
}
html.ir-plan-v2 .ir-rt-parkway--prohibida { color: var(--error, #EF4444); }
html.ir-plan-v2 .ir-rt-parkway svg { flex: 0 0 auto; }

/* YA SE RODEÓ. En verde y con el mismo peso que el rojo de la parkway, no en
   letra pequeña: rodear una vía prohibida cambia las millas, el diésel y la
   hora de llegada, y esas tres son las cifras con las que se decide. Un
   cambio de ruta que la app hace sola y no dice se llama de otra manera. */
html.ir-plan-v2 .ir-rt-parkway-rodeada {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-size: calc(12px * var(--ir-letra, 1));
  font-weight: 600;
  color: var(--success, #10B981);
}
html.ir-plan-v2 .ir-rt-parkway-rodeada svg { flex: 0 0 auto; }

/* LA ZONA QUE EL CONDUCTOR MARCÓ PARA EVITAR.
   Un disco rojo con aro blanco: tiene que leerse sobre calle, sobre satélite
   y sobre el trazo de la ruta, que es justo por donde va a estar. 26 px de
   dibujo pero 48 de zona pulsable (el `::after`), porque quitarla es un toque
   con el dedo y el suelo de la casa son 48. */
.ir-zona-evitada { background: none; border: 0; }
.ir-zona-evitada .ir-ze-disco {
  display: block;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--error, #EF4444);
  border: 3px solid #fff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .45);
  position: relative;
}
/* La barra de «prohibido», dibujada y no un carácter: un glifo depende de la
   fuente del teléfono y aquí ya nos pasó con el alfiler. */
.ir-zona-evitada .ir-ze-disco::before {
  content: '';
  position: absolute; left: 4px; right: 4px; top: 50%;
  height: 3px; margin-top: -1.5px;
  background: #fff; border-radius: 2px;
}
.ir-zona-evitada .ir-ze-disco::after {
  content: '';
  position: absolute; left: 50%; top: 50%;
  width: 48px; height: 48px; margin: -24px 0 0 -24px;
}

/* ───── css/ajustes-premium.css ───── */
/*PORQUE Las cuatro secciones de ajustes que aparecieron al poner
       #settings-container y que no tenían ni una regla. PORQUE*/
/* ==========================================================================
   IRON ROUTE — LAS CUATRO SECCIONES DE AJUSTES QUE NUNCA SE PINTABAN

   POR QUÉ ESTE ARCHIVO EXISTE: `hud-mode.js`, `voice-nav.js`,
   `speed-alerts.js` y `offline-maps.js` construyen los cuatro su sección de
   ajustes y los cuatro empiezan igual:

       var c = document.getElementById('settings-container');
       if (!c) return;

   Ese id no estaba en el HTML. Los cuatro salían por ese `return` en cada
   arranque, en silencio. Modo HUD, navegación por voz, avisos de velocidad y
   descarga de mapas sin señal llevaban escritos desde siempre y nunca se
   habían visto — y «mapas sin señal» es justo lo que un camionero necesita en
   la montaña.

   Al poner el ancla aparecieron, y aparecieron SIN UNA SOLA REGLA DE ESTILO:
   los controles segmentados pegados unos a otros («Voz completaSolo
   alertasSilencio»), las filas de datos sin separación («Tiles en caché0
   tiles»), y el último bloque por debajo del pie. Una función visible y rota
   es peor que una escondida, porque ahora se ve rota.

   Esto es esa hoja, con el mismo sistema que el resto del producto: tres
   tamaños (13 · 16 · 20), 48 px de objetivo táctil, tokens por rol, y cada
   pareja de color medida en LOS DOS temas.
   ========================================================================== */
#settings-container {
  display: flex;
  flex-direction: column;
  gap: 24px;
  margin-top: 24px;
  /* Sitio para que el último bloque no quede detrás de la barra de pestañas.
     Se medía: «Limpiar caché» caía debajo del pie. */
  padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
}

#settings-container {
  --aj-texto:  #F5F7FA;
  --aj-suave:  #9FB0C3;   /*  6.59:1 sobre #10294A  ✓ AA  */
  --aj-fondo:  #10294A;
  --aj-linea:  #2E4E7A;
  /* Valía `#F58220`, el naranja de la paleta vieja. La app se unificó en
     dorado —el porqué largo está en `css/styles.css`— y este token se quedó
     atrás porque NO SE LLAMA `--accent`: la prueba que barría el naranja
     preguntaba por ese nombre, así que este pasó por debajo sin enterarse
     nadie. Ahora la prueba pregunta por el VALOR en cualquier token, que es
     la pregunta que había que hacer desde el principio. */
  --aj-sel:    var(--accent-fill, #D4A84B);
  --aj-tinta:  #0B1F33;   /*  7.90:1 sobre el dorado  ✓ AAA */
  --aj-peligro:#FCA5A5;   /*  7.30:1 sobre #10294A  ✓ AAA */
}
/* Ni una tinta se hereda: #9FB0C3 sobre papel claro da 2.01:1 y #FCA5A5 da
   1.9:1. Se eligen de nuevo, emparejadas con su papel y medidas. */
html[data-theme="light"] #settings-container {
  --aj-texto:  #0B1B2E;   /* 15.73:1 ✓ AAA */
  --aj-suave:  #4A5C72;   /*  6.21:1 ✓ AA  */
  --aj-fondo:  #F1F4F8;
  --aj-linea:  #B4C4D6;
  /* El relleno NO cambia entre temas —es la marca— y por eso la tinta que va
     encima tampoco: dorado con tinta oscura da 7,90:1 en los dos. Antes aquí
     había otro color Y otra tinta, o sea que el mismo control se pintaba de
     dos familias distintas según el tema. */
  --aj-sel:    var(--accent-fill, #D4A84B);
  --aj-tinta:  #0B1F33;   /*  7.90:1 sobre el dorado  ✓ AAA */
  --aj-peligro:#9B1C1C;   /*  7.10:1 sobre #F1F4F8  ✓ AAA */
}

/* ── Títulos de sección ─────────────────────────────────────────────────── */
#settings-container .premium-section-title,
#settings-container h4 {
  margin: 0 0 12px;
  font-family: 'Syne', sans-serif; font-weight: 700;
  font-size: calc(16px * var(--ir-letra, 1)); line-height: 20px;
  color: var(--aj-texto);
}

/* ── Modo HUD: una fila que se pulsa entera ─────────────────────────────── */
#settings-container .hud-menu-entry {
  display: flex; align-items: center; gap: 12px;
  min-height: 64px; padding: 10px 14px;
  background: var(--aj-fondo);
  border: 1px solid var(--aj-linea);
  border-radius: 12px;
  cursor: pointer;
}
#settings-container .hud-menu-entry-icon { flex: 0 0 auto; font-size: calc(20px * var(--ir-letra, 1)); line-height: 1; }
#settings-container .hud-menu-entry-text { flex: 1 1 auto; min-width: 0; }
#settings-container .hud-menu-entry-title {
  font-family: 'DM Sans', sans-serif; font-weight: 700;
  font-size: calc(16px * var(--ir-letra, 1)); line-height: 20px; color: var(--aj-texto);
}
#settings-container .hud-menu-entry-sub {
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 17px;
  color: var(--aj-suave);
}
#settings-container .hud-menu-entry-badge {
  flex: 0 0 auto;
  padding: 4px 8px; border-radius: 6px;
  background: var(--aj-sel); color: var(--aj-tinta);
  font-family: 'DM Sans', sans-serif; font-weight: 700; font-size: calc(13px * var(--ir-letra, 1));
}

/* ── Controles segmentados ──────────────────────────────────────────────── */
/* Salían pegados —«Al limite+5 mph+10 mphApagado»— porque no había ni un
   `display` ni un `gap`: tres botones en flujo de texto se leen como una sola
   palabra larga. */
#settings-container .voice-mode-options,
#settings-container .speed-threshold-options {
  display: flex; flex-wrap: wrap; gap: 8px;
}
#settings-container .voice-mode-btn,
#settings-container .speed-threshold-btn {
  flex: 1 1 auto; min-width: 84px;
  /* 48 px de alto de toque, sin excepción: esto se pulsa con guantes. */
  min-height: 48px; padding: 8px 12px;
  background: var(--aj-fondo);
  border: 2px solid var(--aj-linea);
  border-radius: 10px;
  color: var(--aj-texto);
  font-family: 'DM Sans', sans-serif; font-weight: 500; font-size: calc(13px * var(--ir-letra, 1));
  line-height: 17px; text-align: center; cursor: pointer;
}
/* El estado elegido lleva DOS portadores además del color —relleno y peso—
   porque uno de cada doce hombres no separa estos tonos. */
#settings-container .voice-mode-btn.active,
#settings-container .speed-threshold-btn.active {
  background: var(--aj-sel);
  border-color: var(--aj-sel);
  color: var(--aj-tinta);
  font-weight: 700;
}
#settings-container .voice-mode-btn:focus-visible,
#settings-container .speed-threshold-btn:focus-visible,
#settings-container .hud-menu-entry:focus-visible,
#settings-container button:focus-visible,
#settings-container select:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--aj-fondo), 0 0 0 6px var(--aj-texto);
}

/* ── Selector de voz ────────────────────────────────────────────────────── */
#settings-container .voice-selector-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin-top: 12px;
}
#settings-container .voice-selector-label {
  flex: 0 0 auto;
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); color: var(--aj-suave);
}
#settings-container .voice-selector-select {
  flex: 1 1 160px; min-width: 0; min-height: 48px;
  padding: 0 12px;
  background: var(--aj-fondo); border: 1px solid var(--aj-linea);
  border-radius: 10px; color: var(--aj-texto);
  font-family: 'DM Sans', sans-serif; font-size: calc(16px * var(--ir-letra, 1));
}
#settings-container .voice-test-btn {
  flex: 0 0 auto; min-height: 48px; padding: 0 16px;
  background: var(--aj-fondo); border: 1px solid var(--aj-linea);
  border-radius: 10px; color: var(--aj-texto);
  font-family: 'DM Sans', sans-serif; font-weight: 700; font-size: calc(13px * var(--ir-letra, 1));
  cursor: pointer;
}

#settings-container .speed-settings-hint {
  margin-bottom: 10px;
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); line-height: 17px;
  color: var(--aj-suave);
}

/* ── Mapas sin conexión ─────────────────────────────────────────────────── */
/* «Tiles en caché0 tiles» era una etiqueta y su valor sin nada en medio. */
#settings-container .offline-storage-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; min-height: 32px;
  padding: 6px 0;
  border-bottom: 1px solid var(--aj-linea);
}
#settings-container .offline-storage-row:last-child { border-bottom: none; }
#settings-container .offline-storage-label {
  font-family: 'DM Sans', sans-serif; font-size: calc(13px * var(--ir-letra, 1)); color: var(--aj-suave);
}
#settings-container .offline-storage-value {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: calc(16px * var(--ir-letra, 1)); color: var(--aj-texto);
}
#settings-container .offline-actions {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px;
}
#settings-container .offline-btn {
  flex: 1 1 140px; min-height: 48px; padding: 0 14px;
  background: var(--aj-fondo); border: 1px solid var(--aj-linea);
  border-radius: 10px; color: var(--aj-texto);
  font-family: 'DM Sans', sans-serif; font-weight: 700; font-size: calc(13px * var(--ir-letra, 1));
  cursor: pointer;
}
/* «Limpiar caché» borra los mapas descargados: en la montaña sin señal eso es
   quedarse sin mapa. Se marca como destructivo, y con el rojo emparejado a su
   papel en cada tema — no con una tinta fija. */
#settings-container .offline-btn-peligro {
  color: var(--aj-peligro);
  border-color: var(--aj-peligro);
}

/* ───── css/tema-claro.css ───── */
/*PORQUE El modo claro de verdad: tokens.css decia literalmente «no light theme». PORQUE*/
/* ==========================================================================
   IRON ROUTE — EL MODO CLARO, DE VERDAD

   EL PROBLEMA EXACTO QUE ARREGLA:

   `ajustes-vivos.js` pone `data-theme="light"` en el `<html>`… y **ningún sitio
   redefinía los tokens para claro**. `tokens.css` lo dice literalmente: «No
   light theme; keep dark». Resultado: el conmutador de tema movía un atributo
   que casi nada leía, y las pocas reglas sueltas de claro que existen en cinco
   hojas dejaban una pantalla **blanca sobre blanca**: el fondo, la tarjeta, el
   panel y la barra, todos el mismo blanco, sin un borde que los separe. Eso no
   es «claro», es una hoja en blanco con letras encima.

   LA REGLA QUE LO ARREGLA, Y ES UNA SOLA:

   En oscuro, la jerarquía se construye **subiendo** de luz: el fondo es el más
   oscuro y cada capa por encima aclara. En claro **hay que invertirla**: el
   fondo es el más CLARO y cada capa por encima se ensucia un poco. Copiar el
   modo oscuro cambiando `#030D1C` por `#FFFFFF` y ya —que es lo que estaba
   pasando— colapsa las cuatro capas en una.

   Aquí el fondo NO es blanco puro: es un gris azulado muy claro (#EEF1F6). El
   blanco puro queda reservado para las tarjetas, que así **flotan** sobre el
   fondo en vez de fundirse con él. Es exactamente al revés que en oscuro, y es
   lo que hace que se distinga algo.

   CONTRASTE, MEDIDO Y NO A OJO (WCAG AA sobre el fondo de tarjeta #FFFFFF):
     · texto     #16233A sobre #FFFFFF → 14.0:1   (AA y AAA)
     · apagado   #52627A sobre #FFFFFF →  6.5:1   (AA para texto normal)
     · acento    #A15200 sobre #FFFFFF →  5.4:1   (AA)  ← el naranja de marca
       #F58220 da 2.6:1 sobre blanco, o sea que NO SIRVE para texto en claro.
       Se conserva #F58220 para rellenos y barras, donde no se lee texto encima,
       y para texto se usa la versión oscurecida. Un acento bonito e ilegible es
       un acento roto.
     · dorado    #7A5E10 sobre #FFFFFF →  6.6:1   (AA)
       #D4A84B da 2.1:1 sobre blanco: tampoco vale para texto.

   DÓNDE NO SE APLICA: el mapa en navegación. En una cabina de noche una
   pantalla clara ciega, y eso sí es un asunto de seguridad. La navegación se
   queda oscura aunque el resto de la app esté en claro.
   ========================================================================== */

html[data-theme="light"] {
  color-scheme: light;

  /* ── La escala de superficies, de más clara a más sucia ─────────────── */
  --ink:        #EEF1F6;   /* el lienzo. NO es blanco: si lo fuera, la tarjeta
                              blanca no se vería como una tarjeta. */
  --surface:    #FFFFFF;   /* tarjetas y paneles: aquí sí, blanco */
  --surface-2:  #F4F6FA;   /* elementos elevados dentro de una tarjeta */
  --line:       #D3DAE6;   /* bordes: sin esto, todo lo anterior es lo mismo */
  --text:       #16233A;
  --text-muted: #52627A;

  /* Acentos legibles en claro. Los de marca se conservan aparte para
     rellenos, que es donde sí funcionan. */
  --gold:       #7A5E10;
  --gold-light: #9A7818;
  --gold-fill:  #D4A84B;
  /* ── CORREGIDO: el acento era NARANJA aquí y DORADO en el tema oscuro ──
     Medido: `--accent` valía `#A15200` en claro y `#D4A84B` en oscuro. No son
     dos tonos del mismo color, son dos colores. La app cambiaba de acento al
     cambiar de tema. Se unifica en el dorado, que es el que está desplegado
     — el porqué largo está en `css/styles.css`, donde se decidió.

     El dorado de marca sobre blanco da 2,21:1 y no se puede leer, así que
     aquí el papel de TEXTO lo hace un dorado oscurecido (6,11:1) y el de
     RELLENO se queda con el dorado de verdad. Es la misma separación que ya
     existía: lo que estaba mal no era la separación, era la familia. */
  --accent:     #7A5E10;   /* TEXTO en claro: 6,11:1 sobre blanco */
  --accent-fill:#D4A84B;   /* RELLENO: el dorado de marca */
  --accent-ink: #0B1F33;   /* la tinta que va ENCIMA del relleno — 7,55:1 */
  --accent-hover: #6B5210;

  --success:    #0A7D53;
  --warning:    #A15C00;
  --error:      #B42318;

  /* Los mismos nombres que usa tokens.css, para que las pantallas antiguas
     cambien también. Que existan dos juegos de nombres es deuda de antes;
     mientras exista, los dos tienen que decir lo mismo. */
  --color-bg:             #EEF1F6;
  --color-bg-elev:        #F4F6FA;
  --color-bg-card:        #FFFFFF;
  --color-surface:        #FFFFFF;
  --color-text:           #16233A;
  --color-text-muted:     #52627A;
  --color-text-on-gold:   #1B1403;
  --color-border:         #D3DAE6;
  --color-border-strong:  #B7C1D2;
  /* Estos tres decían el naranja oscurecido mientras `--accent`, doce líneas
     más arriba en este mismo archivo, ya decía el dorado oscurecido. El
     comentario de aquí encima lo avisa —«los dos juegos de nombres tienen que
     decir lo mismo»— y no lo decían: cada pantalla salía de un color según de
     qué juego bebiera. Ahora dicen lo mismo. */
  --color-accent:         #7A5E10;   /* 6.11:1 sobre blanco ✓ AA */
  --color-accent-hover:   #6B5210;
  --color-accent-active:  #5C460E;
  --color-navy-900:       #EEF1F6;
  --color-navy-800:       #F4F6FA;
  --color-navy-700:       #E4E9F1;
  --color-navy-600:       #D3DAE6;

  --color-success:        #0A7D53;
  --color-warning:        #A15C00;
  --color-danger:         #B42318;
  --color-info:           #1A6FB5;
}

/* ═══════════════════════════════════════════════════════════════════════════
   EL ALTA EN CLARO — un solo sitio, y son solo tokens

   EL PROBLEMA: `css/onboarding.css` está escrita con tokens que ahora dicen
   PARA QUÉ sirve cada color (`--ob-texto`, `--ob-lienzo`, `--ob-marca`…), no
   de qué color son. Aquí es donde el tema claro los cambia — y aquí es el
   ÚNICO sitio donde se cambian. Antes esto vivía repartido: nueve reglas
   sueltas en `state-cleanup-f4.css` tapaban nueve selectores y dejaban los
   otros ciento y pico pintándose crema sobre blanco.

   Todos los números están medidos sobre la tarjeta blanca `#FFFFFF`, que es
   el fondo real del alta en claro:
     · `--ob-texto`      #16233A → 15,7:1
     · `--ob-texto-2`    #52627A →  6,2:1   (5,7:1 sobre `--ob-campo`)
     · `--ob-marca`      #7A5E10 →  6,1:1   ← el dorado, oscurecido PARA LEER
     · `--ob-exito`      #0A6B47 →  6,6:1
     · `--ob-aviso`      #A15C00 →  5,2:1
     · `--ob-peligro`    #B42318 →  6,6:1
   Y el relleno dorado NO se toca: `--ob-marca-relleno` sigue siendo #D4A84B
   con tinta `--ob-sobre-marca` #030D1C encima, 8,8:1. Un acento bonito e
   ilegible es un acento roto; un acento legible pero cambiado de color es una
   marca rota. Por eso son dos tokens y no uno.

   LA ESCALA DE SUPERFICIES va al revés que en oscuro, igual que en el resto
   de este archivo: el lienzo es lo más CLARO y cada capa por encima se
   ensucia un poco. Si la tarjeta y el lienzo fueran los dos blancos, no se
   vería dónde acaba una y empieza el otro.
   ═══════════════════════════════════════════════════════════════════════════ */
html[data-theme="light"] {
  --ob-lienzo:      #EEF1F6;
  --ob-fondo-1:     #FFFFFF;
  --ob-fondo-2:     #E1E7F0;
  --ob-tarjeta:     #FFFFFF;
  --ob-tarjeta-2:   #F7F9FC;
  --ob-barra:       rgba(255,255,255,.94);
  --ob-campo:       #F4F6FA;
  --ob-campo-2:     #E8EDF5;

  --ob-texto:       #16233A;
  --ob-texto-2:     #52627A;
  --ob-velo:        rgba(22,35,58,.05);
  --ob-linea:       #CBD4E1;

  --ob-marca:           #7A5E10;
  --ob-marca-borde:     rgba(122,94,16,.45);
  --ob-marca-relleno:   #D4A84B;
  --ob-marca-relleno-0: #E2B656;
  --ob-marca-relleno-2: #B8902F;
  --ob-marca-tinte:     rgba(122,94,16,.10);
  --ob-marca-tinte-2:   rgba(122,94,16,.16);
  /* `--ob-sobre-marca` NO se redefine: el relleno dorado es el mismo en los
     dos temas, así que su tinta también. */

  --ob-relleno-apagado-0: #EFD6A2;
  --ob-relleno-apagado:   #E7CF9C;

  /* La píldora seleccionada (idioma activo, pestaña activa) se invierte: en
     oscuro es crema sobre navy, en claro tiene que ser navy sobre blanco. Si
     se dejara crema, la pestaña activa desaparecería dentro de la tarjeta. */
  --ob-realce:       #16233A;
  --ob-sobre-realce: #FFFFFF;
  --ob-perilla:      #FFFFFF;
  /* El carril apagado del interruptor: con `--ob-linea` (#CBD4E1) la bolita
     blanca encima daba 1,2:1 y el interruptor no se veía. #8A97AC da 3,0:1,
     que es el mínimo de WCAG para un componente que no es texto. */
  --ob-carril:       #8A97AC;

  --ob-exito:       #0A6B47;
  --ob-exito-2:     #0A7D53;
  --ob-sobre-exito: #FFFFFF;
  --ob-aviso:       #A15C00;
  --ob-aviso-2:     #8A6D0B;
  --ob-peligro:     #B42318;
}

/* La sombra hace en claro el trabajo que el color de fondo hace en oscuro:
   separar capas. Sin sombra, una tarjeta blanca sobre gris claro se lee como
   un rectángulo pintado, no como algo que está encima. */
html[data-theme="light"] .card,
html[data-theme="light"] .panel,
html[data-theme="light"] .bottom-sheet,
html[data-theme="light"] .ir-dir-sheet,
html[data-theme="light"] .side-menu,
html[data-theme="light"] .leaflet-popup-content-wrapper {
  background: #FFFFFF;
  border: 1px solid #D3DAE6;
  box-shadow: 0 1px 2px rgba(22, 35, 58, 0.06),
              0 8px 24px rgba(22, 35, 58, 0.08);
}

html[data-theme="light"] body {
  background: #EEF1F6;
  color: #16233A;
}

/* ── EL BOTÓN PRIMARIO, MEDIDO ──────────────────────────────────────────
   Esta regla decía `color: #FFFFFF` porque se escribió cuando el relleno era
   el NARANJA de marca. El relleno se unificó en dorado (`--accent-fill:
   #D4A84B`, doce líneas más arriba) y el blanco se quedó: medido en el alta,
   «Comenzar» y «Continuar» daban 2,21:1 sobre el dorado. Blanco sobre un
   amarillo claro no se lee nunca; la tinta que va encima del dorado es la
   oscura, que da 7,5:1. Es la misma regla de siempre: el color fuerte va al
   fondo, nunca a la letra. */
html[data-theme="light"] .ob-btn-primary,
html[data-theme="light"] .btn-primary {
  background: var(--accent-fill, #D4A84B);
  border-color: var(--accent-fill, #D4A84B);
  color: var(--accent-ink, #0B1F33);
}

/* El foco tiene que verse sobre fondo claro: el dorado de marca sobre blanco
   casi desaparece. */
html[data-theme="light"] :focus-visible {
  outline: 2px solid #A15200;
  outline-offset: 2px;
}

/* ── LA EXCEPCIÓN QUE ES DE SEGURIDAD ───────────────────────────────────
   En navegación el mapa se queda oscuro aunque la app esté en claro. Una
   pantalla blanca a las tres de la mañana en una cabina ciega al conductor
   durante los segundos que tarda la pupila en cerrarse, y esos segundos van a
   65 mph. Esto no es una preferencia estética. */
html[data-theme="light"].ir-navegando,
html[data-theme="light"] .ir-nav-activa {
  --ink:        #030D1C;
  --surface:    #0A1B33;
  --surface-2:  #10294A;
  --line:       #1E3A5F;
  --text:       #F5F7FA;
  --text-muted: #9FB0C3;
  /* Y el acento vuelve al dorado del tema oscuro, no al naranja. Esta línea
     ponía #F58220: en cuanto arrancaba la navegación, la app cambiaba de
     color de acento a mitad de viaje. Es el mismo defecto de familia que se
     arregló arriba, escondido en el sitio donde menos se mira — nadie
     inspecciona la paleta con el camión andando. */
  --accent:      #D4A84B;
  --accent-fill: #D4A84B;
  --accent-ink:  #030D1C;
  --gold:        #D4A84B;
}

/* Alto contraste del sistema: si alguien lo pidió, se le da, no se negocia. */
@media (prefers-contrast: more) {
  html[data-theme="light"] {
    --text:       #0B1524;
    --text-muted: #3A4A61;
    --line:       #9FAABE;
    --color-text: #0B1524;
    --color-text-muted: #3A4A61;
    --color-border: #9FAABE;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   LOS CINCO QUE SE PINTABAN CON EL ACENTO DE TEXTO EN VEZ DEL DE RELLENO

   Este mismo archivo, más arriba, separa dos acentos a propósito y lo explica:

       --accent:      #A15200   para TEXTO — el naranja de marca sobre blanco
                                da 2,6:1 y hay que oscurecerlo para leerlo
       --accent-fill: #F58220   para RELLENOS — ahí el naranja sí es el bueno

   La separación estaba bien. Lo que no se hizo fue repasar quién usa cuál.
   Barrido a 1440×900 en tema claro, buscando `background-color` igual a
   `rgb(161, 82, 0)`:

       #fabNewTrip      106×56   «+ Viaje», el botón principal del mapa
       .skip-link       ...×48   saltar al contenido
       #pulse-dot        16×16   el latido de «estás aquí»
       .origin-dot       10×10   el punto de origen del planificador
       .slider           44×26   el interruptor de los ajustes

   Cinco rellenos pintados con el naranja OSCURECIDO PARA LEER TEXTO. El
   resultado es marrón: el botón más visible de la app, en tema claro, no
   lleva el color de la marca. Y no es solo estética — `ecosystem-core` fija
   `#F58220` como el acento de Iron Route, y es lo que distingue este producto
   de los otros cinco del ecosistema.

   Con el relleno bueno hay que cambiar la tinta: blanco sobre `#F58220` da
   2,59:1 y el mínimo es 4,5. Tinta casi negra sobre ese naranja da 7,3:1. Es
   la misma regla que ya se aplicó a «Calcular Ruta» en
   `css/contraste-y-dedo.css`: el color fuerte va al fondo, nunca a la letra.
   ══════════════════════════════════════════════════════════════════════════ */
html[data-theme="light"] #fabNewTrip,
html[data-theme="light"] .fab,
html[data-theme="light"] .skip-link {
  background: var(--accent-fill, #F58220);
  color: #0B1F33;
}
html[data-theme="light"] #fabNewTrip svg,
html[data-theme="light"] .fab svg { stroke: #0B1F33; }

/* Los tres que no llevan texto encima: solo el relleno. */
html[data-theme="light"] #pulse-dot,
html[data-theme="light"] .origin-dot,
html[data-theme="light"] .input-dot.origin-dot,
html[data-theme="light"] input:checked + .slider {
  background: var(--accent-fill, #F58220);
}

/* ───── css/escritorio.css ───── */
/*PORQUE El layout del dispatcher. `ir-desktop` existia desde hacia meses y
       ninguna hoja lo usaba: el escritorio recibia la interfaz del telefono. PORQUE*/
/* ==========================================================================
   IRON ROUTE — EL LAYOUT DE ESCRITORIO (el del dispatcher)

   EL PROBLEMA, DICHO SIN ADORNOS:

   `device-detect.js` llevaba meses poniendo la clase `ir-desktop` en el
   `<html>`… y **ninguna hoja de estilo la usaba**. Cero reglas. O sea que el
   escritorio recibía exactamente la misma interfaz que el teléfono: hojas que
   suben desde abajo, menú hamburguesa que tapa la pantalla, objetivos de 48 px
   y cero hover. En un monitor de 27" eso se ve como una app de móvil estirada,
   y el dispatcher —que es quien la usa ahí, ocho horas, con ratón y teclado—
   pierde el 80 % de su pantalla.

   Y el ancho por sí solo no bastaba: una tablet de 1280 px se usa con el dedo.
   Por eso esto cuelga de `.ir-escritorio`, que `device-detect.js` solo pone
   cuando se cumplen LAS TRES: ancho ≥ 1024, puntero fino y sin táctil.

   QUÉ CAMBIA, Y POR QUÉ CADA COSA:

     1. Los paneles dejan de subir desde abajo y se ACOPLAN a la derecha, altos
        y fijos. Un dispatcher no «abre y cierra» el planificador: lo tiene
        abierto todo el día al lado del mapa.
     2. El menú deja de ser un cajón que tapa y pasa a ser un RAÍL fijo a la
        izquierda. Sin hamburguesa: en escritorio esconder la navegación
        detrás de un botón es esconderla y ya.
     3. El mapa se encoge para dejarles sitio, en vez de quedar tapado.
     4. Aparece el hover, que en táctil no existe y aquí es la mitad de la
        información: qué es pulsable se ve antes de pulsar.
     5. La densidad sube: con ratón, 48 px por fila desperdicia pantalla. Baja
        a 40, que sigue siendo cómodo con puntero fino. Nunca por debajo.
     6. El arrastre de las hojas se apaga: un `handle` para deslizar con el
        pulgar en una pantalla que no se toca es un adorno confuso.
   ========================================================================== */

.ir-escritorio {
  --rail-w:   240px;   /* el raíl de navegación izquierdo */
  --panel-w:  400px;   /* el panel acoplado a la derecha */
}

/* ── 1 · El raíl de navegación, siempre visible ─────────────────────────── */
.ir-escritorio .side-menu {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--rail-w);
  transform: none;
  border-right: 1px solid var(--line, #1E3A5F);
  border-radius: 0;
  box-shadow: none;
  z-index: 900;
  overflow-y: auto;
  visibility: visible;
}
/* `aria-hidden="true"` es lo que el JS del móvil usa para cerrarlo. En
   escritorio el menú NO se cierra, así que también se neutraliza aquí —
   y el JS lo corrige en el atributo para que un lector de pantalla no
   anuncie como oculto algo que está a la vista. */
.ir-escritorio .side-menu[aria-hidden="true"] { visibility: visible; }
.ir-escritorio .menu-overlay { display: none; }
/* EL BOTÓN DEL MENÚ SE LLAMA `#menuBtn`, Y ESTA REGLA NOMBRABA A OTROS TRES.

   Reportado por el dueño el 5-ago: «en modo escritorio no me funciona bien».
   Medido: de los 32 selectores de esta hoja, 17 no encontraban ni un elemento,
   y estos tres eran de los peores — `[data-action="menu"]`, `.menu-toggle` y
   `.hamburger` no existen en este HTML. El botón real es
   `#menuBtn.search-menu-btn`.

   El efecto: en escritorio quedaban a la vez el menú lateral FIJO y el botón
   que sirve para abrir ese mismo menú. Dos formas de abrir lo que ya está
   abierto, una de ellas empujando el buscador. Se ve como «esto no está bien
   hecho» y en realidad era una regla escrita contra un DOM que no existe.

   Se dejan los tres nombres viejos por si algún parcial los usa, y se añaden
   los dos reales. Una regla que no encuentra nada no cuesta nada; la que
   faltaba costaba el diseño entero. */
.ir-escritorio [data-action="menu"],
.ir-escritorio .menu-toggle,
.ir-escritorio .hamburger,
.ir-escritorio #menuBtn,
.ir-escritorio .search-menu-btn { display: none; }

.ir-escritorio .menu-item {
  min-height: 40px;
  font-size: calc(14px * var(--ir-letra, 1));
  border-radius: 8px;
}
.ir-escritorio .menu-item:hover {
  background: var(--surface-2, #10294A);
  color: var(--accent, #F58220);
}

/* ── 2 · El mapa cede el sitio ──────────────────────────────────────────── */
.ir-escritorio .map-wrapper,
.ir-escritorio #main-content {
  margin-left: var(--rail-w);
  transition: margin-right 180ms ease;
}
/* Con un panel abierto, el mapa se estrecha en vez de quedar tapado: un
   dispatcher compara el panel CONTRA el mapa, no uno encima del otro. */
.ir-escritorio.ir-panel-abierto .map-wrapper,
.ir-escritorio.ir-panel-abierto #main-content {
  margin-right: var(--panel-w);
}

/* ── 3 · Los paneles se acoplan a la derecha ────────────────────────────── */
.ir-escritorio .bottom-sheet {
  top: 0;
  bottom: 0;
  left: auto;
  right: 0;
  width: var(--panel-w);
  max-width: none;
  max-height: none;
  border-radius: 0;
  border-left: 1px solid var(--line, #1E3A5F);
  transform: translateX(100%);
  box-shadow: -8px 0 32px rgba(3, 13, 28, 0.28);
}
.ir-escritorio .bottom-sheet.open,
.ir-escritorio .bottom-sheet[aria-hidden="false"] {
  transform: translateX(0);
}
.ir-escritorio .sheet-content {
  overflow-y: auto;
  padding-bottom: 24px;
}
/* El asa de arrastre no pinta nada donde no hay dedo. */
.ir-escritorio .sheet-handle { display: none; }

/* El planificador de rutas usa su propia hoja, con su propio id. */
.ir-escritorio .ir-dir-sheet {
  top: 0;
  bottom: 0;
  left: auto;
  right: 0;
  width: var(--panel-w);
  max-width: none;
  max-height: none;
  height: 100vh;
  height: 100dvh;
  border-radius: 0;
  border-left: 1px solid var(--line, #1E3A5F);
  transform: translateX(100%);
}
.ir-escritorio .ir-dir-sheet.show { transform: translateX(0); }
.ir-escritorio .ir-dir-drag,
.ir-escritorio .ir-dir-handle { display: none; }

/* ── 4 · Densidad y hover ───────────────────────────────────────────────── */
@media (pointer: fine) and (hover: hover) {
  .ir-escritorio .ir-dir-btn,
  .ir-escritorio .rg-usar,
  .ir-escritorio .pc-btn,
  .ir-escritorio .pc-sel {
    min-height: 40px;      /* nunca menos: con ratón se acierta, pero 32 px
                              sigue siendo incómodo de leer */
  }
  .ir-escritorio .rg-item:hover,
  /* `.ir-dir-route-card` es el nombre ANTERIOR al rediseño del 3-ago; desde
     entonces la fila de ruta es `.ir-rt`. La regla se quedó apuntando al nombre
     viejo, así que el escritorio perdió el realce al pasar el ratón por encima
     justo en la pantalla donde se decide el viaje. */
  .ir-escritorio .ir-rt:hover,
  .ir-escritorio .ir-dir-route-card:hover {
    border-color: var(--accent, #F58220);
    background: var(--surface-2, #10294A);
  }
  .ir-escritorio .rg-usar:hover { background: rgba(245, 130, 32, 0.08); }
  /* El cursor también informa: si cambia, es pulsable. */
  .ir-escritorio button,
  .ir-escritorio [role="button"] { cursor: pointer; }
}

/* ── 5 · Teclado: en escritorio es la mitad de la interfaz ──────────────── */
.ir-escritorio :focus-visible {
  outline: 2px solid var(--accent, #F58220);
  outline-offset: 2px;
}
/* El enlace de saltar al contenido solo tiene sentido con teclado, y en
   escritorio es donde hay teclado. */
.ir-escritorio .skip-link:focus {
  position: fixed;
  top: 8px;
  left: calc(var(--rail-w) + 8px);
  z-index: 2000;
}

/* ── 6 · Pantallas muy anchas ───────────────────────────────────────────── */
@media (min-width: 1600px) {
  .ir-escritorio { --panel-w: 460px; --rail-w: 260px; }
}
@media (min-width: 2000px) {
  /* En un monitor 4K, estirar el panel a 800 px no lo hace más útil: hace las
     líneas de texto ilegiblemente largas. Se topa y punto. */
  .ir-escritorio { --panel-w: 520px; }
}

/* ── 7 · Empotrado en el TMS ────────────────────────────────────────────── */
/* El dispatcher ya tiene la cabecera del TMS encima: la nuestra sería una
   segunda barra que roba alto y confunde sobre dónde está. */
.ir-empotrada .app-header,
.ir-empotrada .side-menu,
.ir-empotrada .menu-overlay { display: none; }
.ir-empotrada .map-wrapper,
.ir-empotrada #main-content { margin-left: 0; }

/* ── 8 · Táctil: lo contrario, y explícito ─────────────────────────────── */
/* Se escribe aunque sea el comportamiento por defecto, porque el defecto
   cambia en cuanto alguien toca una hoja de arriba. En un dedo, 48 px. */
.ir-tactil .menu-item,
.ir-tactil .ir-dir-btn,
.ir-tactil .rg-usar,
.ir-tactil .pc-btn { min-height: var(--touch, 48px); }
/* El hover en táctil se queda «pegado» tras un toque y deja filas iluminadas
   que parecen seleccionadas. Se apaga. */
.ir-tactil .rg-item:hover,
.ir-tactil .menu-item:hover,
.ir-tactil .ir-rt:hover,
.ir-tactil .ir-dir-route-card:hover { background: inherit; }

/* ==========================================================================
   EL CONTENEDOR QUE SE COMÍA LOS CLICS

   Medido con Playwright a 1440×900: `.map-controls` es una caja de
   **1200×230 px con `z-index: 900`** y `pointer-events: auto`. No pinta nada
   —solo agrupa dos o tres botones en una esquina— pero su rectángulo cubre
   toda la franja inferior del mapa y está POR ENCIMA de `.quick-tiles`
   (z-index 800).

   Resultado: los diez accesos rápidos —planificador, truck stops, parking,
   básculas, gas, áreas de descanso, taller, lavado, médico, DOT— se veían
   perfectamente y **ningún clic les llegaba**. El clic moría en un contenedor
   transparente. Es el bug que hace que un usuario diga «los botones no
   funcionan», porque desde fuera es exactamente eso.

   LA REGLA: un contenedor que solo agrupa no debe interceptar el puntero.
   `pointer-events: none` en la caja y `auto` en lo que de verdad se pulsa.
   Va sin `!important` y sin subir el z-index de nadie: subir un z-index para
   ganarle a otro empieza una carrera que acaba en 9999.
   ========================================================================== */
.map-controls,
.quick-tiles {
  pointer-events: none;
}
.map-controls > *,
.quick-tiles .tiles-scroll,
.quick-tiles .tile,
.quick-tiles button,
.quick-tiles a,
.map-controls button,
.map-controls a {
  pointer-events: auto;
}

/* ── La barra de accesos rápidos, a lo ancho del mapa ────────────────────
   Estaba topada a `max-width: 480px` y centrada — la anchura de un teléfono,
   heredada de la regla de ≥768px. En un monitor de 1440 eso deja los tiles
   dentro de una franja de 480 px en el centro, y **los que se salen quedan
   fuera de su contenedor**: se ven, porque el scroll horizontal los pinta,
   pero el mapa está encima y se queda con el clic.

   Medido: de diez accesos, solo cuatro eran alcanzables. Los seis de la
   derecha —gas, áreas de descanso, taller, lavado, médico, DOT— caían fuera.

   En escritorio la barra ocupa el ancho del mapa (descontando el raíl) y ya
   no hace falta desplazarla: caben los diez. */
.ir-escritorio .quick-tiles {
  max-width: none;
  left: var(--rail-w);
  right: 0;
  transform: none;
  padding: 0 16px;
}
.ir-escritorio.ir-panel-abierto .quick-tiles { right: var(--panel-w); }
.ir-escritorio .quick-tiles .tiles-scroll {
  justify-content: center;
  flex-wrap: wrap;
  overflow-x: visible;
}

/* ───── css/paneles-nuevos.css ───── */
/*PORQUE Las seis pantallas que no existian: combustible, HOS, IFTA, peajes,
       rastreo y ayuda. 3.649 lineas de logica cargadas y muertas por falta
       de seis <aside>. Va la ultima para ganar por orden, no con !important. PORQUE*/
/* ==========================================================================
   IRON ROUTE — LAS SEIS PANTALLAS QUE NO EXISTÍAN

   CONTEXTO, PORQUE EXPLICA POR QUÉ ESTA HOJA ES NUEVA Y NO UN PARCHE:

   `fuel.js` (912 líneas), `hos.js` (636), `ifta.js` (848), `tolls.js` (548) y
   `tracking.js` (705) llevaban meses cargados en la página, escritos, con sus
   pruebas, y **muertos**: cada uno terminaba llamando a `Sheets.open('sheet-…')`
   sobre un `<aside>` que no estaba en el HTML. 3.649 líneas de lógica de
   negocio que para quien usaba la app sencillamente no existían.

   Ya están los cinco paneles, más «Ayuda», que abría un aviso de
   «próximamente» desde el primer día. Esta hoja les da forma.

   LA REGLA QUE SIGUE TODO LO DE AQUÍ:
   Un panel vacío tiene que decir qué hacer para llenarlo. Un hueco mudo se
   lee como una función rota — y quien lo ve decide que la app está a medias,
   que es exactamente lo que era.
   ========================================================================== */

/* ── El cuerpo de cualquiera de los paneles nuevos ──────────────────────── */
.panel-body {
  padding: 4px 0 8px;
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
}

/* ── El estado vacío: nunca mudo ────────────────────────────────────────── */
.empty-hint {
  margin: 6px 0 0;
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.5;
  color: var(--text-muted, #9FB0C3);
  /* 13px es el mínimo que se permite aquí — el suelo del sistema son 12px y
     este texto se lee parado, no conduciendo. */
}

/* ══════════════════════════════════════════════════════════════════════════
   HORAS DE SERVICIO — EL NÚMERO GRANDE

   De esta pantalla solo se mira una cosa con el camión andando: cuánto queda.
   Va arriba, en mono, y del tamaño que se lee de un vistazo desde el asiento.
   Todo lo demás de la pantalla es secundario y se ve como secundario.
   ══════════════════════════════════════════════════════════════════════════ */
.hos-remain {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(44px * var(--ir-letra, 1));
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text, #F5F7FA);
  padding: 6px 0 12px;
  border-bottom: 1px solid var(--line, #1E3A5F);
  margin-bottom: 12px;
  font-variant-numeric: tabular-nums;
  /* Tabular: sin esto, el reloj «baila» un pixel a cada segundo porque el 1
     es más estrecho que el 8. En un número que cambia solo, eso se nota. */
}
/* El color cambia ANTES de que sea tarde, no cuando ya lo es. Un aviso que
   llega al minuto cero no es un aviso, es un parte.

   Los nombres `hos-warn` y `hos-critical` NO son inventados aquí: son los que
   `hos.js` ya escribía —«hosEl.className = 'hos-remain' + …»— sobre un
   elemento que no existía y contra unas clases que nadie había estilado.
   Se respetan sus nombres en vez de imponer otros: dos vocabularios para el
   mismo estado es la forma de que mañana uno de los dos deje de pintarse. */
.hos-remain.hos-warn,
.hos-remain.hos-poco     { color: var(--warning, #F5A623); }   /* < 2 h */
.hos-remain.hos-critical,
.hos-remain.hos-fuera    { color: var(--error, #EF4444); }     /* < 1 h */

/* ── Las gráficas: alto propio, nunca colapsadas ────────────────────────── */
#fuel-mpg-chart,
#fuel-gauge-canvas,
#ifta-map-canvas,
#toll-history-chart {
  display: block;
  width: 100%;
  max-width: 100%;
  margin-top: 14px;
  border-radius: 10px;
  background: var(--surface-2, #10294A);
}
/* Un `<canvas>` sin datos pintados es un rectángulo de color, que es peor que
   nada: parece un fallo de carga. Se esconde hasta que el módulo dibuje algo.
   `paneles-nuevos.js` pone `data-pintado` cuando comprueba que hay píxeles.

   La regla va por ID, uno a uno, y NO como `canvas:not([data-pintado])`:
   Leaflet dibuja el mapa en un `<canvas>` cuando usa su renderizador de
   lienzo, y una regla global lo habría hecho desaparecer. Un selector
   demasiado ancho es la forma más silenciosa de romper otra cosa. */
#fuel-mpg-chart:not([data-pintado]),
#fuel-gauge-canvas:not([data-pintado]),
#ifta-map-canvas:not([data-pintado]),
#toll-history-chart:not([data-pintado]) {
  display: none;
}

/* ══════════════════════════════════════════════════════════════════════════
   AYUDA

   No es un manual. Es lo mínimo que hace falta en la carretera y el aviso
   legal que este producto está obligado a dar. En letra pequeña, pero
   completo y legible — nunca por debajo de 12px, que es el suelo del sistema.
   ══════════════════════════════════════════════════════════════════════════ */
.ayuda-h3 {
  font-family: 'Syne', system-ui, sans-serif;
  font-size: calc(15px * var(--ir-letra, 1));
  font-weight: 700;
  color: var(--accent, #F58220);
  margin: 20px 0 8px;
  letter-spacing: 0.01em;
}
.ayuda-h3:first-child { margin-top: 4px; }

.ayuda-p {
  margin: 0 0 10px;
  font-size: calc(14px * var(--ir-letra, 1));
  line-height: 1.55;
  color: var(--text, #F5F7FA);
}
.ayuda-p strong { color: var(--gold, #D4A84B); font-weight: 600; }

.ayuda-dl { margin: 0 0 4px; }
.ayuda-dl dt {
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 700;
  color: var(--text, #F5F7FA);
  margin-top: 12px;
}
.ayuda-dl dd {
  margin: 3px 0 0;
  padding: 0;
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.5;
  color: var(--text-muted, #9FB0C3);
}
.ayuda-dl dd strong { color: var(--warning, #F5A623); font-weight: 600; }

.ayuda-acciones { margin: 22px 0 18px; }
.ayuda-btn {
  width: 100%;
  min-height: var(--touch, 48px);
  padding: 0 16px;
  border-radius: 10px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(15px * var(--ir-letra, 1));
  font-weight: 600;
  cursor: pointer;
}
.ayuda-btn:focus-visible { outline: 2px solid var(--accent, #F58220); outline-offset: 2px; }
/* SE RETIRA. Decía «con ratón el objetivo puede bajar a 40; con el dedo,
   nunca» — y `ir-escritorio` no significa «sin dedo». `device-detect.js` la
   pone en el portátil del dueño, que tiene ratón Y pantalla táctil (es el
   caso que prueba `tests/escritorio.mjs` con nombre propio). Así que esta
   regla le daba 40 px justo a la máquina desde la que se usa esto.

   Y `ecosystem-core` fija `--touch: 48px` como suelo del ecosistema sin
   excepción por dispositivo. Cuando la hoja de un producto contradice la
   fundación, manda la fundación. Ver la nota larga en
   `css/contraste-y-dedo.css`, donde se retiró la misma excepción para otros
   nueve controles. */

/* ── El aviso legal ─────────────────────────────────────────────────────────
   Va en letra pequeña porque no es lo que la gente viene a leer, y va
   COMPLETO porque es la diferencia entre una ayuda a la navegación y una
   responsabilidad. Lo que no puede es ir en gris sobre gris: un aviso que no
   se lee es un aviso que no se dio. */
.ayuda-legal {
  margin: 18px 0 10px;
  padding: 12px 14px;
  border-left: 3px solid var(--warning, #F5A623);
  border-radius: 0 8px 8px 0;
  background: rgba(245, 166, 35, 0.08);
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.6;
  color: var(--text, #F5F7FA);
}
.ayuda-legal strong { color: var(--warning, #F5A623); font-weight: 700; }

.ayuda-pie {
  margin: 0 0 4px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.6;
  color: var(--text-muted, #9FB0C3);
  word-break: break-word;
}

/* ══════════════════════════════════════════════════════════════════════════
   EL MICRÓFONO CUANDO ESTÁ ESCUCHANDO

   Un micrófono que escucha sin decirlo es una app que parece estar grabando
   a escondidas. El estado se ve en el propio botón, en el color de aviso y
   con un latido — no con un texto, que ahí no cabe.
   ══════════════════════════════════════════════════════════════════════════ */
.search-voice-btn.escuchando {
  color: var(--error, #EF4444);
  animation: ir-voz-latido 1.1s ease-in-out infinite;
}
@keyframes ir-voz-latido {
  0%, 100% { opacity: 1;    transform: scale(1);    }
  50%      { opacity: 0.55; transform: scale(0.92); }
}
@media (prefers-reduced-motion: reduce) {
  .search-voice-btn.escuchando { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   TEMA CLARO

   El dueño lo dijo del TMS y vale igual aquí: «se ve demasiado blanca, como
   que no se diferencia». Sobre blanco, `--accent` (#F58220) da 2,6:1 y el
   dorado 2,1:1 — medidos. Ninguno de los dos es legible como texto (hace
   falta 4,5:1). Se usan sus versiones oscuras, que son el mismo color con
   el contraste que la ley pide.
   ══════════════════════════════════════════════════════════════════════════ */
html[data-theme="light"] .panel-body,
html[data-theme="light"] .ayuda-p,
html[data-theme="light"] .ayuda-legal,
html[data-theme="light"] .hos-remain,
html[data-theme="light"] .ayuda-dl dt { color: #16233A; }

html[data-theme="light"] .empty-hint,
html[data-theme="light"] .ayuda-dl dd,
html[data-theme="light"] .ayuda-pie { color: #52627A; }

html[data-theme="light"] .ayuda-h3        { color: #A15200; }   /* 4,6:1 sobre blanco */
html[data-theme="light"] .ayuda-p strong  { color: #7A5E10; }   /* 5,1:1 sobre blanco */
html[data-theme="light"] .ayuda-legal     { background: #FDF6E7; border-left-color: #B77400; }
html[data-theme="light"] .ayuda-legal strong,
html[data-theme="light"] .ayuda-dl dd strong { color: #8A5300; }

html[data-theme="light"] .hos-remain      { border-bottom-color: #C4CDDA; }
html[data-theme="light"] .ayuda-btn       { background: #F4F6FA; border-color: #C4CDDA; color: #16233A; }
html[data-theme="light"] #fuel-mpg-chart,
html[data-theme="light"] #fuel-gauge-canvas,
html[data-theme="light"] #ifta-map-canvas,
html[data-theme="light"] #toll-history-chart { background: #EEF1F6; }

/* ── La X de cerrar (la pone `paneles-nuevos.js`) ────────────────────────
   Va SIEMPRE, no solo en escritorio: deslizar para cerrar una hoja no lo
   descubre todo el mundo, y quedarse atrapado en un panel es de las cosas
   que hacen que alguien cierre la app y no vuelva. */
.pn-cerrar {
  position: absolute;
  top: 8px;
  right: 10px;
  z-index: 5;
  width: var(--touch, 48px);
  height: var(--touch, 48px);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--text-muted, #9FB0C3);
  font-size: calc(18px * var(--ir-letra, 1));
  line-height: 1;
  cursor: pointer;
}
.pn-cerrar:hover { color: var(--text, #F5F7FA); background: var(--surface-2, #10294A); }
.pn-cerrar:focus-visible { outline: 2px solid var(--accent, #F58220); outline-offset: 2px; }
.ir-escritorio .pn-cerrar { width: 40px; height: 40px; }

/* ══════════════════════════════════════════════════════════════════════════
   LA TERCERA LISTA ESCRITA A MANO DEL MISMO ARREGLO

   Aquí había otros seis identificadores —los mismos seis de siempre— para
   darle a la ✕ un bloque de referencia. Cuando el botón de cerrar pasó a
   ponerse en TODOS los paneles de diálogo (porque el planificador no tenía
   con qué cerrarse en escritorio, y el dueño lo reportó), esta lista se
   quedó atrás.

   Medido a 1440×900 con el planificador abierto: la ✕ existía y estaba en
   **x = 1782**, o sea 342 px fuera de una ventana de 1440. Sin ancestro
   posicionado, `right: 10px` se cuenta desde otro sitio. El botón estaba, el
   `querySelector` lo encontraba, y no se veía en la pantalla.

   Es el mismo error que me costó `#fabNewTrip` hace dos días, al revés: allí
   puse `position: relative` donde no tocaba y saqué el botón de la pantalla;
   aquí faltaba y pasó igual.

   Ahora la regla no nombra paneles: **el contenedor que TIENE una ✕ dentro es
   el bloque de referencia de esa ✕**. Lo dice `:has()`, y no puede quedarse
   corta porque no enumera nada.
   ══════════════════════════════════════════════════════════════════════════ */
.sheet-content:has(> .pn-cerrar),
.ir-pf5-body:has(> .pn-cerrar) { position: relative; }

/* Y el título deja sitio, o queda debajo del botón. */
.sheet-content:has(> .pn-cerrar) > .sheet-title,
.sheet-content:has(> .pn-cerrar) > h2,
.sheet-content:has(> .pn-cerrar) > h3 { padding-right: 56px; }

html[data-theme="light"] .pn-cerrar { color: #52627A; }
html[data-theme="light"] .pn-cerrar:hover { color: #16233A; background: #EEF1F6; }

/* ══════════════════════════════════════════════════════════════════════════
   LA ALTURA DE LAS HOJAS NUEVAS EN EL TELÉFONO

   MEDIDO a 375x812: «Combustible» abría a 626 px de alto y «Peajes» a 187.
   Las dos son hojas del mismo menú, abiertas del mismo modo, y una ocupaba
   tres veces más que la otra — porque `.bottom-sheet` no tiene alto propio y
   se ajusta a su contenido. Como los paneles recién estrenados están vacíos
   (todavía no hay peajes que enseñar), quedaban en una tira de 187 px pegada
   al borde inferior.

   Eso no se lee como «no hay datos». Se lee como «esto se abrió a medias».
   Un estado vacío tiene que ocupar el sitio que ocupará cuando tenga datos:
   así la primera vez y la centésima se parecen, y la diferencia entre las dos
   es la información, no el tamaño de la ventana.

   45vh es el suelo, 85vh sigue siendo el techo que ya estaba puesto.
   ══════════════════════════════════════════════════════════════════════════ */
#sheet-fuel,
#sheet-hos,
#sheet-ifta,
#sheet-tolls,
#sheet-tracking,
#sheet-help {
  min-height: 45vh;
  min-height: 45dvh;
}

/* En escritorio no aplica: ahí `escritorio.css` las acopla a la derecha con
   `top: 0; bottom: 0`, o sea alto completo, y un `min-height` en vh sobraría. */
.ir-escritorio #sheet-fuel,
.ir-escritorio #sheet-hos,
.ir-escritorio #sheet-ifta,
.ir-escritorio #sheet-tolls,
.ir-escritorio #sheet-tracking,
.ir-escritorio #sheet-help { min-height: 0; }

/* El estado vacío se centra en ese hueco en vez de quedar arrimado arriba,
   que es lo que hace que un panel vacío parezca cortado. */
#sheet-fuel .empty-state,
#sheet-hos .empty-state,
#sheet-ifta .empty-state,
#sheet-tolls .empty-state,
#sheet-tracking .empty-state {
  padding: 24px 4px 8px;
}

/* ══════════════════════════════════════════════════════════════════════════
   LOS ACCESOS RÁPIDOS DEL MAPA (`amenidades.js`)

   Nueve de los diez botones de la barra del mapa abrían una promesa
   («próximamente», «full integration en F6»). Ahora buscan de verdad. Esta es
   su hoja: una lista donde lo primero que se lee es el NOMBRE, lo segundo la
   DISTANCIA, y el botón de ir cae donde cae el pulgar.
   ══════════════════════════════════════════════════════════════════════════ */
#sheet-amenidades { min-height: 45vh; min-height: 45dvh; }
.ir-escritorio #sheet-amenidades { min-height: 0; }
#sheet-amenidades .sheet-content { position: relative; }
#sheet-amenidades .sheet-title { padding-right: 56px; margin-bottom: 2px; }

/* La advertencia de la categoría. No es letra pequeña decorativa: dice que
   «básculas» puede traer un control fronterizo, o que en Diésel no hay
   precios. Prometer precisión que no se tiene es como se pierde la confianza
   en TODO el mapa, no solo en ese botón. */
.am-sub {
  margin: 0 0 12px;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.5;
  color: var(--warning, #F5A623);
  border-left: 2px solid var(--warning, #F5A623);
  padding-left: 9px;
}

.am-lista { list-style: none; margin: 0; padding: 0; }

.am-item {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "izq km" "ir ir";
  gap: 4px 10px;
  align-items: start;
  padding: 12px 0;
  border-bottom: 1px solid var(--line, #1E3A5F);
}
@media (min-width: 560px) {
  .am-item {
    grid-template-columns: 1fr auto auto;
    grid-template-areas: "izq km ir";
    align-items: center;
  }
}
.am-item:last-child { border-bottom: 0; }

.am-izq { grid-area: izq; min-width: 0; }
.am-n {
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(15px * var(--ir-letra, 1));
  font-weight: 600;
  color: var(--text, #F5F7FA);
  line-height: 1.3;
}
.am-dir {
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.4;
  color: var(--text-muted, #9FB0C3);
  margin-top: 2px;
}

/* La distancia en mono y tabular: en una lista ordenada por cercanía, si las
   cifras no se alinean hay que leerlas una a una en vez de barrerlas. */
.am-km {
  grid-area: km;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 700;
  color: var(--gold, #D4A84B);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.am-ir {
  grid-area: ir;
  min-height: var(--touch, 48px);
  padding: 0 16px;
  border-radius: 10px;
  border: 1px solid var(--accent, #F58220);
  background: rgba(245, 130, 32, 0.12);
  color: var(--accent, #F58220);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.am-ir:hover { background: rgba(245, 130, 32, 0.22); }
.am-ir:focus-visible { outline: 2px solid var(--accent, #F58220); outline-offset: 2px; }
.ir-escritorio .am-ir { min-height: 40px; }

/* De dónde salió la búsqueda. Sin esto, una lista calculada desde el centro
   del mapa cuando el mapa está en otro estado es correcta e inútil, y no hay
   manera de notarlo. */
.am-pie {
  margin: 14px 0 4px;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.5;
  color: var(--text-muted, #9FB0C3);
}

html[data-theme="light"] .am-n      { color: #16233A; }
html[data-theme="light"] .am-dir,
html[data-theme="light"] .am-pie    { color: #52627A; }
html[data-theme="light"] .am-km     { color: #7A5E10; }   /* 5,1:1 sobre blanco */
html[data-theme="light"] .am-sub    { color: #8A5300; border-left-color: #B77400; }
html[data-theme="light"] .am-ir     { color: #A15200; border-color: #A15200; background: #FDF3E8; }
html[data-theme="light"] .am-item   { border-bottom-color: #C4CDDA; }

/* La flota cerrada: no es un estado vacío, es un estado con llave. */
.trk-fleet-cerrada .trk-cerrada-p {
  margin: 6px 0 12px;
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.4;
  color: inherit;
  opacity: .78;
}

/* ───── css/carril-derecho.css ───── */
/*PORQUE Un solo carril derecho para los controles del mapa. Va la ultima para
       ganar por orden de cascada, no con !important. PORQUE*/
/* ==========================================================================
   IRON ROUTE — UN SOLO CARRIL DERECHO PARA LOS CONTROLES DEL MAPA

   EL DEFECTO, MEDIDO A 1440×900 ANTES DE ESTO:

     .irc-capas     x 1084..1428   y  84..139     (344×62)   ← capas base
     .map-controls  x  928.. 980   y 494..720     ( 52×226)  ← todo lo demás
     Botón de cuenta, «Reportar», atribución, baldosas ......  a 12 px

   Dos islas sin eje común. Entre el borde derecho de una y el de la otra hay
   448 px. Para pasar de calle a satélite y luego centrarse en la ubicación,
   el ratón cruza la pantalla en diagonal entre dos grupos que no comparten
   ni fila ni columna. Cinco flotantes a 12 px del borde, uno a 460.

   Y el 460 no es un número fijo: sale de `right: calc(50% - 260px)`, o sea
   que en un monitor de 1920 la columna se planta a 700 px del borde, en el
   centro del lienzo. La regla estaba pensada para centrar una app de teléfono
   dentro de una tableta y nadie la revisó al llegar el escritorio.

   LA DECISIÓN: un solo carril de 48 px pegado al borde derecho, con todo
   dentro y en un orden que responde a con qué frecuencia se toca cada cosa.
   El borde de pantalla es un objetivo de profundidad infinita —el cursor se
   para solo contra él—; una columna flotando en mitad del lienzo hay que
   apuntarla.

   GEOMETRÍA (23-sep-2026, carril de CUATRO, apilado de arriba abajo):

     Capas y opciones   ┐ una hoja: mapa base · capas · voz · modo camión
     Zoom +             │
     Zoom −             │
     Mi ubicación       ┘ abajo del todo, pegado al pulgar (como Google Maps)

   Textual del dueño: «reduce la cantidad de botones… que cuando uno le dé a
   un botón, salgan las diferentes opciones… no quiero que se tenga que
   scrolear para hacer el zoom». Ya no hay ▼ de plegar ni botones sueltos
   de mapa base, voz y camión: viven en la hoja (`opciones-en-la-hoja.js`).
   8 px entre hermanos. Todos 48×48.

   LA BARRA DE TRES CHIPS DESAPARECE EN TODOS LOS ANCHOS. Ocupaba 21.328 px²
   permanentes de mapa que no se podían plegar, pintaba una losa navy
   `rgba(6,20,40,.95)` idéntica en claro y en oscuro —el único objeto oscuro
   del mapa claro— y duplicaba el rótulo «Capas del mapa» con `#layersBtn`.
   Se cambia de mapa base una vez por sesión, no una vez por minuto: no
   merece 21.328 px permanentes, merece un botón de 2.304.

   Va la última de las hojas para ganar por orden de cascada, no con
   `!important`: subir la fuerza de una regla para ganarle a otra empieza una
   carrera que acaba en `!important` por todas partes y en nadie sabiendo
   quién manda.
   ========================================================================== */

/* ── LA BARRA FLOTANTE, FUERA EN TODOS LOS ANCHOS ───────────────────────
   `js/carril-derecho.js` además la quita del DOM. Dos mecanismos para lo
   mismo: si la hoja llega tarde por caché, el otro lo cubre; y quitarla del
   DOM la saca también del tabulador y del lector de pantalla, que es lo
   correcto para un control duplicado. */
.irc-capas { display: none; }

/* ══════════════════════════════════════════════════════════════════════════
   ESCRITORIO Y TABLETA (≥768)
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 768px) {

  .map-controls {
    right: 12px;
    /* 176 = 12 px de aire + los 164 px que ocupan «Reportar» y su margen.
       Es una distancia al borde inferior, así que no depende del alto de la
       ventana: a 900 el pie cae en 724; a 1080, en 904. */
    bottom: 176px;
    left: auto;
    width: 48px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    z-index: 900;
  }

  /* Los tres que ya existían medían 52; el ▼, 40. Un carril con tres anchos
     distintos se lee como tres cosas distintas. */
  .map-controls .map-ctrl-btn,
  .map-controls .ir-carril-btn {
    width: 48px;
    height: 48px;
    min-width: 48px;
    min-height: 48px;
    flex: 0 0 48px;
    border-radius: 12px;
  }

  /* 16 px donde cambia el grupo: encuadre → posición → contexto de la carga.
     El hueco es lo único que dice que son tres asuntos distintos. */
  .map-controls #locateBtn { margin-top: 8px; }

  /* Los ▼ viejos no se esconden a medias: se quitan del DOM en el JS.
     Esto cubre el caso de que el bundle los vuelva a inyectar. Y el carril
     ya no se pliega: `collapsed` no esconde nada. */
  .map-controls .r25-collapse-btn,
  .map-controls .fab-collapse-toggle,
  .ir-carril-colapso { display: none; }
  .map-controls.collapsed { transform: none; opacity: 1; pointer-events: auto; }

  /* El botón de camión de verdad sigue en el DOM (es el que guarda, confirma
     y avisa), pero no en el carril: lo pulsa el interruptor de la hoja. */
  .map-controls #truckRestrictBtn { display: none; }

  /* ── LOS PANELES SE ACOPLAN A LA IZQUIERDA ───────────────────────────
     Medido: un panel lateral abierto a 1440 entraba en x = 1040 y tapaba el
     carril entero. El riel del menú manda en el borde izquierdo, el carril
     manda en el derecho, y el mapa queda entero entre los dos. */
  .ir-escritorio .ir-pf5-panel.open,
  .ir-escritorio .bottom-sheet.open {
    left: var(--rail-w, 240px);
    right: auto;
  }

  /* Y con ellos acoplados a la izquierda, el carril tiene que VOLVER.
     `esquina-superior.css` esconde los cinco flotantes del mapa cuando hay una
     hoja abierta, con un argumento que era correcto en el teléfono: la hoja
     tapa el mapa entera, así que sus botones no pintan nada. En escritorio ya
     no lo es — medido, la hoja ocupa x 240..640 y quedan 800 px de mapa a la
     vista. Esconder el zoom y «mi ubicación» mientras se mira el registro de
     combustible es quitarle al dispatcher justo lo que usa para comprobar lo
     que está leyendo.

     La excepción cuelga de `.ir-escritorio`, que `device-detect.js` solo pone
     cuando hay ratón de verdad: en la tableta de 768 la hoja sigue siendo de
     ancho completo y los controles siguen escondiéndose, que ahí sí toca. */
  .ir-escritorio:has(.ir-pf5-panel.open) .map-controls,
  .ir-escritorio:has(.bottom-sheet.open) .map-controls {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
}

/* Tema claro: el carril no puede ser la única losa oscura del mapa claro. */
html[data-theme="light"] .ir-carril-colapso,
html[data-theme="light"] .ir-carril-base {
  background: #FFFFFF;
  color: #0A1B33;
  border-color: #D8E0EA;
}
html[data-theme="light"] .ir-carril-base-chip { color: #0A1B33; }
html[data-theme="light"] .ir-carril-base-chip:hover { background: #EEF3F9; }
html[data-theme="light"] .ir-carril-base-tit { color: #5B6B7F; }


/* ══════════════════════════════════════════════════════════════════════════
   EL CARRIL COMPLETO TAMBIÉN EN EL TELÉFONO
   20-sep-2026. Todo lo de arriba vivía dentro de `@media (min-width: 768px)`,
   así que en un teléfono el carril se quedaba en tres botones: ubicación,
   capas y modo camión. Ni zoom ni mapa base — dos cosas que la app nativa
   tiene desde el primer día. Se pedían con los dedos, y con el camión en
   marcha no hay dos dedos libres.

   Aquí van solo las piezas que el teléfono necesita. El aspecto de los
   botones —círculo de cristal, 48 px— ya lo dan `styles.css` y
   `ui-fixes-p0.css`; no se repite.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 767px) {

  /* Los ▼ viejos se quitan del DOM en el JS; esto cubre que el bundle los
     vuelva a inyectar. Y el carril ya no se pliega. */
  .map-controls .r25-collapse-btn,
  .map-controls .fab-collapse-toggle,
  .ir-carril-colapso { display: none; }
  .map-controls #truckRestrictBtn { display: none; }

  /* YA NO SE DESPLAZA. Con ocho botones hacía falta un scroll (440 px no
     caben en un teléfono de 640). Con cuatro (216 px) caben en cualquier
     teléfono, y el zoom queda SIEMPRE a la vista, que es lo que pidió el
     dueño: «no quiero que se tenga que scrolear para hacer el zoom». */
  .map-controls {
    max-height: none;
    overflow: visible;
    padding: 2px 0;
  }

  /* NADA SE ENCOGE PARA CABER.
     Medido a 360×640: con el carril al tope de su alto, el flex aplastaba los
     botones de zoom y de mapa base a 44 px. Un blanco de 44 px para un dedo
     con guante es exactamente el defecto que se acaba de corregir en el resto
     del mapa — y aquí entraba por la puerta de atrás, sin que ninguna regla de
     tamaño cambiara. Lo que no cabe se desliza; no se estruja. */
  .map-controls > * { flex: 0 0 auto; }
  .map-controls .ir-carril-btn {
    width: 48px; height: 48px;
    min-width: 48px; min-height: 48px;
  }

  /* `collapsed` ya no esconde nada: no hay ▼. */
  .map-controls.collapsed { transform: none; opacity: 1; pointer-events: auto; }

  /* El cajón de mapas base se abre hacia la izquierda, que es donde hay sitio. */
  .irc-capas { right: 68px; max-width: calc(100vw - 84px); }
}

/* ───── css/modales-dinero.css ───── */
/*PORQUE Los cuatro modales del dinero no tenian NI UNA regla CSS: se pintaban
       como bloques sueltos al final del body, por debajo del mapa. PORQUE*/
/* ==========================================================================
   IRON ROUTE — LOS MODALES DEL DINERO, QUE NUNCA SE VIERON

   EL HALLAZGO, MEDIDO. `js/fuel.js` y `js/ifta.js` construyen cuatro modales
   con estas clases:

       .ifta-modal-overlay   .ifta-modal   .ifta-modal-header
       .ifta-modal-body      .ifta-modal-footer
       .ifta-modal-close     .ifta-input   .ifta-btn

   **Ninguna de las ocho tenía una sola regla CSS en todo el proyecto.** Se
   buscaron en las 34 hojas: cero coincidencias.

   Lo que pasaba al pulsar «Registrar combustible», medido a 390×844:

       #fuel-log-modal   position: static · z-index: auto · fondo transparente
       .ifta-modal       x 0..390  y 138..602   ← en el flujo del documento

   O sea: el modal no es un modal. Es un bloque suelto pegado al final del
   `<body>`, sin fondo, sin caja, por debajo del mapa y de la barra inferior.
   El conductor pulsa «Registrar combustible», no ocurre nada visible, y
   concluye que el botón está roto. Y son los DOS registros que alimentan el
   IFTA: sin ellos no hay galones por estado, y sin galones por estado el
   informe trimestral no existe.

   Un `innerHTML` que se pinta sin estilo no falla: sale mal y calla. No hay
   error de consola, no hay excepción, no hay nada que buscar. Es el mismo
   patrón que las `var()` sin definir de la cabecera: el navegador hace algo
   razonable con lo que le queda y no avisa.

   ── LA ESCALA DE Z-INDEX, DECLARADA ────────────────────────────────────
   El proyecto tenía 22 valores distintos entre 1 y 10.010, puestos cada uno
   para ganarle al anterior. Aquí quedan escritos los seis que importan, con
   su porqué, para que el siguiente no tenga que adivinar ni subir a 10.011:

       --z-mapa       400   el lienzo y lo que Leaflet pinta encima
       --z-flotantes  900   carril derecho, «+ Viaje», «Reportar»
       --z-hojas     1200   paneles y hojas que tapan el mapa
       --z-menu      1300   el menú lateral y su velo
       --z-modal     2600   un modal tapa una hoja: por eso está encima
       --z-aviso     3000   los avisos, que tienen que verse SIEMPRE

   La regla de la casa: **si algo necesita estar por encima de otra cosa, se
   dice aquí con un nombre.** Un número suelto en un archivo cualquiera es
   una carrera que siempre acaba en `9999`.
   ========================================================================== */

:root {
  --z-mapa:      400;
  --z-flotantes: 900;
  --z-hojas:    1200;
  --z-menu:     1300;
  --z-modal:    2600;
  --z-aviso:    3000;
}

/* ── EL VELO ─────────────────────────────────────────────────────────────
   Cerrado es `display: none` y no un `transform` fuera de pantalla: un modal
   escondido con transform sigue recibiendo el tabulador, que es el defecto
   que `js/inerte.js` existe para arreglar. Aquí se evita de origen. */
.ifta-modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: var(--z-modal, 2600);
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(3, 13, 28, .72);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.ifta-modal-overlay.open { display: flex; }

/* ── LA CAJA ─────────────────────────────────────────────────────────────
   `max-height` y el cuerpo desplazable, no por elegancia: el modal de IFTA
   tiene seis campos y un desplegable de 50 estados. En un Android de 640 px
   de alto, sin esto, el botón «Guardar» queda fuera — exactamente el defecto
   que acababa de arreglarse en el alta. */
.ifta-modal {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 440px;
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  background: var(--surface, #0A1B33);
  color: var(--text, #F5F7FA);
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 16px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, .55);
  overflow: hidden;
}

.ifta-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex: 0 0 auto;
  padding: 16px 16px 12px;
  border-bottom: 1px solid var(--line, #1E3A5F);
}
.ifta-modal-header h3 {
  margin: 0;
  font-size: calc(17px * var(--ir-letra, 1));
  font-weight: 700;
  line-height: 1.3;
}

.ifta-modal-close {
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--text-muted, #9FB0C3);
  font-size: calc(18px * var(--ir-letra, 1));
  line-height: 1;
  cursor: pointer;
}
.ifta-modal-close:hover { background: var(--surface-2, #10294A); color: var(--text, #F5F7FA); }

.ifta-modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Los campos vienen dentro de su `<label>`, con el rótulo como texto suelto
   antes del control. Se apilan: rótulo arriba, control debajo. */
.ifta-modal-body label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: calc(13px * var(--ir-letra, 1));
  font-weight: 600;
  color: var(--text-muted, #9FB0C3);
}

.ifta-input {
  width: 100%;
  height: 48px;                    /* el suelo de la casa, también aquí */
  padding: 0 12px;
  font-size: calc(16px * var(--ir-letra, 1));                 /* 16 para que iOS no haga zoom al enfocar */
  font-weight: 600;
  color: var(--text, #F5F7FA);
  background: var(--ink, #030D1C);
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 10px;
  -webkit-appearance: none;
  appearance: none;
}
select.ifta-input {
  /* La flecha, dibujada aquí porque `appearance:none` se lleva la del sistema
     y un desplegable sin flecha no se lee como desplegable. */
  padding-right: 34px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) 21px, calc(100% - 13px) 21px;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.ifta-input:focus-visible {
  outline: 2px solid var(--accent, #F58220);
  outline-offset: 1px;
  border-color: var(--accent, #F58220);
}
.ifta-input::placeholder { color: var(--text-muted, #9FB0C3); opacity: .7; font-weight: 500; }

.ifta-modal-footer {
  flex: 0 0 auto;
  display: flex;
  gap: 10px;
  padding: 12px 16px 16px;
  border-top: 1px solid var(--line, #1E3A5F);
}

.ifta-btn {
  flex: 1 1 0;
  min-height: 48px;
  padding: 0 16px;
  font-size: calc(15px * var(--ir-letra, 1));
  font-weight: 700;
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 12px;
  background: var(--surface-2, #10294A);
  color: var(--text, #F5F7FA);
  cursor: pointer;
}
.ifta-btn:hover { filter: brightness(1.1); }
.ifta-btn:focus-visible { outline: 2px solid var(--accent, #F58220); outline-offset: 2px; }

/* Guardar es la acción; cancelar es la salida. Se distinguen por peso, no
   solo por color: en un camión con sol de frente el color se pierde. */
.ifta-btn-save {
  background: var(--accent, #F58220);
  border-color: var(--accent, #F58220);
  color: #0A1B33;
}
.ifta-btn-cancel { background: transparent; color: var(--text-muted, #9FB0C3); }

/* ── TEMA CLARO ────────────────────────────────────────────────────────── */
html[data-theme="light"] .ifta-modal-overlay { background: rgba(10, 27, 51, .45); }
html[data-theme="light"] .ifta-modal {
  background: #FFFFFF;
  color: #0A1B33;
  border-color: #D8E0EA;
}
html[data-theme="light"] .ifta-modal-header,
html[data-theme="light"] .ifta-modal-footer { border-color: #E4EAF2; }
html[data-theme="light"] .ifta-modal-body label { color: #5B6B7F; }
/* `background-color` y no `background`: la forma corta borra el
   `background-image` con el que se dibuja la flecha del desplegable, y en
   claro el `<select>` de 50 estados se quedaba sin flecha — o sea, sin nada
   que dijera que se puede abrir. Medido en la captura, no deducido. */
html[data-theme="light"] .ifta-input {
  background-color: #F7F9FC;
  color: #0A1B33;
  border-color: #D8E0EA;
}
html[data-theme="light"] .ifta-btn {
  background: #EEF3F9;
  color: #0A1B33;
  border-color: #D8E0EA;
}
/* Y «Guardar» tiene que seguir siendo el naranja de la casa en claro: la
   regla de arriba, por ser más específica que `.ifta-btn-save`, lo dejaba
   igual que «Cancelar». Dos botones idénticos donde uno guarda dinero y el
   otro lo tira. */
html[data-theme="light"] .ifta-btn-save {
  background: var(--accent, #F58220);
  border-color: var(--accent, #F58220);
  color: #0A1B33;
}
html[data-theme="light"] .ifta-btn-cancel { background: transparent; }
html[data-theme="light"] .ifta-btn-cancel { background: transparent; color: #5B6B7F; }
html[data-theme="light"] .ifta-modal-close { color: #5B6B7F; }
html[data-theme="light"] .ifta-modal-close:hover { background: #EEF3F9; color: #0A1B33; }

/* ── TELÉFONO CORTO ──────────────────────────────────────────────────────
   Por debajo de 380 px de alto útil la caja se pega abajo: es más fácil
   alcanzar el pie con el pulgar que el centro de la pantalla. */
@media (max-height: 560px) {
  .ifta-modal-overlay { align-items: flex-end; padding: 8px; }
  .ifta-modal { max-height: calc(100dvh - 16px); }
}

/* ==========================================================================
   EL INFORME IFTA QUE DICE CUÁNDO NO SABE

   Tres clases nuevas para tres estados que antes eran uno solo —un número—:

     .ifta-aviso     el informe está a medias y aquí está qué falta apuntar
     .ifta-sin-dato  esta celda no se sabe. Un guion, nunca «$0.00»
     .ifta-nota      por qué no se enseña un total
     .ifta-descargo  qué es este panel y qué NO es: una ayuda, no la declaración

   La regla que hay detrás, y que vale para todo el dinero de esta app:
   `undefined` = todavía cargando · `null` = se preguntó y no se sabe ·
   `0` = cero de verdad. Tres estados, tres PALABRAS distintas en pantalla.
   Pintar los tres igual es cómo un registro a medias acaba en un expediente.
   ========================================================================== */

.ifta-aviso {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 10px 0 14px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--warning, #F5A623);
  /* Fondo propio y no solo un borde: este aviso tiene que ganar la mirada
     antes que la tabla que hay debajo, porque la tabla parece correcta. */
  background: color-mix(in srgb, var(--warning, #F5A623) 14%, transparent);
  color: var(--text, #F5F7FA);
  font-size: calc(14px * var(--ir-letra, 1));
  line-height: 1.45;
}
.ifta-aviso strong {
  font-size: calc(13px * var(--ir-letra, 1));
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--warning, #F5A623);
}

/* El descargo va antes que las cifras y se lee como lo que es: una nota, no
   una alarma. Un panel que dice «Impuesto» y exporta un papel con el USDOT
   impreso se parece demasiado a una declaración; esto lo desmiente. */
.ifta-descargo {
  margin: 8px 0 12px;
  padding: 10px 12px;
  border-left: 3px solid var(--accent, #F58220);
  border-radius: 0 10px 10px 0;
  background: color-mix(in srgb, var(--accent, #F58220) 8%, transparent);
  color: var(--text-muted, #9FB0C3);
  font-size: calc(12.5px * var(--ir-letra, 1));
  line-height: 1.45;
}

.ifta-sin-dato {
  color: var(--text-muted, #9FB0C3);
  font-variant-numeric: tabular-nums;
}

.ifta-nota {
  margin: 8px 0 0;
  font-size: calc(12.5px * var(--ir-letra, 1));
  line-height: 1.45;
  color: var(--text-muted, #9FB0C3);
}

html[data-theme="light"] .ifta-aviso { color: #0A1B33; }
html[data-theme="light"] .ifta-aviso strong { color: #9A6200; }
html[data-theme="light"] .ifta-sin-dato,
html[data-theme="light"] .ifta-descargo,
html[data-theme="light"] .ifta-nota { color: #5B6B7F; }

/* ───── css/paneles-dinero.css ───── */
/*PORQUE Las cuatro pantallas del dinero y del cumplimiento se pintaban con 130
       clases sin una sola regla. IFTA, combustible, rastreo y horas. PORQUE*/
/* ==========================================================================
   IRON ROUTE — LAS CUATRO PANTALLAS QUE SE PINTABAN SIN UNA SOLA REGLA

   EL HALLAZGO, CONTADO. Se sacaron todas las clases que emiten `ifta.js`,
   `fuel.js`, `tracking.js` y `hos.js` y se buscaron una por una en las 35
   hojas del proyecto:

       js/ifta.js       42 clases  →  29 SIN NINGUNA REGLA
       js/fuel.js       56 clases  →  39 SIN NINGUNA REGLA
       js/tracking.js   44 clases  →  32 SIN NINGUNA REGLA
       js/hos.js        31 clases  →  30 SIN NINGUNA REGLA

   **130 clases sin estilo.** No son cuatro detalles: son las cuatro
   pantallas del dinero y del cumplimiento —IFTA, combustible, rastreo y
   horas de servicio— pintándose como texto corrido. Medido en el informe
   IFTA, la tabla salía así:

       EstadoMillasGalonesTasaImpuesto
       NJ12.0100.0$0.424—

   Cinco columnas pegadas sin un espacio. Eso no se lee: se adivina.

   POR QUÉ NADIE LO VIO. Los cuatro módulos construyen su pantalla con
   `innerHTML` y cadenas. Un `class="ifta-tbl-head"` que no existe en ninguna
   hoja **no falla**: el navegador aplica los estilos por defecto de un `div`
   y sigue. No hay error de consola, no hay excepción, no hay nada que
   buscar. Es el mismo patrón que las `var()` sin definir y que los modales
   del dinero: sale mal y calla.

   Y por qué importa más de lo que parece: son las pantallas que producen el
   informe trimestral que se presenta a la jurisdicción base, el registro de
   combustible que lo alimenta, y el reloj de horas cuya infracción es una
   multa federal. Una pantalla ilegible en el sitio donde se comprueba un
   número es una pantalla que no se comprueba.

   `tests/pantallas-con-estilo.mjs` cuenta las clases contra las hojas en
   cada ejecución, para que esto no pueda volver en silencio.

   La retícula: 12/16/20 px de hueco, 48 px de suelo de toque, 12 px de suelo
   tipográfico, números en `tabular-nums` para que las columnas cuadren.
   ========================================================================== */

/* ══════════════════════════════════════════════════════════════════════════
   VOCABULARIO COMPARTIDO — las cuatro pantallas son la misma familia
   ══════════════════════════════════════════════════════════════════════════ */
.ifta-tbl, .ifta-totals, .fuel-stats, .hos-gauges,
.trk-status-grid, .fuel-stations-section, .trk-fleet-section {
  font-variant-numeric: tabular-nums;
}

/* ══════════════════════════════════════════════════════════════════════════
   1 · IFTA
   ══════════════════════════════════════════════════════════════════════════ */
.ifta-quarter-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.ifta-q-badge {
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--surface-2, #10294A);
  border: 1px solid var(--line, #1E3A5F);
  font-size: calc(13px * var(--ir-letra, 1));
  font-weight: 800;
  letter-spacing: .06em;
}
.ifta-mpg { font-size: calc(13px * var(--ir-letra, 1)); font-weight: 600; color: var(--text-muted, #9FB0C3); }

.ifta-map-wrap {
  margin: 4px 0 16px;
  padding: 8px;
  border-radius: 12px;
  background: var(--ink, #030D1C);
  border: 1px solid var(--line, #1E3A5F);
}
.ifta-map-wrap canvas { display: block; width: 100%; height: auto; }

/* La tabla: rejilla de cinco columnas, no cinco `<span>` pegados. El estado
   a la izquierda y los cuatro números alineados a la derecha, que es como se
   compara una columna de dinero. */
.ifta-tbl {
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 12px;
  overflow: hidden;
}
/* Medido a 360 px: con 8 px de hueco y 12 px de letra, «IMPUESTO» se salía
   por la derecha. Cinco columnas de dinero en un teléfono estrecho no caben
   con la palabra entera, así que el encabezado baja a 11 px sin `letter-
   spacing` —lo que se lee de un encabezado es su sitio, no su tamaño— y el
   hueco a 6. Las cifras se quedan en 13, que es lo que se lee. */
.ifta-tbl-head, .ifta-tbl-row {
  display: grid;
  grid-template-columns: 2.6rem 1fr 1fr 3.2rem 1fr;
  gap: 6px;
  align-items: center;
  padding: 10px;
  font-size: calc(13px * var(--ir-letra, 1));
}
.ifta-tbl-head {
  background: var(--surface-2, #10294A);
  color: var(--text-muted, #9FB0C3);
  font-size: calc(11px * var(--ir-letra, 1));
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0;
}
.ifta-tbl-row { border-top: 1px solid var(--line, #1E3A5F); }
.ifta-tbl-head > *:not(:first-child),
.ifta-tbl-row > *:not(:first-child) { text-align: right; }
.ifta-st-name { font-weight: 700; }

.ifta-owe    { color: var(--warning, #F5A623); font-weight: 700; }
.ifta-credit { color: var(--success, #10B981); font-weight: 700; }

.ifta-totals {
  margin-top: 14px;
  padding: 12px 14px;
  border-radius: 12px;
  background: var(--surface-2, #10294A);
  border: 1px solid var(--line, #1E3A5F);
}
.ifta-total-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 5px 0;
  font-size: calc(14px * var(--ir-letra, 1));
}
.ifta-total-row.ifta-net {
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--line, #1E3A5F);
  font-size: calc(16px * var(--ir-letra, 1));
  font-weight: 800;
}

.ifta-actions, .fuel-actions, .hos-actions, .trk-share-actions, .trk-driver-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}
.ifta-btn-fuel, .ifta-btn-export, .ifta-btn-print, .ifta-btn-reset {
  flex: 1 1 45%;
}
.ifta-btn-reset { color: var(--error, #EF4444); border-color: var(--error, #EF4444); }

.ifta-empty, .fuel-vacio, .trk-vacio {
  padding: 20px 16px;
  text-align: center;
  color: var(--text-muted, #9FB0C3);
  font-size: calc(14px * var(--ir-letra, 1));
  line-height: 1.5;
}

.ifta-fuel-section, .fuel-log-section, .trk-geofence-section,
.trk-status-section, .trk-fleet-section, .trk-my-status,
.fuel-stations-section, .hos-log-section {
  margin-top: 20px;
}
.ifta-fuel-section h4, .fuel-log-section h4, .trk-geofence-section h4,
.trk-status-section h4, .trk-fleet-section h4, .trk-my-status h4,
.fuel-stations-section h4, .hos-log-section h4 {
  margin: 0 0 10px;
  font-size: calc(13px * var(--ir-letra, 1));
  font-weight: 800;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-muted, #9FB0C3);
}

.ifta-fuel-list, .fuel-log-list, .hos-log-timeline {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ifta-fuel-item, .fuel-log-item, .trk-geofence-item {
  display: grid;
  grid-template-columns: auto auto 1fr auto 48px;
  gap: 8px;
  align-items: center;
  padding: 10px 12px;
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 10px;
  font-size: calc(13px * var(--ir-letra, 1));
}
.ifl-date, .fli-date { color: var(--text-muted, #9FB0C3); }
.ifl-state { font-weight: 800; }
.ifl-gal, .fli-gal, .fli-price { color: var(--text-muted, #9FB0C3); }
.ifl-cost, .fli-cost { font-weight: 700; text-align: right; }
.fli-station { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ifl-del, .fli-del, .trk-gf-del {
  width: 48px; height: 48px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 10px;
  background: transparent;
  color: var(--text-muted, #9FB0C3);
  cursor: pointer;
}
.ifl-del:hover, .fli-del:hover, .trk-gf-del:hover {
  background: var(--surface-2, #10294A);
  color: var(--error, #EF4444);
}

/* ══════════════════════════════════════════════════════════════════════════
   2 · COMBUSTIBLE
   ══════════════════════════════════════════════════════════════════════════ */
.fuel-gauge-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 4px auto 12px;
  width: 200px;
  max-width: 100%;
}
.fuel-gauge-wrap canvas { display: block; max-width: 100%; height: auto; }
.fuel-gauge-center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  pointer-events: none;
  text-align: center;
}
.fg-pct   { font-size: calc(30px * var(--ir-letra, 1)); font-weight: 800; line-height: 1; }
.fg-range { font-size: calc(16px * var(--ir-letra, 1)); font-weight: 700; }
.fg-label { font-size: calc(12px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3); }
/* Cuando el alcance sale de un supuesto y no de una medida, se pinta en
   apagado y con el mismo peso que el rótulo: un número grande y brillante se
   lee como medido. */
.fg-range.fg-supuesto { color: var(--text-muted, #9FB0C3); font-weight: 600; font-size: calc(14px * var(--ir-letra, 1)); }

.fuel-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 14px;
}
.fuel-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 10px 6px;
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 12px;
}
.fs-val   { font-size: calc(18px * var(--ir-letra, 1)); font-weight: 800; }
.fs-label { font-size: calc(12px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3); }

.fuel-slider-wrap {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 16px;
  font-size: calc(13px * var(--ir-letra, 1));
  color: var(--text-muted, #9FB0C3);
}
.fuel-slider {
  width: 100%;
  height: 32px;          /* el carril es fino; el ÁREA de toque no */
  accent-color: var(--accent, #F58220);
}

.fuel-type-filters, .trk-status-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin-bottom: 16px;
}
.fuel-filter-btn, .trk-status-btn {
  min-height: 48px;
  padding: 0 12px;
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 12px;
  background: transparent;
  color: var(--text, #F5F7FA);
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 600;
  cursor: pointer;
}
.fuel-filter-btn.fuel-filter-active {
  border-color: var(--accent, #F58220);
  background: color-mix(in srgb, var(--accent, #F58220) 16%, transparent);
}
.trk-status-btn.trk-status-active {
  border-color: var(--status-color, var(--accent, #F58220));
  background: color-mix(in srgb, var(--status-color, #F58220) 18%, transparent);
}

.fuel-station-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 12px;
  margin-bottom: 8px;
}
.fsc-left  { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.fsc-right { text-align: right; flex: 0 0 auto; }
.fsc-name  { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fsc-addr  { font-size: calc(12px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fsc-dist  { font-size: calc(13px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3); }
.fsc-price { font-size: calc(17px * var(--ir-letra, 1)); font-weight: 800; }
/* «Sin precio» no es un precio de cero: se dice con palabras. */
.fsc-sin-precio { font-size: calc(12px * var(--ir-letra, 1)); font-weight: 600; color: var(--text-muted, #9FB0C3); }
.fsc-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.fsc-tag {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-2, #10294A);
  font-size: calc(12px * var(--ir-letra, 1));
  color: var(--text-muted, #9FB0C3);
}
.fuel-optimal, .fuel-optimal-base {
  padding: 12px;
  border-radius: 12px;
  border: 1px solid var(--success, #10B981);
  background: color-mix(in srgb, var(--success, #10B981) 12%, transparent);
  font-size: calc(14px * var(--ir-letra, 1));
  line-height: 1.45;
}
.fuel-urgent {
  border-color: var(--error, #EF4444);
  background: color-mix(in srgb, var(--error, #EF4444) 14%, transparent);
}

/* 45 % y no 30 %: con 30 caben tres por fila y el cuarto se queda solo,
   descolgado y del ancho de la pantalla. Con 45 son dos y dos. */
.fuel-btn, .trk-btn { flex: 1 1 30%; }
.hos-btn { flex: 1 1 44%; }
.fuel-btn, .trk-btn, .hos-btn {
  min-height: 48px;
  padding: 0 12px;
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 12px;
  background: var(--surface-2, #10294A);
  color: var(--text, #F5F7FA);
  font-size: calc(14px * var(--ir-letra, 1));
  font-weight: 700;
  cursor: pointer;
}
.fuel-btn:hover, .trk-btn:hover, .hos-btn:hover { filter: brightness(1.12); }
.fuel-btn:focus-visible, .trk-btn:focus-visible, .hos-btn:focus-visible,
.fuel-filter-btn:focus-visible, .trk-status-btn:focus-visible {
  outline: 2px solid var(--accent, #F58220);
  outline-offset: 2px;
}
.fuel-btn-log { background: var(--accent, #F58220); border-color: var(--accent, #F58220); color: #0A1B33; }

/* ══════════════════════════════════════════════════════════════════════════
   3 · RASTREO
   ══════════════════════════════════════════════════════════════════════════ */
.trk-btn-start { background: var(--accent, #F58220); border-color: var(--accent, #F58220); color: #0A1B33; }
.trk-btn-stop  { background: transparent; color: var(--error, #EF4444); border-color: var(--error, #EF4444); }
.trk-btn-sm    { flex: 0 0 auto; min-height: 48px; font-size: calc(13px * var(--ir-letra, 1)); }
.trk-my-status .trk-btn, .trk-fleet-section .trk-btn { width: 100%; }

.trk-sharing-active {
  padding: 12px;
  border-radius: 12px;
  border: 1px solid var(--success, #10B981);
  background: color-mix(in srgb, var(--success, #10B981) 12%, transparent);
}
.trk-share-link {
  display: block;
  width: 100%;
  margin-top: 8px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--ink, #030D1C);
  border: 1px solid var(--line, #1E3A5F);
  font-size: calc(13px * var(--ir-letra, 1));
  word-break: break-all;
}
.trk-pulse {
  display: inline-block;
  width: 8px; height: 8px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--success, #10B981);
  animation: trk-latido 1.6s ease-in-out infinite;
}
@keyframes trk-latido { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .trk-pulse { animation: none; } }

.trk-driver-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--line, #1E3A5F);
  border-radius: 12px;
  margin-bottom: 8px;
}
.trk-driver-avatar {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--surface-2, #10294A);
  font-size: calc(18px * var(--ir-letra, 1));
}
.trk-driver-info { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
.trk-driver-name { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.trk-driver-meta, .trk-driver-updated, .trk-driver-detail {
  font-size: calc(12px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3);
}
.trk-driver-status {
  flex: 0 0 auto;
  padding: 4px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--status-color, #9FB0C3) 20%, transparent);
  font-size: calc(12px * var(--ir-letra, 1));
  font-weight: 700;
}

.trk-geofence-item { grid-template-columns: auto 1fr auto 48px; }
.trk-gf-icon   { font-size: calc(18px * var(--ir-letra, 1)); }
.trk-gf-name   { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.trk-gf-radius { font-size: calc(12px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3); }
.trk-gf-badge  {
  padding: 2px 8px; border-radius: 999px;
  background: var(--surface-2, #10294A);
  font-size: calc(12px * var(--ir-letra, 1));
}
.trk-breadcrumb-info { font-size: calc(13px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3); margin-top: 8px; }
.trk-cerrada-p { font-size: calc(13px * var(--ir-letra, 1)); line-height: 1.5; color: var(--text-muted, #9FB0C3); margin: 0 0 10px; }
.trk-btn-icon { flex: 0 0 auto; width: 48px; min-height: 48px; padding: 0; }
.trk-btn-copy { flex: 0 0 auto; }

/* ══════════════════════════════════════════════════════════════════════════
   4 · HORAS DE SERVICIO
   ══════════════════════════════════════════════════════════════════════════ */
.hos-current-status {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  margin-bottom: 14px;
  border-radius: 999px;
  border: 1px solid var(--hos-color, var(--line, #1E3A5F));
  background: color-mix(in srgb, var(--hos-color, #666) 16%, transparent);
}
.hos-status-icon  { font-size: calc(20px * var(--ir-letra, 1)); }
.hos-status-label { font-weight: 700; }

.hos-gauges {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 12px;
  margin-bottom: 16px;
}
.hos-gauge-wrap {
  position: relative;
  width: 140px;
  max-width: 45%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hos-gauge-sm { width: 100px; }
.hos-gauge-wrap canvas { display: block; max-width: 100%; height: auto; }
.hos-gauge-label {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  text-align: center;
  pointer-events: none;
}
.hos-gauge-val    { font-size: calc(17px * var(--ir-letra, 1)); font-weight: 800; line-height: 1.1; }
.hos-gauge-val-sm { font-size: calc(14px * var(--ir-letra, 1)); }
.hos-gauge-sub    { font-size: calc(12px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3); }
.hos-gauge-max    { font-size: calc(12px * var(--ir-letra, 1)); color: var(--text-muted, #9FB0C3); opacity: .8; }

.hos-btn-drive   { background: var(--accent, #F58220); border-color: var(--accent, #F58220); color: #0A1B33; }
.hos-btn-break   { background: var(--surface-2, #10294A); }
.hos-btn-stop    { color: var(--error, #EF4444); border-color: var(--error, #EF4444); background: transparent; }
.hos-btn-reset   { color: var(--error, #EF4444); border-color: var(--error, #EF4444); background: transparent; }
.hos-btn-endbreak, .hos-btn-sleeper, .hos-btn-offduty { background: var(--surface-2, #10294A); }
.hos-reset-wrap { display: flex; margin-top: 12px; }

/* El descargo NO se esconde en gris pequeño: esta herramienta no sustituye a
   un ELD certificado, y quien lo confunda se lleva una multa federal. */
.hos-disclaimer {
  margin-top: 16px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--warning, #F5A623);
  background: color-mix(in srgb, var(--warning, #F5A623) 12%, transparent);
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.5;
}

.hos-log-entry {
  display: grid;
  grid-template-columns: 12px auto 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 8px 10px;
  border-radius: 10px;
  font-size: calc(13px * var(--ir-letra, 1));
}
.hos-log-active { background: var(--surface-2, #10294A); font-weight: 700; }
.hos-log-dot    { width: 10px; height: 10px; border-radius: 50%; background: var(--text-muted, #9FB0C3); }
.hos-log-active .hos-log-dot { background: var(--accent, #F58220); }
.hos-log-time   { color: var(--text-muted, #9FB0C3); font-variant-numeric: tabular-nums; }
.hos-log-status { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hos-log-dur    { text-align: right; font-variant-numeric: tabular-nums; }

/* ══════════════════════════════════════════════════════════════════════════
   EL INFORME IMPRESO — `exportHTML()` escribe su propia hoja, pero estas dos
   clases se le escaparon.
   ══════════════════════════════════════════════════════════════════════════ */
.totals { border-radius: 8px; }
.totals > div { display: flex; justify-content: space-between; gap: 16px; padding: 3px 0; }
.totals .net { font-weight: 800; border-top: 1px solid rgba(255, 255, 255, .25); margin-top: 6px; padding-top: 8px; }
.company { font-weight: 700; }
.footer  { margin-top: 24px; font-size: calc(12px * var(--ir-letra, 1)); opacity: .7; }

/* ══════════════════════════════════════════════════════════════════════════
   TEMA CLARO
   ══════════════════════════════════════════════════════════════════════════ */
html[data-theme="light"] .ifta-q-badge,
html[data-theme="light"] .ifta-tbl-head,
html[data-theme="light"] .ifta-totals,
html[data-theme="light"] .fuel-btn,
html[data-theme="light"] .trk-btn,
html[data-theme="light"] .hos-btn,
html[data-theme="light"] .fsc-tag,
html[data-theme="light"] .trk-gf-badge,
html[data-theme="light"] .trk-driver-avatar,
html[data-theme="light"] .hos-log-active {
  background: #EEF3F9;
  color: #0A1B33;
  border-color: #D8E0EA;
}
html[data-theme="light"] .ifta-tbl,
html[data-theme="light"] .ifta-tbl-row,
html[data-theme="light"] .fuel-stat,
html[data-theme="light"] .fuel-station-card,
html[data-theme="light"] .trk-driver-card,
html[data-theme="light"] .ifta-fuel-item,
html[data-theme="light"] .fuel-log-item,
html[data-theme="light"] .trk-geofence-item,
html[data-theme="light"] .fuel-filter-btn,
html[data-theme="light"] .trk-status-btn { border-color: #D8E0EA; color: #0A1B33; }
html[data-theme="light"] .ifta-mpg,
html[data-theme="light"] .fs-label,
html[data-theme="light"] .fg-label,
html[data-theme="light"] .fsc-addr,
html[data-theme="light"] .fsc-dist,
html[data-theme="light"] .hos-gauge-sub,
html[data-theme="light"] .hos-gauge-max,
html[data-theme="light"] .trk-driver-meta,
html[data-theme="light"] .ifta-empty,
html[data-theme="light"] .fuel-vacio { color: #5B6B7F; }
html[data-theme="light"] .trk-share-link { background: #F7F9FC; border-color: #D8E0EA; }
html[data-theme="light"] .fuel-btn-log,
html[data-theme="light"] .trk-btn-start,
html[data-theme="light"] .hos-btn-drive {
  background: var(--accent, #F58220);
  border-color: var(--accent, #F58220);
  color: #0A1B33;
}

/* Los tres que faltaban al contarlos la primera vez. `cng` es la clase de
   estado del filtro de gas natural comprimido; los otros dos, los botones
   secundarios de la fila de acciones. */
.fuel-btn-find, .fuel-btn-settings { background: var(--surface-2, #10294A); }
.fuel-filter-btn.cng { border-color: #3B7DD8; }
html[data-theme="light"] .fuel-btn-find,
html[data-theme="light"] .fuel-btn-settings { background: #EEF3F9; }

/* La frase que dice de dónde sale el alcance cuando no está medido. Va justo
   debajo del aforo, con el mismo ancho, para que se lea como parte del
   número y no como una nota al pie que nadie mira. */
.fuel-supuesto-nota {
  margin: -4px 0 14px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  font-size: calc(12.5px * var(--ir-letra, 1));
  line-height: 1.45;
  color: var(--text-muted, #9FB0C3);
}
html[data-theme="light"] .fuel-supuesto-nota { background: #EEF3F9; border-color: #D8E0EA; color: #5B6B7F; }

/* ───── css/navegacion-rumbo.css ───── */
/*PORQUE Navegando, el mapa gira con la brujula y el vehiculo va siempre derecho.
       Lo pidio el dueno: «el frente del celular siempre tiene que ser el camino
       por donde arrancar». PORQUE*/
/* ==========================================================================
   IRON ROUTE — EL MAPA GIRADO, Y EL VEHÍCULO SIEMPRE DERECHO

   La regla que manda aquí, dicha por el dueño: «el camión siempre tiene que
   estar derecho; el mapa es el que coge la orientación de la brújula». Todo
   lo de este archivo sale de esa frase.

   POR QUÉ EL CONTENEDOR ES MÁS GRANDE QUE LA PANTALLA. Al girar un rectángulo
   sobre su centro, las esquinas se salen y aparecen huecos sin baldosas — el
   defecto que se ve en todos los mapas rotados mal hechos. Con el lado igual a
   la DIAGONAL de la pantalla no hay ángulo en el que asome nada. Los píxeles
   de más no cuestan: son las mismas baldosas que Leaflet ya iba a pedir.

   POR QUÉ EL VEHÍCULO NO ES UN MARCADOR. Un marcador dentro de un mapa rotado
   hay que contragirarlo en cada cuadro, y cualquier desfase entre las dos
   rotaciones se ve como un temblor. Fuera del mapa, fijo en la pantalla, está
   derecho por construcción y no hay nada que sincronizar.

   Y EL EMOJI, FUERA. `js/navigation.js` pintaba `🚛` a 32 px. Un emoji lo
   dibuja cada sistema a su manera —en Android es un camión de juguete de seis
   colores— así que el aspecto del producto dependía del teléfono. Con sol de
   frente, a 32 px, eso no se lee. Aquí hay una silueta con dos tonos y un
   contorno blanco: se distingue de un vistazo sobre mapa claro y oscuro.
   ========================================================================== */

/* ══════════════════════════════════════════════════════════════════════════
   1 · EL MAPA QUE GIRA
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-mapa-rumbo #map {
  position: fixed;
  /* El tamaño y la posición los pone `js/navegacion-rumbo.js`: dependen de la
     diagonal de ESTA pantalla, y eso no se puede escribir en una hoja. */
  transition: transform 350ms cubic-bezier(.22, .61, .36, 1);
  will-change: transform;
}

/* Nada de transición al arrancar y al parar: el primer giro sería una vuelta
   desde el norte, y el último, otra de vuelta. */
html.ir-mapa-rumbo #map.ir-sin-transicion { transition: none; }

@media (prefers-reduced-motion: reduce) {
  html.ir-mapa-rumbo #map { transition: none; }
}

/* La atribución vive DENTRO del contenedor de Leaflet y giraba con él: medido
   a rumbo 38°, salía inclinada y anclada a 463 px FUERA de la pantalla por la
   izquierda. La licencia de OSM exige que sea legible, y torcida y medio fuera
   no lo es.

   Contragirarla no bastaba —se enderezaba el texto pero seguía anclada a una
   esquina fuera de la ventana— y `position: fixed` tampoco, porque un ancestro
   con `transform` se convierte en su bloque contenedor. `js/navegacion-rumbo.js`
   la SACA del elemento que gira y la devuelve al salir; esto solo la coloca
   mientras está fuera. */
.leaflet-control-container.ir-controles-fuera {
  position: fixed;
  inset: 0;
  z-index: 610;
  pointer-events: none;
}
.leaflet-control-container.ir-controles-fuera > * { pointer-events: auto; }

/* ══════════════════════════════════════════════════════════════════════════
   2 · EL VEHÍCULO — fijo, derecho, y en el sitio donde se mira
   ══════════════════════════════════════════════════════════════════════════ */
.ir-puck {
  position: fixed;
  left: 50%;
  /* 68 % y no 50 %: lo que hace falta mirar es lo que VIENE. Con el vehículo
     en el centro, media pantalla se gasta en carretera ya pasada. Es la misma
     proporción que usan Google Maps y Waze en navegación. */
  top: 68%;
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  z-index: 620;              /* encima del mapa, debajo del carril y las hojas */
  pointer-events: none;      /* no se toca: es una indicación, no un control */
  display: none;
}
html.ir-mapa-rumbo .ir-puck { display: block; }

.ir-puck svg { display: block; width: 100%; height: 100%; }

.ir-puck-cono {
  fill: var(--accent-fill, #D4A84B);
  opacity: .28;
}
.ir-puck-borde { fill: #FFFFFF; }
.ir-puck-cuerpo { fill: var(--accent-fill, #D4A84B); }
.ir-puck-punta  { fill: var(--accent-ink, #030D1C); }

/* En tema claro el disco necesita un contorno oscuro: el blanco sobre un mapa
   claro desaparece, y lo que queda es una mancha dorada sin forma. */
html[data-theme="light"] .ir-puck-borde { fill: #0A1B33; }
html[data-theme="light"] .ir-puck-punta { fill: #FFFFFF; }

/* ══════════════════════════════════════════════════════════════════════════
   3 · VOLVER AL NORTE
   Solo mientras el mapa está girado: un botón de «volver al norte» con el
   norte ya arriba no dice nada y ocupa 48 px de mapa.
   ══════════════════════════════════════════════════════════════════════════ */
.ir-btn-norte {
  position: fixed;
  display: none;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 48px;
  height: 48px;
  left: 12px;
  top: calc(env(safe-area-inset-top, 0px) + 96px);
  z-index: 900;
  border-radius: 24px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface, #0A1B33);
  color: var(--text, #F5F7FA);
  cursor: pointer;
  box-shadow: 0 2px 12px rgba(0, 0, 0, .3);
}
html.ir-mapa-rumbo .ir-btn-norte { display: flex; }

/* ── EN MARCHA TAMBIÉN, Y ESTE ERA EL FALLO ───────────────────────────────
   `css/navegacion-pantalla.css` escondía este botón mientras se navegaba,
   con el argumento de que «en navegación el norte arriba no se ofrece». Pero
   el dueño pidió lo contrario con todas las letras —«PERO TIENE QUE SER EN
   DOS SENTIDOS»— y escondiéndolo el mapa girado no tenía salida: ni al
   norte, ni de vuelta.

   Va MÁS ABAJO que fuera de navegación porque arriba está la barra de giro,
   que ocupa los primeros ~210 px, y 56 px en vez de 48 porque con el camión
   en marcha el objetivo tiene que ser el mismo que el del carril. */
html.ir-navegando .ir-btn-norte {
  display: flex;
  width: 56px;
  height: 56px;
  border-radius: 28px;
  top: calc(env(safe-area-inset-top, 0px) + 232px);
}
html.ir-navegando .ir-btn-norte[aria-pressed="false"] {
  /* Norte arriba mientras se navega es el estado RARO: se marca, como se
     marca la voz silenciada, para que se vea de un vistazo por qué el mapa
     no gira. */
  border-color: var(--gold, #D4A84B);
  color: var(--gold-light, #E8C468);
}

/* En escritorio el carril derecho manda en ese borde; este va al izquierdo,
   por debajo de la cabecera de búsqueda, donde no compite con nada. */
.ir-escritorio .ir-btn-norte { left: calc(var(--rail-w, 240px) + 12px); top: 96px; }

.ir-btn-norte svg { position: absolute; transition: transform 350ms cubic-bezier(.22,.61,.36,1); }
.ir-aguja-n { fill: var(--error, #EF4444); }   /* el norte, en rojo, como toda aguja */
.ir-aguja-s { fill: var(--text-muted, #9FB0C3); }
.ir-btn-norte-tx {
  position: relative;
  font-size: calc(12px * var(--ir-letra, 1));   /* el suelo de la casa; estaba a 11 y nadie lo había medido */
  font-weight: 800;
  letter-spacing: .04em;
  transform: translateY(14px);
  pointer-events: none;
}

html[data-theme="light"] .ir-btn-norte {
  background: #FFFFFF;
  color: #0A1B33;
  border-color: #D8E0EA;
}

/* ══════════════════════════════════════════════════════════════════════════
   4 · LO QUE NO PUEDE GIRAR CON EL MAPA
   El carril de controles, la cabecera, las hojas y los avisos viven FUERA del
   contenedor del mapa, así que no giran. Esto es solo un recordatorio con
   dientes: si alguien mete un control dentro de `#map`, girará con él y
   acabará boca abajo. `tests/mapa-gira-camion-derecho.mjs` lo comprueba.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-mapa-rumbo .map-controls,
html.ir-mapa-rumbo .ir-carril-colapso,
html.ir-mapa-rumbo .search-header,
html.ir-mapa-rumbo .bottom-nav,
html.ir-mapa-rumbo .toast-container {
  transform: none;
}


/* ══════════════════════════════════════════════════════════════════════════
   5 · LA ROSA DE LOS VIENTOS EN EL CARRIL
   20-sep-2026. El dueño, con las dos pantallas delante: «la aplicación online
   vía web no tiene lo que es la brújula para centrar el mapa». Tenía razón a
   medias, y la mitad que faltaba era la que se toca: el magnetómetro se leía
   (`brujula.js`) y el mapa sabía girar (`navegacion-rumbo.js`), pero no había
   ningún mando ni ningún número en pantalla.

   La rosa vive en `.map-controls`, encima del botón de ubicación, y hereda de
   `.map-ctrl-btn` el cristal, el círculo y los 48 px. Debajo lleva el rumbo en
   grados y su cardinal —«142° SE»— porque un dibujo dice hacia dónde y un
   número dice cuánto, y en una cabina hacen falta los dos.

   El botón «N» de arriba a la izquierda sobra cuando está la rosa: dos mandos
   para enderezar el mismo mapa es una duda, no una comodidad. Se esconde solo
   cuando la rosa existe de verdad en el DOM.
   ══════════════════════════════════════════════════════════════════════════ */
.ir-rosa-caja {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

/* ── LA ROSA DE MANDO (23-sep-2026, `js/rosa.js` = `RosaDeMando` en la app)
   Un botón donde había cinco. 56 px como el botón de ubicación que
   sustituye. El ARO dice el estado: dorado siguiendo con rumbo arriba,
   blanco con norte arriba, a trazos con el mapa suelto (y entonces una
   marca dorada apunta hacia donde estás tú), y en la ojeada se va vaciando
   con los segundos. La aguja gira con el mapa y apunta al norte de verdad. */
.ir-rosa,
.map-controls .ir-rosa,
.map-controls .ir-carril-btn.ir-rosa {
  width: 56px; height: 56px; min-width: 56px; min-height: 56px; flex: 0 0 56px;
  padding: 0;
  border-radius: 50%;
  cursor: pointer;
  position: relative;
  flex: 0 0 auto;
}
.ir-rosa-svg { width: 56px; height: 56px; display: block; }
.ir-rosa-aro { fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round; }
.ir-rosa-aro-vista { fill: none; stroke: var(--gold, #D4A84B); stroke-width: 2.5; stroke-linecap: round; display: none; }
.ir-rosa-marca { fill: var(--gold, #D4A84B); }
.ir-rosa-n { fill: var(--error, #EF4444); }
.ir-rosa-s { fill: currentColor; fill-opacity: .75; }
.ir-rosa-letra { fill: currentColor; font-size: 8px; font-weight: 800; font-family: 'DM Sans', system-ui, sans-serif; }
.ir-rosa-aguja { transition: transform 350ms cubic-bezier(.22, .61, .36, 1); transform-box: view-box; }

.ir-rosa[data-estado="rumbo"] .ir-rosa-aro { stroke: var(--gold, #D4A84B); }
.ir-rosa[data-estado="norte"] .ir-rosa-aro { stroke: currentColor; }
.ir-rosa[data-estado="vista"] .ir-rosa-aro { stroke: var(--gold, #D4A84B); stroke-opacity: .25; }
.ir-rosa[data-estado="vista"] .ir-rosa-aro-vista { display: block; }
.ir-rosa[data-estado="suelto"] .ir-rosa-aro { stroke: var(--text-muted, #9FB0C3); stroke-dasharray: 6.5 6.6; }
.ir-rosa[data-estado="suelto"] .ir-rosa-n { fill: var(--text-muted, #9FB0C3); }
.ir-rosa[data-estado="suelto"] .ir-rosa-s { fill-opacity: .4; }
.ir-rosa[data-estado="suelto"] .ir-rosa-letra { fill: var(--text-muted, #9FB0C3); }
.ir-rosa[data-estado="sinPermiso"] .ir-rosa-aro { stroke: var(--warning, #F5A623); }
.ir-rosa[data-estado="sinPermiso"] .ir-rosa-aguja { display: none; }

/* El rótulo de debajo: «47° NE», «Norte · 47° NE», «Centrar», «Vista · 6 s».
   Una línea; en el carril del mapa cabe porque el carril es ancho por
   abajo (nada a su izquierda). 12 px, el suelo de la casa. */
.ir-rosa-texto {
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: calc(12px * var(--ir-letra, 1));
  font-weight: 700;
  line-height: 1.15;
  white-space: nowrap;
  color: var(--text, #F5F7FA);
  text-shadow: 0 0 4px #000, 0 0 4px #000;
  pointer-events: none;            /* se toca la rosa, no la etiqueta */
}
html[data-theme="light"] .ir-rosa-texto { color: #0A1B33; text-shadow: 0 0 4px #fff, 0 0 4px #fff; }

/* Navegando: el carril del mapa se esconde, y la rosa vive fija arriba a
   la izquierda, donde estaba el botón «N», que ya no hace falta. Tampoco
   «recentrar» ni «ver la ruta entera» de la pantalla de conducir: los tres
   son la rosa. */
/* Por debajo de la barra de giro, que ocupa los primeros ~210 px (el mismo
   sitio que tenía el «N»). */
.ir-rosa-nav { display: none; position: fixed; left: 12px; top: calc(env(safe-area-inset-top, 0px) + 232px); z-index: 2001; }
html.ir-navegando .ir-rosa-nav { display: flex; }
.ir-escritorio .ir-rosa-nav { left: calc(var(--rail-w, 240px) + 12px); }
body:has(.ir-rosa-nav) .ir-btn-norte { display: none !important; }
body:has(.ir-rosa-nav) #irNavVista,
body:has(.ir-rosa-nav) #irNavRecenter { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  .ir-rosa-aguja { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   6 · LA PIEL DE LOS TRES ICONOS DE NAVEGACIÓN
   Pedido del dueño, 20-sep-2026: «que se pueda modificar el icono de
   navegación por una flecha, un carro, un camión».

   El DIBUJO vive en `app/tools/iconos-de-navegacion.py`, que genera a la vez
   estos SVG y los PNG del mapa de la app nativa. Aquí va solo el color, y va
   en CSS a propósito: así el mismo dibujo cambia de piel con el tema sin
   volver a generar nada, y el marcador nunca se queda oro sobre oro.

   El HALO es lo que hace que el icono exista sobre cualquier mapa. Medido en
   la hoja de tamaños: oro sobre un campo de trigo de una foto de satélite,
   sin halo, desaparece — no «se ve peor», desaparece.
   ══════════════════════════════════════════════════════════════════════════ */
/* LOS COLORES VAN EN LITERAL, NO EN TOKENS. Y esto no es descuido.
   La primera versión usaba `var(--gold)` y `var(--ink)`, que es lo correcto
   para cualquier cosa que viva sobre una superficie de la app. Medido en
   producción con el tema claro puesto:

       --gold  →  #7A5E10   (oliva oscuro)
       --ink   →  #EEF1F6   (casi blanco)

   …y el camión salió marrón con las ruedas blancas. Los dos tokens se
   redefinen en tema claro para conservar contraste SOBRE UNA TARJETA BLANCA,
   y eso es lo que tienen que hacer. Pero este marcador no vive sobre una
   tarjeta: vive sobre un MAPA, que tiene su propia claridad y no es la del
   tema de la interfaz. Con el tema claro y el mapa satélite, el token dice
   «oscurece» y el mapa pide justo lo contrario.

   Así que la figura lleva el oro de la marca siempre, y lo único que cambia
   con el tema es el HALO, que es quien la despega del fondo. Es la misma
   lección que dejó escrita `ecosystem-core` después del botón a 2,59:1: un
   token con nombre de pigmento deja de ser cierto en cuanto hay dos temas. */
.ir-puck-halo    { fill: none; stroke: #FFFFFF; stroke-width: 3.4; stroke-linejoin: round; }
.ir-puck-placa   { fill: #FFFFFF; }
.ir-puck-figura  { fill: #D4A84B; }
.ir-puck-cristal { fill: #030D1C; fill-opacity: .82; }
.ir-puck-rueda   { fill: #030D1C; }

/* El cono de rumbo: un sector ancho que sale de debajo del vehículo, no un
   gorro encima. Se desvanece con un degradado porque un cono de opacidad
   plana tiene un borde que en la realidad no existe. */
.ir-cono-cerca { stop-color: #D4A84B; stop-opacity: .62; }
.ir-cono-lejos { stop-color: #D4A84B; stop-opacity: 0; }

/* En tema claro se invierte el halo: blanco sobre un mapa de papel no separa
   nada, y el marcador se funde con la carretera. */
html[data-theme="light"] .ir-puck-halo  { stroke: #0A1B33; }
html[data-theme="light"] .ir-puck-placa { fill: #0A1B33; }
html[data-theme="light"] .ir-cono-cerca { stop-color: #0A1B33; stop-opacity: .40; }
html[data-theme="light"] .ir-cono-lejos { stop-color: #0A1B33; stop-opacity: 0; }

/* ───── css/navegacion-pantalla.css ───── */
/* ==========================================================================
   IRON ROUTE — LA PANTALLA MIENTRAS SE NAVEGA

   MEDIDO EN EL TELÉFONO DEL DUEÑO, 13-08-2026, navegando de verdad hacia
   Linden. Con la navegación en marcha seguían encima de la pantalla:

     HEADER.search-header      0,0    390×72   ← justo debajo de la barra de giro
     BUTTON.fab-collapse       330,392  48×48
     DIV.map-controls          330,448  48×160
     BUTTON#fabNewTrip «+VIAJE» 281,624  94×48
     SECTION#quickTiles        0,678   390×88  ← tapado por la barra de abajo
     BUTTON.r24-arrow ×2       4/338,698 48×48
     el botón de «Iniciar sesión», el de reportar y el chincheta

   Textual del dueño: «donde dice la llegada, restante, distancia, tapa los
   botones, y el pin, esa cosa ahí, unos botones que están superpuestos,
   entonces no puede ser así».

   POR QUÉ PASABA. `js/navigation.js` tenía esto:

       document.querySelectorAll('.topbar, .bottom-nav, .status-pill,
         .compass, .speed-display, .alert-badge, #irDirectionsSheet')

   Seis de esas siete clases YA NO EXISTEN. `.topbar` se convirtió en
   `.search-header` hace meses; `.compass`, `.status-pill` y `.speed-display`
   se retiraron. O sea que la función escondía fantasmas y dejaba en pie todo
   lo que de verdad estorbaba — y no fallaba, porque `querySelectorAll` de algo
   que no existe devuelve una lista vacía tan tranquila. El mismo defecto de
   siempre: la comprobación pasa porque pregunta lo que no es.

   LA CURA, Y POR QUÉ ES DISTINTA. Aquí no se listan siete clases más: se
   esconde POR REGLA todo lo flotante de la aplicación mientras dura la
   navegación. Si mañana alguien añade otro botón al mapa, queda cubierto sin
   que nadie se acuerde de este archivo. Lo único que sobrevive es la pantalla
   de navegación, que es lo que se mira a 60 mph.
   ========================================================================== */

/* ══════════════════════════════════════════════════════════════════════════
   1 · FUERA TODO LO QUE NO SEA NAVEGAR
   ══════════════════════════════════════════════════════════════════════════ */
/* POR QUÉ LA CLASE VA DOS VECES. `css/state-cleanup-f4.css` declara
   `html.ir-trip-idle .quick-tiles { display: block !important }`. Misma
   especificidad que la nuestra y carga después, así que ganaba él y la fila de
   fichas seguía encima del mapa en plena navegación. Repetir la clase sube la
   especificidad sin depender del ORDEN de las hojas, que es lo que se rompe en
   cuanto alguien mueve una línea del index.
   (Y sí: mientras se navega el viaje no está «idle». Esa clase la pone otro
   módulo; corregirla ahí es otra tarea, y hasta entonces esto no depende de
   ella.) */
html.ir-navegando.ir-navegando .search-header,
html.ir-navegando.ir-navegando .bottom-nav,
html.ir-navegando.ir-navegando .quick-tiles,
/* Y por ID, porque `state-cleanup-f4.css` también escribe `#quickTiles`: un
   ID pesa más que tres clases, así que la versión por clase perdía igual.
   Contra un ID solo gana otro ID. */
html.ir-navegando.ir-navegando #quickTiles,
/* Aquí decía `.r24-arrow`. Eran las flechas DUPLICADAS del carrusel y ya no
   existen: se retiraron de `js/bundle-r5.js` porque se pintaban encima de la
   primera y la última tarjeta. Un selector que ya no casa con nada no da
   error —se queda callado— y el siguiente que lea esta lista creerá que las
   flechas están cubiertas. Se nombra la que sí existe. */
html.ir-navegando.ir-navegando .qa-arrow,
html.ir-navegando.ir-navegando .map-controls,
html.ir-navegando.ir-navegando .ir-carril-colapso,
html.ir-navegando.ir-navegando .fab-collapse-toggle,
html.ir-navegando.ir-navegando #fabNewTrip,
html.ir-navegando.ir-navegando .fab,
html.ir-navegando.ir-navegando .rr-lanzar,
html.ir-navegando.ir-navegando .ir-alert-fab,
html.ir-navegando.ir-navegando .ir-cuenta-boton,
html.ir-navegando.ir-navegando #authBtn,
html.ir-navegando.ir-navegando .ir-atrib-boton,
html.ir-navegando.ir-navegando .irc-capas,
html.ir-navegando.ir-navegando #irDirectionsSheet,
html.ir-navegando.ir-navegando .ir-pretrip-banner,
/* Los tres que aparecieron al medir con la prueba puesta: la barra de voz de
   `voice-nav.js`, el velocímetro suelto de `speed-widget` y el botón de
   iniciar sesión. Los dos primeros son DUPLICADOS —la pantalla de navegación
   ya trae su botón de voz y su velocímetro—, y dos velocímetros distintos en
   la misma pantalla es peor que ninguno. El de iniciar sesión no se toca
   conduciendo. */
html.ir-navegando.ir-navegando #voice-nav-bar,
html.ir-navegando.ir-navegando #speed-widget,
html.ir-navegando.ir-navegando #ir-r4-auth-btn {
  display: none !important;
}

/* EL AVISO DE «SIN SEÑAL» SÍ SE QUEDA, y es de los pocos que se quedan.
   Saber que el mapa que estás viendo sale de la caché y no de la red cambia
   lo que haces en la siguiente salida. Lo que se corrige es DÓNDE: se baja
   por debajo de la barra de giro para que no se monte con ella. */
html.ir-navegando.ir-navegando #offline-mode-banner {
  top: auto !important;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 172px) !important;
  left: 12px !important;
  right: 12px !important;
  z-index: 12;
}

/* EL BOTÓN DE ORIENTACIÓN SÍ SE OFRECE EN MARCHA, y esta línea decía lo
   contrario. Estaba escrito así: «en navegación el norte arriba no se
   ofrece, porque el mapa DEBE ir en el sentido de la marcha». El dueño pidió
   justo lo otro —«pero tiene que ser en dos sentidos»— y además, escondido,
   el conductor que acababa en norte arriba no tenía forma de volver al
   sentido de la marcha. Lo pinta `css/navegacion-rumbo.css`, más abajo de la
   barra de giro y a 56 px. */

/* Las hojas inferiores no se abren durante la navegación, pero si una quedó
   abierta al arrancar, se cierra: media pantalla de mapa tapada a 60 mph no
   es una hoja, es un accidente. */
html.ir-navegando.ir-navegando .bottom-sheet.open,
html.ir-navegando.ir-navegando .bottom-sheet.abierta { transform: translateY(100%) !important; }

/* La atribución de OpenStreetMap SÍ se queda — la licencia lo exige. Se baja
   al hueco que deja la barra de abajo para que no compita con nada.

   ── LOS 10 px QUE NO PODÍAN QUEDARSE (corregido el 7-sep-2026) ────────────

   Aquí ponía `font-size: calc(10px * var(--ir-letra, 1)) !important`. El suelo tipográfico de la casa son
   12 px (`--font-min`), y en la pantalla de navegar el suelo NO son 12 sino
   **20** (`--nav-font`, `css/tokens.css:223`), porque es la única pantalla que
   se lee conduciendo y a un brazo de distancia. Diez píxeles ahí era letra a
   la mitad del suelo general y a la mitad exacta del de navegación.

   El token existía desde siempre y no lo usaba nadie: `--nav-font` no aparecía
   en una sola regla del árbol servido. Aquí se estrena.

   Y no rompe la composición porque esta línea NO es una caja de ancho fijo:
   Leaflet la pone abajo a la derecha, se ajusta sola al texto y el hueco que
   tiene debajo —96 px más el borde seguro— lo dejó libre la barra de abajo.
   Se le pone `max-width` y `line-height` para que, si el proveedor alarga la
   atribución, se parta en dos líneas dentro de ese hueco en vez de empujar
   nada. Medido en el navegador a 390×844 y a 1440×900: no toca la barra de
   abajo ni desborda a lo ancho. */
html.ir-navegando.ir-navegando .leaflet-control-attribution {
  bottom: calc(env(safe-area-inset-bottom, 0px) + 96px) !important;
  font-size: var(--nav-font, 20px) !important;
  line-height: 1.25;
  max-width: min(100%, 60vw);
  opacity: .75;
}

/* ══════════════════════════════════════════════════════════════════════════
   2 · LA BARRA DE GIRO — Y LA SEGUNDA MANIOBRA

   Textual del dueño: «tiene que decirte, en 1.1 millas tiene que salir a la
   derecha, pero ahí mismo tiene que decirte abajito, en letra pequeña, cuál
   es la próxima, porque a veces te llega la salida y ahí mismo hay otra
   entrada y ya uno se vuelve loco, uno va guiando a 50 millas por hora».

   Tiene toda la razón y es un problema real del oficio: en un enlace de
   autopista las dos maniobras caen a menos de 300 pies. Quien solo ve la
   primera toma la salida buena y falla la segunda, y con 53 pies detrás
   rectificar cuesta millas.

   La fila «LUEGO» dice la maniobra siguiente Y a qué distancia de la primera.
   Cuando ese hueco baja de 800 pies —donde de verdad se lía— la fila se
   enciende en ámbar y lo dice con todas las letras: «enseguida».
   ══════════════════════════════════════════════════════════════════════════ */

/* Opaca, no en degradado. Antes era `rgba(3,13,28,.95)` desvaneciéndose al
   60 %: por debajo se leía «Buscar dirección o lugar…» de la cabecera, y dos
   textos superpuestos no se leen ni de cerca ni parado. */
html.ir-navegando.ir-navegando .ir-nav-turn-bar {
  background: var(--ink, #030D1C) !important;
  border-bottom: 1px solid var(--line, #1E3A5F);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .35);
  align-items: flex-start;
  flex-wrap: wrap;
}

html[data-theme="light"].ir-navegando .ir-nav-turn-bar {
  background: #0A1B33 !important;   /* la barra de giro NO se aclara: es la
                                       única zona que se mira en marcha, y el
                                       contraste alto gana al sol de frente */
  border-bottom-color: rgba(255, 255, 255, .14);
}

/* La fila de la segunda maniobra */
.ir-nav-then {
  display: none;
  flex-basis: 100%;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid rgba(255, 255, 255, .14);
  color: var(--text-muted, #9FB0C3);
}
html.ir-navegando.ir-navegando .ir-nav-then.ir-hay { display: flex; }

.ir-nav-then-et {
  font-size: calc(11px * var(--ir-letra, 1));
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted, #9FB0C3);
  flex: 0 0 auto;
}
.ir-nav-then-ic { display: flex; flex: 0 0 auto; color: var(--text, #F5F7FA); }
.ir-nav-then-tx {
  /* 13 px y no menos: el suelo de la casa son 12, y esto se lee conduciendo.
     Si no cabe, se corta la calle — nunca la maniobra, que va delante. */
  font-size: calc(13px * var(--ir-letra, 1));
  line-height: 1.25;
  color: var(--text, #F5F7FA);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Las dos maniobras pegadas: el caso que da el problema. */
.ir-nav-then.ir-pegada {
  border-top-color: var(--warning, #F5A623);
}
.ir-nav-then.ir-pegada .ir-nav-then-et,
.ir-nav-then.ir-pegada .ir-nav-then-ic { color: var(--warning, #F5A623); }

/* ══════════════════════════════════════════════════════════════════════════
   3 · LA BARRA DE ABAJO
   Se apoya en el borde inferior de verdad, contando la zona segura del
   iPhone. En la captura del dueño quedaba montada sobre la fila de fichas —
   que ahora ya no está— pero además le faltaba el respiro de abajo.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-navegando.ir-navegando .ir-nav-bottom {
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
  background: var(--ink, #030D1C);
  border-top: 1px solid var(--line, #1E3A5F);
}
/* EN TEMA CLARO EL CROMO DE NAVEGACIÓN NO SE ACLARA.

   Se probó a ponerlo blanco y salió esto, medido: «19:37», «40m» y «30.2 mi»
   quedaban en dorado sobre blanco — 2,6:1, que es el mismo contraste que este
   proyecto ya declaró inservible para texto. Los números que dicen a qué hora
   llegas eran invisibles.

   Y el arreglo no es teñir cuatro reglas: es que en marcha el mapa va claro
   —de día se lee mejor— pero las barras van oscuras, que es lo que hace
   cualquier navegador serio. Una franja oscura con cifras claras aguanta el
   sol de frente; blanco sobre blanco no. Así que aquí el tema claro cambia el
   MAPA, no el cromo. */
html[data-theme="light"].ir-navegando .ir-nav-bottom {
  background: #030D1C;
  border-top-color: rgba(255, 255, 255, .14);
}
html[data-theme="light"].ir-navegando .ir-nav-bot-val   { color: #F5F7FA; }
html[data-theme="light"].ir-navegando .ir-nav-bot-label { color: #9FB0C3; }

html[data-theme="light"].ir-navegando .ir-nav-speed {
  background: #030D1C;
  border-color: rgba(255, 255, 255, .14);
}
html[data-theme="light"].ir-navegando .ir-nav-speed-num  { color: #E8C468; }
html[data-theme="light"].ir-navegando .ir-nav-speed-unit { color: #9FB0C3; }

/* ══════════════════════════════════════════════════════════════════════════
   4 · EL VELOCÍMETRO Y LOS DOS BOTONES QUE QUEDAN
   Se colocan respecto a la barra de abajo, no respecto al borde: si la barra
   crece —y crece, porque la zona segura del iPhone la empuja— lo de encima
   tiene que subir con ella o se monta.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-navegando.ir-navegando .ir-nav-speed {
  bottom: calc(env(safe-area-inset-bottom, 0px) + 104px);
}
html.ir-navegando.ir-navegando .ir-nav-search-btn {
  bottom: calc(env(safe-area-inset-bottom, 0px) + 104px);
}
html.ir-navegando.ir-navegando .ir-nav-recenter {
  bottom: calc(env(safe-area-inset-bottom, 0px) + 168px);
}

@media (prefers-reduced-motion: reduce) {
  .ir-nav-then { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   5 · LO QUE NO CABÍA

   MEDIDO en el teléfono del dueño y reproducido aquí a 390 px:

     .ir-nav-end-btn      x 305  w 102  →  borde derecho en 407  ·  17 px FUERA
     .ir-nav-turn-street  ancho 220, texto 247  →  «Wood Avenue/C…» cortado
                                                    teniendo 232 px libres

   Lo del botón es lo que fotografió: «Finalizar» a medias contra el borde. Y
   lo de la calle es peor de lo que parece, porque el nombre de la calle es
   justamente lo que hay que leer para saber si es ESTA salida o la siguiente.

   Las dos causas son la misma: medidas fijas —`max-width: 220px`, anchos por
   `flex: 1` sin `min-width: 0`— escritas para una pantalla concreta. Aquí se
   reparte lo que HAY: el botón pide lo suyo, las tres cifras se reparten el
   resto, y ninguna puede empujar a las demás fuera.
   ══════════════════════════════════════════════════════════════════════════ */
html.ir-navegando.ir-navegando .ir-nav-bottom {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr auto;
  align-items: center;
  gap: 8px;
  padding-left: max(env(safe-area-inset-left, 0px), 12px);
  padding-right: max(env(safe-area-inset-right, 0px), 12px);
}
html.ir-navegando.ir-navegando .ir-nav-bot-item {
  min-width: 0;          /* sin esto una cifra larga empuja al resto fuera */
  text-align: center;
}
html.ir-navegando.ir-navegando .ir-nav-bot-val,
html.ir-navegando.ir-navegando .ir-nav-bot-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 17 px y cifras de ancho fijo. A 19 px «30.2 mi» pedía 89 px en una columna
   de 84 y salía «30.…» — la distancia que falta, cortada. Y `tabular-nums`
   evita que la hora baile de ancho al pasar de 19:38 a 19:41, que de reojo
   se lee como un parpadeo. */
html.ir-navegando.ir-navegando .ir-nav-bot-val {
  font-size: calc(17px * var(--ir-letra, 1));
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
html.ir-navegando.ir-navegando .ir-nav-bot-label { font-size: calc(11px * var(--ir-letra, 1)); line-height: 1.2; }
html.ir-navegando.ir-navegando .ir-nav-end-btn {
  min-width: 0;
  width: auto;
  white-space: nowrap;
  padding: 0 14px;
  min-height: 44px;
}

/* La calle usa TODO el hueco que queda hasta el botón de voz. */
html.ir-navegando.ir-navegando .ir-nav-turn-info { min-width: 0; }
html.ir-navegando.ir-navegando .ir-nav-turn-street {
  max-width: none;
  width: 100%;
}

/* El suelo de la casa son 48 px de objetivo táctil, y el de la voz medía 44.
   En una cabina con baches, cuatro píxeles son un fallo de puntería. */
html.ir-navegando.ir-navegando .ir-nav-voice-btn {
  width: 48px;
  height: 48px;
  flex: 0 0 48px;
}

/* Y si aun así no cabe, dos líneas antes que puntos suspensivos: «Turn right
   onto Wood Avenue/C…» no dice si es ESTA salida o la de después, que es
   justo la duda que se tiene a 50 mph. Dos líneas caben de sobra en la barra;
   a partir de ahí sí se corta. */
html.ir-navegando.ir-navegando .ir-nav-turn-street {
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  line-height: 1.22;
}

/* Teléfonos de 360 px —el Android barato que lleva medio oficio— siguen
   cortando por 4 px: «130.2» pide 78 en una columna de 74. Se recorta el aire,
   no la cifra: las tres columnas ganan 8 px cada una quitándoselos al hueco
   entre ellas y al relleno del botón. */
@media (max-width: 375px) {
  html.ir-navegando.ir-navegando .ir-nav-bottom {
    gap: 4px;
    padding-left: max(env(safe-area-inset-left, 0px), 8px);
    padding-right: max(env(safe-area-inset-right, 0px), 8px);
  }
  html.ir-navegando.ir-navegando .ir-nav-end-btn { padding: 0 10px; font-size: calc(14px * var(--ir-letra, 1)); }
  html.ir-navegando.ir-navegando .ir-nav-bot-val { font-size: calc(16px * var(--ir-letra, 1)); }
}

/* El botón de volver al centro, con el suelo táctil de la casa. Medía 44 px y
   se pulsa con el camión en marcha, que es el peor momento para fallar la
   puntería. Y va encima del velocímetro, en el lado del pulgar. */
html.ir-navegando.ir-navegando .ir-nav-recenter {
  width: 48px;
  height: 48px;
  border-radius: 24px;
}

/* ══════════════════════════════════════════════════════════════════════════
   6 · EL CARRIL DE CONDUCCIÓN

   Corregido tras el aviso del dueño: «durante la navegación eliminaste muchos
   botones; recuerda que eso la persona lo va a utilizar mientras conduce, por
   lo tanto debe ser de fácil acceso».

   Tenía razón. Al limpiar la pantalla se fue con lo que estorbaba lo que hace
   falta. Pero la respuesta no es devolver la aplicación entera encima del mapa
   —de ahí veníamos, con los botones montados unos sobre otros— sino un carril
   propio con cinco cosas y solo cinco, las que se usan rodando.

   56 px y no 48. El suelo de 48 de la casa está pensado para el dedo parado.
   Con el camión en marcha, baches y una sola mano, el objetivo tiene que ser
   mayor: 56 con 12 px de separación, para que fallar no signifique pulsar el
   de al lado. Pegados al borde derecho, a la altura del pulgar.
   ══════════════════════════════════════════════════════════════════════════ */
.ir-nav-carril {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-46%);   /* un pelo por encima del centro: abajo está
                                    la barra de datos y el velocímetro */
  display: none;
  flex-direction: column;
  gap: 12px;
  z-index: 14;
}
html.ir-navegando.ir-navegando .ir-nav-carril { display: flex; }

.ir-nav-cb {
  width: 56px;
  height: 56px;
  border-radius: 28px;
  border: 1px solid rgba(255, 255, 255, .16);
  background: rgba(3, 13, 28, .92);
  color: #F5F7FA;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 3px 14px rgba(0, 0, 0, .38);
  -webkit-tap-highlight-color: transparent;
}
.ir-nav-cb .ir-ic-pre { margin: 0; vertical-align: 0; }
.ir-nav-cb:active { background: rgba(16, 41, 74, .96); }
.ir-nav-cb:focus-visible { outline: 3px solid var(--accent-fill, #D4A84B); outline-offset: 2px; }

/* «Reportar» va en ámbar y arriba del todo. No es decoración: es el que más
   se pulsa en marcha y el único con prisa — un accidente se reporta cuando se
   ve, no en la siguiente parada. */
.ir-nav-cb-reportar {
  background: var(--warning, #F5A623);
  color: #1A1200;
  border-color: rgba(0, 0, 0, .2);
}
.ir-nav-cb-reportar:active { background: #E0940F; }

/* Puesto = el ajuste está activo. Se marca con el borde y el color del
   acento, no solo con un tono de fondo: en pantalla al sol un cambio de fondo
   del 8 % no se ve. */
.ir-nav-cb[aria-pressed="true"] {
  border-color: var(--accent-fill, #D4A84B);
  color: var(--accent-fill, #D4A84B);
}
.ir-nav-cb-reportar[aria-pressed="true"] { color: #1A1200; }

/* En claro el carril tampoco se aclara, por lo mismo que la barra de giro: es
   cromo de conducción y el contraste alto gana al sol de frente. */
html[data-theme="light"].ir-navegando .ir-nav-cb {
  background: rgba(3, 13, 28, .92);
  color: #F5F7FA;
}

/* El botón de buscar de la esquina ya no hace falta: su trabajo lo hace el
   carril, y dos botones que abren lo mismo en la misma pantalla es de lo que
   se venía. Se queda en el DOM porque el carril lo pulsa. */
html.ir-navegando.ir-navegando .ir-nav-search-btn { display: none !important; }

/* Pantallas bajas —un teléfono en horizontal en el parabrisas— no tienen sitio
   para cinco de 56 en columna. Se aprieta a 48 y 8, que sigue siendo el suelo
   de la casa, antes que dejar uno fuera de la pantalla. */
@media (max-height: 700px) {
  .ir-nav-carril { gap: 8px; transform: translateY(-50%); }
  .ir-nav-cb { width: 48px; height: 48px; border-radius: 24px; }
}

/* ── EL BOTÓN «PUESTO» NO PUEDE GRITAR ─────────────────────────────────────
   `css/button-states-fix.css` lleva una regla UNIVERSAL:

       button[aria-pressed="true"] { background: gradiente dorado !important;
                                     ... }
       button[aria-pressed="true"]::after { content: "✓"; ... }

   O sea que CUALQUIER botón con `aria-pressed="true"` sale relleno de dorado
   con una insignia de visto en la esquina. Se ve en la primera captura del
   dueño: el botón de la voz salía como una pastilla dorada con un tic, y ese
   era el estado NORMAL —la voz encendida—.

   Dos correcciones, y la primera no es de estilo:

   1. Lo que se marca ahora es SILENCIADO, no «voz encendida». Marcar lo normal
      deja un aviso permanente que se aprende a ignorar; lo que hay que ver de
      un vistazo es cuándo está callada.

   2. Y aquí el estado puesto se dice con el borde y el color del trazo, no
      rellenando el botón. En un carril de cinco redondas iguales, una rellena
      de dorado con un visto encima se lee como «alerta», no como «activado» —
      y en marcha eso cuesta una mirada de más.

   No se toca la regla global: gobierna controles de otras pantallas que hoy
   dependen de ella, y cambiarla desde aquí sería arreglar esto rompiendo
   aquello. Se acota a este carril, que es lo que se está diseñando. */
html.ir-navegando.ir-navegando .ir-nav-cb[aria-pressed="true"],
html.ir-navegando.ir-navegando .ir-nav-voice-btn[aria-pressed="true"] {
  background: rgba(3, 13, 28, .92) !important;
  color: var(--accent-fill, #D4A84B) !important;
  box-shadow: 0 3px 14px rgba(0, 0, 0, .38) !important;
  border-color: var(--accent-fill, #D4A84B) !important;
}
html.ir-navegando.ir-navegando .ir-nav-cb[aria-pressed="true"]::after,
html.ir-navegando.ir-navegando .ir-nav-voice-btn[aria-pressed="true"]::after {
  content: none !important;
}

/* Silenciada: ámbar, que es el color de «ojo con esto» en todo el producto.
   No rojo — la voz callada no es un error, es una decisión que conviene
   recordar. */
html.ir-navegando.ir-navegando #irNavVoz2[aria-pressed="true"],
html.ir-navegando.ir-navegando #irNavVoice[aria-pressed="true"] {
  color: var(--warning, #F5A623) !important;
  border-color: var(--warning, #F5A623) !important;
}

/* Y el de la barra de giro, que heredaba el mismo relleno. */
html.ir-navegando.ir-navegando .ir-nav-voice-btn {
  background: rgba(255, 255, 255, .08) !important;
  color: #F5F7FA;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 24px;
}

/* «PARAR AQUÍ»: AÑADIR LA PARADA SIN TERMINAR EL VIAJE.
   El resultado de la búsqueda solo movía el mapa. El botón va DENTRO de la
   fila, a la derecha, porque la decisión —«¿me paro aquí?»— se toma leyendo
   el nombre y las millas que faltan, no en otra pantalla. 48 px de alto: se
   pulsa con el camión rodando. */
.ir-nav-sp-anadir {
  flex: 0 0 auto;
  min-height: 48px;
  margin-left: 8px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--accent, #F58220);
  background: transparent;
  color: var(--accent, #F58220);
  font-size: calc(13px * var(--ir-letra, 1));
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
.ir-nav-sp-anadir:disabled { opacity: .6; cursor: default; }
.ir-nav-sp-item { align-items: center; }

/* ───── css/botones-en-armonia.css ───── */
/* ==========================================================================
   IRON ROUTE — LOS BOTONES, EN ARMONÍA

   Pedido por el dueño después de cuadrar los de la tarjeta del buscador:
   «basado en eso evalúame todos los botones y arregla lo que tengas que
   arreglar para que todo se vea en armonía».

   ── LO QUE SE MIDIÓ ────────────────────────────────────────────────────────

   99 botones visibles, recorriendo las nueve entradas del menú y los paneles
   del mapa, a 390 px. De ellos, 58 llevan dibujo y rótulo. Y la conclusión no
   fue la que esperaba: la mayoría ESTÁ BIEN.

   Los del menú lateral: nueve filas de 280×50, dibujo en x = 20 con desvío
   vertical 0, texto arrancando en x = 58. Columna común, las nueve. Perfectas.
   Los apilados —fichas, pestañas de la barra inferior, horas de servicio,
   combustible, flota— tienen el dibujo ARRIBA del rótulo, con desvío idéntico
   dentro de cada grupo y el texto centrado. También perfectos.

   ── Y AQUÍ EL INSTRUMENTO VOLVIÓ A MENTIR ──────────────────────────────────

   Mi primer barrido marcó como defecto las nueve filas del menú «porque el
   texto no está centrado en el botón». No lo está, y no debe estarlo: en una
   lista de nueve entradas, centrar cada rótulo lo vuelve ilegible — el ojo
   necesita una columna común por donde bajar. El criterio estaba mal, no la
   pantalla.

   Y marcó «Reportar» como texto cortado. También falso: es un círculo de
   56 px de solo dibujo, y su rótulo es texto solo-para-lectores con `clip`,
   que por definición mide 1×1. Acusar eso es acusar la accesibilidad.

   Van tres veces en esta auditoría que el instrumento falla en la dirección
   cómoda. Por eso `tests/botones-en-armonia.mjs` distingue las TRES FAMILIAS
   en vez de exigirle a todas lo mismo:

     · FILA de lista  → dibujo a la izquierda, rótulo a la izquierda, todos
                        arrancando en la MISMA x. Nunca centrado.
     · APILADO        → dibujo arriba, rótulo debajo, los dos centrados.
     · ACCIÓN         → dibujo anclado a la izquierda y centrado en la altura;
                        rótulo centrado al 100 % del botón.

   ── LO QUE SÍ ESTABA MAL, Y ES LO QUE ARREGLA ESTA HOJA ────────────────────

     `.btn-primary` («Calcular Ruta»)   texto a +15 px del centro
     `.fab`         («+ Viaje»)         texto a +15 px del centro
     `.rp-alt-tab`  (3 pestañas)        61 · 73 · 58 px — tres anchos distintos

   Tres sitios de noventa y nueve. Ese es el resultado honesto.
   ========================================================================== */

/* ══════════════════════════════════════════════════════════════════════════
   1 · EL PATRÓN «BOTÓN DE ACCIÓN»

   El mismo que se fijó en la tarjeta del buscador, y por la misma razón: con
   `text-align: center` sobre el botón NO se centra la palabra, se centra el
   conjunto «dibujo + palabra». El texto queda corrido a la derecha tanto como
   mida el dibujo — medido, 15 px en los dos.

   El dibujo sale del flujo y se ancla; el rótulo ocupa el ancho entero y se
   centra en él. Reserva simétrica a los dos lados: con relleno solo a la
   izquierda, el texto quedaría centrado respecto al hueco y no respecto al
   botón, que es el mismo defecto disimulado.
   ══════════════════════════════════════════════════════════════════════════ */
/* ── Y AQUÍ ROMPÍ EL BOTÓN DE «+ VIAJE». Lo vio el dueño en las capturas ──
   Este bloque llevaba `position: relative` para los dos, porque el dibujo va
   `absolute` y necesita un ancestro posicionado.

   `.btn-primary` sí lo necesitaba. `.fab` NO: ya era `position: fixed`, que
   también es un ancestro posicionado. Y al pisárselo con `relative` le arranqué
   el anclaje: en un elemento fijo, `right: 16px` lo pega a 16 px del borde
   derecho de la pantalla; en uno relativo, la misma línea significa «córrete
   16 px hacia la IZQUIERDA de donde te tocaba».

   Medido después: `left: -16px`, o sea el botón **16 px fuera de la pantalla**.

   Es exactamente el defecto que llevo siete rondas señalando: un arreglo que
   rompe otra cosa porque no comprobé el estado del que partía. Y mi propia
   prueba de armonía no lo vio, porque medía cada botón RESPECTO A SÍ MISMO
   —dibujo centrado, rótulo centrado— y nunca preguntó dónde está el botón en
   la pantalla. Un botón puede estar perfectamente cuadrado por dentro y a la
   vez medio fuera del teléfono.

   Ahora `position` solo se toca donde falta. Y la prueba pregunta también por
   la pantalla. */
.btn-primary {
  position: relative;
}

.btn-primary,
#fabNewTrip.fab {
  display: flex;
  align-items: center;
  justify-content: center;
  padding-left: 44px;
  padding-right: 44px;
}

.btn-primary > svg,
#fabNewTrip.fab > svg {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  flex: 0 0 auto;
  pointer-events: none;
}

.btn-primary > span,
#fabNewTrip.fab > .fab-label {
  width: 100%;
  text-align: center;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* El «+ Viaje» es estrecho —94 px— y con 44 de reserva a cada lado no le
   quedaría sitio al rótulo. Ahí la reserva se ajusta al dibujo real: 28 de
   margen izquierdo más los 28 del signo, y el mismo aire a la derecha. */
#fabNewTrip.fab {
  padding-left: 40px;
  padding-right: 40px;
}
#fabNewTrip.fab > svg { left: 10px; }

/* ══════════════════════════════════════════════════════════════════════════
   2 · LAS TRES PESTAÑAS DEL PLANIFICADOR

   Medidas: 61 · 73 · 58 px. Tres hermanas de la misma fila con tres anchos
   distintos, porque cada una se dimensionaba por su rótulo — «Sin Peajes» es
   más largo que «Alt. Ruta».

   Importa más de lo que parece: la pestaña activa lleva subrayado, y con
   anchos distintos el subrayado cambia de tamaño al cambiar de pestaña. Lo
   que el ojo lee entonces no es «he cambiado de pestaña» sino «se ha movido
   algo», que es medio segundo perdido cada vez.

   Tres columnas iguales. `minmax(0, 1fr)` y no `1fr` a secas, porque un `1fr`
   no baja del ancho mínimo de su contenido y el defecto volvería por la otra
   puerta en cuanto un rótulo creciera al traducirse.
   ══════════════════════════════════════════════════════════════════════════ */
.rp-alt-tabs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
}
.rp-alt-tabs.rp-alt-tabs-hidden { display: none; }

.rp-alt-tab {
  display: flex;
  flex-direction: column;   /* dibujo arriba, rótulo debajo: es un APILADO,
                               y así se queda — solo se igualan los anchos */
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 0;
  padding-left: 4px;
  padding-right: 4px;
}
.rp-alt-tab .rp-alt-tab-label {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-alt-tab .rp-alt-tab-icon { display: flex; }
.rp-alt-tab .rp-alt-tab-icon .ir-ic-pre { margin: 0; vertical-align: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   3 · EL DIBUJO DEL PASO A PASO

   `js/route-planner.js` llevaba una TERCERA tabla de maniobras —flechas
   tipográficas mezcladas con emoji— así que la misma maniobra salía con tres
   dibujos distintos según la pantalla desde la que se mirara. Ahora pide los
   de `js/iconos.js` como todo lo demás; esto solo los coloca.
   ══════════════════════════════════════════════════════════════════════════ */
.rp-step-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.rp-step-icon .ir-ic-pre { margin: 0; vertical-align: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   4 · LA COLUMNA DE LOS CHIPS DE MAPA BASE

   Medido: «Calle» arrancaba en x = 44 y «Satélite» y «Relieve» en x = 43. Un
   píxel. Invisible por separado, y sin embargo es exactamente lo que hace que
   una lista se lea «a saltos» sin que nadie sepa decir por qué.

   La causa es de las que solo salen midiendo: `.ir-base-ic` conserva
   `font-size: calc(18px * var(--ir-letra, 1))` de cuando su contenido era un emoji, y el dibujo que lleva
   dentro trae `margin-right: .4em`. Un margen en `em` sobre 18 px son 7,2 px
   —un valor fraccionario— y el navegador lo redondea distinto según dónde
   caiga el borde del elemento anterior. De ahí el píxel que baila.

   Se le da al hueco del dibujo un ancho FIJO en píxeles. Así la columna del
   texto sale del mismo número siempre, mida lo que mida el dibujo. */
.ir-base-chip .ir-base-ic {
  width: 22px;
  min-width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0;              /* ya no hay emoji dentro: el `em` no debe existir */
}
.ir-base-chip .ir-base-ic .ir-ic-pre { margin: 0; vertical-align: 0; }

/* Y el píxel resultó ser real, no un redondeo. Medido con decimales: el dibujo
   de «Calle» empieza en x = 14 y el de «Satélite» y «Relieve» en x = 13. El
   texto solo lo hereda.

   La causa: el chip elegido se marca con `border-width: 2px`. Un borde más
   grueso EMPUJA su propio contenido un píxel hacia dentro — así que la fila
   entera se descuadra en cuanto eliges una capa, y se vuelve a cuadrar cuando
   eliges otra. El movimiento no es del chip que tocas: es del que dejas.

   El estado no se marca con grosor, se marca con una sombra interior. Pinta
   exactamente igual —dos píxeles de anillo por dentro del borde— pero no ocupa
   sitio en el flujo, así que el contenido no se mueve. */
.ir-base-chip.ir-base-on {
  border-width: 1px;
  box-shadow: inset 0 0 0 2px currentColor;
}

/* ══════════════════════════════════════════════════════════════════════════
   5 · LOS BOTONES DE HORAS, COMBUSTIBLE Y FLOTA

   VISTO EN LAS CAPTURAS, no en una medida: el dibujo quedaba arriba a la
   IZQUIERDA y el rótulo debajo, centrado. No es un apilado —en un apilado de
   verdad el dibujo va centrado encima— sino un botón en línea al que el texto
   se le cae a la segunda línea por falta de sitio. Se ve descuidado, y son los
   botones que más se tocan de los cuatro paneles de dinero.

   Y mi prueba los daba por buenos porque los clasificó como «apilados»: al
   medir vio el dibujo por encima del texto y no preguntó si el dibujo estaba
   CENTRADO. Cuarta vez que el criterio es lo que falla.

   Se les aplica el patrón de acción sin tocar una línea de JavaScript. El
   truco está en que estos rótulos son nodos de texto sueltos, sin envoltorio:
   dentro de un contenedor flex, un texto suelto se convierte en un elemento
   flex anónimo — así que `justify-content: center` lo centra igual que si
   llevara su `<span>`. Y con el dibujo fuera del flujo, se centra respecto al
   BOTÓN entero, que es lo que se pidió.
   ══════════════════════════════════════════════════════════════════════════ */
.hos-btn,
.fuel-btn,
.fuel-filter-btn,
.trk-btn,
.trk-status-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  padding-left: 34px;
  padding-right: 34px;
  text-align: center;
  line-height: 1.2;
}

.hos-btn > .ir-ic-pre,
.fuel-btn > .ir-ic-pre,
.fuel-filter-btn > .ir-ic-pre,
.trk-btn > .ir-ic-pre,
.trk-status-btn > .ir-ic-pre {
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%) !important;
  margin: 0;
  vertical-align: 0;
  pointer-events: none;
}

/* «Reiniciar» y los de ancho completo tienen sitio de sobra: se les da más
   aire al dibujo para que no quede pegado al borde de un botón de 350 px. */
.hos-btn.hos-btn-reset > .ir-ic-pre,
.trk-btn > .ir-ic-pre { left: 16px; }

/* ══════════════════════════════════════════════════════════════════════════
   6 · EL CAMIÓN, VISIBLE EN LA PANTALLA DONDE SE CALCULA LA RUTA

   Pedido por el dueño: «que uno pueda modificar el tipo de camión ahí mismo,
   para no estar teniendo problemas los que ingresen por primera vez».

   El desplegable decía «Configuración del Vehículo» y nada más. Cerrado, sin
   una pista de qué camión iba a usar el cálculo. Ahora el resumen lleva
   debajo, en pequeño, con qué se está ruteando — y si nadie lo ha configurado
   todavía, lo dice en ámbar en vez de enseñar los valores por defecto como si
   alguien los hubiera elegido.
   ══════════════════════════════════════════════════════════════════════════ */
.vehicle-config > summary {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-height: 48px;
  justify-content: center;
  cursor: pointer;
}
.ir-camion-resumen {
  display: block;
  font-size: calc(12.5px * var(--ir-letra, 1));
  font-weight: 500;
  line-height: 1.3;
  color: var(--text-muted, #9FB0C3);
  font-variant-numeric: tabular-nums;
}
/* Sin configurar: ámbar. No es un error —la app funciona con el camión seguro
   por defecto— pero sí es lo que conviene mirar antes de fiarse de la ruta. */
.ir-camion-resumen.ir-camion-sin { color: var(--warning, #F5A623); }

/* ══════════════════════════════════════════════════════════════════════════
   «INDICACIONES PASO A PASO0▼» — LA FILA QUE NO TENÍA NI UNA REGLA

   Reportado por el dueño mirando la pantalla del planificador en escritorio:
   «tiene errores de escritura, eso está mal». Y no era un error de escritura:
   era que esta fila la construía `js/route-planner.js` con tres `<span>`
   seguidos y **no existía una sola regla de CSS para ella** en ninguna hoja.
   Sin reglas, tres `<span>` en línea se pegan uno a otro:

       [icono] Indicaciones paso a paso  +  0  +  ▼   →   «…paso a paso0▼»

   Tres cosas mal a la vez, y las tres se leen como una falta de ortografía:
   un contador a cero que no cuenta nada, un galón tipográfico haciendo de
   icono, y ningún espacio entre nada.

   El rótulo ocupa el hueco libre, el contador solo aparece cuando hay pasos,
   y el galón es un dibujo que GIRA al abrir — girar es una propiedad y se
   anima; cambiar «▼» por «▲» es cambiar el texto de un botón cada vez.
   ══════════════════════════════════════════════════════════════════════════ */
.rp-directions-header {
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 4px 0;
}

.rp-directions-toggle,
#rp-directions-toggle {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  border-radius: 12px;
  border: 1px solid var(--line, #1E3A5F);
  background: var(--surface-2, #10294A);
  color: var(--text, #F5F7FA);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-weight: 600;
  font-size: calc(15px * var(--ir-letra, 1));
  text-align: left;
  cursor: pointer;
}

.rp-dir-ic { flex: 0 0 auto; display: flex; align-items: center; color: var(--accent, #D4A84B); }

/* El rótulo se come el hueco, así que el contador y el galón quedan pegados
   al borde derecho pase lo que pase con la longitud del texto — que en inglés
   y en español no es la misma. */
.rp-dir-rotulo { flex: 1 1 auto; min-width: 0; }

.rp-turns-badge {
  flex: 0 0 auto;
  min-width: 24px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--accent-fill, #D4A84B);
  color: var(--accent-ink, #030D1C);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: calc(12px * var(--ir-letra, 1));
  font-weight: 700;
  text-align: center;
  line-height: 18px;
}
.rp-turns-badge[hidden] { display: none; }

.rp-chevron {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  color: var(--text-muted, #9FB0C3);
  transition: transform .18s ease;
}
#rp-directions-toggle.rp-abierto .rp-chevron,
.rp-directions-toggle.rp-abierto .rp-chevron { transform: rotate(180deg); }

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

.rp-share-btn {
  flex: 0 0 auto;
  width: 48px;
  min-height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  border: 1px solid var(--line, #1E3A5F);
  background: transparent;
  color: var(--text, #F5F7FA);
  cursor: pointer;
}

/* ───── css/vidrio.css ───── */
/*PORQUE El vidrio de iOS 26, el pulso de las cargas, las esferas HOS y las
       pastillas de llegada. AUDITORÍA 2026-09-04: `css/vidrio.css` llevaba
       desde el 21-ago en el servidor y esta línea no existía: hoja desplegada
       y sin enlazar — ni vidrio, ni radar, ni esferas. PORQUE*/
/* ==========================================================================
   IRON ROUTE — EL VIDRIO. EL MISMO QUE LA APP.

   ── POR QUÉ ESTE ARCHIVO ─────────────────────────────────────────────────

   Iron Route son dos programas, la web y la app, y no comparten una línea de
   código. Pero no son dos productos: son el mismo producto en dos sitios. Lo
   que se decidió mirando el teléfono —el material de iOS 26— tiene que verse
   igual aquí, y por las mismas razones.

   Del material de iOS 26, lo que lo define son cuatro cosas y ninguna es la
   transparencia:

     1. LA ESQUINA CONTINUA. Apple no usa un arco de círculo en la esquina:
        usa una superelipse, donde la curvatura entra acelerando. Es lo primero
        que delata a una interfaz que «casi» parece nativa. En CSS se consigue
        con `corner-shape: squircle`, y donde no exista se cae a un radio
        grande, que es lo más parecido.

     2. EL FILO ESPECULAR. Un pelo de luz de un píxel, FUERTE arriba a la
        izquierda y casi nada abajo a la derecha, como una luz de arriba
        resbalando por el canto de una pieza de cristal. Un borde uniforme no
        hace eso: hace una caja con marco. Se pinta con un degradado sobre el
        borde, no con `border-color`.

     3. EL TINTE TRANSLÚCIDO, no un color plano con alfa.

     4. NINGUNA SOMBRA COLGANDO. Es lo que el dueño señaló mirando la app y
        tenía razón: la sombra es del lenguaje anterior. El vidrio de iOS 26 no
        cuelga sobre el fondo, está sobre él.

   ── LO QUE AQUÍ SÍ SE PUEDE Y EN LA APP NO ────────────────────────────────

   EL DESENFOQUE DE VERDAD. `backdrop-filter` funciona en el navegador y
   emborrona el mapa que hay detrás. En Android la app no puede: el mapa es una
   vista nativa incrustada y el desenfoque de Flutter no la puede muestrear
   (flutter#127729). O sea que este mismo diseño queda MEJOR aquí que en el
   teléfono, y por eso se pone.

   ── EL NÚMERO ─────────────────────────────────────────────────────────────

   El velo es 0.80: deja pasar un veinte por ciento, pedido con esas palabras.
   Sale de `data/reglas.json`, igual que en la app. Aquí se escribe una vez, en
   la variable de abajo, y se comprueba contra `window.aspectoVelo`.
   ========================================================================== */

:root {
  --ir-velo: 0.80;
  --ir-superficie: #0A1B33;
  --ir-tinta: #030D1C;
  --ir-oro: #D4A84B;
  --ir-texto: #F5F7FA;
}

/* El material, en una sola clase. Todo lo que flota sobre el mapa la lleva. */
.ir-vidrio,
.map-ctrl-btn,
.search-container,
#mapControls .map-ctrl-btn {
  position: relative;
  background:
    linear-gradient(
      to bottom,
      rgba(29, 42, 65, var(--ir-velo)) 0%,
      rgba(10, 27, 51, var(--ir-velo)) 62%
    );
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  backdrop-filter: blur(18px) saturate(150%);
  /* NINGUNA sombra: el vidrio no cuelga, está encima. */
  box-shadow: none;
  border: 0;
  border-radius: 20px;
  /* La esquina continua de Apple donde el navegador la tenga. */
  corner-shape: squircle;
  color: var(--ir-texto);
}

/* Los botones del carril son REDONDOS, no squircles. Es a propósito: en la
   app se distinguen del resto por la forma, y `corner-shape: squircle` con un
   radio del 50 % da una figura que no es ni una cosa ni la otra — se probó y
   sale un cuadrado con las esquinas muy comidas. */
.map-ctrl-btn {
  border-radius: 999px;
  corner-shape: round;
}

/* ── EL FILO ESPECULAR ───────────────────────────────────────────────────
   Va en un pseudoelemento con máscara: es la única forma de tener un borde
   con DEGRADADO en CSS. `border-image` no admite radio, y un `border-color`
   con degradado no existe.

   Fuerte arriba a la izquierda (0.34), casi nada abajo a la derecha (0.04).
   Esa caída es el efecto entero. Un borde uniforme se ve como un marco. */
.ir-vidrio::after,
.map-ctrl-btn::after,
.search-container::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  corner-shape: inherit;
  padding: 1px;
  background: linear-gradient(
    135deg,
    rgba(255, 255, 255, 0.34) 0%,
    rgba(255, 255, 255, 0.10) 45%,
    rgba(255, 255, 255, 0.04) 100%
  );
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

/* Lo que está PUESTO va sólido y en oro. Es la misma regla que en la app:
   dorado y opaco quiere decir «esto es lo que estás viendo en el mapa», y
   nada más. Lo único que no depende de lo que haya debajo es lo opaco. */
.ir-vidrio.ir-puesto,
.map-ctrl-btn.active,
.map-ctrl-btn[aria-pressed='true'] {
  background: var(--ir-oro);
  color: var(--ir-tinta);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ── ACCESIBILIDAD, Y NO DE ADORNO ───────────────────────────────────────
   Quien enciende «reducir transparencia» lo enciende porque le cuesta leer, y
   a esa persona el vidrio le quita contraste justo donde hace falta. Se le da
   opaco. Es lo mismo que hace la app con el contraste alto del sistema. */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .ir-vidrio,
  .map-ctrl-btn,
  .search-container {
    background: var(--ir-superficie);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* ==========================================================================
   LAS CARGAS: UN PUNTO Y UN ANILLO QUE LATE

   Uno, no varios. Con quince cargas cerca, varios anillos por carga se
   amontonan al alejar el zoom y lo que se ve son manchas — el dueño lo
   fotografió en la app y ahí se corrigió; aquí nace ya bien.

   El anillo es un ARO, no un disco. Un disco creciendo tapa el mapa; un aro
   creciendo lo señala. Es una animación de CSS: no repinta el mapa, no cuesta
   GPU por fotograma y se para sola si el sistema pide menos movimiento.
   ========================================================================== */

.ir-carga { position: relative; }

.ir-carga-punto {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 11px;
  height: 11px;
  margin: -5.5px 0 0 -5.5px;
  border-radius: 50%;
  background: var(--ir-oro);
  border: 2px solid var(--ir-tinta);
}

.ir-carga-pulso {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px;
  border-radius: 50%;
  border: 1.8px solid var(--ir-oro);
  animation: ir-radar 2.2s cubic-bezier(0.2, 0.6, 0.4, 1) infinite;
}

.ir-carga-demo .ir-carga-punto { background: #3B7DD8; }
.ir-carga-demo .ir-carga-pulso { border-color: #3B7DD8; }

@keyframes ir-radar {
  from { transform: scale(1);   opacity: 0.85; }
  to   { transform: scale(4.2); opacity: 0;    }
}

/* Quien pide menos movimiento lo pide porque el movimiento le marea o le
   distrae. El punto se queda; lo que se va es el latido. */
@media (prefers-reduced-motion: reduce) {
  .ir-carga-pulso { animation: none; opacity: 0.35; }
}

.ir-comunidad-pin {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--ir-superficie);
  border: 2px solid #fff;
  font-size: calc(14px * var(--ir-letra, 1));
}

/* ==========================================================================
   LAS ESFERAS DEL HOS Y LA REJILLA DEL LOGBOOK
   ========================================================================== */

.ir-esferas {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
  margin: 14px 0 18px;
}

.ir-esfera { text-align: center; }

.ir-esfera-pie {
  color: var(--ir-texto);
  font-size: calc(12.5px * var(--ir-letra, 1));
  font-weight: 600;
  margin-top: 2px;
}

.ir-log-titulo {
  color: var(--ir-texto);
  font-size: calc(13px * var(--ir-letra, 1));
  font-weight: 700;
  margin: 6px 0 8px;
}

.ir-logbook {
  background: var(--ir-superficie);
  border: 1px solid #1E3A5F;
  border-radius: 12px;
  padding: 8px 10px 4px;
}

.ir-log-fila {
  display: flex;
  align-items: center;
  height: 22px;
}

/* Ancho FIJO en la etiqueta: sin él, «OFF» y «D» empujan el carril a
   distinta altura y las cuatro líneas dejan de estar alineadas — que es lo
   único que hace legible una rejilla de logbook. */
.ir-log-nombre {
  width: 30px;
  flex: 0 0 30px;
  color: var(--ir-texto);
  font-size: calc(11px * var(--ir-letra, 1));
  font-weight: 700;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.ir-log-carril {
  position: relative;
  flex: 1;
  height: 100%;
  border-bottom: 1px solid #1E3A5F;
}

.ir-log-tramo {
  position: absolute;
  top: 5px;
  height: 11px;
  background: var(--ir-oro);
  border-radius: 2px;
  min-width: 2px;
}

.ir-log-horas {
  position: relative;
  height: 16px;
  margin-left: 30px;
  color: #9FB0C3;
  font-size: calc(10px * var(--ir-letra, 1));
}

.ir-log-horas span {
  position: absolute;
  transform: translateX(-50%);
}

/* ── LAS DOS PASTILLAS DE LLEGADA ────────────────────────────────────────
   Una línea, dos botones, sin título. «Medio sigiloso»: una pregunta a
   pantalla completa antes de arrancar una ruta se aprende a despachar sin
   leerla, y entonces deja de preguntar nada. */
.ir-llegada {
  display: flex;
  gap: 8px;
  margin: 8px 0 2px;
}

.ir-llegada-pastilla {
  flex: 1;
  min-height: 44px;
  padding: 8px 10px;
  border-radius: 14px;
  corner-shape: squircle;
  border: 1px solid #1E3A5F;
  background: var(--ir-superficie);
  color: var(--ir-texto);
  font-size: calc(12.5px * var(--ir-letra, 1));
  cursor: pointer;
}

.ir-llegada-pastilla.ir-puesto {
  background: var(--ir-oro);
  color: var(--ir-tinta);
  border-color: var(--ir-oro);
  font-weight: 700;
}

/* ── LO QUE DICE UNA CAPA CUANDO NO ENSEÑA NADA (js/estado-de-las-capas.js) ──
   Debajo del nombre y la fuente, en la fila del panel. 12 px es el mínimo de
   la casa; nunca menos. */
.ir-capa-estado {
  margin-top: 3px;
  font-size: calc(12px * var(--ir-letra, 1));
  line-height: 1.3;
  color: var(--warning, #F5A623);
  max-width: 34ch;
}

/* ── EL GRUPO DE LA COMUNIDAD EN LA HOJA «¿QUÉ ESTÁ MAL AQUÍ?» ──────────── */
.rr-tipos--comunidad .rr-tipo[disabled] {
  opacity: .45;
  cursor: not-allowed;
}
.rr-nota {
  font-size: calc(12px * var(--ir-letra, 1));
  color: var(--text-muted, #9FB0C3);
  margin: 4px 0 10px;
}
