/* ============================================================
   Enex EMS — Sistema de diseño
   Alineado con enex-web (https://github.com/enexenergy/enex-web)
   Tema: Dark teal (paleta del login) por defecto en todas las páginas.
   Estructura:
     1. Tokens (paleta, tipografías, escalas)
     2. Fuentes self-hosted
     3. Base (reset, body, headings, links) — dark
     4. Layout (header, main, .wrap, secciones)
     5. Componentes (cards, botones, modal, forms, badges, alerts, spinner)
     6. Login (variante dark — layout centrado)
   ============================================================ */


/* ─────────── 1. TOKENS ─────────── */
:root {
    /* El panel es dark siempre. Declararlo aquí no es cosmético: lo que pinta
       el navegador —y no el CSS— hereda de aquí. El desplegable abierto de un
       <select>, el calendario de un <input type="date">, las barras de scroll y
       el autofill salían en claro sobre texto claro, ilegibles, por más color
       que llevara el control cerrado. */
    color-scheme: dark;

    /* ── Paleta de marca ── */
    --teal:        #2EB6BC;
    --teal-deep:   #157578;
    --teal-soft:   #D7F0F1;  /* casi blanco-teal, usado sobre fondos teal oscuros */
    --ink:         #0E1A1B;  /* fondo base del tema dark */
    --ink-2:       #34474A;  /* gris-azulado oscuro (botones secundarios, gradientes) */
    --white:       #ffffff;
    --warn:        #C44A2B;
    --success:     #2E8B57;
    --danger:      #C44A2B;

    /* ── Tokens UI activos (tema dark) ──
       La escala salió de contar lo que el panel usaba de verdad (2026-09-11):
       había 13 fondos translúcidos distintos entre .02 y .10, y 11 bordes entre
       .04 y .22, escritos a mano en 468 sitios. No eran decisiones: nadie
       elige .025 y .03 para cosas distintas. Se colapsaron a estos cuatro
       niveles y tres bordes, moviendo como mucho .02 de alpha — imperceptible
       sobre fondo oscuro— y ahora re-teñir el panel entero se hace aquí. */
    --surface-0:       rgba(255,255,255,.02);   /* apenas un tono: filas alternas, zebra */
    --surface-1:       rgba(255,255,255,.04);   /* superficie translúcida base (tarjeta) */
    --surface-2:       rgba(255,255,255,.06);   /* superficie elevada / hover */
    --surface-3:       rgba(255,255,255,.10);   /* activa, seleccionada, chip encendido */
    --surface-strong:  rgba(20, 30, 32, 0.96);  /* superficie sólida (modal) */
    --border-subtle:   rgba(255,255,255,.06);   /* separador interno, apenas visible */
    --border-soft:     rgba(255,255,255,.10);   /* borde de tarjeta, el de rigor */
    --border-strong:   rgba(255,255,255,.18);   /* borde marcado: foco, activo */
    --ink-soft:        rgba(255,255,255,.55);   /* texto secundario (labels, captions) */
    --ink-mid:         rgba(255,255,255,.7);    /* texto destacado */
    --ink-strong:      rgba(255,255,255,.9);    /* texto primario sobre superficie */
    --card-shadow:     0 8px 24px rgba(0,0,0,.25);

    /* ── Tipografías ── */
    --serif: "Fraunces", Georgia, "Times New Roman", serif;
    --sans:  "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    --mono:  "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Monaco, "Courier New", monospace;

    /* ── Escala de elevación ── */
    --shadow-sm: 0 1px 2px rgba(14,26,27,.04), 0 1px 1px rgba(14,26,27,.03);
    --shadow-md: 0 4px 12px rgba(14,26,27,.06), 0 2px 4px rgba(14,26,27,.04);
    --shadow-lg: 0 12px 32px rgba(14,26,27,.10), 0 4px 8px rgba(14,26,27,.05);

    /* ── Radios ──
       Mismo criterio que las superficies: había diez radios sueltos (1, 2, 3,
       4, 5, 6, 8, 10, 12, 18, 999). Los de 1 y 2px se quedan literales donde
       están —una barra de progreso de 4px de alto no tiene «estilo de
       esquina»—; el resto entra en esta escala. */
    --radius-xs: 4px;     /* badge, chip, celda */
    --radius-sm: 8px;
    --radius-md: 12px;
    --radius-lg: 18px;
    --radius-pill: 999px;

    /* ── Alias semánticos (compatibilidad con código existente) ── */
    --bg-color:        var(--ink);
    --card-bg:         var(--surface-1);
    --text-primary:    var(--white);
    --text-secondary:  var(--ink-mid);
    --accent-color:    var(--teal);
    --accent-hover:    var(--teal-soft);
    --font-family:     var(--sans);

    /* ── Legacy / reservados ──
       Tokens del esquema light original. No se usan activamente en el
       tema dark, pero quedan definidos por si: (a) se reactiva un tema
       claro, (b) algún template aún los referencia indirectamente,
       (c) una página los redefine localmente (p.ej. brainstorm, roadmap). */
    --paper:     #FAFAF7;
    --paper-2:   #F4F4EE;
    --line:      #E4ECEC;
    --muted:     #4A5B5D;
    --teal-tint: #F0F9F9;
}


/* ─────────── 2. FUENTES SELF-HOSTED ─────────── */
@font-face { font-family: "Inter";          font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/inter-v20-latin-regular.woff2") format("woff2"); }
@font-face { font-family: "Inter";          font-style: normal; font-weight: 500; font-display: swap; src: url("../fonts/inter-v20-latin-500.woff2")     format("woff2"); }
@font-face { font-family: "Inter";          font-style: normal; font-weight: 600; font-display: swap; src: url("../fonts/inter-v20-latin-600.woff2")     format("woff2"); }
@font-face { font-family: "Inter";          font-style: normal; font-weight: 700; font-display: swap; src: url("../fonts/inter-v20-latin-700.woff2")     format("woff2"); }
@font-face { font-family: "Fraunces";       font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/fraunces-v38-latin-regular.woff2") format("woff2"); }
@font-face { font-family: "Fraunces";       font-style: normal; font-weight: 500; font-display: swap; src: url("../fonts/fraunces-v38-latin-500.woff2")     format("woff2"); }
@font-face { font-family: "Fraunces";       font-style: normal; font-weight: 600; font-display: swap; src: url("../fonts/fraunces-v38-latin-600.woff2")     format("woff2"); }
@font-face { font-family: "Fraunces";       font-style: normal; font-weight: 700; font-display: swap; src: url("../fonts/fraunces-v38-latin-700.woff2")     format("woff2"); }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/jetbrains-mono-v24-latin-regular.woff2") format("woff2"); }
@font-face { font-family: "JetBrains Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url("../fonts/jetbrains-mono-v24-latin-500.woff2")     format("woff2"); }


/* ─────────── 3. BASE ─────────── */
* { box-sizing: border-box; }
/* El atributo hidden pierde contra toda regla con display (.btn-action es
   inline-flex, .conta-barra es flex): sin esto, `el.hidden = true` desde el JS
   no esconde nada. Mismo reset que trae cualquier navegador para [hidden]. */
[hidden] { display: none !important; }

html, body { margin: 0; padding: 0; }

body {
    background:
        radial-gradient(1200px 600px at 80% -10%, rgba(46,182,188,.18), transparent 60%),
        radial-gradient(900px 500px at 0% 110%, rgba(46,182,188,.10), transparent 50%),
        var(--ink);
    background-attachment: fixed;
    color: var(--white);
    font-family: var(--sans);
    font-size: 16px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
}

a { color: inherit; text-decoration: none; }
a:hover { color: var(--teal); }

button { font-family: inherit; cursor: pointer; }

:focus { outline: 0; }
:focus-visible {
    outline: 2px solid var(--teal-deep);
    outline-offset: 2px;
    border-radius: 4px;
}
input:focus-visible, textarea:focus-visible, select:focus-visible {
    outline-offset: 0;
}

/* Saltar al contenido: el primer tabulador de cualquier página. Sin esto, con
   teclado hay que recorrer los ~40 enlaces del sidebar ANTES de llegar a la
   tabla, y en cada página. No se oculta con `display:none` ni con
   `visibility:hidden` a propósito: eso lo sacaría del orden de tabulación y lo
   dejaría inservible, que es el error clásico de este patrón. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 200;
    padding: 10px 16px;
    background: var(--teal-deep);
    color: #fff;
    font-weight: 600;
    border-radius: 0 0 6px 0;
}
.skip-link:focus {
    left: 0;
}

::selection { background: var(--teal); color: var(--ink); }

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


/* ─────────── 4. LAYOUT ─────────── */

/* Ancho del contenido, por tipo de página. Una página elige el suyo con una
   clase en su `main` (`.w-data`, `.w-form`, `.w-doc`); sin clase manda
   `--w-page`. Sustituye a los ~18 wrappers propios que cada página se
   inventaba, y a los `max-width` de 1280/1200 que nunca llegaban a aplicarse
   por ir dentro de este `main`. */
:root {
    --w-data: 1600px;   /* tablas, kanban y análisis: piden aire */
    --w-page: 1240px;   /* por defecto */
    --w-form: 980px;    /* formularios: una columna cómoda de leer */
    --w-doc: 78ch;      /* lectura y documentos imprimibles */
    --w: var(--w-page);
}

header {
    width: 100%;
    max-width: var(--w);
    padding: 40px 32px 24px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 24px;
}

/* Con la barra de búsqueda delante, los 40px de arriba son aire que ya abrió
   ella: sumados, el título de cada página caía un tercio de pantalla más abajo. */
.tb + header { padding-top: 22px; }

.header-container {
    display: flex;
    align-items: center;
    gap: 20px;
    min-width: 0;
}

h1 {
    font-family: var(--serif);
    font-size: clamp(28px, 3vw, 36px);
    font-weight: 400;
    letter-spacing: -0.015em;
    line-height: 1.1;
    margin: 0;
    color: var(--white);
}

h1 em { color: var(--teal); font-style: italic; }

.subtitle {
    font-family: var(--mono);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--teal);
    font-weight: 500;
    margin-top: 6px;
}

/* El ritmo vertical del contenido es UNO y vive aquí. Hasta el 2026-09-11
   dependía de si el template escribía `<main>` o `<main class="stack">`: 15
   páginas separaban sus bloques 8px y 12 los separaban 24px, así que el panel
   respiraba distinto según la pestaña. No era una decisión de diseño, era el
   rastro de qué se copió al crear cada página. Gana 24px, que es lo que ya
   usaban las de más peso visual (Facturación, Coste, Márgenes, Consumo).
   Una página que necesite ir apretada lo DECLARA con `.stack-tight`. */
main {
    width: 100%;
    max-width: var(--w);
    padding: 8px 32px 80px;
    display: flex;
    flex-direction: column;
    gap: 24px;
}

/* Ritmo apretado, para cuando los bloques son piezas de una misma unidad (una
   barra de filtros pegada a su tabla) y el aire de 24px los desune. */
main.stack-tight { gap: 8px; }

/* El tipo se declara en el `main`, pero el `header` de arriba también tiene que
   estrecharse con él: por eso la clase sube al `.app-main` que los envuelve. */
.w-data { --w: var(--w-data); }
.w-form { --w: var(--w-form); }
.w-doc  { --w: var(--w-doc); }

.wrap {
    width: 100%;
    max-width: var(--w);
    margin: 0 auto;
    padding: 0 32px;
}

.section-title {
    font-family: var(--serif);
    font-size: 26px;
    font-weight: 400;
    letter-spacing: -0.01em;
    color: var(--white);
    margin: 28px 0 16px 0;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--border-soft);
}

.eyebrow {
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--teal);
    font-weight: 500;
    display: inline-flex;
    align-items: center;
    gap: 10px;
}
.eyebrow::before {
    content: "";
    width: 20px;
    height: 1px;
    background: var(--teal);
}


/* ─────────── 5. COMPONENTES ─────────── */

/* ── Utilidades de superficie dark (glass / flat) ──
   Aplicadas vía clase HTML o vía selector múltiple desde un componente.
   - .glass: superficie translúcida con backdrop blur. Usar en elementos
     grandes y prominentes (cards, modals, header sticky).
   - .surface-soft: superficie translúcida SIN blur. Más barata en GPU.
     Usar en elementos pequeños / repetidos en listas o grids
     (chips, tiles, info-items, action buttons). */
.glass,
.card,
.modal-content {
    background: var(--surface-1);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border: 1px solid var(--border-soft);
}
.surface-soft,
.alert,
.info-item,
.btn-action,
.table-wrap,
.chart-container,
.btn-ghost {
    background: var(--surface-1);
    border: 1px solid var(--border-soft);
}

/* Cards grid */
.cards-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 20px;
    margin-bottom: 32px;
}

.card {
    /* surface aplicada arriba via .glass (multi-selector) */
    border-radius: var(--radius-lg);
    padding: 28px;
    box-shadow: var(--card-shadow);
    transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    min-height: 200px;
    position: relative;
}

.card:hover {
    transform: translateY(-2px);
    border-color: rgba(46,182,188,.45);
    box-shadow: 0 12px 32px rgba(0,0,0,.35), 0 0 0 1px rgba(46,182,188,.18);
}

.card-icon {
    font-size: 26px;
    margin-bottom: 14px;
    background: rgba(46,182,188,.14);
    color: var(--teal);
    border: 1px solid rgba(46,182,188,.25);
    width: 52px;
    height: 52px;
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
}

.card h2 {
    font-family: var(--sans);
    font-size: 20px;
    font-weight: 600;
    letter-spacing: -0.01em;
    margin: 0 0 8px 0;
    color: var(--white);
}

.card p {
    color: var(--text-secondary);
    font-size: 14px;
    line-height: 1.5;
    margin: 0 0 20px 0;
    flex-grow: 1;
}

/* Buttons */
.btn {
    background: var(--teal-deep);
    color: var(--white);
    border: 0;
    border-radius: var(--radius-pill);
    padding: 12px 22px;
    font-family: var(--sans);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: background .2s, transform .15s, box-shadow .2s;
    text-align: center;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    align-self: flex-start;
}

.btn:hover {
    background: var(--teal);
    color: var(--ink);
    transform: translateY(-1px);
    box-shadow: 0 6px 20px rgba(46,182,188,.35);
}

.btn:disabled {
    background: rgba(255,255,255,.06);
    color: rgba(255,255,255,.35);
    cursor: not-allowed;
    transform: none;
}

.btn-ghost {
    /* surface aplicada via .surface-soft (multi-selector) */
    color: var(--white);
    border-color: var(--border-strong);
}
.btn-ghost:hover {
    background: rgba(46,182,188,.18);
    border-color: var(--teal);
    color: var(--white);
}

.btn-secondary {
    background: var(--ink-2);
    color: var(--white);
}
.btn-secondary:hover {
    background: var(--teal-deep);
    color: var(--white);
}

/* Stretchy action button (fills card width, used in upload cards) */
.btn-block {
    align-self: stretch;
    width: 100%;
}

/* Vertical group of buttons inside a card */
.card-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 12px;
}

/* Roadmap card variant — text-only, no actions */
.card.card-roadmap {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border: 1px dashed var(--border-strong);
    box-shadow: none;
    justify-content: flex-start;
    gap: 12px;
}
.card.card-roadmap:hover {
    transform: none;
    box-shadow: none;
    border-color: rgba(46,182,188,.45);
}
.card.card-roadmap h2 {
    margin: 0;
    color: var(--white);
}
.roadmap-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.roadmap-list li {
    font-size: 14px;
    color: var(--text-secondary);
    padding-left: 18px;
    position: relative;
    line-height: 1.5;
}
.roadmap-list li::before {
    content: "→";
    position: absolute;
    left: 0;
    color: var(--teal);
    font-weight: 600;
}

/* Roadmap grid: tighter min-width to fit up to 4 columns at desktop */
.cards-grid.cards-grid-roadmap {
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

/* Lead paragraph under a section title (subtle helper text) */
.section-lead {
    font-size: 14px;
    color: var(--ink-soft);
    margin: -8px 0 16px 0;
    line-height: 1.5;
}

/* Drag-and-drop overlay (used on cards that accept file drops) */
.drag-overlay {
    position: absolute;
    inset: 0;
    background: rgba(46,182,188,.22);
    border: 2px dashed var(--teal);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--mono);
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--teal);
    pointer-events: none;
    border-radius: var(--radius-lg);
    backdrop-filter: blur(2px);
}

/* Forms (compartidos: login + páginas internas) */
.form-group {
    text-align: left;
}

.form-group label {
    display: block;
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--ink-soft);
    margin-bottom: 8px;
    font-weight: 500;
}

.form-group input,
.form-group textarea,
.form-group select {
    width: 100%;
    background: rgba(0,0,0,.25);
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-md);
    padding: 12px 14px;
    color: var(--white);
    font-family: var(--sans);
    font-size: 15px;
    outline: none;
    transition: border-color .15s, box-shadow .15s, background .15s;
    box-sizing: border-box;
}
.form-group input::placeholder,
.form-group textarea::placeholder {
    color: rgba(255,255,255,.35);
}

/* Las opciones de CUALQUIER select, no solo las de .filter-select: el fondo del
   control cerrado es translúcido y la lista desplegada se pinta sobre el fondo
   del sistema, así que sin un color OPACO aquí las opciones salen gris claro
   sobre texto claro. Va suelto a propósito, sin clase, porque el select que se
   olvide de llevarla es justo el que se rompe. */
select option,
select optgroup {
    background: var(--ink);
    color: var(--white);
}

.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
    border-color: var(--teal);
    background: rgba(0,0,0,.32);
    box-shadow: 0 0 0 3px rgba(46,182,188,.25);
}

/* Badges */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--teal);
    background: rgba(46,182,188,.14);
    border: 1px solid rgba(46,182,188,.35);
    border-radius: var(--radius-pill);
    padding: 4px 10px;
    font-weight: 500;
}
.badge.badge-warn   { color: #FF9472;        background: rgba(196,74,43,.16); border-color: rgba(196,74,43,.40); }
.badge.badge-ok     { color: #6FD49C;        background: rgba(46,139,87,.16); border-color: rgba(46,139,87,.40); }
.badge.badge-muted  { color: var(--ink-soft); background: var(--surface-1);    border-color: var(--border-soft); }

/* Alerts */
.alert {
    /* surface aplicada via .surface-soft (multi-selector) */
    padding: 14px 18px;
    border-radius: var(--radius-md);
    font-size: 14px;
    border-left: 4px solid var(--teal);
    color: var(--ink-strong);
}
.alert.alert-warn   { border-left-color: var(--warn);    color: #FF9472;  background: rgba(196,74,43,.10); }
.alert.alert-error  { border-left-color: var(--danger);  color: #FF8A72;  background: rgba(196,74,43,.12); }
.alert.alert-ok     { border-left-color: var(--success); color: #6FD49C;  background: rgba(46,139,87,.10); }

/* Toasts — avisos efímeros. `.alert` es para lo que se queda en la página
   (un error de carga que explica por qué no hay datos); el toast es para lo
   que acaba de pasar y no tiene por qué ocupar sitio. Los pinta
   static/js/toast.js, que crea la pila solo.  */
.toast-stack {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    z-index: 1400;          /* por encima del sidebar off-canvas y su backdrop */
    pointer-events: none;
}

.toast {
    background: var(--surface-strong);
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-pill);
    padding: 9px 18px;
    font-size: 13px;
    color: var(--ink-strong);
    box-shadow: 0 8px 24px rgba(0,0,0,.4);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    opacity: 0;
    transform: translateY(6px);
    transition: opacity .2s, transform .2s;
    max-width: min(90vw, 520px);
    text-align: center;
}

.toast.is-visible { opacity: 1; transform: none; }
.toast.is-error { color: #F4B8A0; border-color: rgba(224,115,75,.40); }
.toast.is-warn  { color: #FF9472; border-color: rgba(196,74,43,.40); }
.toast.is-ok    { color: var(--teal-soft); border-color: var(--teal-deep); }

/* Modal / Output */
.modal {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(14,26,27,.55);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    z-index: 1000;
    align-items: center;
    justify-content: center;
}
.modal.is-open { display: flex; }

.modal-content {
    /* surface aplicada via .glass (multi-selector); aquí sólo overrides */
    background: var(--surface-strong);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    width: 80%;
    max-width: 800px;
    border-radius: var(--radius-lg);
    padding: 32px;
    box-shadow: 0 24px 60px rgba(0,0,0,.55);
    color: var(--white);
    display: flex;
    flex-direction: column;
    max-height: 80vh;
}

.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--border-soft);
}

.modal-header h3 {
    font-family: var(--serif);
    font-weight: 500;
    margin: 0;
    font-size: 22px;
    letter-spacing: -0.01em;
    color: var(--white);
}

.close-btn {
    background: var(--surface-2);
    border: 1px solid var(--border-soft);
    border-radius: 50%;
    width: 32px;
    height: 32px;
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-mid);
    transition: background .15s, color .15s, border-color .15s;
}
.close-btn:hover {
    background: var(--teal);
    color: var(--ink);
    border-color: var(--teal);
}

.output-container {
    background: rgba(0,0,0,.45);
    color: var(--teal);
    font-family: var(--mono);
    padding: 20px;
    border-radius: var(--radius-md);
    border: 1px solid var(--border-soft);
    overflow-y: auto;
    flex-grow: 1;
    font-size: 13px;
    line-height: 1.6;
    white-space: pre-wrap;
    min-height: 200px;
}


/* Page-level toolbar row (title + search/actions) */
.toolbar-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
    flex-wrap: wrap;
    gap: 12px;
}
.toolbar-row h2 {
    margin: 0;
    font-family: var(--serif);
    font-size: 22px;
    font-weight: 500;
    letter-spacing: -0.01em;
    color: var(--white);
}

/* Search input (inline, used in toolbars) */
.search-input {
    background: rgba(0,0,0,.25);
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-md);
    padding: 10px 14px;
    font-family: var(--sans);
    font-size: 14px;
    color: var(--white);
    width: 320px;
    max-width: 100%;
    outline: none;
    transition: border-color .15s, box-shadow .15s, background .15s;
}
.search-input::placeholder { color: rgba(255,255,255,.35); }
.search-input:focus {
    border-color: var(--teal);
    background: rgba(0,0,0,.32);
    box-shadow: 0 0 0 3px rgba(46,182,188,.25);
}

/* Tab toggle (segmented control) */
.tab-toggle {
    display: inline-flex;
    background: var(--surface-1);
    padding: 4px;
    border-radius: var(--radius-md);
    border: 1px solid var(--border-soft);
}
.tab-toggle button {
    padding: 8px 18px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--ink-soft);
    font-family: var(--sans);
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: background .15s, color .15s, box-shadow .15s;
}
.tab-toggle button.is-active {
    background: var(--teal);
    color: var(--ink);
    box-shadow: 0 2px 8px rgba(46,182,188,.30);
    font-weight: 600;
}

/* Data table (shared across billing / sips / ranking / crm) */
.app-table {
    width: 100%;
    border-collapse: collapse;
    text-align: left;
    font-size: 14px;
}
.app-table thead {
    position: sticky;
    top: 0;
    background: var(--surface-strong);
    z-index: 10;
}
.app-table thead th {
    padding: 12px 14px;
    background: rgba(255,255,255,.06);
    border-bottom: 1px solid var(--border-soft);
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--ink-soft);
    font-weight: 500;
    cursor: default;
    text-align: left;
    white-space: nowrap;
}
.app-table thead th[data-key],
.app-table thead th.sortable { cursor: pointer; }
.app-table thead th .sort-icon {
    margin-left: 6px;
    color: var(--ink-soft);
    font-size: 10px;
}
.app-table tbody tr {
    border-bottom: 1px solid var(--border-soft);
    transition: background .12s;
}
.app-table tbody tr:hover { background: rgba(46,182,188,.10); }
.app-table tbody td {
    padding: 12px 14px;
    color: var(--ink-strong);
}
.app-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.app-table .num-strong { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--white); }

/* Scrollable table wrapper */
.table-wrap {
    /* surface aplicada via .surface-soft (multi-selector) */
    overflow-x: auto;
    max-height: 600px;
    overflow-y: auto;
    border-radius: var(--radius-md);
}

/* Loading progress strip below tables */
.loading-strip {
    padding: 10px 14px;
    text-align: center;
    font-size: 12px;
    color: var(--ink-soft);
    background: rgba(255,255,255,.04);
    border-top: 1px solid var(--border-soft);
    font-family: var(--mono);
    letter-spacing: 0.05em;
}

/* Cache / status badge */
.cache-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-family: var(--mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    background: rgba(46,182,188,.16);
    color: var(--teal);
    border: 1px solid rgba(46,182,188,.30);
    padding: 3px 8px;
    border-radius: var(--radius-pill);
    font-weight: 600;
}

/* Acciones de la página, a la derecha del header. En fila: es lo que pide un
   header SaaS y lo que ya hacían a mano las páginas que lo usaban (la home
   tenía que revertir el `column` con una clase propia). Envuelve si no cabe. */
.header-meta {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 10px;
    font-size: 13px;
    color: var(--ink-soft);
}
.header-meta small {
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--ink-soft);
}

/* Skeleton */
.skeleton {
    background: linear-gradient(110deg, rgba(255,255,255,.04) 8%, rgba(46,182,188,.14) 18%, rgba(255,255,255,.04) 33%);
    background-size: 200% 100%;
    border-radius: var(--radius-sm);
    animation: shine 1.5s linear infinite;
}
.skeleton-text { height: 14px; margin: 6px 0; width: 100%; }
@keyframes shine { to { background-position-x: -200%; } }

/* La otra animación de carga: la usan los skeletons con degradado propio
   (dashboard.css y varias páginas). Vive aquí, que es lo único que carga todo
   el mundo, porque llegó a estar copiada en tres sitios a la vez. */
@keyframes shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* Inline spinner (smaller than .spinner) */
.spinner-inline {
    display: inline-block;
    width: 14px;
    height: 14px;
    border: 2px solid var(--border-soft);
    border-top-color: var(--teal);
    border-radius: 50%;
    animation: spin 1s linear infinite;
    vertical-align: middle;
    margin-left: 8px;
}

/* Chart container card */
.chart-container {
    /* surface aplicada via .surface-soft (multi-selector) */
    position: relative;
    height: 60vh;
    min-height: 480px;
    width: 100%;
    padding: 8px;
    border-radius: var(--radius-md);
}

/* `.stack` ya no hace nada: sus tres declaraciones (flex, column, gap 24) son
   hoy las de `main`. Se deja definida y vacía a propósito —la escriben ~20
   templates— para que quitarla de cada uno sea una limpieza aparte y no un
   requisito de este cambio. Poner aquí cualquier regla volvería a partir el
   ritmo en dos según quién escriba la clase. */
main.stack { }

/* Card variant: full-width, content height */
.card.card-full {
    width: 100%;
    height: auto;
    min-height: 0;
    display: block;
}
.card.card-full[data-padding="tight"] { padding: 22px; }

/* Subsection title inside a card */
.card-section-title {
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--teal);
    margin: 0 0 18px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--border-soft);
    font-weight: 500;
}

/* Info grid (used in sips, crm, rankings) */
.info-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 16px;
}
.info-item {
    /* surface aplicada via .surface-soft (multi-selector) */
    padding: 18px 20px;
    border-radius: var(--radius-md);
}
.info-label {
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--ink-soft);
    margin-bottom: 8px;
    font-weight: 500;
}
.info-value {
    font-size: 18px;
    font-weight: 600;
    color: var(--white);
    word-break: break-word;
}

/* Search container (centered hero search, used in sips) */
.search-container {
    max-width: 720px;
    margin: 0 auto;
    text-align: center;
}
.search-box {
    display: flex;
    gap: 12px;
    margin-bottom: 8px;
}
.search-box .search-input {
    flex: 1;
    width: auto;
}
.search-input.search-input-lg {
    padding: 16px 20px;
    font-size: 17px;
    border-radius: var(--radius-md);
}

/* Big primary button (search, hero CTAs) */
.btn.btn-lg {
    padding: 16px 28px;
    font-size: 16px;
}

/* Botón de fila: en una tabla densa van varios por celda, y a tamaño
   normal la fila crece hasta dejar de leerse como una fila. */
.btn.btn-sm {
    padding: 6px 12px;
    font-size: 12.5px;
    font-weight: 500;
}

/* Full-screen loading overlay */
.loading-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(14,26,27,.85);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    z-index: 1000;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    gap: 16px;
}
.loading-overlay .spinner-lg {
    border: 3px solid var(--border-soft);
    border-top: 3px solid var(--teal);
    border-radius: 50%;
    width: 44px;
    height: 44px;
    animation: spin 1s linear infinite;
}
.loading-overlay .loading-msg {
    font-family: var(--mono);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--ink-mid);
}

/* Badge legacy aliases */
.badge.badge-success { color: #6FD49C; background: rgba(46,139,87,.16); border-color: rgba(46,139,87,.40); }
.badge.badge-warning { color: #FF9472; background: rgba(196,74,43,.16); border-color: rgba(196,74,43,.40); }
.badge.badge-strong  { color: var(--ink); background: var(--teal); border-color: var(--teal); }

/* Search input with leading icon */
.search-with-icon {
    position: relative;
    flex: 1;
    min-width: 280px;
}
.search-with-icon .search-input {
    width: 100%;
    padding-left: 42px;
}
.search-with-icon .search-icon {
    position: absolute;
    left: 14px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--ink-soft);
    pointer-events: none;
}

/* Filter select dropdown */
.filter-select {
    padding: 10px 14px;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-md);
    background: rgba(0,0,0,.25);
    color: var(--white);
    font-family: var(--sans);
    font-weight: 500;
    font-size: 14px;
    cursor: pointer;
    min-width: 160px;
    outline: none;
    transition: border-color .15s, box-shadow .15s;
}
.filter-select option { background: var(--ink); color: var(--white); }
.filter-select:focus {
    border-color: var(--teal);
    box-shadow: 0 0 0 3px rgba(46,182,188,.25);
}

/* Inline action button (smaller, used in row actions) */
.btn-action {
    /* surface aplicada via .surface-soft (multi-selector) */
    padding: 8px 14px;
    border-radius: var(--radius-pill);
    font-family: var(--sans);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background .15s, border-color .15s, color .15s;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    text-decoration: none;
    color: var(--ink-strong);
}
.btn-action:hover {
    background: rgba(46,182,188,.18);
    border-color: var(--teal);
    color: var(--white);
}
.btn-action.btn-primary,
.btn-pdf {
    color: var(--white);
    background: var(--teal-deep);
    border-color: var(--teal-deep);
}
.btn-action.btn-primary:hover,
.btn-pdf:hover {
    background: var(--teal);
    border-color: var(--teal);
    color: var(--ink);
}
/* `.btn` ya traía su estado deshabilitado y `.btn-action` no, así que
   "Analizar" sin ficheros o "Generar" sin nada marcado se veían pulsables —
   el `disabled` solo se notaba al hacer clic y no pasar nada. El :hover se
   repite porque si no, pasar por encima los devolvía al aspecto activo. */
.btn-action:disabled,
.btn-action:disabled:hover {
    background: rgba(255,255,255,.06);
    border-color: var(--border-soft);
    color: rgba(255,255,255,.35);
    cursor: not-allowed;
}

.actions {
    display: inline-flex;
    gap: 8px;
    flex-wrap: wrap;
}

.amount-text {
    font-weight: 700;
    color: var(--white);
    font-variant-numeric: tabular-nums;
}

.empty-state,
.loading-state {
    padding: 56px 20px;
    text-align: center;
    color: var(--ink-soft);
    font-size: 14px;
}

/* Page-level container (CRM and standalone pages). Usa el mismo token que
   `main` y `.wrap`: si no, la página que declara `w-data` se quedaría estrecha
   sin que se viera el porqué. */
/* `.page-container`, `.page-header` y `.page-sub` se retiraron el 2026-09-11.
   Eran la cascara alternativa a `<main>` + `<header>`: daban el ancho pero no
   el ritmo (`main` es flex column con gap 24) ni el landmark, y su h1 iba a
   34px/500 donde el del shell va a 36px/400. Las tres paginas que los usaban
   (/cnmc, /sips/censo, /ventas/crm) eran justo las que se veian distintas al
   cambiar de pestana. No se dejan definidas y vacias como `.stack`: aqui lo
   que se quiere es que copiarlas de otra pagina deje de funcionar.

/* Metric cards (top dashboard cards with big numbers) */
.metrics-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 20px;
}
.metric-card {
    border-radius: var(--radius-lg);
    padding: 28px;
    position: relative;
    overflow: hidden;
    color: var(--white);
    border: 1px solid transparent;
    box-shadow: var(--shadow-md);
    min-height: 160px;
}
.metric-card.metric-dark {
    background: linear-gradient(135deg, var(--ink) 0%, var(--ink-2) 100%);
}
.metric-card.metric-teal {
    background: linear-gradient(135deg, var(--teal-deep) 0%, #0E5557 100%);
}
.metric-card .metric-label {
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: rgba(255,255,255,.65);
    margin: 0 0 14px;
    font-weight: 500;
}
.metric-card .metric-value {
    font-family: var(--serif);
    font-size: 56px;
    font-weight: 500;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--white);
}
.metric-card .metric-value .metric-unit {
    font-family: var(--sans);
    font-size: 18px;
    font-weight: 500;
    color: rgba(255,255,255,.7);
    margin-left: 6px;
}
.metric-card .metric-desc {
    margin-top: 10px;
    font-size: 13px;
    color: rgba(255,255,255,.65);
}
.metric-card .metric-icon {
    position: absolute;
    top: 22px;
    right: 22px;
    font-size: 22px;
    opacity: 0.4;
}
.metric-card.metric-teal .metric-value { color: var(--teal-soft); }
.metric-card.metric-dark .metric-value { color: var(--teal); }

/* Rank badge (numeric circle) */
.rank-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background: var(--surface-2);
    border: 1px solid var(--border-soft);
    border-radius: 50%;
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-mid);
}
.app-table tbody tr.is-user {
    background: rgba(46,182,188,.14);
}
.app-table tbody tr.is-user td {
    color: var(--white);
    font-weight: 600;
}
.app-table tbody tr.is-user .rank-badge {
    background: var(--teal);
    color: var(--ink);
    border-color: var(--teal);
}

/* "You" tag */
.company-tag {
    display: inline-flex;
    align-items: center;
    background: var(--teal-deep);
    color: var(--white);
    font-family: var(--mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    margin-left: 10px;
    font-weight: 600;
}

/* Trend arrows */
.positive-change { color: #6FD49C; font-weight: 600; }
.negative-change { color: #FF9472; font-weight: 600; }
.neutral-change  { color: var(--ink-soft); }

/* Highlight number callout (e.g. consumo anual total) */
.stat-callout {
    text-align: center;
}
.stat-callout .stat-num {
    font-family: var(--serif);
    font-size: 56px;
    font-weight: 500;
    color: var(--teal);
    line-height: 1;
    letter-spacing: -0.02em;
}
.stat-callout .stat-label {
    margin-top: 8px;
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--ink-soft);
}

/* KPI strip — fila de indicadores separados por hairlines de 1px.
   El truco: el grid pinta el borde (`gap: 1px` sobre `--border-soft`) y cada
   tile tapa su celda. Vivía en `sips.css`; subió aquí al reutilizarla los hubs.
   Ojo al añadir propiedades: `.kpi-value` la usan sips y los cinco hubs. */
/* ─────────── KPI: una cifra con su etiqueta ───────────

   Dos contenedores, tres tamaños y cuatro piezas. Nada más, y nada menos.

   Había **70 clases** con «kpi» en el panel (2026-09-11), y no eran 70 cosas:
   eran la misma tarjeta copiada nueve veces con otro prefijo —`cn-`, `cot-`,
   `det-`, `mx-`, `of-`, `og-`, `spv-`, `acc-mini-`—, idénticas pieza por pieza
   y distintas solo en el tamaño de la cifra (20, 22, 24, 26, 28) y en el
   `minmax` de la rejilla. Eso no son cinco decisiones: es un font-size que
   nadie volvió a mirar. `metricas.html` llegaba a redefinir su propia tarjeta
   dos veces dentro del mismo fichero.

   Colapsarlo TODO a una clase tampoco valía, porque sí hay trabajos distintos:

     .kpi-strip   las métricas van PEGADAS, separadas por un hairline, y se
                  leen como una sola pieza. Su cifra es pequeña (17px) porque
                  lo que importa es la fila entera, no cada número.
     .kpi-grid    cada métrica en SU tarjeta, sueltas en una rejilla que se
                  reparte sola. Aquí manda el número, así que es más grande.

   El tamaño se DECLARA (`.kpi-sm` / nada / `.kpi-lg`) en vez de escribirse un
   font-size: es la diferencia entre elegir y heredar el descuido de otro. Y el
   ancho mínimo de columna se afina con `--kpi-min` sin inventar una clase:

     <div class="kpi-grid kpi-lg" style="--kpi-min: 200px">

   Fuera de aquí, con motivo y no por deuda: el **hero** (`.dash-kpi-*`, una
   sola cifra gigante en clamp 36→56px, que no es una tira), el **KPI contra
   objetivo** del Home (lleva barra de progreso y porcentaje) y la **tarjeta de
   departamento** del Home. `tests/test_css_componentes.py` impide que aparezca
   una familia nueva. */

/* Contenedor A — pegadas. El `gap: 1px` sobre fondo de borde es lo que dibuja
   los hairlines entre tiles sin doblar bordes en las juntas. */
.kpi-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 1px;
    background: var(--border-soft);
    border-radius: var(--radius-md);
    overflow: hidden;
    border: 1px solid var(--border-soft);
}

/* Contenedor B — sueltas. */
.kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--kpi-min, 190px), 1fr));
    gap: 12px;
}

.kpi-tile {
    padding: 16px 18px;
    background: var(--surface-1);
    border: 1px solid var(--border-soft);
}
/* Suelta y con esquina; dentro de `.kpi-strip` la esquina la pone el contenedor. */
.kpi-grid > .kpi-tile { border-radius: var(--radius-md); }

/* La tarjeta que lleva el dato que la página existe para dar. */
.kpi-tile.is-destacada {
    background: rgba(46,182,188,.10);
    border-color: rgba(46,182,188,.30);
}

/* ── Las cuatro piezas ── */
.kpi-label {
    font-family: var(--mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--ink-soft);
    margin-bottom: 6px;
}
.kpi-value {
    font-size: 17px;
    font-weight: 600;
    color: var(--white);
}
.kpi-unit {
    font-size: 13px;
    font-weight: 500;
    color: var(--teal);
    margin-left: 4px;
}
.kpi-meta {
    font-size: 12px;
    color: var(--ink-soft);
    margin-top: 4px;
}

/* ── Los tres tamaños ──
   Solo tocan a `.kpi-grid`: en `.kpi-strip` la cifra es pequeña por diseño y
   no se ofrece variante, porque una fila pegada con números de 28px deja de
   leerse como una fila. */
.kpi-grid .kpi-value {
    font-family: var(--sans);
    font-size: 24px;
    letter-spacing: -0.01em;
    line-height: 1.2;
}
.kpi-grid.kpi-sm .kpi-tile  { padding: 10px 12px; }
.kpi-grid.kpi-sm .kpi-value { font-size: 20px; }
.kpi-grid.kpi-lg .kpi-tile  { padding: 18px 20px; }
.kpi-grid.kpi-lg .kpi-value { font-size: 28px; }

/* Dropzone — zona de arrastrar-y-soltar ficheros. Estuvo copiada casi literal
   en Ofertas y en el Facturador de TP, ambas llamándose `.of-drop` y divergiendo
   solo en la altura mínima. El `<input type=file>` va dentro del <label> y se
   esconde así (y no con display:none) porque en algunas versiones de Chromium
   deja artefactos al anidarlo en un label flex.
   El JS solo tiene que alternar `.is-drag` en dragenter/dragover. */
.dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 180px;
    padding: 24px 20px;
    border: 2px dashed rgba(255, 255, 255, .22);
    border-radius: var(--radius-md);
    background: rgba(255, 255, 255, .025);
    text-align: center;
    cursor: pointer;
    transition: border-color .15s, background .15s, transform .12s;
    user-select: none;
}
.dropzone:hover { border-color: rgba(46, 182, 188, .55); background: rgba(46, 182, 188, .05); }
.dropzone.is-drag {
    border-color: var(--teal);
    background: rgba(46, 182, 188, .10);
    transform: scale(1.005);
}
.dropzone input[type="file"] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    opacity: 0;
    pointer-events: none;
}
.dropzone .icon { font-size: 36px; line-height: 1; filter: grayscale(.2); }
.dropzone .lbl { color: var(--ink-strong); font-weight: 500; font-size: 14px; }
.dropzone .lbl .accent { color: var(--teal); text-decoration: underline; text-underline-offset: 3px; }
.dropzone .sub { color: var(--ink-soft); font-size: 12px; font-family: var(--mono); }

/* Desplegable — <details> con chevron, para meter bajo llave el formulario de
   alta de una página cuyo contenido principal es la lista de lo ya creado
   ("Nueva oferta", "Nueva factura"). Nació como `.of-import` en Ofertas.
   Regla de uso: si la lista viene vacía, ábrelo desde JS (`el.open = true`) —
   una página sin nada dentro no puede esconder su único punto de entrada. */
.desplegable {
    background: var(--surface-1);
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-lg);
    margin-bottom: 20px;
}
.desplegable > summary {
    cursor: pointer;
    padding: 14px 20px;
    font-size: 14px;
    font-weight: 500;
    color: var(--ink-strong);
    user-select: none;
    list-style: none;
}
.desplegable > summary::-webkit-details-marker { display: none; }
.desplegable > summary::before {
    content: "▸ ";
    color: var(--ink-soft);
    display: inline-block;
}
.desplegable[open] > summary::before { content: "▾ "; }
.desplegable > summary:hover { color: var(--teal); }
.desplegable[open] > summary { border-bottom: 1px solid var(--border-soft); }
.desplegable-body { padding: 18px 20px; }

/* Spinner */
.spinner {
    border: 3px solid var(--border-soft);
    border-radius: 50%;
    border-top: 3px solid var(--teal);
    width: 18px;
    height: 18px;
    animation: spin 1s linear infinite;
    display: inline-block;
    margin-left: 10px;
    vertical-align: middle;
}

@keyframes spin {
    0%   { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* Mobile */
@media (max-width: 720px) {
    header { padding: 24px 20px 16px; }
    main   { padding: 8px 20px 60px; }
    .wrap  { padding: 0 20px; }
    .header-logo { height: 44px; }
    .cards-grid { grid-template-columns: 1fr; gap: 16px; }
    .modal-content { width: 92%; padding: 22px; }
}


/* ─────────── 6. LOGIN (layout centrado) ─────────── */
.login-body {
    /* Hereda paleta del body global (gradiente teal sobre --ink); aquí solo layout. */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    box-sizing: border-box;
}

.login-container {
    width: 100%;
    max-width: 420px;
}

.glass-effect {
    background: rgba(255,255,255,.04);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border: 1px solid rgba(255,255,255,.10);
    box-shadow: 0 24px 60px rgba(0,0,0,.35);
}

.login-card {
    border-radius: var(--radius-lg);
    padding: 40px 32px;
    text-align: center;
    color: var(--white);
}

.brand-section {
    margin-bottom: 28px;
}

.login-logo {
    height: 56px;
    width: auto;
    margin-bottom: 16px;
    filter: drop-shadow(0 0 12px rgba(46,182,188,.35));
}

.brand-section h1 {
    font-family: var(--serif);
    font-size: 26px;
    font-weight: 400;
    letter-spacing: -0.01em;
    color: var(--white);
    margin-bottom: 6px;
}

.brand-section p {
    font-family: var(--mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: rgba(255,255,255,.55);
    margin: 0;
}

.login-form {
    display: flex;
    flex-direction: column;
    gap: 18px;
}

/* Login: form-group sobreescribe colores para fondo oscuro */
.login-card .form-group label {
    color: rgba(255,255,255,.55);
}

.login-card .form-group input {
    background: rgba(0,0,0,.25);
    border: 1px solid rgba(255,255,255,.10);
    color: var(--white);
}

.login-card .form-group input::placeholder {
    color: rgba(255,255,255,.35);
}

.login-card .form-group input:focus {
    border-color: var(--teal);
    background: rgba(0,0,0,.32);
    box-shadow: 0 0 0 3px rgba(46,182,188,.25);
}

.remember-line {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    color: rgba(255,255,255,.75);
    margin: 4px 0;
}
.remember-line input[type="checkbox"] {
    accent-color: var(--teal);
    width: 16px;
    height: 16px;
}

.btn-login {
    background: var(--teal);
    color: var(--ink);
    border: 0;
    border-radius: var(--radius-pill);
    padding: 14px;
    font-family: var(--sans);
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
    transition: background .2s, transform .15s, box-shadow .2s;
    margin-top: 8px;
}

.btn-login:hover {
    background: var(--white);
    transform: translateY(-1px);
    box-shadow: 0 8px 20px rgba(46,182,188,.30);
}

.error-banner {
    background: rgba(196,74,43,.12);
    border: 1px solid rgba(196,74,43,.35);
    color: #ff8a72;
    padding: 12px 14px;
    border-radius: var(--radius-md);
    font-size: 14px;
    margin-bottom: 4px;
    text-align: left;
}


/* ============================================================
   7. LEGACY: la clase .dark-theme antes era opt-in para el dark.
   Ahora el tema dark es el default global; mantenemos la clase
   como no-op para no romper templates que aún la incluyan.

   Nota: los estilos específicos del visualizador SIPS (kpi-strip,
   hero-card, power-row, alert-card, chip, tech-details, etc.)
   viven ahora en static/sips.css, cargado sólo en sips.html.
   ============================================================ */


/* ─────────── 8. SHELL / SIDEBAR ───────────
   Grid `sidebar | contenido` que envuelve header+main en base.html.
   El `body` de la sección 3 no se toca: login.html (standalone) depende
   de él. `.app-main` replica su centrado para que los `max-width: 1240px`
   de header/main/.wrap sigan funcionando dentro del área restante. */

:root { --sidebar-w: 248px; }

.app-shell {
    display: grid;
    grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
    width: 100%;
    min-height: 100vh;
}

.app-main {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 0;
}

.sidebar {
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    background: var(--surface-1);
    border-right: 1px solid var(--border-soft);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    padding: 20px 12px 16px;
    gap: 8px;
    scrollbar-width: thin;
}

.sb-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 4px 10px 14px;
    border-bottom: 1px solid var(--border-soft);
}

.sb-brand-logo {
    height: 32px;
    width: auto;
    object-fit: contain;
    filter: drop-shadow(0 0 8px rgba(46,182,188,.35));
}

.sb-brand-name {
    font-family: var(--serif);
    font-size: 19px;
    color: var(--white);
}

.sb-brand-name em { color: var(--teal); font-style: italic; }

.sb-nav {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-top: 6px;
}

.sb-group-head {
    display: flex;
    align-items: center;
    gap: 10px;
    list-style: none;
    cursor: pointer;
    border-radius: var(--radius-sm);
    padding: 9px 10px;
    font-size: 14px;
    font-weight: 600;
    color: var(--ink-strong);
    user-select: none;
}

.sb-group-head::-webkit-details-marker { display: none; }

.sb-group-head:hover { background: var(--surface-2); }

.sb-group-title { flex: 1; }

.sb-chevron {
    color: var(--ink-soft);
    font-size: 15px;
    transition: transform .18s;
}

.sb-group[open] > .sb-group-head .sb-chevron { transform: rotate(90deg); }

.sb-group-body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 2px 0 8px;
}

/* Rótulo de bloque: separa los tres grupos de departamentos (la
   comercializadora, las otras dos sociedades, y la casa). Es un divisor, no un
   item — sin hover, sin cursor y sin href—, así que va sangrado a la izquierda
   del todo y con una hairline encima que hace el corte. El primero de la lista
   no la lleva: ahí no hay nada que separar, y la línea quedaría flotando
   justo debajo del buscador. */
.sb-bloque {
    font-family: var(--mono);
    font-size: 9.5px;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--ink-soft);
    opacity: .75;
    padding: 16px 10px 6px 12px;
    margin-top: 8px;
    border-top: 1px solid var(--border-soft);
}

.sb-bloque:first-child {
    border-top: 0;
    margin-top: 0;
    padding-top: 4px;
}

/* Sección dentro de un departamento (‹Captación›, ‹Comunidades›…). Mismo papel
   que `.sb-bloque` un nivel más adentro: sangrada a la altura de los items
   para que se lea como su encabezado y no como uno más. */
.sb-label {
    font-family: var(--mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--ink-soft);
    padding: 8px 10px 3px 36px;
}

.sb-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 10px 7px 24px;
    font-size: 13.5px;
    color: var(--ink-mid);
    border-radius: var(--radius-sm);
    border-left: 2px solid transparent;
}

.sb-item:hover { background: var(--surface-2); color: var(--white); }

.sb-item.is-active {
    background: rgba(46,182,188,.12);
    border-left-color: var(--teal);
    color: var(--teal-soft);
}

/* Fila fija de arriba (Hoy). No es un departamento: no lleva el sangrado de
   24px de los items de grupo, que existe para colgarlos de su cabecera. */
.sb-item-fijo {
    padding-left: 10px;
    font-weight: 600;
    color: var(--ink-strong);
    margin-bottom: 4px;
}

.sb-icon {
    font-size: 15px;
    width: 18px;
    text-align: center;
    flex-shrink: 0;
}

/* ─────────── 8b. BARRA DE BÚSQUEDA (arriba de cada página) ───────────
   Lo que se ve es una caja de búsqueda ancha; lo que hay debajo es el mismo
   botón que abre la paleta ⌘K, que vivía en el sidebar como una fila más de
   13px. Se alinea con el header y el main (`--w`) en vez de ir a sangre: una
   barra que empieza donde no empieza nada más del panel se lee como otra capa.
   El `<input>` real es el de la paleta — aquí no puede haber uno (ver
   _partials/barra_buscar.html). */

.tb {
    width: 100%;
    max-width: var(--w);
    padding: 20px 32px 0;
}

.tb-buscar {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    padding: 13px 22px;
    background: var(--surface-2);
    border: 1px solid var(--border-soft);
    /* El filo claro de arriba es lo que la separa del fondo sin subirle el
       relleno: el borde solo no basta sobre un fondo casi negro. */
    box-shadow: inset 0 1px 0 rgba(255,255,255,.10);
    border-radius: var(--radius-pill);
    color: var(--ink-strong);
    font-size: 16px;
    text-align: left;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.tb-buscar:hover {
    background: var(--surface-3);
    border-color: var(--border-strong);
    color: var(--white);
}

/* El global de :focus-visible redondea a 4px, que en una píldora deja el halo
   con las esquinas por fuera. */
.tb-buscar:focus-visible { border-radius: var(--radius-pill); }

.tb-buscar-icono {
    width: 19px;
    height: 19px;
    flex-shrink: 0;
}

.bs-foot kbd {
    font-family: var(--mono);
    font-size: 10px;
    color: var(--ink-soft);
    background: var(--surface-2);
    border: 1px solid var(--border-soft);
    border-radius: 4px;
    padding: 2px 5px;
}

.sb-foot {
    border-top: 1px solid var(--border-soft);
    padding-top: 8px;
}


/* ─────────── 9. BUSCADOR GLOBAL (⌘K) ───────────
   Paleta de comandos: busca datos (/api/search) y páginas del panel. Vivía solo
   en la home, así que buscar un CUPS desde otra página obligaba a volver. */

.bs-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1500;          /* por encima del sidebar off-canvas */
    background: rgba(0,0,0,.55);
    padding: 12vh 16px 16px;
    justify-content: center;
    align-items: flex-start;
}

.bs-overlay.is-open { display: flex; }

.bs-panel {
    width: 100%;
    max-width: 620px;
    background: var(--surface-strong);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: 0 24px 60px rgba(0,0,0,.55);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    max-height: 70vh;
}

.bs-field {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--border-soft);
}

.bs-icon { font-size: 15px; color: var(--ink-soft); }

.bs-input {
    flex: 1;
    background: transparent;
    border: 0;
    outline: none;
    color: var(--white);
    font-family: var(--sans);
    font-size: 15px;
    min-width: 0;
}

.bs-input::placeholder { color: var(--ink-soft); }

.bs-spinner {
    width: 14px;
    height: 14px;
    border: 2px solid rgba(46,182,188,.25);
    border-top-color: var(--teal);
    border-radius: 50%;
    animation: bs-giro .7s linear infinite;
    display: none;
    flex-shrink: 0;
}

.bs-spinner.is-visible { display: block; }
@keyframes bs-giro { to { transform: rotate(360deg); } }

.bs-results { overflow-y: auto; }

.bs-group { padding: 8px 0; border-bottom: 1px solid var(--border-soft); }
.bs-group:last-child { border-bottom: 0; }

.bs-group-label {
    padding: 6px 18px 4px;
    font-family: var(--mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--ink-soft);
}

.bs-item { display: block; padding: 9px 18px; font-size: 13px; color: var(--white); }
.bs-item:hover, .bs-item.is-active { background: rgba(46,182,188,.12); color: var(--white); }
.bs-item-title { font-weight: 500; word-break: break-all; }
.bs-item-sub { font-size: 12px; color: var(--ink-soft); margin-top: 2px; }

.bs-empty {
    padding: 28px 18px;
    text-align: center;
    font-size: 13px;
    color: var(--ink-soft);
}

.bs-foot {
    display: flex;
    gap: 16px;
    padding: 10px 18px;
    border-top: 1px solid var(--border-soft);
    font-size: 11px;
    color: var(--ink-soft);
}

.bs-foot kbd { margin-right: 4px; }

.sb-foot .sb-item { padding-left: 10px; }

/* Hamburguesa y backdrop: solo existen en móvil */
/* Un A4 mide menos de 1024px de ancho, así que al imprimir manda la rama
   móvil: el sidebar sale de escena por su transform, pero el botón ☰ pasa a
   `display: block` y, como es `fixed`, se estampa en cada página del PDF
   (facturas de Tarifa Plana y SPV). Fuera del papel todo lo que es shell. */
@media print {
    .sidebar, .sb-toggle, .sb-backdrop, .tb { display: none !important; }
}

.sb-toggle {
    display: none;
    position: fixed;
    top: 14px;
    left: 14px;
    z-index: 1300;
    background: var(--surface-strong);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--white);
    font-size: 18px;
    padding: 8px 12px;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
}

.sb-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1100;
    background: rgba(0,0,0,.5);
}

@media (max-width: 1024px) {
    .app-shell { grid-template-columns: 1fr; }

    .sidebar {
        position: fixed;
        left: 0;
        top: 0;
        width: var(--sidebar-w);
        z-index: 1200;
        transform: translateX(-100%);
        transition: transform .22s ease;
        background: var(--ink);
    }

    .app-shell.sidebar-open .sidebar { transform: none; }

    .sb-toggle { display: block; }

    /* Hueco para la hamburguesa fija. Lo abre ahora la barra de búsqueda, que
       es lo primero de la página: en vez de bajarse entera, se aparta a su
       derecha y las dos comparten la primera línea. */
    .app-main > .tb { padding: 12px 20px 0 66px; }
    .app-main > .tb + header { padding-top: 20px; }
    .app-main > header { padding-top: 64px; }
}
