/* ==========================================================================
   China Day Salta 2026 — hoja de estilo única.
   Orden 02 (punto de control): tipografía (T4) + tokens/base + header + hero (T7).
   El resto de las secciones se maqueta en la orden 03.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Montserrat — variable, autohospedada. SIL Open Font License 1.1
   (ver assets/fonts/OFL.txt). Sin CDN de Google Fonts.
   Un solo archivo variable por subset; el peso es el eje `wght` (100–900).
   `latin` cubre español e inglés (incluido el hero); `latin-ext` se descarga
   sólo si aparece un carácter de su rango (unicode-range), lo que en este
   sitio prácticamente no ocurre.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/assets/fonts/Montserrat-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+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/assets/fonts/Montserrat-latinext.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;
}

/* --------------------------------------------------------------------------
   Tokens (Tarea 3). Colores medidos de la invitación salvo los marcados como
   decisión de diseño. Ver README (sección "Paleta") para el origen de cada uno.
   -------------------------------------------------------------------------- */
:root {
  /* Paleta */
  --rojo: #C62620;         /* medido: lockup CHINA / 2026 */
  --tinta: #000000;        /* medido: negro del lockup */
  --gris: #545454;         /* medido: bajada */
  --gris-suave: #767676;   /* diseño: gris tenue para notas/UI (AA: 4.5:1 sobre papel) */
  --papel: #FFFFFF;        /* medido: fondo */
  --nieve: #F5F5F4;        /* diseño: fondo de tarjetas/chips */
  --linea: #E4E2E0;        /* diseño: bordes */
  --granate: #922941;      /* medido: franja lateral */
  --coral: #FC915D;        /* medido: nudos */
  --resaltado: #FFF3A0;    /* diseño (Orden 14): amarillo marcador para "a confirmar" */

  /* Forma */
  --radio: 14px;

  /* Escala tipográfica fluida (una sola familia: Montserrat) */
  --fs-nota: clamp(0.78rem, 0.74rem + 0.2vw, 0.86rem);
  --fs-base: clamp(0.95rem, 0.9rem + 0.3vw, 1.05rem);
  --fs-md:   clamp(1.05rem, 0.98rem + 0.5vw, 1.25rem);
  --fs-lg:   clamp(1.3rem, 1.1rem + 1vw, 1.75rem);
  --fs-xl:   clamp(1.8rem, 1.3rem + 2.4vw, 3rem);
  /* Tope del wordmark del hero: 4.5rem, aprobado en Orden 07 (T1). */
  --fs-2xl:  clamp(2.6rem, 1.7rem + 4.4vw, 4.5rem);

  /* Ritmo */
  --wrap: 72rem;
  --pad-inline: clamp(1rem, 0.6rem + 2vw, 2.5rem);

  /* Alto compartido por el CTA y el logo del header (Orden 17): el pedido era
     que las dos piezas midan lo mismo, así que el valor vive en un solo lugar.
     2.8rem = 44.8px, el alto natural que tenía el botón. */
  --cta-h: 2.8rem;

  /* Navbar fijo (Orden 06, T2): alto real de .site-header (min-height 4.5rem
     de .site-header__inner, box-sizing border-box, + 1px de border-bottom).
     Lo usan el padding-top del hero y el scroll-margin-top de las anclas. */
  --header-h: calc(4.5rem + 1px);

  /* Banda de nudos del hero, variante A (Orden 06, T1): ancho fijo (no baja
     de 96px de motivo mínimo en ningún breakpoint de este sitio) + aire antes
     del borde de la ventana. background-size en enteros: alto = ancho ×
     1.55148 (relación real del master 845×1311, Orden 05), 144px -> 223px. */
  --banda-w: 144px;
  --banda-h: 223px;
  --banda-aire: var(--pad-inline);

  /* Cola de la guarda (Orden 19): cuánto baja la banda POR DEBAJO del hero, y
     —es la misma medida— cuánto dura la disolución. Que sean el mismo valor no
     es economía de variables, es la definición del cambio: la disolución
     arranca EXACTAMENTE en el borde inferior del hero, así que de ahí para
     arriba la banda se ve igual que antes, a plena tinta, y todo lo que se
     agregó es desvanecido puro. Con una disolución más larga que la cola pasaba
     lo contrario: la parte a plena tinta terminaba más arriba que el corte
     viejo y la guarda se leía MÁS corta, justo al revés de lo pedido.

     El valor es el margen que hay antes de que algún píxel de "Qué es" entre en
     el carril de 144px de la banda. Medido sobre el render en los tres idiomas:
     de 900px para arriba hay 452px libres o más (el más ajustado es el ZH, que
     compone más corto y sube las Ediciones); entre 768 y 899px baja a 134px,
     porque ahí la bajada de la sección llega hasta el carril. Por eso son dos
     valores y no uno, los dos por debajo de lo medido:
       ≥900px   1,5 tiles = 334px  (la banda crece de 900 a 1234px en un
                                    escritorio de 900px de alto: +37%)
       768–899  120px
     En tiles y no en px redondos para que la banda siga contando motivos
     enteros: 1,5 tiles deja el último nudo disuelto del todo y el anterior a
     media tinta, que es lo que se lee como "se disuelve" y no como "tiene el
     borde difuminado". */
  --banda-cola: 120px;

  font-family: 'Montserrat', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  color: var(--tinta);
  background: var(--papel);
}

/* --------------------------------------------------------------------------
   Reset mínimo + base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

/* Guarda para el full-bleed con 100vw (ver .hero__foto): evita scroll horizontal
   sin crear contenedor de scroll (no rompe position: sticky). */
html { overflow-x: clip; }

body {
  font-size: var(--fs-base);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, picture, svg { display: block; max-width: 100%; }
/* `ol` además de `ul`: el reset sólo cubría `ul` y el <ol class="timeline"> se
   quedaba con el padding-inline-start de 40px del navegador. Eso metía todo el
   carril del Programa 40px adentro del wrap, así que la línea no caía en la
   misma vertical que el título de la sección y las tarjetas arrancaban con un
   escalón que no comunicaba nada. Cualquier <ol> futuro tenía el mismo problema
   esperando. */
ul, ol { list-style: none; padding: 0; }
a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--rojo);
  outline-offset: 2px;
  border-radius: 3px;
}

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

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--pad-inline);
}

/* Salto al contenido (accesibilidad) */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--tinta);
  color: var(--papel);
  padding: 0.6rem 1rem;
  border-radius: 0 0 var(--radio) 0;
}
.skip-link:focus { left: 0; }

/* --------------------------------------------------------------------------
   Wordmark del evento (China Day Salta 2026). CHINA/2026 en rojo, DAY/SALTA en
   tinta, como el lockup de la invitación.
   -------------------------------------------------------------------------- */
.wordmark {
  /* Jerarquía del lockup (Orden 17): "China Day" un 15 % más grande que
     "Salta 2026". Un solo ratio para header y hero — el tamaño base lo pone
     cada contexto (.brand .wordmark / .wordmark--hero) y las dos líneas se
     derivan de él, así que no hay valores sueltos por breakpoint. */
  --lockup-ratio: 1.15;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 0.95;
  color: var(--tinta);
}
.wordmark__china, .wordmark__anio { color: var(--rojo); }
.wordmark__day, .wordmark__salta { color: var(--tinta); }
.wordmark__china, .wordmark__day { font-size: calc(1em * var(--lockup-ratio)); }
/* Las cuatro piezas van separadas con un espacio real entre <span> (Orden 17 para
   "China Day", Orden 18 para "Salta 2026"), no con margin: en el hero, a 4.5rem,
   un margen en `em` se abría mucho más que en el header y las piezas dejaban de
   leerse como una unidad.
   Nota: la invitación va en MAYÚSCULAS y con "SALTA2026" pegado; tanto el Title
   Case como la separación son decisiones deliberadas (leen mejor en pantalla),
   no heredadas. */

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */
/* Fijo y opaco desde el primer render (Orden 06, T2): decisión de la clienta
   para que no haya colisión visual entre el header y la banda de nudos del
   hero (ni con ningún otro elemento). Sin estado transparente que resolver:
   menos código, menos superficie de bug. */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--papel);
  border-bottom: 1px solid var(--linea);
}
.site-header__inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  min-height: 4.5rem;
  padding-block: 0.6rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  margin-right: auto;   /* empuja nav/switcher/CTA a la derecha */
}
/* Logo y CTA del header a la misma altura (Orden 17). Van atados a una variable
   y no a dos valores sueltos: el alto natural del botón depende de --fs-base,
   que es un clamp, así que un número fijo en el logo dejaba de coincidir al
   cambiar el viewport. Fijando el alto del botón del header, los dos coinciden
   por construcción. Sólo el CTA del header: los demás botones (hero, Programa,
   Enviar) siguen con su alto natural.
   El PNG del logo se recortó en esta misma orden (359×160 → 309×108, sin el
   ~22 % de transparencia que traía). Por eso `height` ahora limita la marca y no
   el lienzo: antes, a 2.75rem de caja, la marca medía 29,7 px. */
.brand__logo { height: var(--cta-h); width: auto; }
.site-header .btn--cta { height: var(--cta-h); }
.brand .wordmark { font-size: 1.15rem; white-space: nowrap; }
/* El lockup del header va en DOS BLOQUES de color, no alternado: "China Day"
   entero en rojo y "Salta 2026" entero en negro. El corte de color cae entre Day
   y Salta, que es justo donde el lockup se parte conceptualmente —el nombre del
   evento y su edición—, así que el color hace el trabajo que en el hero hace el
   salto de renglón.
   El hero conserva el esquema de la invitación (China rojo · Day negro / Salta
   negro · 2026 rojo), que sólo funciona en dos líneas: puesto en una sola, deja
   Day y Salta los dos en negro y el par se lee como un bloque pegado, mientras
   que China y 2026 quedan en rojo en los dos extremos, sin relación entre sí.
   Se reasignan sólo Day y 2026; China ya era rojo y Salta ya era negro en el
   esquema base, así que heredan. */
.brand .wordmark__day { color: var(--rojo); }
.brand .wordmark__anio { color: var(--tinta); }

/* Navegación (desktop: en línea) */
.site-nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}
.site-nav__list {
  display: flex;
  align-items: center;
  gap: clamp(0.8rem, 0.4rem + 1.2vw, 1.6rem);
  font-weight: 600;
}
.site-nav__list a { padding: 0.4rem 0; border-bottom: 2px solid transparent; }
.site-nav__list a:hover { color: var(--rojo); border-bottom-color: var(--rojo); }

/* Switcher de idioma — con aire propio, separado del nav */
.switcher {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-left: 1.25rem;
  border-left: 1px solid var(--linea);
  font-weight: 600;
  font-size: var(--fs-nota);
}
.switcher__lang { color: var(--gris); }
.switcher__lang:hover { color: var(--rojo); }
.switcher__lang--current { color: var(--tinta); text-decoration: underline; text-underline-offset: 3px; }

/* Botón / CTA */
/* border/font/cursor: reset explícito porque .btn también se usa en <button>
   (T6b, Orden 07) — sin esto, el botón "Enviar" hereda el borde grueso por
   defecto del navegador (los <a> no lo tienen, por eso no se veía en "Contacto"). */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-weight: 700;
  border: none;
  border-radius: var(--radio);
  padding: 0.6rem 1.2rem;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
/* Contraste AA: #C62620 sobre blanco ≈ 5.68:1, cumple AA para texto normal
   (>=4.5:1), con margen. */
.btn--cta { background: var(--rojo); color: var(--papel); }
.btn--cta:hover { background: #a81f1a; }

/* Botón hamburguesa — oculto en desktop */
.nav-toggle {
  display: none;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 1px solid var(--linea);
  border-radius: 10px;
  background: var(--papel);
  cursor: pointer;
}
.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  display: block;
  width: 20px;
  height: 2px;
  background: var(--tinta);
  margin-inline: auto;
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.nav-toggle__bars { position: relative; }
.nav-toggle__bars::before, .nav-toggle__bars::after { content: ''; position: absolute; left: 0; }
.nav-toggle__bars::before { top: -6px; }
.nav-toggle__bars::after { top: 6px; }
/* Estado abierto -> X */
.nav-toggle[aria-expanded='true'] .nav-toggle__bars { background: transparent; }
.nav-toggle[aria-expanded='true'] .nav-toggle__bars::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded='true'] .nav-toggle__bars::after { transform: translateY(-6px) rotate(-45deg); }

/* --------------------------------------------------------------------------
   Header responsive: menú móvil real por debajo de 980px

   Estuvo en 899px y ese número estaba mal medido: el header de escritorio no
   entra en 900px. Midiendo el ancho a partir del cual la barra cierra sin pisar
   el padding del wrap —logo + nav + switcher + CTA— da 934px en ES, 964px en EN
   y 880px en ZH (que compone mucho más corto). O sea que entre 900 y 964px el
   CTA "Inscribite" se salía del wrap y quedaba RECORTADO por el
   `html { overflow-x: clip }`, sin aparecer como scroll horizontal. Justo el
   botón que es el objetivo del sitio.

   Se corrige moviendo el umbral a 980px (16px de margen sobre el idioma más
   ajustado) en vez de apretar los espaciados en ese tramo. Las dos opciones
   tocan el diseño; esta lo toca menos:

   - Apretar daba para recuperar los 58px que faltaban en EN, pero sólo
     recortando cuatro cosas a la vez (el gap de la barra, el del nav, el
     padding del switcher y la separación entre ítems, que caería a ~9px).
     Eso es inventar un estado intermedio apretado que nadie diseñó, y que se
     vuelve a romper con la próxima etiqueta de nav que sea un poco más larga.
   - Mover el umbral no cambia ningún estado visual: entre 900 y 979px se usa el
     menú móvil, que ya existe, ya está diseñado y ya se usa 1px más abajo. No
     se inventa nada; se corrige un número que estaba por debajo de lo que el
     layout necesita.

   Ojo: hay otro breakpoint en 900px (`--banda-cola`, la cola de la guarda de
   nudos). No tiene nada que ver con este — mide el aire del carril de la banda
   en la sección de abajo, no el ancho del header. Que coincidieran era
   casualidad; no unificarlos.

   app.js escucha `(min-width: 980px)` para resetear el menú al pasar a
   escritorio. Los dos números tienen que moverse JUNTOS.
   -------------------------------------------------------------------------- */
@media (max-width: 979px) {
  .nav-toggle { display: block; }

  .site-nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--papel);
    border-bottom: 1px solid var(--linea);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.08);
    padding: 0.5rem var(--pad-inline) 1.25rem;
  }
  /* El estado vive en el aria-expanded del botón (lo togglea app.js) */
  .nav-toggle[aria-expanded='true'] + .site-nav { display: flex; }

  .site-nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .site-nav__list li { border-bottom: 1px solid var(--linea); }
  .site-nav__list a { display: block; padding: 0.9rem 0; border-bottom: none; }

  .switcher { padding-left: 0; border-left: none; padding-block: 1rem; gap: 1rem; }

  /* El CTA ya no está dentro del menú desplegable, así que va en la barra, a la
     izquierda de la hamburguesa. En el DOM está después del <nav> —para no
     romper el selector de hermano adyacente que abre el menú—, así que el orden
     acá es visual, no estructural. */
  .nav-toggle { order: 1; }
  /* La barra en móvil lleva logo + CTA + hamburguesa en 320px de ancho, así que
     el aire de escritorio no entra: gap más corto y botón menos ancho. */
  .site-header__inner { gap: 0.6rem; }
  .site-header .btn--cta { padding-inline: 0.85rem; }
}

/* En pantallas muy chicas, el logo lleva la marca solo (evita que el wordmark
   apriete contra la hamburguesa). El lockup grande sigue en el hero. */
@media (max-width: 559px) {
  .brand .wordmark { display: none; }
}

/* --------------------------------------------------------------------------
   Hero — Variante A (Orden 06): fiel a la invitación, sin foto. Fondo blanco
   + banda vertical de nudos pegada al borde derecho. El navbar es fijo (T2):
   el padding-top de .hero__text compensa su alto (--header-h) para que el
   wordmark no arranque tapado. El hero ocupa un viewport completo (antes lo
   definía el alto de la foto) para entrar íntegro en pantalla de laptop.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100vh;   /* fallback, declarado ANTES que svh */
  min-height: 100svh;
  /* Orden 19: era `overflow: hidden`, que recortaba la banda de nudos justo en
     el borde inferior del hero — el corte tajante que marcó la clienta. Ahora la
     banda baja un poco más y se disuelve (ver .banda-nudos), así que el eje
     vertical tiene que dejarla salir.
     `clip` en X y `visible` en Y es la única combinación que el navegador
     respeta tal cual: con `hidden` en un eje y `visible` en el otro, el
     `visible` se convierte en `auto` y aparecería un scroll. El eje horizontal
     igual está cubierto por `html { overflow-x: clip }`. */
  overflow-x: clip;
  overflow-y: visible;
  background: var(--papel);
}

.hero__text {
  position: relative;
  z-index: 1;
  padding-top: calc(var(--header-h) + clamp(1rem, 0.6rem + 2vw, 2rem));
  padding-bottom: clamp(1.5rem, 0.8rem + 2vw, 2.75rem);
  padding-right: calc(var(--banda-w) + var(--banda-aire) * 2);
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.wordmark--hero {
  font-size: var(--fs-2xl);
  margin-bottom: clamp(0.75rem, 0.4rem + 1.5vw, 1.5rem);
}
.wordmark--hero .wordmark__linea { display: block; }

.hero__consigna {
  font-size: var(--fs-lg);
  font-weight: 700;
  max-width: 34ch;
  margin-bottom: 1.1rem;
  /* Viuda tipográfica (Orden 07b, T1): "China." quedaba solo en el último
     renglón. text-wrap:pretty optimiza las últimas líneas de un párrafo para
     evitar viudas/ríos (no es lo mismo que balance, que reparte TODO el
     bloque parejo). Soporte parcial (Chrome sí; Safari/Firefox en adopción):
     donde no está soportado, no hace nada — progressive enhancement. */
  text-wrap: pretty;
}

.hero__cuando { margin-bottom: 1.35rem; }
.hero__cuando-titulo {
  font-size: var(--fs-nota);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gris);
  font-weight: 700;
  margin-bottom: 0.6rem;
}
.cuando-list { display: grid; gap: 0.4rem; }
.cuando-list li { display: flex; flex-wrap: wrap; gap: 0.15rem 0.6rem; align-items: baseline; }
.cuando-list__fecha { font-weight: 700; }
.cuando-list__lugar { color: var(--gris); }
.cuando-list__detalle { color: var(--gris-suave); font-size: var(--fs-nota); font-style: italic; }
/* La franja horaria en el hero (Orden 19). Sale del mismo sedes.items[] que la
   sección Sede —un solo dato, no una copia—, y hasta ahora estaba en el JSON sin
   renderizarse. Con el Programa reducido a título + horario y Oradores fuera de
   la vista, el hero era el único lugar donde el visitante no se enteraba de
   cuánto dura la jornada.
   Va en cuerpo normal y color --gris, como .cuando-list__lugar: es un dato del
   evento, no una acotación (--gris-suave en itálica es el registro de "ingreso
   general"). La fecha sigue siendo la única pieza en negrita de la línea.
   El separador es el gap del flex, igual que entre las demás piezas: en móvil la
   línea envuelve y un "·" escrito duro quedaría colgando a fin de renglón. */
.cuando-list__horario { color: var(--gris); }

.hero__cta-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.85rem 1.25rem; }
.hero__nota {
  font-size: var(--fs-nota);
  color: var(--gris-suave);
  font-style: italic;
  max-width: 26rem;
}

/* Banda de nudos — SIEMPRE aria-hidden, NUNCA <img>. background-size con alto
   ENTERO explícito (nunca auto), par real del tile (144x223, ver --banda-w/
   --banda-h en :root). Ancho de motivo mínimo 96px (regla vigente): a
   --banda-w:144px no baja de eso nunca — la banda no se achica, se muestra a
   144px o no se muestra (ver la media query de abajo: en móvil no va). */
.banda-nudos {
  position: absolute;
  top: 0;
  /* La banda ya no termina donde termina el hero: baja --banda-cola por debajo
     y ahí se disuelve. El corte antes caía exactamente sobre la costura entre el
     hero y "Qué es", que es lo que lo hacía leer como un tajo — una guarda que
     se corta justo en un borde de sección parece un error de recorte, no una
     decisión. Cruzando el borde, la banda deja de estar atada a la sección. */
  bottom: calc(var(--banda-cola) * -1);
  right: var(--banda-aire);
  width: var(--banda-w);
  background-color: var(--granate);
  background-image: url('/assets/img/nudo-tile-360.webp');
  background-repeat: repeat-y;
  background-position: top center;
  background-size: var(--banda-w) var(--banda-h);
  /* La disolución es una MÁSCARA de alfa, no un degradado blanco encima: el
     blanco pintado sólo funciona sobre fondo blanco y, mezclado con el granate,
     da justamente la banda gris sucia que hay que evitar. La máscara baja el
     alfa del granate Y del motivo a la vez, así que lo que aparece detrás es el
     papel real de la página, sea el que sea.
     Estático y sin JS: es un degradado fijo sobre el elemento, no reacciona al
     scroll ni necesita medir nada.
     El prefijo -webkit- va por Safari, que todavía lo pide; Chrome y Firefox
     toman la propiedad sin prefijo. */
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - var(--banda-cola)), transparent 100%);
          mask-image: linear-gradient(to bottom, #000 calc(100% - var(--banda-cola)), transparent 100%);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

/* La guarda se apaga al scrollear (Orden 20). Es COMPLEMENTARIO de la máscara de
   arriba, no un reemplazo: la máscara resuelve el CORTE —el borde inferior duro,
   siempre, en cualquier navegador y sin scrollear— y la opacidad resuelve la
   DESAPARICIÓN —que la banda no siga ahí cuando el visitante ya está leyendo otra
   cosa—. Se componen sin pelearse: la máscara actúa sobre la geometría (qué parte
   del elemento se ve) y la opacidad sobre el elemento entero ya enmascarado.

   Sin JavaScript ni listeners de scroll: es una animación de `opacity` atada a una
   scroll progress timeline del documento. El navegador la resuelve en el
   compositor, así que no hay trabajo por frame en el hilo principal.

   EL RANGO. `animation-range: 0 calc(var(--banda-h) * 2)`, o sea de la posición de
   scroll 0 a 446px, dos tiles del propio motivo. Las anclas son el INICIO DEL
   DOCUMENTO y una longitud absoluta, no porcentajes del scroll total, y eso es una
   decisión medida:

   El punto final pedido es la llegada de "Ediciones anteriores" al viewport. Medido
   sobre el render en los tres idiomas y de 768 a 1440px, ese momento cae entre los
   395px (ZH, que compone más corto) y los 534px (ES/EN) de scroll. Es una distancia
   sorprendentemente estable —36px de dispersión por idioma— porque el hero mide
   exactamente 100svh: al restarle el alto del viewport a la posición del título, el
   viewport se cancela y lo que queda es el desplazamiento del <h3> DENTRO de "Qué
   es", que no depende del alto de la pantalla.
   En PORCENTAJE del scroll total, en cambio, el mismo momento va del 6,4% al 8,4%,
   porque el largo de la página sí cambia mucho con el ancho. Por eso longitud y no
   porcentaje: el porcentaje erraba casi el doble.
   Los 446px salen de 2 × --banda-h, la misma unidad con la que están construidas la
   banda y su cola, y caen dentro del rango medido: en ES/EN la banda llega a 0 unos
   90px antes de que aparezca el título, y en ZH prácticamente sobre la llegada.
   No se usa una view timeline sobre el propio <h3> porque el rango de una view
   timeline no puede empezar en "el tope del documento": arrancaría recién cuando el
   título asoma, y el desvanecido tiene que ocupar todo el recorrido anterior.

   EL @supports NO ES OPCIONAL. Sin él, un navegador que no entiende
   `animation-timeline` igual entiende la propiedad `animation`: la tomaría como una
   animación por tiempo, con duración 0s por defecto, y con `both` saltaría de una al
   fotograma final — la banda desaparecería del todo en Safari y Firefox. Envuelta en
   el @supports, esos navegadores no ven ninguna animación y la banda queda en
   opacidad 1 con la máscara, que es el estado correcto de reserva. */
@keyframes banda-nudos-se-apaga {
  from { opacity: 1; }
  to   { opacity: 0; }
}

@supports (animation-timeline: scroll()) {
  .banda-nudos {
    animation: banda-nudos-se-apaga linear both;
    animation-timeline: scroll(root block);
    animation-range: 0 calc(var(--banda-h) * 2);
  }

  /* Con movimiento reducido, la banda no se mueve: queda fija en opacidad 1 con su
     máscara. Va explícito y no confiado a la regla global de más arriba, que sólo
     recorta `animation-duration` — en una animación atada a una timeline de scroll
     la duración se ignora, así que esa regla no la apagaría. */
  @media (prefers-reduced-motion: reduce) {
    .banda-nudos { animation: none; opacity: 1; }
  }
}

/* De 900px para arriba hay lugar de sobra en el carril (452px libres o más), así
   que la cola pasa a 1,5 tiles: la banda gana un nudo y medio de desvanecido.
   Abajo de 900px se queda en los 120px del valor base, que es lo que entra sin
   tocar la bajada de "Qué es". */
@media (min-width: 900px) {
  :root { --banda-cola: calc(var(--banda-h) * 1.5); }
}

/* Móvil: hero sin banda (decisión de Iván, tras el proto de variantes).
   Por debajo de 768px no entran banda legible y texto legible a la misma altura:
   el motivo no baja de 96px de ancho —regla vigente, ver arriba— y a 390px eso
   dejaba la consigna en 194px y 9 renglones. Las alternativas probadas (banda
   corta, nudo suelto como sello, remate horizontal al cierre) están maquetadas en
   referencias/proto-nudo-movil.html por si la Cámara pide reconsiderarlo; ninguna
   convenció, así que el hero móvil va limpio y el texto usa todo el ancho.
   El padding-right vuelve al del .wrap: si quedara el calc() con --banda-w, el
   texto seguiría cediendo ancho a una banda que ya no está. */
@media (max-width: 767px) {
  .banda-nudos { display: none; }
  .hero__text { padding-right: var(--pad-inline); }
}

/* ==========================================================================
   Secciones (orden 03) — mismo sistema: tarjetas --nieve / --linea / --radio,
   secundario --gris, estados pendientes --gris-suave.
   ========================================================================== */
.section { padding-block: clamp(2.5rem, 1.5rem + 3vw, 5rem); }
/* El ritmo entre secciones es una línea fina, no fondos alternados. La banda de
   declaración vive en el medio de <main>, así que la sección que la sigue deja de
   ser `.section + .section` y pierde ese borde — correcto: la banda roja a sangre
   completa separa mucho más que una línea, y sumarle encima el hairline dejaba
   dos separadores pegados.
   Orden 19: la banda pasó de estar entre Sede y Sponsors (Orden 17) a estar entre
   "Qué es" y Programa. El que pierde el hairline es entonces Programa, y Sponsors
   lo recupera. No hay nada que tocar acá: la regla es posicional y la secuencia se
   reacomoda sola. Hoy llevan borde superior Sede, Sponsors, Galería y Contacto;
   no lo llevan "Qué es" (primera después del hero) ni Programa (va tras la
   banda). */
.section + .section { border-top: 1px solid var(--linea); }

/* Anclas del menú (Orden 06, T6): con el navbar fijo, sin esto el header
   tapa el título de la sección al llegar por link o #hash. */
#que-es, #programa, #sede, #alojamiento, #sponsors, #contacto {
  scroll-margin-top: var(--header-h);
}

.section__titulo {
  font-size: var(--fs-xl);
  font-weight: 800;
  letter-spacing: -0.02em;
  margin-bottom: 1.5rem;
}
.section__sub {
  font-size: var(--fs-md);
  font-weight: 700;
  margin: 2.25rem 0 1rem;
}
.section__nota { color: var(--gris); max-width: 60ch; margin-bottom: 1.5rem; }

/* --- Qué es --- */
/* Fila de temas (Orden 06, T4): venían del hero (chips de los ejes). */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-bottom: 2rem;
}
.chip {
  background: var(--nieve);
  border: 1px solid var(--linea);
  border-radius: 999px;
  padding: 0.4rem 0.95rem;
  font-size: var(--fs-nota);
  font-weight: 600;
}

/* Párrafo de definición (Orden 21). Reemplaza a la lista .puntos/.punto —tres
   ítems con rombo rojo— que ya no existe: la Cámara mandó el texto corrido y no
   hay tres cosas que enumerar. Va a 60ch, la misma medida de línea que
   .section__nota (justo arriba, en esta misma sección) y que .aviso-card, así el
   bloque queda alineado a izquierda y derecha con el resto del sitio. */
.que-es__texto { max-width: 60ch; margin-bottom: 0.5rem; }
.ediciones { display: grid; gap: 0.5rem; }
.edicion {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 1rem;
  padding: 0.85rem 1.1rem;
  background: var(--nieve);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
}
.edicion__anio { font-weight: 800; color: var(--rojo); min-width: 3.5ch; }
.edicion__lugar { flex: 1 1 12rem; }
.edicion__link { font-weight: 600; }

/* --- Programa (timeline vertical) --- */
.timeline { display: grid; gap: 2.5rem; list-style: none; }
/* El carril del recorrido en dos variables, para que el punto no quede atado a
   un `left` calculado a mano: con `left: -8px` escrito duro, cambiar el grosor
   de la línea lo descentraba. Ahora la posición se deriva de las dos medidas y
   el punto queda centrado sobre la línea sea cual sea su grosor.
   La línea cae en el borde izquierdo del contenido del wrap, o sea en la misma
   vertical que el título de la sección: el timeline es el EJE del Programa, no
   un bloque indentado dentro de él. Antes quedaba 40px adentro por el padding
   por defecto del <ol> (ver el reset de listas). El punto sobresale hacia la
   izquierda y se mete en el padding del wrap, que es aire y no contenido. */
.timeline__dia {
  --carril-linea: 2px;
  --carril-punto: 14px;
  --panel-pad-y: 1rem;
  position: relative;
  padding-left: 1.5rem;
  border-left: var(--carril-linea) solid var(--linea);
}
.timeline__dia::before {
  content: '';
  position: absolute;
  /* El 0 de `left` es el borde interno de la línea, así que la línea ocupa de
     -grosor a 0 y su centro está en -grosor/2. */
  left: calc(var(--carril-linea) / -2 - var(--carril-punto) / 2);
  /* El punto se alinea con la primera línea del título de la primera tarjeta, no
     con el borde superior del bloque: bajando por el padding de la tarjeta y
     media caja de línea, y descontando su propio radio. Estaba en 0.35rem, que lo
     dejaba ~16px por encima del título. `lh` es la caja de línea heredada del
     body, la misma que usa .panel__titulo (mismo font-size y line-height), así
     que acompaña al clamp de --fs-base sin números por breakpoint.
     El `top` de arriba es el fallback para navegadores sin unidad `lh`: si no la
     entienden, descartan el calc entero y se quedan con el valor aproximado. */
  top: 1.3rem;
  top: calc(var(--panel-pad-y) + 0.5lh - var(--carril-punto) / 2);
  width: var(--carril-punto);
  height: var(--carril-punto);
  border-radius: 50%;
  background: var(--rojo);
  border: 3px solid var(--papel);
}
/* Orden 17: el encabezado de día (.timeline__head con fecha + sede) se quitó del
   render — con un solo día era redundante con el hero y con la sección Sede. Sus
   reglas (.timeline__fecha / .timeline__sede) se fueron con él. El array
   programa.dias sigue siendo un array: si vuelve un segundo día, hay que
   reponer el encabezado. */
.paneles { display: grid; gap: 0.75rem; }
/* El CTA repetido al final del timeline (Orden 17) se quitó en la 18: es
   redundante con el del header, que ahora es sticky Y visible en móvil. */
/* Sin barra lateral de color (Orden 07, T3): decisión de Iván, es un patrón
   de output genérico a evitar. Reemplazo: sombra mínima de una sola dirección
   (offset chico, opacidad baja, sin halo difuso).
   Alternativa a comparar (Iván elige mirando el render): separación por
   espacio + borde completo de 1px tenue, más afín a la sobriedad plana de la
   invitación — más abajo, comentada, lista para intercambiar. */
.panel {
  /* El padding vertical va en variable porque el punto del carril se posiciona
     contra él: si cambia acá, el punto lo sigue solo. */
  padding: var(--panel-pad-y, 1rem) 1.25rem;
  background: var(--nieve);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
/* Alternativa T3 — borde completo tenue, sin sombra:
.panel {
  padding: 1rem 1.25rem;
  background: var(--nieve);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
}
*/
/* `balance` reparte las líneas del título en vez de llenar la primera hasta el
   tope: sin esto, "Admisión · Café de bienvenida · Networking" en un móvil de
   390px corta como "Admisión · Café de bienvenida ·" + "Networking", que deja el
   separador colgando al final de la primera línea y una palabra huérfana en la
   segunda. Con balance parte en "Admisión · Café de" + "bienvenida · Networking".
   Es tipografía de composición, no de tamaño: el cuerpo no cambia. Los navegadores
   que no lo soportan lo ignoran y caen en el comportamiento de antes. */
.panel__titulo { font-size: var(--fs-base); font-weight: 700; text-wrap: balance; }
.panel__bajada { color: var(--gris); margin-top: 0.15rem; }
/* Sub-ítems del bloque (Orden 14): un renglón por participante/dato, fiel al
   formato "· " del borrador — no se colapsan en una sola línea. */
.panel__subitems { color: var(--gris); margin-top: 0.75rem; list-style: none; }
.panel__subitem { margin-top: 0.15rem; }
/* Segundo nivel opcional (Orden 15): el tema/descripción de un orador, sangrado
   y sin bullet propio — nunca un ítem hermano. */
.panel__detalle {
  margin: 0.15rem 0 0 1.1rem;
  color: var(--gris-suave);
  font-size: var(--fs-nota);
}
/* Separador de sub-grupo dentro de un panel (Orden 15b): rótulo tipo "A
   confirmar:", sin bullet — no debe leerse como un orador más. */
.panel__separador { margin-top: 0.6rem; font-weight: 700; color: var(--gris); }
/* Moderador/a: en el borrador va sin bullet y con un renglón en blanco antes,
   cerrando el panel. El rótulo ("Moderadora:") en negrita; el nombre normal. */
.panel__moderador { margin-top: 0.85rem; }
.panel__horario { font-weight: 700; margin-top: 0.35rem; }
/* Programa reducido (flags.programa_detalle en false, Orden 19): la tarjeta se
   queda con título + horario y nada más. Con el detalle apagado el horario deja
   de ser el pie de una ficha larga y pasa a ser el único dato al lado del
   título, así que baja a rótulo —cuerpo de nota, gris, seminegrita— y el título
   queda como la única voz principal de la tarjeta. Dos líneas del mismo peso se
   leían como dos títulos.
   El selector adyacente ES la descripción del caso: el horario pegado al
   título, sin bajada ni subitems en el medio. Se apaga solo cuando vuelve el
   detalle, sin clase nueva ni lógica en build.mjs — mismo criterio que
   .sede-card:only-child. (El bloque "Coffee break", que ya venía sin subitems,
   entra por la misma puerta: también es una tarjeta reducida.)
   No hace falta tocar padding ni bordes: el aire de .panel es el mismo arriba y
   abajo, y las piezas que se apagaron eran todas condicionales —no dejan margen
   ni separador huérfano al no renderizarse. */
.panel__titulo + .panel__horario {
  margin-top: 0.25rem;
  font-weight: 600;
  font-size: var(--fs-nota);
  color: var(--gris);
}
/* Resaltado de "a confirmar" (Orden 14, T2): se inyecta en el build, nunca en
   los JSON. Un solo lugar para el color — var(--resaltado) en :root. */
.por-confirmar {
  /* El aire lateral es del resaltado, no separación tipográfica. Sin el margen
     negativo, el ")" de "(BYD / GWM / MG — a confirmar)" arrancaba 0.3em después
     del último glifo y se leía despegado de la palabra. Devolviendo ese avance,
     el texto vecino queda pegado y el amarillo se extiende por detrás. Padding y
     margen salen de la misma variable para que no se desincronicen. */
  --resaltado-aire: 0.3em;
  background: var(--resaltado);
  padding: 0.05em var(--resaltado-aire);
  margin-inline: calc(var(--resaltado-aire) * -1);
  border-radius: 0.2em;
}
.panel__estado {
  color: var(--gris-suave);
  font-size: var(--fs-nota);
  font-style: italic;
  margin-top: 0.4rem;
}

/* --- Autoridades y oradores --- */
.grupos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 1rem;
}
.grupo {
  display: grid;
  justify-items: center;
  text-align: center;
  gap: 0.7rem;
  padding: 1.75rem 1rem;
  background: var(--nieve);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
}
/* Silueta neutra (nunca una foto de stock): cabeza + hombros. */
.grupo__avatar {
  position: relative;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--papel);
  border: 1px solid var(--linea);
  overflow: hidden;
}
.grupo__avatar::before,
.grupo__avatar::after {
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  background: var(--gris-suave);
  border-radius: 50%;
}
.grupo__avatar::before { top: 11px; width: 20px; height: 20px; }
.grupo__avatar::after { bottom: -16px; width: 40px; height: 40px; }
.grupo__estado { font-size: var(--fs-nota); color: var(--gris-suave); font-style: italic; }

/* --- Sede (las clases siguen en plural a propósito, ver Orden 17) --- */
.sedes-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}
.sede-card {
  display: flex;
  flex-direction: column;
  background: var(--papel);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
  overflow: hidden;
}
.sede-card__mapa { position: relative; margin: 0; }
.sede-card__mapa img { width: 100%; height: auto; aspect-ratio: 800 / 520; object-fit: cover; }
.map-note {
  position: absolute;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.85);
  color: var(--gris);
  font-size: 0.7rem;
  padding: 2px 6px;
  border-top-left-radius: 6px;
}
.sede-card__body { display: grid; gap: 0.4rem; padding: 1.1rem 1.25rem 1.35rem; }
.sede-card__nombre { font-size: var(--fs-md); font-weight: 700; }
.sede-card__dir { color: var(--gris); }
.sede-card__link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  justify-self: start;
  margin-top: 0.3rem;
  font-weight: 600;
}
.sede-card__link-icon { flex-shrink: 0; }
/* Orden 17: .sedes__nota (la nota de distancia entre las dos sedes) se fue con
   la tarjeta de la segunda sede. Las clases siguen diciendo "sedes" a
   propósito: sólo se renombró lo visible (título, nav y el ancla #sede). */

/* Sede única (Orden 17): con un solo ítem, el auto-fit de .sedes-grid le da a la
   tarjeta el ancho completo del wrap y el mapa quedaba desproporcionado. La
   tarjeta se parte entonces en dos columnas: mapa a la izquierda con el mismo
   ancho que tenía cuando eran dos tarjetas (50% menos medio gap de la grilla),
   texto a la derecha centrado vertical.

   Va con :only-child y no con una clase nueva ni con lógica en build.mjs a
   propósito: si vuelve una segunda sede, el selector deja de aplicar y el
   layout se revierte solo, sin que nadie tenga que acordarse.

   El alto lo sigue marcando el mapa (aspect-ratio 800/520 intacto): el body es
   más corto y por eso se centra dentro de la altura de la tarjeta. */
.sede-card:only-child { flex-direction: row; }
.sede-card:only-child .sede-card__mapa {
  flex: 0 0 calc(50% - 0.75rem);   /* 0.75rem = mitad del gap de .sedes-grid */
}
.sede-card:only-child .sede-card__body { flex: 1; align-content: center; }

/* Por debajo de 860px la tarjeta vuelve a apilarse (mapa arriba, texto abajo):
   en dos columnas el mapa quedaría demasiado angosto para leerse. */
@media (max-width: 859px) {
  .sede-card:only-child { flex-direction: column; }
  .sede-card:only-child .sede-card__mapa { flex: 0 1 auto; }
  .sede-card:only-child .sede-card__body { align-content: start; }
}

/* Link discreto (Orden 07, T2/T4): mismo criterio en las dos apariciones
   (ediciones anteriores, sedes) — sin subrayado en estado normal, color +
   subrayado sólo en hover, como affordance. (La tercera aparición, el link
   de texto del footer, se sacó en la Orden 07b T3: el logo ya está enlazado.) */
.edicion__link,
.sede-card__link {
  color: var(--gris);
  text-decoration: none;
  transition: color 0.15s ease;
}
.edicion__link:hover,
.sede-card__link:hover {
  color: var(--rojo);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* --------------------------------------------------------------------------
   Sponsors (T6). El problema que resolvemos: en el sitio anterior los logos
   venían en proporciones dispares y una marca pesaba visualmente más que otra
   de su misma categoría. Acá cada logo vive en un contenedor de proporción fija
   (aspect-ratio) y se limita por ALTURA según el tier (--logo-h), con
   object-fit: contain. Resultado: peso visual homogéneo dentro de cada categoría
   y jerarquía clara entre tiers.
   -------------------------------------------------------------------------- */
.tier { margin-top: 2.25rem; }
.tier:first-of-type { margin-top: 1rem; }
.tier__nombre {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: var(--fs-nota);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--gris);
  margin-bottom: 0.9rem;
}
.tier__nombre::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--linea);
}
/* Columnas fijas por tier (--cols), no auto-fill: con auto-fill todos los tiers
   caían en celdas del mismo ancho y el logo de Main topaba contra ese ancho, así
   que no se veía más grande que el de Plata. Menos columnas = celda más ancha =
   más lugar para el logo. El número de columnas coincide con la cantidad de
   sponsors de cada tier, así ninguna fila queda coja. */
.tier__grid {
  display: grid;
  grid-template-columns: repeat(var(--cols, 3), 1fr);
  gap: 1rem;
}
/* "Organiza" (T5, Orden 07): comparte el tamaño de Main Sponsor (misma altura
   de logo, --logo-h) pero NO su tratamiento — es el organizador, no un
   sponsor pago de primer nivel. Se distingue por POSICIÓN (primera) y TAMAÑO
   (celda de ancho completo, banner) — no por color: precedente de la edición
   2025, la Cámara arriba, grande y sola, sin recuadro de marca. El logo-cell
   base (papel + borde gris fino) queda igual que en cualquier otro tier. */
/* Logo ~50% más grande que el tamaño Main (Orden 07b, T2). Esa proporción se
   había perdido: al pasar la escala a ancho de marca, Main quedó en 260px y esta
   banda en 237, o sea que la organizadora medía MENOS que el Main Sponsor y sólo
   ganaba por alto. Con --logo-w: 390px vuelve a ser exactamente 1,5× Main, que es
   lo que decía la intención original. Va con caja de dos topes como los demás
   tiers, en la misma relación 2:1. */
.tier--organiza { --logo-w: 390px; --logo-h: 195px; }
.tier--organiza .logo-cell {
  grid-column: 1 / -1;
}
/* Alternativa T2(b) — más altura de tarjeta para que el logo respire, sin
   tocar el ancho (Iván compara mirando el render; no se aplica todavía):
.tier--organiza .logo-cell {
  min-height: calc(var(--logo-h) + 5rem);
}
*/

/* Jerarquía por tier, en tres variables:
     --cols     columnas del tier, y por lo tanto ancho de celda
     --logo-w   tope de ANCHO del logo — la variable que manda
     --logo-h   tope de ALTO del logo — guarda de seguridad

   El alto de celda NO se declara por tier: sale de --logo-h por fórmula, con un
   único coeficiente compartido (--k-celda, en .logo-cell). Cuando eran cuatro
   constantes sueltas se fueron desalineando cada vez que cambió el tamaño de los
   logos —la Orden 17 tuvo que subir Main a 305px para agrandar un lienzo que ya
   no existe— y el aire terminó entre 62% y 88%, distinto en cada tier.

   Los PNG ya no traen lienzo. Hasta la Orden 18 eran cuadrados de 500×500 con la
   marca centrada y transparencia alrededor, así que --logo-h limitaba el LIENZO y
   no la marca. `npm run trim-logos` recortó los 12 a la caja mínima de píxeles
   opacos: el margen era 21-23% a lo ancho pero 61-86% a lo alto, o sea que el
   alto de logo venía midiendo entre 3 y 7 veces más lienzo que marca.

   Recortados, las proporciones quedan muy dispares —5,7:1 (andreani, made,
   ganfeng) contra 2,0:1 (veladero)—, así que un solo tope no alcanza: por alto
   los apaisados se comerían el ancho de la celda, por ancho los cuadrados se
   irían de alto. Van los dos topes, y `object-fit: contain` encaja la marca en
   esa caja sin deformarla.

   La caja de cada tier va en relación 2:1, apenas por encima del logo más
   cuadrado del set (veladero, 1,99:1). Con eso el ancho es el que ata a los 12 y
   la escala entre tiers se lee en una sola dimensión —260/165/130/100 px de marca
   real, o sea 1 : 0,63 : 0,50 : 0,38—, mientras --logo-h queda de guarda para que
   un logo cuadrado que llegue mañana no reviente el alto de la celda.

   Las marcas quedan entre 18% y 30% más grandes que antes del recorte en todos
   los tiers, con una excepción: veladero es el único que topa por alto, así que
   crece 22% en vez de ~25%. */
.tier--main   { --cols: 2; --logo-w: 260px; --logo-h: 130px; }
/* Orden 19 (cierre de lista): Platino vuelve a 3 marcas (entra Puken) y Plata
   pasa a 5 (entran Minera Plasa y Litio y MERCH). Platino sigue la regla de
   arriba —una columna por sponsor, ninguna fila coja—. Plata NO: con 5 marcas,
   5 columnas dejarían la celda más angosta que la de Oro y se daría vuelta la
   jerarquía. Va a 4 columnas y la fila que queda coja se centra, igual que Oro. */
.tier--platino{ --cols: 3; --logo-w: 165px; --logo-h: 82px; }
.tier--oro    { --cols: 3; --logo-w: 130px; --logo-h: 65px; }
.tier--plata  { --cols: 4; --logo-w: 100px; --logo-h: 50px; }

/* Oro en 3 columnas. Cuando la última fila lleva 2 celdas hay que centrarla. Se
   hace sobre una grilla de 6 columnas donde cada celda ocupa 2: las filas
   completas quedan idénticas a las de 3 columnas (mismo ancho de celda, mismo
   gap) y la última fila se corre media columna arrancando en la 2, con lo que
   sus 2 celdas quedan centradas respecto del bloque. Sin celdas vacías de
   relleno.
   El guard `:nth-child(3n + 1)` sobre la anteúltima celda es lo que hace que la
   regla se apague sola, igual que el `:nth-child(4n + 1)` de Plata acá abajo:
   sólo hay fila final de 2 cuando la cantidad de marcas deja resto 2 al dividir
   por 3. Con 5 marcas aplica (la 4ª); con 4 —que es como quedó Oro al salir
   Pluspetrol en la Orden 23— la última fila lleva UNA celda y el centrado no
   corresponde. Sin el guard, la regla agarraba la 3ª celda, o sea la última de
   la PRIMERA fila, y partía la grilla en dos filas de 2. */
.tier--oro .tier__grid { grid-template-columns: repeat(6, 1fr); }
.tier--oro .logo-cell { grid-column: span 2; }
/* `2 / span 2` y no `grid-column-start: 2`: el shorthand de arriba deja
   grid-column-end en auto, así que pisar sólo el start dejaba la celda con una
   columna de ancho (media celda) en vez de correrla. */
.tier--oro .logo-cell:nth-last-child(2):nth-child(3n + 1) { grid-column: 2 / span 2; }
/* Y la otra mitad del caso: cuando la última fila lleva UNA celda (resto 1 al
   dividir por 3, que es como quedó Oro con 4 marcas), la huérfana se centra en
   la columna del medio —3/4 de las 6— en vez de quedar pegada a la izquierda.
   Mismo criterio que Plata acá abajo y que el propio Oro en el breakpoint de 2
   columnas, más abajo: un huérfano suelto a la izquierda se lee como una grilla
   rota, no como una fila incompleta. */
.tier--oro .logo-cell:last-child:nth-child(3n + 1) { grid-column: 3 / span 2; }

/* Plata: 5 marcas en 4 columnas, así que la última fila lleva UNA celda sola.
   Mismo mecanismo que Oro, con el doble de columnas: grilla de 8 donde cada
   celda ocupa 2. Las filas completas quedan idénticas a las de 4 columnas
   —2·(A−7g)/8 + g = (A−3g)/4, la misma cuenta que hace la grilla de 4—, y la
   celda huérfana arranca en la columna 4, que la deja centrada (3 columnas y 3
   gaps de cada lado). Sin celdas vacías de relleno.
   El guard `:nth-child(4n + 1)` es lo que hace que la regla se apague sola: sólo
   hay huérfano cuando la cantidad de marcas deja resto 1 al dividir por 4. Con 4
   o 6 marcas la fila cierra o el resto es otro, y el centrado no se aplica. */
.tier--plata .tier__grid { grid-template-columns: repeat(8, 1fr); }
.tier--plata .logo-cell { grid-column: span 2; }
.tier--plata .logo-cell:last-child:nth-child(4n + 1) { grid-column: 4 / span 2; }

.logo-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Un solo coeficiente para los cinco tiers: la caja del logo ocupa 1/1,2 =
     83% del interior de la celda, y el resto es aire. Al ser proporcional, el
     aire es el mismo porcentaje en todos los tiers por construcción; no hay
     manera de que se desalineen como pasaba con cuatro constantes sueltas.
     Con box-sizing: border-box hay que sumarle el padding, que va aparte porque
     es aire de borde y no escala con el logo.
     Lo que no vuelve es el aspect-ratio 3/2: con lienzos cuadrados dejaba la
     celda más baja que la imagen y las celdas de un mismo tier quedaban
     desparejas (la de texto a 111px, la de logo a 144px). */
  --k-celda: 1.2;
  --celda-pad: 1rem;
  min-height: calc(var(--logo-h, 52px) * var(--k-celda) + var(--celda-pad) * 2);
  /* 1rem. Estuvo en 0.75rem porque el lienzo cuadrado del PNG topaba contra el
     padding en Oro y Plata y se comía el crecimiento del logo; sin lienzo, ya
     no compite. */
  padding: var(--celda-pad);
  background: var(--papel);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
}
/* Mención especial (Orden 17): bloque propio, debajo de todos los tiers. NO es
   un tier — no lleva .logo-cell (ni recuadro, ni borde, ni alto de celda) ni
   entra en la escala --logo-h, justamente para que no se lea como una categoría
   comercial más. Centrado, con la leyenda arriba y una línea fina que lo separa
   del último tier. El logo va al tamaño de Plata, el escalón más bajo. */
.mencion {
  margin-top: 2.75rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--linea);
  display: grid;
  justify-items: center;
  gap: 0.9rem;
}
.mencion__titulo {
  font-size: var(--fs-nota);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--gris);
}
/* La caja de ORO (Orden 19). La clienta pidió "un poco más grande, un poco de
   zoom": Oro es el escalón siguiente al de Plata y suma +30% de ancho de marca
   (100 -> 130px). Platino, que era la otra opción, es +65% y dejaría a la
   mención por encima de sponsors que sí pagaron ese lugar — que es justamente lo
   que la sección no tiene que comunicar.
   Sin recuadro ni etiqueta de tier, y sola debajo de todos los tiers, la mención
   se lee como algo aparte de la escala comercial más allá de qué caja use.
   El seguimiento a Oro tiene que valer en TODOS los anchos: como la mención no
   vive en una celda de grilla, en cada breakpoint donde Oro cambia de columnas o
   de --logo-fill hay que reconstruirle acá el interior de una celda de Oro (ver
   las dos reglas .mencion__logo de los media queries). */
.mencion__logo {
  max-width: min(130px, 100%);
  max-height: 65px;
  width: auto;
  height: auto;
  object-fit: contain;
}

.logo-cell__link { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
.logo-cell__img {
  /* Los dos topes, ancho y alto: con proporciones de 5,7:1 a 2,0:1 uno solo deja
     que el otro eje se dispare.
     El ancho sale del menor entre dos cosas: la caja del tier en px (--logo-w) y
     una fracción del interior de la celda (--logo-fill, 100% por defecto). En
     escritorio manda siempre la caja en px y --logo-fill sólo actúa de tope duro
     contra el ancho de celda. En móvil se invierte: la celda es más angosta que
     la caja, así que la escala entre tiers pasa a ser el porcentaje. */
  max-width: min(var(--logo-w, 100%), var(--logo-fill, 100%));
  max-height: var(--logo-h, 52px);
  width: auto;
  height: auto;
  object-fit: contain;      /* nunca deforma el logo */
}
/* Estado vacío: una sola celda a lo ancho, con la leyenda "En confirmación".
   Hereda el alto de .logo-cell (derivado de --logo-h), así la jerarquía entre tiers
   —el argumento central de la sección— se ve DESDE YA, sin logos cargados. */
.logo-cell--vacia {
  grid-column: 1 / -1;
  background: var(--nieve);
  border-style: dashed;
}
.logo-cell__estado { color: var(--gris-suave); font-size: var(--fs-nota); font-style: italic; }
/* Sponsor confirmado sin logo todavía (Orden 13): nombre en texto dentro de la
   misma celda -papel + borde-, para no anticipar un isologo que no llegó. Cuando
   llegue el logo real, este span se reemplaza por <img class="logo-cell__img">. */
.logo-cell__nombre {
  text-align: center;
  font-weight: 700;
  font-size: var(--fs-base);
  color: var(--gris);
  line-height: 1.3;
}
/* --------------------------------------------------------------------------
   Botón secundario (contorno) — galería y "Inscribirme"
   -------------------------------------------------------------------------- */
.btn--linea {
  background: transparent;
  color: var(--rojo);
  border: 2px solid var(--rojo);
  padding: 0.55rem 1.15rem;
}
.btn--linea:hover { background: var(--rojo); color: var(--papel); }

/* --------------------------------------------------------------------------
   Galería (T7) — bloque de aviso, sin cuadrícula vacía
   -------------------------------------------------------------------------- */
.aviso-card {
  display: grid;
  justify-items: start;
  gap: 1rem;
  max-width: 60ch;
  padding: 1.75rem;
  background: var(--nieve);
  border: 1px solid var(--linea);
  border-radius: var(--radio);
}

/* --------------------------------------------------------------------------
   Alojamiento y guía de Salta (Orden 19) — reusa .aviso-card, la misma tarjeta
   de la Galería. No hay bloque nuevo de layout: la sección es un título + una
   tarjeta con bajada, botón y pie.
   -------------------------------------------------------------------------- */
/* Aviso de idioma (sólo EN y ZH: el PDF está únicamente en español). Registro de
   acotación, el mismo que usa .hero__nota. En el ES la clave es null y el
   párrafo directamente no se renderiza. */
.alojamiento__idioma {
  font-size: var(--fs-nota);
  color: var(--gris-suave);
  font-style: italic;
}
/* Peso y formato del archivo, pegado al botón: es una etiqueta del botón, no un
   párrafo más de la tarjeta, así que se come el gap de 1rem de .aviso-card con
   un margen negativo en vez de quedar flotando a media distancia. Va acá y no
   dentro del <a> para no meterlo en el texto accesible del enlace. */
.alojamiento__peso {
  margin-top: -0.75rem;
  font-size: var(--fs-nota);
  color: var(--gris-suave);
}

/* --------------------------------------------------------------------------
   Contacto (T8) — intro + formulario en dos columnas (una sola en móvil)
   -------------------------------------------------------------------------- */
/* Dos filas (Orden 17): el título ocupa la fila 1 de la columna izquierda y la
   tarjeta y el formulario arrancan los dos en la fila 2. Antes el título estaba
   dentro de .contacto__intro, así que la tarjeta quedaba 71,5px por debajo del
   primer campo. Resuelto con colocación en la grilla —ni márgenes negativos ni
   un desplazamiento fijo—, así el desfase sigue solo al alto real del <h2>,
   que depende de --fs-xl (un clamp). row-gap en 0: el aire entre título y fila 2
   lo pone el margin-bottom del propio .section__titulo. */
.contacto__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  column-gap: clamp(1.5rem, 1rem + 3vw, 3.5rem);
  row-gap: 0;
  align-items: start;
}
.contacto__titulo { grid-column: 1; grid-row: 1; }
.contacto__intro  { grid-column: 1; grid-row: 2; }
.contacto__grid .form { grid-column: 2; grid-row: 2; }
/* Sin barra lateral de color (Orden 07, T6a): mismo criterio que T3.
   Orden 17: la tarjeta pasó de <p> a contenedor — el botón de inscripción va
   adentro, con el texto de acceso, no suelto debajo. */
.contacto__acceso {
  display: grid;
  justify-items: start;
  gap: 0.9rem;
  padding: 0.9rem 1.1rem;
  background: var(--nieve);
  border-radius: var(--radio);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
  margin-bottom: 1.25rem;
}
.contacto__acceso-texto { font-weight: 700; }
/* Las dos líneas del acceso son dos claves separadas del JSON, no una sola con
   un <br> adentro: el salto es decisión de maquetado y no tiene por qué viajar
   dentro del contenido, que además hay que traducir a tres idiomas. Acá el
   salto lo hace el `display: block`, y si mañana se quieren en una sola línea
   alcanza con sacar esta regla. */
.contacto__acceso-linea { display: block; }
.form__legal { font-size: var(--fs-nota); color: var(--gris-suave); max-width: 42ch; }

.form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}
/* min-width: 0 — sin esto, el min-content del <input> (que sale de su `size`
   por defecto, no del CSS) le gana al 1fr de la grilla y el formulario desborda
   el viewport entre 768 y ~830px. Bug preexistente, detectado en la Orden 17. */
.field { display: grid; gap: 0.35rem; min-width: 0; }
.field--full { grid-column: 1 / -1; }
.field__label { font-size: var(--fs-nota); font-weight: 600; color: var(--gris); }
.field__input {
  font: inherit;
  color: var(--tinta);
  padding: 0.65rem 0.8rem;
  background: var(--papel);
  border: 1px solid var(--linea);
  border-radius: 10px;
  /* El ancho lo manda la grilla, no el `size` por defecto del <input>: sin esto
     el control no baja de su min-content y arrastra el desborde (ver .field). */
  min-width: 0;
  width: 100%;
}
.field__input:focus-visible { outline: 2px solid var(--rojo); outline-offset: 1px; border-color: var(--rojo); }
textarea.field__input { resize: vertical; min-height: 7rem; }

/* Honeypot: fuera de la vista y del árbol accesible. No usar display:none
   (algunos bots lo ignoran); se saca de pantalla. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; }
.form__status { font-size: var(--fs-nota); font-weight: 600; }
.form__status[data-tono='ok'] { color: #1a7f37; }
.form__status[data-tono='error'] { color: var(--rojo); }

/* --------------------------------------------------------------------------
   Banda de declaración (T9) — ancho completo, fondo rojo.
   Acá había un full-bleed de `width: 100vw` + `margin-inline: calc(50% - 50vw)`,
   copiado de la foto del hero. Sobraba y además rompía: .declaracion es hija
   directa de <main>, que no limita el ancho —el max-width lo pone el .wrap de
   adentro—, así que un <div> de bloque ya ocupaba todo el ancho disponible sin
   ayuda. Lo que sí hacía el hack era medir 100vw, que INCLUYE la barra de
   scroll: en un escritorio con barra clásica (Windows, Linux, macOS con
   "mostrar siempre") la banda quedaba ~15px más ancha que el área de contenido
   y aparecía scroll horizontal en toda la página. Con superposición —el default
   de macOS, y lo que usa el Chromium headless de las pruebas— la barra mide 0 y
   el bug no se ve, que es la razón por la que sobrevivió hasta acá.
   -------------------------------------------------------------------------- */
.declaracion {
  background: var(--rojo);
  color: var(--papel);
  padding-block: clamp(1.1rem, 0.8rem + 1vw, 1.75rem);
}
.declaracion__texto {
  text-align: center;
  font-weight: 700;
  font-size: var(--fs-md);
  text-wrap: balance;
}

/* --------------------------------------------------------------------------
   Footer (T9)
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--nieve);
  border-top: 1px solid var(--linea);
  padding-block: clamp(2rem, 1.2rem + 2vw, 3.25rem);
}
.site-footer__inner {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: clamp(1.5rem, 1rem + 2vw, 3rem);
}
/* Logo al doble (Orden 07b, T3): 3rem -> 6rem. La URL en texto (footer-camara__cta)
   se sacó del template y del CSS: redundante, el logo ya está enlazado. */
.footer-camara { display: inline-grid; justify-items: start; }
/* 4.05rem y no 6rem: al recortarle la transparencia al PNG (Orden 17) el alto
   pasó a limitar la marca en vez del lienzo. 64,8px era el alto real que tenía
   la marca acá, así que el footer se ve igual que antes. */
.footer-camara__logo { height: 4.05rem; width: auto; }
.footer-col__titulo {
  font-size: var(--fs-nota);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gris);
  font-weight: 700;
  margin-bottom: 0.8rem;
}
/* -2px (T7b, Orden 07): un paso más discreto que el resto del footer. */
.footer-col__list { display: grid; gap: 0.5rem; font-weight: 600; font-size: calc(var(--fs-base) - 2px); }
.footer-col__list a:hover { color: var(--rojo); }

.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 1.5rem;
  margin-top: clamp(1.5rem, 1rem + 1.5vw, 2.5rem);
  padding-top: 1.25rem;
  border-top: 1px solid var(--linea);
  font-size: var(--fs-nota);
  color: var(--gris-suave);
}
/* Crédito NRD centrado y enlazado (T7c, Orden 07). */
.creditos { width: 100%; text-align: center; }
.creditos a { text-decoration: none; }
.creditos a:hover { color: var(--rojo); text-decoration: underline; text-underline-offset: 2px; }

/* --------------------------------------------------------------------------
   Responsive de las secciones de la tanda B
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
  /* Una columna: se sueltan las filas explícitas del desktop (si no, la tarjeta
     y el formulario caerían los dos en la fila 2 y se pisarían) y vuelve el
     row-gap, que en desktop lo cubre el margen del título. */
  .contacto__grid { grid-template-columns: 1fr; row-gap: 1.5rem; }
  .contacto__titulo, .contacto__intro, .contacto__grid .form {
    grid-column: 1;
    grid-row: auto;
  }
  .form { grid-template-columns: 1fr; }
  .site-footer__inner { grid-template-columns: 1fr 1fr; }
  .footer-brand { grid-column: 1 / -1; }

  /* Programa: recuperar ancho útil de texto. En un móvil de 390px, entre el
     margen del wrap (34,8px), el carril del timeline (26px) y el padding de la
     tarjeta (42px) se iban 102,8px —el 26% de la pantalla— y quedaban 247px para
     el texto, que parte mal los títulos y alarga el scroll.
     El carril baja de 26 a 13px: la línea a 1px y el padding a 0.75rem. El
     recorrido se conserva —línea y punto siguen ahí—, sólo que el punto ahora
     queda casi tocando la tarjeta, que es espacio que no aportaba nada. El punto
     no hay que reposicionarlo a mano: sale de --carril-linea.
     El padding de la tarjeta baja de 1.25rem a 0.75rem por lado, de 42 a 26px.
     Total recuperado: 29px, sin tocar la tipografía ni el wrap (que es global y
     afectaría a todas las secciones).
     Al alinear el carril al margen del wrap se recuperaron otros 40px, y parte
     de esos vuelven acá: la separación línea→tarjeta sube de 0.75rem a 1.25rem,
     que era lo que había quedado apretado. Neto para el texto: +32px. */
  .timeline__dia { --carril-linea: 1px; padding-left: 1.25rem; }
  .panel { padding-inline: 0.75rem; }
  /* Sponsors en móvil: la jerarquía la hacen las COLUMNAS, no cajas más chicas.
     Antes acá iban todos los tiers a 2 columnas y la escala se aplanaba: la celda
     quedaba más angosta que la caja del tier, ganaba el max-width: 100% y a 320px
     Platino, Oro y Plata terminaban en 102/102/100px, indistinguibles. Con una
     cantidad de columnas distinta por tier, el ancho de celda vuelve a escalonar
     solo. Plata se queda en las 3 de escritorio. */
  .tier--main { --cols: 1; }
  .tier--platino, .tier--oro { --cols: 2; }
  /* Oro vuelve a la grilla normal: acá manda --cols, no las 6 columnas del
     centrado de la última fila (con 2 columnas no hay nada que centrar). */
  .tier--oro .tier__grid { grid-template-columns: repeat(var(--cols), 1fr); }
  /* Se sueltan LAS DOS reglas de centrado de escritorio (la fila final de 2 y la
     celda huérfana). Si alguna se olvida acá, su `grid-column` de la grilla de 6
     sobrevive sobre una grilla de 2 y el navegador inventa columnas implícitas:
     la fila se desarma y quedan celdas de un par de píxeles. */
  .tier--oro .logo-cell,
  .tier--oro .logo-cell:nth-last-child(2):nth-child(3n + 1),
  .tier--oro .logo-cell:last-child:nth-child(3n + 1) { grid-column: auto; }
  /* Plata baja de 4 a 3 columnas: a 4 la celda quedaba más angosta que la de Oro
     a 2 y se daba vuelta la jerarquía. Con 5 marcas en 3 columnas la última fila
     lleva DOS celdas, así que el centrado pasa a ser el mismo de Oro en
     escritorio —grilla de 6, celda de 2, la anteúltima arranca en la columna 2—
     y se apaga el de la celda sola, que acá no aplica.
     El guard `:nth-child(3n + 1)` sobre la anteúltima celda hace lo mismo que el
     `4n + 1` de escritorio: sólo centra cuando la cantidad de marcas deja resto
     2 al dividir por 3. */
  .tier--plata { --cols: 3; }
  .tier--plata .tier__grid { grid-template-columns: repeat(6, 1fr); }
  .tier--plata .logo-cell:last-child:nth-child(4n + 1) { grid-column: span 2; }
  .tier--plata .logo-cell:nth-last-child(2):nth-child(3n + 1) { grid-column: 2 / span 2; }
  /* A 2 columnas, Platino (3 marcas) y Oro (5) dejan una celda sola en la última
     fila. Se centra, mismo criterio que la última fila de Oro en desktop. El
     selector se apaga solo si el tier vuelve a un número par de marcas —Platino
     entró acá en la Orden 19, cuando volvió a 3 con Puken—. Plata no entra: acá
     está a 3 columnas y su última fila lleva dos celdas, que se centran arriba. */
  .tier--platino .logo-cell:last-child:nth-child(odd),
  .tier--oro .logo-cell:last-child:nth-child(odd) {
    grid-column: 1 / -1;
    justify-self: center;
    width: calc(50% - 0.5rem);   /* 0.5rem = mitad del gap de .tier__grid */
  }
  /* La mención no vive en una celda de grilla, así que sin esto se le escapa al
     tier que tiene que igualar: los logos de Oro se achican contra el ancho de
     su celda y este se quedaría en los 130px de la caja. La cuenta reconstruye
     el interior útil de una celda de ORO a 2 columnas:
     (ancho - 1 gap) / 2 - padding - borde. */
  .mencion__logo { max-width: min(130px, calc((100% - 1rem) / 2 - 2rem - 2px)); }
}
@media (max-width: 479px) {
  .site-footer__inner { grid-template-columns: 1fr; }
  /* Organiza y Main son las dos únicas celdas a todo el ancho, así que en pantalla
     angosta las dos topan contra el mismo interior de celda y quedan del mismo
     tamaño — se pierde que la organizadora va por encima del Main Sponsor. Acá
     las cajas se achican a mano para recuperar el escalón: 240 contra 200.
     Se tocan --logo-w/--logo-h, no el alto de celda: ese lo sigue sacando la
     fórmula, así que las celdas se achican solas y no queda una banda de 266px
     con un logo de 87 adentro. */
  .tier--organiza { --logo-w: 240px; --logo-h: 120px; }
  .tier--main     { --logo-w: 200px; --logo-h: 100px; }

  /* Los tres tiers de abajo comparten las 2 columnas y la jerarquía la da el
     PORCENTAJE del interior de celda que ocupa cada uno. Con distinta cantidad
     de columnas no salía: a 2 y 2, Platino y Oro topaban los dos contra el mismo
     ancho de celda y quedaban idénticos; y llevar Plata a 3 columnas lo dejaba
     en 51px de marca, ilegible. */
  .tier--platino, .tier--oro, .tier--plata { --cols: 2; }
  .tier--platino { --logo-fill: 100%; }
  .tier--oro     { --logo-fill: 85%; }
  .tier--plata   { --logo-fill: 73%; }
  /* El padding de celda baja a la mitad sólo acá. Es lo que hace entrar la
     escala: a 1rem el interior de celda en 320px queda en 102px, y el 73% de
     Plata caía en 74px. A 0.5rem el interior sube a 118px y los tres tiers
     quedan por encima del piso de legibilidad (~85px) sin tocar los porcentajes.
     Sigue siendo la variable de la fórmula de --celda-h, no un alto a mano: las
     celdas se achican solas y en la misma proporción. */
  .logo-cell { --celda-pad: 0.5rem; }

  /* Plata baja a 2 columnas, así que vuelve a la grilla normal (se sueltan las 6
     columnas del centrado de arriba) y con 5 marcas queda una celda sola en la
     última fila. Se centra con el mismo criterio que Oro acá al lado. El
     selector se apaga solo si el tier vuelve a un número par de marcas. */
  .tier--plata .tier__grid { grid-template-columns: repeat(var(--cols), 1fr); }
  .tier--plata .logo-cell,
  .tier--plata .logo-cell:nth-last-child(2):nth-child(3n + 1) { grid-column: auto; }
  .tier--plata .logo-cell:last-child:nth-child(odd) {
    grid-column: 1 / -1;
    justify-self: center;
    width: calc(50% - 0.5rem);   /* 0.5rem = mitad del gap de .tier__grid */
  }

  /* UCASAL sigue a ORO: interior de una celda de Oro a 2 columnas
     —(ancho - gap) / 2 - padding - borde— por su mismo 85%. */
  .mencion__logo { max-width: min(130px, calc(((100% - 1rem) / 2 - 1rem - 2px) * 0.85)); }
}

/* ==========================================================================
   404 (Orden 22)
   ==========================================================================
   Único bloque nuevo del sistema. La página de error reusa el header, el botón,
   la tipografía y el footer que ya existen; acá sólo se centra el bloque entre
   el header fijo y el footer. El padding-top compensa --header-h con el mismo
   criterio que .hero__text (que es fijo, así que sin esto el título queda
   debajo de la barra). */
.error404 {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 70vh;
  padding-block: calc(var(--header-h) + clamp(1rem, 0.6rem + 2vw, 2rem)) clamp(2.5rem, 1.5rem + 3vw, 5rem);
}
.error404__caja {
  max-width: 46ch;
  margin-inline: auto;
  text-align: center;
}
/* El "404" es el único dato de la página que no sale de los JSON: es un número
   de estado HTTP, no copy traducible. aria-hidden en el marcado — el lector de
   pantalla ya recibe el título de la página. */
.error404__codigo {
  font-size: clamp(3.5rem, 2rem + 8vw, 7rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--rojo);
}
/* line-height propio (el body va a 1.55): a --fs-xl el título parte en dos
   líneas en los tres idiomas y con la interlínea del cuerpo quedaba un hueco
   que separaba el título del "404" de arriba. */
.error404__titulo {
  font-size: var(--fs-xl);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0.75rem 0 1rem;
}
.error404__mensaje {
  color: var(--gris);
  margin-bottom: 1.75rem;
}
