/* ═══════════════════════════════════════════════════════════════════════════
   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 (Starrynight) ─────────────────────────────── */
/* ⚠️ 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.
   2026-09-21: papel y tarjeta pasan a los de la skin Starrynight de la app
   (`kDs2Space` / `kDs2SpaceCard`, lib/theme/ds2_palette.dart): el mismo cielo
   nocturno del splash y el onboarding, para que web y app sean un producto. */
:root {
  color-scheme: dark;

  --paper: #05070B;
  --card: #101722;
  --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);

  /* Glass (port de `ds2GlassDecoration`, lib/theme/ds2_glass.dart): tinte del
     bloque al 5,5 % sobre la tarjeta, reflejo diagonal de tinta y opacidad
     0,88-0,96. SIN blur: es un degradado estatico, como en la app. Con esa
     opacidad las estrellas apenas asoman y el contraste del texto no cambia
     (medido: tinta ≥ 14:1 y `--muted` ≥ 5,7:1 en las ocho gamas, aun con una estrella detras). El cuerpo
     se declara aqui y en cada gama (§3), porque un var() se resuelve donde se
     declara; el degradado se pinta en §17b, en el propio elemento. */
  --glass-body: color-mix(in srgb, var(--block) 5.5%, var(--card));
  --glass-edge: color-mix(in srgb, var(--ink) 17%, transparent);
  --glass-edge-strong: color-mix(in srgb, var(--ink) 24%, transparent);
  /* Oro: SOLO la cifra de mercado del hero de la home (Oriol 2026-09-21). Es
     la unica excepcion al «cero oro» del DS2: no marca premium ni botones. */
  --gold: #E8C36A;
  --gold-deep: #B8892E;
  --gold-hi: #FFF3CC;
  /* Rango salarial de los paneles de rol: desde (naranja) hasta (verde). */
  --sal-lo: #F2A65A;
  --sal-hi: #6FD08C;
  /* Marco de telefono generico del cilindro de la home (§16c): bisel e isla. */
  --fon-hi: #3A3F49;
  --fon-mid: #14171C;
  --fon-lo: #0C0E12;
  --fon-island: #07090C;
  /* Telefono plateado del escaparate de pilares. */
  --silver-hi: #C9CED6;
  --silver-mid: #8D949E;
  --silver-lo: #4E545D;
  /* Chrome pegajoso (cabecera, barra inferior): el papel al 78 %. */
  --glass-chrome: color-mix(in srgb, var(--paper) 78%, transparent);

  --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));
                           --glass-body: color-mix(in srgb, var(--g-azul) 5.5%, var(--card)); }
[data-block="indigo"],   .fam-indigo   { --block: var(--g-indigo);   --block-soft: color-mix(in srgb, var(--g-indigo) 12%, var(--card));
                           --glass-body: color-mix(in srgb, var(--g-indigo) 5.5%, var(--card)); }
[data-block="ambar"],    .fam-ambar    { --block: var(--g-ambar);    --block-soft: color-mix(in srgb, var(--g-ambar) 12%, var(--card));
                           --glass-body: color-mix(in srgb, var(--g-ambar) 5.5%, var(--card)); }
[data-block="teja"],     .fam-teja     { --block: var(--g-teja);     --block-soft: color-mix(in srgb, var(--g-teja) 12%, var(--card));
                           --glass-body: color-mix(in srgb, var(--g-teja) 5.5%, var(--card)); }
[data-block="turquesa"], .fam-turquesa { --block: var(--g-turquesa); --block-soft: color-mix(in srgb, var(--g-turquesa) 12%, var(--card));
                           --glass-body: color-mix(in srgb, var(--g-turquesa) 5.5%, var(--card)); }
[data-block="verde"],    .fam-verde    { --block: var(--g-verde);    --block-soft: color-mix(in srgb, var(--g-verde) 12%, var(--card));
                           --glass-body: color-mix(in srgb, var(--g-verde) 5.5%, var(--card)); }
[data-block="oliva"],    .fam-oliva    { --block: var(--g-oliva);    --block-soft: color-mix(in srgb, var(--g-oliva) 12%, var(--card));
                           --glass-body: color-mix(in srgb, var(--g-oliva) 5.5%, var(--card)); }
[data-block="ciruela"],  .fam-ciruela  { --block: var(--g-ciruela);  --block-soft: color-mix(in srgb, var(--g-ciruela) 12%, var(--card));
                           --glass-body: color-mix(in srgb, var(--g-ciruela) 5.5%, 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;
  /* El halo del cielo de la app (`TourStarfield`: el bloque al 5,5 % en un
     radial centrado al 38 % de alto). Va en CSS y no en el canvas para que
     exista tambien sin JS; `fixed` lo deja quieto como el fondo de la app. */
  background:
    radial-gradient(ellipse 70vmax 60vmax at 50% 38%,
      color-mix(in srgb, var(--block) 7%, transparent), transparent 70%) fixed,
    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(--glass-chrome); border-block-end: 1px solid var(--glass-edge);
}

/* El cielo estrellado de la skin Starrynight (port de `TourStarfield`): estrellas
   que titilan, algun cometa y el halo del bloque (este, en `html`). Va a
   pantalla completa detras de TODO (z-index negativo sobre el papel de `html`,
   con `body` transparente); las superficies glass (§17b) lo dejan asomar
   apenas. Con puntero fino, las estrellas cercanas se encienden y se unen en
   constelacion; con `prefers-reduced-motion`, cielo quieto. 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); }
/* El emblema oficial: estrellas en tinta; la linea azul viene en el propio SVG. */
.brand .mark { inline-size: 26px; block-size: 29px; color: var(--ink); 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); }
  .hdr-end .stores--sm { --bh: 30px; }
}
@media (max-width: 480px) {
  .brand .wordmark {
    position: absolute; inline-size: 1px; block-size: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
}

/* ── 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%; }

/* Figuras (diagramas SVG en linea, contrato 2026-09-22). El SVG llega con sus
   `fill`/`stroke` ya cambiados a variables DS2 por tools/build_web.py, asi que
   hereda claro/oscuro sin una sola regla de color aqui. Sin zoom: en la web se
   sirve a ancho de columna y basta; el visor con pellizco es cosa de la app.
   Las familias que existen en las DOS superficies son Archivo y Spline Sans
   Mono; el <text> de un diagrama deberia usar solo esas dos. */
.figure { margin-block: var(--s5); }
.figure svg {
  display: block; inline-size: 100%; block-size: auto;
  background: var(--card); border: 1px solid var(--cline);
  border-radius: var(--r-card); padding: var(--s3);
}
.figure figcaption {
  margin-block-start: var(--s2);
  font-size: var(--step--1); line-height: 1.5; color: var(--muted);
}

.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; }

/* ── 12b · CTA a la app: los badges de store (_store_badges.html) ────────────
   Oriol 2026-10-02: con la app en Google Play, TODOS los CTA son el badge de
   Play y, al lado, el de Apple atenuado con «Soon» (no es enlace). El arte de
   Apple y Google no se toca: solo se escala con `--bh` (alto VISIBLE del
   badge). El PNG de Google trae su margen de seguridad dentro (646 × 250 con
   el badge a 564 × 167): se pinta a 1,497 × --bh y un margen negativo de
   0,248 × --bh recorta el aire, asi los dos badges miden lo mismo. */
.stores { --bh: 46px; display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }
.stores--sm { --bh: 38px; flex-wrap: nowrap; gap: var(--s2); }
.store { position: relative; display: block; flex: none; line-height: 0; border-radius: calc(var(--bh) * .2); }
/* `max-inline-size: none`: el `img { max-width: 100% }` global se mide contra
   el <a>, que con el margen negativo es MAS estrecho que el PNG, y aplastaba el
   badge de Google en horizontal. */
.store img { display: block; block-size: var(--bh); inline-size: auto; max-inline-size: none; }
.store--gp img { block-size: calc(var(--bh) * 1.497); margin: calc(var(--bh) * -.248); }
.store--gp:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
/* La placa de iOS es PROPIA (no el badge de Apple: sus normas no permiten
   usarlo para una app que aun no esta en el App Store). Mismo alto, radio y
   filete que los badges, para que la pareja se lea como una sola pieza. */
.store-ios {
  display: flex; align-items: center; gap: calc(var(--bh) * .14);
  block-size: var(--bh); padding-inline: calc(var(--bh) * .2) calc(var(--bh) * .3);
  border: 1px solid var(--cline-strong); border-radius: calc(var(--bh) * .2);
  background: var(--paper); color: var(--ink); white-space: nowrap;
  opacity: .5;
}
.store-ios svg { inline-size: calc(var(--bh) * .56); block-size: calc(var(--bh) * .56); flex: none; }
.store-ios-t { display: flex; flex-direction: column; line-height: 1.05; }
.store-ios-s { font-size: calc(var(--bh) * .2); letter-spacing: .02em; }
.store-ios-n { font-size: calc(var(--bh) * .34); font-weight: 600; letter-spacing: -.01em; }
.store-soon {
  /* En la esquina, como etiqueta: centrada tapaba «iPhone & iPad». */
  position: absolute; inset-block-start: 0; inset-inline-end: 0; transform: translate(28%, -50%);
  padding: .3em .6em; border-radius: 999px; white-space: nowrap;
  font-family: var(--mono); font-size: max(.56rem, calc(var(--bh) * .22)); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; line-height: 1;
  background: var(--paper); color: var(--ink); border: 1px solid var(--glass-edge-strong);
}

/* 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;
  /* Pegajosa: el contenido pasa por debajo, asi que aqui (y solo aqui) hay
     un blur leve para que el texto de detras no compita con los botones. */
  background: var(--glass-chrome); border-block-start: 1px solid var(--glass-edge);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
}
.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; }

/* Por debajo de 640 px las tres piezas no caben con texto: se pliegan las DOS
   flechas —nunca el CTA, que son los dos badges de store— y la barra queda
   «← · badges · →». 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). El umbral era 420 con el boton «Get the app»; los badges
   miden el doble y a 600 px ya partian la barra en dos pisos.
   ⚠️ 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: 640px) {
  .bar .inner { gap: var(--s2); }
  .bar .stores--sm { --bh: 32px; }
  /* 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; }

/* Blog: el filtro por curso va debajo del de categoria, un paso mas discreto
   (son una docena de cursos: se desplaza en horizontal como el otro). */
.blogf .picker { display: flex; inline-size: fit-content; }
.picker--sub { margin-block-start: calc(-1 * var(--s3)); }
.picker--sub label { padding: .38rem .8rem; font-weight: 500; }

/* 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; }
.catalog:has(#cat-interview:checked)  .door:not([data-cat="interview"])  { display: none; }
/* Buscador del catalogo (site.js §9): filtra las puertas por texto (nombre,
   codigo, vendor, descripcion) y se suma al filtro por categoria. Sin JS no se
   enseña (`.jsonly`): el catalogo entero sigue ahi. */
.cat-find { display: flex; align-items: center; gap: var(--s2); max-inline-size: 30rem;
  margin-block-end: var(--s3); padding: .2rem .4rem .2rem .9rem; border-radius: var(--r-pill);
  background: var(--card); border: 1px solid var(--cline-strong); color: var(--muted); }
.cat-find:focus-within { border-color: var(--block); color: var(--ink); }
.cat-find svg { inline-size: 18px; block-size: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.cat-find input { flex: 1 1 auto; min-inline-size: 0; border: 0; background: none; color: var(--ink);
  font: inherit; font-size: var(--step-0); padding: .55rem .2rem; outline: none; }
.cat-find input::placeholder { color: var(--muted); }
.door.is-q-out { display: none; }
.cat-none { margin-block-start: var(--s4); color: var(--muted); }
/* Plan B de site.js donde `:has()` no existe (Safari < 15.4, Firefox < 121). */
.door.is-out, .blogf .row.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); }

/* Hero de cielo (home, 2026-09-21): la primera pantalla es el splash de la app
   —el emblema Peso a tamaño grande sobre el cielo estrellado— en vez de un
   plano de color. Sin `.block`: texto en tinta sobre el papel, y el cielo
   respeta sus lineas (site.js). El resto de paginas siguen abriendo con bloque. */
.hero--sky .inner { display: grid; grid-template-columns: minmax(0, 1fr); column-gap: var(--s7); align-items: center; }
.hero--sky .inner > * { grid-column: 1; }
.hero--sky .wordmark { color: var(--ink); }
/* El lema, no una nota al pie (Oriol 2026-09-21: «muy pequeño»). */
.hero--sky .meta { font-size: var(--step-2); font-weight: 700; font-stretch: 108%; letter-spacing: -.02em;
  line-height: 1.15; color: var(--ink); margin-block-start: var(--s2); }
.hero--sky .eyebrow { color: var(--muted); }
/* Estrellas en tinta; linea y destellos llevan el azul del icono en el SVG. */
/* En movil, centrado sobre el nombre (Oriol: pegado a la izquierda no). */
.mark-hero { color: var(--ink); inline-size: 96px; block-size: 96px; margin-block-end: var(--s5); overflow: visible; justify-self: center; }
@media (min-width: 900px) {
  .hero--sky .inner { grid-template-columns: minmax(0, 1fr) auto; }
  .hero--sky .mark-hero {
    grid-column: 2; grid-row: 1 / span 4; align-self: center;
    inline-size: clamp(220px, 24vw, 320px); block-size: auto; margin: 0;
  }
}

/* La cifra de mercado del hero: dos parrafos cortos y en medio el gancho de
   seis cifras, en oro con un brillo que lo recorre y un contador que sube al
   cargar (site.js §8). Debajo, SIEMPRE la fuente: es un dato del mercado (BLS,
   mediana en EE. UU.), nunca una promesa (CLAUDE.md §8). Sin JS o con
   `prefers-reduced-motion`: la cifra final, quieta. */
.money { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s4);
  margin-block-start: var(--s5); line-height: 1; }
.money-n { font-weight: 800; font-stretch: 112%; letter-spacing: -.035em;
  font-size: clamp(2.5rem, 1.5rem + 3.6vw, 4.6rem); font-variant-numeric: tabular-nums;
  color: var(--gold);
  background: linear-gradient(100deg, var(--gold-deep) 0%, var(--gold) 30%, var(--gold-hi) 46%, var(--gold) 62%, var(--gold-deep) 100%);
  background-size: 220% 100%; background-position: 100% 0;
  -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.money-l { font-family: var(--mono); font-size: var(--step-0); letter-spacing: .08em;
  text-transform: uppercase; color: var(--gold); }
.money { position: relative; }
.money-meta { display: inline-flex; align-items: center; gap: var(--s2); white-space: nowrap; }
.money-meta .src { margin: 0; }
.money + .lead { margin-block-start: var(--s5); }
@media (prefers-reduced-motion: no-preference) {
  .money-n { animation: gold-sheen 5.5s var(--ease) .6s infinite; }
  @keyframes gold-sheen { 0% { background-position: 100% 0; } 45%, 100% { background-position: -20% 0; } }
}

/* Lo que trae cada curso: una fila de pastillas de cristal, todas del color de
   la pagina y con su icono (Oriol 2026-09-21: nada de un color por pastilla).
   En escritorio ocupan el ancho de las dos columnas para caber en una fila; en
   movil se desplazan en horizontal antes que partirse en dos pisos. */
.perks { list-style: none; margin: var(--s5) 0 0; padding: 0 0 var(--s1); display: flex; gap: var(--s2);
  overflow-x: auto; scrollbar-width: none; }
.perks::-webkit-scrollbar { display: none; }
.hero--sky .perks { grid-column: 1 / -1; }
/* En movil, varias filas: todas a la vista, sin ir a buscarlas a la derecha. */
@media (max-width: 899.98px) { .perks { flex-wrap: wrap; overflow: visible; } }
.perks li, .perks .cta-cap { flex: none; display: inline-flex; align-items: center; gap: var(--s2);
  padding: .34rem .85rem .34rem .38rem; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--block) 12%, var(--card));
  border: 1px solid color-mix(in srgb, var(--block) 40%, transparent);
  color: var(--ink); font-size: var(--step--1); font-weight: 600; line-height: 1.2; white-space: nowrap; }
.perks svg { inline-size: 24px; block-size: 24px; padding: 4px; border-radius: var(--r-pill); flex: none;
  background: var(--block); color: var(--on-block); fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* Flujo: cuatro pasos unidos por una linea que recorre un pulso (de izquierda a
   derecha en escritorio, de arriba abajo en movil). Cada paso lleva su icono en
   el color de su gama; el texto es tinta sobre el cielo. Sin movimiento, la
   linea queda quieta. */
.flow { list-style: none; margin: var(--s6) 0 0; padding: 0; display: grid; gap: var(--s5);
  grid-template-columns: repeat(4, minmax(0, 1fr)); position: relative; }
.flow li { position: relative; display: grid; align-content: start; justify-items: center; gap: var(--s2); text-align: center; }
.flow svg { inline-size: 52px; block-size: 52px; padding: 12px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--block) 16%, var(--card));
  border: 1px solid color-mix(in srgb, var(--block) 55%, transparent);
  color: var(--block); fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
  position: relative; z-index: 1; margin-block-end: var(--s3); }
.flow-t { font-weight: 700; font-stretch: 108%; font-size: var(--step-1); letter-spacing: -.02em; line-height: 1.15; }
.flow-d { font-size: var(--step--1); color: var(--muted); line-height: 1.5; max-width: 26ch; }
/* El conector: del borde de un icono (centrado en su columna) al del
   siguiente. Los % van contra el ancho del paso: 50 % es su centro. */
.flow li:not(:last-child)::after { content: ""; position: absolute; z-index: 0;
  inset-block-start: 26px; inset-inline-start: calc(50% + 34px);
  inset-inline-end: calc(-1 * (var(--s5) + 50% - 34px)); block-size: 2px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--ink) 18%, transparent) 0 100%);
  background-size: 100% 100%; }
@media (prefers-reduced-motion: no-preference) {
  .flow li:not(:last-child)::after {
    background: linear-gradient(90deg, transparent 0, var(--ink) 50%, transparent 100%) no-repeat,
                color-mix(in srgb, var(--ink) 18%, transparent);
    background-size: 30% 100%, 100% 100%; background-position: -40% 0, 0 0;
    animation: flow-pulse 2.8s var(--ease) infinite; }
  .flow li:nth-child(2)::after { animation-delay: .7s; }
  .flow li:nth-child(3)::after { animation-delay: 1.4s; }
  @keyframes flow-pulse { to { background-position: 140% 0, 0 0; } }
}
@media (max-width: 899.98px) {
  .flow { grid-template-columns: minmax(0, 1fr); gap: var(--s4); }
  .flow li { grid-template-columns: 52px minmax(0, 1fr); column-gap: var(--s4); padding: 0;
    justify-items: start; text-align: start; }
  .flow svg { grid-row: 1 / span 2; margin: 0; }
  .flow-t, .flow-d { grid-column: 2; }
  .flow li:not(:last-child)::after { inset-block-start: 56px; inset-block-end: calc(-1 * var(--s4) - 4px);
    inset-inline-start: 25px; inset-inline-end: auto; inline-size: 2px; block-size: auto; }
  @media (prefers-reduced-motion: no-preference) {
    .flow li:not(:last-child)::after {
      background: linear-gradient(180deg, transparent 0, var(--ink) 50%, transparent 100%) no-repeat,
                  color-mix(in srgb, var(--ink) 18%, transparent);
      background-size: 100% 30%, 100% 100%; background-position: 0 -40%, 0 0;
      animation-name: flow-pulse-v; }
    @keyframes flow-pulse-v { to { background-position: 0 140%, 0 0; } }
  }
}

/* ── 16c · El cilindro de capturas (home) ──────────────────────────────── */
/* Importado de sobresalia.com (spec alli: 2026-08-11-web-cilindro-capturas.md).
   Veinticuatro pantallas reales (dos skins, cuatro colores, el sol del login) en marcos de telefono pegadas por dentro a un cilindro
   transparente que gira con el scroll (site.js §6 escribe `--giro`, `--fade` y
   `--lift`); la frase queda quieta delante. Lo que cambia respecto del original:
   · 24 paneles de 170 px: R = (pw/2) / tan(pi/24) = 85 × 7,596 ≈ 646 px. Mas
     densidad que el original (16) a peticion de Oriol; las Marble alternan con
     las oscuras y nunca caen en la franja de la frase (|dy| ≥ 140 px).
   · Sin sombras (DS2) y el bisel en tokens: el volumen lo da la geometria.
   · Sin velo (Oriol 2026-09-24): el plano del eje tapaba las estrellas y se
     notaba el corte arriba y abajo. Los telefonos de espaldas se ocultan
     (`.is-away`, site.js §6), asi que ya no hay nada detras que atenuar.
   Sin JS o con `prefers-reduced-motion`: quieto, en pose y visible. */
.cyl { --n: 24; --pw: 170px; --R: 646px; --giro: -14deg; --fade: 1; --lift: 0px;
  position: relative; block-size: 155vh; }
.cyl-stage { position: sticky; inset-block-start: 0; block-size: 100svh;
  opacity: var(--fade); contain: layout;
  /* Mascara en vez de overflow: los telefonos se desvanecen arriba y abajo en
     vez de cortarse a cuchillo en el borde del panel pegado. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, var(--ink) 9%, var(--ink) 91%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, var(--ink) 9%, var(--ink) 91%, transparent 100%); }
/* Perspectiva y preserve-3d en el MISMO contenedor: anillo, halo y frase se
   ordenan por profundidad real. Sin grid: cada pieza se ancla al centro. */
.cyl-space { position: relative; inline-size: 100%; block-size: 100%;
  perspective: 1150px; transform-style: preserve-3d; translate: 0 var(--lift); }
.cyl-ring, .cyl-halo, .cyl-say { position: absolute; inset-block-start: 50%; inset-inline-start: 50%; }
.cyl-ring { transform-style: preserve-3d;
  transform: translate(-50%, -50%) translateZ(calc(-1 * var(--R))) rotateY(var(--giro)); }
.cyl-ring figure { position: absolute; inset-block-start: 0; inset-inline-start: 0;
  margin: 0; inline-size: var(--pw); transform-style: preserve-3d; backface-visibility: hidden;
  transform:
    translate(-50%, -50%)
    rotateY(calc(var(--i) * (360deg / var(--n)) + var(--jit, 0deg)))
    translateZ(calc(var(--R) + var(--dz, 0px)))
    translateY(var(--dy, 0px))
    rotateZ(var(--tilt, 0deg))
    scale(var(--sc, 1)); }
/* El marco: telefono generico, sin marca de nadie (§8). La isla es lo unico que
   hace falta para leer «telefono»; girando, una imagen plana parece una carta. */
.fon { position: relative; padding: calc(var(--pw) * .028);
  border-radius: calc(var(--pw) * .135);
  background: linear-gradient(155deg, var(--fon-hi) 0%, var(--fon-mid) 42%, var(--fon-lo) 100%);
  outline: 1px solid color-mix(in srgb, var(--ink) 8%, transparent); outline-offset: -1px; }
.fon::after { content: ""; position: absolute; z-index: 2;
  inset-block-start: calc(var(--pw) * .062); inset-inline-start: 50%;
  inline-size: 24%; block-size: calc(var(--pw) * .042);
  transform: translateX(-50%); border-radius: var(--r-pill); background: var(--fon-island); }
.fon img { display: block; inline-size: 100%; block-size: auto;
  aspect-ratio: 1206 / 2622; object-fit: cover; object-position: top center;
  border-radius: calc(var(--pw) * .108); background: var(--card); }
/* Telefonos con cuerpo (Oriol 2026-09-24: «telefonos, no pantallazos»). Barato
   a proposito: el escaparate apila once capas por telefono; aqui son 24, asi que
   cada uno lleva solo cuatro cantos finos doblados 90° hacia atras (ninguna
   capa grande mas). Al girar se ve el grosor. En cuanto la pantalla deja de
   mirar al espectador el telefono entero se oculta (`.is-away`, site.js §6):
   el foco va a las capturas, no a espaldas. Grafito por defecto; plata
   (`.fon--plata`) en las capturas Marble. */
/* Canto de titanio oscuro, no grafito: sobre el cielo negro el grafito se
   fundia con el fondo y el telefono no se leia (Oriol). El aro claro de la cara
   hace el mismo papel de frente. */
.cyl-ring figure { --fon-t: calc(var(--pw) * .045);
  --fon-a: color-mix(in srgb, var(--silver-hi) 55%, var(--fon-hi));
  --fon-b: color-mix(in srgb, var(--silver-mid) 60%, var(--fon-mid));
  --fon-c: color-mix(in srgb, var(--silver-lo) 70%, var(--fon-lo)); }
.cyl-ring figure.fon--plata { --fon-a: var(--silver-hi); --fon-b: var(--silver-mid); --fon-c: var(--silver-lo); }
.cyl-ring .fon { backface-visibility: hidden;
  outline: 1.5px solid color-mix(in srgb, var(--fon-a) 75%, transparent); outline-offset: -1.5px; }
.cyl-ring figure.is-away { visibility: hidden; }
.cyl-ring .fon--plata .fon { background: linear-gradient(120deg, var(--silver-lo) 0%, var(--silver-mid) 22%, var(--silver-hi) 34%, var(--silver-mid) 48%, var(--silver-lo) 70%, var(--silver-mid) 100%); }
/* Las esquinas: las tiras son rectas y acaban donde empieza la curva, asi que
   dos contornos redondeados (a media profundidad y al fondo) cierran el canto
   en las cuatro esquinas. Solo pintan los telefonos visibles. */
.cyl-ring figure::before, .cyl-ring figure::after { content: ""; position: absolute; inset: 0;
  pointer-events: none; border-radius: calc(var(--pw) * .135);
  background: linear-gradient(90deg, var(--fon-c), var(--fon-b) 50%, var(--fon-c)); }
.cyl-ring figure::before { transform: translateZ(calc(var(--fon-t) * -.5)); }
.cyl-ring figure::after { transform: translateZ(calc(var(--fon-t) * -1)); }
.cyl-ring .fon-edges { position: absolute; inset: 0; transform-style: preserve-3d; pointer-events: none; }
.cyl-ring .fon-edges i { position: absolute; background: linear-gradient(var(--fon-a), var(--fon-b) 50%, var(--fon-c)); }
.cyl-ring .e-l, .cyl-ring .e-r { inset-block: calc(var(--pw) * .13); inline-size: var(--fon-t); }
.cyl-ring .e-t, .cyl-ring .e-b { inset-inline: calc(var(--pw) * .13); block-size: var(--fon-t); }
.cyl-ring .e-l { inset-inline-start: 0; transform-origin: 0 50%; transform: rotateY(90deg); }
.cyl-ring .e-r { inset-inline-end: 0; transform-origin: 100% 50%; transform: rotateY(-90deg);
  /* Los tres botones del canto derecho, pintados: sin elementos de mas. */
  background:
    linear-gradient(var(--fon-a), var(--fon-a)) 50% 8% / 70% 5% no-repeat,
    linear-gradient(var(--fon-a), var(--fon-a)) 50% 20% / 70% 10% no-repeat,
    linear-gradient(var(--fon-a), var(--fon-a)) 50% 34% / 70% 10% no-repeat,
    linear-gradient(var(--fon-b), var(--fon-c)); }
.cyl-ring .e-t { inset-block-start: 0; transform-origin: 50% 0; transform: rotateX(-90deg); }
.cyl-ring .e-b { inset-block-end: 0; transform-origin: 50% 100%; transform: rotateX(90deg); }
/* Claro de niebla SOLO detras de la frase. */
.cyl-halo { inline-size: 70vmin; block-size: 36vmin; pointer-events: none;
  background: radial-gradient(closest-side, var(--paper) 0 48%, transparent 100%);
  opacity: .92; transform: translate(-50%, -50%) translateZ(140px); }
/* La frase, DELANTE del cilindro (en el eje la tapaba el panel frontal de
   turno). La escala compensa el acercamiento: 1150 / (1150 − 150). */
.cyl-say { margin: 0; text-align: center; inline-size: min(14ch, 84vw);
  font-size: var(--step-4); line-height: 1.04; color: var(--ink);
  transform: translate(-50%, -50%) translateZ(150px) scale(.87);
  text-shadow: 0 0 18px var(--paper); }
/* El termino marcado: banda del bloque bajo la tinta, como el `.mk` de la web
   de Sobresalia; tinta sobre la banda, asi que el contraste no depende de ella. */
.mk { background-image: linear-gradient(to top, color-mix(in srgb, var(--block) 55%, transparent) 0 42%, transparent 42%);
  background-repeat: no-repeat; padding-inline: .06em;
  box-decoration-break: clone; -webkit-box-decoration-break: clone; }
@media (max-width: 759.98px) {
  .cyl { --n: 14; --pw: 112px; --R: 245px; block-size: 130vh; }
  /* Catorce paneles de 112 px (R = 56 / tan(pi/14)): densidad sin cargar el
     hardware mas justo con los 24. Las alturas casi como en escritorio
     (× 1,02): abriendolas mas (× 1,5) la frase quedaba sola en medio y los
     telefonos en los extremos (Oriol). Las profundidades se encogen. */
  .cyl-ring figure:nth-child(n+15) { display: none; }
  .cyl-say { font-size: var(--step-3); }
  .cyl-ring figure { transform:
    translate(-50%, -50%)
    rotateY(calc(var(--i) * (360deg / var(--n)) + var(--jit, 0deg)))
    translateZ(calc(var(--R) + var(--dz, 0px) * .55))
    translateY(calc(var(--dy, 0px) * 1.02))
    rotateZ(var(--tilt, 0deg))
    scale(var(--sc, 1)); }
}
@media (max-width: 420px) { .cyl { --pw: 106px; --R: 232px; } }
@media (prefers-reduced-motion: reduce) {
  .cyl { block-size: auto; --giro: -14deg; --fade: 1; }
  .cyl-stage { position: static; block-size: min(100svh, 760px); }
}
html:not(.js) .cyl { block-size: auto; }
html:not(.js) .cyl-stage { position: static; block-size: min(100svh, 760px); }

/* 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); }

/* Gancho de mercado de la ficha (design/SALARIOS_MERCADO.md): la cifra en mono,
   grande; la fuente siempre debajo y a la vista. */
.market { display: grid; gap: var(--s2); max-width: 62ch; }
.market-n { font-family: var(--mono); font-size: var(--step-2); font-weight: 600; letter-spacing: -.02em; line-height: 1.1; }
.market-t { font-size: var(--step-0); }
/* La fila de cifras de mercado de la home: cada cifra con su fuente debajo. */
.figs--market { margin-block-start: var(--s6); }
.figs--market li { max-width: 26ch; }
.figs--market li { position: relative; }
.tbl td.nowrap { white-space: nowrap; }

/* La fuente detras de una (i) (Oriol 2026-09-21: «que lo lea solo quien
   quiera»). Es un <details>: se abre sin JS y con teclado; el texto flota en
   una capa opaca sobre lo que tenga debajo, sin mover el layout. */
.src { display: inline-block; vertical-align: middle; margin-inline-start: var(--s2); }
.src > summary { list-style: none; display: inline-grid; place-items: center;
  inline-size: 20px; block-size: 20px; border-radius: var(--r-pill); cursor: pointer;
  border: 1px solid var(--cline-strong); color: var(--muted);
  font-family: var(--mono); font-size: .7rem; font-weight: 600; line-height: 1; }
.src > summary::-webkit-details-marker { display: none; }
.src > summary::before { content: "i"; }
.src > summary:hover, .src[open] > summary { color: var(--ink); border-color: var(--ink); }
.src > p { position: absolute; z-index: 30; inset-inline: 0; inset-block-start: 100%;
  margin-block-start: var(--s2); padding: var(--s3) var(--s4);
  background: var(--card); border: 1px solid var(--glass-edge); border-radius: var(--r-field);
  font-size: var(--step--1); line-height: 1.45; color: var(--muted); text-transform: none; letter-spacing: 0; }
.figs--market .fig-l .src, .figs--market > li > .src { margin-block-start: var(--s2); margin-inline-start: 0; }

/* Roles: paneles con icono, rol y salario que desfilan en bucle en vez de una
   tabla. La pista va dos veces (la copia `inert`); la animacion mueve las dos
   una anchura entera y vuelve, sin costura. Se para al pasar el raton, al
   enfocar o con una fuente abierta; con movimiento reducido, rejilla quieta. */
.roles { position: relative; display: flex; gap: var(--s5); overflow: hidden;
  margin-block-start: var(--s6); padding-block: var(--s2) var(--s4);
  -webkit-mask-image: linear-gradient(90deg, transparent, var(--ink) 6%, var(--ink) 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, var(--ink) 6%, var(--ink) 94%, transparent); }
.roles-track { list-style: none; margin: 0; padding: 0; display: flex; gap: var(--s5); flex: none;
  animation: roles-run 90s linear infinite; }
.roles:hover .roles-track, .roles:focus-within .roles-track, .roles:has(details[open]) .roles-track { animation-play-state: paused; }
@keyframes roles-run { to { transform: translateX(calc(-100% - var(--s5))); } }
.role { position: relative; flex: none; inline-size: 272px; display: grid; gap: var(--s1);
  padding: var(--s4) var(--s5) var(--s5); border-radius: var(--r-card);
  background: linear-gradient(135deg,
      color-mix(in srgb, color-mix(in srgb, var(--ink) 5%, var(--glass-body)) 96%, transparent) 0%,
      color-mix(in srgb, var(--glass-body) 88%, transparent) 48%,
      color-mix(in srgb, var(--card) 94%, transparent) 100%);
  border: 1px solid var(--glass-edge); }
.role-ico { inline-size: 40px; block-size: 40px; padding: 8px; border-radius: var(--r-field);
  background: var(--block); color: var(--on-block); fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; margin-block-end: var(--s3); }
.role-t { font-weight: 700; font-stretch: 108%; font-size: var(--step-0); letter-spacing: -.015em; }
/* «Between $X and $Y»: el suelo en naranja y el techo en verde, cada cifra en
   su linea y grande, con la palabra de enlace en mono pequeña. */
.role-range { display: grid; grid-template-columns: auto 1fr; align-items: baseline; column-gap: var(--s3); row-gap: var(--s1);
  margin-block: var(--s2) var(--s1); }
.role-k { font-family: var(--mono); font-size: .68rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
.role-lo, .role-hi { font-family: var(--mono); font-weight: 700; font-size: var(--step-1); letter-spacing: -.02em; white-space: nowrap; }
.role-lo { color: var(--sal-lo); }
.role-hi { color: var(--sal-hi); }
.role-per { font-family: var(--mono); font-size: .7rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.role .src { margin: 0; }
.role .src > summary { position: absolute; inset-block-start: var(--s4); inset-inline-end: var(--s4); }
/* Dentro del panel la fuente cubre su parte baja, no se sale: la pista recorta. */
.role .src > p { inset: auto var(--s3) var(--s3); margin: 0; }
@media (prefers-reduced-motion: reduce) {
  .roles { flex-wrap: wrap; -webkit-mask-image: none; mask-image: none; }
  .roles-track { animation: none; flex-wrap: wrap; }
  .roles-copy { display: none; }
}
@media (max-width: 560px) { .role { inline-size: 236px; } }

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

/* Prueba social: una resena real de la store en una linea, junto al CTA.
   Estrellas en tinta (premium = forma y tinta, sin oro). */
.proof { margin: var(--s5) 0 0; max-width: 58ch; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3); }
.proof-stars svg { display: block; inline-size: 80px; block-size: 16px; fill: var(--ink); }
.proof blockquote { margin: 0; flex: 1 1 100%; order: -1; }
.proof blockquote p { font-weight: 600; line-height: 1.4; }
.proof figcaption { font-size: var(--step--1); font-weight: 650; }
.proof figcaption span { font-family: var(--mono); font-weight: 400; color: var(--muted); margin-inline-start: var(--s2); }

/* 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(3, minmax(0, 1fr)); margin-block-start: var(--s6); }
/* 3 × 2 en escritorio y 2 × 3 por debajo (Oriol 2026-09-21: seis pilares, sin
   Comunidad). En movil tambien dos columnas: la captura cabe igual. */
@media (max-width: 899.98px) { .pillars { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3); } }
@media (max-width: 560px) {
  .pillars .card { padding: var(--s4); }
  .pillars .card h3 { font-size: var(--step-0); }
}

/* Pilares + escenario (Oriol 2026-09-21). Dos columnas de tarjetas y, en la
   tercera, un telefono plateado que flota y gira un poco sobre un haz de luz.
   La tarjeta activa (raton, foco o toque: site.js §7) pinta su borde en el
   bloque y el telefono cambia a su captura con un fundido. Sin JS, la primera.
   Sin sombras (DS2): el volumen lo hacen el degradado metalico y la luz. */
.showcase { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: var(--s6);
  align-items: center; margin-block-start: var(--s6); }
.showcase .pillars { grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 0; }
.pillar { cursor: pointer; transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease); }
.pillar:hover, .pillar.is-on { border-color: var(--block); }
.pillar.is-on { background: color-mix(in srgb, var(--block) 10%, var(--card)); }
.stage { position: relative; display: grid; place-items: center; min-block-size: 640px; perspective: 1100px; }
.stage-float { position: relative; z-index: 2; transform-style: preserve-3d; transform: rotateY(-20deg) rotateX(4deg); }
/* Volumen (Oriol 2026-09-22: «que se note que es un telefono»). Detras de la
   cara van once capas del mismo contorno, cada una 1 px mas atras y algo mas
   oscura: juntas son el canto de aluminio que se ve al girar. Los botones son
   tres lengüetas en el canto derecho, el que queda a la vista al mirar hacia
   las tarjetas. */
.phone3d { --pw: 262px; position: relative; transform-style: preserve-3d; }
.phone3d > .fon--silver { position: relative; transform: translateZ(1px); }
.phone-body { position: absolute; inset: 0; transform-style: preserve-3d; pointer-events: none; }
.phone-body i { position: absolute; inset: 0; border-radius: calc(var(--pw) * .135);
  transform: translateZ(calc(var(--z) * -1px));
  background: linear-gradient(90deg, var(--silver-lo), color-mix(in srgb, var(--silver-mid) 70%, var(--silver-lo)) 40%, var(--silver-lo)); }
.phone-body i:last-of-type { background: var(--silver-lo); }
.phone-body b { position: absolute; inset-inline-end: -3px; inline-size: 4px; border-radius: 2px;
  background: linear-gradient(90deg, var(--silver-lo), var(--silver-hi)); transform: translateZ(-6px); }
.phone-body .btn-a { inset-block-start: 17%; block-size: 5%; }
.phone-body .btn-b { inset-block-start: 25%; block-size: 9%; }
.phone-body .btn-c { inset-block-start: 36%; block-size: 9%; }
/* Reflejo del cristal: una diagonal muy tenue sobre la captura. */
.stage-screen .glass { position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background: linear-gradient(115deg, color-mix(in srgb, var(--ink) 14%, transparent) 0%, transparent 32%, transparent 68%, color-mix(in srgb, var(--ink) 5%, transparent) 100%); }
/* Plata satinada, no espejo: tonos medios, un solo reflejo suave en diagonal y
   el canto en un gris frio. */
.fon--silver { --pw: 262px; inline-size: var(--pw);
  background: linear-gradient(120deg, var(--silver-lo) 0%, var(--silver-mid) 22%, var(--silver-hi) 34%, var(--silver-mid) 48%, var(--silver-lo) 70%, var(--silver-mid) 100%);
  outline-color: color-mix(in srgb, var(--silver-hi) 35%, transparent); }
/* La pantalla es negra y lleva DOS capas: la nueva captura entra encima de la
   vieja y solo entonces se va la vieja. Nunca queda un hueco por el que asome
   la plata (el «destello» que vio Oriol). */
.stage-screen { position: relative; overflow: hidden; border-radius: calc(var(--pw) * .108); background: var(--paper); }
.stage-screen .stage-img { transition: opacity 260ms var(--ease); opacity: 0; }
.stage-screen .stage-img:first-child { position: relative; }
.stage-screen .stage-img + .stage-img { position: absolute; inset: 0; block-size: 100%; }
.stage-screen .stage-img.is-front { opacity: 1; }
/* El aro de luz sobre el que flota: un anillo, no un disco (sin la base
   rectangular que hubo). */
.stage-floor { position: absolute; z-index: 1; inset-block-end: 22px; inset-inline-start: 50%;
  inline-size: 280px; block-size: 44px; transform: translateX(-50%); border-radius: 50%;
  background: radial-gradient(closest-side, transparent 52%, color-mix(in srgb, var(--block) 70%, var(--ink)) 66%, color-mix(in srgb, var(--block) 30%, transparent) 80%, transparent 100%);
  pointer-events: none; }
@media (prefers-reduced-motion: no-preference) {
  .stage-float { animation: stage-float 7s ease-in-out infinite; }
  .stage-floor { animation: stage-floor 7s ease-in-out infinite; }
  /* Oscila, no gira: siempre vuelto hacia el interior de la pagina, donde
     estan las tarjetas (rotateY negativo: la cara mira a la izquierda). */
  @keyframes stage-float {
    0%, 100% { transform: translateY(0) rotateY(-22deg) rotateX(4deg); }
    50% { transform: translateY(-12px) rotateY(-14deg) rotateX(2deg); }
  }
  @keyframes stage-floor {
    0%, 100% { transform: translateX(-50%) scale(1); opacity: 1; }
    50% { transform: translateX(-50%) scale(.84); opacity: .75; }
  }
}
@media (max-width: 899.98px) {
  .showcase { grid-template-columns: minmax(0, 1fr); gap: var(--s4); }
  .stage { order: -1; }
  .stage { min-block-size: 480px; }
  .fon--silver, .phone3d { --pw: 196px; }
  .stage-floor { inline-size: 230px; }
}
.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); }

/* /get/, el destino del enlace de la bio. Una sola pagina con los dos estados;
   el interruptor es `data-get` en el <html> (waitlist | stores) y aqui solo se
   esconde el que no toca, asi funciona tambien sin JS. El reenvio a la store
   lo hace site.js §10. El arte de Apple y Google es intocable: solo se escala,
   sobre una placa de cristal para que no se pierda contra el cielo. */
html[data-get="waitlist"] .get-stores,
html[data-get="stores"] .get-wait { display: none; }
.get-dl { padding-block: var(--s7) var(--s8); }
.get-note { font-family: var(--mono); font-size: var(--step--1); color: var(--muted); margin-block-end: var(--s4); }
.get-badges {
  --bh: 54px;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--s5);
  inline-size: fit-content; max-inline-size: 100%;
  padding: var(--s5) var(--s6); border: 1px solid var(--cline); border-radius: var(--r-card);
}
.get-badges a { display: block; line-height: 0; border-radius: var(--r-card); }
.get-badges img { block-size: 54px; inline-size: auto; }
/* El PNG de Google trae su margen de seguridad dentro (646 × 250 con el badge a
   564 × 167): a 81 px de alto el badge visible mide 54, como el de Apple, y el
   margen negativo recorta el aire transparente. */
.get-badges .gp img { block-size: 81px; margin: -13px; }
.get-hint { margin-block-start: var(--s5); max-width: 58ch; color: var(--muted); }
.get-legal { margin-block-start: var(--s6); max-width: 70ch; font-size: .72rem; color: var(--muted); }

/* CTA dentro de un post (_cta_inline.html). Oriol 2026-09-24: la tarjeta gris
   plana «se ve poco atractiva». Halo del bloque, borde teñido, las seis
   features en chips de columnas iguales (2 × 3) y, a la derecha, EL MISMO
   telefono plateado del escaparate de la home (`.phone3d`: once capas de
   canto y botones) en la misma pose, flotando sobre su aro de luz. Cada 5 s
   da una vuelta en 3D y vuelve con la siguiente feature; su nombre va al pie
   en la misma pastilla que el chip encendido (site.js §3c). Un boton, centrado.
   Es mas ancha que la columna de lectura (sale de ella por los dos lados). En
   movil el telefono se oculta. Sin sombras (DS2). */
.cta-inline { position: relative; overflow: hidden; display: grid;
  grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "txt fon" "acts acts";
  column-gap: var(--s7); row-gap: var(--s4); align-items: center;
  inline-size: min(62rem, 100vw - 2 * var(--gutter)); margin-inline-start: 50%; translate: -50% 0;
  padding: var(--s6) var(--s7);
  border-radius: var(--r-group);
  border: 1px solid color-mix(in srgb, var(--block) 45%, var(--cline));
  background:
    radial-gradient(90% 120% at 88% 40%, color-mix(in srgb, var(--block) 30%, transparent) 0%, transparent 60%),
    linear-gradient(160deg, color-mix(in srgb, var(--block) 12%, var(--card)) 0%, var(--card) 60%); }
.cta-inline--top { margin-block-start: var(--s6); }
.cta-inline--mid { margin-block: var(--s7); }
.cta-inline--end { margin-block-start: var(--s8); }
.cta-inline-txt { grid-area: txt; }
.cta-inline .eyebrow { margin: 0 0 var(--s2); color: color-mix(in srgb, var(--block) 55%, var(--ink)); }
.cta-inline-hd { margin: 0; font-size: var(--step-2); font-weight: 700; letter-spacing: -.01em; line-height: 1.15; color: var(--ink); }
.cta-inline-p { margin: var(--s3) 0 0; color: var(--muted); font-size: var(--step-0); line-height: 1.5; max-width: 46ch; }
/* Las features en rejilla de columnas IGUALES (2 × 3): todas del mismo ancho y
   alineadas. La encendida es la que enseña el telefono. */
.cta-inline-perks { margin: var(--s5) 0 0; padding: 0; display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s2); overflow: visible; }
.cta-inline-perks li, .cta-cap { inline-size: 100%; min-inline-size: 0; white-space: normal;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), opacity 220ms var(--ease); }
.cta-inline-perks li.is-on, .perks .cta-cap { background: color-mix(in srgb, var(--block) 32%, var(--card)); border-color: var(--block); }
.cta-inline-acts { grid-area: acts; justify-content: center; margin: 0; }
/* El telefono ocupa las dos filas y se apoya en la BASE de la placa (Oriol:
   «que repose en la base de la carta»); el boton sigue centrado en la placa
   entera, por debajo de los chips, sin tocar la columna del telefono. */
@media (min-width: 700px) {
  .cta-inline .cta-inline-acts { grid-area: auto; grid-column: 1 / -1; grid-row: 2; }
  .cta-inline .cta-stage { grid-area: auto; grid-column: 2; grid-row: 1 / span 2; align-self: end;
    }
}
/* El escenario del telefono: la misma pieza que la home, a escala. */
.cta-stage { grid-area: fon; position: relative; margin: 0; perspective: 1100px;
  display: grid; justify-items: center; gap: var(--s5); padding-block: 0 var(--s6); }
.cta-stage .phone3d, .cta-stage .fon--silver { --pw: clamp(150px, 15vw, 196px); }
.cta-stage .stage-float { transform: rotateY(-20deg) rotateX(4deg); }
.cta-spin { position: relative; transform-style: preserve-3d; }
.cta-stage .stage-floor { inset-block-end: 0; inline-size: 230px; }
/* iPhone de verdad (Oriol 2026-09-24), sin logo: aro de titanio fino, bisel
   negro alrededor de la pantalla, Dynamic Island, botón de acción y volumen a
   la izquierda, botón lateral a la derecha, y detrás la meseta de cámara con
   tres lentes en triángulo, flash y LiDAR. Solo en la CTA del blog. */
.cta-stage .fon--silver { padding: calc(var(--pw) * .013);
  background: linear-gradient(120deg, var(--silver-lo) 0%, var(--silver-hi) 18%, var(--silver-mid) 40%, var(--silver-hi) 62%, var(--silver-lo) 100%); }
.cta-stage .stage-screen { border: calc(var(--pw) * .026) solid var(--fon-island); border-radius: calc(var(--pw) * .122); }
.cta-stage .fon--silver::after { inset-block-start: calc(var(--pw) * .058); inline-size: 31%; block-size: calc(var(--pw) * .088); }
/* Botones: acción y volumen a la izquierda (se ven en la vuelta), lateral a
   la derecha, el canto que la pose deja a la vista. */
.cta-stage .phone-body b { inset-inline-end: auto; inset-inline-start: -3px; inline-size: 3px;
  background: linear-gradient(90deg, var(--silver-hi), var(--silver-lo)); }
.cta-stage .phone-body .btn-a { inset-block-start: 18%; block-size: 5%; }
.cta-stage .phone-body .btn-b { inset-block-start: 26%; block-size: 8%; }
.cta-stage .phone-body .btn-c { inset-block-start: 36%; block-size: 8%; }
.cta-stage .phone-body::before { content: ""; position: absolute; inset-inline-end: -3px; inset-block-start: 29%;
  inline-size: 3px; block-size: 12%; border-radius: 2px; transform: translateZ(-6px);
  background: linear-gradient(90deg, var(--silver-lo), var(--silver-hi)); }
/* La tapa trasera, de vidrio mate, detrás de la última capa del canto. */
.cta-stage .phone-body::after { content: ""; position: absolute; inset: 0; border-radius: calc(var(--pw) * .135);
  transform: translateZ(-12px) rotateY(180deg);
  background: linear-gradient(150deg, var(--silver-hi) 0%, var(--silver-mid) 45%, var(--silver-lo) 100%); }
/* La meseta de cámara: arriba a la izquierda vista desde atrás. */
.cta-stage .cam { position: absolute; inset-block-start: calc(var(--pw) * .05); inset-inline-end: calc(var(--pw) * .05);
  inline-size: calc(var(--pw) * .44); aspect-ratio: 1; border-radius: calc(var(--pw) * .11);
  transform: translateZ(-13px) rotateY(180deg);
  outline: 1px solid color-mix(in srgb, var(--silver-hi) 60%, transparent); outline-offset: -1px;
  --l: calc(var(--pw) * .078);
  background:
    radial-gradient(circle at 27% 27%, var(--silver-mid) 0 calc(var(--l) * .22), var(--fon-island) calc(var(--l) * .3) calc(var(--l) * .78), var(--fon-hi) calc(var(--l) * .82) var(--l), transparent calc(var(--l) + 1px)),
    radial-gradient(circle at 27% 73%, var(--silver-mid) 0 calc(var(--l) * .22), var(--fon-island) calc(var(--l) * .3) calc(var(--l) * .78), var(--fon-hi) calc(var(--l) * .82) var(--l), transparent calc(var(--l) + 1px)),
    radial-gradient(circle at 73% 50%, var(--silver-mid) 0 calc(var(--l) * .22), var(--fon-island) calc(var(--l) * .3) calc(var(--l) * .78), var(--fon-hi) calc(var(--l) * .82) var(--l), transparent calc(var(--l) + 1px)),
    radial-gradient(circle at 76% 18%, var(--ink) 0 calc(var(--l) * .22), transparent calc(var(--l) * .3)),
    radial-gradient(circle at 76% 82%, var(--fon-lo) 0 calc(var(--l) * .25), transparent calc(var(--l) * .32)),
    linear-gradient(150deg, color-mix(in srgb, var(--silver-hi) 80%, var(--ink)), var(--silver-mid)); }
/* El pie: la misma pastilla que el chip encendido, con su icono. */
/* El pie va ENCIMA del telefono: debajo lo separaba de la base de la placa. */
.cta-stage figcaption { margin: 0; position: relative; z-index: 2; order: -1; }
.perks .cta-cap { inline-size: auto; }
.cta-cap.is-out { opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
  /* La vuelta (Oriol): continua, sin frenar de espaldas. Dos animaciones que
     no se pisan: `rotate` gira de 0 a 360° y aterriza suave en la pose de
     flotar, y `translate` pega el saltito. site.js lee el angulo
     real y cambia la captura al pasar de 180°, con la pantalla sin verse. */
  .cta-spin.is-spin { animation: cta-turn 1.9s cubic-bezier(.45, 0, .15, 1), cta-hop 1.9s cubic-bezier(.4, 0, .2, 1); }
  /* Un solo tramo que frena poco a poco y cae justo en la pose de flotar, sin
     pasarse ni volver (Oriol: «que caiga directamente en la posición de
     floating»). */
  @keyframes cta-turn {
    from { rotate: y 0deg; }
    to { rotate: y 360deg; }
  }
  @keyframes cta-hop {
    0% { translate: 0 0; }
    40% { translate: 0 -16px; }
    100% { translate: 0 0; }
  }
  /* La estela: detras del telefono se enciende un destello del bloque que se
     abre con el giro, y el aro de luz del suelo brilla mas mientras dura. */
  .cta-stage::after { content: ""; position: absolute; z-index: 0; inset: 12% -18% 18%; border-radius: 50%;
    pointer-events: none; opacity: 0; transform: scale(.6);
    background: radial-gradient(closest-side, color-mix(in srgb, var(--block) 60%, transparent), transparent 75%); }
  .cta-stage.is-spinning::after { animation: cta-trail 1.9s cubic-bezier(.3, 0, .3, 1); }
  @keyframes cta-trail {
    0% { opacity: 0; transform: scale(.6) rotate(0deg); }
    35% { opacity: .9; transform: scale(1.15, .9) rotate(8deg); }
    100% { opacity: 0; transform: scale(1.3, .7) rotate(-4deg); }
  }
  .cta-stage.is-spinning .stage-floor { filter: brightness(1.6); transition: filter 300ms var(--ease); }
}
@media (max-width: 699.98px) {
  .cta-inline { grid-template-columns: minmax(0, 1fr); grid-template-areas: "txt" "acts"; padding: var(--s5); }
  .cta-stage { display: none; }
  .cta-inline-hd { font-size: var(--step-1); }
  .cta-inline-perks li { font-size: .8rem; padding-inline-end: .6rem; }
}
/* La columna de lectura del post, centrada: cabecera, migas, entradilla y
   cuerpo arrancan en el mismo borde; la CTA sale de ella por los dos lados. */
.read { max-width: 46rem; margin-inline: auto; }

/* 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; }

/* ── 16a · «How it works» (landing/method.html) ───────────────────────────── */
/* La pagina era un muro de prosa (Oriol 2026-10-05): ahora son cifras, cuatro
   pasos con captura real, un contraste de fuentes y la mezcla 3/4/3 dibujada. */
.how-stats { list-style: none; padding: 0; margin: var(--s6) 0 0; display: grid; gap: var(--s3);
  grid-template-columns: repeat(4, minmax(0, 1fr)); }
.how-stats li { display: grid; gap: var(--s2); align-content: start; }
.how-stats b { font-family: var(--mono); font-size: var(--step-3); font-weight: 600; letter-spacing: -.04em; line-height: 1; }
.how-stats span { font-size: var(--step--1); color: var(--muted); line-height: 1.4; }

.how-steps { list-style: none; padding: 0; margin: var(--s7) 0 0; display: grid; gap: var(--s6) var(--s5);
  grid-template-columns: repeat(4, minmax(0, 1fr)); }
.how-steps li { display: grid; gap: var(--s5); align-content: start; justify-items: center; text-align: center; }
.how-steps .fon { --pw: clamp(150px, 17vw, 208px); inline-size: var(--pw); }
.how-step-t { display: grid; gap: var(--s2); justify-items: inherit; }
.how-n { font-family: var(--mono); font-size: .69rem; font-weight: 500; letter-spacing: .17em; text-transform: uppercase;
  color: color-mix(in srgb, var(--block) 60%, var(--ink)); }
.how-steps p { font-size: var(--step--1); color: var(--muted); line-height: 1.55; max-width: 30ch; }
.how-note { margin-block-start: var(--s6); color: var(--muted); font-size: var(--step--1); }

.how-vs { display: grid; gap: var(--s4); grid-template-columns: repeat(2, minmax(0, 1fr)); margin-block-start: var(--s6); }
.how-vs h3 { font-size: var(--step-0); margin-block-end: var(--s4); }
.how-vs ul { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s3); }
.how-vs li { display: flex; gap: var(--s3); line-height: 1.5; }
.how-vs .card:not(.how-vs-us) li { color: var(--muted); }
.how-vs-us { border-color: color-mix(in srgb, var(--success) 55%, transparent); }
.how-quote { margin-block-start: var(--s4); background: var(--block-soft);
  border-color: color-mix(in srgb, var(--block) 55%, transparent); }
.how-quote p { font-size: var(--step-1); line-height: 1.45; letter-spacing: -.012em; max-width: 62ch; }

.how-mix { display: flex; gap: var(--s3); margin-block-start: var(--s6); max-width: 780px; }
.how-mix-g { flex: var(--n) 1 0; min-inline-size: 0; display: grid; gap: var(--s2); align-content: start; }
.how-mix-bar { display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); gap: var(--s1); margin-block-end: var(--s2); }
.how-mix-bar i { block-size: 14px; border-radius: var(--r-pill); background: var(--block); }
.how-mix b { font-family: var(--mono); font-size: var(--step-2); font-weight: 600; line-height: 1; color: var(--block); }
.how-mix span { font-size: var(--step--1); color: var(--muted); line-height: 1.35; }

@media (max-width: 899.98px) {
  .how-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .how-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .how-steps { grid-template-columns: minmax(0, 1fr); gap: var(--s5); margin-block-start: var(--s6); }
  .how-steps li { grid-template-columns: 112px minmax(0, 1fr); align-items: center; justify-items: start; text-align: start; gap: var(--s4); }
  .how-steps .fon { --pw: 112px; }
  .how-vs { grid-template-columns: minmax(0, 1fr); }
  .how-quote p { font-size: var(--step-0); }
}

/* 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; }
/* Las fuentes del post muestran la URL (hasta 72 caracteres): sin corte, a 375 px
   empujaban la página a ~460 de ancho con scroll horizontal (QA 2026-09-25). */
.topics .link-out { white-space: normal; overflow-wrap: anywhere; }

/* ── 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); }
/* Redes: a la derecha del pie (space-between de .ftr-grid); en móvil bajan debajo. */
.ftr-social a { 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); }

/* ── 17b · Glass (port de lib/theme/ds2_glass.dart) ──────────────────────── */
/* El acabado de las tarjetas de la app, 2026-09-20: tinte del bloque, reflejo
   diagonal y opacidad 0,88-0,96, sin blur ni sombra. Va al final para ganar a
   los `background` de cada componente con la misma especificidad; el degradado
   se escribe en el propio elemento para que `--glass-body` sea el de SU gama
   (las puertas del hub llevan cada una la suya). Lo que NO lleva glass, igual
   que en la app: el bloque (plano de color), las superficies de lectura larga
   (tarjeta de la baraja) y el feedback de acierto/error, que siguen solidos. */
.card, .door, .tl-item, .picker, .cta {
  background: linear-gradient(135deg,
      color-mix(in srgb, color-mix(in srgb, var(--ink) 5%, var(--glass-body)) 96%, transparent) 0%,
      color-mix(in srgb, var(--glass-body) 88%, transparent) 48%,
      color-mix(in srgb, var(--card) 94%, transparent) 100%);
  border-color: var(--glass-edge);
}
.cta { border-inline: 0; border-block-end: 0; }
/* Estados que ya pintaban su borde: el glass no se los quita. */
.card--lesson:hover, .door:hover, .deck > .card.is-playing { border-color: var(--block); }
.cut { border-color: var(--block); }
/* Lectura larga: la tarjeta de la baraja se queda opaca (en la app, el lector
   va sin estrellas); conserva el filete de cristal. */
.deck > .card { background: var(--card); }

/* ── 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; }
}
