/* Expy Reservas — visitor.
 *
 * Built from the v1 section of spec page 10 only. That page still carries the
 * dead prototype tokens further down, inside the archived Claude Design prompt
 * — the neon pair, the #14101F ground, Space Grotesk. CLAUDE.md lists those
 * exact values under "muertos, no reintroducir", and the v1 section says it
 * supersedes everything below it. They are not here.
 *
 * Mobile-first at 380px. Desktop is the rare case, so it is a max-width and
 * nothing else.
 */

/* ── Faces ───────────────────────────────────────────────────────────
 * Self-hosted. The use context is a saturated mobile uplink at a venue
 * entrance: a face fetched from a third party renders as the fallback exactly
 * when the page matters most. `font-display: swap` so text is readable while
 * the file lands, and a real fallback stack behind each one regardless.
 *
 * Atkinson Hyperlegible is designed for low vision, with letterforms made
 * deliberately hard to confuse. Chosen for the condition of use — full sun,
 * standing, thumb, in a hurry — not for taste. Two weights only, so hierarchy
 * comes from size and colour: there is no 500 or 600 to lean on.
 */
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('/static/fonts/atkinson-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: normal; font-weight: 700; font-display: swap;
  src: url('/static/fonts/atkinson-700.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('/static/fonts/plexmono-400.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('/static/fonts/plexmono-600.woff2') format('woff2');
}
/* La cara de titular, y UN solo peso. Se usa de 17px para arriba y siempre en
 * 700; traer 400 y 600 seria payload que ninguna pantalla renderiza, y el
 * brief dice "datos moviles saturados". */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal; font-weight: 700; font-display: swap;
  src: url('/static/fonts/bricolage-700.woff2') format('woff2');
}

:root {
  --body: 'Atkinson Hyperlegible', system-ui, -apple-system, 'Segoe UI', sans-serif;
  /* Titulares, de 17px para arriba y NUNCA en una tabla. El cuerpo sigue
   * siendo Atkinson porque se eligio por la condicion de uso —a pleno sol, de
   * pie, apurado— y no por gusto: es una cara disenada para baja vision.
   * Lo que la de titular compra es caracter donde se MIRA, sin pagarlo donde
   * se LEE. */
  --display: 'Bricolage Grotesque', 'Atkinson Hyperlegible', system-ui, sans-serif;
  /* Times and codes ONLY. The tabular figures are the reason: without them a
   * column of times shifts width row to row and stops reading as a grid. */
  --mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;

  /* Six neutral steps, and no more — con TEMPERATURA, que es lo que los
   * separa de un gris de stock.
   *
   * El gris azulado anterior (#E7EBF0) no era un color elegido: es el que sale
   * por defecto en cualquier panel, y por eso la pantalla se leia generica.
   * Estos llevan una torsion verde medida —dE 5,1 contra su gris de igual
   * brillo— que los hace pertenecer a la familia del acento SIN ser verdes y
   * sin agregar una semantica nueva.
   *
   * La tarjeta tampoco es blanco puro: blanco puro sobre un fondo con
   * temperatura se ve como un parche recortado de otra pantalla.
   *
   * Medido sobre tarjeta / sobre fondo:
   *   ink    17,11 / 12,79      accent  7,66 / 5,72
   *   ink-2   6,96 /  5,20      line    1,70 / 1,27
   *   ink-3   5,13 /  3,84  <-- NO PASA sobre el fondo. Ver abajo.
   */
  --bg:        #D9DDD4;
  --surface:   #FBFCF9;
  --line:      #BFC6B9;
  --ink:       #161A14;
  --ink-2:     #525A4E;
  /* `--ink-3` es el unico que NO se puede usar sobre `--bg`: ahi mide 3,84 y
   * queda debajo de AA. Es el token que mas facil se usa mal, porque el ojo no
   * distingue 5,13 de 3,84 — la norma si, y alguien leyendo a pleno sol
   * tambien. Sobre tarjeta, y nada mas. */
  --ink-3:     #666E62;

  /* One green, one meaning: "es tuyo, está confirmado". The accent and the
   * presence semantic are the same colour on purpose. forest-700 of Expyfest,
   * and a prop — 2027 changes this value, not the system.
   *
   * Mide 7,66 sobre la tarjeta y 5,72 sobre el fondo. Era 6,59 contra el gris
   * azulado viejo: sobre la tarjeta GANÓ contraste, y sobre el fondo perdió un
   * punto y sigue holgado. El valor no se movió — se movió lo que tiene atrás. */
  --accent:       #1B5C45;
  --accent-hover: #164B38;
  --accent-soft:  #E2EDE7;

  /* Amber says "se vence o se llenó". Never a third meaning. */
  --warn:      #7A4B00;
  --warn-soft: #F8EFDC;
  --warn-line: #E0C68C;

  /* ── Elevacion: dos niveles, y tintadas ────────────────────────────
   * Con la tinta y nunca con negro puro: una sombra negra sobre un fondo con
   * temperatura se ve sucia, porque no pertenece a la paleta.
   *
   * Dos niveles y no cinco. El nivel 1 es el reposo de todo lo que es una
   * superficie; el 2 es lo que esta arriba de eso — el hover y la tarjeta
   * abierta. Una escala con cinco pasos es una escala que nadie respeta.
   */
  /* Los alfas no son los que uno escribe de memoria. La primera version iba a
   * .06/.05 —los valores tipicos de una sombra sobre BLANCO— y sobre este
   * fondo, que ya tiene cuerpo, no se veian: la tarjeta seguia sin despegarse
   * y la sombra no hacia el trabajo que se le asigno. Se subieron mirando, no
   * calculando. */
  --sh-1: 0 1px 2px rgba(22,26,20,.10), 0 3px 10px rgba(22,26,20,.09);
  --sh-2: 0 2px 6px rgba(22,26,20,.13), 0 12px 28px rgba(22,26,20,.14);
  /* Lo verde tira sombra VERDE. Una sombra gris debajo de un boton de acento
   * lo despega del sistema en vez de asentarlo. */
  --sh-accent:  0 2px 10px rgba(27,92,69,.28);
  --sh-accent-2:0 4px 16px rgba(27,92,69,.34);

  /* ── Movimiento: una curva, dos duraciones ─────────────────────────
   * `--t-fast` es lo que responde al dedo. `--t-slow` es lo que acomoda la
   * luz, y es mas lento a proposito: la luz no salta.
   *
   * Todo esto es CSS y no hay una linea de JS, que no es una preferencia:
   * este sistema tiene CERO JavaScript propio, y esa es la razon por la que
   * un error en la consola significa algo. Animar con un script romperia una
   * propiedad que el proyecto verifica.
   */
  --ease: cubic-bezier(.2,.7,.3,1);
  --t-fast: 120ms;
  --t-slow: 240ms;

  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px; --s7: 48px;

  --r-field: 6px;   /* fields, chips, cells */
  --r-card: 12px;   /* cards and buttons */
  --r-pill: 999px;

  /* Nothing tappable goes under 44. Density belongs to the information, not
   * to the geometry: this is used with a thumb, at night, in a hurry. */
  /* Las dos columnas fijas de una fila de lista. Existen para que el
   * metadato y el estado arranquen en el MISMO pixel en las 21 filas, que es
   * lo unico que permite compararlas de un vistazo. --col-estado sale del
   * texto de estado mas largo que el sistema produce —«Viernes 28 · 14:00»,
   * unos 150— mas aire. */
  --col-meta:   232px;
  --col-estado: 168px;

  /* 36 es la altura «solo mouse» de la escala del proyecto, y hasta hoy era
   * la unica de las cuatro que la hoja no tenia. Se usa donde el dedo no
   * llega nunca: una tabla de admin de 898 filas. */
  --h-admin: 36px;
  --tap: 44px;
  --tap-primary: 58px;
}

*, *::before, *::after { box-sizing: border-box; }

/* `scrollbar-gutter` reserva la canaleta de la barra exista o no.
 *
 * Sin esto, una pantalla corta del panel no tiene barra y una larga si, asi
 * que la caja centrada de 1240 arranca 8 px mas a la derecha en una que en la
 * otra. Medido: `main.wrap` a 144 en `/admin/tickets` y a 136 en las demas.
 * No se ve mirando una pantalla; se ve navegando entre dos, que es como se
 * usa un panel, y se lee como que la pagina salta. */
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }

body {
  margin: 0;
  font-family: var(--body);
  /* 16 is the visitor body. 14 is the admin table and 15 the staff floor —
   * three densities of one language, not three ways to say the same thing.
   * Nothing under 14 on a screen, ever. */
  font-size: 16px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg);
  /* Room for the fixed action bar so the last card is never trapped under it. */
  padding-bottom: calc(var(--tap-primary) + var(--s7));
}

.wrap { max-width: 480px; margin: 0 auto; padding: var(--s4); }

/* De 17px para arriba va la cara de titular. Debajo de eso no: en cuerpo y en
 * tabla manda la legibilidad, y ahi Atkinson no se negocia. */
h1 { font-family: var(--display); font-size: 21px; line-height: 1.25;
     letter-spacing: -.02em; margin: 0 0 var(--s2); }
h2 { font-family: var(--display); font-size: 18px; line-height: 1.3;
     letter-spacing: -.015em; margin: 0 0 var(--s2); }
p  { margin: 0 0 var(--s3); }
.lead      { color: var(--ink-2); }
.muted     { color: var(--ink-2); }
.small     { font-size: 14px; }
.mono      { font-family: var(--mono); font-variant-numeric: tabular-nums; }

a { color: var(--accent); }

/* ── Las tres anchuras ───────────────────────────────────────────────
 * S es el diseno, y las otras dos no son «lo mismo con mas margen»: eso se
 * lee como algo sin terminar. Cada una tiene que comprar algo.
 *
 *   S  hasta 700   una columna. La referencia, 380px.
 *   M  700–1099    DOS columnas de tarjeta. Lo que compra es ver mas obras de
 *                  una sola mirada, que en una lista de 24 es el trabajo.
 *   L  1100+       la columna se ensancha y respira, y la tarjeta abierta
 *                  puede mostrar sus horarios sin que la lista se vaya a
 *                  kilometros.
 *
 * UNA COLUMNA EN TODOS LOS ANCHOS, y el grid de dos y tres se retira.
 *
 * Lo de arriba se escribio pensando que mas ancho tenia que comprar mas
 * columnas. Medido, compraba lo contrario: con 21 obras y seis alturas de
 * tarjeta distintas —86, 107, 109, 130, 133 y 180 px— las filas de un grid de
 * tres no cierran nunca, y la pildora arrancaba donde terminaba cada titulo,
 * asi que 21 tarjetas daban 16 izquierdas distintas.
 *
 * Elegir entre 21 opciones es comparar, y comparar es vertical: una columna,
 * una altura de fila, y cada dato en la misma abscisa hacia abajo. La estacion
 * ya lo hace con 446 filas en este mismo codigo — es la prueba de que aguanta
 * el volumen.
 */
.tarjetas { display: block; }

@media (min-width: 700px) {
  .wrap { max-width: 760px; }
  /* El ancho ya lo acota la regla global de `.btn.primary`; lo que es propio
   * de la banda es que el boton se vaya a la derecha en vez de ocupar la
   * franja entera. */
  .bar .wrap { display: flex; justify-content: flex-end; }
}

@media (min-width: 1100px) {
  .wrap { max-width: 1040px; }
  /* Los horarios de la abierta se reparten en el ancho que ahora sobra. */
  .exp.abierta .times { gap: var(--s3); }
}

/* ── La banda ────────────────────────────────────────────────────────
 * `--ink` como SUPERFICIE. No hay un color nuevo: hay un neutro haciendo un
 * segundo trabajo.
 *
 * Es lo que separa esto de un panel. Un titulo suelto sobre gris se lee como
 * un formulario; una banda con el nombre del evento arriba se lee como la tapa
 * de un programa, y esto ES un programa: 898 horarios.
 *
 * Va a sangre completa y no dentro de `.wrap`, porque una banda con margenes
 * es un rectangulo, no una banda.
 */
.banda {
  background: var(--ink);
  padding: var(--s3) 0;
  margin-bottom: var(--s5);
  position: relative; z-index: 5;
  box-shadow: 0 1px 0 rgba(22,26,20,.18), 0 6px 22px rgba(22,26,20,.16);
}
.banda .wrap { padding: 0 var(--s4); }
.banda .evento {
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase;
  color: #949E92; margin: 0;
}
.banda .pasos { margin: var(--s3) 0 0; }
/* Sobre la tinta el acento NO se puede usar: mide 2,26 y es ilegible. La
 * jerarquia la hace el blanco, que mide 17,62. */
.banda .pasos .barra      { background: #333B32; }
.banda .pasos .done .barra{ background: #4E5A4C; }
.banda .pasos .now  .barra{ background: #FFFFFF; }
.banda .pasos .rotulo     { color: #6E786C; }
.banda .pasos .done .rotulo { color: #949E92; }
.banda .pasos .now  .rotulo { color: #FFFFFF; font-weight: 700; }

/* ── Progress, not a menu ────────────────────────────────────────────
 * Booking is a task with a beginning and an end, so the visitor gets a step
 * bar and one fixed button — never a tab bar and never an ambiguous "back".
 */
/* ── Barra de pasos ──────────────────────────────────────────────────
 * Con el nombre de cada jornada escrito. Los puntos sin etiqueta decían «vas
 * por el primero de tres» y no decían de qué — y eso obligaba a un toggle
 * arriba para orientarse. Con el rótulo, la barra ES la orientación.
 */
.pasos {
  display: flex; gap: var(--s2); margin: 0 0 var(--s5); padding: 0;
  list-style: none;
}
.pasos li { flex: 1; min-width: 0; }
.pasos .barra {
  display: block; height: var(--s1); border-radius: var(--r-pill);
  background: var(--line);
}
.pasos .rotulo {
  /* `--ink-2` y no `--ink-3`: el rotulo va sobre `--bg`, y ahi el terciario
   * mide 3,84 y queda debajo de AA. Es el unico lugar del sistema donde el
   * terciario era texto sobre el fondo, y por eso es el unico que hubo que
   * mover cuando los neutros tomaron temperatura. */
  display: block; margin-top: var(--s1); font-size: 14px; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pasos .done .barra { background: var(--accent-soft); }
.pasos .now  .barra { background: var(--accent); }
.pasos .now  .rotulo { color: var(--accent); font-weight: 700; }
.pasos .done .rotulo { color: var(--ink-2); }

/* ── Atrás ───────────────────────────────────────────────────────────
 * No es un toggle. Un toggle dice «elegí cuál mirar»; el día es una
 * secuencia, y atrás dice «volvé a lo anterior», que es lo único que hace
 * falta. Va arriba del título, donde se busca, y no compite con el botón
 * fijo de abajo.
 */
.btn.atras { padding-left: var(--s3); }
.btn.atras::before {
  content: ''; width: 8px; height: 8px; margin-right: var(--s2);
  border: solid currentColor; border-width: 2px 0 0 2px;
  transform: rotate(-45deg);
}

/* ── Cards ───────────────────────────────────────────────────────────*/
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: var(--s4); margin-bottom: var(--s3);
  box-shadow: var(--sh-1);
  transition: box-shadow var(--t-slow) var(--ease),
              border-color var(--t-fast) var(--ease);
}

/* The experience card is an accordion, and the server decides which one is
 * open — `?abierta=` in the URL, not a `<details>` element. Two reasons, and
 * neither is taste: one experience runs to 48 times in a day, so only the open
 * card's times are BUILT at all, and the state survives a reload, a share and
 * the back button like everything else on this screen.
 */
.exp { padding: 0; overflow: hidden; }

/* El encabezado es una REJILLA con celdas nombradas, no un flex con
 * `space-between`. La diferencia es toda la queja: con `space-between` cada
 * elemento arranca donde termina el anterior, asi que el metadato y la pildora
 * caian en una abscisa distinta por fila. Con celdas, caen en la misma.
 *
 * En S son dos renglones y el titulo se queda con el PRIMERO entero: a 380 px
 * compartir renglon con la pildora le dejaba unos 200 px, y con eso «Entre
 * Luces y Sombras IV» se parte en dos y la fila crece. El nombre de la obra es
 * lo que se esta eligiendo — es lo ultimo que se aprieta.
 *
 * Y en S las alturas NO se igualan, a proposito: una sala de nombre largo
 * envuelve el metadato y esa fila mide un renglon mas. Igualarlas obligaria a
 * reservar el renglon en las 21, y en un telefono el alto es el recurso caro.
 * La columna alineada compra comparar de un vistazo, y de un vistazo se
 * compara en un monitor; en un telefono se scrollea. */
.exp .head {
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 1fr) auto 9px;
  grid-template-areas: "nombre nombre chev" "meta estado chev";
  column-gap: var(--s3); row-gap: var(--s1);
  padding: var(--s4); text-decoration: none; color: inherit;
  min-height: 76px;
}
.exp .head > h2     { grid-area: nombre; }
.exp .head > .meta  { grid-area: meta; }
.exp .head > .estado{ grid-area: estado; justify-self: end; }
.exp .head > h2 { justify-self: start; }
.exp .head::after   { grid-area: chev; }

/* El titulo no puede empujar a nadie: `minmax(0, 1fr)` deja que la celda se
 * encoja, y el clamp corta en dos renglones. Sin el clamp, «Entre Luces y
 * Sombras IV» hace la fila mas alta que la de al lado y se pierde la altura
 * unica, que es el punto. */
.exp .head > h2 {
  margin: 0; padding-right: 0;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
}
.exp .head .pill { margin-top: 0; }

@media (min-width: 700px) {
  /* Un solo renglon, y las dos columnas fijas. Con 1040 de ancho el titulo se
   * queda con unos 600 px, de sobra para el mas largo del programa, asi que
   * aca el clamp baja a un renglon sin cortar nada. */
  .exp .head {
    grid-template-columns: minmax(0, 1fr) var(--col-meta) var(--col-estado) 9px;
    grid-template-areas: "nombre meta estado chev";
    min-height: 68px;
  }
  .exp .head > h2 { -webkit-line-clamp: 1; }
  /* El metadato tambien en un renglon. La columna esta medida contra el
   * programa real —el mas largo es «20 min · Fachada Alianza Francesa», 215
   * px— asi que hoy no corta nada. El clamp es el respaldo para la edicion
   * que traiga una sala de nombre mas largo: degrada a puntos suspensivos en
   * vez de envolver, y envolver movia esa fila 6 px y se llevaba puesta la
   * altura unica de la lista entera. En S no aplica: ahi el metadato tiene su
   * propio renglon y el ancho completo. */
  .exp .head > .meta {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* La pildora llena su columna: mismo ancho, mismo borde izquierdo y mismo
   * borde derecho en las 21 filas. */
  .exp .head > .estado { justify-self: stretch; }
  .exp .head > .estado > .pill { display: flex; width: 100%; justify-content: center; }
}

/* La explicacion de la tomada la otra jornada, ya fuera del encabezado. */
.exp .notes.porque { padding: 0 var(--s4) var(--s4); margin-top: 0; }
a.head { transition: background var(--t-fast) var(--ease); }
a.head:hover { background: var(--bg); }
a.head:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; }
/* The chevron is the only decoration on this screen, and it earns it: without
 * it a card header reads as a link to another page rather than as something
 * that opens in place. */
a.head::after {
  content: ''; align-self: center;
  width: 9px; height: 9px;
  border: solid var(--ink-3); border-width: 0 2px 2px 0;
  transform: rotate(45deg) translate(-2px, -2px);
}
a.head::after { transition: transform var(--t-slow) var(--ease); }
.exp.abierta a.head::after { transform: rotate(225deg) translate(-2px, -2px); }
.exp.abierta { border-color: var(--ink-3); box-shadow: var(--sh-2); }
.exp:hover { box-shadow: var(--sh-2); }
/* Chosen. Same green as presence on the pass, and the same meaning: it is
 * yours. */
.exp.elegida {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent), var(--sh-1);
}
.exp .horarios { padding: 0 var(--s4) var(--s4); }

/* Lo que no se reserva. Mismo esqueleto que una parada del recorrido —hora a
 * la izquierda, qué y dónde a la derecha— pero sin borde de acento y sin nada
 * que tocar: el verde de esta paleta significa «es tuyo», y acá no hay nada
 * que sea de nadie. */
.libre { display: flex; gap: var(--s4); align-items: flex-start; }
.libre .time {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 21px; font-weight: 600; min-width: 4.5ch; color: var(--ink-2);
}
.libre .what { flex: 1; }
.libre .what h2 { margin-bottom: 0; font-size: 16px; }
.exp .horarios .notes { margin-bottom: var(--s2); }

.card h2 { padding-right: var(--s6); }
.meta { color: var(--ink-2); font-size: 14px; margin: 0; }
/* Conditions of use, shown BEFORE choosing. Without them somebody reserves,
 * walks over, cannot do it, and has burned a place that was someone else's. */
.notes { color: var(--ink-2); font-size: 14px; margin: var(--s2) 0 0; }

/* ── Availability pill ───────────────────────────────────────────────
 * Never colour alone: "agotado" has to read in black and white too, so the
 * word carries the meaning and the colour only reinforces it.
 */
/* `nowrap` is not taste. The pill is a flex item next to a title that wraps,
 * so it gets squeezed: at 380px, 14 of the 21 pills on the Friday list broke
 * into two lines, and a 999px radius over two lines stops reading as a pill
 * and reads as a blob. "quedan 36" split across lines is also just worse to
 * read than the count it exists to give. */
.pill {
  display: inline-block; border-radius: var(--r-pill);
  padding: var(--s1) var(--s3); font-size: 14px; font-weight: 700;
  background: var(--accent-soft); color: var(--accent); margin-top: var(--s2);
  white-space: nowrap;
}
.pill.gone { background: var(--warn-soft); color: var(--warn); }
/* One green, one meaning: it is yours. The neutral pill is a count, which
 * is information and not a state. */
.pill { background: var(--bg); color: var(--ink-2); }
.pill.ok { background: var(--accent-soft); color: var(--accent); }
/* Elegida. El tilde, no solo la hora: una píldora con «16:10» se lee como un
 * dato más de la tarjeta, y con el tilde adelante se lee como una decisión
 * tomada — que es lo que hace falta al recorrer nueve tarjetas buscando cuáles
 * ya resolviste. Dibujado, no un emoji: hereda el color y no depende de la
 * fuente del sistema. */
.pill.elegido {
  display: inline-flex; align-items: center; gap: var(--s2);
  background: var(--accent); color: #fff;
}
.pill .tick {
  width: 7px; height: 12px; flex: 0 0 auto;
  border: solid currentColor; border-width: 0 2px 2px 0;
  transform: rotate(45deg) translate(-1px, -1px);
}
.pill .mono { font-variant-numeric: tabular-nums; }

/* ── Timeline ────────────────────────────────────────────────────────*/
.stop { display: flex; gap: var(--s4); align-items: flex-start; }
.stop .time {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 26px; font-weight: 600; min-width: 4.5ch;
}
.stop .what { flex: 1; }
.stop .what h2 { margin-bottom: 0; }

/* Struck through with the reason written out. Exactly two reasons reach this
 * state — Sin lugares and Se pisa — because those are the two the visitor was
 * offered and cannot have. Everything else was never rendered. */
.gone-row { opacity: 0.62; }
.gone-row .time, .gone-row h2 { text-decoration: line-through; }
.reason { color: var(--warn); font-size: 14px; font-weight: 700; }

/* ── Buttons ─────────────────────────────────────────────────────────*/
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap); padding: 0 var(--s4);
  border-radius: var(--r-card); border: 1px solid var(--line);
  background: var(--surface); color: var(--ink); font: inherit;
  text-decoration: none; cursor: pointer;
  box-shadow: var(--sh-1);
  transition: background var(--t-fast) var(--ease),
              box-shadow var(--t-fast) var(--ease),
              transform 90ms var(--ease);
}
/* El foco NO se anima, nunca. Animarlo es la forma mas rapida de volver la
 * navegacion por teclado desagradable. */
.btn:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; transition: none; }
/* `width: 100%` es la regla de S y solo de S: en 380 px, de pie y con el
 * pulgar, el objetivo mas grande posible es el correcto. De ahi para arriba
 * deja de serlo — y estaba acotado unicamente adentro de `.bar` y de
 * `body.admin`, asi que el primario del CUERPO del visitante seguia estirado.
 * Medido: «Empezar» en `/pase` daba 1008x58 en un monitor. Una franja verde de
 * mil pixeles no se lee como un boton, se lee como un fondo. */
.btn.primary {
  min-height: var(--tap-primary); width: 100%;
  background: var(--accent); border-color: var(--accent); color: #fff;
  font-size: 18px; font-weight: 700;
}
@media (min-width: 700px) {
  .btn.primary { width: auto; min-width: 280px; }
  /* Un primario que vive DENTRO de una fila no es el primario de la pantalla:
   * es la accion de esa fila, y hay una por fila. Con 280 el boton «Presente»
   * pasa a ser el objeto mas grande de una lista de veinte personas, y lo que
   * hay que leer ahi es el nombre. */
  .anotados .btn.primary { min-width: 140px; }
}
.btn.primary { box-shadow: var(--sh-accent); }
.btn.primary:hover {
  background: var(--accent-hover); box-shadow: var(--sh-accent-2);
  transform: translateY(-1px);
}
.btn.primary:active { transform: translateY(1px); box-shadow: var(--sh-1); }
.btn.primary[disabled] {
  background: var(--ink-3); border-color: var(--ink-3); cursor: not-allowed;
  box-shadow: none; transform: none;
}
.btn.quiet { min-height: var(--tap); font-size: 14px; }

/* `small` es un TAMAÑO de boton, y hasta el 2026-08-25 no era ninguno.
 *
 * La hoja tenia `.small { font-size: 14px }` —la clase tipografica, que se usa
 * en parrafos— y ninguna regla `.btn.small`. El chequeo 3 de
 * `verificar_plantillas.py` preguntaba «existe .small?» y la respuesta era que
 * si, asi que los tres botones de la columna de acciones de `/admin/turnos`
 * pasaban en verde dibujados con el alto tactil entero y el ancho de su
 * etiqueta, adentro de una tabla que se opera con el mouse.
 *
 * Es la colision mas probable que existe: los nombres que uno le pone a un
 * tamaño de boton —`small`, `wide`, `ok`— son exactamente los que ya viven
 * como utilitarios. El chequeo ahora pide el selector compuesto.
 *
 * El selector esta acotado a donde el argumento vale, y no suelto: 36 esta por
 * DEBAJO de `--tap`, que es el piso tactil del proyecto. Lo unico que lo
 * justifica es que una tabla del panel se opera con el mouse — y eso es una
 * afirmacion sobre esta tabla, no sobre un modificador global. Suelto, la
 * proxima pantalla que escriba `class="btn small"` heredaria un objetivo por
 * debajo del piso sin que nada lo diga, que es exactamente el modo de fallo
 * que esta tanda vino a arreglar. */
body.admin td > form.acciones > .btn.small {
  min-height: var(--h-admin); padding: 0 var(--s3); font-size: 14px;
}

/* La columna de acciones de una tabla: dos ranuras iguales, no dos etiquetas.
 *
 * Cada fila muestra exactamente dos de las tres acciones —la que falta es el
 * estado que el turno ya tiene—, asi que sin rejilla el segundo boton arranca
 * donde termina el primero y el primero mide lo que mide su palabra. Medido
 * sobre las 898 filas antes de esto: 1796 botones, CINCO anchos (74, 84, 117,
 * 157, 159) y TRES izquierdas.
 *
 * Dos columnas y no tres, aunque las acciones sean tres: con tres, cada accion
 * tendria su abscisa fija y la que falta dejaria un hueco, y son 898 huecos.
 * Con dos, la primera ranura es siempre «cambiale el estado» y la segunda
 * siempre «cerralo» en los dos estados que existen casi siempre. */
td > form.acciones {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s2); align-items: stretch;
}
/* `stretch` y no `start`: «Cerrar y cancelar 1» no entra en 144 px de una
 * linea, asi que envuelve y mide 44 donde su compañero mide 36. Estirados,
 * los dos botones de una fila miden siempre lo mismo.
 *
 * Doce filas de 898 quedan 8 px mas altas por eso, y se deja asi: la etiqueta
 * dice cuantas reservas se cancelan, y esconder el numero para ganar una
 * altura uniforme es cambiar la unica informacion que importa por simetria. */
td > form.acciones > .btn { min-width: 0; }
/* Lo que cancela.
 *
 * El pedido fue «mas visible, para no apretar por error», y las dos mitades
 * tiran para lados opuestos: mas visible es mas facil de encontrar Y mas facil
 * de tocar sin querer. Lo que las concilia no es el tamano, es que se vea
 * INCONFUNDIBLE — que en un vistazo no se confunda con nada de la ruta feliz.
 *
 * Por eso ambar de fondo y no solo de borde: el ambar de este sistema significa
 * «se vence o se llena», y una cancelacion es exactamente eso. Y por eso
 * tampoco crece: un boton destructivo grande al lado del primario es la
 * receta del toque equivocado. Se distingue por color, no por peso. */
.btn.danger {
  color: var(--warn); border-color: var(--warn-line);
  background: var(--warn-soft); font-weight: 700;
}
.btn.danger:hover {
  background: #F3E4C6; border-color: var(--warn);
}

/* One fixed CTA at the bottom that always says what happens next and how many
 * are carried. Two buttons of equal weight means neither gets pressed. */
/* Se separa con SOMBRA y no con borde, y la diferencia dice algo distinto: un
 * borde dice «aca termina la lista», una sombra dice «la lista sigue abajo»
 * — que es lo cierto, y es lo que hace falta que entienda alguien que todavia
 * no scrolleo. */
.bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 10;
  background: var(--surface);
  box-shadow: 0 -1px 0 rgba(22,26,20,.07), 0 -8px 24px rgba(22,26,20,.07);
  padding: var(--s3) var(--s4) calc(var(--s3) + env(safe-area-inset-bottom));
}
.bar .wrap { padding: 0; }

/* Notices ride above the button, never as a modal. */
.notice {
  border-radius: var(--r-card); padding: var(--s3) var(--s4);
  margin-bottom: var(--s3); font-size: 16px;
  background: var(--warn-soft); border: 1px solid var(--warn-line); color: var(--warn);
}
.notice.ok { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }

/* ── Fields ──────────────────────────────────────────────────────────*/
label.field { display: block; margin-bottom: var(--s4); }
label.field > span { display: block; margin-bottom: var(--s2); font-weight: 700; }
/* A qualifier inside the label — "(opcional)" — is part of the same line
 * and not a second heading. Without the child combinator above it
 * inherited block display and weight 700, and wrapped onto its own row
 * looking like a field of its own. */
label.field > span span { display: inline; font-weight: 400; }
input[type=text], input[type=email], input[type=password] {
  width: 100%; min-height: var(--tap-primary);
  padding: 0 var(--s3); font: inherit; font-size: 16px;  /* 16 stops iOS zooming */
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-field);
  transition: border-color var(--t-fast) var(--ease),
              box-shadow var(--t-fast) var(--ease);
}
input:hover { border-color: var(--ink-3); }
input:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 1px; transition: none;
}

.btn[aria-disabled="true"] { opacity: 0.45; pointer-events: none; }

/* ── Times sheet ─────────────────────────────────────────────────────
 * Grouped by hour, at most about twenty visible. Never the 238 chips: the
 * visitor is not solving a scheduling puzzle, they are choosing what to see.
 */
.hour { font-family: var(--mono); font-weight: 600; color: var(--ink-2);
        font-size: 14px; margin: var(--s4) 0 var(--s2); }
.times { display: flex; flex-wrap: wrap; gap: var(--s2); margin: 0; padding: 0; list-style: none; }
.times li { flex: 0 0 auto; }
.times a, .times button, .times .off {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  min-height: var(--tap); min-width: 5.5ch; padding: 0 var(--s3);
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-field); border: 1px solid var(--line);
  background: var(--surface); color: var(--ink); font-size: 16px; cursor: pointer;
  transition: border-color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease),
              box-shadow var(--t-fast) var(--ease),
              transform 90ms var(--ease);
}
.times a:hover, .times button:hover { border-color: var(--ink-3); box-shadow: var(--sh-1); }
/* Se hunde al tocarlo. 90ms: mas lento se siente pegajoso con el pulgar. */
.times a:active, .times button:active { transform: scale(.96); box-shadow: none; }
.times a { text-decoration: none; }
.times a:hover { border-color: var(--ink-3); }
.times a:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
/* The one already chosen, and tapping it removes it. Filled rather than
 * outlined so it survives the printed sheet and full sun alike. */
.times .mine {
  background: var(--accent); border-color: var(--accent);
  color: #fff; font-weight: 700;
}
/* `--ink-2` y no el terciario. El terciario media 3,08 contra la tarjeta —
 * debajo del 4,5 que AA pide de un texto normal— y esto se lee a la
 * intemperie, a un brazo de distancia, apurado.
 *
 * Con los neutros nuevos `--ink-2` mide 6,96 sobre la tarjeta, y sigue
 * leyendose apagado: lo que dice "esta no se puede" es la linea que lo cruza,
 * no lo palido del gris. */
.times .off { color: var(--ink-2); text-decoration: line-through; cursor: not-allowed; }

/* ── El pase ─────────────────────────────────────────────────────────
 * La pantalla de salida, y la única que se lee de pie en una cola. Todo acá
 * está ordenado por lo que se busca en ese momento: la hora primero, el lugar
 * después, y el nombre de quien sos arriba de todo porque es lo que el staff
 * pide para encontrarte en la lista impresa.
 */
/* La cabecera es TINTA, no verde, y el cambio arregla un choque que se veia
 * de lejos: el pase verde y el boton primario verde quedaban pegados, dos
 * manchas del mismo color haciendo trabajos distintos. El pase dice «esto es
 * tuyo»; el boton dice «hace esto». Con los dos en verde, ninguno de los dos
 * lo decia.
 *
 * La tinta ademas la emparenta con la banda de arriba —las dos son identidad,
 * no accion— y le devuelve al verde su unico significado. */
.pase-cabecera {
  background: var(--ink); color: #fff;
  border-radius: var(--r-card); padding: var(--s4);
  margin-bottom: var(--s4);
  box-shadow: var(--sh-2);
}
.pase-cabecera .rotulo {
  margin: 0; font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: #949E92;
}
.pase-cabecera .nombre {
  font-family: var(--display);
  margin: var(--s2) 0 0; font-size: 27px; font-weight: 700;
  line-height: 1.12; letter-spacing: -.028em;
}
.pase-cabecera .ticket {
  margin: var(--s2) 0 0; font-family: var(--mono); font-size: 14px; color: #949E92;
}

/* Aire entre la cabecera y la accion. Pegados, el pase y el boton se leian
 * como un solo bloque partido al medio. */
.pase-cabecera + .btn.primary { margin-bottom: var(--s6); }

.jornada {
  margin: var(--s6) 0 var(--s3); padding-bottom: var(--s2);
  border-bottom: 1px solid var(--line); font-size: 15px; color: var(--ink-2);
}

/* La hora es el ancla. Se busca «¿qué tengo a las cuatro?», no «¿dónde está
 * Cruzar?», así que va primero, grande y en cifras tabulares — sin ellas la
 * columna baila de fila en fila. */
.parada { display: flex; gap: var(--s4); align-items: flex-start; }
/* 32 y no 26. La escala tiene el paso y no se estaba usando: esta es LA cifra
 * que alguien busca parado en una cola, y hasta ahora medía lo mismo que el
 * apellido de la cabecera. */
.parada .hora {
  font-variant-numeric: tabular-nums; font-size: 32px; font-weight: 600;
  min-width: 4.5ch; line-height: 1.05; letter-spacing: -.02em;
}
.parada .que h3 { font-family: var(--display); letter-spacing: -.012em; }
.parada .que { flex: 1; }
.parada .que h3 { margin: 0 0 var(--s1); font-size: 16px; line-height: 1.3; }
.parada .aviso { margin: var(--s2) 0; font-size: 14px; color: var(--ink-2); }
/* Sin sombra: es un boton DENTRO de una tarjeta que ya esta elevada, y
 * elevacion sobre elevacion deja de significar nada. La jerarquia adentro de
 * una superficie la hace el borde, no la luz. */
.parada .btn { margin: 0; box-shadow: none; }
.parada .btn:hover { box-shadow: var(--sh-1); }

/* Ya confirmada por el staff. El mismo verde que «es tuyo», porque significa
 * lo mismo: esto ya pasó y es tuyo. */
.parada.presente { border-color: var(--accent); }
.presente-marca { color: var(--accent); font-weight: 700; }

.vacia { margin-bottom: var(--s4); }

/* Las dos acciones de cierre, juntas y al final. Separadas y sueltas parecían
 * dos decisiones; juntas son el pie de la pantalla. */
/* Las dos acciones de cierre son lo MENOS importante de la pantalla, y hasta
 * ahora se veian como dos botones mas: blancos, elevados, del mismo peso que
 * «Cancelar». Sin sombra y con el texto secundario pasan a leerse como el pie
 * que son, sin dejar de ser tocables a 44. */
.pase-acciones {
  display: flex; flex-wrap: wrap; gap: var(--s3);
  margin-top: var(--s6); padding-top: var(--s4);
  border-top: 1px solid var(--line);
}
.pase-acciones form { margin: 0; }
/* Un link que cancela no es un link mas. Los del pie NO cancelan —calendario
 * y salir— y por eso quedan callados; el que cancela vive adentro de su
 * tarjeta y es el unico ambar de la pantalla. */
.pase-acciones .btn {
  box-shadow: none; background: transparent; color: var(--ink-2);
  border-color: var(--line);
}
.pase-acciones .btn:hover {
  background: var(--surface); color: var(--ink); box-shadow: var(--sh-1);
}

/* ── Que nada de esto se mueva si pidieron que no ────────────────────
 * No es un detalle de cortesia aca: la tipografia de este sistema se eligio
 * POR baja vision, asi que la poblacion que activa esta preferencia es
 * exactamente la que este producto dice atender.
 *
 * Las transiciones no se apagan a cero sino a 1ms: a cero, algunos motores
 * no disparan `transitionend` y un estado que espera ese evento se cuelga.
 * Aca no hay ninguno, pero el patron es el correcto y sale igual de barato.
 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Print: the pass on paper ────────────────────────────────────────
 * Pure black on white, no fills and no greys — it comes out of a laser
 * printer and a grey background is toner spent on nothing.
 */
@media print {
  :root { --ink: #000; --bg: #fff; --surface: #fff; --line: #000; --accent: #000; }
  body { background: #fff; padding: 0; }
  .bar, .pasos, .no-print { display: none !important; }
  .card { break-inside: avoid; }
}

/* ── Staff ───────────────────────────────────────────────────────────
 * 15 is the staff floor: between the visitor's 16 and the admin's 14. The
 * condition of use is a phone held in one hand, at night, at a station, with
 * a queue waiting — so the rows are big and the type is not.
 *
 * 52px minimum on anything tappable here, not 44. The floor is higher than
 * anywhere else in the system on purpose: this is the one screen operated
 * while looking at somebody else.
 */
body.staff { font-size: 15px; padding-bottom: var(--s7); }

.jornadas {
  display: flex; gap: var(--s2); list-style: none; margin: 0 0 var(--s4);
  padding: 0;
}
/* This is the staff door: it is used standing up, at night, with a thumb. The
 * pill measured 29px, under the 44 floor and well under the 52 the staff scale
 * asks for. The type stays at pill size; only the box grows. */
/* Las dos jornadas se ven igual de tocables. Antes la activa era una pildora
 * verde y la otra era texto pelado: la que se podia tocar era justamente la
 * que no parecia un control. */
.jornadas a.pill, .jornadas .pill {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap); padding: 0 var(--s4);
  text-decoration: none; font-size: 15px; margin-top: 0;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-2);
  box-shadow: var(--sh-1);
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.jornadas a.pill:hover { color: var(--ink); border-color: var(--ink-3); }
/* La actual va en TINTA y no en verde: el verde de este sistema significa «es
 * tuyo», y una jornada no es de nadie. Es identidad, como la banda. */
.jornadas .pill.jornada-actual {
  background: var(--ink); border-color: var(--ink); color: #fff; font-weight: 700;
}

/* La ocupacion es lo que el staff lee primero despues de la hora, y hasta
 * ahora «0/1» y «1/1» se veian identicos. Lleno va en ambar, que es lo que el
 * ambar significa. */
.turno .cuenta {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-variant-numeric: tabular-nums; font-weight: 700;
  border-radius: var(--r-pill); padding: 2px var(--s3);
  background: var(--accent-soft); color: var(--accent);
}
/* Lleno en ambar, que es lo que el ambar significa en este sistema: «se vence
 * o se lleno». El staff no tiene que restar dos numeros para saberlo. */
.turno .cuenta.lleno { background: var(--warn-soft); color: var(--warn); }
/* Y vacio queda callado: un turno sin nadie no es una alarma, es lo normal a
 * las dos de la tarde. Pintarlo de verde diria que algo esta bien cuando no
 * dice nada. */
.turno .cuenta.vacio { background: transparent; color: var(--ink-2); padding-left: 0; }

.turnos, .anotados { list-style: none; margin: 0; padding: 0; }
.turnos li { margin-bottom: var(--s2); }

/* La fila de la estacion es una REJILLA, por el mismo argumento que la del
 * selector: con la cuenta y el estado apilados adentro de `.what`, la fila
 * media dos, tres o cuatro renglones segun cuantos existieran, y 446 filas
 * daban dos alturas. Con celdas, cada dato cae en la misma abscisa.
 *
 * En S se apila —la estacion se usa parada, con un telefono— y ahi las alturas
 * no se igualan: reservar el renglon del estado en las 446 gasta el alto, que
 * es el recurso caro en un telefono. La comparacion de columna se compra donde
 * se mira una pantalla entera. */
a.turno {
  display: grid; gap: 2px var(--s4); align-items: center;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "time what" "time cuenta" "time estado";
  min-height: 52px; padding: var(--s3) var(--s4);
  box-shadow: var(--sh-1);
  transition: border-color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease),
              box-shadow var(--t-slow) var(--ease);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-card); color: var(--ink); text-decoration: none;
}
a.turno:hover { border-color: var(--ink-3); }
a.turno:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
a.turno .time {
  font-weight: 600; font-size: 18px; font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}
a.turno .time   { grid-area: time; }
a.turno .what   { grid-area: what; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
a.turno .cuenta { grid-area: cuenta; color: var(--ink-2); }
a.turno .estado { grid-area: estado; }

@media (min-width: 700px) {
  /* Un renglon y cuatro columnas. `--col-estado` es el mismo token que usa la
   * lista del visitante: dos pantallas distintas, la misma columna de estado,
   * y por eso es un token y no un numero escrito dos veces. */
  a.turno {
    grid-template-columns: auto minmax(0, 1fr) 220px var(--col-estado);
    grid-template-areas: "time what cuenta estado";
  }
  /* Sin la pildora, la celda sigue existiendo y sigue midiendo lo mismo: es lo
   * que hace que la cuenta de la fila de al lado no se corra. */
  a.turno .estado > .pill { display: flex; width: 100%; justify-content: center; }
}

.anotados li {
  display: flex; gap: var(--s3); align-items: center;
  justify-content: space-between;
  min-height: 52px; padding: var(--s3) var(--s4); margin-bottom: var(--s2);
}
.anotados .quien { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.anotados form { margin: 0; }
.anotados .btn { min-height: 52px; }
/* Presence is the accent, same as everywhere: one green, one meaning —
 * "es tuyo, está confirmado". Here it means the person is standing there. */
.anotados li.presente { border-color: var(--accent); background: var(--accent-soft); }

body.staff input { min-height: 52px; }

/* ── Admin ───────────────────────────────────────────────────────────
 * 14 is the admin density, per the three scales: 14 admin, 15 staff floor,
 * 16 visitor. Same tokens, same faces, same components — the panel is not a
 * second design system, it is this one read closer.
 *
 * The tap targets do NOT shrink with the text. Density belongs to the
 * information; 44px is the floor everywhere, and the one screen that freezes
 * the whole festival is not where to start missing buttons. There is no
 * 36px "mouse-only" row here because there is no table yet — that arrives
 * with the full panel, and so does the height.
 */
body.admin { font-size: 14px; padding-bottom: var(--s7); }
body.admin .wrap { max-width: 560px; }
body.admin input { font-size: 16px; }   /* still 16: iOS zooms below it */

/* Aquí sí aparece el 36 de "solo-mouse admin": la tabla del catálogo se lee
 * sentado, con un mouse, y apretar sus filas no es la tarea. Todo lo que se
 * TOCA —botones, campos, selects— sigue en 44 o más. La densidad va en la
 * información, no en la geometría táctil. */
body.admin .wrap { max-width: 720px; }

/* El admin es la unica de las tres puertas que se usa SENTADO, y era la unica
 * que no aprovechaba la pantalla: 720px clavados en un monitor de 1280, con
 * una tabla de seis columnas scrolleando adentro de una caja angosta mientras
 * media pantalla quedaba vacia.
 *
 * Sube en dos escalones y no en uno: a 1100 la tabla ya entra sin scroll
 * horizontal, y estirar hasta el borde en un monitor ancho da lineas de texto
 * imposibles de seguir. El techo existe por eso, no por prolijidad. */
@media (min-width: 900px)  { body.admin .wrap { max-width: 1020px; } }

/* UN FORMULARIO ES UNA COLUMNA, Y NO SE ESTIRA.
 *
 * Acá vivía la regla contraria: en ancho, `form.card` pasaba a `display: flex`
 * y cada campo tomaba lo que su contenido pedía. Medida, esa regla producía el
 * desorden que venía a evitar. En `/admin/experiencias/1` a 760 px daba
 * **cinco anchos de campo y nueve izquierdas**; a 1528, cuatro rejillas
 * conviviendo —380, 720, 285 y 278—. Y en `/admin/estaciones` dejaba el botón
 * primario en x383 mientras el mismo botón está en x152 o x169 en todas las
 * demás pantallas.
 *
 * La distinción que faltaba: la PÁGINA usa todo el ancho, el formulario no.
 * Una tabla se lee a lo ancho porque sus datos son anchos; un formulario se
 * lee de arriba abajo, y estirarlo a 1240 no agrega información — agrega
 * distancia entre la etiqueta y su campo. El login siempre fue así; el resto
 * del panel dejó de serlo por esta regla.
 *
 * 560 es el techo. El ancho que sobra se gasta poniendo OTRA columna de
 * contenido al lado, no ensanchando ésta. */
/* La excepcion, y no es un caso especial: es la misma regla del otro lado.
 *
 * Una barra de FILTRO no es un formulario de decision — es un control de la
 * tabla que tiene debajo, y una tabla se lee a lo ancho. Apilada en columna
 * ocupaba 260 px de alto para ofrecer dos desplegables y un boton, arriba de
 * una tabla de 1240. Los datos van a lo ancho, y esto es parte de los datos.
 *
 * Se marca en la plantilla con `filtros` y no se infiere del `method="get"`:
 * que una consulta sea GET es una casualidad del protocolo, no una decision de
 * layout, y el dia que un filtro tenga que ser POST el layout no se tiene que
 * enterar. */
@media (min-width: 700px) {
  body.admin form.card.filtros {
    max-width: none;
    display: flex; flex-wrap: wrap; gap: var(--s4); align-items: flex-end;
  }
  body.admin form.card.filtros > label.field {
    margin-bottom: 0; flex: 0 1 260px; min-width: 180px;
  }
  body.admin form.card.filtros > .btn { flex: 0 0 auto; }
}

/* Dos columnas de contenido. `minmax(0, 560px)` con `justify-content: start`
 * y no `560px 560px`: a 1020 —el escalon de abajo del panel— dos columnas
 * fijas suman 1152 y se desbordan. Asi cada una toma lo que hay hasta 560 y
 * nunca mas. */
.dos-columnas { display: block; }
/* `min-width: 0` no es decoracion: un item de grid trae `min-width: auto` de
 * fabrica, que le prohibe encogerse por debajo de su contenido. Con un campo
 * ancho adentro, la columna se planta en el ancho de ese campo y empuja a la
 * otra fuera de la pantalla. El `minmax(0, ...)` del contenedor cubre las
 * pistas; esto cubre a los hijos. */
.dos-columnas > .columna { min-width: 0; }
@media (min-width: 900px) {
  .dos-columnas {
    display: grid; grid-template-columns: repeat(2, minmax(0, 560px));
    justify-content: start; gap: var(--s6); align-items: start;
  }
  body.admin form.card { max-width: 560px; }
  /* Los campos sueltos toman la columna entera: un solo ancho, una sola
   * izquierda. Los que se leen juntos van en un `.fila`, que ya los reparte
   * en partes iguales. */
  body.admin form.card > label.field { max-width: none; }
}

/* Partes iguales, siempre. Con la misma base y el mismo crecimiento, tres
 * campos en una fila miden exactamente lo mismo — que es lo que hace que la
 * fila de abajo empiece donde empieza la de arriba. */
.fila > .field { flex: 1 1 140px; min-width: 0; }
/* El primario del admin deja de ser una franja de 1200px de ancho.
 *
 * `width: 100%` es correcto para el visitante —se aprieta con el pulgar, de
 * pie, apurado— y absurdo sentado frente a un monitor: un boton de mil pixeles
 * deja de leerse como un boton y pasa a leerse como una barra de color. Y con
 * el ancho se va tambien la altura de 58, que existe por el mismo motivo que
 * ya no aplica. */
@media (min-width: 900px) {
  body.admin .btn.primary {
    width: auto; min-width: 240px;
    min-height: var(--tap); font-size: 16px;
  }
}

@media (min-width: 1320px) { body.admin .wrap { max-width: 1240px; } }

/* Los tres interruptores en fila cuando hay ancho. Apilados, la primera y la
 * tercera palanca quedaban a pantalla y media de distancia — y la noche que
 * hacen falta se usan de a dos: se frena todo y se mira si la configuracion
 * quedo congelada. `align-items: start` para que una tarjeta con mas texto no
 * estire a las otras dos. */
.interruptores { display: block; }
@media (min-width: 900px) {
  .interruptores {
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: var(--s4); align-items: start; margin-bottom: var(--s5);
  }
  .interruptores .card { margin-bottom: 0; }
}

.admin-nav {
  display: flex; flex-wrap: wrap; align-items: center;
  /* Dos gaps distintos, y esa es toda la separacion entre familias: --s1
   * adentro de un grupo, --s4 entre grupos. Un separador dibujado ademas del
   * rotulo habria sido decir dos veces lo mismo. */
  gap: var(--s2) var(--s4);
  margin: 0 0 var(--s5); padding-bottom: 0;
  border-bottom: 1px solid var(--line);
}
/* Cada familia es su propia caja: la barra envuelve POR GRUPO y nunca parte
 * uno al medio. En un monitor ancho es una fila de grupos; en uno angosto,
 * tres bloques con su nombre puesto.
 *
 * El rotulo va ARRIBA de sus links y no al lado. Al lado —que fue el primer
 * intento— se leia como un decimo destino apagado: misma linea, misma base,
 * y la separacion entre familias medida en pixeles de gap contra pixeles de
 * gap. Arriba, el agrupamiento se ve sin contar espacios. */
.admin-nav .grupo {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
}
.admin-nav .grupo .links { display: flex; align-items: center; gap: var(--s1); }
/* El rotulo NO es un link y no puede parecerlo: mono, chico y en versalitas,
 * el mismo tratamiento que el rotulo del pase. --ink-2 y no --ink-3 porque
 * esto va sobre --bg, donde --ink-3 mide 3,84 y no pasa AA. */
.admin-nav .rotulo {
  padding: 0 var(--s3);
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-2); white-space: nowrap;
}
/* Los dos sueltos —Panel y Ajustes— se alinean con los links de los grupos y
 * no con los rotulos, o quedarian medio renglon mas arriba. */
.admin-nav > a { align-self: flex-end; }
.admin-nav a {
  padding: var(--s2) var(--s3); min-height: 36px;
  display: inline-flex; align-items: center;
  border-radius: var(--r-field) var(--r-field) 0 0;
  border-bottom: 2px solid transparent;
  text-decoration: none; color: var(--ink-2);
  transition: color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.admin-nav a:hover { background: var(--surface); color: var(--ink); }
/* La encendida se marca con una linea abajo y no con un relleno: un relleno
 * la convierte en un boton, y no lo es — ya estas ahi. La linea dice «este
 * es el lugar», que es lo unico que hace falta. */
.admin-nav a.on {
  color: var(--ink); font-weight: 700;
  border-bottom-color: var(--ink);
}

/* The table scrolls inside its own box, not the page. Without this every admin
 * table pushed the document past the viewport — `/admin/turnos` measured 558px
 * of scrollWidth on a 380px screen — and the whole page slid sideways, which
 * takes the nav and the headings with it. The wrapper is a div in the template
 * because `overflow` on a `table` needs `display: block`, and that throws away
 * the column sizing that makes it a table in the first place. */
/* `max-height` mas `sticky` en el `th`: una tabla larga sin encabezado a la
 * vista es una tabla que hay que leer de memoria, y `/admin/turnos` tiene 898
 * filas. El alto maximo es lo que hace que el sticky tenga contra que pegarse
 * — sin el, la caja crece con el contenido y no scrollea nunca por dentro. */
.tabla-scroll {
  overflow-x: auto; margin-bottom: var(--s5);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-card); box-shadow: var(--sh-1);
}
.tabla-scroll > .tabla { margin-bottom: 0; }
@media (min-width: 900px) {
  .tabla-scroll { max-height: 70vh; overflow-y: auto; }
  .tabla-scroll .tabla th {
    position: sticky; top: 0; z-index: 1;
    background: var(--surface); box-shadow: 0 1px 0 var(--line);
  }
}
/* La fila responde al mouse. En seis columnas, saber en cual estas parado es
 * la mitad del trabajo. */
.tabla tbody tr { transition: background var(--t-fast) var(--ease); }
.tabla tbody tr:hover { background: var(--accent-soft); }
.tabla { width: 100%; border-collapse: collapse; margin-bottom: var(--s5); }
.tabla th {
  text-align: left; padding: var(--s2); border-bottom: 1px solid var(--line);
  font-size: 14px; font-weight: 400; color: var(--ink-2);
}
/* El encabezado de COLUMNA habla el mismo idioma que los rotulos de la barra y
 * del pase: mono, chico, en versalitas. No es decoracion — es lo que lo separa
 * del contenido sin ponerle una linea mas a una tabla que ya tiene una por
 * fila.
 *
 * `thead th` y no `th` pelado, y la diferencia no es cosmetica: en
 * `admin_registro.html` conviven DOS tablas, y la de arriba usa `th` como
 * etiqueta de FILA —«Ticket», «Direccion», «Cargada»— sin `thead`. Ese `th`
 * no encabeza una columna, encabeza un valor, y ponerle versalitas mono de 11
 * al lado de un dato de 16 lo convierte en otra cosa. */
.tabla thead th {
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase; white-space: nowrap;
}
/* La etiqueta de fila toma lo justo y le deja el ancho al valor. */
.tabla tbody th { width: 1%; white-space: nowrap; padding-right: var(--s4); }
.tabla td {
  padding: var(--s3) var(--s2); border-bottom: 1px solid var(--line);
  vertical-align: middle; min-height: 36px;
}
.tabla td.mono, .tabla th { font-variant-numeric: tabular-nums; }
.tabla td.mono { font-family: var(--mono); }

/* Las columnas de numeros van a la DERECHA y no se parten.
 *
 * Alineados a la izquierda de una celda ancha, 74 y 120 arrancan en el mismo
 * pixel y terminan en distintos, asi que compararlos exige leerlos uno por uno.
 * A la derecha, las unidades se apilan sobre las unidades y la columna se lee
 * de un vistazo — que es la unica razon por la que estos numeros estan en una
 * tabla y no en una oracion.
 *
 * `width: 1%` con `nowrap` hace que cada una tome lo justo y le deje el resto
 * del ancho a la primera columna, que es la que tiene texto de verdad. */
.tabla th.num, .tabla td.num {
  text-align: right; width: 1%; white-space: nowrap;
  padding-left: var(--s4);
}

/* Lleno. Sobre tarjeta el ambar mide 7,2 — pasa AAA — y el peso lo respalda,
 * porque el color solo nunca alcanza. */
.tabla td.lleno { color: var(--warn); font-weight: 700; }

/* El nombre y la clave dejan de pesar lo mismo. La clave es para tipearla en
 * el YAML; el nombre es como se llama la obra. Se veian identicos. */
.tabla .nombre { font-size: 16px; }
.tabla .clave {
  font-family: var(--mono); font-size: 12px; color: var(--ink-3);
  margin-left: var(--s2);
}
.tabla form { margin: 0; }
/* Apagada, no escondida: una fila que no se muestra al visitante sigue
 * siendo una fila que el admin tiene que poder ver y volver a encender. */
.tabla tr.apagada td { opacity: 0.55; }

/* Campos en línea. Se apilan solos abajo de 560, que es donde el panel deja
 * de ser una tabla y pasa a ser un teléfono. */
.fila { display: flex; gap: var(--s3); flex-wrap: wrap; }
.fila > .field { flex: 1 1 140px; margin-bottom: var(--s3); }
.ventana { padding-bottom: var(--s3); border-bottom: 1px dashed var(--line); }

/* ── Los bloques del formulario del admin ────────────────────────────
 * Trece campos en una columna eran trece decisiones sin orden: el cupo a la
 * misma altura que la descripcion, y nada diciendo cuales se miran juntas.
 *
 * `fieldset` y no `div` porque es exactamente eso —campos que se contestan
 * juntos, con su nombre— y porque el lector de pantalla anuncia el `legend`
 * al entrar a cada campo. El agrupamiento no es solo visual. */
.bloque {
  border: 0; padding: 0; margin: 0 0 var(--s5);
  /* Un `fieldset` trae `min-width: min-content` de fabrica y eso le impide
   * encogerse adentro de un contenedor flex. Sin esto, el bloque estira el
   * formulario hasta el ancho de su campo mas largo. */
  min-width: 0;
}
.bloque > legend {
  padding: 0; margin-bottom: var(--s3);
  font-family: var(--display); font-size: 18px; font-weight: 700;
  letter-spacing: -.015em; color: var(--ink);
}
/* Sin linea entre bloques, y esto es un arreglo de algo que se vio: un
 * `border-top` sobre el `fieldset` sale dibujado A LA ALTURA del `legend`,
 * porque el navegador reserva ese hueco para el titulo. Quedaba una raya
 * cruzando cada encabezado menos el primero. El aire separa igual y no
 * depende de una particularidad del `fieldset`. */
.bloque + .bloque { margin-top: var(--s6); }
.bloque .fila { margin-bottom: 0; }
/* Sin topes propios: el tope es la columna de 560, y un campo mide lo que le
 * toque de ella. Los `.ancho` toman la fila entera. Antes cada uno traía su
 * numero —380 y 720— y eso era dos de las cuatro rejillas que convivian. */
.bloque .fila > .field.ancho { flex: 1 1 100%; }
.bloque > p.small { margin: var(--s1) 0 var(--s3); }

/* Una ventana son cuatro campos, y en una columna de 560 puestos en fila dan
 * 120 px cada uno — un desplegable de 120 que tiene que decir «Viernes 28».
 * Dos por renglon dan 256, y el reparto no es arbitrario: arriba QUE dia y
 * DONDE, abajo las dos horas, que son el par que se lee junto. */
@media (min-width: 700px) {
  .ventana {
    display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--s3);
    grid-template-areas: "jornada lugar" "abre cierra";
  }
  .ventana > .w-jornada { grid-area: jornada; }
  .ventana > .w-abre    { grid-area: abre; }
  .ventana > .w-cierra  { grid-area: cierra; }
  .ventana > .w-lugar   { grid-area: lugar; }
}

/* ── La fila que todavia no es una ventana ───────────────────────────
 * El rotulo mas el borde punteado son la afordancia que faltaba: agregar una
 * ventana se podia desde siempre, pero una fila en blanco identica a las
 * llenas no se lee como «escribi aca». */
h3.agregar {
  margin: var(--s5) 0 var(--s2);
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
}
.ventana.vacia {
  border: 1px dashed var(--line); border-radius: var(--r-field);
  padding: var(--s3) var(--s3) 0; background: var(--bg);
  /* El padding y el borde se compensan hacia AFUERA. Sin esto la fila vacia
   * corria sus celdas 13 px a la derecha y las hacia 13 mas angostas que las
   * llenas de arriba —medido: 278 contra 285— asi que la unica fila que hay
   * que llenar era la unica que no alineaba con ninguna. Los 13 caben adentro
   * del padding de la tarjeta, que es 16. */
  margin: 0 calc(-1 * (var(--s3) + 1px)) var(--s2);
}

label.check {
  display: flex; gap: var(--s3); align-items: flex-start;
  min-height: var(--tap); margin-bottom: var(--s3); cursor: pointer;
}
label.check input { width: 22px; height: 22px; margin-top: 2px; flex: 0 0 auto; }
/* Una casilla que acompaña a un campo de búsqueda se queda EN la fila: la
 * barra entera es una sola pregunta, y partirla en tres renglones la vuelve
 * tres. Es la excepción a que todo `label.check` del admin ocupe el ancho. */
body.admin form.card > label.check.inline {
  flex: 0 0 auto; margin-bottom: 0; align-items: center;
}

select, textarea {
  width: 100%; min-height: var(--tap); padding: var(--s2) var(--s3);
  font: inherit; font-size: 16px;   /* 16 sigue frenando el zoom de iOS */
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-field);
}
textarea { min-height: 180px; font-family: var(--mono); line-height: 1.45; }
select:focus-visible, textarea:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 1px;
}

.resumen {
  display: flex; gap: var(--s5); flex-wrap: wrap;
  margin: 0 0 var(--s4); font-size: 18px;
}
.resumen strong { font-variant-numeric: tabular-nums; }

/* ── El preview de la grilla ─────────────────────────────────────────
 * La tarjeta donde se decide. Se levanta más que una tarjeta cualquiera —
 * `--sh-2` y no `--sh-1`— porque abajo de ella hay 45 filas de tabla y tiene
 * que ganarles la primera mirada.
 *
 * El borde es TINTA y no verde: el verde de este sistema significa «es tuyo»
 * y acá no hay nada de nadie todavía. Ese es justo el punto de la pantalla. */
.card.preview {
  border-color: var(--ink-3);
  box-shadow: var(--sh-2);
  margin-bottom: var(--s6);
}
.card.preview .rotulo {
  margin: 0 0 var(--s3);
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2);
}
.card.preview form { margin: 0; }
.card.preview .notice { margin: var(--s4) 0 0; }
.card.preview .notice + form { margin-top: var(--s4); }
.card.preview .small.muted { margin: var(--s2) 0 0; }

/* Las cifras del preview. Tres números y sus nombres, no una oración con
 * números adentro: es la información que decide si se aprieta, y leerla tiene
 * que costar un vistazo.
 *
 * `--display` con cifras tabulares y no `--mono`: en este sistema el mono es
 * para horarios y códigos —cosas que se comparan columna contra columna— y un
 * conteo no es ninguna de las dos. Las tabulares alcanzan para que 898 y 626
 * midan lo mismo. */
.cifras {
  display: flex; flex-wrap: wrap; gap: var(--s5);
  margin: 0 0 var(--s4);
}
.cifras > div {
  display: flex; flex-direction: column; gap: 2px;
  padding-right: var(--s5); border-right: 1px solid var(--line);
}
.cifras > div:last-child { border-right: 0; padding-right: 0; }
.cifras strong {
  font-family: var(--display); font-size: 32px; font-weight: 700;
  line-height: 1.05; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.cifras span { font-size: 14px; color: var(--ink-2); }

/* El token, la única vez que existe en claro. Se rompe donde sea: es para
 * copiarlo, no para que quede lindo. */
.link-nuevo { word-break: break-all; font-size: 14px; margin: var(--s2) 0; }
.borrar { margin-top: var(--s6); }

/* Los tres interruptores a un mismo ancho. Median 148, 109 y 192 —cada uno lo
 * que mide su texto— y son tres opciones del mismo rango: medir distinto los
 * jerarquiza sin que nadie lo haya decidido. */
body.admin .interruptores .btn { min-width: 200px; }

.interruptor h2 { margin-bottom: var(--s2); }
.interruptor .estado {
  display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
  margin: var(--s3) 0 var(--s4);
}
.interruptor form { margin: 0; }
.salir { margin-top: var(--s6); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
