/* base.css — Hoténtico, reportes-semanales
 *
 * Segunda capa de la arquitectura de CSS (tokens → base → app/página). Trae
 * las fuentes reales del sistema (@font-face), el reset, el `body` base y el
 * piso de calidad (foco de teclado, reduced-motion) — lo que necesita
 * CUALQUIER pantalla de esta app, tenga o no sidebar.
 *
 * La usan las 12 páginas que extienden `_layout.html` (dashboard, hotel_home,
 * hotel_config, hotentico, blindspot, minlos, tarifas, apertura, upload,
 * minutas, minuta_detalle, calculadora) — vía los tres `<link>` de
 * `_layout.html` (tokens → base → app) — MÁS `login.html` y
 * `tarifa_cero.html`, que linkean sólo `tokens.css` + `base.css` (sin
 * `app.css`, que es el chrome de sidebar/nav: ver el comentario de ese
 * archivo y `docs/navegacion.md`).
 *
 * Trae también la capa de componentes (.main, .page-heading, .btn, tabla de
 * datos, alerts, tags, KPI grid, estado vacío, campos de formulario) — Fase 2
 * del rediseño de interfaz (20/08/2026): una sola definición de cada cosa,
 * compartida por los 14 templates en vez de una copia por página.
 *
 * ⚠️ Trampa verificada al escribir este archivo — YA RESUELTA (20/08/2026):
 * `tokens.css` declaraba `--font-sans` / `--font-data` / `--text-*` /
 * `--print-*` SÓLO dentro de `@theme inline { }` (sintaxis de Tailwind v4),
 * que un browser sin el compilador de Tailwind descarta entero. Esta app no
 * corre Tailwind, así que esas variables no resolvían fuera de ahí. El
 * arreglo quedó en `hotentico-design` (commit del 20/08/2026) y se
 * re-vendorizó acá: ahora el `:root` plano de `tokens.css` TAMBIÉN declara
 * los 8 pasos de escala, los `--print-*` y las dos familias — por eso acá
 * se puede escribir `var(--ui-label)` o `var(--font-sans)` sin fallback.
 * Los fallbacks numéricos que quedaban de la época en que hacía falta el
 * shim se sacaron; los de color se dejaron (no estorban y documentan el
 * valor esperado a simple vista).
 */

/* ── Fuentes — Inter e IBM Plex Mono, peso 400 únicamente ─────────────────
 * .woff2 vendorizados en assets/fonts/ (el build de Docker no tiene red).
 * unicode-range copiado tal cual de la CSS API de Google Fonts
 * (fonts.googleapis.com/css2?family=Inter:wght@400&family=IBM+Plex+Mono:wght@400),
 * subsets latin + latin-ext únicamente (se descartan cyrillic/greek/vietnamese).
 */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/inter-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/inter-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-mono-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-mono-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── Reset (antes en app.css:17-18) ────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ── Peso y forma explícitos — reemplaza el reset plano (Fase 6, 22/08/2026)
 * Antes: un selector universal forzaba radio 0 y peso 400 con la bandera
 * de máxima prioridad de CSS, aplanando TODO — incluidos los defaults del
 * user-agent que le dan peso a <h1>-<h6>/<b>/<strong>/<th> y radio a
 * <button>/<input>/<select>/<textarea>/<fieldset>/<summary>. Sacarlo sin
 * reemplazo hacía aparecer
 * negritas y redondeos que nadie pidió. Reglas explícitas en su lugar:
 * lo que tiene semántica de encabezado o énfasis gana --w-strong (la
 * jerarquía que el rediseño necesita — ver el comentario de --ui-* más
 * abajo); los controles nativos heredan la tipografía del sistema y el
 * radio del sistema, sin ganar un peso que nunca tuvieron por defecto.
 *
 * No hay .woff2 de 500/600 (sólo 400, ver @font-face arriba) y el build de
 * Docker no tiene red para bajar uno — Chromium sintetiza el peso que falta.
 * `font-synthesis: weight` en `body`, más abajo, deja esa síntesis limitada
 * al peso (nunca itálica, que este sistema no usa).
 *
 * ⚠️ Con UNA excepción declarada desde el 10/09/2026: `.hb-msg em` del chat de
 * HotiBot la vuelve a habilitar (`font-synthesis: weight style`). El modelo
 * escribe *itálica* de markdown para nombres de tarifario y estados, y sin
 * síntesis esa itálica no se ve: quedaba texto plano indistinguible. Vale sólo
 * ahí, y sólo porque la consola ya es una excepción entera al design system —
 * el reporte del cliente sigue sin itálica.
 */
h1, h2, h3, h4, h5, h6,
th,
b, strong {
  font-weight: var(--w-strong);
}

button, input, select, textarea, fieldset, summary {
  font-family: inherit;
  border-radius: var(--radius);
}

/* ── Body base ── */
body {
  font-family: var(--font-sans);
  background: var(--n-25);
  color: var(--black, #1f1f1f);
  -webkit-font-smoothing: antialiased;
  font-synthesis: weight;
}

/* ── Superficies semánticas — el hueco del sistema (20/08/2026) ───────────
 * `hotentico-design` define pos/neg/warn sólo como TINTA (color de texto),
 * no como superficie (fondo). Estos cuatro son locales de este repo,
 * derivados de esos semánticos, para los fondos de alert/badge/hover que
 * hoy están sueltos y a ojo en 8 templates (`#dff0e6`, `#ffe0de`, `#fdf6e3`,
 * `#ffeab0`, entre otros). Elegidos reutilizando los 4 valores que ya más
 * se repiten en el código existente, así Fase 2 no introduce un tinte más:
 * sólo les pone nombre.
 *
 * Contraste — resuelto por DISEÑO, no por elegir otro tinte (20/08/2026):
 *   el par tinta/superficie de --warn sobre --surf-warn daba 4,36:1 ✗ (no
 *   llega a 4,5), techo estructural del matiz amber (4,71:1 aun contra
 *   blanco puro, contra 6,49/6,54 de --pos/--neg — el mismo motivo por el
 *   que el sistema ya documenta la excepción de hotentico-orange a partir
 *   de 24px). En vez de perseguir un tinte casi blanco que deja de leerse
 *   como superficie, `.alert` (más abajo) cambió de patrón: el texto va
 *   SIEMPRE en --black sobre la superficie, y el color semántico vive sólo
 *   en el borde izquierdo de 4px. Con texto negro los tres estados dan
 *   ~15:1 — el problema de contraste no puede volver a aparecer porque ya
 *   no hay ningún par tinta-de-color/superficie que tenga que cumplir 4,5:1.
 *   --surf-hover no es un par de texto — es sólo fondo de fila/card en
 *     hover (ya usado así, sin nombre, en 5 templates) — no aplica 4,5:1.
 *
 * Candidatos a subir a `hotentico-design` si se adoptan en más proyectos.
 */
:root {
  --surf-pos:   #dff0e6;
  --surf-neg:   #ffe0de;
  --surf-warn:  #fdf6e3;
  --surf-hover: #ffeab0;

  /* ── Rampa neutra cálida — Fase 6 del rediseño de interfaz (22/08/2026) ──
   * Desaturada desde --gray (#5e4a3a), NO gris azulado: el objetivo es que
   * el "gris" de esta app siga leyéndose de la misma familia que el marrón
   * de marca, no como un neutro genérico de sistema operativo. Reemplaza
   * ivory/cream/cream-dark como fondo del área de trabajo — el cream queda
   * reservado para UNA sola cosa: esto está activo (ver .sidebar-hotel[open]
   * en app.css, la única superficie cream que sobrevive fuera de .alert,
   * que conserva su patrón propio a propósito).
   */
  --n-0:   #ffffff;
  --n-25:  #faf8f4;
  --n-50:  #f2efe9;
  --n-100: #e7e2d9;
  --n-200: #d5cec2;
  --n-400: #a2988a;
  --n-600: #6b6156;
  --n-900: #23201c;

  /* ── Escala de app — hermana de las escalas `--text-` y `--print-` de
   * tokens.css, con nombres propios (`--ui-`) en vez de pisar `--text-`.
   *
   * ⚠️ NO escribas `--text-*` con el asterisco pegado a una barra en este
   * comentario: la secuencia cierra el comentario CSS ahí mismo, el resto
   * del texto queda como declaración basura y se come la PRIMERA
   * declaración real que le sigue hasta el `;` — o sea --ui-micro. Pasó el
   * 22/08/2026: `--ui-micro` no existía, `.page-label` y los `thead th`
   * caían a los 16px del user-agent, y no hubo ni un error de consola.
   * Congelado en test_diseno.py (todo var(--ui-*) usado tiene que estar
   * declarado).
   *
   * Por qué: --text-label tiene que seguir valiendo 13px en
   * hotentico-website Y en el PDF/PNG que arma Playwright para el cliente
   * (_report_styles.html, fuera de esta fase) — son otro proyecto y otro
   * documento respectivamente, cada uno con su propio contrato de esa
   * variable. Si este archivo redefiniera --text-label para la interfaz
   * interna, el mismo nombre pasaría a valer distinto según qué CSS se
   * cargó último: exactamente el drift silencioso que este repo ya sufrió
   * con `@theme inline` (ver el encabezado de este archivo) — ahí el
   * síntoma fue "la variable no resuelve nada"; acá sería peor, "la
   * variable resuelve a un valor DISTINTO del que el otro proyecto espera",
   * sin ningún error que lo delate. Con --ui-* en vez de --text-*, un
   * `grep -r "text-transform\|--text-" _report_styles.html` (o el mismo
   * grep en hotentico-website) sigue devolviendo sólo lo que hay ahí: la
   * contaminación entre proyectos queda grepeable, no implícita.
   */
  --ui-micro:   11px;
  --ui-label:   13px;
  --ui-body:    14px;
  --ui-lead:    16px;
  --ui-h3:      25px;
  --ui-h2:      31px;
  --ui-h1:      38px;
  --ui-display: clamp(2.5rem, 6.5vw, 5rem);

  /* ── Forma, peso, elevación ── */
  --radius:    4px;
  --radius-sm: 3px;
  --w-normal:  400;
  --w-medium:  500;
  --w-strong:  600;
  --elev-1: 0 1px 2px rgba(35,32,28,.04);
}

/* ── Foco de teclado ────────────────────────────────────────────────────
 * Antes esto vivía suelto: 6 reglas repartidas entre calculadora y minlos,
 * tres de ellas con `outline: none` sin reemplazo. Uno global acá.
 */
:focus-visible {
  outline: 2px solid var(--orange, #FA500F);
  outline-offset: 2px;
}

/* ── Movimiento reducido ────────────────────────────────────────────────
 * Antes sólo en calculadora y minlos. Global acá para toda la app.
 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition: none !important;
    animation: none !important;
  }
}

/* ══════════════════════════════════════════════════════════════════════
 * Capa de componentes — Fase 2 del rediseño de interfaz (20/08/2026)
 *
 * Antes: cada uno de los 14 templates redeclaraba .main/.page-heading/
 * botones/tablas/alerts/tags/KPIs a mano en su propio {% block head %} —
 * ~700 líneas de CSS casi idéntico repetidas página a página. Ahora hay
 * una sola definición acá y cada template sólo trae lo que es genuinamente
 * suyo. Los nombres de clase existentes se conservaron donde alcanzaba;
 * donde había varios nombres para la misma cosa, el HTML pasó al nombre
 * nuevo (ver docs/diseno.md para el mapeo completo).
 *
 * La usan las 12 páginas con sidebar (vía _layout.html) Y login.html +
 * tarifa_cero.html, que linkean tokens+base sin app.css — por eso estos
 * componentes viven acá y no en app.css (que es sólo chrome de sidebar).
 * ══════════════════════════════════════════════════════════════════════ */

/* ── Página: contenedor + encabezado ───────────────────────────────────
 * Referencia: hotel_home.html. Páginas con otro ancho o padding-bottom
 * (la barra de cola fija necesita más aire abajo) lo declaran como única
 * línea de más en su propio head — no es una copia del componente entero.
 */
.main {
  padding: 48px 32px 80px;
  max-width: 900px;
  margin: 0 auto;
}
/* Anchos nombrados — Fase 3 del rediseño de interfaz (20/08/2026). Antes: 8
 * `max-width` ad hoc (620/640/760/780/980/1040/1080/1200px) repartidos en 8
 * de los 14 templates, cada uno "a ojo". Cuatro anchos con nombre, cada
 * página mapeada al más cercano de los cuatro (no se inventa un ancho por
 * página): `--narrow` para formularios/lectura, el default (900px, arriba)
 * para contenido general, `--wide`/`--xwide` para tablas densas — la matriz
 * de Mínimo LOS es la única que necesita 1200 (ver docs/diseno.md). */
.main--narrow { max-width: 640px; }
.main--wide   { max-width: 1080px; }
.main--xwide  { max-width: 1200px; }
/* El quinto, agregado el 12/09/2026 con /whatsapp, y NO es "un ancho a ojo"
 * más: es otra categoría. Los cuatro de arriba miden CONTENIDO —prosa, tablas,
 * formularios— y por eso tienen un tope, aunque sea grande. Una bandeja de chat
 * no es contenido: es una aplicación de dos paneles cuyo ancho útil es el que
 * haya, igual que WhatsApp Web o un cliente de correo. Toparla en 1200 deja el
 * hilo angosto en una pantalla grande sin que eso mejore nada. Se usa sólo
 * cuando la página ES la herramienta, no cuando la página muestra algo ancho. */
.main--full   { max-width: none; }
.page-label {
  font-size: var(--ui-micro);
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--orange);
  margin-bottom: 6px;
}
.page-heading {
  font-size: var(--ui-h3);
  font-weight: var(--w-strong);
  letter-spacing: -0.03em;
  color: var(--black);
  margin-bottom: 8px;
  line-height: 1.1;
}
.page-sub {
  font-size: var(--ui-body);
  color: var(--gray);
  line-height: 1.5;
  margin-bottom: 28px;
  max-width: var(--measure);
}

/* ── Botones ──────────────────────────────────────────────────────────
 * Antes: .btn-send / .btn-link / .action-link / .ghost / button.ghost /
 * .btn-review y botones sueltos, 9 templates, 6 nombres. Uno con
 * modificadores. .btn--primary (naranja/blanco, hover rojo) es el mismo
 * lenguaje que ya dominaba la mayoría de los formularios — hotel_home
 * era la excepción con su primario en negro y pasó a converger acá.
 *
 * Fase 6 (22/08/2026): MAYÚSCULAS → sentence case (era la única jerarquía
 * disponible sin peso; con --w-medium ya no hace falta gritar) y el fondo
 * pasa de --cream (ahora reservado a "esto está activo") a --n-0/--n-200.
 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 7px 13px;
  font-size: var(--ui-label);
  font-weight: var(--w-medium);
  text-decoration: none;
  cursor: pointer;
  background: var(--n-0);
  color: var(--black);
  border: 1px solid var(--n-200);
  border-radius: var(--radius);
  transition: background .12s, border-color .12s, color .12s;
}
.btn:hover { border-color: var(--orange); color: var(--orange); }
.btn:disabled { cursor: default; opacity: .55; }
.btn--primary { background: var(--orange); color: #fff; border-color: var(--orange); }
.btn--primary:hover { background: var(--red); border-color: var(--red); color: #fff; }
.btn--primary:disabled { background: var(--gray); border-color: var(--gray); color: #fff; }
.btn--quiet { background: transparent; color: var(--gray); border-color: transparent; }
.btn--quiet:hover { background: var(--n-50); color: var(--black); border-color: transparent; }
/* Contexto claro (fondo neutro) — para el contexto oscuro de la barra
   de cola de blindspot/minlos, ver .btn-reject en minlos.html: mismo rol,
   colores on-dark. */
.btn--danger { background: transparent; color: var(--neg); border-color: var(--neg); }
.btn--danger:hover { background: var(--neg); color: #fff; border-color: var(--neg); }
.btn--sm { padding: 5px 10px; font-size: var(--ui-label); }

/* ── Tabla de datos ── design/design.md § "Tabla de datos":
 * header micro uppercase color warm-gray (tracking .1em — el piso de
 * --text-micro exige >= .1em; el diseño original documentaba .07em pero
 * eso no cumple la regla del propio sistema); celdas label; números a la
 * derecha siempre; tabular-nums (ya lo da tokens.css para cualquier
 * td/th); separador de fila 1px cream; total con fondo cream.
 */
.data-table { width: 100%; border-collapse: collapse; }
.data-table thead th {
  text-align: left;
  font-size: var(--ui-micro);
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--n-600);
  background: var(--n-25);
  padding: 10px 12px;
  border-bottom: 2px solid var(--n-100);
  white-space: nowrap;
}
/* Encabezado ordenable — mismo patrón data-key/data-sort que /blindspot y
   /tarifa-cero (ver .sort-arrow más abajo): cualquier .data-table que agregue
   data-key a un <th> se vuelve clicable, sin CSS aparte por página. */
.data-table thead th[data-key] { cursor: pointer; user-select: none; }
.data-table thead th[data-key]:hover { color: var(--black); }
.data-table tbody td {
  font-size: var(--ui-label);
  padding: 9px 12px;
  border-bottom: 1px solid var(--n-50);
  color: var(--black);
}
/* Antes --surf-hover (#ffeab0, amarillo fuerte). Con cream reservado a
   "activo", el hover de fila pasa a --n-50 acá. El token --surf-hover NO
   se toca ni se borra: dashboard.html, hotel_home.html y hotentico.html lo
   referencian directo en su propio {% block head %} (.selector-card:hover,
   .automation-row:hover, .tool-row:hover) — son templates, fuera de
   alcance de este cambio. */
.data-table tbody tr:hover td { background: var(--n-50); }
.data-table td.num, .data-table th.num { text-align: right; }
/* --n-100 y no --cream: un total es énfasis, no "esto está activo". */
.data-table tr.total td { background: var(--n-100); }

/* ── Tabs (fila de secciones) ── estrenado por /tareas · /tareas/recurrentes
 * · /tareas/esporadicas (templates/_tareas_tabs.html, 21/08/2026) y
 * generalizado el 28/08/2026 para /tarifas, que tiene cuatro secciones en una
 * sola ruta. La fila es puro CSS de estado (`.is-active` + `aria-current` o
 * `aria-selected`); quién cambia ese estado depende de la página: en Tareas
 * cada tab es un link real a su propia URL, en Tarifas es un botón que muestra
 * y oculta paneles (ahí un link perdería lo que haya sin guardar en el editor).
 * Vive acá y no en un `{% block head %}` porque varias páginas la comparten
 * byte a byte — un componente más de esta capa, como `.data-table` o `.tag`.
 */
.tabs { display: flex; flex-wrap: wrap; border-bottom: 1px solid var(--n-100); margin-bottom: 28px; }
.tab {
  font-size: var(--ui-label);
  color: var(--gray);
  text-decoration: none;
  padding: 10px 4px;
  margin-right: 24px;
  border-bottom: 2px solid transparent;
}
.tab:hover { color: var(--black); }
.tab.is-active { color: var(--black); font-weight: var(--w-strong); border-bottom-color: var(--orange); }
/* La variante botón (Tarifas): un <button> no hereda ni la tipografía ni el
 * fondo del documento, así que se los devuelve acá. Sin esto los tabs de
 * /tarifas saldrían con la fuente por defecto del browser. */
button.tab { background: none; border: none; border-bottom: 2px solid transparent;
  font-family: inherit; cursor: pointer; }

/* ── Alerts ── ver el comentario de --surf-warn más arriba: el texto va
 * SIEMPRE en --black, el color semántico vive en el borde izquierdo.
 */
.alert {
  padding: 14px 18px;
  margin-bottom: 18px;
  font-size: var(--ui-label);
  line-height: 1.5;
  color: var(--black);
  background: var(--cream);
  border-left: 4px solid var(--gray);
  border-radius: var(--radius);
}
.alert--ok    { background: var(--surf-pos); border-left-color: var(--pos); }
.alert--error { background: var(--surf-neg); border-left-color: var(--neg); }
.alert--warn  { background: var(--surf-warn); border-left-color: var(--warn); }
.alert ul { margin: 8px 0 0 18px; }
.alert li { margin-bottom: 3px; }

/* ── Tags / badges ── Fase 6: mayúsculas → sentence case (rule 2). Un tag a
 * --ui-micro (11px) sólo es legal uppercase+tracking (rule 1) — al pasar a
 * sentence case sube a --ui-label (13px). --n-50/--n-100 para el default;
 * las variantes de color siguen filled (sin borde propio, el ring de --tag
 * quedaría desalineado sobre un fondo sólido). .tag--warn (ámbar) es sólo
 * para lo que efectivamente pide acción — no un estado informativo más.
 */
.tag {
  display: inline-block;
  font-size: var(--ui-label);
  padding: 2px 8px;
  color: var(--gray);
  background: var(--n-50);
  border: 1px solid var(--n-100);
  border-radius: var(--radius-sm);
}
.tag--pos  { color: #fff; background: var(--pos); border-color: transparent; }
.tag--neg  { color: #fff; background: var(--neg); border-color: transparent; }
.tag--warn { color: var(--black); background: var(--gold); border-color: transparent; }
.tag--dark { color: #fff; background: var(--black); border-color: transparent; }

/* ── Tarjeta ── Fase 6 (22/08/2026): el componente que hoy falta y por el
 * que los bloques de hotel_home.html flotan sueltos sobre el fondo. Base
 * genérica (`.card`) + dos hijos opcionales para el patrón "encabezado con
 * título a la izquierda y un dato secundario a la derecha" que se repite
 * en KPIs, listados y resúmenes. `.kpi-cell` (más abajo) es la misma idea
 * sin el header — se declara aparte porque el KPI ya trae su propio
 * label/value con una jerarquía distinta.
 */
.card {
  background: var(--n-0);
  border: 1px solid var(--n-100);
  border-radius: var(--radius);
  box-shadow: var(--elev-1);
}
.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--n-100);
}
.card-head-title { font-size: var(--ui-label); font-weight: var(--w-strong); color: var(--black); }
.card-head-meta { font-size: var(--ui-label); color: var(--n-600); }
.card-body { padding: 18px; }

/* ── KPI grid ── referencia: hotel_home.html. Antes: gap:2px sobre fondo
 * --cream-dark, truco de bordes falsos. Ahora cada celda es una tarjeta de
 * verdad (mismo lenguaje que .card, sin duplicarlo: --n-0/--n-100/--radius/
 * --elev-1 a mano porque .kpi-cell no es semánticamente un .card completo).
 */
.kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 40px; }
.kpi-cell {
  background: var(--n-0);
  border: 1px solid var(--n-100);
  border-radius: var(--radius);
  box-shadow: var(--elev-1);
  padding: 20px 22px;
}
.kpi-label { font-size: var(--ui-label); color: var(--n-600); margin-bottom: 6px; }
.kpi-value { font-size: var(--ui-h3); font-weight: var(--w-strong); letter-spacing: -0.03em; color: var(--black); line-height: 1.1; font-variant-numeric: tabular-nums; }
.kpi-delta { font-size: var(--ui-label); letter-spacing: 0; margin-top: 4px; }
.kpi-empty { padding: 28px 22px; grid-column: 1 / -1; text-align: center; color: var(--n-600); font-size: var(--ui-label); }

/* ── KPI chip ── NUEVO (22/08/2026): el delta como pastilla, no como texto
 * de color suelto — hotel_home.html hoy pinta `.kpi-delta` con un color
 * inline vía el helper `color_delta` (ver docs/diseno.md, "color_var vs.
 * color_delta"); este componente queda declarado para cuando esa página
 * adopte el patrón de pastilla, sin tocarla en esta fase. El triángulo es
 * el mismo `.sort-arrow` de más abajo (por bordes CSS) puesto DENTRO del
 * chip — nunca un glifo Unicode ▲▼: en Render (Linux) la fuente que
 * resuelve Chromium los dibuja como un punto, e Inter no cubre ese rango.
 * Marcado esperado: <span class="kpi-chip kpi-chip--pos"><span
 * class="sort-arrow asc"></span> 4%</span>.
 */
.kpi-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  font-size: var(--ui-label);
  font-weight: var(--w-medium);
  border-radius: var(--radius-sm);
}
.kpi-chip .sort-arrow { margin-left: 0; }
.kpi-chip--pos { background: #e4f1ea; color: var(--pos); }
.kpi-chip--neg { background: #fbeceb; color: var(--neg); }

/* ── Estado vacío ── un estado vacío es una invitación a actuar, no un
 * parte médico — ver el texto reescrito en cada template.
 */
.empty {
  padding: 48px 24px;
  text-align: center;
  color: var(--n-600);
  background: var(--n-0);
  border: 1px dashed var(--n-200);
  border-radius: var(--radius);
  font-size: var(--ui-label);
  line-height: 1.6;
}
.empty-icon { font-size: var(--ui-lead); margin-bottom: 10px; }

/* ── Campos de formulario ── antes: font-family: inherit repetido a mano
 * en 4 templates. */
input[type="text"], input[type="email"], input[type="password"], input[type="file"], input[type="number"],
input[type="date"], input[type="month"], select, textarea {
  font-family: inherit;
  font-size: var(--ui-label);
  padding: 9px 11px;
  border: 1px solid var(--n-200);
  border-radius: var(--radius);
  background: var(--n-0);
  color: var(--black);
}
input:focus, select:focus, textarea:focus { border-color: var(--orange); outline: none; }

/* ── Triángulo CSS para encabezados ordenables ─────────────────────────
 * Mismo patrón que .sidebar-hotel-summary::before (app.css): un glifo
 * Unicode ▲/▼ depende de la cobertura de la fuente que resuelva Chromium
 * en cada SO: sale como un punto en Linux/Render. El JS de blindspot.html
 * y tarifa_cero.html pone la clase .asc/.desc en vez de escribir el
 * carácter; el triángulo lo dibuja el borde. Nombre .sort-arrow (no
 * .arrow) para no chocar con el "→" de texto (cubierto por Inter, se
 * queda) que ya usa hotel_home.html en .automation-status .arrow. */
.sort-arrow { display: inline-block; width: 0; height: 0; vertical-align: middle; margin-left: 3px; }
.sort-arrow.asc, .sort-arrow.desc {
  border-style: solid;
  border-width: 0 3.5px 5px 3.5px;
  border-color: transparent transparent currentColor transparent;
}
.sort-arrow.desc { border-width: 5px 3.5px 0 3.5px; border-color: currentColor transparent transparent transparent; }

/* ── `.tabla-scroll` — utilidad opt-in (Fase 1 del plan mobile, 26/09/2026) ──
 * Envoltorio para una tabla ancha: scroll horizontal contenido en vez de
 * empujar la página entera (mismo problema que documenta docs/diseno.md,
 * "un canvas que se sale de su caja" — acá el contenido es una tabla, no un
 * canvas, y la contención es la misma receta). Fuera del @media a propósito:
 * sirve en cualquier ancho, no sólo en el teléfono — cada pantalla decide
 * cuándo envolver su tabla con esto, ninguna lo hace todavía en esta fase. */
.tabla-scroll { overflow-x: auto; }

/* ══════════════════════════════════════════════════════════════════════
 * Mobile — Fase 1 del plan `pure-yawning-graham.md` (26/09/2026). Mismo
 * breakpoint LITERAL que app.css: test_diseno.py prohíbe un `:root` nuevo,
 * así que no puede ser una variable.
 * ══════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  /* Menos aire a los costados — alcanza para las cinco variantes de ancho
   * (.main--narrow/--wide/--xwide/--full) porque ninguna redeclara el
   * padding, sólo el max-width. */
  .main { padding: 20px 16px 48px; }

  /* 4 columnas no entran en un teléfono sin achicar la letra (piso de
   * 11px) — 2x2 es el mismo contenido, apilado. */
  .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Área táctil mínima recomendada (44px) para cualquier botón del sistema. */
  .btn { min-height: 44px; }
}
