/* ==========================================================================
   Rodago — Design System
   Fuente única de estilos compartidos por las 3 experiencias
   (admin / rider / company). Sustituye los <style> inline que vivían
   en base.html, rider_base.html y company_base.html.
   ========================================================================== */

:root {
    /* Color: marca */
    --primary: #4A90E2;
    --primary-dark: #3a7bc8;
    --primary-light: #eaf2fc;

    /* Color: marca Rodago (pantalla de acceso). La tinta es el fondo del panel
       de marca y el verde el acento; no son los tokens de la app (que sigue
       con --primary azul), sino la identidad que se muestra al entrar. */
    --rodago-tinta: #16233D;
    --rodago-tinta-2: #1d2b47;
    --rodago-verde: #15C77E;
    --rodago-verde-dark: #11a869;
    --rodago-tinta-text: #9fb0cc;   /* gris azulado del lema sobre tinta */

    /* Color: acento portal empresa (mismo sistema, tono distinto) */
    --cp: #059669;
    --cp-dark: #047857;
    --cp-light: #d1fae5;
    --cp-text: #065f46;

    --secondary: #6c757d;
    --success: #27AE60;
    --danger: #E74C3C;
    --warning: #F39C12;
    --info: #3498DB;
    --dark: #2C3E50;
    --light: #F8F9FA;

    /* Escala de grises usada en textos/bordes secundarios */
    --gray-900: #111827;
    --gray-700: #374151;
    --gray-500: #6b7280;
    --gray-400: #9ca3af;
    --gray-200: #e5e7eb;
    --gray-100: #f3f4f6;
    --gray-50: #f9fafb;

    /* Tipografía */
    --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;

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

    /* Radios */
    --radius-sm: 6px;
    --radius-md: 8px;
    --radius-lg: 12px;
    --radius-xl: 16px;

    /* Sidebar */
    --sidebar-w: 240px;
    --topbar-h: 60px;

    /* Superficie / elevación (contenido en claro) */
    --surface: #ffffff;          /* superficie base de cards/tablas */
    --surface-sunken: #f5f6fa;   /* fondo de app / zonas hundidas */
    --surface-raised: #ffffff;   /* superficie elevada (dropdowns, modales) */
    --border-subtle: var(--gray-200);
    --elevation-1: 0 1px 3px rgba(16,24,40,0.06);
    --elevation-2: 0 2px 8px rgba(16,24,40,0.08);
    --elevation-3: 0 8px 24px rgba(16,24,40,0.12);

    /* Chrome oscuro del dispatcher (sidebar + topbar del admin — "command
       center"). Sólo se aplica bajo body.role-admin; empresa mantiene su
       chrome claro con acento verde. NO es un tema oscuro global. */
    --chrome-bg: #1f2937;
    --chrome-bg-2: #111827;
    --chrome-border: rgba(255,255,255,0.08);
    --chrome-text: #cbd5e1;
    --chrome-text-strong: #f8fafc;
    --chrome-muted: #94a3b8;
    --chrome-hover-bg: rgba(255,255,255,0.06);
    --chrome-active-bg: rgba(74,144,226,0.20);

    /* Cifras tabulares (tablas, KPIs, timers, precios) */
    --num: tabular-nums;

    /* Color por estado del pedido — ÚNICA fuente de color de estado.
       Estas variables son la fuente canónica del hue por estado; deben
       mantenerse sincronizadas con STATUS_HEX en
       app/services/dashboard_service.py (mismos hex), que las usa para las
       gráficas (Chart.js no puede leer estas variables desde Python).
       Paleta: pendiente=ámbar, asignado=azul, en_sitio/recogido/en_camino=
       cian, entregado=verde, cancelado/demorado=rojo. */
    --status-pendiente: #F39C12;   /* ámbar */
    --status-asignado:  #4A90E2;   /* azul */
    --status-progress:  #06B6D4;   /* cian: en_sitio / recogido / en_camino */
    --status-entregado: #27AE60;   /* verde */
    --status-cancelado: #E74C3C;   /* rojo: cancelado / demorado */
}

* {
    font-family: var(--font-sans);
}

body {
    background-color: #f5f6fa;
    min-height: 100vh;
}

/* ==========================================================================
   Scrollbar moderna (global): fina y translúcida, no siempre "presente";
   el thumb es tenue por defecto y se ensancha/oscurece al pasar el puntero.
   Sirve igual sobre el claro (empresa/rider) y el chrome oscuro (admin).
   ========================================================================== */
* {
    scrollbar-width: thin;                                 /* Firefox */
    scrollbar-color: rgba(130,140,160,0.35) transparent;
}
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background-color: rgba(130,140,160,0.28);
    border-radius: 10px;
    border: 3px solid transparent;        /* margen transparente = thumb fino */
    background-clip: padding-box;
    transition: background-color 0.15s;
}
::-webkit-scrollbar-thumb:hover {
    background-color: rgba(130,140,160,0.6);
    border-width: 2px;                     /* se ensancha bajo el puntero */
}
::-webkit-scrollbar-corner { background: transparent; }
/* Sobre el chrome oscuro del admin, thumb en tono claro translúcido */
body.role-admin { scrollbar-color: rgba(255,255,255,0.22) transparent; }
body.role-admin ::-webkit-scrollbar-thumb { background-color: rgba(255,255,255,0.18); }
body.role-admin ::-webkit-scrollbar-thumb:hover { background-color: rgba(255,255,255,0.4); }

/* ==========================================================================
   Layout: sidebar + topbar (admin / company)
   ========================================================================== */
.app-shell {
    display: flex;
    min-height: 100vh;
}

.app-sidebar {
    width: var(--sidebar-w);
    flex-shrink: 0;
    background: #fff;
    border-right: 1px solid var(--gray-200);
    display: flex;
    flex-direction: column;
    position: fixed;
    top: var(--topbar-h);
    left: 0;
    bottom: 0;
    z-index: 1040;
    transition: transform 0.2s ease;
}

/* Brand ahora vive en la topbar (franja superior unificada de ancho completo),
   ya no en la sidebar. Alineado con el ancho de la columna del sidebar para
   que el logo quede sobre ella (como el ejemplo command-center). */
.app-brand {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    width: var(--sidebar-w);
    height: 100%;
    margin-left: calc(-1 * var(--space-5));   /* flush a la izquierda, alineado al sidebar */
    padding: 0 var(--space-5);
    font-weight: 700;
    font-size: 1.15rem;
    color: var(--primary);
    text-decoration: none;
    box-sizing: border-box;
    white-space: nowrap;
    overflow: hidden;
}
@media (max-width: 575px) {
    .app-brand { width: auto; }
}
/* El logo es una imagen: la altura manda y el ancho sale de su proporción. */
.app-brand-logo {
    height: 26px;
    width: auto;
    max-width: 100%;
    display: block;
}
.app-brand:hover { color: var(--primary-dark); }
.app-brand.cp-brand { color: var(--cp); }
.app-brand.cp-brand:hover { color: var(--cp-dark); }
.app-brand .bi { font-size: 1.3rem; }

.app-sidebar-company {
    padding: var(--space-3) var(--space-5);
    font-size: 0.78rem;
    font-weight: 500;
    color: var(--gray-500);
    border-bottom: 1px solid var(--gray-100);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.app-sidebar-nav {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-3) var(--space-3);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.app-sidebar-link {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border-radius: var(--radius-md);
    text-decoration: none;
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--gray-700);
    transition: background 0.15s, color 0.15s;
    border: none;
    background: none;
    width: 100%;
    text-align: left;
    cursor: pointer;
}
.app-sidebar-link i { font-size: 1.05rem; width: 20px; text-align: center; flex-shrink: 0; }
.app-sidebar-link:hover { background: var(--gray-100); color: var(--gray-900); }
.app-sidebar-link.active { background: var(--primary-light); color: var(--primary-dark); }
.app-sidebar-link.cp-scope.active { background: var(--cp-light); color: var(--cp-text); }
.app-sidebar-link.danger,
.app-sidebar-link.danger i { color: var(--danger); }
.app-sidebar-link.danger:hover { background: #fdecea; }

.app-sidebar-footer {
    padding: var(--space-3);
    border-top: 1px solid var(--gray-100);
}

.app-topbar {
    height: var(--topbar-h);
    background: #fff;
    border-bottom: 1px solid var(--gray-200);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-5);
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1045;
}

.app-sidebar-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    font-size: 1.4rem;
    color: var(--gray-700);
    cursor: pointer;
    padding: 4px 8px;
    border-radius: var(--radius-sm);
    flex-shrink: 0;
    transition: background 0.15s, color 0.15s;
}
.app-sidebar-toggle:hover { background: var(--gray-100); color: var(--gray-900); }

.app-main {
    flex: 1;
    min-width: 0;
    margin-left: var(--sidebar-w);
    margin-top: var(--topbar-h);
    display: flex;
    flex-direction: column;
    transition: margin-left 0.2s ease;
}

/* Área de contenido de los portales de escritorio (admin / empresa):
   FLUIDA A ANCHO COMPLETO por defecto. Flexa para llenar el espacio tras el
   sidebar y provee el ÚNICO padding interno consistente (~24px), para que el
   contenido no toque el borde pero tampoco quede acotado como card centrada.
   El rider NO usa este contenedor (mantiene su .page-wrap mobile-first). */
.app-content {
    flex: 1;
    /* El padding del borde se publica como variable porque hay contenido que
       necesita SANGRAR hasta el borde de la pantalla —las filas de acciones y
       de pills que scrollean en el teléfono— y cancelarlo con un margen negativo
       escrito a ojo es la manera de que dejen de coincidir cuando este valor
       cambie. */
    --content-pad: var(--space-6);
    padding: var(--content-pad);
}
/* En un teléfono de 360px, 24px por lado son 48px (el 13% del ancho) que se
   suman a los 20px de .cp-card-body a cada lado: el contenido útil de un
   formulario quedaba en ~272px. El padding del borde es el multiplicador de
   todos los desbordes de dentro. El escritorio no se toca.
   El corte era `575.98px` (donde Bootstrap deja de tener columnas `-sm`) y pasó
   a `767.98px`, el mismo de la tanda del dispatcher en el teléfono: las tablas
   densas y las barras de filtros ya no entran en una tableta en vertical, y con
   dos cortes distintos el sangrado de las filas que scrollean —que cancela
   justo este padding— quedaba corto por 12px en esa franja. */
@media (max-width: 767.98px) {
    .app-content { --content-pad: var(--space-3); }
}

/* Neutraliza los contenedores de ancho máximo de Bootstrap dentro del shell:
   pasan a ocupar el 100% y ceden el padding a .app-content (así el borde de
   contenido queda alineado a ~24px sea .container o .container-fluid, y las
   tablas/grids ocupan todo el ancho). Fuera del shell (login/index/errores)
   Bootstrap sigue centrando normal. */
.app-shell .app-content > .container,
.app-shell .app-content > .container-fluid,
.app-shell .app-content > .container-sm,
.app-shell .app-content > .container-md,
.app-shell .app-content > .container-lg,
.app-shell .app-content > .container-xl,
.app-shell .app-content > .container-xxl,
.app-shell .app-content .container,
.app-shell .app-content .container-sm,
.app-shell .app-content .container-md,
.app-shell .app-content .container-lg,
.app-shell .app-content .container-xl,
.app-shell .app-content .container-xxl,
.app-shell .app-content .container-fluid {
    max-width: none;
    width: 100%;
    margin-right: 0;
    margin-left: 0;
    padding-right: 0;
    padding-left: 0;
}

/* El portal empresa envuelve todo en .page-wrap (max-width centrado). Dentro
   del shell de escritorio también debe ser fluido a ancho completo; el rider
   conserva su .page-wrap acotado vía body.role-rider más abajo. */
.app-content .page-wrap {
    max-width: none;
    margin: 0;
    padding: 0;
}

/* Ancho de lectura cómodo opcional para bloques de texto largo y formularios
   dentro del área fluida (se aplica por pantalla cuando convenga). */
.readable-col { max-width: 760px; }
.readable-col-wide { max-width: 1100px; }

.app-sidebar-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.35);
    z-index: 1035;
}

@media (max-width: 991px) {
    .app-sidebar {
        transform: translateX(-100%);
        box-shadow: 2px 0 12px rgba(0,0,0,0.12);
    }
    .app-sidebar.open { transform: translateX(0); }
    .app-sidebar.open ~ .app-sidebar-backdrop,
    .app-sidebar-backdrop.open { display: block; }
    .app-main { margin-left: 0; }
}

/* Colapso horizontal del sidebar en desktop (admin/company), estilo panel
   deslizable — el toggle de la topbar alterna esta clase en .app-shell. */
@media (min-width: 992px) {
    .app-shell.sidebar-collapsed .app-sidebar {
        transform: translateX(-100%);
    }
    .app-shell.sidebar-collapsed .app-main {
        margin-left: 0;
    }
}

/* ==========================================================================
   Chrome oscuro del dispatcher (SOLO admin — body.role-admin)
   Sidebar + topbar en gris azulado oscuro tipo "command center". El
   contenido sigue en claro. La empresa (role-company) NO se ve afectada:
   mantiene su sidebar claro y acento verde. No es un tema oscuro global ni
   hay toggle claro/oscuro.
   ========================================================================== */
body.role-admin .app-sidebar {
    background: var(--chrome-bg);
    border-right-color: var(--chrome-border);
}
body.role-admin .app-sidebar-brand {
    color: var(--chrome-text-strong);
    border-bottom-color: var(--chrome-border);
}
body.role-admin .app-sidebar-brand:hover { color: #fff; }
body.role-admin .app-sidebar-company {
    color: var(--chrome-muted);
    border-bottom-color: var(--chrome-border);
}
body.role-admin .app-sidebar-link { color: var(--chrome-text); }
body.role-admin .app-sidebar-link i { color: var(--chrome-muted); }
body.role-admin .app-sidebar-link:hover { background: var(--chrome-hover-bg); color: #fff; }
body.role-admin .app-sidebar-link:hover i { color: #fff; }
body.role-admin .app-sidebar-link.active { background: var(--chrome-active-bg); color: #fff; }
body.role-admin .app-sidebar-link.active i { color: #fff; }
body.role-admin .app-sidebar-link.danger,
body.role-admin .app-sidebar-link.danger i { color: #f87171; }
body.role-admin .app-sidebar-link.danger:hover { background: rgba(248,113,113,0.14); color: #fca5a5; }
body.role-admin .app-sidebar-nav { scrollbar-color: rgba(255,255,255,0.2) transparent; }
body.role-admin .app-sidebar-footer { border-top-color: var(--chrome-border); }

body.role-admin .app-topbar {
    background: var(--chrome-bg-2);
    border-bottom-color: var(--chrome-border);
    color: var(--chrome-text);
}
body.role-admin .app-topbar .text-secondary { color: var(--chrome-muted) !important; }
body.role-admin .app-sidebar-toggle { color: var(--chrome-text); }
body.role-admin .app-sidebar-toggle:hover { background: var(--chrome-hover-bg); color: #fff; }
/* Buscador global sobre topbar oscura */
body.role-admin .navbar-search-wrap input[type="search"] {
    background: rgba(255,255,255,0.08);
    border-color: var(--chrome-border);
    color: var(--chrome-text-strong);
}
body.role-admin .navbar-search-wrap input[type="search"]::placeholder { color: var(--chrome-muted); }
body.role-admin .navbar-search-wrap input[type="search"]:focus {
    background: rgba(255,255,255,0.12);
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(74,144,226,0.20);
}
/* Marca en la topbar oscura */
body.role-admin .app-brand { color: var(--chrome-text-strong); }
body.role-admin .app-brand:hover { color: #fff; }
body.role-admin .app-brand .bi { color: var(--primary); }

/* ==========================================================================
   Tema oscuro del ÁREA CENTRAL del dispatcher (SOLO admin — body.role-admin)
   El contenido central acompaña al chrome (mismo #1f2937), como pidió el
   diseño "command center". Se resuelve acá, en la base, para TODAS las
   páginas admin sin tocar plantilla por plantilla.

   Estrategia: (1) remapear la escala de grises y los tokens de superficie a
   equivalentes oscuros — así todas las clases propias que ya usan var(--gray-*)
   /var(--surface) (cp-table, kpi-card, ops-*, queue-*, disp-*, timeline,
   steppers, etc.) se invierten solas; (2) overrides puntuales para los
   componentes de Bootstrap, que usan sus PROPIAS variables --bs-* y no se
   enteran del remapeo. La empresa (role-company) y el rider NO se ven
   afectados: este bloque está enteramente bajo body.role-admin.
   ========================================================================== */
body.role-admin {
    /* Grises usados como TEXTO → tonos claros */
    --gray-900: #f1f5f9;
    --gray-700: #cbd5e1;
    --gray-500: #94a3b8;
    --gray-400: #7f8ea3;
    /* Grises usados como BORDES / FONDOS suaves → blanco translúcido */
    --gray-200: rgba(255,255,255,0.12);
    --gray-100: rgba(255,255,255,0.07);
    --gray-50:  rgba(255,255,255,0.04);
    /* Superficies / elevación en oscuro */
    --surface: #253243;          /* cards/tablas: un paso sobre el fondo */
    --surface-sunken: #1f2937;   /* fondo de app (igual que el chrome) */
    --surface-raised: #2a3849;   /* dropdowns/menús */
    --border-subtle: rgba(255,255,255,0.10);
    --primary-light: rgba(74,144,226,0.18);  /* fondos "activos" azules */
    background-color: #1f2937;
}
body.role-admin .app-main { background: #1f2937; }
body.role-admin .app-content { color: #e5eaf0; }

/* Única "víctima" del remapeo: badge del mock de mapa usa gray-900 como
   FONDO oscuro (no como texto) — se le restituye un fondo oscuro real. */
body.role-admin .map-mock-badge { background: #0b1220; }

/* Utilidades de texto tenue de Bootstrap (usan --bs-*, no nuestros grises) */
body.role-admin .app-content .text-muted,
body.role-admin .app-content .text-secondary { color: #94a3b8 !important; }

/* Fondos claros utilitarios → neutros dentro del área oscura */
body.role-admin .app-content .bg-white,
body.role-admin .app-content .bg-light { background-color: rgba(255,255,255,0.05) !important; }
body.role-admin .app-content .badge.bg-light { background-color: rgba(255,255,255,0.14) !important; }

/* Cards genéricas: degradado sutil + borde translúcido (como el ejemplo) */
body.role-admin .card,
body.role-admin .cp-card {
    background: linear-gradient(160deg, #2a3849 0%, #212e3d 100%);
    border: 1px solid rgba(255,255,255,0.08);
    box-shadow: 0 1px 3px rgba(0,0,0,0.35);
    color: #e5eaf0;
}
body.role-admin .card-header,
body.role-admin .cp-card-header {
    background: transparent;
    border-bottom-color: rgba(255,255,255,0.08);
    color: #e8eef5;
}
/* Pie de card: Bootstrap le pone su propio fondo y un borde OSCURO desde sus
   --bs-*, que sobre el chrome oscuro es una línea invisible. Mismo trato que la
   cabecera — el remapeo de --gray-* no llega a los componentes de Bootstrap. */
body.role-admin .card-footer {
    background: transparent;
    border-top-color: rgba(255,255,255,0.08);
    color: #e8eef5;
}

/* KPI-cards como en el ejemplo (degradado + label en mayúsculas) */
body.role-admin .kpi-card {
    background: linear-gradient(160deg, #2c3c50 0%, #212e3d 100%);
    border: 1px solid rgba(255,255,255,0.09);
    box-shadow: 0 1px 3px rgba(0,0,0,0.35);
}
body.role-admin .kpi-label {
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 0.72rem;
    font-weight: 600;
    color: #93a1b3;
}
body.role-admin .kpi-value { color: #f8fafc; }
body.role-admin .kpi-eye { color: #93a1b3; }
body.role-admin .kpi-eye:hover { color: #f8fafc; background: rgba(255,255,255,0.08); }
body.role-admin .kpi-card-secret.is-hidden .kpi-secret::after { color: #93a1b3; }

/* Números de alerta más brillantes sobre oscuro */
body.role-admin .ops-cell.alert-amber .ops-num,
body.role-admin .disp-timer.warn { color: #fbbf24; }

/* Tablas de Bootstrap (usan --bs-*).
   OJO: la regla base `.table { background: white }` (más abajo) pinta el
   ELEMENTO tabla de blanco; como acá las celdas quedan transparentes para
   mostrar la card oscura, hay que neutralizar ese fondo blanco o el <tbody>
   se ve blanco. */
body.role-admin .app-content .table {
    background: transparent;
    --bs-table-bg: transparent;
    --bs-table-color: #dbe2ea;
    --bs-table-border-color: rgba(255,255,255,0.08);
    --bs-table-striped-color: #dbe2ea;
    --bs-table-striped-bg: rgba(255,255,255,0.03);
    --bs-table-hover-color: #f1f5f9;
    --bs-table-hover-bg: rgba(255,255,255,0.05);
    color: #dbe2ea;
}
body.role-admin .app-content .table > thead th,
body.role-admin .app-content thead.bg-white,
body.role-admin .app-content thead.sticky-top.bg-white,
body.role-admin .app-content thead.sticky-top.bg-white th {
    background-color: #212e3d !important;
    color: #94a3b8;
    border-color: rgba(255,255,255,0.12);
}
/* Variantes contextuales de Bootstrap (usan --bs-table-bg claro por celda):
   thead.table-light, th/td .table-primary/.table-active/.table-secondary →
   fondo oscuro + texto claro, para que no queden recuadros blancos. */
body.role-admin .app-content .table-light,
body.role-admin .app-content .table-light > th,
body.role-admin .app-content .table-light > td,
body.role-admin .app-content .table-active,
body.role-admin .app-content .table-secondary {
    --bs-table-bg: #212e3d;
    --bs-table-color: #cbd5e1;
    background-color: #212e3d !important;
    color: #cbd5e1 !important;
    border-color: rgba(255,255,255,0.12);
}
body.role-admin .app-content .table-primary {
    --bs-table-bg: rgba(74,144,226,0.20);
    --bs-table-color: #dbe9fb;
    background-color: rgba(74,144,226,0.20) !important;
    color: #dbe9fb !important;
    border-color: rgba(74,144,226,0.35);
}

/* Formularios de Bootstrap */
body.role-admin .app-content .form-control,
body.role-admin .app-content .form-select {
    background-color: rgba(255,255,255,0.05);
    border-color: rgba(255,255,255,0.14);
    color: #f1f5f9;
}
body.role-admin .app-content .form-control::placeholder { color: #7f8ea3; }
body.role-admin .app-content .form-control:focus,
body.role-admin .app-content .form-select:focus {
    background-color: rgba(255,255,255,0.08);
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(74,144,226,0.20);
    color: #fff;
}
/* El desplegable nativo del select se pinta con el tema del SO: texto oscuro */
body.role-admin .app-content .form-select option { color: #1f2937; }
body.role-admin .app-content .form-label,
body.role-admin .app-content .col-form-label { color: #cbd5e1; }
body.role-admin .app-content .form-text { color: #94a3b8; }
body.role-admin .app-content .form-check-input {
    background-color: rgba(255,255,255,0.08);
    border-color: rgba(255,255,255,0.25);
}
body.role-admin .app-content .form-check-input:checked {
    background-color: var(--primary);
    border-color: var(--primary);
}
body.role-admin .app-content .input-group-text {
    background-color: rgba(255,255,255,0.06);
    border-color: rgba(255,255,255,0.14);
    color: #cbd5e1;
}

/* Listas, separadores y bordes utilitarios */
body.role-admin .app-content .list-group-item {
    background-color: transparent;
    border-color: rgba(255,255,255,0.08);
    color: #dbe2ea;
}
body.role-admin .app-content hr { border-top-color: rgba(255,255,255,0.14); opacity: 1; }
body.role-admin .app-content .border,
body.role-admin .app-content .border-top,
body.role-admin .app-content .border-bottom,
body.role-admin .app-content .border-start,
body.role-admin .app-content .border-end { border-color: rgba(255,255,255,0.12) !important; }

/* Filas CLICABLES de una lista (picker de productos, etc.): Bootstrap las pinta
   con sus --bs-list-group-action-*, que no se enteran del remapeo. */
body.role-admin .app-content .list-group-item-action:hover,
body.role-admin .app-content .list-group-item-action:focus {
    background-color: rgba(255,255,255,0.07);
    color: #f8fafc;
}
body.role-admin .app-content .list-group-item-action:disabled,
body.role-admin .app-content .list-group-item-action.disabled {
    background-color: transparent;
    color: #7f8ea3;
}

/* Modales de Bootstrap DENTRO del área oscura.
   .modal-content usa sus propias --bs-modal-* y no se entera del remapeo de
   arriba: quedaba BLANCO mientras el texto y los .list-group-item de adentro
   ya venían claros/transparentes — texto claro sobre fondo blanco = ilegible
   (es lo que pasaba con el modal "Ver catálogo").
   El modal de confirmación de base.html vive FUERA de .app-content y sigue en
   claro a propósito: es global (también lo usan empresa y rider). */
body.role-admin .app-content .modal-content {
    background: linear-gradient(160deg, #2a3849 0%, #212e3d 100%);
    border: 1px solid rgba(255,255,255,0.10);
    border-radius: var(--radius-lg);
    box-shadow: 0 20px 60px rgba(0,0,0,0.55);
    color: #e5eaf0;
}
body.role-admin .app-content .modal-header,
body.role-admin .app-content .modal-footer { border-color: rgba(255,255,255,0.08); }
body.role-admin .app-content .modal-title { color: #f1f5f9; }
body.role-admin .app-content .modal-title .bi { color: var(--primary); }
/* El ✕ de Bootstrap es un SVG negro incrustado: se invierte para que se vea */
body.role-admin .app-content .btn-close {
    filter: invert(1) grayscale(100%) brightness(200%);
    opacity: 0.7;
}
body.role-admin .app-content .btn-close:hover { opacity: 1; }

/* Dropdowns de Bootstrap sobre oscuro */
body.role-admin .dropdown-menu {
    background-color: var(--surface-raised);
    border-color: rgba(255,255,255,0.10);
}
body.role-admin .dropdown-item { color: #dbe2ea; }
body.role-admin .dropdown-item:hover,
body.role-admin .dropdown-item:focus { background-color: rgba(255,255,255,0.06); color: #fff; }
body.role-admin .dropdown-divider { border-top-color: rgba(255,255,255,0.10); }

/* Footer */
footer {
    background: none;
    padding: var(--space-6) 0;
}

/* ==========================================================================
   Componentes genéricos
   ========================================================================== */

/* Cards */
.card {
    border: none;
    border-radius: var(--radius-lg);
    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
    margin-bottom: 1.5rem;
}
.card-header {
    background: white;
    border-bottom: 1px solid #e9ecef;
    font-weight: 600;
    padding: 1.25rem;
}

/* Card del portal empresa */
.cp-card {
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: 0 1px 4px rgba(0,0,0,0.06);
    margin-bottom: var(--space-5);
    overflow: hidden;
}
.cp-card-header {
    padding: 14px 18px;
    border-bottom: 1px solid var(--gray-100);
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--gray-700);
    display: flex;
    align-items: center;
    gap: 8px;
}
.cp-card-body { padding: var(--space-5); }

/* Buttons */
.btn {
    border-radius: var(--radius-md);
    font-weight: 500;
    padding: 0.5rem 1.25rem;
    transition: all 0.2s;
}
.btn-primary { background: var(--primary); border: none; }
.btn-primary:hover {
    background: var(--primary-dark);
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(74, 144, 226, 0.3);
}
.btn-success { background: var(--success); border: none; }
.btn-danger { background: var(--danger); border: none; }
.btn-warning { background: var(--warning); border: none; color: white; }

/* Botón con acento del portal empresa */
.btn-cp {
    background: var(--cp);
    color: #fff;
    border: none;
    border-radius: var(--radius-md);
    font-weight: 500;
}
.btn-cp:hover { background: var(--cp-dark); color: #fff; }

/* Badges */
.badge {
    font-weight: 500;
    padding: 0.4rem 0.8rem;
    border-radius: var(--radius-sm);
}

/* Tables */
.table {
    background: white;
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.table thead th {
    background: #f8f9fa;
    border-bottom: 2px solid #dee2e6;
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.85rem;
    letter-spacing: 0.5px;
    color: var(--dark);
}

/* Cifras tabulares en tablas y métricas: columnas de números alineadas,
   sin "saltos" al actualizar timers/contadores. */
.table,
.cp-table,
.kpi-value,
.stat-cell-num,
.stat-card h3,
.session-timer,
.session-duration,
.price-tag,
.tnum { font-variant-numeric: var(--num); }

/* Tabla compacta del portal empresa */
.cp-table { width: 100%; border-collapse: collapse; }
.cp-table th {
    text-align: left;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--gray-400);
    padding: 0 12px 10px;
    border-bottom: 1px solid var(--gray-100);
}
.cp-table td {
    padding: 12px;
    font-size: 0.875rem;
    color: var(--gray-700);
    border-bottom: 1px solid var(--gray-50);
    vertical-align: middle;
}
.cp-table tr:last-child td { border-bottom: none; }
.cp-table tr.clickable { cursor: pointer; }
.cp-table tr.clickable:hover td { background: var(--gray-50); }
.cp-table td.truncate {
    max-width: 200px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.cp-table td.muted { color: var(--gray-400); font-size: 0.82rem; }

/* Stats */
.stat-card {
    background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
    color: white;
    border-radius: var(--radius-lg);
    padding: 1.5rem;
    margin-bottom: 1.5rem;
}
.stat-card.success { background: linear-gradient(135deg, var(--success) 0%, #229954 100%); }
.stat-card.warning { background: linear-gradient(135deg, var(--warning) 0%, #ca8b0a 100%); }
.stat-card.danger { background: linear-gradient(135deg, var(--danger) 0%, #c0392b 100%); }
.stat-card h3 { font-size: 2.5rem; font-weight: 700; margin: 0; }
.stat-card p { margin: 0; opacity: 0.9; font-size: 0.95rem; }

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--space-3);
    margin-bottom: var(--space-6);
}
/* Variante compacta: las 4 celdas SIEMPRE en una sola fila (dashboard rider,
   móvil). Cifras y paddings reducidos para que entren sin desbordar. */
.stat-grid--4 {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-2);
}
.stat-grid--4 .stat-cell { padding: 12px 5px; }
.stat-grid--4 .stat-cell-icon { font-size: 0.9rem; margin-bottom: 5px; }
.stat-grid--4 .stat-cell-num { font-size: 1.3rem; }
.stat-grid--4 .stat-cell-label {
    font-size: 0.58rem;
    letter-spacing: 0.01em;
    line-height: 1.15;
}
/* Variante compacta de 3 columnas SIEMPRE en una fila (jornadas del rider:
   Mi Jornada y detalle del día). Los valores son tipo "1h 30m", así que el
   número es algo más chico para que entren sin envolver. */
.stat-grid--3 {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-2);
}
.stat-grid--3 .stat-cell { padding: 12px 6px; }
.stat-grid--3 .stat-cell-num { font-size: 1.1rem; }
.stat-grid--3 .stat-cell-label {
    font-size: 0.6rem;
    letter-spacing: 0.01em;
    line-height: 1.15;
}

/* Grid de 2 columnas genérico para formularios largos (Tanda 6.6) — evita
   scroll interminable en pantallas de creación/edición. Colapsa a 1 columna
   en el mismo breakpoint donde .app-sidebar ya colapsa a mobile (991px). */
.mess-form-grid {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
    gap: var(--space-6);
    align-items: start;
}
.mess-form-grid > .mess-form-col {
    min-width: 0;
}
@media (max-width: 991.98px) {
    .mess-form-grid {
        grid-template-columns: 1fr;
    }
}
.stat-cell {
    background: #fff;
    border-radius: var(--radius-lg);
    padding: 16px 12px;
    text-align: center;
    box-shadow: 0 1px 4px rgba(0,0,0,0.06);
}
/* Ícono tenue de encabezado de la celda (escaneo rápido, sin ruido de color) */
.stat-cell-icon {
    display: block;
    font-size: 1.05rem;
    line-height: 1;
    margin-bottom: 8px;
    color: var(--gray-400);
}
.stat-cell-icon.green { color: var(--cp); opacity: 0.85; }
.stat-cell-num {
    display: block;
    font-size: 1.75rem;
    font-weight: 700;
    color: var(--gray-900);
    line-height: 1;
    margin-bottom: 4px;
}
.stat-cell-num.green { color: var(--cp); }
.stat-cell-num.orange { color: #d97706; }
.stat-cell-label {
    display: block;
    font-size: 0.68rem;
    color: var(--gray-400);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Alerts */
.alert { border: none; border-radius: var(--radius-md); font-weight: 500; }

/* Forms */
.form-control, .form-select {
    border-radius: var(--radius-md);
    border: 1px solid #dee2e6;
    padding: 0.625rem 0.875rem;
}
.form-control:focus, .form-select:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 0.2rem rgba(74, 144, 226, 0.15);
}
.form-label { font-weight: 500; color: var(--gray-700); margin-bottom: 0.5rem; }

/* Search bar (genérica, usada en topbar) */
.search-container { position: relative; }
.search-container input { padding-left: 2.5rem; }
.search-container i {
    position: absolute;
    left: 0.875rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--gray-500);
}

/* Status badges — ÚNICA fuente visual de color de estado. El hue de cada
   estado sale de las variables --status-* de :root (paleta canónica, espejo
   de STATUS_HEX en dashboard_service.py). El texto/clase de estado viene de
   Order.status/status_display (modelo). Los fondos son tintes claros del
   mismo hue; el borde izquierdo usa la variable canónica para acoplarlos
   visualmente al color sólido de las gráficas. */
.status-badge {
    padding: 0.35rem 0.75rem;
    border-radius: var(--radius-sm);
    font-size: 0.875rem;
    font-weight: 500;
    font-variant-numeric: var(--num);
    border-left: 3px solid transparent;
}
.status-pendiente { background: #fef3c7; color: #92400e; border-left-color: var(--status-pendiente); }
.status-asignado  { background: #dbeafe; color: #1e40af; border-left-color: var(--status-asignado); }
.status-en_sitio,
.status-recogido,
.status-en_camino { background: #cffafe; color: #155e75; border-left-color: var(--status-progress); }
.status-entregado { background: #d1fae5; color: #065f46; border-left-color: var(--status-entregado); }
.status-cancelado,
.status-demorado  { background: #fee2e2; color: #991b1b; border-left-color: var(--status-cancelado); }

/* Session control (rider) */
.session-control {
    background: white;
    border-radius: var(--radius-lg);
    padding: 1.5rem;
    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
.session-timer {
    font-size: 3rem;
    font-weight: 700;
    color: var(--primary);
    text-align: center;
    margin: 1rem 0;
}
.session-status { display: inline-block; padding: 0.5rem 1rem; border-radius: 20px; font-weight: 600; }
.session-active { background: #d4edda; color: #155724; }
.session-paused { background: #fff3cd; color: #856404; }

/* Empty state (usado en admin/rider: .empty-state, rider: .empty-card, empresa: .cp-empty) */
.empty-state, .empty-card, .cp-empty {
    text-align: center;
    padding: 3rem 1rem;
    color: var(--gray-400);
}
.empty-state i, .empty-card i, .cp-empty i {
    font-size: 3rem;
    margin-bottom: 0.75rem;
    display: block;
    opacity: 0.5;
}
.empty-state p, .empty-card p, .cp-empty p { font-size: 0.9rem; margin: 0 0 1rem; }
.empty-card { background: #fff; border-radius: var(--radius-xl); box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
.cp-empty { padding: 48px 16px; }

/* Highlight card (gradiente de marca) */
.highlight-card {
    background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
    color: #fff;
    border-radius: var(--radius-xl);
    padding: 18px;
    margin-bottom: var(--space-4);
    box-shadow: 0 4px 16px rgba(74,144,226,0.35);
}

/* Page header genérico (reemplaza duplicados en rider_base/company_base) */
.page-wrap { max-width: 900px; margin: 0 auto; padding: var(--space-6) var(--space-4) 40px; }
.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
    flex-wrap: wrap;
}
.page-title { font-size: 1.25rem; font-weight: 700; color: var(--gray-900); margin: 0; }
.page-subtitle { font-size: 0.82rem; color: var(--gray-500); margin: 2px 0 0; }
.section-label {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--gray-400);
    margin-bottom: 10px;
}
/* Rótulo de sección con aire arriba, para separar grupos apilados (rider) */
.section-label--spaced { margin-top: 22px; }

/* Detail row (label + valor con ícono) */
.detail-row { display: flex; gap: 8px; margin-bottom: 10px; font-size: 0.875rem; }
.detail-row i { color: var(--gray-400); flex-shrink: 0; margin-top: 2px; }
.detail-row i.cp-scope { color: var(--cp); }
.detail-label { font-weight: 600; color: var(--gray-700); min-width: 110px; }

/* Price */
.price-tag { font-weight: 600; color: var(--gray-900); }
.price-pending { color: var(--gray-400); font-style: italic; font-size: 0.82rem; }

/* ===== Session card (jornadas: rider/sessions.html y rider/shift_day_detail.html) ===== */
.session-card {
    background: #fff;
    border-radius: 12px;
    padding: 14px 16px;
    margin-bottom: 8px;
    box-shadow: 0 1px 4px rgba(0,0,0,0.06);
}
.session-card-top {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 12px;
}
.session-date {
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--dark);
    margin-bottom: 2px;
}
.session-time {
    font-size: 0.8rem;
    color: #6b7280;
}
.session-card-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    flex-shrink: 0;
}
.session-duration {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--dark);
}
.session-break {
    font-size: 0.75rem;
    color: #9ca3af;
    margin-top: 7px;
    display: flex;
    align-items: center;
    gap: 4px;
    border-top: 1px solid #f3f4f6;
    padding-top: 7px;
}
/* Bloque de varias líneas (extras/notas) dentro de una session-card */
.session-lines {
    font-size: 0.75rem;
    color: #9ca3af;
    margin-top: 7px;
    border-top: 1px solid #f3f4f6;
    padding-top: 7px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* Encabezado de tabla clickeable para ordenar (empresa) */
.cp-sort-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: inherit;
    text-decoration: none;
}
.cp-sort-link:hover { color: var(--cp); }
.cp-sort-link.active { color: var(--cp); font-weight: 700; }

/* Badge "más usado" (catálogo de empresa) */
.cp-badge-hot {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 0.68rem;
    font-weight: 600;
    background: #fef3c7;
    color: #92400e;
    border-radius: 4px;
    padding: 1px 6px;
    margin-left: 6px;
    vertical-align: middle;
}

/* Mini stats en el subtítulo del dashboard de empresa */
.cp-mini-stat {
    display: inline-flex;
    align-items: center;
    font-size: 0.8rem;
    color: var(--gray-500);
    margin-right: 12px;
}
.cp-mini-stat.green { color: var(--cp); font-weight: 600; }
.cp-mini-stat.orange { color: #d97706; font-weight: 600; }

/* Chip / filtro clickeable */
.chip-filter {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border-radius: 20px;
    padding: 6px 14px;
    font-size: 0.82rem;
    font-weight: 500;
    cursor: pointer;
    text-decoration: none;
    border: 1.5px solid var(--gray-200);
    background: #fff;
    color: var(--gray-700);
    white-space: nowrap;
    flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.1s;
}
.chip-filter:hover { background: var(--gray-100); color: var(--gray-700); }
.chip-filter.active { background: var(--primary); border-color: var(--primary); color: #fff; }
.chip-filter.active:hover { background: var(--primary); color: #fff; }
.chip-filter.cp-scope.active { background: var(--cp); border-color: var(--cp); color: #fff; }
.chip-filter.cp-scope.active:hover { background: var(--cp); color: #fff; }

/* Image thumbnail grid (portal empresa) */
.cp-img-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: var(--space-3);
}
.cp-img-wrap { text-align: center; }
.cp-img-thumb {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-md);
    border: 1px solid var(--gray-200);
    display: block;
}
.cp-img-caption { font-size: 0.7rem; color: var(--gray-500); margin: 4px 0 0; }

/* Pagination (portal empresa) */
.cp-pagination { display: flex; justify-content: center; gap: 6px; margin-top: var(--space-5); }
.cp-pagination a, .cp-pagination span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: var(--radius-md);
    font-size: 0.875rem;
    font-weight: 500;
    text-decoration: none;
    border: 1px solid var(--gray-200);
    color: var(--gray-700);
    background: #fff;
}
.cp-pagination a:hover { background: var(--gray-100); }
.cp-pagination .current { background: var(--cp); color: #fff; border-color: var(--cp); }
.cp-pagination .disabled { color: #d1d5db; pointer-events: none; }

/* Settings list (filas tipo iOS, usado en rider) */
.settings-list {
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: 0 1px 4px rgba(0,0,0,0.06);
    overflow: hidden;
    margin-bottom: var(--space-4);
}
.settings-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    border: none;
    border-bottom: 1px solid var(--gray-100);
    background: none;
    text-decoration: none;
    color: var(--dark);
    font-size: 0.9rem;
    font-weight: 500;
    cursor: pointer;
    text-align: left;
    width: 100%;
    transition: background 0.12s;
    -webkit-tap-highlight-color: transparent;
}
.settings-list > *:last-child .settings-row,
.settings-list > a:last-child { border-bottom: none; }
.settings-row:hover { background: #f8f9fa; color: var(--dark); }
.settings-icon { font-size: 1rem; color: var(--primary); width: 20px; flex-shrink: 0; text-align: center; }
.settings-label { flex: 1; }
.settings-chevron { font-size: 0.75rem; color: #c7c7cc; }
.settings-row-danger,
.settings-row-danger .settings-icon { color: var(--danger); }
.page-title .order-card-map { vertical-align: middle; }

/* Chip genérico */
.chip { display: inline-flex; align-items: center; gap: 6px; border-radius: 20px; padding: 6px 16px; font-size: 0.82rem; font-weight: 500; }

/* Back crumb (link pequeño encima del page-title, rider/company) */
.back-crumb {
    font-size: 0.75rem;
    color: var(--gray-400);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    margin-bottom: 4px;
}
.back-crumb:hover { color: var(--primary); }
.back-crumb.cp-scope:hover { color: var(--cp); }

/* Responsive */
@media (max-width: 768px) {
    .stat-card h3 { font-size: 2rem; }
    .session-timer { font-size: 2rem; }
}

/* ==========================================================================
   Toast notifications
   ========================================================================== */
#mess-toast-container {
    position: fixed;
    top: 1rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1065;
    min-width: 300px;
    max-width: 480px;
    width: 90%;
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
}
.mess-toast {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 13px 15px;
    border-radius: 10px;
    font-size: 0.9rem;
    font-weight: 500;
    box-shadow: 0 4px 20px rgba(0,0,0,0.15);
    pointer-events: auto;
    animation: messToastIn 0.28s ease both;
}
.mess-toast.mess-toast-hiding { animation: messToastOut 0.28s ease both; }
@keyframes messToastIn {
    from { opacity: 0; transform: translateY(-14px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes messToastOut {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(-14px); }
}
.mess-toast-success { background: #d1fae5; color: #065f46; border-left: 4px solid #10b981; }
.mess-toast-danger  { background: #fee2e2; color: #991b1b; border-left: 4px solid #ef4444; }
.mess-toast-warning { background: #fef3c7; color: #92400e; border-left: 4px solid #f59e0b; }
.mess-toast-info    { background: #dbeafe; color: #1e40af; border-left: 4px solid #3b82f6; }
.mess-toast-icon    { flex-shrink: 0; margin-top: 1px; }
.mess-toast-content { flex: 1; line-height: 1.4; }
.mess-toast-close {
    background: none; border: none; cursor: pointer;
    padding: 0 0 0 4px; line-height: 1;
    opacity: 0.55; font-size: 1.25rem; font-weight: 700;
    color: inherit; flex-shrink: 0;
}
.mess-toast-close:hover { opacity: 1; }

/* Confirm modal */
.mess-confirm-content { border-radius: 14px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.2); }

/* ==========================================================================
   Global search (topbar)
   ========================================================================== */
.navbar-search-wrap { position: relative; flex: 1; max-width: 340px; }
.navbar-search-wrap input[type="search"] {
    background: #f5f6fa;
    border: 1px solid var(--gray-200);
    border-radius: 20px;
    padding: 0.35rem 1rem 0.35rem 2.25rem;
    font-size: 0.875rem;
    width: 100%;
    transition: border-color 0.2s, box-shadow 0.2s;
    outline: none;
}
.navbar-search-wrap input[type="search"]:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(74,144,226,0.12);
    background: #fff;
}
.navbar-search-wrap .search-icon {
    position: absolute;
    left: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    color: #aaa;
    font-size: 0.85rem;
    pointer-events: none;
}
#navbar-search-dropdown {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    background: #fff;
    border: 1px solid var(--gray-200);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.10);
    z-index: 9999;
    overflow: hidden;
}
.search-suggestion-item {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.55rem 1rem;
    text-decoration: none;
    color: var(--dark);
    font-size: 0.85rem;
    border-bottom: 1px solid #f0f0f0;
    transition: background 0.15s;
}
.search-suggestion-item:last-child { border-bottom: none; }
.search-suggestion-item:hover { background: #f5f6fa; }
.search-suggestion-item .sug-icon { margin-top: 2px; color: var(--primary); flex-shrink: 0; }
.search-suggestion-item .sug-text { flex: 1; min-width: 0; }
.search-suggestion-item .sug-label { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.search-suggestion-item .sug-sub { font-size: 0.75rem; color: #888; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.search-suggestion-item .sug-type { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.04em; color: #aaa; margin-bottom: 1px; }
.search-no-results { padding: 0.7rem 1rem; font-size: 0.83rem; color: #888; text-align: center; }

@media (max-width: 991px) {
    .navbar-search-wrap { max-width: 220px; }
}
@media (max-width: 640px) {
    .navbar-search-wrap { display: none; }
}

/* ==========================================================================
   Chat FAB + panel flotante (admin / empresa)
   ========================================================================== */
#chat-fab, #company-chat-fab {
    position: fixed;
    bottom: 20px;
    right: 20px;
    width: 54px;
    height: 54px;
    border-radius: 50%;
    background: var(--primary);
    color: #fff;
    border: none;
    font-size: 1.35rem;
    cursor: pointer;
    z-index: 1060;
    box-shadow: 0 4px 18px rgba(74,144,226,0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, transform 0.1s;
}
#chat-fab:hover, #company-chat-fab:hover { background: var(--primary-dark); }
#chat-fab:active, #company-chat-fab:active { transform: scale(0.93); }
.chat-fab-badge {
    position: absolute;
    top: -3px;
    right: -3px;
    background: #ef4444;
    color: #fff;
    border-radius: 20px;
    font-size: 0.62rem;
    font-weight: 700;
    min-width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 4px;
    border: 2px solid #fff;
    line-height: 1;
}
.chat-panel {
    position: fixed;
    bottom: 84px;
    right: 20px;
    width: 360px;
    height: 500px;
    background: #fff;
    border-radius: 16px;
    box-shadow: 0 8px 40px rgba(0,0,0,0.18);
    z-index: 1060;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    animation: chatPanelIn 0.2s ease both;
}
@keyframes chatPanelIn {
    from { opacity: 0; transform: translateY(12px) scale(0.97); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (max-width: 520px) {
    .chat-panel {
        width: calc(100vw - 16px);
        height: calc(100dvh - 90px);
        bottom: 74px;
        right: 8px;
    }
}
.chat-panel-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 14px;
    border-bottom: 1px solid #e9ecef;
    flex-shrink: 0;
}
.chat-panel-title { flex: 1; font-weight: 600; font-size: 0.92rem; color: var(--dark); }
.chat-panel-btn {
    background: none;
    border: none;
    color: #9ca3af;
    cursor: pointer;
    font-size: 1rem;
    padding: 2px 4px;
    border-radius: 6px;
    line-height: 1;
    transition: color 0.12s, background 0.12s;
}
.chat-panel-btn:hover { color: var(--dark); background: #f3f4f6; }
#chat-list-view { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
.chat-search-bar { padding: 8px 12px; border-bottom: 1px solid #f3f4f6; flex-shrink: 0; }
.chat-search-bar input {
    width: 100%;
    border: 1px solid #e5e7eb;
    border-radius: 20px;
    padding: 6px 12px;
    font-size: 0.82rem;
    outline: none;
    background: #f9fafb;
}
.chat-search-bar input:focus { border-color: var(--primary); background: #fff; }
.chat-tabs { display: flex; gap: 4px; padding: 8px 12px 0; flex-shrink: 0; }
.chat-tab-btn {
    flex: 1;
    border: none;
    background: #f3f4f6;
    color: #6b7280;
    font-size: 0.78rem;
    font-weight: 600;
    padding: 7px 8px;
    border-radius: 8px 8px 0 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
}
.chat-tab-btn.active { background: #fff; color: var(--primary); box-shadow: 0 -1px 0 var(--primary) inset; }
.chat-tab-btn .chat-tab-dot {
    background: #ef4444;
    color: #fff;
    font-size: 0.62rem;
    font-weight: 700;
    border-radius: 10px;
    padding: 0 5px;
    min-width: 15px;
    line-height: 15px;
    text-align: center;
}
#chat-list-items { flex: 1; overflow-y: auto; }
.chat-rider-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 14px;
    cursor: pointer;
    border-bottom: 1px solid #f3f4f6;
    transition: background 0.1s;
    -webkit-tap-highlight-color: transparent;
}
.chat-rider-item:hover { background: #f8f9fa; }
.chat-rider-item.has-unread { background: #eff6ff; }
.chat-rider-item.has-unread:hover { background: #e3efff; }
.chat-rider-item.has-unread .chat-rider-name { font-weight: 700; }
.chat-rider-item.has-unread .chat-rider-last { color: var(--dark); font-weight: 600; }
.chat-rider-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--primary);
    color: #fff;
    font-size: 0.85rem;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.chat-rider-info { flex: 1; min-width: 0; }
.chat-rider-name { font-weight: 600; font-size: 0.85rem; color: var(--dark); }
.chat-rider-last { font-size: 0.75rem; color: #9ca3af; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-rider-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; }
.chat-rider-time { font-size: 0.68rem; color: #9ca3af; }
.chat-unread-dot {
    background: #ef4444;
    color: #fff;
    border-radius: 20px;
    font-size: 0.62rem;
    font-weight: 700;
    min-width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 4px;
}
.chat-list-empty { padding: 40px 16px; text-align: center; color: #9ca3af; font-size: 0.85rem; }
#chat-conv-view, #company-chat-conv-view { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
.chat-conv-messages {
    flex: 1;
    overflow-y: auto;
    padding: 12px;
    background: #f5f6fa;
    display: flex;
    flex-direction: column;
    gap: 5px;
}
.chat-bubble-wrap { display: flex; flex-direction: column; max-width: 80%; }
.chat-bubble-wrap.mine { align-self: flex-end; align-items: flex-end; }
.chat-bubble-wrap.theirs { align-self: flex-start; align-items: flex-start; }
.chat-bubble { padding: 8px 12px; border-radius: 14px; font-size: 0.83rem; line-height: 1.45; word-break: break-word; }
.chat-bubble-wrap.mine .chat-bubble { background: var(--primary); color: #fff; border-bottom-right-radius: 3px; }
.chat-bubble-wrap.theirs .chat-bubble { background: #fff; color: var(--dark); border-bottom-left-radius: 3px; box-shadow: 0 1px 3px rgba(0,0,0,0.07); }
.chat-bubble-meta { font-size: 0.62rem; color: #9ca3af; margin-top: 2px; padding: 0 2px; }
.chat-date-sep { text-align: center; font-size: 0.65rem; color: #9ca3af; margin: 6px 0 3px; font-weight: 500; }
.chat-conv-empty { flex: 1; display: flex; align-items: center; justify-content: center; color: #9ca3af; font-size: 0.82rem; text-align: center; padding: 20px; }
.chat-conv-input-bar { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: #fff; border-top: 1px solid #e9ecef; flex-shrink: 0; }
.chat-conv-input-bar textarea {
    flex: 1;
    border: 1px solid #e5e7eb;
    border-radius: 18px;
    padding: 7px 12px;
    font-size: 0.83rem;
    resize: none;
    outline: none;
    max-height: 80px;
    line-height: 1.4;
    font-family: inherit;
    background: #f9fafb;
}
.chat-conv-input-bar textarea:focus { border-color: var(--primary); background: #fff; }
.chat-conv-send-btn {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--primary);
    color: #fff;
    border: none;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.9rem;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.15s;
}
.chat-conv-send-btn:hover { background: var(--primary-dark); }
.chat-conv-send-btn:disabled { background: #c7d2de; cursor: not-allowed; }

/* ==========================================================================
   Rider (mobile-first, mantiene bottom nav — ver rider_base.html)
   ========================================================================== */
/* Alto del bottom-nav. Es una VARIABLE porque hay dos consumidores que tienen
   que moverse juntos: el hueco que le reserva el contenido (padding-bottom) y el
   borde inferior del shell del mapa (.rider-map-shell) — si divergen, el mapa se
   dibuja por debajo del nav o deja una franja muerta. Bajó de 64 a 54px: en las
   vistas de mapa cada píxel del chrome es mapa que el rider no ve, y con el
   icono más chico la fila sigue teniendo área de toque de sobra. */
:root { --rider-nav-h: 54px; }

body.role-rider main { padding-top: 16px; padding-bottom: calc(var(--rider-nav-h) + 16px); }
body.role-rider .page-wrap { max-width: 480px; padding: 0 16px 16px; }
body.role-rider .page-header { align-items: center; padding: 4px 0 16px; margin-bottom: 0; }

.rider-bnav {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    height: var(--rider-nav-h);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    box-sizing: content-box;   /* el área segura se SUMA, no come alto útil */
    background: #fff;
    border-top: 1px solid #e9ecef;
    display: flex;
    z-index: 1040;
    box-shadow: 0 -2px 10px rgba(0,0,0,0.08);
}
.rider-bnav a {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    color: #9ca3af;
    font-size: 0.6rem;
    font-weight: 500;
    gap: 2px;
    transition: color 0.15s;
    -webkit-tap-highlight-color: transparent;
}
.rider-bnav a i { font-size: 1.15rem; line-height: 1; }
.rider-bnav a.active,
.rider-bnav a:hover { color: var(--primary); }

.rider-chat-badge-bubble {
    position: absolute;
    top: -5px;
    right: -8px;
    background: #ef4444;
    color: #fff;
    border-radius: 20px;
    font-size: 0.58rem;
    font-weight: 700;
    min-width: 16px;
    height: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 3px;
    border: 2px solid #fff;
    line-height: 1;
}

/* ==========================================================================
   Utilidades (reemplazan estilos inline sueltos en templates)
   ========================================================================== */
.text-cp { color: var(--cp) !important; }
.text-gray-400 { color: var(--gray-400) !important; }
.text-gray-500 { color: var(--gray-500) !important; }
.text-gray-700 { color: var(--gray-700) !important; }
.fs-xs { font-size: 0.72rem; }
.fs-sm { font-size: 0.82rem; }
.fs-md { font-size: 0.875rem; }
.fw-medium { font-weight: 500; }

.cp-stack { display: flex; flex-direction: column; gap: 20px; }
.cp-hr { border-color: var(--gray-100); margin: 16px 0; }
.cp-notice {
    background: var(--cp-light);
    border: 1px solid #a7f3d0;
    border-radius: var(--radius-md);
    padding: 14px 16px;
    margin-bottom: var(--space-5);
    font-size: 0.85rem;
    color: var(--cp-text);
}
.cp-timeline-dot {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--cp);
    margin-top: 5px;
}
.truncate-160 { max-width: 160px; }
.truncate-200 { max-width: 200px; }
.fs-lg { font-size: 1.1rem; }
.w-180 { width: 180px; }
.mw-160 { max-width: 160px; }
.mw-220 { max-width: 220px; }

/* Timeline de historial (usado en detalle de pedido) */
.cp-timeline-list { padding: 8px 0; }
.cp-timeline-row { display: flex; gap: 12px; padding: 12px 18px; border-bottom: 1px solid var(--gray-50); }
.cp-timeline-row:last-child { border-bottom: none; }
.cp-timeline-body { flex: 1; }
.cp-timeline-title { font-size: 0.875rem; font-weight: 600; color: var(--gray-900); margin: 0; }
.cp-timeline-note { font-size: 0.78rem; color: var(--gray-500); margin: 2px 0 0; }
.cp-timeline-usernote {
    font-size: 0.78rem;
    background: #fffbe6;
    border-left: 3px solid #f5c518;
    padding: 4px 8px;
    border-radius: 4px;
    margin: 4px 0 0;
    color: #5a4a00;
}
.cp-timeline-time { font-size: 0.72rem; color: var(--gray-400); flex-shrink: 0; white-space: nowrap; }

/* ==========================================================================
   Dashboard del admin (movido desde admin/dashboard.html — antes inline)
   ========================================================================== */
.kpi-card {
    background: var(--surface);
    border: 1px solid var(--gray-200);
    border-radius: 12px;
    padding: 16px 18px;
    height: 100%;
}
.kpi-label {
    color: var(--gray-500);
    font-size: 0.8rem;
    font-weight: 500;
    margin-bottom: 6px;
}
.kpi-value {
    font-size: 1.7rem;
    font-weight: 700;
    color: var(--gray-900);
    margin-bottom: 4px;
}
.kpi-trend {
    font-size: 0.8rem;
    font-weight: 600;
    margin: 0;
}
.kpi-trend-positive { color: var(--success); }
.kpi-trend-negative { color: var(--danger); }
.kpi-trend-neutral { color: var(--gray-400); }
.kpi-trend-caption {
    color: var(--gray-400);
    font-weight: 400;
    margin-left: 4px;
}
.chart-box {
    position: relative;
    width: 100%;
}
.status-legend {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    justify-content: center;
}
.status-legend li {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.8rem;
    color: var(--gray-700);
}
.status-legend .dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
}
.kpi-sub {
    font-size: 0.72rem;
    color: var(--gray-400);
    font-weight: 500;
    margin: 2px 0 0;
    font-variant-numeric: var(--num);
}

/* KPI de ingresos: ojo para ocultar/mostrar los importes. */
.kpi-card-secret .kpi-label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
}
.kpi-eye {
    border: none;
    background: transparent;
    color: var(--gray-400);
    padding: 2px 4px;
    line-height: 1;
    cursor: pointer;
    border-radius: 6px;
    font-size: 0.95rem;
    transition: color .15s, background .15s;
}
.kpi-eye:hover { color: var(--gray-700); background: var(--gray-100); }
.kpi-secret { position: relative; }
.kpi-card-secret.is-hidden .kpi-secret { visibility: hidden; }
.kpi-card-secret.is-hidden .kpi-secret::after {
    content: "•••";
    visibility: visible;
    position: absolute;
    left: 0;
    top: 0;
    letter-spacing: 0.06em;
    color: var(--gray-400);
}

/* Fila de KPI-cards adaptable (empresa y otros): rellena el ancho sin
   depender de la grilla de 12 columnas de Bootstrap. */
.kpi-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(155px, 1fr));
    gap: var(--space-3);
    margin-bottom: var(--space-5);
}
/* Variante del portal empresa: acento verde en el ícono del KPI. */
.kpi-card.cp-scope .kpi-label i { color: var(--cp); }
/* KPI de alerta (ej. pedidos sin precio pendientes). */
.kpi-card.alert .kpi-value { color: #d97706; }

/* Alto del gráfico del dashboard de empresa (antes inline en dashboard.html) */
body.role-company .chart-box { height: 260px; }

/* ==========================================================================
   Torre de control del dispatcher (admin/dashboard.html)
   Disciplina de color: superficies y tipografía NEUTRAS; el color solo
   aparece para estado (badges .status-*), alerta (ámbar/rojo en los números
   de la barra operativa) y tendencia (flechas kpi-trend).
   ========================================================================== */

/* Encabezado de sección de dashboard */
.dash-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
    flex-wrap: wrap;
}
.dash-title { font-size: 1.35rem; font-weight: 700; color: var(--gray-900); margin: 0; }
.dash-title-sub { font-size: 0.82rem; color: var(--gray-500); margin: 2px 0 0; }
.dash-section-label {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--gray-400);
    margin: 0 0 10px;
}

/* Barra de estado operativo (siempre visible) */
.ops-bar {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 1px;
    background: var(--gray-200);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg);
    overflow: hidden;
    margin-bottom: var(--space-6);
}
.ops-cell {
    background: var(--surface);
    padding: 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.ops-label {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-500);
    display: flex;
    align-items: center;
    gap: 6px;
}
.ops-num {
    font-size: 2rem;
    font-weight: 700;
    line-height: 1.05;
    color: var(--gray-900);          /* neutro por defecto */
    font-variant-numeric: var(--num);
}
.ops-sub { font-size: 0.74rem; color: var(--gray-400); font-variant-numeric: var(--num); }
/* Color SOLO cuando es alerta */
.ops-cell.alert-amber .ops-num { color: #b45309; }
.ops-cell.alert-red .ops-num { color: var(--status-cancelado); }
.ops-cell.alert-amber { box-shadow: inset 3px 0 0 var(--status-pendiente); }
.ops-cell.alert-red { box-shadow: inset 3px 0 0 var(--status-cancelado); }

/* Panel de despacho: mapa (mock) + cola en vivo */
.dispatch-grid {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: var(--space-5);
    align-items: stretch;
    margin-bottom: var(--space-6);
}
@media (max-width: 1100px) {
    .dispatch-grid { grid-template-columns: 1fr; }
}

/* Mock de mapa — claramente etiquetado, sin datos reales (no hay lat/lng) */
.map-mock {
    position: relative;
    min-height: 320px;
    border-radius: var(--radius-lg);
    border: 1px solid var(--gray-200);
    background:
        linear-gradient(var(--gray-100) 1px, transparent 1px) 0 0 / 32px 32px,
        linear-gradient(90deg, var(--gray-100) 1px, transparent 1px) 0 0 / 32px 32px,
        var(--surface-sunken);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}
.map-mock-badge {
    position: absolute;
    top: 12px;
    left: 12px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--gray-900);
    color: #fff;
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    padding: 4px 10px;
    border-radius: 20px;
    opacity: 0.85;
}
.map-mock-center { text-align: center; color: var(--gray-400); padding: 20px; }
.map-mock-center i { font-size: 2.6rem; display: block; margin-bottom: 10px; opacity: 0.6; }
.map-mock-center p { margin: 0; font-size: 0.85rem; }
/* "pins" decorativos neutros del mock */
.map-mock-pin {
    position: absolute;
    width: 12px; height: 12px;
    border-radius: 50% 50% 50% 0;
    background: var(--gray-400);
    transform: rotate(-45deg);
    opacity: 0.5;
}

/* Cola de despacho */
.dispatch-queue {
    background: var(--surface);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg);
    display: flex;
    flex-direction: column;
    max-height: 420px;
    overflow: hidden;
}
.queue-scroll { overflow-y: auto; }
.queue-group-title {
    position: sticky;
    top: 0;
    background: var(--surface);
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--gray-400);
    padding: 12px 16px 6px;
    display: flex;
    align-items: center;
    gap: 6px;
    z-index: 1;
}
.queue-group-title .count {
    background: var(--gray-100);
    color: var(--gray-700);
    border-radius: 20px;
    padding: 0 8px;
    font-size: 0.7rem;
    font-variant-numeric: var(--num);
}
.queue-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    border-top: 1px solid var(--gray-100);
}
.queue-row-link { color: inherit; text-decoration: none; cursor: pointer; transition: background 0.12s ease; }
.queue-row-link:hover { background: var(--gray-100); color: inherit; }
.queue-row-main { flex: 1; min-width: 0; }
.queue-row-id { font-weight: 600; font-size: 0.85rem; color: var(--gray-900); }
.queue-row-id a { color: inherit; text-decoration: none; }
.queue-row-id a:hover { color: var(--primary); }
.queue-row-sub {
    font-size: 0.75rem;
    color: var(--gray-500);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.queue-row-age { font-size: 0.72rem; color: var(--gray-400); font-variant-numeric: var(--num); white-space: nowrap; }
.queue-assign {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}
.queue-assign select {
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-sm);
    padding: 5px 8px;
    font-size: 0.78rem;
    max-width: 130px;
    background: var(--surface);
    color: var(--gray-700);
}
.queue-assign button {
    border: none;
    background: var(--primary);
    color: #fff;
    border-radius: var(--radius-sm);
    padding: 5px 10px;
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}
.queue-assign button:hover { background: var(--primary-dark); }
.queue-empty { padding: 24px 16px; text-align: center; color: var(--gray-400); font-size: 0.82rem; }

/* Fila analítica compacta */
.analytics-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-5);
}
@media (max-width: 1100px) {
    .analytics-grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Cola de despacho (admin/orders.html)
   Tabla densa, cifras tabulares, agrupada por prioridad operativa.
   Color solo para estado/alerta.
   ========================================================================== */
.disp-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-bottom: var(--space-4);
}
.disp-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 10px;
    align-items: flex-end;
    margin-bottom: var(--space-4);
}
.disp-filters .form-label { margin-bottom: 2px; font-size: 0.72rem; color: var(--gray-500); }
.disp-filters .form-control, .disp-filters .form-select { font-size: 0.82rem; }

.disp-card {
    background: var(--surface);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.disp-table-wrap { overflow-x: auto; }
.disp-table { width: 100%; border-collapse: collapse; font-variant-numeric: var(--num); }
.disp-table thead th {
    text-align: left;
    font-size: 0.66rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--gray-400);
    padding: 9px 12px;
    border-bottom: 1px solid var(--gray-200);
    white-space: nowrap;
}
.disp-table thead th a { color: inherit; text-decoration: none; }
.disp-table thead th a:hover { color: var(--gray-700); }
.disp-table td {
    padding: 8px 12px;
    font-size: 0.82rem;
    color: var(--gray-700);
    border-bottom: 1px solid var(--gray-100);
    vertical-align: middle;
}
.disp-row { cursor: pointer; }
.disp-row:hover td { background: var(--gray-50); }
.disp-id { font-weight: 700; color: var(--gray-900); }
.disp-route { line-height: 1.35; min-width: 220px; }
.disp-route .leg {
    display: flex;
    align-items: center;
    gap: 5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 320px;
}
.disp-route .leg.from i { color: var(--gray-400); }
.disp-route .leg.to i { color: var(--status-entregado); }
.disp-route .leg.to { color: var(--gray-900); }
.disp-sub { font-size: 0.72rem; color: var(--gray-400); }
.disp-origin { color: var(--gray-400); font-size: 0.8rem; }

/* Encabezado de grupo (fila de tabla que ocupa todo el ancho) */
.disp-group-row td {
    background: var(--surface-sunken);
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--gray-500);
    padding: 9px 12px;
    border-bottom: 1px solid var(--gray-200);
    border-top: 1px solid var(--gray-200);
}
.disp-group-row .count {
    background: var(--gray-200);
    color: var(--gray-700);
    border-radius: 20px;
    padding: 0 8px;
    margin-left: 6px;
    font-variant-numeric: var(--num);
}
.disp-group-row.g-sin_asignar td { box-shadow: inset 3px 0 0 var(--status-pendiente); }
.disp-group-row.g-demoradas td { box-shadow: inset 3px 0 0 var(--status-cancelado); }
/* Separador por DÍA DE SERVICIO: la cola leída por día (sort=service_day). No
   es un grupo operativo, así que se distingue con su propio filo. */
.disp-group-row.g-dia td {
    box-shadow: inset 3px 0 0 var(--primary);
    text-transform: none;
    font-size: 0.78rem;
    color: var(--gray-700);
}
.disp-group-link {
    float: right;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
    color: var(--primary);
    text-decoration: none;
}
.disp-group-link:hover { text-decoration: underline; }

/* Asignación inline dentro de la fila */
.disp-assign { display: flex; align-items: center; gap: 5px; }
.disp-assign select {
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-sm);
    padding: 4px 6px;
    font-size: 0.76rem;
    max-width: 130px;
    background: var(--surface);
    color: var(--gray-700);
}
.disp-assign button {
    border: none;
    background: var(--primary);
    color: #fff;
    border-radius: var(--radius-sm);
    padding: 4px 9px;
    font-size: 0.76rem;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}
.disp-assign button:hover { background: var(--primary-dark); }

/* Timer 'en_sitio' en la fila */
.disp-timer { font-size: 0.72rem; font-weight: 600; display: inline-block; margin-top: 2px; }
.disp-timer.warn { color: #b45309; }
.disp-timer.over { color: var(--status-cancelado); }

/* ==========================================================================
   Formulario de pedido (crear / editar): grilla producto×parada,
   editor de paradas y mini-card de precio. La lógica cliente (IDs y names)
   NO cambia: esto es solo presentación.
   ========================================================================== */

/* Grilla producto × parada */
.item-grid { width: 100%; border-collapse: collapse; font-variant-numeric: var(--num); }
.item-grid thead th {
    text-align: left;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-500);
    padding: 8px 10px;
    border-bottom: 1px solid var(--gray-200);
    background: var(--surface-sunken);
    white-space: nowrap;
    vertical-align: bottom;
}
.item-grid tbody td {
    padding: 8px 10px;
    border-bottom: 1px solid var(--gray-100);
    vertical-align: top;
    font-size: 0.85rem;
}
.item-grid tbody tr:last-child td { border-bottom: none; }
.item-grid .cell-qty-input { max-width: 88px; text-align: right; }
.item-grid .row-total-cell { text-align: right; font-weight: 700; color: var(--gray-900); }
.item-grid .remove-stop-col-btn { font-weight: 700; }

/* Buscador + "Ver catálogo": una fila en escritorio, apilados a ancho completo
   en el teléfono. Reemplaza al `style="min-width: 220px"` inline que había en
   las tres superficies del formulario — a 360px ese mínimo forzaba el wrap del
   flex y dejaba el botón solo en su línea, alineado a la izquierda, leyéndose
   como si perteneciera a otra cosa. */
.product-picker-bar {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    align-items: stretch;
}
@media (min-width: 576px) {
    .product-picker-bar { flex-direction: row; align-items: flex-start; }
    .product-picker-bar #product-search { flex: 1 1 220px; min-width: 0; }
    .product-picker-bar > .btn { flex-shrink: 0; }
}

/* Selector de productos (buscador + "Ver catálogo"), compartido por el alta, la
   edición y el formulario de plantilla vía admin/_order_grid_js.html.
   Los resultados FLOTAN sobre la grilla, así que necesitan superficie propia:
   en el tema oscuro del admin los .list-group-item quedan transparentes y sin
   esto se leería la tabla de abajo a través de la lista. */
#product-search-results {
    background: var(--surface-raised);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-md);
    overflow: hidden;
}
#catalog-modal-list .list-group-item,
#product-search-results .list-group-item { font-size: 0.85rem; }
#catalog-modal-list { max-height: 52vh; overflow-y: auto; }

/* Desplegable de la LIBRETA de direcciones (address-picker.js, tanda E).
   Misma regla que los resultados de producto de arriba: la lista FLOTA sobre el
   formulario, así que lleva superficie propia — con el remapeo oscuro del admin
   los .list-group-item quedan transparentes y se leerían los campos de abajo a
   través de las direcciones. */
.address-picker-list {
    background: var(--surface-raised);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-md);
    overflow: hidden auto;
    max-height: 280px;
    z-index: 6;
}
.address-picker-group {
    padding: 6px 12px 2px;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--gray-500);
}
.address-picker-empty {
    padding: 10px 12px;
    font-size: 0.82rem;
    color: var(--gray-500);
}
.address-picker-item { display: block; text-align: left; font-size: 0.85rem; }
.address-picker-item.is-active { background: var(--gray-100); }
.address-picker-name { display: block; font-weight: 600; }
.address-picker-addr { display: block; }
.address-picker-meta {
    display: block;
    font-size: 0.75rem;
    color: var(--gray-500);
}
/* Entrada elegida: el aro dice que esos campos los llenó la libreta y que hay
   un id viajando en el formulario. Tocar cualquier campo lo suelta. */
.address-picker.is-picked .address-picker-input {
    border-color: var(--primary);
}

/* Editor de paradas adicionales (bloques generados por JS: .extra-stop-block).
   design-system.css se carga DESPUÉS de Bootstrap, así que estas reglas ganan
   sobre las utilidades .border/.rounded/.p-2 del markup sin !important. */
.extra-stop-block {
    background: var(--surface);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-md);
    padding: 12px;
    margin-bottom: 10px;
}
.extra-stop-block .stop-block-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gray-500);
}

/* Mini-card de precio (preview / último cálculo) */
.price-preview .cp-card-body,
.price-preview.cp-card-body { padding: 14px 16px; }
.price-preview-total {
    font-size: 1.15rem;
    font-weight: 700;
    color: var(--gray-900);
    font-variant-numeric: var(--num);
}
.price-line-list { list-style: none; margin: 0 0 8px; padding: 0; }
.price-line-list li {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    font-size: 0.82rem;
    color: var(--gray-600, var(--gray-700));
    padding: 2px 0;
    font-variant-numeric: var(--num);
}

/* --------------------------------------------------------------------------
   El formulario de pedido EN EL TELÉFONO
   --------------------------------------------------------------------------
   El dispatcher da de alta pedidos EN VIVO desde el móvil: esta pantalla no es
   una versión degradada de la de escritorio, es la superficie principal. Todo
   lo de abajo vive en `max-width: 575.98px` (el breakpoint donde Bootstrap deja
   de tener columnas `-sm`, o sea donde el formulario ya está en una sola
   columna) y NADA de esto llega al escritorio.
   -------------------------------------------------------------------------- */

/* La tarjeta de precio se pega solo cuando hay dos columnas al lado. Apilada,
   una tarjeta pegajosa al final del scroll tapa el botón de guardar. 992px es
   el mismo umbral que usa `.mess-form-grid` para volver a dos columnas: si se
   separan, la tarjeta se pega en una pantalla donde no hay nada al lado. */
@media (min-width: 992px) {
    .mess-form-sticky { position: sticky; top: var(--space-4); }
}

/* La grilla producto × parada NO cabe en un teléfono en cuanto hay más de una
   entrega, y comprimirla no es una opción: la columna del producto se partía
   letra a letra. El scroll horizontal es la respuesta correcta, pero tiene que
   estar CONTENIDO (nunca desbordar la página) y ser evidente. */
.item-grid-scroll { -webkit-overflow-scrolling: touch; }
.item-grid-hint { display: flex; align-items: center; gap: 6px; }
/* El `display: flex` de arriba GANA sobre el `display: none` que el navegador
   le da a `[hidden]` (una clase pesa más que un atributo), así que el aviso se
   veía igual cuando el JS lo apagaba. La regla no es defensiva: sin ella el
   `hidden` de `renderGrid()` no hace nada. */
.item-grid-hint[hidden] { display: none; }
@media (max-width: 575.98px) {
    /* Columna del producto: un suelo para que el nombre se lea, y un techo
       para que no se coma sola el ancho del scroll. */
    .item-grid thead th:first-child,
    .item-grid tbody td:first-child { min-width: 9.5rem; max-width: 12rem; }
    /* Las columnas de cantidad son el objetivo de toque real de la grilla. */
    .item-grid thead th + th,
    .item-grid tbody td + td { min-width: 5.5rem; }
    .item-grid .cell-qty-input { max-width: none; }
}

/* Barra de acciones del formulario. En escritorio es la fila de siempre
   (submit + cancelar, alineados a la izquierda). En el teléfono se pega al
   borde inferior con los dos botones a ancho completo: el alta mide varias
   pantallas de alto y buscar "Crear pedido" al final es el gesto que más se
   repite del día. `env(safe-area-inset-bottom)` la levanta de la barra de
   gestos de iOS — funciona porque `base.html` emite `viewport-fit=cover`. */
.form-actionbar { display: flex; gap: var(--space-4); }
.form-actionbar .btn { flex-shrink: 0; }
@media (max-width: 575.98px) {
    .form-actionbar {
        position: sticky;
        bottom: 0;
        z-index: 10;
        gap: var(--space-2);
        margin: var(--space-4) calc(var(--space-3) * -1) 0;
        padding: var(--space-3);
        padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
        background: var(--surface);
        border-top: 1px solid var(--gray-200);
    }
    /* `.form-touch .btn:not(.btn-link)` de más abajo pesa MÁS que un
       `.form-actionbar .btn` pelado y le impondría sus 40px: los botones
       principales van con la misma cuenta de selectores para ganar por orden. */
    .form-touch .form-actionbar .btn:not(.btn-link),
    .form-actionbar .btn {
        flex: 1 1 0;
        min-height: 48px;
        padding-left: var(--space-2);
        padding-right: var(--space-2);
    }
}

@media (max-width: 575.98px) {
    /* Aire interno de las tarjetas: 20px por lado sobre 360px es el 11% del
       ancho, y el formulario son ~8 tarjetas anidando paradas dentro. */
    .cp-card-body { padding: var(--space-3); }
    .extra-stop-block { padding: var(--space-2); }

    /* iOS hace ZOOM automático al enfocar un input de menos de 16px, y el admin
       usa `form-control-sm` en casi todo el formulario de pedido (las paradas
       extra van enteras en `compact`). El zoom deja la página desencuadrada y
       obliga a un pellizco por campo. 16px es el mínimo que lo evita; la altura
       de 44px es el objetivo de toque.
       Se marca con `.form-touch` en el <form>, no se aplica a todo el admin:
       el resto de las pantallas del portal tiene su propia tanda y subirles la
       altura de los controles les cambia el layout de los filtros. Cuando les
       toque, heredan esto poniéndose la clase. */
    .form-touch .form-control,
    .form-touch .form-select,
    .form-touch .form-control-sm,
    .form-touch .form-select-sm {
        font-size: 16px;
        min-height: 44px;
    }
    .form-touch textarea.form-control { min-height: 66px; }
    /* El input de la planta pierde su tope: con Planta a ancho completo, el
       select y el número se reparten la fila y 5rem lo dejaban de sobra. */
    .form-touch .address-planta-num { max-width: 6rem; }

    /* Botones de acción del formulario (quitar parada, quitar producto,
       agregar producto/parada, ver catálogo). `btn-sm` deja ~31px de alto.
       `.btn-link` queda FUERA: son enlaces en línea ("+ nueva unidad", la × de
       la cabecera de una columna) y estirarlos a 40px rompe la fila. */
    .form-touch .btn:not(.btn-link) { min-height: 40px; }
    /* Las listas flotantes (libreta de direcciones, resultados de producto,
       catálogo) son lo que más se toca del alta y sus filas eran de ~30px. */
    .form-touch .list-group-item-action,
    #catalog-modal .list-group-item-action { min-height: 44px; }
}

/* De `md` para abajo el tope de 5rem del número de planta sigue valiendo (la
   fila es de dos columnas, no de cinco). No se declara dentro del media query
   de arriba porque el tope es el comportamiento por DEFECTO, no la excepción. */
.address-planta-num { max-width: 5rem; }

/* ==========================================================================
   Detalle de pedido (admin/order_detail.html): stepper de estado + desglose
   ========================================================================== */
.status-stepper {
    display: flex;
    align-items: flex-start;
    margin: 4px 0 var(--space-5);
}
.stepper-node {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    flex: 1;
    min-width: 56px;
    position: relative;
}
.stepper-line {
    position: absolute;
    top: 13px;
    right: 50%;
    width: 100%;
    height: 2px;
    background: var(--gray-200);
    z-index: 0;
}
.stepper-node:first-child .stepper-line { display: none; }
.stepper-dot {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 2px solid var(--gray-300);
    background: var(--surface);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
    color: var(--gray-400);
    z-index: 1;
}
.stepper-label {
    font-size: 0.68rem;
    color: var(--gray-400);
    text-align: center;
    line-height: 1.2;
}
.stepper-node.done .stepper-dot { border-color: var(--status-entregado); color: var(--status-entregado); }
.stepper-node.done .stepper-line { background: var(--status-entregado); }
.stepper-node.current .stepper-dot { border-color: var(--primary); background: var(--primary); color: #fff; }
.stepper-node.current .stepper-line { background: var(--status-entregado); }
.stepper-node.current .stepper-label { color: var(--gray-900); font-weight: 700; }
.stepper-node.cancelled .stepper-dot { border-color: var(--status-cancelado); background: var(--status-cancelado); color: #fff; }
.stepper-node.cancelled .stepper-label { color: var(--status-cancelado); font-weight: 700; }
/* Variante verde del portal empresa (identidad --cp) */
.status-stepper.cp-scope .stepper-node.done .stepper-dot { border-color: var(--cp); color: var(--cp); }
.status-stepper.cp-scope .stepper-node.done .stepper-line,
.status-stepper.cp-scope .stepper-node.current .stepper-line { background: var(--cp); }
.status-stepper.cp-scope .stepper-node.current .stepper-dot { border-color: var(--cp); background: var(--cp); }

/* Día de servicio del pedido (detalle) — va bajo el título, donde antes se
   repetía el estado. Un pedido "al vuelo" (sin scheduled_date) se ve en gris:
   se hace el día en que se creó, pero nadie lo comprometió para ese día. */
.order-day-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    border-radius: 20px;
    background: var(--primary-light);
    color: var(--primary-dark);
    border: 1px solid rgba(74, 144, 226, 0.25);
    font-size: 0.82rem;
    font-weight: 600;
    font-variant-numeric: var(--num);
}
.order-day-chip .order-day-rel {
    font-weight: 500;
    opacity: 0.75;
    text-transform: lowercase;
}
.order-day-chip .order-day-rel::before { content: '· '; }
.order-day-chip.is-loose {
    background: var(--gray-100);
    color: var(--gray-700);
    border-color: var(--gray-200);
}
.order-day-chip.is-late {
    background: #fdecea;
    color: #a93226;
    border-color: rgba(231, 76, 60, 0.3);
}

/* Franja horaria comprometida de una parada (Stop.window_from/window_to) */
.win-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 8px;
    border-radius: 20px;
    background: var(--gray-100);
    color: var(--gray-700);
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    font-variant-numeric: var(--num);
}
.win-chip i { color: var(--gray-400); }

/* Parada (detalle) */
.stop-block { padding: 12px 0; border-bottom: 1px solid var(--gray-100); }
.stop-block:last-child { border-bottom: none; }
.stop-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.stop-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--gray-100);
    color: var(--gray-700);
    font-size: 0.72rem;
    font-weight: 700;
    flex-shrink: 0;
    font-variant-numeric: var(--num);
}
.stop-head .stop-addr { font-weight: 600; color: var(--gray-900); font-size: 0.88rem; }

/* Desglose de precio */
.price-breakdown { width: 100%; font-variant-numeric: var(--num); }
.price-breakdown td { padding: 5px 0; font-size: 0.85rem; color: var(--gray-700); }
.price-breakdown td:last-child { text-align: right; white-space: nowrap; }
.price-breakdown tr.total td {
    border-top: 1px solid var(--gray-200);
    font-weight: 700;
    padding-top: 9px;
    color: var(--gray-900);
    font-size: 0.95rem;
}

/* ==========================================================================
   Dashboard del rider (movido desde rider/dashboard.html — antes inline)
   ========================================================================== */
.dash-greeting {
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--dark);
    margin: 0;
}

/* ===== Session pill (jornada activa) ===== */
.session-pill-wrap { position: relative; }
.session-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: #eaf3fe;
    border: none;
    border-radius: 20px;
    padding: 6px 14px 6px 10px;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--primary);
    cursor: pointer;
    white-space: nowrap;
}
.session-pill .dot {
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--success);
    flex-shrink: 0;
}
.session-pill.paused .dot { background: var(--warning); }
.session-pill .pill-chevron {
    font-size: 0.7rem;
    transition: transform 0.2s;
}
.session-pill.open .pill-chevron { transform: rotate(180deg); }

.session-pill-dropdown {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.12);
    z-index: 200;
    overflow: hidden;
    min-width: 200px;
}
.session-pill-dropdown form { margin: 0; }
.session-pill-dropdown button {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 13px 16px;
    background: none;
    border: none;
    border-bottom: 1px solid #f3f4f6;
    font-size: 0.88rem;
    font-weight: 500;
    color: var(--dark);
    cursor: pointer;
    text-align: left;
    transition: background 0.12s;
}
.session-pill-dropdown form:last-child button { border-bottom: none; }
.session-pill-dropdown button:hover { background: #f8f9fa; }
.session-pill-dropdown button.danger { color: var(--danger); }

/* ===== PANTALLA 1: sin jornada ===== */
.no-session-screen {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 56px 16px 32px;
}
.no-session-icon {
    font-size: 4.5rem;
    color: var(--primary);
    opacity: 0.25;
    margin-bottom: 20px;
}
.no-session-title {
    font-size: 1.2rem;
    font-weight: 700;
    color: var(--dark);
    margin-bottom: 8px;
}
.no-session-sub {
    font-size: 0.9rem;
    color: #6b7280;
    margin-bottom: 24px;
    max-width: 280px;
}
.start-session-btn {
    font-size: 1rem;
    padding: 14px 40px;
    border-radius: 14px;
}

/* ===== Pedido activo — específico ===== */
#activeOrderCard { cursor: pointer; }
.highlight-card:hover { transform: none; box-shadow: 0 4px 16px rgba(74,144,226,0.35); }
.aoc-label {
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    opacity: 0.85;
    text-transform: uppercase;
}
.aoc-top {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 12px;
}
.aoc-id {
    font-size: 1.2rem;
    font-weight: 700;
}
.aoc-pickup {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    font-size: 0.9rem;
    margin-bottom: 10px;
    opacity: 0.95;
}
.aoc-pickup i { flex-shrink: 0; margin-top: 2px; }
.aoc-status-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 14px;
}
.aoc-status-badge {
    background: rgba(255,255,255,0.2);
    border-radius: 8px;
    padding: 3px 10px;
    font-size: 0.8rem;
    font-weight: 600;
}
.aoc-timer {
    font-size: 0.8rem;
    font-weight: 600;
    opacity: 0.9;
}
.aoc-timer.overdue {
    color: #ff8a8a;
    opacity: 1;
}
.aoc-details {
    border-top: 1px solid rgba(255,255,255,0.2);
    margin-bottom: 14px;
}
.aoc-details summary {
    padding: 10px 0 0;
    font-size: 0.82rem;
    font-weight: 600;
    opacity: 0.9;
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 6px;
    -webkit-tap-highlight-color: transparent;
    user-select: none;
}
.aoc-details summary::-webkit-details-marker { display: none; }
.aoc-details summary .det-chevron { font-size: 0.7rem; transition: transform 0.2s; }
.aoc-details[open] summary .det-chevron { transform: rotate(180deg); }
.aoc-detail-body {
    padding: 10px 0 4px;
    font-size: 0.85rem;
}
.aoc-detail-body p { margin-bottom: 6px; opacity: 0.95; }
.aoc-detail-body ul {
    margin: 4px 0 6px 0;
    padding-left: 16px;
    opacity: 0.9;
}
.aoc-detail-body li { margin-bottom: 2px; }
.aoc-multi-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background: rgba(255,255,255,0.2);
    border-radius: 6px;
    padding: 3px 9px;
    font-size: 0.75rem;
    font-weight: 600;
    margin-top: 6px;
    width: fit-content;
}
.aoc-route-badge {
    display: inline-block;
    background: rgba(255,255,255,0.2);
    border-radius: 6px;
    padding: 2px 8px;
    font-size: 0.78rem;
    font-weight: 500;
    margin-top: 4px;
}
.aoc-action-btn {
    width: 100%;
    background: #fff;
    color: var(--primary);
    border: none;
    border-radius: 10px;
    font-weight: 700;
    font-size: 0.95rem;
    padding: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
}
.aoc-action-btn:hover {
    background: #f0f4ff;
    color: var(--primary);
    transform: none;
    box-shadow: none;
}
/* Aviso en el lugar del botón de acción: la parada existe y hay que ir hasta
   ahí, pero no se opera desde este lado (punto de traspaso del rider origen).
   Ocupa el mismo hueco que el botón para que la tarjeta no "salte". */
.aoc-hint {
    background: #f5f3ff;
    color: #5b21b6;
    border-radius: 10px;
    padding: 11px 12px;
    margin: 0;
    font-size: 0.85rem;
    line-height: 1.35;
    display: flex;
    align-items: flex-start;
    gap: 8px;
}
/* Variante ÁMBAR: falta algo que el rider tiene que resolver antes (el pedido
   todavía no está recogido), distinto del morado informativo del traspaso. */
.aoc-hint--warn { background: #fef3c7; color: #92400e; }
/* Dentro del aviso ámbar el círculo verde saltaría: se lee como parte de la
   etiqueta, con el mismo ámbar en negativo. */
.aoc-hint--warn .seq-chip { background: #92400e; color: #fef3c7; }
/* Sobre la tarjeta de color del Inicio, donde el morado no contrastaría. Va
   ÚLTIMO a propósito: gana sobre las variantes de color de arriba. */
.aoc-hint--on-color { background: rgba(255, 255, 255, 0.18); color: #fff; }

/* ===== Gráfico semanal (rider) ===== */
.weekly-chart-card {
    background: #fff;
    border-radius: 12px;
    padding: 14px 16px 8px;
    margin: 18px 0;
    box-shadow: 0 1px 4px rgba(0,0,0,0.06);
}
.weekly-chart-title {
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--dark);
    margin: 0 0 10px;
}
.weekly-chart-box { height: 200px; position: relative; }

/* ===== Cumplimiento de turno (tarjeta destacada del dashboard rider) ===== */
.shift-progress {
    display: block;
    background: #fff;
    border: 1px solid var(--gray-200);
    border-radius: 12px;
    padding: 12px 14px;
    margin-bottom: 14px;
    text-decoration: none;
    color: inherit;
    -webkit-tap-highlight-color: transparent;
}
.shift-progress:active { background: #f9fafb; }
.shift-progress-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 8px;
}
.shift-progress-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--gray-500);
    display: flex;
    align-items: center;
    gap: 6px;
}
.shift-progress-pct { font-size: 0.95rem; font-weight: 700; color: var(--gray-900); font-variant-numeric: var(--num); }
.shift-progress-pct.ok { color: var(--cp); }
.shift-progress-bar { height: 8px; border-radius: 6px; background: var(--gray-100); overflow: hidden; }
.shift-progress-fill { height: 100%; border-radius: 6px; background: var(--primary); transition: width 0.3s; }
.shift-progress-fill.ok { background: var(--cp); }
.shift-progress-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 0.75rem;
    color: var(--gray-500);
    margin-top: 7px;
    font-variant-numeric: var(--num);
}
.shift-progress-meta .delta.pos { color: var(--cp); font-weight: 600; }
.shift-progress-meta .delta.neg { color: #b45309; font-weight: 600; }
.shift-progress-times {
    font-size: 0.75rem;
    color: var(--gray-500);
    margin-top: 7px;
    border-top: 1px solid var(--gray-100);
    padding-top: 7px;
    font-variant-numeric: var(--num);
}

/* Chip informativo (turno planificado en pantalla "sin jornada") */
.chip.chip-info { background: #f0f9ff; border: 1px solid #bae6fd; color: #0369a1; }

/* ==========================================================================
   Rider: drag-and-drop de "Próximos" + cards de parada compartidas.
   (Las clases del viejo route_detail.html/routes.html del rider se retiraron
   al migrar "Mis Rutas" al modelo por-parada; lo que queda acá lo usan la
   pestaña "Próximos" de Mis Pedidos, order_detail y las vistas de admin.)
   ========================================================================== */

/* Estados de drag-and-drop (SortableJS de "Próximos") */
.sortable-ghost {
    opacity: 0.4;
    background: #eef2ff !important;
    border: 2px dashed #c7d2fe;
    border-radius: 12px;
}
.sortable-ghost * { visibility: hidden; }
.sortable-drag {
    opacity: 0.98 !important;
    box-shadow: 0 12px 32px rgba(0,0,0,0.22) !important;
    background: #fff !important;
}
/* Elemento agarrado (aún en su lugar): lo elevamos sutilmente para dar
   feedback de "listo para arrastrar". */
.sortable-chosen {
    box-shadow: 0 0 0 2px var(--primary), 0 6px 18px rgba(74,144,226,0.22) !important;
}

/* ===== Card de parada (compartida por order_detail/orders del rider y admin) =====
   Sobrevive a la poda del route_detail del rider: .stop-card y .stop-addr las
   siguen usando rider/order_detail, rider/orders y las vistas de admin. */
.stop-card {
    display: flex;
    align-items: stretch;
    gap: 12px;
    background: #fff;
    border-radius: 12px;
    padding: 14px 16px;
    margin-bottom: 10px;
    box-shadow: 0 1px 4px rgba(0,0,0,0.06);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.1s, box-shadow 0.12s;
}
.stop-card.is-current { box-shadow: 0 0 0 2px var(--primary), 0 1px 4px rgba(0,0,0,0.06); }

.stop-addr {
    font-size: 0.83rem;
    color: #374151;
    margin-bottom: 5px;
    display: flex;
    align-items: flex-start;
    gap: 6px;
}
.stop-addr i { flex-shrink: 0; margin-top: 2px; }
.order-card-stops-list li.delivered { color: #6b7280; text-decoration: line-through; }

/* ══════════════════════════════════════════════════════════════════════════
   MAPA (MapLibre + Protomaps) — dispatcher + rider
   ═════════════════════════════════════════════════════════════════════════ */

/* ── Dashboard del dispatcher: mapa embebido en el panel de despacho ──────── */
.dispatch-map-wrap {
    position: relative;
    /* Altura DEFINIDA (no solo min-height): con inset:0 el mapa necesita que su
       contenedor tenga altura resoluble o MapLibre se inicializa a 0px y queda
       en blanco (en la grilla de 2 columnas el stretch ya lo estiraba, pero en
       una sola columna min-height no alcanzaba). */
    height: 400px;
    border-radius: var(--radius-lg);
    border: 1px solid var(--gray-200);
    overflow: hidden;
}
/* height/width 100% (no solo inset:0): maplibre-gl.css declara
   `.maplibregl-map { position: relative }` con la misma especificidad y se
   carga después, pisando el `position:absolute` — sin él, inset:0 no daba
   altura y el mapa colapsaba a 0px. Con el wrap de altura definida, el 100%
   resuelve aunque MapLibre fuerce position:relative. */
.dispatch-map { position: absolute; inset: 0; width: 100%; height: 100%; }
.dispatch-map-expand {
    position: absolute;
    z-index: 2;
    top: 10px; left: 10px;
    display: inline-flex; align-items: center; gap: 6px;
    background: rgba(11, 18, 32, 0.85);
    color: #fff;
    font-size: 0.7rem; font-weight: 600;
    padding: 5px 10px;
    border-radius: 20px;
    text-decoration: none;
}
.dispatch-map-expand:hover { background: #0b1220; color: #fff; }

/* ── Pantalla completa del dispatcher ─────────────────────────────────────── */
.map-screen { display: flex; flex-direction: column; gap: var(--space-3); }
.map-screen-bar {
    display: flex; align-items: center; justify-content: flex-start;
    flex-wrap: wrap; gap: var(--space-2);
}
.map-city-switch .btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }

/* ── El DÍA en la barra del mapa (navegación + alcance + contador) ──────────
   El mapa del dispatcher se mira POR DÍA: el día es el contexto, no un filtro
   más, y por eso vive arriba del todo y no entre los chips del panel. Mismo
   gesto que el calendario (‹ día ›) para que se opere sin volver a aprenderlo. */
.map-daynav { display: flex; align-items: center; gap: 4px; }
.map-daynav-label {
    border: 1px solid var(--gray-300); background: var(--surface);
    border-radius: var(--radius-sm); cursor: pointer;
    font-size: 0.82rem; font-weight: 700; color: var(--gray-700);
    padding: 3px 10px; min-width: 132px; text-align: center;
    text-transform: capitalize; white-space: nowrap;
}
.map-daynav-label:hover { border-color: var(--primary); color: var(--primary); }
/* El <input type="date"> es el selector real; el botón de arriba lo abre. Se
   deja en el DOM (y no display:none) porque showPicker() necesita un campo
   visible en varios navegadores: se esconde por tamaño, no por visibilidad. */
.map-daynav-input {
    width: 0; height: 0; padding: 0; margin: 0; border: 0;
    opacity: 0; pointer-events: none;
}
.map-span-switch .btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }
.map-daycount {
    display: flex; align-items: center; gap: 6px;
    font-size: 0.78rem; color: var(--gray-600);
    padding: 3px 10px; border-radius: 999px; background: var(--gray-100);
}
.map-daycount-main { font-weight: 700; color: var(--gray-800); }
.map-daycount-sep { color: var(--gray-400); }
.map-daycount-warn.is-on { color: #b45309; font-weight: 700; }
.map-daycount-late {
    display: inline-flex; align-items: center; gap: 4px;
    color: #92400e; background: #fde68a; font-weight: 700;
    border-radius: 999px; padding: 0 8px; cursor: help;
}
@media (max-width: 700px) {
    .map-daynav-label { min-width: 0; }
    .map-daycount { width: 100%; justify-content: center; }
}
.map-screen-body {
    display: flex;
    gap: var(--space-3);
    align-items: stretch;
    min-height: 0;
}
.map-screen-canvas {
    flex: 1;
    min-width: 0;
    height: calc(100vh - 190px);
    min-height: 360px;
    border-radius: var(--radius-lg);
    border: 1px solid var(--gray-200);
    overflow: hidden;
}
.map-legend-panel {
    /* El ancho del panel se declara ACÁ y en un solo sitio. Estaba escrito dos
       veces —240px acá y `.map-legend-panel { width: 380px }` unas líneas más
       abajo, DESPUÉS del media query de 900px—, y esa segunda copia, con la
       misma especificidad y más tarde en el archivo, GANABA también dentro del
       media query: en un teléfono de 360px el panel medía 380px y desbordaba la
       página entera hacia la derecha. El 380 era el ancho real de escritorio, así
       que se conserva. */
    width: 380px;
    flex-shrink: 0;
    background: var(--surface);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg);
    padding: var(--space-3);
    overflow-y: auto;
    height: calc(100vh - 190px);
}
.map-legend-title {
    font-size: 0.8rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.03em; color: var(--gray-500);
    margin-bottom: var(--space-2);
}
.map-legend-item { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 0.88rem; }
.map-legend-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.map-legend-empty { color: var(--gray-400); font-size: 0.85rem; }
.map-legend-warn {
    margin-top: var(--space-3);
    padding-top: var(--space-2);
    border-top: 1px solid var(--gray-200);
    color: #b45309;
    font-size: 0.8rem;
}
/* Aviso de "tenés paradas sin ruta" en el panel del rider: va pegado a sus
   rutas (no al pie del panel), así que sin la línea de arriba ni el margen. */
.map-legend-loose {
    margin: 4px 0 0; padding: 6px 8px; border-top: 0;
    background: color-mix(in srgb, #b45309 8%, transparent);
    border-radius: var(--radius-sm); line-height: 1.3;
}
@media (max-width: 900px) {
    .map-screen-body { flex-direction: column; }
    .map-screen-canvas { height: 60vh; }
    .map-legend-panel { width: auto; height: auto; }
    .map-rider-block { --rider-color: var(--primary); }
}

/* ── Panel del dispatcher: bloques por rider ───────────────────────────────── */

/* ── Bloque de filtros del panel (encima de la lista de riders) ─────────────
   Chips, no una matriz de casillas: lo que está activo se ve encendido y se
   toca de una. Va pegado arriba (sticky) porque la lista de abajo puede ser
   larga y perder los filtros al hacer scroll es perder el contexto. */
/* Modo UNA RUTA del dispatcher (`?route=<id>`, "Ver en el mapa" del detalle de
   la ruta): ocupa el lugar del bloque de filtros, porque en ese modo NINGUNO se
   aplica. Finalizada va en verde y con el aviso de que es solo información: el
   panel de al lado tampoco dibuja sus acciones. */
.map-routeview {
    position: sticky; top: calc(var(--space-3) * -1); z-index: 3;
    display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
    background: var(--surface);
    padding: var(--space-2) 0;
    margin-bottom: var(--space-2);
    border-bottom: 1px solid var(--gray-200);
}
.map-routeview-title {
    display: flex; align-items: center; gap: 6px;
    font-weight: 700; color: var(--gray-800);
}
.map-routeview-sub { font-size: 0.78rem; color: var(--gray-600); }
.map-routeview-tag {
    font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.04em; color: #166534; background: #dcfce7;
    border-radius: 999px; padding: 1px 8px;
}
.map-routeview.is-finished .map-routeview-title { color: #166534; }
/* Una ruta que solo se mira: el bloque se atenúa apenas para que se lea de un
   vistazo que ahí no hay nada que tocar (las acciones ni siquiera existen). */
.map-route.is-readonly > .map-route-head { opacity: 0.92; }
/* `[hidden]` no gana contra el display de la clase: sin esto, esconder la
   navegación por día en el modo UNA RUTA no la escondía. */
.map-daynav[hidden], .map-daycount[hidden] { display: none; }

.map-filters {
    position: sticky; top: calc(var(--space-3) * -1); z-index: 3;
    display: flex; flex-direction: column; gap: 6px;
    background: var(--surface);
    padding: var(--space-2) 0;
    margin-bottom: var(--space-2);
    border-bottom: 1px solid var(--gray-200);
}
.map-filters-label {
    font-size: 0.64rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.04em; color: var(--gray-400); flex-shrink: 0;
}
.map-preset-row, .map-chip-row { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
/* Vistas rápidas de DOS EN DOS. Con flex-wrap las cuatro píldoras rompían por
   donde caía su ancho ("Recogidas pendientes" arrastraba a la de al lado) y el
   bloque se leía desordenado; la grilla fija las dos columnas, así que cada par
   comparte línea y ancho pase lo que pase con el texto. La etiqueta "Vistas
   rápidas" es su propia línea: dentro de la fila competía con las píldoras. */
.map-preset-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.map-preset {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 0.7rem; font-weight: 600; line-height: 1.5;
    color: var(--gray-600); background: var(--gray-100);
    border: 1px solid transparent; border-radius: 999px;
    padding: 2px 9px; cursor: pointer;
}
/* En la grilla, la píldora ocupa su celda entera y el texto envuelve si hace
   falta: no se corta ni desborda la columna. */
.map-preset-grid .map-preset {
    justify-content: center; text-align: center; min-width: 0;
    padding: 3px 8px; overflow-wrap: anywhere;
}
.map-preset:hover { color: var(--primary); border-color: var(--primary); }
/* Un preset ENCENDIDO no es una vista cerrada: solo dice "el estado coincide
   con esta combinación". En cuanto se toca un chip a mano, se apaga solo. */
.map-preset.is-on {
    color: #fff; background: var(--primary); border-color: var(--primary);
}
.map-filter-rider { display: flex; align-items: center; gap: 6px; margin: 0; }
.map-filter-rider .form-select {
    font-size: 0.76rem; padding: 2px 22px 2px 8px;
    flex: 1; min-width: 0;   /* ocupa la fila: el nombre del rider se lee entero */
}
.map-chip {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 0.72rem; font-weight: 600; line-height: 1.5;
    color: var(--gray-600); background: var(--surface);
    border: 1px solid var(--gray-300); border-radius: 999px;
    padding: 2px 9px; cursor: pointer;
}
.map-chip:hover { border-color: var(--primary); color: var(--primary); }
.map-chip.is-on {
    color: var(--primary); border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 12%, var(--surface));
}
.map-chip-sep { width: 1px; align-self: stretch; background: var(--gray-200); margin: 0 2px; }

.map-panel-toolbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-2); margin-bottom: var(--space-2); flex-wrap: wrap;
}
.map-panel-hidetoggle {
    display: flex; align-items: center; gap: 5px;
    font-size: 0.74rem; color: var(--gray-500); cursor: pointer; margin: 0;
}
.map-rider-block {
    border: 1px solid var(--gray-200);
    border-left: 4px solid var(--rider-color);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--rider-color) 8%, var(--surface));
    margin-bottom: var(--space-2);
    overflow: hidden;
}
.map-rider-block.is-selected {
    box-shadow: 0 0 0 2px var(--rider-color);
    background: color-mix(in srgb, var(--rider-color) 16%, var(--surface));
}
.map-rider-head {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 10px; cursor: pointer; margin: 0; user-select: none;
}
.map-rider-name { font-weight: 600; font-size: 0.9rem; flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.map-rider-count {
    font-size: 0.72rem; font-weight: 700;
    background: var(--rider-color); color: #fff;
    padding: 1px 7px; border-radius: 999px; flex-shrink: 0;
}
.map-rider-orders { padding: 0 8px 8px; display: flex; flex-direction: column; gap: 6px; }
.map-order {
    background: var(--surface); border: 1px solid var(--gray-200);
    border-radius: var(--radius-sm); padding: 6px 8px; font-size: 0.8rem;
    cursor: pointer;
}
.map-order.is-flash { animation: mapOrderFlash 1.2s ease; }
@keyframes mapOrderFlash {
    0%, 40% { box-shadow: 0 0 0 2px var(--rider-color); }
    100% { box-shadow: none; }
}
.map-order-multi > summary { list-style: none; cursor: pointer; }
.map-order-multi > summary::-webkit-details-marker { display: none; }
.map-order-top { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.map-order-seq, .map-del-seq {
    font-size: 0.7rem; font-weight: 700; color: var(--gray-600);
    background: var(--gray-100); border-radius: 4px; padding: 0 5px; flex-shrink: 0;
}
.map-order-addr {
    color: var(--gray-500); font-size: 0.75rem; margin-top: 2px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.map-order-addr .bi { color: var(--gray-400); }
.map-order-multi-hint { font-size: 0.7rem; color: var(--gray-400); display: block; margin-top: 3px; }
.map-order-deliveries { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
.map-del { display: flex; align-items: center; gap: 6px; font-size: 0.76rem; }
.map-del-addr { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.map-status {
    font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
    padding: 1px 6px; border-radius: 4px; flex-shrink: 0;
    background: var(--gray-100); color: var(--gray-600);
}
.map-status-pendiente { background: #e5e7eb; color: #374151; }
.map-status-asignado { background: #dbeafe; color: #1e40af; }
.map-status-en_sitio { background: #fef3c7; color: #92400e; }
.map-status-recogido { background: #cffafe; color: #155e75; }
.map-status-en_camino { background: #ffedd5; color: #9a3412; }
.map-status-entregado { background: #d1fae5; color: #065f46; }
.map-status-fallida { background: #fee2e2; color: #991b1b; }
.map-status-traspasada { background: #ede9fe; color: #5b21b6; }
.map-order-fail {
    font-size: 0.66rem; font-weight: 700; text-transform: uppercase;
    color: #991b1b; background: #fee2e2; padding: 1px 6px; border-radius: 4px;
}

/* ── Panel del dispatcher: acordeón rider→rutas + reorden (Tanda 2) ────────── */
.map-panel-msg {
    font-size: 0.76rem; padding: 5px 8px; border-radius: var(--radius-sm);
    margin-bottom: var(--space-2);
    background: #d1fae5; color: #065f46;
}
.map-panel-msg.is-error { background: #fee2e2; color: #991b1b; }
.map-rider-routes { padding: 0 8px 8px; display: flex; flex-direction: column; gap: 6px; }
.map-route {
    background: var(--surface); border: 1px solid var(--gray-200);
    border-radius: var(--radius-sm); overflow: hidden;
}
.map-route-head {
    display: flex; align-items: center; gap: 6px;
    padding: 5px 8px; cursor: pointer; list-style: none; user-select: none;
    font-size: 0.78rem;
}
.map-route-head::-webkit-details-marker { display: none; }
.map-route-name { font-weight: 700; flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Cabecera de ruta a DOS líneas — SOLO el panel del dispatcher (`is-two-line`).
   Identidad arriba (etiqueta + NOMBRE + avance), contexto y acciones abajo
   (fecha + botones). Antes iba todo en una sola línea y el nombre —lo único por
   lo que se reconoce una ruta— competía con seis elementos y se cortaba.
   La clase es obligatoria: `.map-route-head` a secas la comparte la hoja del
   RIDER, y volver esa regla de dos líneas le desarma su cabecera. */
.map-route-head.is-two-line { flex-direction: column; align-items: stretch; gap: 3px; padding: 6px 8px; }
.map-route-l1 { display: flex; align-items: center; gap: 6px; min-width: 0; }
.map-route-l2 { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.map-route-actions { display: flex; align-items: center; gap: 2px; margin-left: auto; }
/* El nombre se ve ENTERO: envuelve en vez de cortarse con puntos suspensivos.
   Dos rutas del mismo cliente se distinguen justo por el final del nombre. */
.map-route-head.is-two-line .map-route-name {
    overflow: visible; white-space: normal;
    overflow-wrap: anywhere; line-height: 1.25;
}
.map-route-count {
    font-size: 0.68rem; font-weight: 700; color: var(--gray-600);
    background: var(--gray-100); border-radius: 999px; padding: 1px 6px; flex-shrink: 0;
}
/* Cola de RUTAS del rider (cuál va primero). El asa vive en la cabecera del
   <details>; mismo criterio táctil que .map-stop-drag (touch-action: none +
   área de toque agrandada), porque en el panel del rider esto se arrastra con
   el pulgar dentro de un contenedor con scroll propio. */
.map-route-queue { display: flex; flex-direction: column; gap: 6px; }
.map-route-queue > details.map-route.is-dragging { opacity: 0.5; }
.map-route-drag {
    color: var(--gray-400); cursor: grab; flex-shrink: 0;
    font-size: 0.8rem; line-height: 1;
    touch-action: none;
    padding: 10px 6px; margin: -10px -2px -10px -6px;
    -webkit-tap-highlight-color: transparent;
}
.map-route-drag:active { cursor: grabbing; }
/* Punto que marca una ruta con trabajo ya empezado: no puede quedar por debajo
   de una intacta (el backend lo rechaza con 422). */
.map-route-started { color: var(--warning, #F59E0B); flex-shrink: 0; font-size: 0.9rem; }
.map-route-restore {
    border: none; background: transparent; color: var(--gray-500);
    cursor: pointer; padding: 2px 4px; font-size: 0.8rem; line-height: 1; flex-shrink: 0;
}
.map-route-restore:hover { color: var(--primary); }
/* Optimizar el orden. Mismo peso visual que restaurar: son las dos caras de la
   misma decisión (el orden lo propone el algoritmo o el orden de carga), y
   ninguna de las dos manda sobre el drag, que es lo que decide de verdad. */
.map-route-optimize {
    border: none; background: transparent; color: var(--gray-500);
    cursor: pointer; padding: 2px 4px; font-size: 0.8rem; line-height: 1; flex-shrink: 0;
}
.map-route-optimize:hover { color: var(--primary); }
/* Círculo de activación del TRAZADO de una ruta (solo el mapa general del
   rider): encendido = la línea desde mi posición va hasta la parada que toca de
   ESTA ruta. Es excluyente entre rutas y se apaga con el ⌖, nunca con un segundo
   toque. Área táctil grande (padding negativo) sobre un punto chico.

   VA SCOPEADO a `.rider-sheet` a propósito: el dispatcher tiene su propio
   `.map-route-trace` (más abajo en este archivo) que es un BOTÓN CON ICONO —
   `background: none; border: none` y el dibujo lo pone un `<i>`. Los dos
   nombres eran iguales y sin ámbito, así que la regla del dispatcher, por venir
   después, le borraba el borde y el fondo a la del rider… cuyo botón está
   VACÍO (el círculo ES el borde). Resultado: un botón invisible de 0 px que
   seguía siendo clickeable. Con el ámbito, cada uno gana en su panel sin
   depender del orden del archivo. */
.rider-sheet .map-route-trace {
    position: relative; flex-shrink: 0;
    border: 2px solid var(--gray-400); background: transparent;
    width: 16px; height: 16px; border-radius: 999px; padding: 0; cursor: pointer;
    box-sizing: border-box; -webkit-tap-highlight-color: transparent;
    outline-offset: 2px; transition: background-color .15s, border-color .15s;
}
.rider-sheet .map-route-trace::before {   /* zona táctil, sin agrandar el punto */
    content: ''; position: absolute; top: -13px; left: -13px;
    width: 40px; height: 40px;
}
.rider-sheet .map-route-trace.is-on {
    border-color: #1D4ED8;                                 /* el azul del trazado */
    background: #1D4ED8;
    box-shadow: 0 0 0 3px color-mix(in srgb, #1D4ED8 22%, transparent);
}
.map-route-stops { list-style: none; margin: 0; padding: 2px 4px 4px; display: flex; flex-direction: column; gap: 3px; }
.map-stop-row {
    display: flex; align-items: flex-start; gap: 5px;
    padding: 4px 5px; border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--rider-color) 6%, var(--surface));
    border: 1px solid var(--gray-200); font-size: 0.76rem; cursor: pointer;
}
.map-stop-row[draggable="true"] { cursor: grab; }
.map-stop-row.is-dragging { opacity: 0.5; cursor: grabbing; }
.map-stop-row.is-frozen { opacity: 0.6; background: var(--gray-100); }
/* Parada FALLIDA: se resalta, no se atenúa. Está cerrada, pero es trabajo que
   quedó sin hacer y reclama una decisión del dispatcher — por eso tampoco la
   esconde el chip "ocultar hechas". Va después de .is-frozen para ganarle la
   opacidad, que la dejaba más apagada que una entregada. */
.map-stop-row.is-fallida.is-frozen, .map-stop-row.is-fallida {
    opacity: 1;
    background: color-mix(in srgb, #dc2626 8%, var(--surface));
    border-color: color-mix(in srgb, #dc2626 35%, var(--gray-200));
}
.map-stop-row.is-fallida .map-stop-seq { color: #b91c1c; font-weight: 700; }
/* Asa de arrastre de una parada. `touch-action: none` es OBLIGATORIO desde que
   el panel del rider vive dentro del bottom sheet: el cuerpo de la hoja tiene
   scroll propio y, sin esto, el navegador se queda con el gesto vertical y
   scrollea el panel en lugar de arrastrar la parada. Mismo criterio que el asa
   de la hoja (.rider-sheet-handle). El área de toque se agranda con padding
   (el glifo ⠿ solo mide unos 10px, imposible de agarrar con el pulgar). */
.map-stop-drag {
    color: var(--gray-400); cursor: grab; flex-shrink: 0;
    font-size: 0.8rem; line-height: 1.3;
    touch-action: none;
    padding: 10px 8px; margin: -10px -4px -10px -8px;
    -webkit-tap-highlight-color: transparent;
}
.map-stop-seq {
    font-size: 0.68rem; font-weight: 700; color: #fff;
    background: var(--rider-color); border-radius: 4px; padding: 0 5px; flex-shrink: 0;
    min-width: 18px; text-align: center; margin-top: 1px;
}
.map-stop-tipo { color: var(--gray-400); flex-shrink: 0; }
/* Botón "quitar pedido de la ruta" en la fila de recogida */
.map-stop-del {
    flex-shrink: 0; background: none; border: none; padding: 0 2px;
    color: var(--gray-400); cursor: pointer; font-size: 0.9rem; line-height: 1;
}
.map-stop-del:hover { color: #dc2626; }
/* Fila de parada en 3 líneas: (1) nº de pedido + estado + quitar, (2) dirección
   completa, (3) CP + ícono de ir al detalle del pedido. */
.map-stop-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.map-stop-l1 { display: flex; align-items: center; gap: 6px; }
.map-stop-order { flex-shrink: 0; }
.map-stop-l1 .map-status { margin-left: 2px; }
.map-stop-l1 .map-stop-del { margin-left: auto; }
.map-stop-l3 { display: flex; align-items: center; gap: 5px; }
.map-stop-cp { color: var(--gray-500); font-size: 0.72rem; letter-spacing: 0.02em; }
/* Franja horaria comprometida de la parada (winChip en map-rider.js). Va al
   final de la primera línea de la fila, empujada con `auto`: todas las ventanas
   del recorrido caen en la misma columna y se leen en vertical. */
.map-stop-win {
    margin-left: auto; flex: 0 0 auto;
    display: inline-flex; align-items: center; gap: 3px;
    padding: 1px 7px; border-radius: 20px;
    background: #eef2ff; color: #3730a3; border: 1px solid #c7d2fe;
    font-size: 0.68rem; font-weight: 800; white-space: nowrap;
}
.map-stop-win i { color: #6366f1; font-size: 0.92em; }
.map-stop-l3 .map-stop-open { margin-left: auto; }
/* Direcciones completas: envuelven en varias líneas en vez de cortarse. */
.map-stop-addr { min-width: 0; overflow-wrap: anywhere; line-height: 1.3; }
.map-stop-nocoord { color: #b45309; }
.mess-pop-rt {
    font-size: 0.68rem; font-weight: 700; color: var(--gray-500);
    background: var(--gray-100); border-radius: 4px; padding: 0 4px;
}

/* Swatch del patrón de guiones de cada ruta (correlación panel↔mapa, Tanda 4b) */
.map-route-dash {
    width: 22px; height: 3px; flex-shrink: 0; border-radius: 2px;
    background-image: repeating-linear-gradient(90deg, var(--rider-color) 0 4px, transparent 4px 6px);
}
.map-dash-0 { background-image: repeating-linear-gradient(90deg, var(--rider-color) 0 2px, transparent 2px 4px); }
.map-dash-1 { background-image: repeating-linear-gradient(90deg, var(--rider-color) 0 4px, transparent 4px 6px); }
.map-dash-2 { background-image: repeating-linear-gradient(90deg, var(--rider-color) 0 1px, transparent 1px 3px); }
.map-dash-3 { background-image: repeating-linear-gradient(90deg, var(--rider-color) 0 6px, transparent 6px 8px); }
/* Etiqueta de ruta (letra A/B/…) cuando el rider tiene más de una: correlaciona
   el prefijo del número del marcador ("A5") con su ruta en el panel. */
.map-route-tag {
    flex-shrink: 0; font-size: 0.62rem; font-weight: 700; line-height: 1.4;
    color: #fff; background: var(--rider-color); border-radius: 4px;
    padding: 0 5px; min-width: 16px; text-align: center;
}
/* Botón "Añadir pedido" en la cabecera de cada ruta */
.map-route-addorder {
    flex-shrink: 0; background: none; border: none; padding: 0 2px;
    color: var(--gray-400); cursor: pointer; font-size: 0.85rem; line-height: 1;
}
.map-route-addorder:hover { color: var(--primary, #4A90E2); }
/* Botón "Eliminar ruta" en la cabecera de cada ruta (mismo estilo que el "+") */
.map-route-delete {
    flex-shrink: 0; background: none; border: none; padding: 0 2px;
    color: var(--gray-400); cursor: pointer; font-size: 0.85rem; line-height: 1;
}
.map-route-delete:hover { color: #dc2626; }
/* "Finalizar ruta". En el DISPATCHER es un icono más de la fila de acciones
   (está en toda ruta, siempre). En el RIDER es un botón con texto —"Terminé"—
   y solo aparece cuando ya no le queda nada por hacer: ahí no es una acción
   más entre otras, es el gesto que cierra el recorrido, y con guantes y en
   movimiento un icono suelto de 0,85rem no se acierta. */
.map-route-finish {
    flex-shrink: 0; background: none; border: none; padding: 0 2px;
    color: var(--gray-400); cursor: pointer; font-size: 0.85rem; line-height: 1;
}
.map-route-finish:hover { color: var(--success, #16a34a); }
.rider-sheet .map-route-finish {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 4px 10px; border-radius: 999px;
    background: var(--success, #16a34a); color: #fff;
    font-size: 0.72rem; font-weight: 700;
}
.rider-sheet .map-route-finish:hover { color: #fff; filter: brightness(1.06); }
/* Fecha de servicio de la ruta (pedidos programados): chip en la cabecera del
   bloque. Solo aparece si la ruta tiene fecha — sin fecha es "hoy", que es el
   caso normal y no necesita decirse. En ámbar cuando es FUTURA: esa ruta
   todavía no está en el mapa del rider, y esa es la información que el
   dispatcher necesita ver de un vistazo para no buscar un bug que no existe. */
.map-route-date {
    flex-shrink: 0; font-size: 0.62rem; font-weight: 700; line-height: 1.4;
    border-radius: 4px; padding: 0 5px; white-space: nowrap;
    color: var(--gray-600); background: var(--gray-200);
}
.map-route-date.is-future { color: #92400e; background: #fde68a; }
/* Botón "Fecha de la ruta" (mismo estilo que el "+" y la papelera) */
.map-route-datebtn {
    flex-shrink: 0; background: none; border: none; padding: 0 2px;
    color: var(--gray-400); cursor: pointer; font-size: 0.85rem; line-height: 1;
}
.map-route-datebtn:hover { color: var(--primary, #4A90E2); }
/* Botón de RUTA ACTIVA del DISPATCHER: "mirar SOLO el trazado de esta ruta".
   Mismo estilo que los demás botones de la cabecera; encendido se tiñe del color
   del rider (el del bloque) porque lo que enciende es una línea de ESE color en
   el mapa. Acá el dibujo lo pone un `<i>`, así que va sin borde ni fondo.
   OJO: el rider usa la MISMA clase para un botón VACÍO cuyo círculo es el borde
   — su regla está arriba, scopeada a `.rider-sheet`. Quitarle ese ámbito (o
   dárselo a esta) es lo que dejaba el círculo del rider invisible. */
.map-route-trace {
    flex-shrink: 0; background: none; border: none; padding: 0 2px;
    color: var(--gray-400); cursor: pointer; font-size: 0.85rem; line-height: 1;
}
.map-route-trace:hover { color: var(--primary, #4A90E2); }
.map-route-trace.is-on { color: var(--rider-color, var(--primary, #4A90E2)); }
/* La ruta que se está mirando queda marcada en el panel: con varias rutas a la
   vista, el botón encendido solo no alcanza para saber cuál es la del trazo. */
.map-route.is-tracing {
    border-color: var(--rider-color, var(--primary, #4A90E2));
    box-shadow: inset 2px 0 0 var(--rider-color, var(--primary, #4A90E2));
}
/* Bloque "Sin ruta": paradas de pedidos activos aún no agrupadas en una ruta */
.map-route-loose { border-style: dashed; }
.map-route-loose .map-stop-row { cursor: default; }
.map-route-loose .map-stop-drag { display: none; }
.map-legend-sub { font-size: 0.72rem; padding: 4px 8px; }
.map-route-create { flex-shrink: 0; font-size: 0.68rem; padding: 1px 6px; }
/* Modal "Crear ruta": lista de pedidos sin ruta con checkboxes */
.map-create-orders { display: flex; flex-direction: column; gap: 4px; max-height: 240px; overflow-y: auto; margin-bottom: var(--space-2); }
.map-create-order {
    display: flex; align-items: flex-start; gap: 6px; font-size: 0.78rem;
    padding: 4px 6px; border: 1px solid var(--gray-200); border-radius: var(--radius-sm); cursor: pointer;
}
.map-create-order span { overflow-wrap: anywhere; }
/* Título de cada grupo de la lista (los del rider / los sin asignar) */
.map-create-group {
    margin: 6px 0 2px; font-size: 0.7rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: .04em; color: var(--gray-500);
}
.map-create-group:first-child { margin-top: 0; }
/* "Nueva ruta" del bloque de un rider: crear OTRA ruta sin depender de que le
   queden pedidos sueltos (el modal ya ofrece también los sin asignar). */
.map-rider-newroute { align-self: flex-start; font-size: 0.68rem; padding: 1px 6px; margin: 2px 0 4px; }

/* ── Bloque "Sin asignar" (rutas por asignar + pedidos pendientes sueltos) ─── */
.map-unassigned-block { border-left-style: dashed; }
.map-route-assign { flex-shrink: 0; font-size: 0.68rem; padding: 1px 6px; }

/* ── Barra flotante del modo "crear ruta" (selección de recogidas) ─────────── */
.map-screen-body { position: relative; }
.map-select-bar {
    position: absolute; left: 50%; bottom: var(--space-3); transform: translateX(-50%);
    z-index: 20; display: flex; align-items: center; gap: var(--space-3);
    flex-wrap: wrap; max-width: min(680px, calc(100% - 360px));
    padding: 10px 14px; border-radius: var(--radius-lg);
    background: #1f2937; color: #f9fafb;
    border: 1px solid rgba(255, 255, 255, 0.12);
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
}
.map-select-bar-info { font-size: 0.82rem; }
.map-select-bar-info b { color: #fff; }
.map-select-bar-rider { display: flex; align-items: center; gap: 6px; font-size: 0.76rem; margin: 0; }
.map-select-bar-rider select { min-width: 150px; }
.map-select-bar-actions { display: flex; gap: 6px; margin-left: auto; }
.map-select-bar-msg { flex-basis: 100%; font-size: 0.74rem; color: #fca5a5; }
.map-select-bar-msg.is-error { color: #fca5a5; }
@media (max-width: 900px) {
    .map-select-bar { max-width: calc(100% - 24px); position: fixed; }
}

/* ── Tarjeta de detalle de la parada (esquina inferior IZQUIERDA) ───────────
   Sustituye al popup que se abría sobre el marcador. Sigue el chrome oscuro del
   dispatcher (los mismos grises que la barra de "crear ruta", escritos a mano y
   no heredados: esta tarjeta va POR ENCIMA del canvas del mapa, que es claro u
   oscuro según el basemap, y no puede depender de él).

   El indicativo de qué clase de parada es NO es solo un texto: la tarjeta lleva
   una franja del color del TIPO —el mismo par verde/ámbar/morado del pin y de la
   fila del panel, más el rojo de la fallida—, de modo que "qué estoy mirando" se
   contesta antes de leer una palabra. Es la misma correlación que hace que el
   mapa se pueda leer de un vistazo; romperla acá obligaría a leer siempre. */
.map-stopcard {
    position: absolute; left: var(--space-3); bottom: var(--space-3); z-index: 25;
    width: min(340px, calc(100% - 2 * var(--space-3)));
    max-height: min(62%, 520px); overflow-y: auto;
    display: flex; flex-direction: column;
    border-radius: var(--radius-lg);
    background: #1f2937; color: #e5e7eb;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-left: 4px solid var(--msc-accent, #64748b);
    box-shadow: 0 10px 32px rgba(0, 0, 0, 0.45);
    font-size: 0.86rem;
}
.map-stopcard.is-recogida { --msc-accent: #22C55E; }
.map-stopcard.is-entrega  { --msc-accent: #F59E0B; }
.map-stopcard.is-traspaso { --msc-accent: #7C3AED; }
.map-stopcard.is-fallida  { --msc-accent: #DC2626; }
.map-stopcard.is-grupo    { --msc-accent: #64748B; }

.msc-head {
    display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
    padding: 9px 10px; border-bottom: 1px solid rgba(255, 255, 255, 0.10);
    background: color-mix(in srgb, var(--msc-accent) 16%, transparent);
}
.msc-kind {
    font-size: 0.7rem; font-weight: 800; letter-spacing: 0.05em;
    text-transform: uppercase; color: var(--msc-accent);
}
/* Número de recorrido de ESTA parada: el mismo que lleva su pin en el mapa y su
   fila en el panel — es como el dispatcher y el rider nombran una parada. */
.msc-seq {
    min-width: 26px; height: 22px; padding: 0 6px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 7px; font-size: 0.78rem; font-weight: 800; line-height: 1;
    background: var(--msc-accent); color: #0b1220;
}
.map-stopcard.is-traspaso .msc-seq, .map-stopcard.is-fallida .msc-seq { color: #fff; }
.msc-order { font-size: 0.78rem; font-weight: 700; color: #cbd5e1; }
.msc-open, .msc-close, .msc-back {
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; border-radius: 7px; flex-shrink: 0;
    border: 0; background: rgba(255, 255, 255, 0.07); color: #cbd5e1;
    cursor: pointer; text-decoration: none;
}
.msc-open:hover, .msc-close:hover, .msc-back:hover { background: rgba(255, 255, 255, 0.16); color: #fff; }
.msc-open { margin-left: auto; }
.msc-back { order: -1; }

.msc-body { padding: 10px; display: flex; flex-direction: column; gap: 8px; }
.msc-addr { font-size: 0.95rem; font-weight: 600; color: #f8fafc; line-height: 1.35; }
.msc-cp { font-weight: 600; color: #94a3b8; }
/* El detalle de unidad y la empresa son el segundo renglón de la dirección: se
   dibujan solo cuando existen (ver addrBlock/empresaRow en map-dispatcher.js),
   así que no reservan espacio ni dejan un hueco cuando faltan. */
.msc-unit,
.msc-empresa { display: flex; align-items: center; gap: 6px; font-size: 0.8rem; }
.msc-unit { color: #cbd5e1; }
.msc-empresa { color: #e2e8f0; font-weight: 600; }
.msc-unit i, .msc-empresa i { color: #94a3b8; font-size: 0.85em; }
/* Franja horaria comprometida (windowRow en map-dispatcher.js): va pegada a la
   dirección porque dónde y cuándo son la misma pregunta al armar un recorrido.
   Superficie propia sobre el fondo oscuro de la tarjeta para que se encuentre
   sin leer, sin llegar al peso de la dirección. */
.msc-win {
    align-self: flex-start;
    display: inline-flex; align-items: center; gap: 5px;
    padding: 2px 9px; border-radius: 20px;
    background: rgba(99, 102, 241, 0.18); color: #c7d2fe;
    border: 1px solid rgba(129, 140, 248, 0.45);
    font-size: 0.78rem; font-weight: 700; white-space: nowrap;
}
.msc-win i { color: #a5b4fc; font-size: 0.9em; }
.msc-meta { display: flex; flex-wrap: wrap; gap: 10px; font-size: 0.78rem; color: #cbd5e1; }
.msc-route, .msc-rider { display: inline-flex; align-items: center; gap: 5px; }
.msc-route.is-none, .msc-rider.is-none { color: #94a3b8; font-style: italic; }
.msc-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.msc-hint { font-size: 0.78rem; color: #94a3b8; }

/* Avisos de la tarjeta (traspaso / fallida / sin ubicar): una línea, con el tono
   de lo que informan. */
.msc-note {
    display: flex; gap: 7px; align-items: flex-start;
    font-size: 0.78rem; line-height: 1.35; border-radius: var(--radius-sm);
    padding: 6px 8px;
}
.msc-note.is-traspaso { background: rgba(124, 58, 237, 0.18); color: #ddd6fe; }
.msc-note.is-fallida { background: rgba(220, 38, 38, 0.18); color: #fecaca; }
.msc-note.is-warn { background: rgba(245, 158, 11, 0.16); color: #fde68a; }
/* Recorrido ya cerrado: la tarjeta es información y no lleva acciones. Va en
   verde (el mismo tono con que el historial dice "finalizada") y no en ámbar:
   no es un aviso de que algo esté mal, es el estado del recorrido. */
.msc-note.is-finished { background: rgba(22, 163, 74, 0.16); color: #bbf7d0; }

/* Bloque "dónde está la otra mitad" del pedido. */
.msc-block { border-top: 1px solid rgba(255, 255, 255, 0.10); padding-top: 8px; }
.msc-block-t {
    font-size: 0.68rem; font-weight: 800; letter-spacing: 0.05em;
    text-transform: uppercase; color: #94a3b8;
}
.msc-empty { color: #64748b; }
.msc-more > summary {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    cursor: pointer; list-style: none;
}
.msc-more > summary::-webkit-details-marker { display: none; }
/* El chevron es un <i> real y no un `content` con el codepoint del icono: el
   codepoint depende de la versión de bootstrap-icons del CDN y un cambio menor
   lo convertiría en un cuadrito. */
.msc-caret { margin-left: auto; font-size: 0.8rem; color: #94a3b8; transition: transform 0.15s; }
.msc-more[open] > summary .msc-caret { transform: rotate(180deg); }
.msc-sibs { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.msc-sib {
    display: flex; align-items: center; gap: 8px; cursor: pointer;
    padding: 4px 6px; border-radius: var(--radius-sm);
}
.msc-sib:hover { background: rgba(255, 255, 255, 0.08); }
.msc-sib-addr { color: #e2e8f0; line-height: 1.3; }

/* Punto con 3 o más paradas: la lista para elegir cuál mirar (el pin "xN"). */
.msc-picks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.msc-pick {
    display: flex; align-items: center; gap: 8px; cursor: pointer;
    padding: 6px 7px; border-radius: var(--radius-sm);
    background: rgba(255, 255, 255, 0.05);
    border-left: 3px solid var(--msc-pick-accent, #64748b);
}
.msc-pick:hover { background: rgba(255, 255, 255, 0.12); }
.msc-pick.is-recogida { --msc-pick-accent: #22C55E; }
.msc-pick.is-entrega  { --msc-pick-accent: #F59E0B; }
.msc-pick.is-traspaso { --msc-pick-accent: #7C3AED; }
.msc-pick.is-fallida  { --msc-pick-accent: #DC2626; }
.msc-pick .msc-kind { color: var(--msc-pick-accent); }
.msc-pick-body { min-width: 0; flex: 1; }
.msc-pick-l1 { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.msc-pick-rider { font-size: 0.72rem; color: #94a3b8; }
.msc-pick-addr { color: #e2e8f0; line-height: 1.3; overflow-wrap: anywhere; }
.msc-go { color: #64748b; flex-shrink: 0; }

.msc-action {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    border: 1px solid rgba(255, 255, 255, 0.18); background: transparent;
    color: #e5e7eb; border-radius: var(--radius-sm); padding: 6px 10px;
    font-size: 0.8rem; cursor: pointer;
}
.msc-action:hover { background: var(--primary); border-color: var(--primary); color: #fff; }
/* "Quitar la ubicación" acompaña a "corregir" pero es la acción secundaria: se
   apaga para que las dos no compitan por el mismo clic. */
.msc-action.is-quiet { color: #94a3b8; border-color: rgba(255, 255, 255, 0.1); }
.msc-action.is-quiet:hover { background: rgba(248, 113, 113, 0.16); border-color: rgba(248, 113, 113, 0.5); color: #fecaca; }
/* "Deshacer el traspaso" BORRA el punto de encuentro de dos rutas: se lee en
   rojo desde el principio, no solo al pasar por encima. */
.msc-action.is-danger { color: #fca5a5; border-color: rgba(248, 113, 113, 0.45); }
.msc-action.is-danger:hover { background: rgba(220, 38, 38, 0.85); border-color: rgba(220, 38, 38, 0.9); color: #fff; }

/* Los chips de número de las hermanas usan la misma escala de color que el pin.
   `--pickup`/`--delivery` ya existían para la hoja del rider; acá se nombran por
   TIPO para que traspaso y fallida tengan el suyo y no caigan en un gris mudo. */
.seq-chip--recogida { background: #22C55E; color: #fff; }
.seq-chip--entrega { background: #F59E0B; color: #fff; }
.seq-chip--traspaso { background: #7C3AED; color: #fff; }
.seq-chip--fallida { background: #DC2626; color: #fff; }

@media (max-width: 900px) {
    /* Con el panel debajo del mapa, la tarjeta pasa a fija: si no, queda anclada
       al fondo del contenedor entero y se sale de la pantalla. */
    .map-stopcard { position: fixed; width: min(340px, calc(100% - 24px)); }
}

/* ── Traspaso propuesto por el rider (Pieza 1) ─────────────────────────────
   Mismo violeta que ya identifica al traspaso en el panel del rider
   (.addr-block-handoff / .handoff-source), para que "esto es un relevo" se
   lea igual en las dos puntas. */
.handoff-inbox-card {
    background: #faf5ff; border: 1px solid #d8b4fe; border-left: 4px solid #7c3aed;
    border-radius: 12px; padding: 12px 14px; margin-bottom: 12px;
}
.handoff-inbox-title {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    margin: 0 0 8px; font-size: 0.92rem; font-weight: 700; color: #5b21b6;
}
.handoff-inbox-ttl {
    margin-left: auto; font-size: 0.7rem; font-weight: 700;
    background: #ede9fe; color: #6d28d9; border-radius: 20px; padding: 2px 8px;
}
.handoff-inbox-line {
    margin: 0 0 4px; font-size: 0.83rem; color: #4b5563;
}
.handoff-inbox-actions {
    display: flex; gap: 8px; margin-top: 10px;
}
.handoff-inbox-actions form { flex: 1; }
.handoff-btn-accept, .handoff-btn-reject {
    width: 100%; border: none; border-radius: 8px; padding: 9px 10px;
    font-size: 0.85rem; font-weight: 700; cursor: pointer;
}
.handoff-btn-accept { background: #7c3aed; color: #fff; }
.handoff-btn-reject { background: #fff; color: #b91c1c; border: 1px solid #fecaca; }
.handoff-outbox-note {
    background: #fef2f2; border: 1px solid #fecaca; border-radius: 10px;
    padding: 9px 12px; margin-bottom: 10px; font-size: 0.82rem; color: #7f1d1d;
}
.handoff-outbox-note.ok {
    background: #f0fdf4; border-color: #bbf7d0; color: #14532d;
}

/* ── Traspaso (handoff) de entrega — Tanda 3 ──────────────────────────────── */
.mess-pop-handoff {
    display: inline-flex; align-items: center; gap: 4px;
    margin: 6px 10px 8px; padding: 3px 8px;
    font-size: 0.72rem; font-weight: 600;
    border: 1px solid var(--primary); color: var(--primary);
    background: transparent; border-radius: 6px; cursor: pointer;
}
.mess-pop-handoff:hover { background: var(--primary); color: #fff; }
/* Marca de traspaso en la fila de parada del panel */
.map-stop-row.is-traspasada .map-stop-seq { color: var(--stop-handoff, #7C3AED); }
.map-stop-handoff-tag {
    font-size: 0.6rem; font-weight: 700; text-transform: uppercase;
    color: var(--primary); background: color-mix(in srgb, var(--primary) 14%, transparent);
    border-radius: 3px; padding: 0 4px; flex-shrink: 0;
}
/* Traspaso PROPUESTO por un rider, todavía sin respuesta del destino: ámbar
   para que se distinga de un traspaso ya efectivo (que es un hecho, no algo
   que el dispatcher pueda tener que resolver). */
.map-stop-handoff-tag.is-proposed {
    color: #92400e; background: rgba(250, 204, 21, 0.22);
}

/* ── Código de color por TIPO de parada (mismo que el halo del mapa) ───────── */
/* Recogida = verde (#22C55E), entrega = ámbar (#FACC15). El borde izquierdo de
   la tarjeta de cada parada repite ese código, igual que el bloque del rider
   marca su color a la izquierda. */
.map-stop-row.is-recogida { border-left: 3px solid var(--stop-pickup, #22C55E); }
.map-stop-row.is-entrega { border-left: 3px solid var(--stop-drop, #FACC15); }
.map-stop-row.is-recogida .map-stop-tipo { color: #16A34A; }
.map-stop-row.is-entrega .map-stop-tipo { color: #CA8A04; }
/* Punto de TRASPASO: morado, el mismo que marca el traspaso en el detalle del
   pedido. Va después de las dos de arriba a propósito — una parada de traspaso
   es tipo 'recogida' en el modelo, y acá gana el morado. La entrega asociada
   NO se tiñe: sigue siendo una entrega normal (ámbar). */
.map-stop-row.is-handoff { border-left-color: var(--stop-handoff, #7C3AED); }
.map-stop-row.is-handoff .map-stop-tipo { color: var(--stop-handoff, #7C3AED); }
/* Parada marcada desde el mapa: aro con el color del rider + pulso al llegar */
.map-stop-row.is-active {
    box-shadow: 0 0 0 2px var(--rider-color);
    background: color-mix(in srgb, var(--rider-color) 14%, var(--surface));
}
/* Hermanas del mismo pedido: mismo marcador, más tenue (la clickeada manda). */
.map-stop-row.is-related {
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--rider-color) 45%, transparent);
    background: color-mix(in srgb, var(--rider-color) 8%, var(--surface));
}
.map-stop-row.is-flash { animation: mapStopGlow 1.4s ease; }
@keyframes mapStopGlow {
    0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--rider-color) 70%, transparent); }
    35% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--rider-color) 35%, transparent); }
    100% { box-shadow: 0 0 0 2px var(--rider-color); }
}
/* Ícono "ver el detalle del pedido" (mismo estilo que el "+" de la ruta) */
.map-stop-open {
    flex-shrink: 0; padding: 0 2px; line-height: 1;
    color: var(--gray-400); font-size: 0.85rem; text-decoration: none;
}
.map-stop-open:hover { color: var(--primary, #4A90E2); }

/* ── Lista de rutas agrupada por rider (admin/routes) ──────────────────────── */
.route-groups { display: flex; flex-direction: column; gap: var(--space-4); }
.route-group {
    background: var(--surface); border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg); overflow: hidden;
    border-left: 4px solid var(--rider-color);
}
.route-group-unassigned { border-left-style: dashed; }
.route-group-head {
    display: flex; align-items: center; gap: 8px;
    padding: 10px 16px; border-bottom: 1px solid var(--gray-200);
    background: color-mix(in srgb, var(--rider-color) 6%, var(--surface));
}
.route-group-dot {
    width: 12px; height: 12px; border-radius: 50%;
    background: var(--rider-color); flex-shrink: 0;
}
.route-group-name { font-weight: 700; font-size: 0.95rem; }
.route-group-count {
    margin-left: auto; font-size: 0.76rem; font-weight: 600;
    color: var(--gray-600); background: var(--gray-100);
    border-radius: 999px; padding: 2px 10px;
}
.route-group-list { display: flex; flex-direction: column; }
.route-item {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 16px; text-decoration: none; color: inherit;
    border-top: 1px solid var(--gray-100); transition: background 0.12s ease;
}
.route-group-list > .route-item:first-child { border-top: none; }
.route-item:hover { background: color-mix(in srgb, var(--rider-color) 8%, var(--surface)); }
.route-item-num {
    width: 34px; height: 34px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: 0.9rem; color: #fff;
    background: var(--rider-color); border-radius: 8px;
}
.route-item-body { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.route-item-name { font-weight: 600; }
.route-item-meta { font-size: 0.76rem; color: var(--gray-600); display: flex; gap: 6px; flex-wrap: wrap; }
.route-item-chevron { color: var(--gray-400); flex-shrink: 0; }

/* ── Detalle de ruta (admin/routes/<id>) ──────────────────────────────────── */
.route-detail-stops .map-stop-seq { background: var(--rider-color); }
.route-rider-dot {
    width: 12px; height: 12px; border-radius: 50%;
    background: var(--rider-color); display: inline-block; flex-shrink: 0;
}
/* Fila de pedido clickeable: quitar el color de link del número */
.route-order-row { cursor: pointer; }
.route-order-id { font-weight: 700; color: var(--body-color, inherit); }

.mess-handoff-backdrop {
    position: fixed; inset: 0; z-index: 1080;
    background: rgba(0, 0, 0, 0.45);
    display: flex; align-items: center; justify-content: center;
    padding: var(--space-3);
}
.mess-handoff-backdrop[hidden] { display: none; }
.mess-handoff-modal {
    background: var(--surface); color: var(--body-color, inherit);
    border-radius: var(--radius-lg); width: 100%; max-width: 380px;
    max-height: 90vh; overflow-y: auto;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
}
.mess-handoff-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--space-3); border-bottom: 1px solid var(--gray-200);
}
.mess-handoff-close {
    border: none; background: transparent; font-size: 1.4rem; line-height: 1;
    color: var(--gray-500); cursor: pointer;
}
.mess-handoff-body { padding: var(--space-3); }
.mess-handoff-body .form-label { font-size: 0.78rem; font-weight: 600; margin-bottom: 2px; }
.mess-handoff-order { font-size: 0.82rem; }
.mess-handoff-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }
.mess-handoff-msg {
    font-size: 0.78rem; padding: 5px 8px; border-radius: var(--radius-sm);
    margin-bottom: var(--space-2); background: #fee2e2; color: #991b1b;
}
.mess-handoff-msg.is-error { background: #fee2e2; color: #991b1b; }


/* ── Rider: mapa a pantalla completa ──────────────────────────────────────── */
.rider-map-screen { position: relative; }
.rider-map-canvas {
    width: 100%;
    height: calc(100dvh - 72px); /* deja lugar para el bottom-nav del rider */
    min-height: 320px;
}
.rider-map-warn {
    position: absolute;
    z-index: 2;
    top: 10px; left: 10px; right: 10px;
    background: rgba(180, 83, 9, 0.92);
    color: #fff;
    font-size: 0.78rem;
    padding: 6px 10px;
    border-radius: 8px;
    text-align: center;
}

/* ── Rider: mapa a pantalla completa + bottom sheet ────────────────────────
   Chrome de las vistas de mapa del rider (enfocada y general): el mapa ocupa
   toda la pantalla por debajo del bottom-nav y el panel vive en una hoja
   arrastrable encima (bottom-sheet.js). En >=900px la hoja deja de ser hoja y
   pasa a panel lateral fijo: en desktop hay alto de sobra y arrastrar con el
   mouse un panel que no estorba es fricción sin beneficio. */
.rider-map-shell {
    position: fixed;
    inset: 0;
    bottom: calc(var(--rider-nav-h) + env(safe-area-inset-bottom, 0px));  /* alto de .rider-bnav */
    /* `clip`, NO `hidden`: `hidden` recorta pero deja el elemento como
       contenedor de SCROLL programático, y el translateY de la hoja colapsada
       genera área desplazable real (medido: clientHeight 632 / scrollHeight
       1050). Cualquier scrollIntoView() o focus() dentro de la hoja —o del
       popup del mapa— corría el shell entero 140px: el mapa y la hoja subían y
       la hoja parecía trabada (el gesto funcionaba, pero se dibujaba fuera de
       sitio). `clip` no crea contenedor de scroll, así que eso no puede pasar.
       Si alguien lo "normaliza" a hidden, vuelve el bug. */
    overflow: clip;
    background: var(--bg, #f8f9fa);
}
.rider-map-canvas { position: absolute; inset: 0; }

/* Controles del mapa. "Mi ubicación" NO vive en la banda superior: va anclado al
   borde SUPERIOR DE LA HOJA, a la derecha, y viaja con ella (colapsada, media,
   máxima). Así nunca queda tapado ni compite con la banda de arriba, y en la
   altura máxima —donde antes desaparecía— sigue a mano.

   El anclaje es `--sheet-visible` (alto visible de la hoja, que bottom-sheet.js
   publica en el shell en cada frame del gesto) aplicado como translate: se anima
   solo `transform`. Durante el snap la variable salta al valor final, así que la
   transición se replica desde `.is-sheet-animating` con la MISMA curva de la
   hoja — si no, el botón llegaría antes que ella. */
.rider-map-fabs {
    position: absolute;
    right: var(--space-2);
    bottom: var(--space-2);
    z-index: 5;                 /* sobre la hoja: el borde de la hoja lo roza */
    display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
    transform: translate3d(0, calc(-1 * var(--sheet-visible, 0px)), 0);
}
.rider-map-shell.is-sheet-animating .rider-map-fabs {
    transition: transform 0.26s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.rider-map-back {
    position: absolute;
    top: calc(var(--space-2) + env(safe-area-inset-top, 0px));
    left: var(--space-2);
    z-index: 3;
}
.rider-map-fab {
    width: 44px; height: 44px;              /* área de tap mínima */
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid #e5e7eb; border-radius: 12px;
    background: rgba(255,255,255,0.94);
    color: #374151; font-size: 1.05rem; line-height: 1;
    box-shadow: 0 2px 8px rgba(0,0,0,0.12);
    -webkit-tap-highlight-color: transparent;
    cursor: pointer;
}
.rider-map-fab:disabled { opacity: 0.45; cursor: default; }
.rider-map-fab.is-back { font-size: 1.15rem; }

/* ── Mi ubicación (geolocalización Fase A) ─────────────────────────────────
   Tres estados visibles en el propio botón, porque entre pedir el permiso y
   tener un fix pueden pasar varios segundos y sin señal el rider no sabe si
   tocó bien: buscando (parpadea), activa (relleno primario) y error. */
.rider-map-geo.is-locating { color: var(--primary); animation: geoPulse 1.2s ease-in-out infinite; }
.rider-map-geo.is-active {
    background: var(--primary); border-color: var(--primary); color: #fff;
}
.rider-map-geo.is-error { color: #b45309; border-color: #fcd34d; }
/* Ubicación encendida pero SIN trazado (el rider lo apagó con el mismo botón):
   estado intermedio — el punto azul sigue en el mapa, así que el botón no puede
   verse apagado del todo, pero tampoco relleno como cuando hay ruta. Contorno
   primario: "te estoy ubicando, sin la línea". */
.rider-map-geo.is-active.is-dot-only {
    background: rgba(255,255,255,0.94); color: var(--primary); border-color: var(--primary);
}

/* Ojo: filtro "solo lo que falta". Activo = relleno primario, igual que "mi
   ubicación": los dos son interruptores de la misma columna y encendido se lee
   igual en ambos. El icono (ojo / ojo tachado) lo cambia map-rider.js. */
.rider-map-eye.is-active {
    background: var(--primary); border-color: var(--primary); color: #fff;
}
@keyframes geoPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }

/* Mensaje bajo los controles: no tapa el mapa (pointer-events: none) y solo el
   error de permiso queda fijo — el resto se va solo (lo maneja map-rider.js). */
.rider-geo-msg {
    position: absolute;
    z-index: 3;
    /* 52px de guarda a cada lado (= 44 del control + gap): a la izquierda por el
       botón de volver, a la derecha por simetría — así el aviso queda centrado
       sobre el mapa y puede envolver en varias líneas sin pisar nada. */
    top: calc(var(--space-2) + env(safe-area-inset-top, 0px));
    left: calc(var(--space-2) + 52px); right: calc(var(--space-2) + 52px);
    margin: 0 auto; max-width: 24rem;
    pointer-events: none;
    background: rgba(17, 24, 39, 0.88); color: #fff;
    font-size: 0.78rem; line-height: 1.3;
    padding: 7px 10px; border-radius: 10px; text-align: center;
    box-shadow: 0 2px 8px rgba(0,0,0,0.18);
}
.rider-geo-msg.is-warn { background: rgba(180, 83, 9, 0.92); }
.rider-geo-msg.is-error { background: rgba(185, 28, 28, 0.94); }

/* ── Píldora "traspaso entrante" ───────────────────────────────────────────────
   Aviso de que otro rider le propuso una entrega, sobre el mapa y en el mismo
   morado que todo el traspaso. Va en la banda superior CENTRAL (entre las dos
   columnas de controles, igual que el mensaje de ubicación) y lleva al Inicio,
   donde están los botones de aceptar/rechazar. Translúcida con blur: es un
   aviso, no una barra — el mapa se sigue leyendo detrás. */
.rider-handoff-pill {
    position: absolute;
    z-index: 5;                 /* por encima del mapa, por debajo de la hoja */
    top: calc(var(--space-2) + env(safe-area-inset-top, 0px));
    left: 50%; transform: translateX(-50%);
    max-width: calc(100% - 116px);   /* 52px de guarda por columna de controles */
    display: flex; align-items: center; gap: 9px;
    padding: 7px 13px 7px 9px; border-radius: 999px;
    text-decoration: none;
    background: rgba(243, 238, 254, 0.86);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    border: 1px solid rgba(124, 58, 237, 0.28);
    box-shadow: 0 6px 20px rgba(124, 58, 237, 0.16);
}
.rider-handoff-pill .rhp-icon {
    flex: none; width: 24px; height: 24px; border-radius: 50%;
    background: rgba(124, 58, 237, 0.92); color: #fff;
    display: flex; align-items: center; justify-content: center; font-size: 0.78rem;
}
.rider-handoff-pill .rhp-text {
    font-size: 0.8rem; font-weight: 800; color: #6d28d9;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rider-handoff-pill .rhp-ttl {
    flex: none; font-size: 0.74rem; font-weight: 800; color: #6d28d9;
    background: rgba(124, 58, 237, 0.14); border-radius: 999px; padding: 2px 8px;
}
/* Con píldora presente, el mensaje de ubicación baja para no solaparse (los dos
   viven en la misma banda; la píldora es persistente y el mensaje, pasajero). */
.rider-handoff-pill ~ .rider-geo-msg {
    top: calc(var(--space-2) + 46px + env(safe-area-inset-top, 0px));
}

/* ── Píldora "cuánto falta" (trazado hasta la parada, Fase B) ─────────────────
   Distancia y tiempo estimados en formato CORTO ("4,2 km · 18 min"), sobre el
   mapa y arriba a la izquierda. No vive en la hoja a propósito: es el dato que
   se mira de reojo con el móvil en el manillar, y ahí abajo quedaba debajo del
   resumen —desapareciendo al colapsar la hoja— compitiendo con la lista de
   paradas, que es lo que se opera. Separada de las esquinas (no pegada al borde)
   para no pelear con el redondeo de la pantalla ni con la barra de estado.
   Tocarla encuadra mi posición + la ruta activa entera. */
.rider-route-pill {
    position: absolute;
    z-index: 5;
    top: calc(var(--space-2) + 6px + env(safe-area-inset-top, 0px));
    left: calc(var(--space-2) + 6px);
    display: flex; align-items: center; gap: 7px;
    padding: 7px 12px; border-radius: 999px;
    border: 1px solid rgba(29, 78, 216, 0.22);
    background: rgba(255, 255, 255, 0.9);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    box-shadow: 0 4px 14px rgba(17, 24, 39, 0.14);
    color: #1d4ed8;                     /* el azul del trazado que resume */
    font-size: 0.82rem; font-weight: 800; line-height: 1;
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
    cursor: pointer;
}
.rider-route-pill .bi { font-size: 0.9rem; opacity: 0.8; }
/* Vista enfocada: el botón de volver ocupa la esquina, la píldora se corre. */
.rider-route-pill.is-with-back { left: calc(var(--space-2) + 52px); }
/* Con píldora de traspaso (centrada en la misma banda) baja una fila, igual que
   el mensaje de ubicación. */
.rider-handoff-pill ~ .rider-route-pill {
    top: calc(var(--space-2) + 46px + env(safe-area-inset-top, 0px));
}

/* ── La hoja ──────────────────────────────────────────────────────────────── */
.rider-sheet {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    z-index: 4;
    display: flex; flex-direction: column;
    background: #fff;
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -6px 24px rgba(0,0,0,0.16);
    will-change: transform;                 /* solo transform, nunca height/top */
    transform: translate3d(0, 0, 0);
    touch-action: pan-y;
}
.rider-sheet.is-animating { transition: transform 0.26s cubic-bezier(0.22, 0.61, 0.36, 1); }

/* Asa: barrita fina. Su alto bajó de 44 a 30px porque ya NO es la única forma de
   mover la hoja — desde 2026-08-05 se arrastra desde la cabecera y el cuerpo
   (bottom-sheet.js), así que el asa es la señal de que la hoja se mueve y el
   atajo del tap, no el único blanco. Esos 14px son mapa: entran enteros en la
   altura colapsada, que se mide como asa + cabecera + pie.
   touch-action:none SOLO acá — así el tap-toggle no compite con nada. */
.rider-sheet-handle {
    flex: 0 0 auto;
    height: 30px; width: 100%;
    display: flex; align-items: center; justify-content: center;
    border: 0; background: transparent; padding: 0;
    touch-action: none;
    cursor: grab;
    -webkit-tap-highlight-color: transparent;
}
.rider-sheet-handle:active { cursor: grabbing; }
.rider-sheet-grip {
    display: block; width: 42px; height: 5px; border-radius: 3px; background: #d1d5db;
}
.rider-sheet-handle:active .rider-sheet-grip { background: #9ca3af; }

/* Línea de status compacto: lo ÚNICO que se lee con la hoja colapsada, así que
   nunca se corta ni se envuelve en dos líneas. */
.rider-sheet-peek {
    flex: 0 0 auto;
    display: flex; align-items: center; gap: 8px;
    padding: 0 var(--space-3) 8px;
    /* La cabecera también arrastra la hoja (bottom-sheet.js) y no scrollea nada:
       con `pan-y` heredado, el navegador podía interpretar el gesto como un
       scroll de la página y cancelar el arrastre a mitad de camino. */
    touch-action: none;
    font-size: 0.86rem; color: #111827; font-weight: 600;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rider-sheet-peek .peek-sub {
    font-weight: 400; color: #6b7280; overflow: hidden; text-overflow: ellipsis;
}
.rider-sheet-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;           /* el scroll no se escapa al mapa */
    -webkit-overflow-scrolling: touch;
    padding: 0 var(--space-3) var(--space-3);
}

/* Contenido propio de la hoja en la vista ENFOCADA */
.rider-sheet-next {
    display: flex; align-items: center; gap: 8px;
    background: #eff6ff; color: #1e3a8a; border: 1px solid #bfdbfe;
    border-radius: 10px; padding: 8px 10px; font-size: 0.84rem; margin-bottom: 10px;
}
.rider-sheet-next span { flex: 1; min-width: 0; }
/* Trazado hasta la parada en curso (geolocalización Fase B): lo que hay que LEER
   y decidir — la parada no está ubicada, ORS no contestó. Los km y el tiempo se
   fueron a la píldora sobre el mapa (.rider-route-pill): son un dato de un
   vistazo, no algo que se lee dentro de la hoja. Por eso esta caja solo aparece
   en 'is-warn': el rider tiene que enterarse de que no hay ruta, no quedarse
   esperándola. */
.rider-route-me {
    display: flex; align-items: center; gap: 8px;
    background: #fffbeb; color: #92400e; border: 1px solid #fde68a;
    border-radius: 10px; padding: 8px 10px; font-size: 0.84rem; margin-bottom: 10px;
}
.rider-route-me .rrm-text { flex: 1; min-width: 0; }

.rider-sheet-actions { margin-bottom: 12px; }
.rider-sheet-actions .aoc-action-btn { width: 100%; }

/* Parada recién completada (?done=): pulso de ~2s. Es feedback, no navegación:
   el foco NO avanza solo a la parada siguiente. */
.map-stop-row.is-just-done {
    animation: mapStopDone 2s ease-out 1;
}
@keyframes mapStopDone {
    0%   { box-shadow: 0 0 0 0 rgba(34,197,94,0.55); background: #ecfdf5; }
    70%  { box-shadow: 0 0 0 10px rgba(34,197,94,0); background: #ecfdf5; }
    100% { box-shadow: 0 0 0 0 rgba(34,197,94,0); }
}

@media (min-width: 900px) {
    /* Panel lateral fijo: sin gestos, sin transform (bottom-sheet.js lo detecta
       con la misma media query y se desactiva). */
    .rider-sheet {
        top: 0; left: auto; right: 0; bottom: 0;
        width: 380px;
        height: auto !important;
        transform: none !important;
        border-radius: 0;
        box-shadow: -4px 0 18px rgba(0,0,0,0.10);
    }
    .rider-sheet-handle { display: none; }
    .rider-sheet-peek { padding-top: var(--space-3); }
    /* Panel fijo: la hoja no se traslada, así que el pie tampoco. */
    .rider-sheet-foot { transform: none !important; }
    .rider-map-fabs { right: calc(380px + var(--space-2)); }
    .rider-geo-msg { right: calc(380px + var(--space-2) + 52px); }
    /* El panel lateral ocupa 380px a la derecha: la píldora se centra sobre el
       MAPA, no sobre el shell entero (si no, queda medio tapada por el panel). */
    .rider-handoff-pill { left: calc(50% - 190px); }
}

/* ── Popup de parada (compartido) ─────────────────────────────────────────── */
.maplibregl-popup-content { border-radius: 10px; padding: 0; overflow: hidden; }
.mess-map-popup { font-size: 0.82rem; color: #1f2937; }
.mess-pop-head {
    display: flex; align-items: center; gap: 6px;
    padding: 8px 10px;
    border-left: 4px solid var(--primary);
    background: #f9fafb;
    font-size: 0.9rem;
}
.mess-pop-status {
    margin-left: auto;
    font-size: 0.68rem; font-weight: 700; text-transform: uppercase;
    color: var(--gray-500);
}
.mess-pop-row { padding: 4px 10px 0; }
.mess-pop-sub { color: var(--gray-500); font-size: 0.75rem; padding-bottom: 8px; }
/* Selector de pedido cuando varios coinciden en el mismo punto (modo "crear ruta") */
.mess-pop-pick {
    display: flex; align-items: flex-start; gap: 7px; cursor: pointer;
    padding: 6px 10px; font-size: 0.8rem; border-top: 1px solid var(--gray-200);
}
.mess-pop-pick:hover { background: var(--gray-100); }
.mess-pop-pick input { margin-top: 2px; flex-shrink: 0; }
.mess-pop-pick .mess-pop-sub { padding: 0; }

/* Tarjeta del popup teñida por TIPO de parada (mismo código de color que el
   halo del mapa y que el borde de la fila del panel): recogida verde, entrega
   ámbar, traspaso morado. Tinte translúcido sobre fondo claro + borde fino del
   color, para que el texto siga legible sobre el mapa.
   Las reglas cuelgan de la clase común `.mess-pop-stop` y cada variante solo
   aporta sus tres variables — así sumar un tipo nuevo es una línea, no repetir
   el bloque entero (ver popupClassFor en map-common.js). */
.mess-pop-stop .maplibregl-popup-content {
    border: 1px solid var(--pop-accent);
    background: var(--pop-bg);
    /* Translúcido de verdad: el mapa se ve detrás. El blur + la sombra de texto
       de abajo son los que mantienen legible el contenido. */
    backdrop-filter: blur(3px) saturate(1.1);
    -webkit-backdrop-filter: blur(3px) saturate(1.1);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.22);
}
/* Tinte claro y semitransparente + tinta CASI NEGRA teñida del color: mantiene
   la transparencia pero deja el contraste texto/fondo bien por encima de 7:1. */
.mess-pop-recogida { --pop-accent: #15803D; --pop-bg: rgba(226, 252, 235, 0.74); --pop-ink: #04240F; }
.mess-pop-entrega { --pop-accent: #A16207; --pop-bg: rgba(254, 249, 205, 0.74); --pop-ink: #2E1704; }
.mess-pop-traspaso { --pop-accent: #6D28D9; --pop-bg: rgba(240, 233, 255, 0.74); --pop-ink: #1E0A3C; }
.mess-pop-stop .mess-map-popup {
    color: var(--pop-ink);
    font-weight: 500;
    /* Halo claro mínimo: despega el texto si debajo cae una zona oscura del
       mapa, sin llegar a verse borroso. */
    text-shadow: 0 0 3px rgba(255, 255, 255, 0.9);
}
.mess-pop-stop .mess-pop-head {
    background: color-mix(in srgb, var(--pop-accent) 22%, transparent);
    border-left-color: var(--pop-accent);
    color: var(--pop-ink); font-weight: 700;
}
/* Subtexto: opaco (mezclado con blanco, NO con transparente) para que no se
   desvanezca sobre el mapa. */
.mess-pop-stop .mess-pop-sub { color: color-mix(in srgb, var(--pop-ink) 80%, #ffffff); }
/* Botón "Traspasar a otro rider": relleno sólido oscuro, texto blanco — sobre
   el tinte de color el contorno en color primario no se leía. */
.mess-pop-stop .mess-pop-handoff {
    background: var(--pop-ink);
    border-color: var(--pop-ink);
    color: #fff;
    text-shadow: none;
    font-weight: 600;
}
.mess-pop-stop .mess-pop-handoff:hover {
    background: var(--pop-accent);
    border-color: var(--pop-accent);
    color: #fff;
}
.mess-pop-stop .mess-pop-rt {
    color: var(--pop-ink);
    background: color-mix(in srgb, var(--pop-accent) 25%, transparent);
}
.mess-pop-stop .maplibregl-popup-tip { border-top-color: var(--pop-bg); border-bottom-color: var(--pop-bg); }
.mess-pop-stop .maplibregl-popup-close-button { color: var(--pop-ink); }

/* ══════════════════════════════════════════════════════════════════════════
   RIDER · hoja del mapa: contextos LISTA y PARADA (rediseño 2026-07-25)

   La hoja muestra UNA de dos cosas y el nivel al que está abierta decide
   cuánto de esa cosa se ve:

     data-context="lista"   resumen (colapsada) → lista de paradas.
     data-context="parada"  cabecera + acciones (colapsada) → detalle (medio)
                            → notas del pedido y del traspaso (completo).

   `data-level` lo escribe bottom-sheet.js al asentarse la hoja (0/1/2). Es el
   único mecanismo de "qué entra en cada altura": nada se calcula en JS por
   cada bloque, así que agregar una sección nueva es ponerle la clase de su
   nivel y nada más.
   ══════════════════════════════════════════════════════════════════════════ */
.rider-sheet[data-level="0"] .sheet-lvl-mid,
.rider-sheet[data-level="0"] .sheet-lvl-full,
.rider-sheet[data-level="1"] .sheet-lvl-full { display: none; }

/* `data-level` se escribe EN VIVO (ver bottom-sheet.js): el bloque aparece
   mientras la hoja sube, no al terminar de subir. Entra con un fundido corto —
   sin él, el bloque se materializa de golpe a mitad del recorrido y se lee como
   un salto; con él, la hoja y su contenido son el mismo movimiento. Es más corto
   que la transición de la hoja (0.26s) a propósito: tiene que estar entero antes
   de que la hoja se detenga. */
.rider-sheet .sheet-lvl-mid,
.rider-sheet .sheet-lvl-full { animation: sheetLvlIn 0.18s ease-out both; }
@keyframes sheetLvlIn {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

/* Acción en curso (una acción de parada viaja por fetch: ver map-rider.js).
   Hilo indeterminado en el borde superior de la hoja + el pie apagado: el rider
   tocó "Entregado" y tiene que ver que se está haciendo algo. Antes esto era una
   recarga de página entera —el mapa se ponía en blanco y volvía—, así que el
   "está pasando algo" lo daba el parpadeo; sin recarga hay que decirlo. */
.rider-sheet::after {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
    border-radius: 18px 18px 0 0;
    background: linear-gradient(90deg, transparent, var(--primary), transparent);
    opacity: 0; pointer-events: none;
    transition: opacity 0.15s;
}
.rider-sheet.is-busy::after { opacity: 1; animation: sheetBusy 1.1s linear infinite; }
@keyframes sheetBusy {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
}
.rider-sheet.is-busy .rider-sheet-foot { opacity: 0.55; pointer-events: none; }
.rider-sheet-foot { transition: opacity 0.15s; }

/* Cambio de parada (tocar otra, o el avance automático al cerrar una): la
   tarjeta nueva entra con el mismo fundido. Sin esto el reemplazo es un corte
   seco y se lee como el parpadeo de recarga que esta tanda vino a sacar. */
.sheet-stop-head.is-swap-in,
.sheet-stop-body.is-swap-in { animation: sheetLvlIn 0.2s ease-out both; }
/* El PIE solo se funde, nunca se desplaza: vive contra-trasladado
   (`--sheet-shift`, ver .rider-sheet-foot) y una animación de `transform` le
   pisaría esa traslación — los botones se irían fuera de pantalla durante la
   animación, que es exactamente el bug que ese contra-traslado arregla. */
.sheet-stop-foot.is-swap-in { animation: sheetFadeIn 0.2s ease-out both; }
@keyframes sheetFadeIn { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
    .rider-sheet .sheet-lvl-mid,
    .rider-sheet .sheet-lvl-full,
    .sheet-stop-head.is-swap-in,
    .sheet-stop-body.is-swap-in,
    .sheet-stop-foot.is-swap-in { animation: none; }
    /* La transición de la HOJA se queda: `settle()` (y con él los insets del
       mapa) cuelga de su `transitionend`. */
    .rider-sheet.is-busy::after { animation: none; }
}

/* Pie: las acciones de la parada abierta. Vive FUERA del cuerpo scrolleable a
   propósito — con la hoja colapsada, los botones tienen que verse igual (el
   snap "auto" de bottom-sheet.js mide asa + cabecera + este pie).

   Y va CONTRA-TRASLADADO (2026-07-26). La hoja tiene siempre el alto del snap
   máximo y se colapsa empujándola hacia abajo con translateY: su borde inferior
   queda FUERA de la pantalla, así que el pie —último hijo del flex— se iba con
   él y los botones no se veían en ninguna altura menor que la máxima (era el bug
   reportado: solo la cabecera y una franja blanca). Deshaciendo esa traslación
   (`--sheet-shift`, que bottom-sheet.js escribe en cada frame del gesto), el pie
   se queda pegado al borde VISIBLE de la hoja a cualquier altura, y el detalle
   scrollea EN MEDIO, entre la cabecera y los botones.

   La transición replica exactamente la de la hoja: si no, al animar el snap el
   pie se adelantaría o quedaría atrás y se vería flotar. */
.rider-sheet-foot {
    flex: 0 0 auto; padding: 0;
    position: relative; z-index: 2;
    background: #fff;
    transform: translate3d(0, calc(-1 * var(--sheet-shift, 0px)), 0);
}
.rider-sheet.is-animating .rider-sheet-foot {
    transition: transform 0.26s cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* El cuerpo reserva el alto del pie: si no, con la hoja a media altura el final
   del detalle quedaría siempre debajo de los botones, sin forma de leerlo. */
.rider-sheet[data-context="parada"] .rider-sheet-body {
    padding-bottom: calc(var(--space-3) + var(--sheet-foot-h, 0px));
}
/* El padding lo pone la tarjeta, no el contenedor: en el contexto LISTA (y en
   una parada ya resuelta) no hay pie visible, y un contenedor con padding
   dejaría una franja blanca bajo la hoja colapsada. */
.sheet-stop-foot { padding: 0 var(--space-3) var(--space-3); }
.rider-sheet-foot .aoc-action-btn,
.rider-sheet-foot .status-action-btn { width: 100%; }

/* ── Contexto LISTA: el resumen de la ruta ─────────────────────────────── */
.sheet-lista-peek { display: flex; flex-direction: column; gap: 2px; }
/* El peek era de una sola línea (status compacto): el contenido de los dos
   contextos nuevos ocupa varias, así que anulan el nowrap heredado. */
.sheet-lista-peek, .sheet-stop-head, .sheet-grupo-head { width: 100%; white-space: normal; }
/* Nombre del recorrido y cuántas paradas quedan comparten LÍNEA: son la misma
   frase ("tu ruta, 12 paradas") y apiladas costaban una línea entera de la
   altura colapsada, que es la que decide cuánto mapa se ve. */
.slp-top { display: flex; align-items: baseline; gap: 8px; }
.slp-title {
    flex: 1; min-width: 0;
    font-size: 1.02rem; font-weight: 800; color: #0f172a;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.slp-count { flex: none; font-size: 0.8rem; font-weight: 600; color: #64748b; }
.slp-now {
    display: flex; align-items: center; gap: 7px;
    font-size: 0.82rem; font-weight: 700; color: #64748b;
}
.slp-now::before {
    content: ""; width: 10px; height: 10px; border-radius: 50%;
    background: currentColor; flex-shrink: 0;
}
.slp-now.is-recogida { color: #16A34A; }
.slp-now.is-entrega { color: #B45309; }
.slp-now.is-traspaso { color: #6D28D9; }

/* ── Contexto PARADA: cabecera ─────────────────────────────────────────── */
.sheet-stop-head { display: flex; align-items: flex-start; gap: 10px; width: 100%; }
.sheet-stop-head .ssh-main { flex: 1; min-width: 0; }
.ssh-addr {
    font-size: 1.16rem; font-weight: 800; line-height: 1.18; color: #1e293b;
    overflow-wrap: anywhere; white-space: normal;
}
/* Una parada ya resuelta no compite por la atención: su dirección es un dato,
   no una instrucción. */
.sheet-stop-head.is-done .ssh-addr { font-size: 1.05rem; color: #475569; }
.ssh-meta {
    display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
    margin-top: 5px; white-space: normal;
}
.ssh-type { font-size: 0.82rem; font-weight: 800; letter-spacing: 0.04em; }
.sheet-stop-head.is-recogida .ssh-type { color: #16A34A; }
.sheet-stop-head.is-entrega .ssh-type { color: #B45309; }
.sheet-stop-head.is-traspaso .ssh-type { color: #6D28D9; }
.ssh-order { font-size: 0.82rem; font-weight: 600; color: #64748b; }
/* El CP y la ciudad cierran la línea de la calle, apagados y sin cortar: son
   contexto, no el dato que se busca. */
.ssh-place { font-size: 0.8em; font-weight: 700; color: #94a3b8; white-space: nowrap; }
/* Segundo nivel de la dirección: local, planta, puerta. Solo se dibuja cuando
   existe (ver la cabecera en rider/_stop_sheet.html), así que no reserva
   espacio ni deja un hueco cuando la parada no lo tiene. */
.ssh-unit {
    display: flex; align-items: center; gap: 6px; margin-top: 3px;
    font-size: 0.92rem; font-weight: 700; color: #334155;
    overflow-wrap: anywhere;
}
.ssh-unit i { color: #94a3b8; font-size: 0.9em; flex: 0 0 auto; }
/* La franja comprometida: es lo único de la cabecera que marca un límite, así
   que se distingue del resto de la meta con superficie propia — pero sin
   competir con la dirección, que sigue mandando. */
.ssh-win {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 2px 9px; border-radius: 20px;
    background: #eef2ff; color: #3730a3; border: 1px solid #c7d2fe;
    font-size: 0.8rem; font-weight: 800; white-space: nowrap;
}
.ssh-win i { color: #6366f1; font-size: 0.9em; }
.ssh-close {
    flex: 0 0 auto; width: 40px; height: 40px; border: 0; border-radius: 12px;
    background: #f1f3f7; color: #475569; font-size: 1rem; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    -webkit-tap-highlight-color: transparent;
}

/* ── Contexto GRUPO: 3+ paradas en el mismo punto ──────────────────────────
   El marcador agrupado no puede decir cuál es cuál (y si mezcla tipos va en
   gris), así que esta lista es donde se distingue: cada fila lleva el color y
   el número de SU parada, y al tocarla se entra al contexto PARADA normal. */
.sheet-grupo-head { display: flex; align-items: flex-start; gap: 10px; width: 100%; }
.sheet-grupo-head .sgh-main { flex: 1; min-width: 0; }
.sgh-title { font-size: 1.15rem; font-weight: 800; color: #0f172a; }
.sgh-sub { font-size: 0.85rem; color: #64748b; margin-top: 3px; }
.sheet-group-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.sheet-group-row {
    display: flex; align-items: center; gap: 10px; cursor: pointer;
    background: #fff; border: 1px solid #e6e9ef; border-left-width: 4px;
    border-radius: 12px; padding: 10px 11px; min-height: 56px;
    -webkit-tap-highlight-color: transparent;
}
.sheet-group-row.is-recogida { border-left-color: #22C55E; }
.sheet-group-row.is-entrega { border-left-color: #F59E0B; }
.sheet-group-row.is-traspaso { border-left-color: #7C3AED; }
.sheet-group-row.is-done { opacity: 0.62; }
.sheet-group-row .sgr-body { flex: 1; min-width: 0; }
.sgr-l1 { display: flex; align-items: center; gap: 8px; }
.sheet-group-row.is-recogida .map-stop-kind { color: #16A34A; }
.sheet-group-row.is-entrega .map-stop-kind { color: #B45309; }
.sheet-group-row.is-traspaso .map-stop-kind { color: #6D28D9; }
.sgr-go { flex: 0 0 auto; color: #94a3b8; font-size: 0.9rem; }

/* ── Contexto PARADA: detalle (nivel medio) ───────────────────────────── */
.sheet-stop-detail {
    background: #f6f7fa; border-radius: 14px; padding: 12px 13px;
    display: flex; flex-direction: column; gap: 11px; margin-bottom: 10px;
}
.ssd-label {
    font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
    text-transform: uppercase; color: #94a3b8;
}
.ssd-value { font-size: 0.95rem; font-weight: 600; color: #1e293b; margin-top: 2px; }
.ssd-muted { font-weight: 500; color: #94a3b8; }
.ssd-contact {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; flex-wrap: wrap;
}
.ssd-tel {
    display: inline-flex; align-items: center; gap: 7px;
    background: #eaf2fc; color: #2563EB; font-weight: 700; font-size: 0.86rem;
    padding: 9px 13px; border-radius: 10px; text-decoration: none; min-height: 40px;
}
.ssd-items { margin: 3px 0 0; padding-left: 18px; font-size: 0.9rem; color: #334155; }
.ssd-items li { margin-bottom: 3px; }
.ssd-belongs { font-size: 0.8rem; color: #64748b; line-height: 1.9; }
/* Número de una parada HERMANA del pedido dentro de una frase ("Recogida en la
   parada ③"). Círculo del color de su TIPO —verde recogida, ámbar entrega—, el
   mismo par que usan los marcadores y las filas del panel, para que el número
   se lea como "ese pin de allá" y no como un dato suelto. Los rellena
   map-rider.js desde el feed (ver setSeqLabels). */
.seq-chip {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 21px; height: 21px; padding: 0 6px; border-radius: 999px;
    font-size: 0.74rem; font-weight: 800; line-height: 1;
    vertical-align: -3px;
}
.seq-chip--pickup { background: #22C55E; color: #fff; }
.seq-chip--delivery { background: #F59E0B; color: #fff; }
/* Varias entregas: los círculos se separan y bajan de línea si no caben. */
.seq-chip-list { display: inline-flex; flex-wrap: wrap; gap: 5px; vertical-align: -3px; }
/* Parada ya resuelta: el acuse, con su hora */
.sheet-done-mark {
    display: flex; align-items: center; gap: 11px;
    background: #d1fae5; border-radius: 14px; padding: 11px 13px; margin-bottom: 10px;
}
.sheet-done-mark.is-fail { background: #fee2e2; }
.sheet-done-mark.is-handoff { background: #f3eefe; }
.sdm-icon {
    flex: 0 0 auto; width: 36px; height: 36px; border-radius: 50%;
    background: #fff; color: #16A34A; font-size: 1.1rem;
    display: flex; align-items: center; justify-content: center;
}
.sheet-done-mark.is-fail .sdm-icon { color: #dc2626; }
.sheet-done-mark.is-handoff .sdm-icon { color: #6D28D9; }
.sdm-title { font-size: 0.95rem; font-weight: 800; color: #065f46; }
.sheet-done-mark.is-fail .sdm-title { color: #991b1b; }
.sheet-done-mark.is-handoff .sdm-title { color: #5b21b6; }
.sdm-sub { font-size: 0.82rem; font-weight: 600; color: #64748b; }

/* Modo CONSULTA: por qué esta parada no tiene botones (recorrido de otro día o
   ya finalizado). Neutro a propósito — no es un error ni algo que resolver. */
.sheet-readonly-note {
    display: flex; align-items: center; gap: 9px;
    background: #f1f5f9; border-radius: 14px; padding: 10px 13px; margin-bottom: 10px;
    font-size: 0.85rem; font-weight: 700; color: #475569;
}
.sheet-readonly-note .bi { font-size: 1rem; color: #94a3b8; }
/* Chip "solo consulta" en la cabecera de una ruta del panel del rider. */
.map-route-ro {
    flex: 0 0 auto; font-size: 0.62rem; font-weight: 800; letter-spacing: 0.06em;
    text-transform: uppercase; color: #64748b; background: #e2e8f0;
    border-radius: 999px; padding: 2px 7px;
}

/* ── Contexto PARADA: notas y traspaso (nivel completo) ───────────────── */
.sheet-stop-notes {
    border: 1px dashed #dfe3ea; border-radius: 14px; padding: 12px 13px;
    display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px;
}
.ssn-title, .sho-title {
    font-size: 0.7rem; font-weight: 800; letter-spacing: 0.09em;
    text-transform: uppercase; color: #94a3b8;
}
.ssn-text { font-size: 0.9rem; color: #475569; line-height: 1.45; margin-top: 3px; }
.ssn-special {
    display: flex; align-items: flex-start; gap: 8px;
    background: #FEF3C7; border: 1px solid #FCD34D; color: #92400E;
    border-radius: 10px; padding: 9px 11px; font-size: 0.85rem; font-weight: 600;
}
.sheet-stop-handoff {
    background: #F3EEFE; border: 1px solid #ddd6fe; border-radius: 14px;
    padding: 12px 13px; display: flex; flex-direction: column; gap: 7px;
    margin-bottom: 10px;
}
.sheet-stop-handoff .sho-title { color: #7C3AED; }
.sho-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.sho-row span { font-size: 0.82rem; font-weight: 700; color: #7c6bb0; flex: 0 0 auto; }
.sho-row b { font-size: 0.82rem; font-weight: 700; color: #3b2a63; text-align: right; }

/* ── Contexto PARADA: imágenes y nota (nivel completo) ────────────────────
   Una sola fila que envuelve: las dos HERRAMIENTAS (nota y cámara) y, a
   continuación, las miniaturas — primero las heredadas (con candado: se abren,
   no se tocan) y después las de esta parada (con ✕ mientras siga abierta).
   Herramienta y miniatura miden lo mismo y comparten radio: la fila se lee como
   una tira de fichas y no como "botones + galería". La nota, cuando existe, va
   siempre DEBAJO. */
.sheet-stop-media {
    border: 1px dashed #dfe3ea; border-radius: 14px; padding: 12px 13px;
    display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px;
}
.ssm-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.ssm-cam-form, .ssm-del-form { display: contents; }
.ssm-tool {
    flex: 0 0 auto; width: 54px; height: 54px; border-radius: 14px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid #dbe6f6; background: #f4f8fe; color: var(--primary, #4A90E2);
    font-size: 1.25rem; cursor: pointer; position: relative;
    -webkit-tap-highlight-color: transparent;
}
.ssm-tool:hover { background: #e8f1fd; }
/* Punto azul: ya hay una nota escrita (el botón pasa a "editar" sin cambiar de
   sitio ni de tamaño). */
.ssm-tool.has-content::after {
    content: ''; position: absolute; top: 7px; right: 7px;
    width: 8px; height: 8px; border-radius: 50%; background: var(--primary, #4A90E2);
}
.ssm-thumb {
    flex: 0 0 auto; position: relative; width: 54px; height: 54px;
}
.ssm-thumb-btn {
    width: 100%; height: 100%; padding: 0; border-radius: 14px; overflow: hidden;
    border: 1px solid #e5e7eb; background: #f1f5f9; cursor: pointer; display: block;
    -webkit-tap-highlight-color: transparent;
}
.ssm-thumb-btn img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Candado de la imagen heredada (del pedido o de la recogida): dice por qué no
   trae ✕, en vez de dejar la ausencia sin explicación. */
.ssm-lock {
    position: absolute; right: -3px; bottom: -3px;
    width: 20px; height: 20px; border-radius: 50%;
    background: #64748b; color: #fff; font-size: 0.6rem;
    display: flex; align-items: center; justify-content: center;
    border: 2px solid #fff;
}
.ssm-del {
    position: absolute; right: -6px; top: -6px;
    width: 22px; height: 22px; border-radius: 50%; padding: 0;
    background: #fee2e2; color: #b91c1c; border: 2px solid #fff;
    font-size: 0.6rem; display: flex; align-items: center; justify-content: center;
    cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.ssm-empty { font-size: 0.85rem; color: #94a3b8; }
.ssm-note {
    background: #f8fafc; border-radius: 10px; padding: 9px 11px;
    display: flex; flex-direction: column; gap: 4px;
}
.ssm-note.is-locked { background: #f1f5f9; }
.ssm-note-text { font-size: 0.88rem; color: #334155; line-height: 1.45; white-space: pre-wrap; }
.ssm-note-meta { font-size: 0.72rem; color: #94a3b8; font-weight: 600; }
.stop-action-panel--note { border-color: #bfdbfe; }
.sap-submit--note { background: #eff6ff; color: #1d4ed8; }

/* Foto OBLIGATORIA de la entrega: ocupa el lugar del botón principal entre
   "Llegué" y "Entregado" (es un <label> porque contiene el input de la cámara,
   pero se ve y se toca como el botón que reemplaza). */
.aoc-action-btn--cam { margin: 0; cursor: pointer; }

/* Visor de una imagen a pantalla completa (map-rider.js). Por encima de la hoja
   y de los controles del mapa: mientras se mira una foto no hay nada más. */
.rider-photo-viewer {
    position: fixed; inset: 0; z-index: 1090;
    background: rgba(15, 23, 42, 0.92);
    display: flex; align-items: center; justify-content: center; padding: 16px;
}
.rider-photo-viewer[hidden] { display: none; }
.rider-photo-viewer img { max-width: 100%; max-height: 100%; border-radius: 10px; }
.rider-photo-viewer .rpv-close {
    position: absolute; top: 14px; right: 14px;
    width: 40px; height: 40px; border-radius: 50%; border: 0;
    background: rgba(255, 255, 255, 0.16); color: #fff; font-size: 1rem;
    display: flex; align-items: center; justify-content: center; cursor: pointer;
}

/* ── Filas de la lista de paradas (panel del rider) ───────────────────── */
/* El número lleva el color del TIPO, igual que el marcador del mapa: la
   correlación lista↔mapa se lee sin pensar. */
.map-stop-badge {
    flex: 0 0 auto; min-width: 26px; height: 26px; padding: 0 5px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 8px; font-size: 0.85rem; font-weight: 800; color: #fff;
}
.map-stop-badge.is-recogida { background: #22C55E; }
.map-stop-badge.is-entrega { background: #B45309; }
.map-stop-badge.is-traspaso { background: #7C3AED; border-radius: 5px; }
.map-stop-kind {
    font-size: 0.7rem; font-weight: 800; letter-spacing: 0.04em;
    text-transform: uppercase; color: #64748b;
}
.map-stop-row.is-recogida .map-stop-kind { color: #16A34A; }
.map-stop-row.is-entrega .map-stop-kind { color: #B45309; }
.map-stop-row.is-handoff .map-stop-kind { color: #6D28D9; }
.map-stop-row .map-stop-order { font-size: 0.72rem; color: #94a3b8; font-weight: 600; }
.map-stop-row.is-now { border-color: var(--primary, #4A90E2); }
/* .map-status-now se retiró junto con la etiqueta "Ahora" de la lista del rider
   (era su único uso): la parada en curso se marca con la fila entera. */
/* En la hoja del rider la dirección se ve ENTERA: envuelve en las líneas que
   haga falta (calle, número, distrito, CP) en vez de recortarse con puntos
   suspensivos. Antes se recortaba "porque la completa está a un toque", pero
   eso escondía justo el número — el dato que se necesita al llegar. */
.rider-sheet .map-stop-row { align-items: flex-start; }
.rider-sheet .map-stop-addr {
    white-space: normal; overflow-wrap: anywhere;
    font-size: 0.85rem; font-weight: 600; color: #1e293b;
}

@media (min-width: 900px) {
    /* Panel lateral fijo: no hay niveles que valgan, se ve todo. */
    .rider-sheet[data-level] .sheet-lvl-mid,
    .rider-sheet[data-level] .sheet-lvl-full { display: block; }
}

/* ══════════════════════════════════════════════════════════════════════════
   RIDER · Inicio aligerado (rediseño 2026-07-25)

   El Inicio dejó de tener tarjeta de pedido activo y mini-mapa: quedan el
   estado de la jornada, las cifras del día, el cumplimiento, la semana y UNA
   salida al mapa. Los valores salen del diseño (docs/design/rider_design.dc.html).
   ══════════════════════════════════════════════════════════════════════════ */
.dash-greeting { font-size: 1.6rem; font-weight: 800; color: #0f172a; letter-spacing: -0.01em; }

/* ── KPIs del Inicio (rediseño 2026-07-28) ─────────────────────────────────
   Sustituyen a la grilla 2×2 de cifras iguales (.dash-kpi), que además contaba
   PEDIDOS. Tres alturas de lectura, no una pila de tarjetas: hero de jornada →
   avance del día en paradas → bento de KPIs. El tamaño ES la jerarquía; cuatro
   números idénticos no dicen cuál mirar primero.

   Superficie común de todas las piezas: blanco, borde #eceef2, radio 18px
   (mismo lenguaje que .weekly-chart-card y .dash-map-cta). */
.dash-hero, .dash-progress, .dash-tile, .dash-break, .dash-daysum {
    background: #fff; border: 1px solid #eceef2; border-radius: 18px;
}

/* ── Pausa en curso ────────────────────────────────────────────────────────
   Lo único ámbar de la pantalla, y arriba de todo: mientras dura, es el estado
   del rider. El reloj corre en segundos (ver el JS del dashboard). */
.dash-break {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    margin-top: 16px; padding: 14px 16px;
    background: linear-gradient(180deg, #fffbeb 0%, #fef7e0 100%);
    border-color: #fcd34d;
}
.dash-break-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.dash-break-icon {
    flex: none; width: 40px; height: 40px; border-radius: 50%;
    display: grid; place-items: center;
    background: var(--warning, #F39C12); color: #fff; font-size: 1.25rem;
}
.dash-break-label { margin: 0; font-size: 0.98rem; font-weight: 800; color: #92400e; }
.dash-break-since { margin: 2px 0 0; font-size: 0.78rem; font-weight: 600; color: #b45309; }
.dash-break-right { display: flex; align-items: center; gap: 10px; flex: none; }
.dash-break-clock {
    font-size: 1.5rem; font-weight: 800; color: #92400e; letter-spacing: -0.02em;
    font-variant-numeric: var(--num);
}
.dash-break-btn {
    display: inline-flex; align-items: center; gap: 5px;
    border: none; border-radius: 999px; padding: 9px 14px;
    background: #92400e; color: #fff; font-size: 0.85rem; font-weight: 700;
}
.dash-break-btn:active { background: #78350f; }

/* ── Hero: la jornada ──────────────────────────────────────────────────────
   Anillo de cumplimiento con el reloj neto adentro. El reloj vive DENTRO del
   anillo porque son la misma pregunta ("¿cómo voy con mi turno?"): separados
   eran una tarjeta de cumplimiento y un KPI "Horas trabajadas" que decían lo
   mismo dos veces. */
.dash-hero {
    display: flex; align-items: center; gap: 18px;
    margin-top: 16px; padding: 18px 18px 18px 14px;
    text-decoration: none; color: inherit;
}
.dash-hero:active { background: #fafbfc; }
.dash-hero.is-paused { opacity: 0.72; }

.dash-hero-ring { position: relative; flex: none; width: 118px; height: 118px; }
.dash-hero-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.dash-hero-ring circle { fill: none; stroke-width: 11; stroke-linecap: round; }
.dhr-track { stroke: #eef1f5; }
.dhr-fill { stroke: var(--primary, #4A90E2); transition: stroke-dasharray 0.4s ease; }
.dhr-fill.is-done { stroke: var(--success, #27AE60); }
.dash-hero-ring-mid {
    position: absolute; inset: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 1px;
}
.dhr-time {
    font-size: 1.42rem; font-weight: 800; color: #0f172a; line-height: 1;
    letter-spacing: -0.02em; font-variant-numeric: var(--num);
}
.dhr-cap { font-size: 0.68rem; font-weight: 700; color: #94a3b8; text-transform: lowercase; }

.dash-hero-side { min-width: 0; flex: 1; }
.dash-hero-title { margin: 0 0 8px; font-size: 1.02rem; font-weight: 800; color: #0f172a; }
.dash-hero-row {
    display: flex; align-items: baseline; gap: 8px; margin: 0 0 5px;
    font-size: 0.86rem;
}
.dhs-key { color: #64748b; font-weight: 600; flex: 1; }
.dhs-val { color: #0f172a; font-weight: 700; font-variant-numeric: var(--num); }
.dhs-pct {
    font-weight: 800; color: var(--primary, #4A90E2);
    font-variant-numeric: var(--num);
}
.dhs-pct.ok { color: var(--success, #27AE60); }
.dhs-delta { font-weight: 700; font-variant-numeric: var(--num); }
.dhs-delta.pos { color: var(--success, #27AE60); }
.dhs-delta.neg { color: #b45309; }
.dash-hero-shift {
    margin: 8px 0 0; font-size: 0.78rem; font-weight: 600; color: #94a3b8;
    font-variant-numeric: var(--num);
}

/* ── Avance del día, en PARADAS ────────────────────────────────────────────
   Barra segmentada: "14 de 21" no dice si esas 14 salieron bien. Una fallida
   es trabajo cerrado pero no es una entrega, y se ve. */
.dash-progress { margin-top: 12px; padding: 16px; }
.dash-progress-head {
    display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.dash-progress-count {
    margin: 0; font-size: 0.95rem; font-weight: 600; color: #64748b;
    font-variant-numeric: var(--num);
}
.dash-progress-count strong {
    font-size: 1.7rem; font-weight: 800; color: #0f172a; letter-spacing: -0.02em;
}
.dash-progress-pct {
    font-size: 1.05rem; font-weight: 800; color: var(--primary, #4A90E2);
    font-variant-numeric: var(--num);
}
.dash-progress-bar {
    display: flex; height: 10px; margin-top: 10px;
    border-radius: 999px; background: #eef1f5; overflow: hidden;
}
.dpb-seg { display: block; height: 100%; transition: width 0.4s ease; }
.dpb-seg.is-ok { background: var(--success, #27AE60); }
.dpb-seg.is-fail { background: var(--danger, #E74C3C); }
.dash-progress-legend {
    display: flex; flex-wrap: wrap; gap: 12px; margin-top: 9px;
    font-size: 0.78rem; font-weight: 600;
}
.dpl { display: inline-flex; align-items: center; gap: 5px; color: #64748b; }
.dpl::before {
    content: ''; width: 8px; height: 8px; border-radius: 50%; background: #cbd5e1;
}
.dpl.is-ok::before { background: var(--success, #27AE60); }
.dpl.is-fail::before { background: var(--danger, #E74C3C); }

.dash-route-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.dash-route-chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 11px; border-radius: 999px;
    background: #f1f5f9; color: #334155;
    font-size: 0.78rem; font-weight: 600;
}
.dash-route-chip b { font-weight: 800; }
.dash-route-chip .num { font-variant-numeric: var(--num); color: #64748b; }
.dash-route-chip.is-done { background: #d1fae5; color: #065f46; }
.dash-route-chip.is-done .num { color: #047857; }
.dash-progress-empty {
    margin: 12px 0 0; font-size: 0.8rem; font-weight: 600; color: #94a3b8;
}

/* ── Bento de KPIs ─────────────────────────────────────────────────────────
   2 columnas; las piezas .is-wide ocupan la fila entera. El ritmo va ancho
   porque es el número sobre el que el rider puede ACTUAR, y del que sale la
   hora estimada de fin. */
.dash-bento {
    display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px;
}
.dash-tile { padding: 14px 15px; display: flex; flex-direction: column; }
.dash-tile.is-wide { grid-column: 1 / -1; flex-direction: row; align-items: center; gap: 14px; }
.dash-tile.is-wide .dt-main { flex: 1; min-width: 0; }
.dash-tile.is-paused { border-color: #fcd34d; background: #fffdf5; }
.dash-tile.is-quiet { background: #f8fafc; }

.dt-label {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 0.76rem; font-weight: 700; color: #64748b;
    text-transform: uppercase; letter-spacing: 0.03em;
}
.dt-label i { font-size: 0.9rem; color: #94a3b8; }
.dt-value {
    margin: 7px 0 0; font-size: 1.85rem; font-weight: 800; color: #0f172a;
    line-height: 1.05; letter-spacing: -0.02em; font-variant-numeric: var(--num);
}
.dt-value.is-empty { color: #cbd5e1; }
.dt-value.is-warn { color: #b45309; }
.dt-unit { font-size: 0.82rem; font-weight: 700; color: #94a3b8; margin-left: 4px; }
.dt-note { margin: 3px 0 0; font-size: 0.74rem; font-weight: 600; color: #94a3b8; }

/* Costado de las piezas anchas: la consecuencia del número grande (la hora a
   la que terminaría, el enlace al mapa). Separado por una línea, no por otra
   tarjeta: es la misma idea leída hacia adelante. */
.dt-aside {
    flex: none; max-width: 44%; padding-left: 14px; border-left: 1px solid #eef1f5;
    display: flex; flex-direction: column; gap: 2px; text-align: right;
}
.dt-aside-label { font-size: 0.72rem; font-weight: 700; color: #94a3b8; }
.dt-aside-value {
    font-size: 1.35rem; font-weight: 800; color: var(--primary, #4A90E2);
    line-height: 1.1; font-variant-numeric: var(--num);
}
.dt-aside-note { font-size: 0.72rem; font-weight: 600; color: #94a3b8; }
.dt-aside-done {
    font-size: 0.82rem; font-weight: 700; color: var(--success, #27AE60);
    display: inline-flex; align-items: center; gap: 5px; justify-content: flex-end;
}
.dt-aside-link {
    font-size: 0.8rem; font-weight: 700; color: var(--primary, #4A90E2);
    text-decoration: none; white-space: nowrap;
}

/* Resumen del día en la pantalla SIN jornada activa: quien acaba de terminar
   no debería perder de vista lo que hizo. */
.dash-daysum {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
    margin-top: 12px; padding: 14px 10px; text-align: center;
}
.dash-daysum > div { display: flex; flex-direction: column; gap: 2px; }
.dds-num {
    font-size: 1.45rem; font-weight: 800; color: #0f172a;
    font-variant-numeric: var(--num);
}
.dds-num small { font-size: 0.72rem; font-weight: 700; color: #94a3b8; margin-left: 2px; }
.dds-cap { font-size: 0.72rem; font-weight: 600; color: #94a3b8; }

/* Pantallas angostas: el hero pasa a columna antes de que el anillo y el texto
   se peleen el ancho. */
@media (max-width: 380px) {
    .dash-hero { flex-direction: column; align-items: stretch; text-align: center; }
    .dash-hero-ring { align-self: center; }
    .dash-tile.is-wide { flex-direction: column; align-items: stretch; }
    .dt-aside {
        max-width: none; padding: 10px 0 0; text-align: left;
        border-left: none; border-top: 1px solid #eef1f5;
    }
    .dt-aside-done { justify-content: flex-start; }
}

/* Única salida operativa: el mapa. */
.dash-map-cta {
    display: flex; align-items: center; justify-content: center; gap: 10px;
    width: 100%; min-height: 60px; margin: 22px 0 8px;
    border-radius: 18px; background: var(--primary, #4A90E2); color: #fff;
    font-size: 1.05rem; font-weight: 800; text-decoration: none;
    box-shadow: 0 8px 20px rgba(74, 144, 226, 0.35);
}
.dash-map-cta:hover, .dash-map-cta:focus { color: #fff; }
.dash-map-cta i { font-size: 1.3rem; }

/* La tarjeta del gráfico se alinea con las de arriba (el rótulo "Esta semana"
   ya lo pone la sección, así que el título interno se retiró). */
.weekly-chart-card {
    border: 1px solid #eceef2; border-radius: 18px; padding: 16px 14px 10px;
    box-shadow: none; margin: 0;
}

/* ══════════════════════════════════════════════════════════════════════════
   RIDER · hoja del mapa, fidelidad con el diseño (docs/design/rider_design.dc.html
   + docs/design/screenshots/). Todo lo de acá vive bajo `.rider-sheet`: el panel
   del DISPATCHER comparte clases (.map-stop-row, .map-status…) y no se toca.
   ══════════════════════════════════════════════════════════════════════════ */

/* Resumen de la ruta (contexto LISTA, hoja colapsada). */
.slp-title { font-size: 1.28rem; }
.slp-count { font-size: 0.95rem; margin-top: 4px; }
.slp-now { font-size: 0.95rem; font-weight: 800; margin-top: 5px; }
.slp-now::before { width: 11px; height: 11px; }

/* Cabecera de la parada (contexto PARADA): la dirección manda. */
.ssh-addr { font-size: 1.45rem; }
.sheet-stop-head.is-done .ssh-addr { font-size: 1.1rem; }
.ssh-type { font-size: 0.86rem; }
.ssh-order { font-size: 0.86rem; }
.ssh-unit { font-size: 0.98rem; }

/* Filas de la lista de paradas: tarjeta con borde de color por tipo, número a
   la izquierda y la DIRECCIÓN ENTERA como protagonista (screenshots
   sheet_ruta2/3). A la derecha ya no va la etiqueta de estado, solo la marca de
   cerrada (.map-stop-mark): con una parada por línea, el texto del estado le
   comía el ancho a la dirección. El panel del dispatcher usa estas mismas
   clases mucho más chicas y CON estado — de ahí el scope. */
.rider-sheet .map-route-stops { gap: 7px; padding: 4px 2px 8px; }
.rider-sheet .map-stop-row {
    align-items: flex-start; gap: 9px;
    padding: 9px 11px; border-radius: 14px;
    background: #f7f8fb; border: 1px solid transparent; border-left-width: 4px;
    font-size: 0.8rem;
}
.rider-sheet .map-stop-row.is-recogida { border-left-color: #22C55E; }
.rider-sheet .map-stop-row.is-entrega { border-left-color: #FACC15; }
.rider-sheet .map-stop-row.is-handoff { border-left-color: #7C3AED; }
/* La parada EN CURSO ya no lleva la etiqueta "Ahora" (la lista no repite
   estados): el aviso es la fila entera — fondo azul y contorno, dejando el
   borde IZQUIERDO con el color del tipo, que sigue significando otra cosa. */
.rider-sheet .map-stop-row.is-now {
    background: #eaf2fc;
    border-top-color: #4A90E2; border-right-color: #4A90E2; border-bottom-color: #4A90E2;
}
.rider-sheet .map-stop-row.is-now .map-stop-addr { color: #0f172a; font-weight: 700; }
.rider-sheet .map-stop-badge {
    min-width: 27px; height: 27px; border-radius: 9px; font-size: 0.85rem;
}
/* Mismos colores que el marcador: el número de la fila y el del mapa son el
   mismo número, así que no pueden ser de dos colores distintos. */
.rider-sheet .map-stop-badge.is-recogida { background: #22C55E; color: #fff; }
.rider-sheet .map-stop-badge.is-entrega { background: #FACC15; color: #3A2A04; }
.rider-sheet .map-stop-badge.is-traspaso {
    background: #7C3AED; color: #fff; border-radius: 6px;
    clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
.rider-sheet .map-stop-kind { font-size: 0.64rem; letter-spacing: 0.05em; }
.rider-sheet .map-stop-row .map-stop-order { font-size: 0.68rem; }
.rider-sheet .map-stop-body { gap: 1px; }
.rider-sheet .map-stop-addr { font-size: 0.84rem; line-height: 1.25; }
/* El CP cierra la dirección en la misma línea, más apagado: es contexto, no el
   dato que se busca. */
.rider-sheet .map-stop-cp { color: #64748b; font-weight: 600; white-space: nowrap; }
/* La franja horaria en la fila: es el único dato de la lista que no se puede
   deducir del mapa (el orden lo dice el número, el sitio la dirección), así que
   lleva superficie propia para que se encuentre de un vistazo — chica, porque
   la que manda sigue siendo la dirección. Se empuja a la derecha de su línea:
   así todas las ventanas del recorrido quedan en la misma columna y se leen en
   vertical, que es como se mira si el orden aguanta. */
.rider-sheet .map-stop-win { font-size: 0.72rem; padding: 1px 7px; }

/* Marca de parada CERRADA, en lugar de la etiqueta de estado (ver stopMark en
   map-rider.js). Un círculo del tamaño del pulgar-nada: no compite con la
   dirección y se ve de un vistazo cuáles ya están hechas. */
.map-stop-mark {
    flex: 0 0 auto; width: 21px; height: 21px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 0.7rem; line-height: 1; margin-top: 3px;
}
/* La fila del grupo alinea al centro: ahí la marca no se despega de arriba. */
.sheet-group-row .map-stop-mark { margin-top: 0; }
.map-stop-mark.is-ok { background: #d1fae5; color: #047857; }
.map-stop-mark.is-fail { background: #fee2e2; color: #b91c1c; }
.map-stop-mark.is-handoff { background: #ede9fe; color: #6d28d9; }

/* ── Acciones de la parada, EN LÍNEA ───────────────────────────────────────
   La fila es lo único que se toca con la hoja abajo: acción principal ancha +
   "Fallida" (y "Traspaso") al lado. Los botones secundarios NO se abren en el
   sitio: son disparadores de tamaño FIJO y su formulario se despliega en el
   cuerpo de la hoja (.stop-action-panel). Antes eran <details> acá adentro y al
   abrirlos el botón saltaba de fila y el pie crecía hasta tapar la dirección. */
.sheet-actions { display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px; }
.sheet-actions > form { flex: 1.6 1 84px; min-width: 0; margin: 0; }
.sheet-actions > .aoc-hint { flex: 1 0 100%; margin: 0; }
.sheet-actions .aoc-action-btn,
.sheet-actions .status-action-btn {
    width: 100%; min-height: 62px; border-radius: 16px;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; font-size: 0.72rem; font-weight: 800; line-height: 1.1; text-align: center;
}
.sheet-actions .aoc-action-btn i { font-size: 1.2rem; }
/* Hasta CUATRO en la misma fila (acción principal + Ir + Fallida + Traspaso):
   las bases son chicas a propósito y `min-width: 0` deja que se respeten — sin
   eso el ancho MÍNIMO DEL CONTENIDO ("Traspaso" es una palabra que no parte)
   hace de suelo, la suma no entra y el último botón cae a una segunda fila. Esa
   fila de más engorda el pie, y el pie le come alto al mapa, que es lo único
   que el rider mira mientras pedalea. Por eso se achican los cuatro antes que
   apilar: la fila importa más que el tamaño de cada botón.
   El tamaño NO cambia con el panel abierto — el botón se queda donde el rider
   lo tocó. */
.sheet-actions > .sheet-action-trigger {
    flex: 1 1 54px; min-width: 0; margin: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; min-height: 62px; border-radius: 16px;
    border: 2px solid #fecaca; background: #fff; color: #dc2626;
    font-size: 0.72rem; font-weight: 800; line-height: 1.1; text-align: center;
    cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.sheet-actions > .sheet-action-trigger i { font-size: 1.2rem; }
/* La etiqueta puede achicarse un punto más antes que romper la fila: el icono ya
   dice cuál es cada botón y el texto es la confirmación, no la señal. */
.sheet-actions > .sheet-action-trigger > span { font-size: 0.68rem; }
/* Panel abierto: el botón queda "hundido" para que se vea de dónde salió lo que
   está mostrando el cuerpo de la hoja. */
.sheet-actions > .sheet-action-trigger.is-open { background: #fef2f2; }
.sheet-actions > .sheet-action-trigger--handoff.is-open { background: #faf5ff; }
/* El botón principal en la hoja va RELLENO (el `.aoc-action-btn` de siempre es
   blanco sobre azul, porque nació dentro de la tarjeta de color del Inicio, que
   ya no existe). Acá el fondo es blanco, así que el relleno es el azul. */
.sheet-actions .aoc-action-btn {
    background: var(--primary, #4A90E2); color: #fff;
    box-shadow: 0 6px 16px rgba(74, 144, 226, 0.3);
}
.sheet-actions .aoc-action-btn:hover,
.sheet-actions .aoc-action-btn:focus { background: #3a7bc8; color: #fff; }

/* "Traspaso" no es un problema como "Fallida": es ceder la entrega a otro
   rider. Mismo botón, código de color del traspaso (morado, el de los
   marcadores y los badges de la cadena de custodia). */
.sheet-actions > .sheet-action-trigger--handoff {
    border-color: #ddd6fe; color: #7C3AED;
}

/* "Ir" (navegación externa) no cierra ni cambia nada: solo abre Google Maps.
   Por eso va en NEUTRO —ni el rojo de "Fallida" ni el morado del traspaso, que
   son códigos de desenlace— y sin relleno, para no competir con la acción
   principal, que sigue siendo la que manda en la fila. Es un <a>, no un
   <button>: hereda el layout de `.sheet-action-trigger` pero necesita que le
   quiten el subrayado del enlace. */
.sheet-actions > .sheet-action-trigger--go {
    border-color: #cbd5e1; color: #334155; text-decoration: none;
}
.sheet-actions > .sheet-action-trigger--go:hover,
.sheet-actions > .sheet-action-trigger--go:focus {
    border-color: #94a3b8; color: #0f172a; background: #f8fafc;
}

/* ── Paneles de las acciones secundarias (2026-07-27) ──────────────────────
   El formulario de "Fallida" / "Traspaso" vive en el CUERPO scrolleable, no en
   el pie: acá puede medir lo que necesite sin mover un solo botón ni comerse la
   cabecera. Mientras uno está abierto la tarjeta no muestra nada más, así que
   la hoja subida a tope es el panel y punto. */
.sheet-stop-body.has-open-panel > :not(.sheet-stop-panels) { display: none; }
.stop-action-panel {
    border: 1px solid #e5e7eb; border-radius: 14px; background: #fff;
    padding: var(--space-3); margin-top: var(--space-2);
}
.stop-action-panel--fail { border-color: #fecaca; }
.stop-action-panel--handoff { border-color: #ddd6fe; }
.sap-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.sap-title { flex: 1 1 auto; font-size: 0.98rem; font-weight: 800; color: #0f172a; }
.sap-title .pkg-ico { margin-right: 4px; }
.stop-action-panel--fail .sap-title { color: #b91c1c; }
.stop-action-panel--handoff .sap-title { color: #6d28d9; }
.sap-close {
    flex: 0 0 auto; width: 34px; height: 34px; border-radius: 10px;
    border: 1px solid #e5e7eb; background: #fff; color: #6b7280; cursor: pointer;
}
.sap-lead { font-size: 0.82rem; color: #475569; margin: 0 0 8px; }
.sap-empty {
    display: flex; align-items: center; gap: 8px; margin: 0;
    border-radius: 10px; padding: 10px 12px;
    background: #f8fafc; color: #475569; font-size: 0.86rem; font-weight: 600;
}
.sap-submit {
    width: 100%; min-height: 44px; border: 0; border-radius: 10px;
    font-size: 0.88rem; font-weight: 700; cursor: pointer;
}
.sap-submit--fail { background: #fef2f2; color: #b91c1c; }
.sap-submit--handoff { background: #faf5ff; color: #6d28d9; }

/* ── Familia de iconos "paquete" (2026-07-26) ──────────────────────────────
   Una MISMA caja (bi-box-seam) con un badge distinto según lo que hace el
   botón: check al cerrar la parada, aspa en "Fallida", flechas en "Traspaso".
   Se compone de dos glifos en vez de dibujar un SVG: la silueta sale toda de
   `currentColor`, así el mismo marcado se ve blanco sobre el botón azul, rojo
   sobre el de borde rojo y morado sobre el de traspaso — sin una variante de
   icono por color.

   El badge lleva un disco del color del FONDO del botón (--pkg-bg) para
   separarse de la caja: sin ese corte, los dos glifos se pisan y no se lee
   ninguno. Por eso cada botón declara su --pkg-bg; el default es blanco. */
.pkg-ico {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 1em; height: 1em; line-height: 1;
    font-size: 1.35rem;                       /* el tamaño de icono de la fila */
}
.pkg-ico .pkg-box { font-size: 1em; line-height: 1; }
.pkg-ico .pkg-badge {
    position: absolute; right: -0.30em; bottom: -0.20em;
    display: inline-flex; align-items: center; justify-content: center;
    width: 1em; height: 1em; font-size: 0.62em; line-height: 1;
    border-radius: 50%;
    background: var(--pkg-bg, #fff);
    box-shadow: 0 0 0 0.14em var(--pkg-bg, #fff);
}
.sheet-actions .aoc-action-btn { --pkg-bg: var(--primary, #4A90E2); }
.sheet-actions .aoc-action-btn:hover,
.sheet-actions .aoc-action-btn:focus { --pkg-bg: #3a7bc8; }


/* ── Calendario (jornadas y despacho) ──────────────────────────────────────
   Las clases `.cal-*` las comparten `admin/shifts_calendar.html` (jornadas) y
   `admin/calendar.html` (pedidos programados, tanda 4). Estaban inline en la
   primera; con dos calendarios vivos, mantener dos copias del mismo bloque es
   la manera de que se separen. */
.cal-table { table-layout: fixed; }
.cal-cell {
    min-height: 70px;
    vertical-align: top;
    padding: 6px 8px;
    width: 14.28%;
}
.cal-cell.empty { background-color: var(--gray-100); }
.cal-cell.today { outline: 2px solid var(--primary); outline-offset: -2px; }
.cal-cell.has-shifts { background-color: color-mix(in srgb, var(--primary) 12%, transparent); }
.cal-day-num { font-size: 0.85rem; font-weight: 600; line-height: 1; margin-bottom: 4px; }
.cal-shift-badge { font-size: 0.72rem; }
.cal-add { color: var(--gray-500); font-size: 0.7rem; }

/* Celda del calendario de DESPACHO: chips de pedidos/rutas + semáforo de
   RESULTADO (tanda C). Tres colores y tres significados distintos:
     rojo  = hay trabajo que NADIE ruteó (lo único accionable),
     ámbar = día pasado que quedó a medias ("esto lo arrastro"),
     verde = día cerrado.
   Rojo y ámbar eran el mismo rojo antes de la tanda C, y son problemas
   distintos: uno se resuelve planificando, el otro ya pasó. */
.cal-cell.is-alert {
    background-color: color-mix(in srgb, var(--danger) 10%, transparent);
    box-shadow: inset 3px 0 0 var(--danger);
}
.cal-cell.is-late {
    background-color: color-mix(in srgb, var(--warning) 14%, transparent);
    box-shadow: inset 3px 0 0 var(--warning);
}
.cal-cell.is-done { box-shadow: inset 3px 0 0 var(--success); }
.cal-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.cal-chip {
    display: inline-flex; align-items: center; gap: 3px;
    font-size: 0.7rem; font-weight: 600; line-height: 1;
    padding: 3px 7px; border-radius: 999px;
    background: var(--gray-100); color: var(--gray-700);
    text-decoration: none; white-space: nowrap;
}
.cal-chip.is-orders { background: color-mix(in srgb, var(--primary) 18%, transparent); }
/* El verde es del RESULTADO ("se entregó"), no de las rutas: dos verdes con
   significados distintos en la misma celda no se leen de un golpe, que es todo
   el punto de la vista mes. La ruta es contexto → neutra. */
.cal-chip.is-routes { background: var(--gray-200); color: var(--gray-700); }
.cal-chip.is-done {
    background: color-mix(in srgb, var(--success) 20%, transparent);
    color: color-mix(in srgb, var(--success) 75%, #000);
}
.cal-chip.is-pending {
    background: color-mix(in srgb, var(--warning) 26%, transparent);
    color: color-mix(in srgb, var(--warning) 70%, #000);
}
.cal-chip.is-alert { background: var(--danger); color: #fff; }
/* Muestras de color de la leyenda: replican el borde de la celda, no el chip. */
.cal-legend-swatch {
    display: inline-block; width: 0.85em; height: 0.85em;
    border-radius: 3px; vertical-align: -0.1em;
    background: var(--gray-200);
}
.cal-legend-swatch.is-alert { background: var(--danger); }
.cal-legend-swatch.is-late { background: var(--warning); }
.cal-legend-swatch.is-done { background: var(--success); }
/* Previsto por una plantilla recurrente: el pedido TODAVÍA no existe, así que
   va punteado — no es trabajo, es una intención (tanda 6). */
.cal-chip.is-planned {
    background: transparent; color: var(--gray-600);
    border: 1px dashed var(--gray-400);
}
.cal-day-link { display: block; text-decoration: none; color: inherit; }
.cal-day-link:hover .cal-day-num { text-decoration: underline; }

/* Cabecera del DÍA (admin/calendar_day.html) */
.cal-day-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.cal-day-titlerow { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.cal-day-title { font-size: 1.25rem; font-weight: 700; margin: 0; text-transform: capitalize; }
/* Estado del día: un día pasado a medias ("arrastra") no puede verse igual que
   uno terminado ("cerrado"). Va fuera del `<h1>` porque el título capitaliza. */
.cal-day-state {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 0.72rem; font-weight: 700; line-height: 1;
    padding: 4px 10px; border-radius: 999px;
    background: var(--gray-100); color: var(--gray-600);
}
.cal-day-state.is-done {
    background: color-mix(in srgb, var(--success) 18%, transparent);
    color: var(--success);
}
/* "Arrastra" es ÁMBAR, no rojo: el rojo del calendario significa "nadie lo
   ruteó" (`is-alert`), que es lo accionable. Un día que quedó a medias es otra
   cosa y con el mismo color no se distinguían (tanda C). El mismo `is-late`
   pinta la celda del mes: una clase, un significado. */
.cal-day-state.is-late {
    background: var(--warning); color: #fff;
}
.cal-day-state.is-now {
    background: color-mix(in srgb, var(--primary) 18%, transparent);
    color: var(--primary);
}
.cal-day-sub { font-size: 0.82rem; color: var(--gray-500); }
.cal-section { margin-bottom: var(--space-4); }
.cal-section-head {
    display: flex; align-items: center; gap: 8px;
    font-size: 0.9rem; font-weight: 700; margin-bottom: var(--space-2);
}
.cal-section-count {
    font-size: 0.74rem; font-weight: 600; color: var(--gray-500);
    background: var(--gray-100); border-radius: 999px; padding: 2px 10px;
}

/* ── SEMANA riders × días (admin/calendar_week.html, tanda 7) ───────────────
   Grilla de reparto: una fila por rider, una columna por día, y abajo el pozo
   de lo que ese día no tiene recorrido. Con 7 columnas + la de riders, la
   tabla no entra en pantallas chicas: scrollea ELLA (`.cal-week-wrap`), nunca
   el body. */
.cal-week-wrap { overflow-x: auto; }
.cal-week { table-layout: fixed; min-width: 1080px; }
.cal-week th, .cal-week td { padding: 6px; vertical-align: top; }
.cal-week-rider-col { width: 150px; }
.cal-week-rider-name { font-weight: 700; font-size: 0.85rem; }
.cal-week-dayname { font-size: 0.72rem; color: var(--gray-500); text-transform: uppercase; }
.cal-week th.today, .cal-week-cell.today { background: color-mix(in srgb, var(--primary) 7%, transparent); }
.cal-week-cell { width: 13.2%; }
.cal-week-cell.is-alert { box-shadow: inset 3px 0 0 var(--danger); }

/* Una ruta dentro de la celda: la barra de color es la del rider, la misma
   que el mapa y la lista de rutas. */
.cal-week-route {
    border-left: 3px solid var(--rider-color, var(--gray-400));
    background: var(--gray-100);
    border-radius: 4px; padding: 4px 6px; margin-bottom: 5px;
}
.cal-week-route-name {
    display: block; font-size: 0.78rem; font-weight: 700;
    text-decoration: none; color: inherit;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-week-route-name:hover { text-decoration: underline; }
.cal-week-route-meta { font-size: 0.68rem; color: var(--gray-600); }

/* El "soltar acá": es un submit de verdad (form= por columna), no un gesto de
   drag-and-drop. Punteado porque es un HUECO donde va lo marcado. */
.cal-week-drop {
    display: inline-flex; align-items: center; gap: 3px;
    font-size: 0.68rem; font-weight: 600; line-height: 1;
    padding: 3px 7px; margin-top: 3px;
    border: 1px dashed var(--gray-400); border-radius: 999px;
    background: transparent; color: var(--gray-600); cursor: pointer;
}
.cal-week-drop:hover { border-color: var(--primary); color: var(--primary); }
.cal-week-drop.is-new { border-style: dotted; }

/* El pozo: lo que ese día nadie recorre todavía. */
.cal-week-pool > .cal-week-cell, .cal-week-pool > th { background: var(--gray-50, var(--gray-100)); }
.cal-week-all { display: block; font-size: 0.68rem; color: var(--gray-600); margin-bottom: 3px; }
.cal-week-order {
    display: flex; align-items: center; gap: 4px;
    font-size: 0.7rem; line-height: 1.2; margin-bottom: 2px; cursor: pointer;
}
.cal-week-order-id { font-weight: 700; }
.cal-week-order-addr { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-week-order-win { color: var(--gray-500); white-space: nowrap; }

/* ══════════════════════════════════════════════════════════════════════════
   RIDER · "Mi semana" (pedidos programados, tanda 8) + la línea del Inicio
   que lleva hasta ahí. Vista de LECTURA: ningún elemento de acá es un botón
   que toque una parada, y el lenguaje visual es deliberadamente más apagado
   que el del Inicio (blanco, sin sombras de color) — lo que se opera es el
   mapa, esto solo informa.
   ══════════════════════════════════════════════════════════════════════════ */

/* Línea "lo que te viene" en el Inicio. */
.dash-next {
    display: flex; align-items: center; gap: 12px;
    margin-top: 18px; padding: 13px 14px;
    background: #fff; border: 1px solid #eceef2; border-radius: 18px;
    text-decoration: none; color: inherit;
}
.dash-next.is-quiet { background: #f8fafc; }
.dash-next-icon {
    flex: none; width: 38px; height: 38px; border-radius: 12px;
    display: grid; place-items: center;
    background: #eef4fd; color: var(--primary, #4A90E2); font-size: 1.1rem;
}
.dash-next-body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
.dash-next-title { font-size: 0.94rem; font-weight: 700; color: #0f172a; line-height: 1.25; }
.dash-next-title b { font-weight: 800; }
.dash-next-sub { font-size: 0.74rem; font-weight: 600; color: #94a3b8; }
.dash-next-chev { flex: none; color: #cbd5e1; font-size: 1.05rem; }

/* Cabecera y navegación de semana. */
.rw-back {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 0.82rem; font-weight: 700; color: #64748b; text-decoration: none;
}
.rw-nav {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    margin-top: 6px; padding: 10px 12px;
    background: #fff; border: 1px solid #eceef2; border-radius: 18px;
}
.rw-nav-btn {
    flex: none; width: 38px; height: 38px; border-radius: 12px;
    display: grid; place-items: center;
    background: #f1f5f9; color: #475569; text-decoration: none; font-size: 1.05rem;
}
.rw-nav-title { text-align: center; line-height: 1.2; }
.rw-nav-title strong { display: block; font-size: 1rem; font-weight: 800; color: #0f172a; }
.rw-nav-title span { font-size: 0.74rem; font-weight: 600; color: #94a3b8; }

/* Totales de la semana (mismo lenguaje que .dash-daysum). */
.rw-summary {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
    margin-top: 12px; padding: 14px 10px; text-align: center;
    background: #fff; border: 1px solid #eceef2; border-radius: 18px;
}
.rw-summary > div { display: flex; flex-direction: column; gap: 2px; }
.rw-sum-num {
    font-size: 1.45rem; font-weight: 800; color: #0f172a;
    font-variant-numeric: var(--num);
}
.rw-sum-num small { font-size: 0.72rem; font-weight: 700; color: #94a3b8; margin: 0 1px; }
.rw-sum-cap { font-size: 0.72rem; font-weight: 600; color: #94a3b8; }

/* Un día por tarjeta: en un teléfono el eje útil es el día, no la grilla
   riders × días del dispatcher. */
.rw-days { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 10px; }
.rw-day {
    padding: 12px 14px;
    background: #fff; border: 1px solid #eceef2; border-radius: 18px;
}
.rw-day.is-today { border-color: var(--primary, #4A90E2); box-shadow: 0 0 0 1px var(--primary, #4A90E2) inset; }
.rw-day.is-past { opacity: 0.62; }
.rw-day.is-empty { background: #f8fafc; }

.rw-day-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 10px; flex-wrap: wrap;
}
.rw-day-when { display: flex; align-items: baseline; gap: 7px; }
.rw-day-name { font-size: 0.95rem; font-weight: 800; color: #0f172a; }
.rw-day-num { font-size: 0.95rem; font-weight: 700; color: #94a3b8; font-variant-numeric: var(--num); }
.rw-today-tag {
    font-size: 0.66rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--primary, #4A90E2); background: #eef4fd;
    padding: 2px 7px; border-radius: 999px;
}
.rw-day-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.rw-chip {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 0.72rem; font-weight: 700; color: #475569;
    background: #f1f5f9; border-radius: 999px; padding: 3px 9px;
}
.rw-chip.is-shift { background: #eef4fd; color: #1d4ed8; }
.rw-chip i { font-size: 0.8rem; }

/* Las rutas del día. Se muestra el RECORRIDO (cuántas paradas, en qué franja),
   no la lista de pedidos: el rider ejecuta paradas, no gestiona pedidos. */
.rw-routes { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 7px; }
.rw-route {
    padding: 9px 11px; border-radius: 13px;
    background: #f8fafc; border: 1px solid #eef1f5;
}
.rw-route.is-done { background: #f6fdf8; border-color: #d6f0de; }
/* La fila entera es el enlace al mapa (modo consulta): sin subrayado ni color de
   enlace — se lee como la tarjeta que ya era, solo que se puede tocar. */
.rw-route-link { display: block; text-decoration: none; color: inherit; }
.rw-route-main { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.rw-route-name { font-size: 0.9rem; font-weight: 800; color: #0f172a; }
.rw-route-stops { font-size: 0.82rem; font-weight: 700; color: #475569; font-variant-numeric: var(--num); }
.rw-route-meta {
    display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 3px;
    font-size: 0.74rem; font-weight: 600; color: #94a3b8;
}
.rw-route-window { display: inline-flex; align-items: center; gap: 5px; }
.rw-route-state.is-open { color: #b45309; }
.rw-route-state.is-done { color: #15803d; display: inline-flex; align-items: center; gap: 5px; }
.rw-route-note { margin: 4px 0 0; font-size: 0.7rem; font-weight: 600; color: #b45309; }

.rw-day-empty { margin: 8px 0 0; font-size: 0.8rem; font-weight: 600; color: #94a3b8; }

.rw-foot {
    margin: 16px 0 0; font-size: 0.76rem; font-weight: 600; color: #94a3b8;
    line-height: 1.4;
}
.rw-cta {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    margin-top: 14px; min-height: 46px; border-radius: 14px;
    background: #f1f5f9; color: #334155; font-size: 0.9rem; font-weight: 800;
    text-decoration: none;
}

/* ── Historial de rutas del rider (.rh-*) ──────────────────────────────────
   Hermano de "Mi semana": misma tarjeta blanca sobre el fondo del rider, misma
   escala tipográfica. Vive acá y no en un <style> de la plantilla, igual que
   los `.rw-*` y los `.cal-*`. */
.rh-filter {
    display: flex; align-items: flex-end; gap: 8px;
    margin-top: 12px; padding: 12px;
    background: #fff; border: 1px solid #eceef2; border-radius: 18px;
}
.rh-filter label { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.rh-filter label span { font-size: 0.72rem; font-weight: 700; color: #94a3b8; }
.rh-filter input {
    width: 100%; min-height: 40px; padding: 0 10px;
    border: 1px solid #e2e8f0; border-radius: 12px;
    font-size: 0.85rem; font-weight: 700; color: #0f172a; background: #f8fafc;
}
.rh-filter button {
    flex: none; width: 44px; height: 40px; border: 0; border-radius: 12px;
    background: var(--primary, #4A90E2); color: #fff; font-size: 1rem;
}

.rh-empty { margin-top: 28px; text-align: center; color: #94a3b8; }
.rh-empty i { font-size: 2rem; }
.rh-empty p { margin: 10px 0 0; font-size: 0.85rem; font-weight: 600; }

.rh-list { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 10px; }
.rh-item {
    padding: 12px 14px;
    background: #fff; border: 1px solid #eceef2; border-radius: 18px;
}
.rh-item-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.rh-item-name { margin: 0; font-size: 0.95rem; font-weight: 800; color: #0f172a; }
.rh-item-day { margin: 2px 0 0; font-size: 0.74rem; font-weight: 600; color: #94a3b8; }
.rh-item-total {
    flex: none; font-size: 1.3rem; font-weight: 800; color: #0f172a;
    font-variant-numeric: var(--num);
}
.rh-letter {
    display: inline-block; margin-left: 4px; padding: 1px 6px; border-radius: 7px;
    background: #f1f5f9; color: #475569; font-size: 0.72rem; font-weight: 800;
}

.rh-marks { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.rh-mark {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 3px 8px; border-radius: 9px;
    font-size: 0.74rem; font-weight: 700; background: #f1f5f9; color: #475569;
}
.rh-mark.is-done { background: #eaf7ee; color: #15803d; }
.rh-mark.is-fail { background: #fdecec; color: #b91c1c; }
.rh-mark.is-hand { background: #f3eefd; color: #6d28d9; }
.rh-mark.is-open { background: #fdf4e6; color: #b45309; }

/* "Ver en el mapa": abre el recorrido archivado en el mapa de siempre, en modo
   consulta. Es la acción PRINCIPAL de la fila; ocultar es la secundaria. */
.rh-map {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    width: 100%; min-height: 40px; margin-top: 10px; text-decoration: none;
    border: 1px solid #dbeafe; border-radius: 12px; background: #eff6ff;
    color: #1d4ed8; font-size: 0.82rem; font-weight: 800;
}
.rh-hide { margin-top: 10px; }
.rh-hide button, .rh-range button {
    width: 100%; min-height: 40px; border: 1px solid #e2e8f0; border-radius: 12px;
    background: #f8fafc; color: #64748b; font-size: 0.8rem; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
}
.rh-range { margin-top: 14px; }
.rh-range button { border-style: dashed; }

/* ==========================================================================
   Acceso (login) — pantalla partida
   Izquierda: panel de marca (tinta). Derecha: el formulario de Flask de
   siempre, solo re-estilizado. En móvil pasa a una columna con la marca
   ARRIBA (el panel va primero en el DOM, así que el orden sale solo).
   ========================================================================== */

.login-split {
    display: grid;
    grid-template-columns: 1fr;
    min-height: 100vh;
}
@media (min-width: 900px) {
    .login-split { grid-template-columns: 1fr 1fr; }
}

/* El pie global de base.html lo comparten todas las vistas anónimas; bajo la
   pantalla partida sobra (deja una franja muerta tras un panel a 100vh) y el
   copyright ya no aporta nada al lado del panel de marca. */
body:has(.login-split) > footer { display: none; }

/* ── Panel de marca ─────────────────────────────────────────────────────── */
/* El fondo NO es un plano liso: dos halos muy tenues (verde arriba-izquierda,
   tinta clara abajo-derecha) le dan profundidad sin competir con el texto.
   Van en `background-image` sobre el color, así que el fallback es el plano
   de siempre. */
.login-brand {
    position: relative;
    background-color: var(--rodago-tinta);
    background-image:
        radial-gradient(60% 55% at 12% 8%,  rgba(21,199,126,0.16), transparent 70%),
        radial-gradient(55% 50% at 92% 96%, rgba(85,120,190,0.16), transparent 70%);
    color: #fff;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--space-6);
    padding: 48px 32px;
    overflow: hidden;
}
@media (min-width: 900px) {
    .login-brand { padding: 56px clamp(32px, 5vw, 72px); }
}

/* El logotipo es lo primero que se lee: manda en la jerarquía del panel. */
.login-brand-logo {
    height: clamp(52px, 8vw, 96px);
    width: auto;
    max-width: 100%;
    display: block;
}

.login-lema {
    margin: 0;
    color: var(--rodago-tinta-text);
    font-size: 1rem;
    line-height: 1.5;
}

.login-features {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3);
}

.login-feature {
    background: var(--rodago-tinta-2);
    border: 1px solid rgba(255,255,255,0.07);
    border-radius: 14px;
    padding: var(--space-6) var(--space-5);
    text-align: center;
}
.login-feature-icon {
    width: 32px; height: 32px;
    color: var(--rodago-verde);
    margin-bottom: var(--space-3);
}
.login-feature-title {
    margin: 0 0 var(--space-2);
    color: var(--rodago-verde);
    font-size: 1rem;
    font-weight: 700;
}
.login-feature-text {
    margin: 0;
    color: #fff;
    font-size: 0.9rem;
    line-height: 1.45;
}

/* ── La separación del medio ────────────────────────────────────────────── */
/* Un corte a hueso entre tinta y blanco se lee como dos capturas pegadas. La
   junta lleva tres cosas: una línea fina que se DESVANECE por los extremos y
   se enciende en verde en el centro, la sombra del panel de marca cayendo
   sobre el claro, y un lavado del lado claro que recoge esa sombra. Solo en
   dos columnas: en móvil los paneles se apilan y no hay junta vertical. */
@media (min-width: 900px) {
    .login-brand {
        z-index: 1;                                  /* la sombra por encima del panel claro */
        box-shadow: 18px 0 40px -18px rgba(22,35,61,0.45);
    }
    .login-brand::after {
        content: "";
        position: absolute;
        top: 0; right: 0; bottom: 0;
        width: 2px;
        background: linear-gradient(
            to bottom,
            transparent 0%,
            rgba(21,199,126,0.15) 22%,
            rgba(21,199,126,0.75) 50%,
            rgba(21,199,126,0.15) 78%,
            transparent 100%);
    }
    .login-access {
        background-image: linear-gradient(
            to right, rgba(22,35,61,0.055), rgba(22,35,61,0) 40%);
    }
}

/* ── Panel de acceso ────────────────────────────────────────────────────── */
.login-access {
    background: var(--surface);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 48px 24px;
}

.login-form-wrap { width: 100%; max-width: 380px; }

/* Marca en pequeño sobre el formulario: el logotipo grande ya está enfrente,
   acá solo hace falta que el formulario no se lea como una caja anónima. */
.login-lockup {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-6);
}
.login-lockup-mark { height: 34px; width: 34px; display: block; }
.login-lockup-word {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--rodago-tinta);
}

.login-title {
    margin: 0 0 var(--space-1);
    font-size: 1.75rem;
    font-weight: 700;
    color: var(--gray-900);
}
.login-subtitle {
    margin: 0 0 var(--space-6);
    color: var(--gray-500);
    font-size: 0.95rem;
}

.login-field { margin-bottom: var(--space-4); }
.login-label {
    display: block;
    margin-bottom: var(--space-2);
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--gray-700);
}
/* El campo arranca con un relleno apenas gris y se ABRE a blanco al enfocarlo:
   es lo que hace que el formulario no se lea como una hoja lisa. */
.login-input {
    width: 100%;
    padding: 13px 14px;
    font-size: 0.95rem;
    color: var(--gray-900);
    background: var(--gray-50);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    transition: border-color .15s, box-shadow .15s, background-color .15s;
}
.login-input::placeholder { color: var(--gray-400); }
.login-input:hover:not(:focus) { border-color: var(--gray-400); }
.login-input:focus {
    outline: none;
    background: var(--surface);
    border-color: var(--rodago-verde);
    box-shadow: 0 0 0 3px rgba(21,199,126,0.18);
}
.login-input.is-invalid { border-color: var(--danger); }

/* Los errores de validación del form siguen siendo los mismos, solo cambia
   cómo se ven. */
.login-error {
    margin-top: var(--space-2);
    color: var(--danger);
    font-size: 0.8rem;
}

.login-submit {
    width: 100%;
    padding: 13px 16px;
    margin-top: var(--space-2);
    border: none;
    border-radius: var(--radius-md);
    background: var(--rodago-verde);
    color: #fff;
    font-size: 0.95rem;
    font-weight: 700;
    cursor: pointer;
    box-shadow: 0 6px 16px -6px rgba(21,199,126,0.65);
    transition: background .15s, box-shadow .15s, transform .15s;
}
.login-submit:hover {
    background: var(--rodago-verde-dark);
    box-shadow: 0 10px 22px -8px rgba(21,199,126,0.75);
    transform: translateY(-1px);
}
.login-submit:active { transform: translateY(0); }
.login-submit:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px rgba(21,199,126,0.35);
}

.login-remember {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
}
.login-remember input {
    width: 16px; height: 16px;
    accent-color: var(--rodago-verde);
    margin: 0;
    cursor: pointer;
}
.login-remember label {
    margin: 0;
    font-size: 0.85rem;
    color: var(--gray-500);
    cursor: pointer;
}

/* ── El acceso EN EL TELÉFONO ───────────────────────────────────────────────
   En el escritorio la pantalla partida enseña la marca al lado del formulario:
   los dos se ven de una vez. Apilada tal cual, en un móvil eso son DOS
   pantallas —el panel de marca solo ya medía 100vh— y había que desplazar para
   llegar a los campos. Entrar es lo único que se hace acá, así que el
   formulario tiene que estar A LA VISTA sin un gesto previo.

   La marca no se tira: se COMPRIME a una banda superior (logotipo + lema + los
   cuatro rasgos reducidos a icono y palabra) y el formulario se queda con todo
   el alto que sobra, centrado. El corte es el de la propia pantalla partida
   (900px), no los 767.98px del panel del dispatcher: acá lo que decide es
   cuándo el grid deja de ser de dos columnas.

   `svh` (alto de viewport CON la barra del navegador a la vista) y no `vh`: en
   iOS y Chrome móvil `100vh` es el viewport EXPANDIDO, así que la última fila
   —el "recordarme"— queda tapada justo hasta que uno desplaza, que es
   exactamente lo que esta sección viene a evitar. `vh` queda antes como
   fallback para el navegador que no entienda la unidad.
   ────────────────────────────────────────────────────────────────────────── */
@media (max-width: 899.98px) {
    .login-split {
        min-height: 100vh;
        min-height: 100svh;
        grid-template-rows: auto 1fr;   /* la marca ocupa lo que pide; el resto es del formulario */
    }

    .login-brand {
        gap: var(--space-3);
        padding: max(20px, env(safe-area-inset-top)) 20px 20px;
        text-align: center;
    }
    .login-brand-logo {
        height: clamp(36px, 11vw, 52px);
        margin-inline: auto;
    }
    .login-lema { font-size: 0.86rem; line-height: 1.4; }

    /* Los cuatro rasgos pasan de tarjetas 2×2 a una fila de cuatro: se quedan
       el icono y la palabra, que es lo que se lee de un vistazo. La frase de
       cada uno es lo que costaba media pantalla. */
    .login-features { grid-template-columns: repeat(4, 1fr); gap: var(--space-2); }
    .login-feature {
        background: none;
        border: 0;
        padding: 0;
    }
    .login-feature-icon { width: 22px; height: 22px; margin-bottom: 4px; }
    .login-feature-title { margin: 0; font-size: 0.68rem; line-height: 1.2; }
    .login-feature-text { display: none; }

    /* El lockup pequeño existe para que el formulario no se lea como una caja
       anónima cuando la marca está ENFRENTE; apilado queda el mismo logotipo
       dos veces a 200px de distancia. */
    .login-lockup { display: none; }

    .login-access {
        padding: var(--space-5) 20px calc(var(--space-5) + env(safe-area-inset-bottom));
    }
    .login-title { font-size: 1.4rem; }
    .login-subtitle { margin-bottom: var(--space-5); font-size: 0.88rem; }

    /* Mismo criterio que `.form-touch`: 16px para que iOS no haga zoom al
       enfocar y 44px de objetivo táctil. */
    .login-input, .login-submit { font-size: 16px; min-height: 44px; }
}

/* Teléfono en horizontal (o pantalla muy corta): ni comprimida entra la banda
   de marca entera. Lo que se va es lo prescindible —el lema y los rasgos—; el
   logotipo se queda, porque sin él la pantalla no dice de qué app es. */
@media (max-width: 899.98px) and (max-height: 620px) {
    .login-lema, .login-features { display: none; }
    .login-brand { padding-bottom: 14px; }
}

/* ==========================================================================
   EL DISPATCHER EN EL TELÉFONO
   ==========================================================================
   El dispatcher trabaja de pie: la cola de pedidos, el calendario (mes / día /
   semana / rango), las jornadas y el mapa se miran desde el móvil tanto como
   desde el escritorio. Esta sección es el único sitio donde vive ese ajuste, y
   va AL FINAL del archivo a propósito: son overrides de bloques que ya existen
   más arriba y tienen que ganar por ORDEN, no por especificidad inflada.

   El corte es `767.98px` (y no los `575.98px` del formulario de pedido): lo que
   se rompe acá no son columnas de Bootstrap sino barras de acciones, filas de
   filtros y tablas densas, y eso ya no entra en una tableta en vertical.
   Ninguna regla de acá llega al escritorio.
   ========================================================================== */

/* ── 1. Barra de acciones de una pantalla ──────────────────────────────────
   `.disp-toolbar` es título + acciones. En el teléfono se apilan, y las
   acciones ENVUELVEN en tantos renglones como haga falta, repartiéndose el
   ancho del bloque.
   Antes era una fila que scrollaba en horizontal y sangraba hasta el borde. Se
   cambió porque una barra de ACCIONES no es una barra de filtros: los pills de
   estado se recorren de vez en cuando y el que importa suele estar al
   principio, pero acá el botón que más se usa —"Nuevo Pedido"— es el ÚLTIMO, y
   quedaba fuera de cuadro hasta deslizar la fila entera. En una pantalla que se
   mira de pie, un botón que hay que buscar es un botón que no está.
   `flex: 1 1 auto` reparte lo que sobra de cada renglón, así que la fila
   termina a ras del bloque en vez de dejar un hueco a la derecha, y
   `white-space: normal` deja que una etiqueta larga parta en dos líneas antes
   que desbordar. */
.disp-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
@media (max-width: 767.98px) {
    .disp-toolbar {
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-2);
    }
    .disp-toolbar .dash-title { font-size: 1.15rem; }
    .disp-actions { flex-wrap: wrap; }
    /* Un `<form>` (eliminar, generar…) es un botón más de la fila: sin esto se
       queda en su ancho de contenido y rompe el reparto del renglón. */
    .disp-actions > .btn,
    .disp-actions > form { flex: 1 1 auto; min-width: 0; }
    .disp-actions > form { display: flex; }
    .disp-actions > form > .btn { flex: 1; }
    .disp-actions > .btn,
    .disp-actions > form > .btn {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 5px;
        white-space: normal;
        text-align: center;
        padding-left: 10px;
        padding-right: 10px;
    }
}

/* ── 2. Filas de pills (estado, día de servicio, rangos rápidos) ───────────
   Ocho estados + siete días de servicio son quince pills: envueltos ocupaban
   cinco renglones y empujaban la tabla fuera de la primera pantalla. Misma
   respuesta que las acciones: una fila que scrollea y sangra hasta el borde.
   Va acotado a `.app-content` del admin — el portal de empresa y el rider
   tienen sus propias superficies y ninguna usa `.nav-pills` así. */
@media (max-width: 767.98px) {
    body.role-admin .app-content .nav-pills {
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        margin-left: calc(var(--content-pad) * -1);
        margin-right: calc(var(--content-pad) * -1);
        padding: 0 var(--content-pad);
    }
    body.role-admin .app-content .nav-pills::-webkit-scrollbar { display: none; }
    body.role-admin .app-content .nav-pills > .nav-item { flex: 0 0 auto; }
    body.role-admin .app-content .nav-pills .nav-link {
        white-space: nowrap;
        padding: 7px 12px;
    }
}

/* ── 3. Los FILTROS se pliegan ─────────────────────────────────────────────
   Diez campos en columna son ~600px de formulario ANTES de la primera fila de
   la tabla: en el teléfono, la pantalla que sirve para mirar pedidos no mostraba
   ninguno. Se pliegan en un `<details>` cuyo resumen dice CUÁNTOS filtros hay
   puestos: un filtro activo que no se ve es peor que uno que estorba, porque
   explica una lista vacía sin decirlo.
   En el escritorio el `<details>` no se nota: el resumen no se dibuja y el
   formulario se ve entero como siempre. Nace con `open`, así que sin JS el
   comportamiento del teléfono es también el de siempre. */
.disp-filterbox { margin-bottom: var(--space-4); }
.disp-filterbox > summary { display: none; }
.disp-filterbox > .disp-filters { margin-bottom: 0; }
@media (max-width: 767.98px) {
    .disp-filterbox > summary {
        display: flex;
        align-items: center;
        gap: 8px;
        list-style: none;
        cursor: pointer;
        padding: 10px 12px;
        border: 1px solid var(--gray-200);
        border-radius: var(--radius-md);
        background: var(--surface);
        font-size: 0.88rem;
        font-weight: 600;
        color: var(--gray-700);
        min-height: 44px;
    }
    .disp-filterbox > summary::-webkit-details-marker { display: none; }
    .disp-filterbox > summary::after {
        content: "\F282";                       /* bi-chevron-down */
        font-family: "bootstrap-icons";
        margin-left: auto;
        font-size: 0.8rem;
        color: var(--gray-400);
        transition: transform 0.15s;
    }
    .disp-filterbox[open] > summary::after { transform: rotate(180deg); }
    .disp-filterbox[open] > summary {
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
    }
    .disp-filter-count {
        background: var(--primary);
        color: #fff;
        border-radius: 999px;
        font-size: 0.7rem;
        font-weight: 700;
        padding: 1px 8px;
    }
    /* El formulario abierto: dos columnas, que es lo que hace que "Desde /
       Hasta" y "Precio min / max" se lean como el par que son. */
    .disp-filterbox[open] > .disp-filters {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: var(--space-2);
        padding: var(--space-3);
        border: 1px solid var(--gray-200);
        border-top: 0;
        border-radius: 0 0 var(--radius-md) var(--radius-md);
    }
    /* El buscador y la fila de botones ocupan el ancho entero. */
    .disp-filterbox > .disp-filters > .disp-filter-wide,
    .disp-filterbox > .disp-filters > .d-flex { grid-column: 1 / -1; }
    .disp-filterbox > .disp-filters > .d-flex > .btn { flex: 1 1 0; min-height: 44px; }
    /* 16px es el mínimo que evita el zoom automático de iOS al enfocar, y 44px
       el objetivo de toque. El `style="width:92px"` en línea de los precios se
       neutraliza acá: en una celda de grilla, un ancho fijo deja media columna
       vacía. */
    .disp-filters .form-control,
    .disp-filters .form-select {
        font-size: 16px;
        min-height: 44px;
        width: 100%;
        max-width: none;
    }
}

/* ── 4. Tablas densas: TARJETAS en el teléfono ─────────────────────────────
   Una tabla de ocho columnas en 360px no se arregla comprimiéndola: o se parte
   el texto letra a letra o desborda la página. Con `.is-cards` cada fila pasa a
   ser una TARJETA y cada celda una línea con su etiqueta, que sale del atributo
   `data-label` de la propia celda — la cabecera desaparece, así que sin la
   etiqueta un "Sin asignar" suelto no dice de qué columna es.
   La `<tr>` conserva su `data-href`: el click de fila sigue funcionando igual.
   Las tablas SIN `.is-cards` (una matriz de números como el resumen por rango)
   se quedan como tabla y scrollean: ahí las columnas SON la información. */
@media (max-width: 767.98px) {
    .disp-table-wrap {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior-x: contain;
    }
    /* `max-content` en vez de un ancho a ojo: cada columna se queda con lo que
       necesita y la tabla scrollea lo que haga falta, ni un píxel más. */
    .disp-table:not(.is-cards) { min-width: max-content; }

    .disp-table.is-cards,
    .disp-table.is-cards tbody,
    .disp-table.is-cards tfoot,
    .disp-table.is-cards tr,
    .disp-table.is-cards td { display: block; }
    .disp-table.is-cards thead { display: none; }
    .disp-table.is-cards tbody tr {
        padding: 10px 12px;
        border-bottom: 1px solid var(--gray-200);
    }
    .disp-table.is-cards td {
        display: flex;
        align-items: baseline;
        gap: 8px;
        padding: 3px 0;
        border: 0;
    }
    .disp-table.is-cards td[data-label]::before {
        content: attr(data-label);
        flex: 0 0 5.25rem;
        font-size: 0.64rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: var(--gray-400);
    }
    /* El identificador es el TÍTULO de la tarjeta, no un campo más: va solo,
       arriba y sin etiqueta (para eso lleva el #). */
    .disp-table.is-cards td:first-child {
        display: block;
        font-size: 0.95rem;
        margin-bottom: 2px;
    }
    /* La cabecera de grupo (prioridad operativa o día) sigue siendo una franja
       de ancho completo: es lo que ordena la lista. */
    .disp-table.is-cards tr.disp-group-row { padding: 0; }
    .disp-table.is-cards tr.disp-group-row td {
        display: block;
        padding: 9px 12px;
        font-size: 0.72rem;
    }
    .disp-table.is-cards tr.disp-group-row td::before { content: none; }
    .disp-group-link { float: none; display: block; margin-top: 3px; }
    /* Las direcciones dejan de estar acotadas a 320px y de recortarse con
       puntos suspensivos: en una tarjeta hay ancho para la línea entera, y la
       dirección es lo único que se lee de verdad en movimiento. */
    .disp-table.is-cards .disp-route { min-width: 0; }
    .disp-table.is-cards .disp-route .leg {
        max-width: none;
        white-space: normal;
        align-items: flex-start;
        overflow: visible;
    }
    .disp-table.is-cards .disp-route .leg i { padding-top: 3px; }
    /* Asignar un rider desde la fila: el select y el botón a lo ancho. */
    .disp-table.is-cards .disp-assign { flex: 1; min-width: 0; }
    .disp-table.is-cards .disp-assign select { flex: 1; min-width: 0; min-height: 38px; }
    .disp-table.is-cards .disp-assign button { min-height: 38px; }
}

/* ── 5. Calendario del MES ─────────────────────────────────────────────────
   Siete columnas no se negocian: es un mes. Lo que se achica es todo lo demás
   —el aire de la celda y los chips— y el `+` de "nuevo pedido este día" se va a
   la esquina de la celda en vez de ocupar un renglón propio, que en seis
   semanas eran seis renglones de nada. El COLOR de la celda es lo que se lee de
   un vistazo, y es lo único que no se toca. */
@media (max-width: 767.98px) {
    .cal-cell { padding: 4px 3px; min-height: 58px; position: relative; }
    .cal-day-num { font-size: 0.78rem; margin-bottom: 3px; }
    .cal-chips { gap: 2px; }
    .cal-chip { font-size: 0.62rem; padding: 2px 5px; gap: 2px; }
    /* El `+` va arriba a la derecha, sobre el aire que deja el número del día.
       El objetivo de toque se agranda con padding, no con tamaño de fuente. */
    .cal-cell > .mt-1 { position: absolute; top: 0; right: 0; margin: 0 !important; }
    .cal-cell > .mt-1 .cal-add { display: block; padding: 5px 6px; font-size: 0.8rem; }
    .cal-table thead th { font-size: 0.68rem; padding: 4px 2px !important; }
}

/* ── 6. Calendario de la SEMANA: la columna del rider se queda ──────────────
   La tabla de reparto scrollea en horizontal por diseño (7 días + la columna de
   riders no entran en ninguna pantalla chica). Sin fijar la primera columna, al
   llegar al jueves ya no se sabe de quién es la fila que se está mirando, que es
   la única pregunta que contesta esta vista. Vale en todos los anchos: donde la
   tabla entera entra, `sticky` no hace nada. */
.cal-week th.cal-week-rider-col {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--surface);
}
.cal-week thead th.cal-week-rider-col { z-index: 3; }

/* ── 7. Cabecera de sección del calendario ────────────────────────────────
   `.cal-section-head` lleva título + contador + un botón empujado con `ms-auto`.
   Sin dejarla envolver, el botón se salía de la pantalla. */
.cal-section-head { flex-wrap: wrap; }
@media (max-width: 767.98px) {
    .cal-section-head > .btn,
    .cal-section-head > form { margin-left: 0 !important; }
    .cal-section-head > form { width: 100%; }
    .cal-section-head > form > .btn { width: 100%; }
}

/* ── 8. El MAPA del dispatcher ─────────────────────────────────────────────
   El cuerpo ya se apila bajo 900px (mapa arriba, panel abajo). Lo que faltaba es
   la barra de arriba, que con el título, la navegación por día, el alcance, el
   contador y la ciudad se comía cuatro renglones antes del mapa. */
@media (max-width: 767.98px) {
    .map-screen-bar { gap: 6px; }
    .map-screen-bar > h1 { flex: 1 1 auto; font-size: 1rem; }
    /* La ciudad deja de estar empujada al otro extremo: en una barra que ya
       envuelve, `ms-auto` solo abre un hueco impredecible. */
    .map-city-switch.ms-auto { margin-left: 0 !important; }
    .map-daynav { flex: 1 1 100%; }
    .map-daynav-label { flex: 1; }
    .map-span-switch, .map-city-switch { flex: 1 1 auto; display: flex; }
    .map-span-switch .btn, .map-city-switch .btn { flex: 1; }
    .map-screen-canvas { height: 58vh; min-height: 280px; }
    /* Los filtros del panel van `sticky` porque la lista de riders es larga y
       el panel scrollea POR DENTRO. Apilado, el que scrollea es el documento y
       su altura pasa a `auto`: el sticky se resuelve entonces contra el
       viewport, y la barra de filtros se despegaba para quedar flotando encima
       del mapa y de las paradas. En una sola columna no hay contexto que
       perder al bajar — la lista viene justo debajo. */
    .map-filters, .map-routeview { position: static; }
    /* La tarjeta de la parada y la barra de "crear ruta" están posicionadas
       contra `.map-screen-body`, que apilado mide el mapa MÁS el panel: la
       tarjeta aparecía al final de todo, a varias pantallas del pin que se
       acababa de tocar. Fijas al viewport quedan donde se las mira. */
    .map-stopcard {
        position: fixed;
        left: var(--space-2);
        right: var(--space-2);
        bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
        width: auto;
        max-height: 58vh;
    }
    .map-select-bar {
        left: var(--space-2);
        right: var(--space-2);
        bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
        transform: none;
        max-width: none;
    }
    .map-select-bar-actions { margin-left: 0; width: 100%; }
    .map-select-bar-actions .btn { flex: 1; min-height: 40px; }
    /* Los modales del mapa (traspaso, crear ruta, optimizar, fecha de la ruta)
       no son de Bootstrap: son `.mess-handoff-modal` a mano. El ancho ya se
       adapta (100% con tope de 380px), pero sus controles van todos en `-sm`, o
       sea por debajo de los 16px que evitan el zoom automático de iOS — y son
       formularios que el dispatcher completa DE PIE. */
    .mess-handoff-modal .form-control,
    .mess-handoff-modal .form-select { font-size: 16px; min-height: 44px; }
    .mess-handoff-actions { flex-wrap: wrap; }
    .mess-handoff-actions .btn { flex: 1 1 auto; min-height: 44px; }
}

/* ── 9. Formularios de filtro sueltos (jornadas, sesiones, rangos) ─────────
   Las pantallas de jornadas no usan `.disp-filters`: llevan un `<form>` con dos
   fechas y un botón, con anchos escritos en línea (`style="width:140px"`). En el
   teléfono eso son tres controles peleando por 336px. `.disp-inline-filters` los
   apila a ancho completo y neutraliza el ancho en línea. */
.disp-inline-filters { display: flex; gap: var(--space-2); align-items: flex-end; flex-wrap: wrap; }
@media (max-width: 767.98px) {
    .disp-inline-filters { width: 100%; }
    .disp-inline-filters > * { flex: 1 1 auto; }
    .disp-inline-filters .form-control,
    .disp-inline-filters .form-select {
        width: 100% !important;         /* gana al style="width:140px" en línea */
        font-size: 16px;
        min-height: 44px;
    }
    .disp-inline-filters .btn { min-height: 44px; }
}

/* ── 10. La cabecera `row / col / col-auto` de TODO el panel ───────────────
   Dieciocho pantallas del admin (riders, empresas, rutas, precios, plantillas,
   historial…) empiezan igual: un `.row` con el título en un `.col` y los
   botones en un `.col-auto`. Ninguna de las dos clases tiene breakpoint —no son
   `.col-md-*`—, así que en el teléfono se quedan en la MISMA línea: `.col`
   puede encogerse hasta cero, y el título terminaba partido letra a letra
   contra un bloque de botones que igual se salía por la derecha.
   Se arregla acá y no plantilla por plantilla porque el patrón es el mismo en
   las dieciocho: darle a cada `.col-auto` la línea entera. En una fila de
   filtros (riders, historial de rutas, pedidos de un rider) el efecto es el
   mismo que se querría de todos modos — un campo por línea, a ancho completo. */
@media (max-width: 767.98px) {
    body.role-admin .app-content .row > .col-auto { flex: 1 0 100%; }
    body.role-admin .app-content .row > .col-auto > .form-control,
    body.role-admin .app-content .row > .col-auto > .form-select {
        width: 100%;
        font-size: 16px;      /* por debajo, iOS hace zoom al enfocar */
        min-height: 44px;
    }
    /* Un `.col-auto` de botones: a lo ancho y con altura de dedo. `.btn-link`
       queda fuera (es un enlace en línea, no un botón). */
    body.role-admin .app-content .row > .col-auto > .btn:not(.btn-link) {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        gap: 5px;
    }
}

/* ── 11. Un solo ancho: los BLOQUES sangran hasta donde llega la barra ─────
   La fila de acciones (§1) y las de pills (§2) sangran hasta el borde de la
   pantalla, y todo lo de abajo —la tabla, el calendario, los filtros— quedaba
   metido `--content-pad` hacia adentro: la línea de "Nuevo Pedido" se leía como
   si perteneciera a otra pantalla. La respuesta NO es recortar esa fila (el
   ancho que gana es justo el que le falta a una tabla de ocho columnas o a un
   mes de siete): es que el resto llegue HASTA AHÍ.
   Sangra el BLOQUE, no su contenido: el marco se va al borde y el texto sigue
   separado por el padding interno que la tarjeta ya tenía (10-16px), así que
   nada queda pegado al cristal. Un marco cortado contra el borde no puede
   conservar ni el borde lateral ni el radio: sin quitarlos se lee como una
   tarjeta a la que le falta un trozo.
   El TÍTULO y el texto suelto se quedan donde están: nunca fueron el problema,
   y son lo único que no puede tocar el borde.
   Qué sangra: `.disp-card` y `.disp-filterbox` (son SIEMPRE bloques de ancho
   completo, así que no hace falta marcarlos) y lo que se marque a mano con
   `.disp-bleed` — que es lo que hay que ponerle a una `.card` o a un
   `.table-responsive` de ancho completo, porque `.card` también se usa dentro
   de una columna de KPIs y ahí sangrar sería romperla. */
@media (max-width: 767.98px) {
    body.role-admin .app-content .disp-card,
    body.role-admin .app-content .disp-filterbox,
    body.role-admin .app-content .disp-bleed,
    body.role-admin .app-content .route-group,
    body.role-admin .app-content .cp-card,
    body.role-admin .app-content .ops-bar,
    body.role-admin .app-content .dispatch-map-wrap,
    body.role-admin .app-content .dispatch-queue {
        margin-left: calc(var(--content-pad) * -1);
        margin-right: calc(var(--content-pad) * -1);
        border-left-width: 0;
        border-right-width: 0;
        border-radius: 0;
    }
    /* La franja de color del rider ES la identidad de la ruta en la lista del
       día: se queda aunque el marco se vaya al borde. */
    body.role-admin .app-content .route-group { border-left-width: 4px; }
    /* `.disp-filterbox` no tiene marco propio: lo tienen su resumen y el
       formulario abierto, y son ellos los que hay que abrir. */
    body.role-admin .app-content .disp-filterbox > summary,
    body.role-admin .app-content .disp-filterbox[open] > .disp-filters {
        border-left-width: 0;
        border-right-width: 0;
        border-radius: 0;
    }
}

/* ── 12. La TORRE DE CONTROL (dashboard) ───────────────────────────────────
   Es la primera pantalla del dispatcher y la que peor entraba: la barra de
   estado se apilaba en cuatro celdas de 90px de alto (con la cifra a 2rem) y
   empujaba el mapa y la cola fuera de la primera pantalla, y la fila de asignar
   un rider —select + botón— no cabía junto a la dirección.
   El criterio es el mismo de toda la tanda: nada se esconde, se reordena. */
@media (max-width: 767.98px) {
    /* El encabezado se apila y su acción principal ocupa la línea entera. Lo
       comparte el detalle de pedido, que usa la misma cabecera. */
    .dash-header {
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-2);
        margin-bottom: var(--space-4);
    }
    .dash-header .dash-title { font-size: 1.15rem; }
    .dash-header > .btn {
        width: 100%;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 6px;
    }

    /* Las cuatro cifras de la barra de estado son un VISTAZO: en una columna
       dejan de serlo, porque hay que desplazarse para ver la cuarta. Dos y dos,
       con la celda más compacta y la cifra un punto más chica —sigue siendo lo
       más grande de la pantalla. `auto-fit` no sirve acá: con 180px de mínimo
       decide una sola columna justo en el ancho en el que caben dos. */
    .ops-bar { grid-template-columns: 1fr 1fr; margin-bottom: var(--space-4); }
    .ops-cell { padding: 12px 14px; }
    .ops-num { font-size: 1.6rem; }
    .ops-label { font-size: 0.66rem; }
    .ops-sub { font-size: 0.68rem; }

    /* El mapa del dashboard es un RESUMEN (el operativo es `/admin/map`): 400px
       en vertical son dos tercios de la pantalla antes de llegar a la cola, que
       es lo accionable. */
    .dispatch-map-wrap { height: 260px; }

    /* Asignar un rider desde la cola: el select y el botón no entran en la
       misma línea que la dirección y la antigüedad. Pasan a su propio renglón,
       a lo ancho de la fila y con altura de dedo. */
    .queue-row { flex-wrap: wrap; }
    .queue-assign { flex: 1 1 100%; }
    .queue-assign select { flex: 1; min-width: 0; max-width: none; min-height: 40px; }
    .queue-assign button { min-height: 40px; padding: 5px 14px; }

    /* Las dos tarjetas de analítica: el título y los cuatro rangos no comparten
       línea en 360px. El grupo de rangos baja entero y se reparte el ancho —son
       cuatro botones de tres caracteres, así que en una fila entran de sobra. */
    .analytics-grid .card-header { flex-wrap: wrap; gap: var(--space-2); }
    .analytics-grid .card-header .btn-group { flex: 1 1 100%; }
    .analytics-grid .card-header .btn-group .btn { flex: 1; min-height: 38px; }
}

/* ── 13. El DETALLE de un pedido ───────────────────────────────────────────
   La pantalla ya se apilaba sola (`.col-lg-8` / `.col-lg-4`), así que lo que
   quedaba era el stepper y las dos filas de acciones. */
@media (max-width: 767.98px) {
    /* Seis nodos × 56px son 336px: justo el ancho útil de un teléfono de 360,
       o sea que el stepper "entraba" al milímetro y cualquier etiqueta larga lo
       desbordaba. Con 44px sobra aire y las etiquetas parten en dos líneas
       —que es lo que ya hacían— sin empujar nada. */
    .stepper-node { min-width: 44px; }
    .stepper-label { font-size: 0.62rem; }
    /* El desglose de precio y la tabla de información son de dos columnas
       (concepto / importe): sin esto el importe se parte en dos líneas. */
    .price-breakdown td:last-child { padding-left: 8px; }
}
/* Los formularios de la barra lateral (asignar rider, recalcular precio, subir
   una imagen) van todos en `-sm`, o sea por debajo de los 16px con los que iOS
   hace zoom al enfocar. No se cubren con una regla nueva: heredan `.form-touch`
   del alta poniéndose la clase, que es exactamente lo que esa sección dejó
   dicho. Por eso la marca está en el `<form>` y acá no hay nada. */
