/* ═══════════════════════════════════════════════════════════════════════════
   ds2.css — LA UNICA hoja de prepstellar.com (spec 1.4).
   Sustituye a landing/styles.css y a web/static/web.css: la landing y las ~270
   paginas del builder apuntan aqui, en absoluto (`/ds2.css`).

   Spec: docs/superpowers/specs/2026-09-07-web-design-system-evolucion-design.md
   Mockup aprobado: docs/superpowers/specs/assets/2026-09-07-web-tarjetas-mockup.html

   REGLAS DURAS DE ESTA HOJA
   · Los literales #rrggbb viven SOLO en el bloque de tokens (§2), que es uno
     solo: el sitio tiene UNA piel, la oscura. Todo lo demas consume
     var(--token) o color-mix() sobre tokens.
   · Cero sombras (ni una sola): la jerarquia la hacen el filete `--cline`
     y el propio bloque de color. Hover = borde en `--block`, no elevacion.
   · Cero tipografia con remates y cero oro: Observatory queda derogado.
     La unica voz de titular es Archivo; la mono es Spline Sans Mono.
   · `.prose` es el UNICO ambito bajo el que se estiliza HTML de contenido
     suelto. Fuera de el, un selector de elemento solo aparece acotado por una
     clase de componente (`.block h1`, `.tbl th`), nunca desnudo, y las unicas
     reglas globales son las del reset (`*`, `html`, `body`, `a`, `img`,
     `button`). La clase generica de pagina de Observatory ya no existe.

   INDICE
     1 · Fuentes            8 · Baraja
     2 · Tokens             9 · Prosa y tablas
     3 · Gamas             10 · Checkpoint
     4 · Reset y base      11 · Corte hacia la app
     5 · Utilidades        12 · Botones
     6 · Chrome            13 · Barra inferior (navegacion + CTA)
     7 · Bloque y rail     14 · Ficha, hub, practica
                           16 · Landing
                           17 · Pie
                           18 · Impresion y movimiento reducido
   (§15, el player del test, murio con test-player.js: la practica es ahora una
    baraja servida en HTML, §14.)
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · Fuentes auto-hospedadas (variables, OFL) ─────────────────────────── */
/* Servidas desde la raiz del dominio: /fonts/*.woff (web/static/fonts/).
   Son variables: Archivo `wght` 100-900 y `wdth` 62-125; Spline Sans Mono
   `wght` 300-700. `swap` para no bloquear el primer pintado. */
@font-face {
  font-family: 'Archivo';
  src: url("/fonts/archivo.woff") format("woff");
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Spline Sans Mono';
  src: url("/fonts/spline-sans-mono.woff") format("woff");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

/* ── 2 · Tokens · UNA sola piel (Grafito) ─────────────────────────────────── */
/* ⚠️ EL UNICO BLOQUE CON HEX DE TODO EL DOMINIO. Fuera de aqui, cero literales.
   Decision de Oriol (2026-09-07): prepstellar.com es oscuro y punto. Una sola
   piel — ni base clara, ni interruptor, ni preferencia del sistema que valga:
   el cielo es la marca, y una marca no tiene dos caras. */
:root {
  color-scheme: dark;

  --paper: #15181C;
  --card: #1E232A;
  --cline: #2C333C;
  --ink: #EDF1F5;
  --muted: #9AA6B2;
  --on-block: #12151A;
  --premium: #EDF1F5;
  --on-premium: #15181A;
  --success: #5FBE73;
  --danger: #F0725F;

  /* Las 8 gamas de la spec 1.1. El bloque es del PRODUCTO, no del usuario. */
  --g-azul: #4A85B9;
  --g-indigo: #6F7AC7;
  --g-ambar: #9B7B3C;
  --g-teja: #B96C4B;
  --g-turquesa: #3A8B94;
  --g-verde: #38906F;
  --g-oliva: #5A8D37;
  --g-ciruela: #996CC6;

  /* Derivados. Viven en el MISMO bloque a proposito: asi el gate de la hoja
     («hex solo bajo un selector que nombre :root») tiene un unico sitio que
     vigilar, y no hay forma de colar un color suelto en otra parte. */
  /* Azul de marca por defecto; el builder pone [data-block] en <html>. */
  --block: var(--g-azul);
  --block-soft: color-mix(in srgb, var(--g-azul) 12%, var(--card));
  /* DS2: dentro de una tarjeta el filete `cline` desaparece. Tinta al 22 %. */
  --cline-strong: color-mix(in srgb, var(--ink) 22%, var(--card));
  /* El texto sobre verde/rojo es el mismo par que sobre el bloque: tinta oscura
     sobre tono claro. */
  --on-success: var(--on-block);
  --on-danger: var(--on-block);

  --sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'Spline Sans Mono', ui-monospace, Menlo, monospace;

  --step--1: clamp(.8rem, .78rem + .1vw, .84rem);
  --step-0: clamp(1rem, .97rem + .15vw, 1.075rem);
  --step-1: clamp(1.2rem, 1.12rem + .4vw, 1.45rem);
  --step-2: clamp(1.5rem, 1.34rem + .8vw, 2.05rem);
  --step-3: clamp(1.9rem, 1.55rem + 1.7vw, 2.9rem);
  --step-4: clamp(2.4rem, 1.7rem + 3.4vw, 4.4rem);

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

  --r-pill: 999px;
  --r-field: 12px;
  --r-card: 18px;
  --r-group: 26px;

  --measure: 68ch;
  --card-pad: clamp(16px, 3vw, 24px);
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --col: 1120px;

  --dur: 220ms;
  --ease: cubic-bezier(.22, 1, .36, 1);

  /* Medidos por deck.js; estos valores son el fallback sin JS. El bloque de la
     leccion incluye ahora el reproductor, de ahi los ~250 px. */
  --block-h: 250px;
  --bar-h: 68px;
}

/* ── 3 · Las 8 gamas ──────────────────────────────────────────────────────── */
/* Contexto de pagina (`<html data-block="indigo">`) o familia fija de una
   puerta del hub (`.fam-indigo`). `--block-soft` se redeclara en cada regla
   porque una custom property resuelve su var() en el elemento que la DECLARA,
   no donde se usa: sin esto las puertas heredaban la gama global. */
[data-block="azul"],     .fam-azul     { --block: var(--g-azul);     --block-soft: color-mix(in srgb, var(--g-azul) 12%, var(--card)); }
[data-block="indigo"],   .fam-indigo   { --block: var(--g-indigo);   --block-soft: color-mix(in srgb, var(--g-indigo) 12%, var(--card)); }
[data-block="ambar"],    .fam-ambar    { --block: var(--g-ambar);    --block-soft: color-mix(in srgb, var(--g-ambar) 12%, var(--card)); }
[data-block="teja"],     .fam-teja     { --block: var(--g-teja);     --block-soft: color-mix(in srgb, var(--g-teja) 12%, var(--card)); }
[data-block="turquesa"], .fam-turquesa { --block: var(--g-turquesa); --block-soft: color-mix(in srgb, var(--g-turquesa) 12%, var(--card)); }
[data-block="verde"],    .fam-verde    { --block: var(--g-verde);    --block-soft: color-mix(in srgb, var(--g-verde) 12%, var(--card)); }
[data-block="oliva"],    .fam-oliva    { --block: var(--g-oliva);    --block-soft: color-mix(in srgb, var(--g-oliva) 12%, var(--card)); }
[data-block="ciruela"],  .fam-ciruela  { --block: var(--g-ciruela);  --block-soft: color-mix(in srgb, var(--g-ciruela) 12%, var(--card)); }

/* Transicion entre documentos: las ~270 paginas navegan como una app.
   Donde no hay soporte, no pasa nada. */
@view-transition { navigation: auto; }
/* Un solo `.block` por documento (asi lo emite el builder): al pasar de ficha a
   leccion el rectangulo se desliza en vez de parpadear. Dos elementos con el
   mismo `view-transition-name` en una pagina abortarian la transicion. */
.block { view-transition-name: pageblock; }

/* ── 4 · Reset y base ─────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  background: var(--paper);
}
body {
  font-family: var(--sans);
  font-size: var(--step-0);
  line-height: 1.62;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  /* El papel lo pinta `html`: `body` va transparente para que el cielo de
     puntos (#sky, z-index -1) quede DETRAS de todo el contenido y aun asi se
     vea, porque el fondo que lo taparia esta un nivel mas arriba. */
  background: transparent;
  -webkit-font-smoothing: antialiased;
  min-height: 100dvh;
}
h1, h2, h3 {
  line-height: 1.08;
  letter-spacing: -.03em;
  font-weight: 700;
  font-stretch: 108%;
  text-wrap: balance;
}
h1 { font-size: var(--step-3); }
h2 { font-size: var(--step-2); }
h3 { font-size: var(--step-1); letter-spacing: -.02em; }
p { text-wrap: pretty; }
img, svg, video { display: block; max-width: 100%; height: auto; }
a { color: inherit; }
button { font: inherit; }

/* Enlace: tinta con subrayado de 2 px en el bloque (spec 1.1). Evita un token
   `--link` por gama: tinta sobre papel es par obligatorio, asi que AA esta
   garantizado en las 16 combinaciones. */
.prose a, .lnk {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  text-decoration-color: var(--block);
}
.prose a:hover, .lnk:hover { text-decoration-color: var(--ink); }

::selection { background: var(--block-soft); color: var(--ink); }
:where(a, button, summary, input, select, label, [tabindex]):focus-visible {
  outline: 2px solid var(--block);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ── 5 · Utilidades ───────────────────────────────────────────────────────── */
.wrap { width: 100%; max-width: var(--col); margin-inline: auto; padding-inline: var(--gutter); }
.narrow { max-width: 780px; }
.prose { max-width: var(--measure); }
.vh { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.center { text-align: center; }
.maxcard { max-width: 760px; }
/* Lo que solo tiene sentido con JS. Se cuelga de la AUSENCIA de `js` (la pone
   site.js al arrancar) y no de un `nojs` en el <head>: ese script se retiro al
   quedarse el sitio con una sola piel. */
html:not(.js) .jsonly { display: none; }

/* Salto al contenido: primer tabulador de cada pagina. */
.skip {
  position: absolute; inset-inline-start: -9999px; z-index: 100;
  padding: var(--s3) var(--s5);
  background: var(--card); color: var(--ink);
  border: 1px solid var(--block); border-radius: var(--r-card);
  text-decoration: none; font-size: var(--step--1); font-weight: 600;
}
.skip:focus { inset-inline-start: var(--gutter); inset-block-start: var(--s3); }

.eyebrow {
  font-family: var(--mono);
  font-size: .69rem;
  font-weight: 500;
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--muted);
}
.mono { font-family: var(--mono); font-size: var(--step--1); }
.num { font-family: var(--mono); font-size: var(--step--1); color: var(--muted); white-space: nowrap; }
.lead { font-size: var(--step-1); line-height: 1.45; max-width: 58ch; letter-spacing: -.012em; }

/* Ritmo vertical: pasos de la escala, nunca un pixel suelto. */
.mt3 { margin-block-start: var(--s3); }
.mt4 { margin-block-start: var(--s4); }
.mt5 { margin-block-start: var(--s5); }
.mt6 { margin-block-start: var(--s6); }
.mt7 { margin-block-start: var(--s7); }

/* ── 6 · Chrome: cabecera, menu de movil y el cielo de puntos ────────────── */
.hdr {
  position: relative; z-index: 60;
  background: var(--paper); border-block-end: 1px solid var(--cline);
}

/* El cielo: una reticula de puntos tenues a pantalla completa, detras de TODO
   (z-index negativo sobre el papel de `html`, con `body` transparente). Las
   tarjetas y los bloques son opacos, asi que solo se ve en el papel libre. Se
   enciende alrededor del puntero (site.js); sin puntero fino o con
   `prefers-reduced-motion` queda quieto, como textura. Nunca intercepta un
   click ni participa del scroll. */
#sky {
  position: fixed; inset: 0; z-index: -1;
  pointer-events: none;
  inline-size: 100%; block-size: 100%;
}
.hdr .inner {
  display: flex; align-items: center; gap: var(--s4);
  padding-block: var(--s2); position: relative;
}
.brand { display: flex; align-items: center; gap: var(--s2); text-decoration: none; color: var(--ink); }
.brand .mark { inline-size: 24px; block-size: 26px; color: var(--block); flex: none; }
.wordmark {
  font-family: var(--sans); font-weight: 800; font-stretch: 108%;
  letter-spacing: -.035em; font-size: 1.16rem; line-height: 1;
}
/* Solo los enlaces de texto: si esto fuese `.hdr nav a`, ganaria por
   especificidad al color de `.btn-pro` y el boton de la app saldria en muted. */
.hdr nav { display: flex; align-items: center; gap: var(--s5); margin-inline-start: auto; }
.hdr nav .nlink { font-size: var(--step--1); color: var(--muted); text-decoration: none; padding-block: var(--s2); }
.hdr nav .nlink:hover, .hdr nav .nlink[aria-current] { color: var(--ink); }
.hdr nav .nlink[aria-current] {
  background-image: linear-gradient(var(--block), var(--block));
  background-repeat: no-repeat; background-position: 0 100%; background-size: 100% 2px;
}
/* Bloque final: "Get the app". NO se pliega en el menu. */
.hdr-end { display: flex; align-items: center; gap: var(--s3); margin-inline-start: auto; }
.hdr nav + .hdr-end { margin-inline-start: 0; }

/* Menu de movil SIN JS. El <nav> va FUERA del <details>, como HERMANO suyo, y
   se revela con `[open] ~`.
   ⚠️ Lo obvio —meter la nav dentro del <details>— rompe el escritorio: el
   navegador oculta el contenido de un `details` cerrado aunque le pongas
   `display: contents`, asi que la navegacion desaparecia por completo. */
.menu { display: none; }
.menu > summary { list-style: none; }
.menu > summary::-webkit-details-marker { display: none; }
@media (max-width: 860px) {
  .menu {
    display: block; margin-inline-start: var(--s2); order: 3;
  }
  .menu > summary {
    display: grid; place-items: center; inline-size: 44px; block-size: 44px;
    border: 1px solid var(--cline-strong); border-radius: var(--r-field);
    color: var(--muted); cursor: pointer;
  }
  .menu[open] > summary { color: var(--ink); border-color: var(--ink); }
  .menu > summary svg { inline-size: 19px; block-size: 19px; }
  .menu .i-close { display: none; }
  .menu[open] .i-close { display: block; }
  .menu[open] .i-burger { display: none; }

  .hdr .inner { flex-wrap: wrap; }
  .hdr nav { display: none; }
  .hdr-end { order: 2; margin-inline-start: auto; }
  /* El panel cae a la segunda linea de la cabecera, a ancho completo. */
  .menu[open] ~ nav {
    display: flex; flex-direction: column; align-items: stretch; gap: 0;
    order: 9; flex-basis: 100%; margin-inline-start: 0;
    padding-block-end: var(--s3);
  }
  .menu[open] ~ nav .nlink {
    padding-block: .85rem; font-size: var(--step-0);
    border-block-start: 1px solid var(--cline);
  }
  .menu[open] ~ nav .nlink[aria-current] { background: none; color: var(--ink); }
}
/* Por debajo de 430 px la cabecera no da para marca + "Get the app" +
   hamburguesa en una linea, y una cabecera de dos pisos se come la primera
   pantalla en las ~270 paginas. Se pliega la PALABRA, no el emblema: la marca
   sigue ahi y el nombre sigue en el arbol de accesibilidad como texto del
   enlace, solo que fuera de pantalla. */
/* Tamaño tactil en movil: medido a 375 px, el selector de velocidad quedaba en
   31 px y la marca de la cabecera en 26. Ambos son controles, no texto en linea. */
@media (pointer: coarse) {
  .speed-sel { min-block-size: 44px; }
  .brand { min-block-size: 44px; }
}
@media (max-width: 560px) {
  .hdr .inner { gap: var(--s3); }
  .hdr-end { gap: var(--s2); }
}
@media (max-width: 430px) {
  .brand .wordmark {
    position: absolute; inline-size: 1px; block-size: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .hdr-end .btn-pro { font-size: .74rem; padding: .6rem .85rem; }
}

/* ── 7 · El bloque y el rail ──────────────────────────────────────────────── */
/* Toda pagina abre con el: rectangulo en `--block`, eyebrow arriba, h1 en
   `--on-block` y el dato protagonista. Sin sombra: es un plano, no una tarjeta.
   ⚠️ DENTRO DEL BLOQUE NO HAY TONO SECUNDARIO: el bloque es claro con tinta
   oscura y cualquier mezcla del TEXTO hacia transparente cae por debajo de AA. */
.block { background: var(--block); color: var(--on-block); }
.block .inner { padding-block: clamp(1.6rem, 4vw, 2.4rem) var(--s5); }
.block .eyebrow, .block .lead, .block .meta, .block a { color: var(--on-block); }
.block h1 { color: var(--on-block); max-width: 22ch; margin-block-start: var(--s3); }
.block .meta { font-family: var(--mono); font-size: var(--step--1); margin-block-start: var(--s3); }
.block .lead { margin-block-start: var(--s4); font-size: var(--step-0); max-width: 62ch; }
.block .head-row { display: flex; align-items: baseline; gap: var(--s4); justify-content: space-between; }
@media (min-width: 900px) {
  .block { border-end-start-radius: var(--r-group); border-end-end-radius: var(--r-group); }
}

/* Migas */
.crumbs { font-family: var(--mono); font-size: var(--step--1); color: var(--muted); padding-block: var(--s4) 0; }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--ink); }
.crumbs span { margin-inline: .4em; }
.crumbs .here { color: var(--ink); margin: 0; }

/* El rail (replica de _DeckProgressBar): un segmento por diapositiva, un aro
   por checkpoint. Cada uno es un <a href="#s7">: funciona sin una linea de JS. */
.rail {
  display: flex; align-items: center; gap: 3px;
  margin-block-start: var(--s5);
  inline-size: 100%;
}
.rail a { display: grid; place-items: center; block-size: 16px; text-decoration: none; }
.rail .seg { flex: 3 1 0; min-inline-size: 0; }
.rail .ring { flex: 0 0 14px; }
.rail i {
  display: block;
  background: color-mix(in srgb, var(--on-block) 62%, transparent);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.rail .seg i { block-size: 3px; inline-size: 100%; border-radius: var(--r-pill); }
.rail .ring i {
  inline-size: 9px; block-size: 9px; border-radius: var(--r-pill);
  background: none; border: 1.6px solid color-mix(in srgb, var(--on-block) 62%, transparent);
}
.rail .dim i { background: color-mix(in srgb, var(--on-block) 26%, transparent); }
.rail .ring.dim i { background: none; border-color: color-mix(in srgb, var(--on-block) 26%, transparent); }
.rail .seen i, .rail .here i { background: var(--on-block); }
.rail .ring.seen i, .rail .ring.here i { background: none; border-color: var(--on-block); }
/* Acertado: verde relleno con filete `on-block`, para que el verde se despegue
   del bloque en las ocho gamas (el verde solo no garantiza el 3,0 contra ambar). */
.rail .ring.done i { background: var(--success); border-color: var(--on-block); }
.rail .here i { outline: 2px solid color-mix(in srgb, var(--on-block) 40%, transparent); outline-offset: 3px; }
@media (max-width: 640px) { .rail { gap: 2px; } .rail .ring { flex: 0 0 12px; } }

/* Bloque de la leccion: pegado arriba y se compacta al desplazar (deck.js pone
   `is-compact` en <html>). El rail NO sale nunca de pantalla. */
/* En el mockup era `#lesson .block` porque las cuatro paginas convivian en un
   fichero; en produccion cada pagina es un documento, asi que la marca es una
   clase. Se acepta tambien `#lesson-block` (el id que deck.js mide) para que la
   plantilla funcione lleve una cosa, la otra o las dos. */
.block--lesson, #lesson-block { position: sticky; inset-block-start: 0; z-index: 50; }
.block--lesson h1, #lesson-block h1 { transition: font-size var(--dur) var(--ease), margin var(--dur) var(--ease); }
/* Todo lo que cambia al compactar transiciona a la vez y con la misma curva:
   asi el bloque se pliega como una pieza, no a saltos. */
.block--lesson .inner, #lesson-block .inner,
.block--lesson .rail, #lesson-block .rail,
.block-player { transition: padding var(--dur) var(--ease), margin var(--dur) var(--ease); }
.deck > .card { transition: max-height var(--dur) var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .block--lesson h1, #lesson-block h1, .block--lesson .inner, #lesson-block .inner,
  .block--lesson .rail, #lesson-block .rail, .block-player, .deck > .card { transition: none; }
}
html.is-compact .block--lesson .inner,
html.is-compact #lesson-block .inner { padding-block: var(--s3) var(--s3); }
html.is-compact .block--lesson h1,
html.is-compact #lesson-block h1 {
  font-size: var(--step-1); margin-block-start: var(--s2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
html.is-compact .block--lesson .rail,
html.is-compact #lesson-block .rail { margin-block-start: var(--s3); }

/* El reproductor vive DENTRO del bloque, bajo el rail (decision de Oriol
   2026-09-07: el audio se quiere siempre a mano, y la barra inferior se queda
   solo para navegar). Al compactarse el bloque se pliega el h1 y NADA MAS: el
   player sigue visible. */
.block-player {
  display: flex; align-items: center; gap: var(--s3);
  margin-block-start: var(--s4);
}
html.is-compact .block-player { margin-block-start: var(--s3); }
.block-player .audio { flex: 1 1 auto; min-inline-size: 0; block-size: 34px; }
.block-player .speed { flex: none; display: inline-flex; position: relative; }
/* Control de bloque: relleno de `on-block` al 12 % y texto en `on-block` puro
   (el texto NUNCA se mezcla dentro del bloque: caeria por debajo de AA).
   ⚠️ `appearance: none` no es cosmetica: con la apariencia nativa macOS pinta
   su propio menulist gris encima y el relleno del bloque no se ve (medido). */
.block-player .speed-sel {
  appearance: none; -webkit-appearance: none;
  font-family: var(--mono); font-size: var(--step--1); font-weight: 500;
  color: var(--on-block);
  background: color-mix(in srgb, var(--on-block) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--on-block) 28%, transparent);
  border-radius: var(--r-field);
  padding: .42rem 1.55rem .42rem .6rem; cursor: pointer; line-height: 1.2;
}
/* La flecha la dibuja la etiqueta, no una imagen: sin `appearance` el <select>
   se queda sin ninguna pista de que se despliega. */
.block-player .speed::after {
  content: "\25BE";
  position: absolute; inset-inline-end: .55rem; inset-block-start: 50%;
  transform: translateY(-50%);
  color: var(--on-block); font-size: .7rem; line-height: 1;
  pointer-events: none;
}
/* Sin JS el desplegable no haria nada: no se enseña. site.js pone `js` en
   <html> al arrancar. */
html:not(.js) .speed { display: none; }

/* ── 8 · La baraja horizontal (spec 4.2) ──────────────────────────────────── */
/* Paso lateral con scroll-snap: gesto nativo en movil, rueda/trackpad y flechas
   en escritorio. Las 21 tarjetas van en el HTML servido, en orden de lectura:
   una tarjeta desplazada a la derecha esta en el DOM y tiene layout, asi que
   indexa como contenido normal (spec 4.3). */
/* UNA tarjeta a la vez (decision de Oriol 2026-09-07): el peek se retira. El
   ancho de la baraja lo fija este envoltorio —760 px de tarjeta mas los dos
   margenes—, no `grid-auto-columns`, para que la columna pueda ser el 100 %
   exacto del scroller y la siguiente quede SIEMPRE fuera de pantalla.
   `.deck-wrap` va despues de `.wrap` en la hoja, asi que gana el max-width. */
.deck-wrap {
  padding-block: var(--s4) var(--s5);
  max-width: calc(760px + var(--gutter) * 2);
}
.deck-hint { font-family: var(--mono); font-size: .7rem; color: var(--muted); padding-block-end: var(--s3); display: block; }

.deck {
  display: grid; grid-auto-flow: column; gap: var(--s5);
  grid-auto-columns: 100%;
  overflow-x: auto; scroll-snap-type: x mandatory;
  scroll-padding-inline: 0;
  overscroll-behavior-x: contain;
  list-style: none; padding: 0 0 var(--s3); margin: 0;
}

.card {
  background: var(--card);
  border: 1px solid var(--cline);
  border-radius: var(--r-card);
  padding: var(--card-pad);
}
.card--slide, .card--check, .cut, .card--q {
  scroll-snap-align: start;
  scroll-margin-block-start: calc(var(--block-h) + var(--s4));
}
.deck > .card {
  /* `--card-max` lo mide deck.js sobre el DOM (§8). La formula queda de
     respaldo sin JS: es aproximada porque no puede saber cuanto ocupan las
     migas y el aviso de deslizar que hay entre el bloque y la baraja. */
  max-height: var(--card-max, calc(100dvh - var(--block-h) - var(--bar-h) - var(--s6)));
  overflow-y: auto;
  display: flex; flex-direction: column;
}
.deck > .card.is-playing { border-color: var(--block); }

/* Tarjeta de diapositiva */
.card--slide h2 { margin-block: var(--s3) var(--s4); }
.card-body { flex: 1 1 auto; }
.card-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  margin-block-start: var(--s5);
  /* El aire de abajo vive AQUI, no en el relleno de la tarjeta: la tarjeta
     desplaza por dentro (`overflow-y: auto`) y un contenedor con scroll no
     pinta su relleno inferior al final del contenido — el boton quedaba a 8 px
     del borde y a 17 del separador (Oriol 2026-09-07: «pegado a la base y con
     espacio mas grande encima»). Dentro del pie, el aire va en el flujo y se
     ve siempre. */
  padding-block: var(--card-pad);   /* simetrico: el boton queda centrado en su banda */
  border-block-start: 1px solid var(--cline);
}
/* Su pareja: la tarjeta que TIENE pie cede su relleno inferior al pie. Las que
   no lo tienen (checkpoint, cortes) lo conservan. */
.deck > .card--slide, .deck > .card--q { padding-block-end: 0; }
.card--slide.is-playing .num { color: var(--ink); font-weight: 600; }

/* ── 9 · Prosa y tablas ───────────────────────────────────────────────────── */
/* `.prose` es el UNICO ambito bajo el que se estiliza HTML de contenido. */
.prose > * + * { margin-block-start: var(--s4); }
.prose p { font-size: var(--step-0); line-height: 1.62; }
.prose strong { font-weight: 650; }
.prose h3 { margin-block-start: var(--s5); }
.prose ul, .prose ol { padding-inline-start: 1.15rem; display: grid; gap: var(--s2); }
.prose li { line-height: 1.55; }
.prose li::marker { color: var(--block); }
.prose code {
  font-family: var(--mono); font-size: .9em;
  background: var(--block-soft); color: var(--ink);
  border-radius: 6px; padding: .1em .35em;
}
.prose pre[data-lang] {
  background: var(--paper); border: 1px solid var(--cline); border-radius: var(--r-field);
  padding: var(--s4); overflow-x: auto; font-family: var(--mono); font-size: var(--step--1);
}
.prose pre[data-lang]::before {
  content: attr(data-lang); display: block; font-family: var(--mono); font-size: .68rem;
  letter-spacing: .17em; text-transform: uppercase; color: var(--muted); margin-block-end: var(--s2);
}
.prose pre code { background: none; padding: 0; }
.prose audio { inline-size: 100%; }

.tblwrap { overflow-x: auto; margin-block: var(--s5); }
.tbl { inline-size: 100%; border-collapse: collapse; font-size: var(--step--1); }
.tbl th {
  font-family: var(--mono); font-size: .7rem; font-weight: 500; letter-spacing: .1em;
  text-transform: uppercase; text-align: left; color: var(--muted);
  background: var(--block-soft); padding: .7rem .8rem; vertical-align: bottom;
}
.tbl td { padding: .7rem .8rem; border-block-end: 1px solid var(--cline-strong); vertical-align: top; line-height: 1.45; }
.tbl tr:last-child td { border-block-end: 0; }
.tbl .k { font-family: var(--mono); white-space: nowrap; color: var(--muted); }

/* ── 10 · Checkpoint (spec 4.4) ───────────────────────────────────────────── */
.card--check .check-q { font-size: var(--step-1); font-weight: 600; line-height: 1.34; letter-spacing: -.015em; margin-block-start: var(--s3); }
.opts { list-style: none; display: grid; gap: var(--s2); padding: 0; margin-block-start: var(--s4); }
.opt { border: 1px solid var(--cline); border-radius: var(--r-field); background: var(--card); }
.opt summary {
  display: flex; gap: var(--s3); align-items: flex-start;
  padding: 13px 14px; cursor: pointer; list-style: none;
  font-size: var(--step--1); line-height: 1.45; color: var(--ink);
}
.opt summary::-webkit-details-marker { display: none; }
.opt summary::marker { content: ""; }
.badge {
  flex: none; inline-size: 26px; block-size: 26px;
  display: grid; place-items: center;
  border-radius: var(--r-card);
  background: var(--block-soft); color: var(--ink);
  border: 1px solid var(--cline);
  font-family: var(--mono); font-size: .78rem; font-weight: 600; line-height: 1;
}
.why {
  margin: 0 14px 13px; padding: 12px 14px;
  font-size: var(--step--1); line-height: 1.5; color: var(--ink);
  border-radius: var(--r-field);
}
/* Estado: color fijo fuera de gama Y glifo, siempre (DS2 4). El tinte va sobre
   `card`, nunca sobre el papel: al 9 % el glifo se mantiene por encima de AA. */
.opt.ok:has(details[open]) {
  border-color: var(--success); border-width: 2px;
  background: color-mix(in srgb, var(--success) 9%, var(--card));
}
.opt.ok details[open] .badge { background: var(--success); border-color: var(--success); color: var(--on-success); font-size: 0; }
.opt.ok details[open] .badge::after { content: "\2713"; font-size: .85rem; }
.opt.ok .why { background: color-mix(in srgb, var(--success) 7%, var(--card)); border-inline-start: 3px solid var(--success); }
.opt:not(.ok):has(details[open]) {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 9%, var(--card));
}
.opt:not(.ok) details[open] .badge { background: var(--danger); border-color: var(--danger); color: var(--on-danger); font-size: 0; }
.opt:not(.ok) details[open] .badge::after { content: "\2717"; font-size: .85rem; }
.opt:not(.ok) .why { background: color-mix(in srgb, var(--danger) 7%, var(--card)); border-inline-start: 3px solid var(--danger); }
.opt:hover:not(:has(details[open])) { border-color: var(--block); }
.card--check.is-solved .opt:not(.ok) summary { cursor: default; }
.card--check.is-solved .eyebrow { color: var(--success); }

/* ── 11 · Corte hacia la app (spec 4.6) ───────────────────────────────────── */
.cut { border: 2px solid var(--block); }
.cut h2 { font-size: var(--step-2); }
.cut p { margin-block-start: var(--s4); color: var(--ink); max-width: 46ch; }
.acts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }
.cut .acts { margin-block-start: var(--s5); }

/* ── 12 · Botones ─────────────────────────────────────────────────────────── */
.btn, .btn-pro, .btn-ghost {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  font: inherit; font-weight: 600; font-size: var(--step--1);
  padding: .72rem 1.2rem; border-radius: var(--r-card); border: 1px solid transparent;
  text-decoration: none; cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.btn { background: var(--block); color: var(--on-block); }
.btn:hover { background: color-mix(in srgb, var(--block) 86%, var(--ink)); }
/* Premium = el UNICO relleno de tinta del sitio. Igual en las ocho gamas, y por
   eso se reconoce sin leerlo. */
.btn-pro { background: var(--premium); color: var(--on-premium); }
.btn-pro:hover { background: color-mix(in srgb, var(--premium) 88%, var(--block)); }
/* El rombo de premium. El mockup lo trae como <svg class="pmark">; las
   plantillas del builder, como <span class="pmark"> vacio — se dibuja aqui para
   que valgan los dos y la marca no dependa de repetir un SVG en cada boton. */
.btn-pro .pmark { inline-size: 13px; block-size: 13px; flex: none; }
.btn-pro .pmark:not(svg) {
  inline-size: 9px; block-size: 9px; border-radius: 1px;
  background: currentColor; transform: rotate(45deg);
}
.btn-ghost { background: none; color: var(--ink); border-color: var(--cline-strong); font-size: .78rem; padding: .5rem .85rem; }
.btn-ghost:hover { border-color: var(--block); }
.btn-sm { padding: .55rem .95rem; }
button[disabled] { opacity: .6; cursor: progress; }

/* Sobre un bloque pintado la accion se invierte: el papel es el relleno. */
.block .btn { background: var(--on-block); color: var(--block); }
.block .btn:hover { background: color-mix(in srgb, var(--on-block) 88%, var(--block)); }
.block .btn-ghost { color: var(--on-block); border-color: color-mix(in srgb, var(--on-block) 45%, transparent); }
.block .btn-ghost:hover { border-color: var(--on-block); }

/* ── 13 · Barra inferior: navegacion con el CTA en el centro ──────────────── */
/* El reproductor se mudo al bloque de la leccion (§7). Aqui quedan las dos
   anclas, una a cada extremo, y entre ellas el unico boton premium que sigue al
   lector toda la leccion. Las tres funcionan sin una linea de JS. */
.bar {
  position: sticky; inset-block-end: 0; z-index: 40;
  background: var(--paper); border-block-start: 1px solid var(--cline);
}
.bar .inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); padding-block: var(--s3);
}
/* Tres columnas de verdad: con `space-between` el boton del medio se descoloca
   en cuanto «Previous» y «Next card» miden distinto, y el CTA tiene que caer en
   el eje de la pagina. El `:has()` es deliberado —una barra sin CTA, o un
   navegador sin `:has()`, se queda con el flex de arriba, que es exactamente el
   reparto de siempre. */
.bar .inner:has(.bar-cta) { display: grid; grid-template-columns: 1fr auto 1fr; }
.bar .inner > :first-child { justify-self: start; }
.bar .inner > .bar-cta { justify-self: center; }
.bar .inner > :last-child { justify-self: end; }
/* Barra de la practica: solo el CTA, centrado. Va DESPUES del `:has()` y con
   `!important` no, con especificidad: `.bar--cta .inner` empata a 0-2-0 contra
   el `:has()` (0-3-0), asi que se acota igual con `:has()` para ganar. */
.bar--cta .inner:has(.bar-cta), .bar--cta .inner { display: flex; justify-content: center; }
/* En la primera tarjeta «Previous» no lleva a ningun sitio: se atenua y deja de
   recibir el puntero (deck.js pone `aria-disabled`). Sigue siendo enfocable,
   que es justo lo que un `aria-disabled` promete. */
.bar [aria-disabled="true"] { opacity: .42; pointer-events: none; }

/* El CTA se encoge antes que sus vecinos: por debajo de 640 px se retira la
   promesa larga y queda «Get the app». Es `display: none` a proposito —sale
   tambien del arbol de accesibilidad— porque la frase corta se vale sola y
   media promesa leida en voz alta seria peor que ninguna. */
@media (max-width: 640px) {
  .bar-cta .long { display: none; }
  .bar .inner { gap: var(--s3); }
}
/* Por debajo de 420 px las tres piezas no caben con texto: se pliegan las DOS
   flechas —nunca el CTA— y la barra queda «← · Get the app · →». La flecha la
   dibuja el pseudoelemento y el texto del enlace sigue intacto en el arbol de
   accesibilidad (mismo truco que la chapa del checkpoint).
   ⚠️ Se pliega tambien «Next card» y no solo «Previous» porque, con texto, el
   boton de la derecha parte en dos lineas y la barra se hace de dos pisos: eso
   se come una pantalla de movil entera (medido: 400 px de ancho). */
@media (max-width: 420px) {
  .bar .inner { gap: var(--s2); }
  .bar-cta { padding-inline: .9rem; }
  /* Los tres controles con el MISMO alto: plegar el texto a una flecha dejaba
     el boton mas bajo que el CTA y la barra se veia desigual. */
  .bar .inner > a { min-block-size: 44px; }
  #prevcard, #nextcard { font-size: 0; padding-inline: .8rem; }
  #prevcard::before, #nextcard::before { font-size: .78rem; line-height: 1.45; }
  #prevcard::before { content: "\2190"; }
  #nextcard::before { content: "\2192"; }
}

/* ── 14 · Ficha, hub, indice y practica ───────────────────────────────────── */
.stack { display: grid; gap: var(--s4); }
.section { padding-block: var(--s6); }
.section > h2 { margin-block-end: var(--s4); }

.card--lesson { display: flex; flex-wrap: wrap; gap: var(--s4); align-items: flex-start; text-decoration: none; transition: border-color var(--dur) var(--ease); }
.card--lesson:hover { border-color: var(--block); }
/* Dos acciones por leccion (4.a ronda): la tarjeta deja de ser un <a> y pasa a
   ser una caja con el enlace de la leccion (`.who`) y el de su test (`.go`).
   El boton se va al final en escritorio; por debajo de 560 px baja debajo y
   ocupa la fila entera, que es donde el pulgar lo alcanza. */
.card--lesson .who { display: flex; gap: var(--s4); align-items: flex-start; flex: 1 1 260px; min-inline-size: 0; text-decoration: none; color: inherit; }
.card--lesson .go { flex: none; margin-inline-start: auto; align-self: center; display: flex; gap: var(--s2); }
@media (max-width: 560px) {
  .card--lesson .go { margin-inline-start: 0; inline-size: 100%; }
  .card--lesson .go > a { flex: 1; text-align: center; }
}
.card--lesson .n {
  flex: none; inline-size: 44px; block-size: 44px; display: grid; place-items: center;
  border-radius: var(--r-field); background: var(--block-soft); color: var(--ink);
  font-family: var(--mono); font-size: .85rem; font-weight: 600;
}
.card--lesson .t { display: block; font-weight: 650; font-size: var(--step-1); letter-spacing: -.02em; line-height: 1.25; }
.card--lesson .m { display: block; font-family: var(--mono); font-size: var(--step--1); color: var(--muted); margin-block-start: var(--s2); }

/* Selector de categoria del catalogo. Filtra SIN UNA LINEA DE JS: son radios
   `.vh` y `:has()` sobre `.catalog`. Las puertas ocultas siguen en el DOM
   —`display: none` no las borra del HTML servido, y Google lee el HTML—, pero
   quien llega por «AWS certifications» ve solo AWS. site.js solo aporta el
   plan B para navegadores sin `:has()`. */
.picker {
  display: inline-flex; align-items: center; gap: var(--s1);
  max-inline-size: 100%; overflow-x: auto; overscroll-behavior-x: contain;
  padding: var(--s1);
  border: 1px solid var(--cline); border-radius: var(--r-group);
  margin-block-end: var(--s5);
}
.picker label {
  display: block; white-space: nowrap; cursor: pointer;
  padding: .5rem 1rem; border-radius: var(--r-pill);
  font-size: var(--step--1); font-weight: 600; color: var(--muted);
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.picker label:hover { color: var(--ink); }
/* El radio esta fuera de pantalla (`.vh`), asi que el foco se dibuja sobre su
   etiqueta o no se ve. */
.picker input:checked + label { background: var(--block); color: var(--on-block); }
.picker input:focus-visible + label { outline: 2px solid var(--block); outline-offset: 2px; }

/* Aire del catalogo bajo el lead (antes lo daba `.doors.mt6`). */
.catalog { margin-block-start: var(--s6); }
/* Ficha: lo que se aprende, como chips (sin conteos: spec ronda 2). */
.topics { list-style: none; margin: var(--s4) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s2); }
.topics li { padding: var(--s2) var(--s3); border-radius: var(--r-field); background: var(--block-soft); color: var(--ink); font-size: var(--step--1); line-height: 1.35; }
.mute { color: var(--muted); font-size: var(--step--1); }

.catalog:has(#cat-microsoft:checked)  .door:not([data-cat="microsoft"])  { display: none; }
.catalog:has(#cat-aws:checked)        .door:not([data-cat="aws"])        { display: none; }
.catalog:has(#cat-databricks:checked) .door:not([data-cat="databricks"]) { display: none; }
.catalog:has(#cat-snowflake:checked)  .door:not([data-cat="snowflake"])  { display: none; }
.catalog:has(#cat-courses:checked)    .door:not([data-cat="courses"])    { display: none; }
/* Plan B de site.js donde `:has()` no existe (Safari < 15.4, Firefox < 121). */
.door.is-out { display: none; }

/* El hub son ocho colores sobre papel: cada puerta lleva su gama de familia. */
.doors { display: grid; gap: var(--s4); grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.door {
  display: block; text-decoration: none; color: inherit;
  border: 1px solid var(--cline); border-radius: var(--r-card); overflow: hidden;
  background: var(--card); transition: border-color var(--dur) var(--ease);
}
.door:hover { border-color: var(--block); }
.door .cap { display: block; background: var(--block); color: var(--on-block); padding: var(--s5) var(--s4) var(--s4); }
.door .cap .eyebrow { color: var(--on-block); }
.door .cap .t { font-weight: 700; font-stretch: 108%; font-size: var(--step-1); letter-spacing: -.025em; margin-block-start: var(--s2); line-height: 1.15; }
.door .cap .eyebrow, .door .cap .t { display: block; }
.door .body { display: block; padding: var(--s4); }
.door .body .d { display: block; font-size: var(--step--1); color: var(--muted); line-height: 1.5; }
.door .body .m { display: block; font-family: var(--mono); font-size: .7rem; color: var(--muted); margin-block-start: var(--s3); }

/* La puerta al mock exam en la ficha y en el indice de practica: la misma
   `.door` del hub, pero sola en su fila y acotada a la medida de la baraja. */
.door--mock { max-inline-size: 760px; }

/* Mock exam (spec 5 bis): los datos del examen y los pesos por dominio. */
.exam-facts { padding: var(--card-pad); }
.exam-facts .figs { margin: 0; }
.weights { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }
.weights li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s2) var(--s4); align-items: center; }
.weights .w-label { font-family: var(--mono); font-size: var(--step--1); color: var(--ink); }
.weights .w-pct { font-family: var(--mono); font-size: var(--step--1); color: var(--muted); }
.weights .w-bar { grid-column: 1 / -1; height: 8px; border-radius: var(--r-pill); background: var(--block-soft); overflow: hidden; }
.weights .w-fill { display: block; height: 100%; width: var(--w, 0%); background: var(--block); border-radius: inherit; }

/* La practica es una BARAJA POR SET (Oriol, ronda 3): la lista larga se leia
   como un examen resuelto, y lo que se quiere es la misma tarjeta a pantalla
   que en la leccion. Cada `<section class="set">` trae su rail y su deck. */
.set { margin-block-start: var(--s7); max-inline-size: 760px; }
.set-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4); flex-wrap: wrap;
  font-family: var(--mono); font-size: var(--step--1); color: var(--muted);
  padding-block-end: var(--s3); border-block-end: 1px solid var(--cline-strong);
}
.set-head .t { color: var(--ink); letter-spacing: .06em; text-transform: uppercase; }

/* El rail de un set va sobre PAPEL, no sobre el bloque: los segmentos se pintan
   con la gama del producto (`--block`), no con `--on-block`, que aqui seria
   tinta oscura sobre fondo oscuro. */
.rail--set { margin-block-start: var(--s4); }
/* En el mock el rail no cuelga de un `.set`: va directo bajo `.deck-wrap`. */
.deck-wrap > .rail--set { margin-block-end: var(--s4); }
.rail--set i { background: color-mix(in srgb, var(--block) 62%, transparent); }
.rail--set .dim i { background: color-mix(in srgb, var(--block) 22%, transparent); }
.rail--set .seen i, .rail--set .here i { background: var(--block); }
.rail--set .here i { outline-color: color-mix(in srgb, var(--block) 40%, transparent); }
/* Acertada / fallada: color fijo fuera de gama, como en el checkpoint. */
.rail--set .done i { background: var(--success); }
.rail--set .miss i { background: var(--danger); }

/* Misma baraja que la leccion —una tarjeta por pantalla, snap, sin peek— pero
   sin bloque pegajoso del que descontar altura: el techo es la pantalla. */
.deck--set { margin-block-start: var(--s4); }
.deck--set > .card { max-height: 80dvh; scroll-margin-block-start: var(--s6); }

.card--q .check-q { font-size: var(--step-1); font-weight: 600; line-height: 1.34; letter-spacing: -.015em; margin-block-start: var(--s3); }
/* La tarjeta es una columna flexible (`.deck > .card`): el `auto` de abajo del
   reveal es lo que empuja el pie —«Next» y el contador— al fondo. */
.deck--set .card--q .reveal { margin-block-end: auto; }
/* Las opciones son ESTATICAS en el HTML: sin JS se leen y `details.reveal`
   descubre cual era la buena; con JS el clic las resuelve (deck.js §7). */
.opts--q .opt { display: flex; gap: var(--s3); align-items: flex-start; padding: 13px 14px; font-size: var(--step--1); line-height: 1.45; }
html.js .opts--q .opt { cursor: pointer; }
html.js .card--q:not(.is-solved) .opts--q .opt:hover { border-color: var(--block); }
/* Correcta: sin JS la descubre el <details>; con JS, el clic. El mismo par de
   reglas sirve a los dos caminos. */
.card--q:has(.reveal[open]) .opt.ok,
.opts--q .opt.is-ok {
  border-color: var(--success); border-width: 2px; padding: 12px 13px;
  background: color-mix(in srgb, var(--success) 9%, var(--card));
}
.card--q:has(.reveal[open]) .opt.ok .badge,
.opts--q .opt.is-ok .badge { background: var(--success); border-color: var(--success); color: var(--on-success); font-size: 0; }
.card--q:has(.reveal[open]) .opt.ok .badge::after,
.opts--q .opt.is-ok .badge::after { content: "\2713"; font-size: .85rem; }
/* Fallada: solo existe con JS (es la que ELIGIO el lector). Va despues para que
   gane a cualquier verde heredado. */
.opts--q .opt.is-ko {
  border-color: var(--danger); border-width: 2px; padding: 12px 13px;
  background: color-mix(in srgb, var(--danger) 9%, var(--card));
}
.opts--q .opt.is-ko .badge { background: var(--danger); border-color: var(--danger); color: var(--on-danger); font-size: 0; }
.opts--q .opt.is-ko .badge::after { content: "\2717"; font-size: .85rem; }

/* «Show the answer»: el unico control de la practica sin JS. */
.reveal { margin-block-start: var(--s4); }
.reveal > summary {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-family: var(--mono); font-size: var(--step--1); font-weight: 500;
  color: var(--muted); cursor: pointer; list-style: none;
  border: 1px solid var(--cline-strong); border-radius: var(--r-field);
  padding: .5rem .8rem;
}
.reveal > summary::-webkit-details-marker { display: none; }
.reveal > summary::marker { content: ""; }
.reveal > summary::after { content: "+"; }
.reveal[open] > summary::after { content: "\2212"; }
.reveal > summary:hover { color: var(--ink); border-color: var(--block); }
.q-why {
  margin-block-start: var(--s4); padding: 12px 14px; border-radius: var(--r-field);
  background: var(--block-soft); color: var(--ink); font-size: var(--step--1); line-height: 1.5;
}


/* ── 16 · Landing ─────────────────────────────────────────────────────────── */
/* El hero es una variante del bloque: mismo plano de color, sin cielo, sin
   velos y sin video. Se retira el 100svh de la version Observatory. */
.hero .inner { padding-block: clamp(2.4rem, 7vw, 4.4rem) clamp(2rem, 5vw, 3.2rem); }
.hero .wordmark { display: block; font-size: var(--step-4); line-height: 1; color: var(--on-block); }
.hero .lead { font-size: var(--step-1); max-width: 46ch; margin-block-start: var(--s5); }
.hero .acts { margin-block-start: var(--s6); }
.hero .note { font-family: var(--mono); font-size: var(--step--1); margin-block-start: var(--s4); color: var(--on-block); }

/* Fila de datos en mono. Dentro del bloque no hay tono secundario: las dos
   lineas van en `--on-block`. */
.figs { list-style: none; padding: 0; margin-block-start: var(--s6); display: flex; flex-wrap: wrap; gap: var(--s4) var(--s7); }
.fig-n { display: block; font-family: var(--mono); font-size: var(--step-2); font-weight: 600; letter-spacing: -.02em; line-height: 1.1; }
.fig-l { display: block; font-family: var(--mono); font-size: var(--step--1); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin-block-start: var(--s1); }
.block .fig-l { color: var(--on-block); }

/* Titular de seccion */
.sec-h { margin-block-end: var(--s3); }
.sec-p { color: var(--muted); max-width: 58ch; }
.center .sec-p { margin-inline: auto; }

/* Los dos paneles enfrentados: banco de preguntas contra academia. */
.duo { display: grid; gap: var(--s4); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); margin-block-start: var(--s6); }
.vs-card h3 { margin-block-end: var(--s4); }
.vs-card--us { border-color: var(--block); }
.vs-list { list-style: none; padding: 0; display: grid; gap: var(--s3); }
.vs-list li { display: flex; gap: var(--s3); align-items: flex-start; font-size: var(--step--1); line-height: 1.5; }
/* Color fijo Y glifo, siempre: el signo tiene que leerse sin ver el color. */
.mark { flex: none; font-family: var(--mono); font-weight: 700; line-height: 1.5; }
.mark--no { color: var(--danger); }
.mark--yes { color: var(--success); }

/* Los pilares de la academia */
.pillars { display: grid; gap: var(--s4); grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); margin-block-start: var(--s6); }
.pillars .card h3 { margin-block: var(--s4) var(--s3); }
.pillars .card p { font-size: var(--step--1); line-height: 1.55; color: var(--muted); }
.ico { inline-size: 26px; block-size: 26px; color: var(--block); fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

/* Lista de espera. El honeypot `.hp` y el contrato con subscribe.php (campos
   `email` y `website`) no se tocan: el PHP responde 200 a un bot que rellena
   `website` para no ensenarle que lo hemos detectado. */
.wl { padding-block: var(--s8); }
.wl-form { display: flex; gap: var(--s3); margin-block: var(--s5) var(--s4); max-width: 520px; }
@media (max-width: 560px) { .wl-form { flex-direction: column; } }
.wl-form input[type="email"] {
  flex: 1 1 auto; min-inline-size: 0; font: inherit; font-size: var(--step--1);
  color: var(--ink); background: var(--card);
  border: 1px solid var(--cline-strong); border-radius: var(--r-card);
  padding: .72rem 1rem;
}
.wl-form input[type="email"]::placeholder { color: var(--muted); }
.wl-form input[type="email"][aria-invalid="true"] { border-color: var(--danger); }
.hp { position: absolute; inset-inline-start: -9999px; inline-size: 1px; block-size: 1px; overflow: hidden; }
.wl-consent { font-size: var(--step--1); color: var(--muted); max-width: 58ch; }
.wl-msg { margin-block-start: var(--s4); font-size: var(--step--1); min-block-size: 22px; }
.wl-msg.ok { color: var(--success); }
.wl-msg.err { color: var(--danger); }

/* Banda de cierre: sale en todas las paginas del builder, asi que no puede
   quedarse sin estilo. */
.cta {
  background: var(--card); border-block-start: 1px solid var(--cline);
  margin-block-start: var(--s7); padding-block: var(--s8);
}
/* Tras una barra pegajosa (leccion, practica) la banda va PEGADA: con el aire
   de --s7 en medio, al llegar al final el cielo asomaba entre las dos (Oriol:
   «el efecto ahi en medio queda roto»). */
.bar + .cta { margin-block-start: 0; }
.cta h2 { margin-block-end: var(--s3); }
.cta p { color: var(--muted); max-width: 52ch; }
.cta .acts { margin-block-start: var(--s5); }
.cta.center p { margin-inline: auto; }
.cta.center .acts { justify-content: center; }

/* Premium sobre un bloque pintado (solo el hero): el relleno de tinta se queda
   corto contra el azul; el filete on-block lo despega (medido, carril C). */
.block .btn-pro { border: 1px solid var(--on-block); }

/* ── 16b · Calendario y blog (spec 2026-09-08) ────────────────────────────── */
/* Linea de tiempo. NO hay rejilla mensual a proposito: con 4-8 hechos al mes,
   veintiseis dias vacios se leen como abandono. Cada hito es una fila con su
   fecha en mono a la izquierda, el tipo como etiqueta y el cuerpo a la derecha. */
.tl { list-style: none; margin: var(--s4) 0 0; padding: 0; display: grid; gap: var(--s3); }
.tl-month { font-family: var(--mono); font-size: var(--step--1); letter-spacing: .17em;
            text-transform: uppercase; color: var(--muted); margin-block-start: var(--s6); }
.tl-item {
  display: grid; gap: var(--s2) var(--s4); align-items: start;
  grid-template-columns: 10.5rem auto; grid-template-areas: "date kind" "body body";
  background: var(--card); border: 1px solid var(--cline); border-radius: var(--r-card);
  padding: var(--card-pad);
}
@media (min-width: 780px) {
  .tl-item { grid-template-columns: 10.5rem 12rem minmax(0, 1fr); grid-template-areas: "date kind body"; }
}
.tl-date { grid-area: date; font-family: var(--mono); font-size: var(--step--1); color: var(--muted); white-space: nowrap; }
.tl-kind { grid-area: kind; justify-self: start; font-family: var(--mono); font-size: .72rem;
           letter-spacing: .1em; text-transform: uppercase; padding: var(--s1) var(--s3);
           border-radius: var(--r-pill); background: var(--block-soft); color: var(--ink); }
.tl-body { grid-area: body; display: grid; gap: var(--s2); min-width: 0; }
.tl-t { font-weight: 650; line-height: 1.3; }
.tl-d { color: var(--muted); font-size: var(--step--1); line-height: 1.55; }
.tl-src { font-family: var(--mono); font-size: .72rem; color: var(--muted); }
/* El filtro del calendario reusa el `.picker`: mismas reglas `:has()` que el hub,
   aplicadas ademas a las filas de la linea de tiempo y de la tabla de estado. */
.catalog:has(#cat-microsoft:checked)  .tl-item:not([data-cat="microsoft"]),
.catalog:has(#cat-microsoft:checked)  tr[data-cat]:not([data-cat="microsoft"]),
.catalog:has(#cat-aws:checked)        .tl-item:not([data-cat="aws"]),
.catalog:has(#cat-aws:checked)        tr[data-cat]:not([data-cat="aws"]),
.catalog:has(#cat-databricks:checked) .tl-item:not([data-cat="databricks"]),
.catalog:has(#cat-databricks:checked) tr[data-cat]:not([data-cat="databricks"]),
.catalog:has(#cat-snowflake:checked)  .tl-item:not([data-cat="snowflake"]),
.catalog:has(#cat-snowflake:checked)  tr[data-cat]:not([data-cat="snowflake"]) { display: none; }

/* Indice del blog: FILAS de lectura, no tarjetas. En una lista donde lo unico
   que decide el clic es el titular, la caja alrededor solo anade ruido. */
.rows { display: grid; }
.row-stack {
  display: grid; gap: var(--s2); padding: var(--s5) 0;
  border-block-start: 1px solid var(--cline); text-decoration: none; color: inherit;
  transition: background var(--dur) var(--ease);
}
.rows > .row-stack:last-child { border-block-end: 1px solid var(--cline); }
.row-stack:hover { background: var(--block-soft); }
.row-stack .ttl { font-size: var(--step-2); font-weight: 700; font-stretch: 108%;
                  letter-spacing: -.02em; line-height: 1.2; text-wrap: balance; }
.row-stack .desc { color: var(--muted); max-inline-size: var(--measure); }
/* Enlace que SALE del sitio: la fuente del vendor. */
.link-out { white-space: nowrap; }

/* ── 17 · Pie ─────────────────────────────────────────────────────────────── */
.ftr { border-block-start: 1px solid var(--cline); padding-block: var(--s6) var(--s8); margin-block-start: var(--s7); }
.ftr-grid { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s4) var(--s6); justify-content: space-between; }
.ftr-links { display: flex; flex-wrap: wrap; gap: var(--s3) var(--s5); }
.ftr-links a { font-size: var(--step--1); color: var(--muted); text-decoration: none; }
.ftr-links a:hover { color: var(--ink); }
.ftr-legal { margin-block-start: var(--s6); font-size: var(--step--1); color: var(--muted); max-width: 72ch; }
.ftr-legal a { color: var(--muted); }

/* ── 18 · Impresion y movimiento reducido ─────────────────────────────────── */
/* La vista vertical de la baraja vive SOLO aqui: en pantalla es horizontal
   siempre (no hay "Show as a list"). */
@media print {
  .deck-wrap { max-width: none; }
  .deck { grid-auto-flow: row; overflow: visible; gap: var(--s5); }
  .deck > .card { max-height: none; overflow: visible; }
  .bar, .menu, .skip, .hdr nav, .hdr-end, .deck-hint, #sky, .block-player { display: none; }
  .block--lesson, #lesson-block { position: static; }
  /* En papel el filtro no tiene sentido: se imprime el catalogo entero. */
  .picker { display: none; }
  .door, .door.is-out { display: block !important; }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .deck { scroll-snap-type: none; }
  * { transition-duration: 1ms !important; }
}
