/* ═══════════════════════════════════════════════════════════════════════════
   ds2.css — la máquina SEO de sobresalia.com en DS2 «Bloque de color»

   Sustituye a `web.css` Y a la dependencia de `landing/styles.css`: estas 627
   páginas ya no toman NADA de la landing, así que la landing puede migrar por su
   cuenta sin que las dos ramas se pisen. Cuando la landing entre en DS2, apunta
   aquí y este fichero pasa a ser el único CSS del dominio.

   Dirección (spec 2026-08-10-web-ds2-design.md): el índigo NO rellena cajas,
   SUBRAYA. El marfil manda en el fondo; el color de marca aparece para marcar lo
   que importa, que es lo que hace un opositor con un boli sobre la ley.

   Portado del prototipo `design/mockups/web_ds2/ds2.css`, que mide 0 de 1.428
   nodos por debajo de AA en las dos skins. Los comentarios de contraste que
   vienen de allí son mediciones, no opiniones: no los toques sin volver a medir
   con `tools/design/contrast_audit_web.py`.

   Cero oro en todo el fichero: el oro es de premium (CLAUDE.md), y premium aquí
   se marca con FORMA (relleno de tinta), no con color.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · Tokens ──────────────────────────────────────────────────────────── */
:root {
  color-scheme: light dark;

  /* Skin Mármol — la misma que la app */
  --paper: #F2EFE9;
  --card: #FFFFFF;
  --cline: #E0DACE;
  --cline-strong: #C9C1B1;
  --ink: #17140F;         /* 16,00 sobre papel */
  --muted: #6B6455;       /* 5,11 sobre papel */
  --block: #394CCA;       /* Índigo · 5,98 sobre papel */
  --on-block: #FFFFFF;    /* 6,86 sobre bloque */
  --link: #394CCA;        /* en Mármol el enlace ES el bloque */
  --mark: rgba(57, 76, 202, .22);        /* la marca: tinta encima queda a 11,48 */
  --mark-strong: rgba(57, 76, 202, .38); /* la de titular: tinta encima a 9,19 */
  /* Dentro del bloque el rotulador NO puede ser índigo: sería índigo sobre
     índigo. El lavado va SIEMPRE hacia el lado contrario al del texto —así es
     como funciona un rotulador de verdad, hundiendo la superficie—. */
  --mark-on-block: rgba(0, 0, 0, .26);
  --block-soft: #E7EAF9;            /* bloque al 12 % sobre tarjeta */
  --premium: #17140F;               /* premium se marca con FORMA: relleno de tinta */
  --on-premium: #F2EFE9;
  --success: #1F7A3A;
  --danger: #C0392B;
  /* Velo de las secciones con vídeo: ÍNDIGO, no el marrón de DS1. La alfa no es
     estética — está calculada contra el PEOR fotograma posible (blanco puro): ahí
     el titular queda a 6,79 y el párrafo a 5,10. Así el texto pasa AA sea cual sea
     el frame, que era la única salvedad que la auditoría no podía medir.
     ⚠️ Sin esta variable `.shot::after` queda transparente y el texto cae sobre el
     vídeo a pelo. Se quedó fuera en el primer port y lo cazó la portada. */
  --veil: rgba(12, 18, 48, .72);

  /* Tipografía — Archivo variable hace display y texto con un solo fichero:
     el ancho (wdth 112) da la voz de titular sin meter una segunda familia. */
  --sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'Spline Sans Mono', ui-monospace, SFMono-Regular, 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);

  --measure: 68ch;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --col: 1120px;
  --r: 3px;               /* DS2: casi recto */

  --dur: 220ms;
  --dur-slow: 620ms;
  --ease: cubic-bezier(.22, 1, .36, 1);       /* out-quart */

  --z-sticky: 100;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  /* Skin Grafito — neutro FRÍO, no el ónice cálido de DS1 */
  --paper: #15181C;
  --card: #1E232A;
  --cline: #2C333C;
  --cline-strong: #3D4650;
  --ink: #EDF1F5;
  --muted: #9AA6B2;
  --block: #6F7AC7;
  --on-block: #12151A;
  --link: #8E97D9;        /* el bloque a secas da 3,98 sobre tarjeta: se aclara */
  --mark: rgba(111, 122, 199, .30);
  --mark-strong: rgba(111, 122, 199, .42);
  --mark-on-block: rgba(255, 255, 255, .26);
  --block-soft: #282D3D;
  --premium: #EDF1F5;
  --on-premium: #15181A;
  --success: #5FBE73;
  --danger: #F0725F;
  --veil: rgba(12, 18, 48, .74);
}

/* ── 1b · Transición entre páginas ───────────────────────────────────────── */
/* Transiciones de vista ENTRE DOCUMENTOS: tres líneas de CSS que hacen que un
   sitio estático de 627 páginas navegue como una app, sin router ni framework.
   Donde no hay soporte simplemente no pasa nada. */
@view-transition { navigation: auto; }

::view-transition-old(root) { animation: vt-out 140ms var(--ease) both; }
::view-transition-new(root) { animation: vt-in 260ms var(--ease) both; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; transform: translateY(6px); } }

/* El chrome no debe parpadear al cambiar de página: con nombre propio el
   navegador lo trata como el MISMO elemento entre documentos. */
.hdr { view-transition-name: chrome-top; }
.ftr { view-transition-name: chrome-bottom; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}

/* ── 2 · Base ────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

/* El papel se pinta en `html`, no en `body`: así la malla de triángulos puede
   vivir en `z-index: -1` —por DETRÁS de todo el contenido y de los paneles, que
   es donde la quiere Oriol— y seguir viéndose, porque el fondo que la taparía
   está un nivel más arriba. */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; background: var(--paper); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  font-family: var(--sans);
  font-size: var(--step-0);
  line-height: 1.62;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: transparent;       /* el papel lo pinta `html` (ver arriba) */
  -webkit-font-smoothing: antialiased;
  min-height: 100dvh;
}

h1, h2, h3, h4 { line-height: 1.06; letter-spacing: -.03em; font-weight: 700; text-wrap: balance; }
h1 { font-size: var(--step-4); font-stretch: 112%; letter-spacing: -.038em; }
h2 { font-size: var(--step-3); font-stretch: 108%; }
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; }

::selection { background: var(--mark); color: var(--ink); }

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

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

/* ── 3 · El gesto: la marca ──────────────────────────────────────────────── */
/* El índigo no rellena: subraya. La banda se define DENTRO del degradado («el
   76 % de abajo»), no colocando una caja con `background-position`: ese
   porcentaje se mide contra la caja del inline —la altura de la fuente, no la
   del renglón— y la misma regla salía a media altura en el cuerpo, leyendo como
   un TACHADO. Al 76 % la banda cubre la minúscula entera. */
.mk {
  background-image: linear-gradient(to top, var(--mark) 0 76%, transparent 76%);
  background-repeat: no-repeat;
  background-size: 100% 100%;
  padding-inline: .1em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
/* En titular la marca se satura: a 4 rem, la misma alfa se diluye. */
h1 .mk, h2 .mk { --mark: var(--mark-strong); }
.block .mk, .block h1 .mk { --mark: var(--mark-on-block); }

/* Barrido de entrada: el default ya es visible (banda completa) y la animación
   solo se AÑADE. Si no corre —pestaña oculta, render headless— no se pierde nada,
   que es la trampa clásica de los reveals. */
@media (prefers-reduced-motion: no-preference) {
  .mk-sweep { animation: sweep var(--dur-slow) var(--ease) .18s both; }
  @keyframes sweep { from { background-size: 0 100%; } to { background-size: 100% 100%; } }
}

/* ⚠️ La marca se pone A MANO y una sola vez por página (spec §0.1): el término
   que define el titular, la cifra protagonista o la frase del artículo de la que
   sale la pregunta. Si se marca todo, deja de marcar nada. */

/* ── 4 · Chrome ──────────────────────────────────────────────────────────── */
.hdr {
  position: sticky; top: 0; z-index: var(--z-sticky);
  background: var(--paper);
  border-bottom: 1px solid var(--cline);
}
.hdr .wrap { display: flex; align-items: center; gap: 1.5rem; min-height: 60px; position: relative; }
/* El lockup: emblema + palabra. El emblema es el BIRRETE con la S
   (DESIGN_SYSTEM_2 §5 bis, decisión de Oriol 2026-08-11), y se IMPORTA de
   `assets/brand/` — nunca se redibuja: redibujarlo a ojo ya produjo once
   variantes distintas de la marca, y la S son curvas medidas, no una fuente.

   Dos ficheros y no un `currentColor` porque el birrete es BICOLOR (el birrete
   detrás, la S delante, separadas por un filo que es un AGUJERO): teñirlo de un
   solo color lo destruye. Sobre Mármol va el índigo (5,98 sobre el papel); sobre
   Grafito, el marfil — el índigo daría 2,1 y no llega ni a los 3,0 de un
   gráfico. Es la misma regla que ya usaban los badges de tienda. */
.wm {
  padding-block: .55rem;   /* caja táctil de 44 px, no solo el texto */
  font-weight: 800; font-size: 1.05rem; font-stretch: 108%; letter-spacing: -.035em;
  text-decoration: none; margin-right: auto;
  display: inline-flex; align-items: center; gap: .5rem;
}
.wm img { inline-size: 26px; block-size: 26px; flex: none; }
.wm .m-dark { display: none; }
:root[data-theme="dark"] .wm .m-light { display: none; }
:root[data-theme="dark"] .wm .m-dark { display: block; }
/* ⚠️ SOBRE UN BLOQUE PINTADO LA REGLA SE INVIERTE, y no es un capricho: en Mármol
   el bloque es índigo oscuro y pide el arte marfil; en Grafito el bloque es índigo
   claro con tinta oscura y pide el arte índigo. Es la lógica de `onBlock`, no la
   del papel — la misma que ya obliga a que el texto del bloque cambie de color. */
.block .m-light, .shot .m-light { display: none; }
.block .m-dark, .shot .m-dark { display: block; }
:root[data-theme="dark"] .block .m-light,
:root[data-theme="dark"] .shot .m-light { display: block; }
:root[data-theme="dark"] .block .m-dark,
:root[data-theme="dark"] .shot .m-dark { display: none; }
/* El acento del wordmark usa --link, no --block: en Grafito el bloque a
   secas da 4,48 sobre el papel y esto es texto de 17 px en negrita. */
.wm i { font-style: normal; color: var(--link); }
.nav { display: flex; gap: 1.35rem; font-size: var(--step--1); font-weight: 500; }
.nav a { color: var(--muted); text-decoration: none; transition: color var(--dur) var(--ease);
  padding-block: .45rem; white-space: nowrap; }
.nav a:hover, .nav a[aria-current] { color: var(--ink); }
.nav a[aria-current] { background-image: linear-gradient(var(--block), var(--block));
  background-repeat: no-repeat; background-position: 0 calc(100% - .45rem); background-size: 100% 2px; }

/* Menú de móvil. 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 y no se
   ve venir: el navegador oculta el contenido de un `details` cerrado aunque le
   pongas `display: contents` (en Chrome moderno va por `::details-content`), así
   que la navegación desaparecía en TODAS las páginas a partir de 861 px.
   Cero JS, y el estado abierto/cerrado lo anuncia el propio `<summary>`. */
.menu { display: none; }
@media (max-width: 980px) {
  .menu { display: block; margin-left: auto; }
  .menu > summary {
    display: grid; place-items: center; inline-size: 44px; block-size: 44px;
    border: 1px solid var(--cline-strong); border-radius: var(--r);
    color: var(--muted); cursor: pointer; list-style: none;
  }
  .menu > summary::-webkit-details-marker { display: none; }
  .menu[open] > summary { color: var(--ink); border-color: var(--ink); }
  .menu > summary svg { inline-size: 19px; block-size: 19px; }

  .hdr .nav { display: none; }
  .menu[open] ~ .nav {
    display: flex; flex-direction: column; gap: 0;
    position: absolute; inset-inline: 0; top: 100%;
    background: var(--paper); border-block-end: 1px solid var(--cline);
    padding: .4rem var(--gutter) 1rem;
  }
  .menu[open] ~ .nav a { padding-block: .85rem; border-block-start: 1px solid var(--cline); font-size: var(--step-0); }
  .menu[open] ~ .nav a[aria-current] { background: none; color: var(--ink); }
}

/* Interruptor de tema. 44 px de caja táctil: sin esto cae a los estilos por
   defecto del navegador (16×6 px) y en la captura sigue pareciendo un botón. */
.thm {
  display: inline-grid; place-items: center; inline-size: 44px; block-size: 44px;
  border: 1px solid var(--cline-strong); border-radius: var(--r);
  background: none; color: var(--muted); cursor: pointer; flex: none;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.thm:hover { color: var(--ink); border-color: var(--ink); }
.thm svg { inline-size: 17px; block-size: 17px; }
:root[data-theme="dark"] .thm .i-moon, :root:not([data-theme="dark"]) .thm .i-sun { display: none; }

/* Hilo de lectura: cuánto queda del documento. Solo en las 438 páginas de
   lectura larga; en el resto no aporta nada y no se pinta. */
.thread { position: fixed; inset-block-start: 0; inset-inline-start: 0; block-size: 2px;
  inline-size: 0; background: var(--block); z-index: calc(var(--z-sticky) + 1);
  transition: inline-size 90ms linear; }
html.nojs .thread { display: none; }

/* ── 5 · El bloque (solo donde hay un dato protagonista) ─────────────────── */
.block {
  background: var(--block); color: var(--on-block);
  padding-block: clamp(2.2rem, 5vw, 3.4rem);
}
/* ⚠️ DENTRO DEL BLOQUE NO HAY TONO SECUNDARIO, y no es pereza: está medido.
   En Grafito el bloque es claro con tinta oscura, y esa tinta a plena opacidad
   ya solo da 4,60 sobre el bloque — cualquier `color-mix` hacia transparente lo
   tira por debajo de AA (0,78 → 3,52). La jerarquía dentro del bloque la hacen
   el TAMAÑO, el peso y la mono, nunca la opacidad. */
.block .eyebrow,
.block .crumbs,
.block .crumbs a,
.block .lead,
.block .fig span,
.block a { color: var(--on-block); }
.block h1 { color: var(--on-block); }
.block h1 { margin-top: .7rem; max-width: 18ch; }
.block .lead { max-width: 56ch; font-size: var(--step-0); color: var(--on-block); }
/* El dato que acompaña al título (una fecha de actualización, un número de
   convocatoria) va en MONO, que es la voz de los datos en este sistema. En
   `onBlock` a plena opacidad: dentro del bloque no hay tono secundario. */
.block .meta { font-family: var(--mono); font-size: .74rem; color: var(--on-block); margin-top: 1rem; }
.block .crumbs { opacity: 1; }
.block .crumbs a { text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--on-block) 45%, transparent); }

/* El eyebrow existe UNA vez por página, dentro del bloque o de la cabecera.
   Repetirlo encima de cada sección es andamiaje, no voz. */
.eyebrow {
  font-family: var(--mono); font-size: .69rem; font-weight: 500;
  letter-spacing: .17em; text-transform: uppercase; color: var(--muted);
}

/* ── 6 · Cabecera de página sin bloque ───────────────────────────────────── */
.phead { padding-block: clamp(2rem, 5vw, 3.2rem) clamp(1.4rem, 3vw, 2rem); }
.phead h1 { font-size: var(--step-3); max-width: 20ch; }
.phead .meta { font-family: var(--mono); font-size: .74rem; color: var(--muted); margin-top: 1rem; }
/* Lo primero que va sobre el papel después del bloque necesita aire propio: las
   páginas SEO lo cobraban del margen del CTA de cabecera, pero un legal no lleva
   CTA y el cuerpo quedaba pegado al borde del color. */
.block + main > .wrap:first-child,
.block + .wrap { padding-block-start: clamp(2rem, 5vw, 3rem); }

.crumbs { font-family: var(--mono); font-size: .74rem; color: var(--muted); margin-bottom: 1.1rem; }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--ink); }
/* ⚠️ Tres intentos y dos lecciones, las dos medidas.
   1) El separador iba con `opacity: .5`, y eso es un PUNTO CIEGO del medidor: el
      recorrido del DOM lee el `color` computado y la opacidad del elemento no
      entra ahí, así que un `›` a media tinta pasaba la auditoría sin que nadie lo
      mirase. Al darle un color de verdad, empezó a medirse — y falló.
   2) Bajarlo de tono tampoco vale: el filete daba 1,56 sobre el papel y, dentro
      del bloque, el `onBlock` al 55 % daba 3,22. Es la misma ley que DS2 §2 ya
      había escrito para el bloque: **la jerarquía no se hace con opacidad**. Aquí
      la hace el tamaño (11,8 px frente a los 17 del cuerpo).
   `:not(.here)` porque la última miga TAMBIÉN es un `<span>` y se llevaba por
   delante el color del separador. */
.crumbs span:not(.here) { color: var(--muted); margin-inline: .4em; }
.block .crumbs span:not(.here) { color: var(--on-block); }
/* La última miga no es un separador: es dónde estás. */
.crumbs .here { color: inherit; margin: 0; }
.lead { font-size: var(--step-1); line-height: 1.45; color: var(--muted); max-width: 58ch;
  margin-top: 1.1rem; letter-spacing: -.012em; }

/* ── 7 · Datos ───────────────────────────────────────────────────────────── */
.figs { display: flex; flex-wrap: wrap; gap: clamp(1.5rem, 5vw, 3.6rem); margin-top: 1.9rem; }
.fig b { display: block; font-size: var(--step-2); font-weight: 700; font-stretch: 108%;
  letter-spacing: -.035em; line-height: 1; }
.fig span { display: block; font-family: var(--mono); font-size: .72rem; color: var(--muted); margin-top: .5rem; }

/* ── 8 · Superficies ─────────────────────────────────────────────────────── */
.section { padding-block: clamp(2.6rem, 6vw, 4.6rem); }
.section + .section { border-top: 1px solid var(--cline); }
/* Primera sección después del bloque: ni filete ni aire arriba — el bloque ya
   los pone. Sin esto, la página abre con una franja vacía de 4,6 rem justo donde
   más importa que se vea el contenido. */
.section-top { border-top: 0 !important; padding-top: 0; }

/* Sección sin filete propio: para las que se encadenan DENTRO de una lista ya
   reglada (los grupos del hub). Con `.section` el filete de la sección caía
   pegado al borde inferior de la última fila del grupo anterior y se veía doble. */
.section-flush { padding-block: 2.2rem 0; }
.section-flush + .section-flush { border-top: 0; }
.sec-h { font-size: var(--step-2); margin-bottom: .5rem; }
/* Titular de segundo nivel: el de un grupo dentro de una página que ya tiene su
   `.sec-h`. Mismo objeto, un escalón menos. */
.sec-h-sm { font-size: var(--step-1); margin-bottom: .5rem; }
.sec-p { color: var(--muted); max-width: 56ch; margin-bottom: 1.8rem; }

/* Icono dentro de una frase (un «ver en el BOE ↗»). `svg { display: block }` lo
   saca del renglón, así que en línea hay que devolverlo a la línea base — y con
   `vertical-align`, no con un `inline-flex` en el enlace: con un título largo el
   flex comprime el icono en vez de dejarlo caer al final del texto. */
a .ico, p .ico, li .ico, td .ico { display: inline-block; vertical-align: -.18em; }

/* Filas antes que tarjetas: DS2 pide ángulos y las listas densas se leen mejor
   con filas regladas que con una rejilla de cajas idénticas. */
.rows { border-top: 1px solid var(--cline); }
.row {
  display: flex; align-items: baseline; gap: 1rem;
  padding: .85rem .6rem; border-bottom: 1px solid var(--cline);
  text-decoration: none; transition: background var(--dur) var(--ease);
}
.row:hover { background: color-mix(in srgb, var(--block) 5%, transparent); }
/* ⚠️ SIN ESTO EL BUSCADOR NO OCULTA NADA. `[hidden]` lo pinta el navegador con
   `display:none`, pero es una regla de la HOJA DE USUARIO y la del autor
   (`.row{display:flex}`) le gana: las filas seguían viéndose aunque el contador
   dijera «3 de 29». El `web.css` viejo ya tenía este aviso escrito para
   `.opo-item`; el fallo se repitió igual. */
.row[hidden], [data-opo][hidden], [data-group][hidden] { display: none !important; }
.row .idx { font-family: var(--mono); font-size: .8rem; color: var(--muted); min-inline-size: 2.4ch; flex: none; }
.row .ttl { font-weight: 550; }
.row .meta-ico { display: inline-flex; align-items: center; gap: .35rem; }
.row .meta { margin-inline-start: auto; font-family: var(--mono); font-size: .76rem; color: var(--muted); white-space: nowrap; }
.row.soon { opacity: .62; pointer-events: none; }
/* En móvil la fila se parte en dos renglones. `white-space: nowrap` en la meta es
   lo correcto en escritorio (mantiene «C1 · 109 guías · test gratis» de una pieza),
   pero a 390 px una meta larga empujaba la página a 405 px de ancho: el único
   desbordamiento horizontal que quedaba en todo el sitio. */
@media (max-width: 560px) {
  .row { flex-wrap: wrap; row-gap: .2rem; }
  .row .meta { margin-inline-start: 0; white-space: normal; }
  .row .idx + .ttl { flex: 1 1 auto; }
}
/* Fila de lectura (blog, temario con resumen): el título manda y la meta va arriba. */
.row-stack { flex-direction: column; align-items: flex-start; gap: .35rem; padding-block: 1.15rem; }
.row-stack .meta { margin-inline-start: 0; }
.row-stack .ttl { font-size: var(--step-1); letter-spacing: -.02em; font-weight: 700; }
.row-stack .desc { color: var(--muted); font-size: var(--step--1); }

/* Puertas de entrada: NO es una rejilla de tarjetas iguales — cada una dice una
   cosa distinta. Un solo componente para las tres variantes que había. */
.doors { display: grid; gap: 1px; background: var(--cline); border: 1px solid var(--cline);
  border-radius: var(--r); overflow: hidden;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); margin-top: 1.6rem; }
.door { background: var(--card); padding: 1.35rem 1.3rem 1.5rem; text-decoration: none;
  display: flex; flex-direction: column; gap: .45rem;
  transition: background var(--dur) var(--ease); }
.door:hover { background: var(--block-soft); }
.door h3 { font-size: var(--step-1); }
.door p { font-size: var(--step--1); color: var(--muted); line-height: 1.5; }
.door .go { margin-top: auto; padding-top: .8rem; font-family: var(--mono); font-size: .74rem; color: var(--link); }
.door svg { inline-size: 20px; block-size: 20px; color: var(--link); margin-bottom: .35rem; }
.door.soon { opacity: .66; }
.door.soon .go { color: var(--muted); }

/* Enlace-CTA suelto (no el de dentro de un párrafo): necesita caja táctil. */
.lnk { display: inline-block; color: var(--link); text-decoration: none; font-weight: 600;
  padding-block: .75rem; }   /* 44 px de caja táctil */
.lnk:hover { text-decoration: underline; text-underline-offset: 3px; }

/* Enlace en un sitio donde el color no se hereda solo (un `<h3>`, una celda). */
.link { color: var(--link); text-decoration: none; }
.link:hover { text-decoration: underline; text-underline-offset: 3px; }
/* Cuando el enlace ES el titular de una sección (el nombre de la oposición en el
   calendario), en móvil necesita caja táctil: no le vale la excepción de los
   enlaces en línea dentro de una frase, porque es el objetivo principal de ese
   bloque. En escritorio se queda como está — ahí se pulsa con un ratón. */
@media (max-width: 860px) {
  h2 > .link, h3 > .link { display: inline-block; padding-block: .6rem; }
}

/* Enlace que SALE del sitio (un boletín oficial) y lleva su icono. `inline-flex`
   para que el icono no se despegue del texto al final del renglón — aquí sí vale
   porque son etiquetas cortas («Ver en BOJA ↗»), no títulos de 170 caracteres. */
.link-out { display: inline-flex; align-items: center; gap: .3rem; white-space: nowrap;
  color: var(--link); text-decoration: none; }
.link-out:hover { text-decoration: underline; text-underline-offset: 3px; }

/* Fila con icono: `align-items: baseline` es lo correcto para texto, pero
   descoloca un SVG (que no tiene línea base). */
.row-ico { align-items: center; }

/* ── 8b · Tablas de datos ────────────────────────────────────────────────────
   Un solo tratamiento para las cuatro que tenía la web. Cabecera en mono sobre
   `blockSoft`, filas separadas por filete y NADA de esquinas redondeadas: una
   tabla con radio y `overflow:hidden` es justo lo que DS2 quita. */
.tblwrap { overflow-x: auto; margin-block: 1.2rem; }
.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; white-space: nowrap;
}
.tbl td { padding: .75rem .8rem; border-block-end: 1px solid var(--cline); vertical-align: top; }
.tbl tr:last-child td { border-block-end: 0; }
.tbl tbody tr:hover { background: color-mix(in srgb, var(--block) 4%, transparent); }
.tbl a { color: var(--link); text-decoration: none; }
.tbl a:hover { text-decoration: underline; text-underline-offset: 2px; }
.tbl .num { font-family: var(--mono); white-space: nowrap; }
.tbl .more { font-family: var(--mono); font-size: .72rem; color: var(--muted); white-space: nowrap; }
/* La columna que gana en una comparativa: peso, no color de fondo. */
.tbl .ours { color: var(--ink); font-weight: 600; }
.tbl thead .ours { color: var(--ink); }

/* Chips: etiquetas de dato. */
.chips { list-style: none; padding: 0; margin: 1rem 0 0; display: flex; flex-wrap: wrap; gap: .45rem; }
.chips li, .chip {
  font-family: var(--mono); font-size: .72rem; color: var(--muted);
  border: 1px solid var(--cline); border-radius: 999px; padding: .35rem .75rem;
  display: inline-block;
}
.chip-on { border-color: var(--block); color: var(--link); }
/* Estado de cobertura (índice de leyes). El «Disponible» era el único fallo de
   contraste que caía TAMBIÉN en oscuro (111 nodos): ahora es forma + mono. */
.chip-live { border-color: var(--block); color: var(--link); }
.chip-soon { border-style: dashed; }
.block .chips li, .block .chip { border-color: color-mix(in srgb, var(--on-block) 45%, transparent); color: var(--on-block); }

/* ── 8c · Buscador de oposiciones ────────────────────────────────────────────
   Filtra en el cliente sobre filas que YA están en el HTML — no pide nada al
   servidor, así que sirve igual en un sitio estático y el contenido sigue siendo
   indexable, que en una web que vive del SEO no es un detalle.

   Sin JS el buscador no se pinta (no podría funcionar) y la lista se muestra
   entera: nunca se esconde contenido detrás de un control que no responde. */
html.nojs .finder { display: none; }

.finder { margin-block: 1.4rem 1.1rem; }
.finder-in { position: relative; display: flex; align-items: center; }
.finder-in > svg { position: absolute; inset-inline-start: .85rem; inline-size: 17px; block-size: 17px;
  color: var(--muted); pointer-events: none; }
.finder input {
  font: inherit; font-size: var(--step-0); color: var(--ink);
  inline-size: 100%; padding: .8rem 2.6rem .8rem 2.6rem;
  background: var(--card); border: 1px solid var(--cline-strong); border-radius: var(--r);
  transition: border-color var(--dur) var(--ease);
}
/* Que un placeholder largo se corte con puntos suspensivos y no a media palabra
   («…o tu cuerp», Oriol 2026-08-12). El tamaño no se toca: por debajo de 16 px
   iOS hace zoom al enfocar el campo. */
.finder input { text-overflow: ellipsis; }
.finder input::placeholder { color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.finder input:focus { border-color: var(--block); outline: none; box-shadow: 0 0 0 2px var(--mark); }
.finder input::-webkit-search-cancel-button { display: none; }
.finder-x { position: absolute; inset-inline-end: .45rem; inline-size: 34px; block-size: 34px;
  display: none; place-items: center; border: 0; background: none; color: var(--muted);
  cursor: pointer; font: inherit; font-size: 1.1rem; border-radius: var(--r); }
.finder[data-has] .finder-x { display: grid; }
/* Mientras no hay texto no hay ✕, así que ese hueco reservado a la derecha son
   26 px que le faltan al placeholder para caber en una pantalla de 320 px. */
.finder:not([data-has]) input { padding-inline-end: 1rem; }
.finder-x:hover { color: var(--ink); }

/* Filtros por ámbito. Son botones, no enlaces: no llevan a ninguna parte. */
.finder-tags { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .8rem; }
.finder-tags button {
  font-family: var(--mono); font-size: .72rem; color: var(--muted);
  border: 1px solid var(--cline); border-radius: 999px; padding: .4rem .8rem;
  background: none; cursor: pointer; transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.finder-tags button:hover { color: var(--ink); border-color: var(--cline-strong); }
.finder-tags button[aria-pressed="true"] { color: var(--on-block); background: var(--block); border-color: var(--block); }
/* En móvil los chips SÍ necesitan caja táctil: son controles, no enlaces dentro
   de una frase, así que no les vale la excepción de los enlaces en línea. */
@media (max-width: 860px) { .finder-tags button { padding-block: .8rem; } }

.finder-n { font-family: var(--mono); font-size: .74rem; color: var(--muted); margin-top: .85rem; }
.finder-n b { color: var(--ink); font-weight: 600; }

/* Estado vacío: dice qué hacer, no solo que no hay nada. */
.finder-none { border: 1px dashed var(--cline-strong); border-radius: var(--r); padding: 1.6rem 1.4rem;
  color: var(--muted); font-size: var(--step--1); }
.finder-none b { color: var(--ink); font-weight: 600; }
.finder-none .lnk { margin-top: .2rem; }

/* La coincidencia se marca con el rotulador: mismo gesto que el resto. */
.row mark { background: none; color: inherit;
  background-image: linear-gradient(to top, var(--mark) 0 76%, transparent 76%);
  background-repeat: no-repeat; background-size: 100% 100%; padding-inline: .06em; }

/* ── 8e · Formularios ────────────────────────────────────────────────────────
   Hasta ahora el único campo del sistema era el del buscador, y los formularios
   de embajadores lo reusaban: heredaban sus **41,6 px de sangrado** (el hueco de
   la lupa y de la ✕), que a 375 px se comían 83 px de ancho útil para nada.

   Y algo peor: `--danger` y `--success` solo existían dentro de componentes
   concretos (`.q-opt.ko`, `.duo .lbl`), así que **un error de validación se
   quedaba sin rojo** — «los dos correos no coinciden» salía en gris, igual que
   una nota. Un formulario sin estado de error no avisa, solo informa. */
.field { margin-block: 1.1rem; }
.field > label { display: block; font-weight: 550; font-size: var(--step--1); margin-bottom: .4rem; }
.field input, .field select, .field textarea {
  font: inherit; font-size: var(--step-0); color: var(--ink);
  inline-size: 100%; padding: .75rem .9rem;
  background: var(--card); border: 1px solid var(--cline-strong); border-radius: var(--r);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.field textarea { min-block-size: 7rem; resize: vertical; line-height: 1.55; }
.field input::placeholder, .field textarea::placeholder { color: var(--muted); }
.field :is(input, select, textarea):focus {
  border-color: var(--block); outline: none; box-shadow: 0 0 0 2px var(--mark);
}
/* La ayuda va en mono: es metadato del campo, no prosa. */
/* Sin anidar bajo `.field`: el JS del formulario reescribe el `className` de los
   avisos de nivel de formulario, que viven fuera de cualquier campo. */
.help { font-family: var(--mono); font-size: .72rem; color: var(--muted); margin-top: .45rem; }
/* Error y acierto: par fijo fuera de gama + GLIFO, igual que en el quiz. El glifo
   no es decoración — contra algunos bloques el tono solo no es inequívoco. */
.err { font-family: var(--mono); font-size: .72rem; color: var(--danger); margin-top: .45rem; }
.ok  { font-family: var(--mono); font-size: .72rem; color: var(--success); margin-top: .45rem; }
/* El glifo lo pone el CSS, no el texto: así un error escrito en el HTML y otro
   inyectado por el JS se ven igual. `:empty` evita el glifo huérfano cuando el
   aviso está vacío, que es su estado normal. */
.err:not(:empty)::before { content: "✗ "; font-weight: 700; }
.ok:not(:empty)::before { content: "✓ "; font-weight: 700; }
.field :is(input, select, textarea)[aria-invalid="true"] { border-color: var(--danger); border-width: 2px; padding: calc(.75rem - 1px) calc(.9rem - 1px); }
/* Un código de invitación se dicta y se teclea: mayúsculas y aire entre letras. */
.code-input { text-transform: uppercase; letter-spacing: .14em; font-family: var(--mono); }
/* Mensaje de estado del formulario entero (enviando, enviado, ha fallado). */
.form-note { font-family: var(--mono); font-size: .74rem; color: var(--muted); margin-top: .9rem; }
.form-note.is-err { color: var(--danger); }
.form-note.is-ok { color: var(--success); }

/* La tienda que corresponde al dispositivo detectado se marca con FORMA (un aro
   de bloque), no agrandando el badge: el arte de Apple y Google es intocable. */
.badges a.is-hot { border-radius: calc(var(--r) + 2px); box-shadow: 0 0 0 2px var(--block); }

/* Barra suelta para el interruptor de tema en las páginas SIN chrome (`c.html`,
   `descargar.html`): vienen de un código o de un reel y no llevan cabecera, pero
   sí tienen que poder cambiar de tema. */
.thm-bar { display: flex; justify-content: flex-end; padding: .6rem var(--gutter); }

/* ── 8d · Piezas editoriales (método y blog) ────────────────────────────────
   Las dos páginas más largas del sitio eran un muro de párrafos. Cada pieza
   existe porque hay un tipo de contenido que se lee mejor así que en prosa. */

/* Contraposición: dos paneles enfrentados. Para «esto falla / esto lo arregla». */
.duo { display: grid; gap: 1px; background: var(--cline); border: 1px solid var(--cline);
  border-radius: var(--r); overflow: hidden; margin-block: 1.6rem;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.duo > div { background: var(--card); padding: 1.5rem 1.4rem; }
.duo .lbl { font-family: var(--mono); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--danger); margin-bottom: .8rem; }
.duo h3 { font-size: var(--step-1); }
.duo p { color: var(--muted); font-size: var(--step--1); margin-top: .6rem; line-height: 1.6; }
.duo .then { color: var(--ink); font-weight: 600; margin-top: .9rem; }

/* El circuito de vigilancia. Cuatro paradas con su tiempo real: el argumento más
   difícil de contar en prosa y el más fácil de ver en una línea. */
.flow { margin-block: 1.8rem; }
.flow-t { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--cline);
  border: 1px solid var(--cline); border-radius: var(--r); overflow: hidden; }
.flow-t > div { background: var(--card); padding: 1.1rem 1rem 1.2rem; position: relative; }
.flow-t .when { font-family: var(--mono); font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--link); }
.flow-t .what { font-weight: 650; margin-top: .5rem; font-size: var(--step--1); line-height: 1.35; }
.flow-t .who { color: var(--muted); font-size: .76rem; margin-top: .4rem; line-height: 1.45; }
/* La flecha entre paradas: un pseudo-elemento, no un icono repetido por celda. */
.flow-t > div + div::before { content: "→"; position: absolute; inset-block-start: 1.15rem;
  inset-inline-start: -.62rem; inline-size: 1.24rem; text-align: center; color: var(--muted);
  font-size: .8rem; background: var(--card); }
@media (max-width: 760px) { .flow-t { grid-template-columns: 1fr; }
  .flow-t > div + div::before { content: "↓"; inset-block-start: -.55rem; inset-inline-start: 1rem; } }

/* Lista de piezas numeradas: solo cuando la sección ES una secuencia. */
.steps { counter-reset: st; border-block-start: 1px solid var(--cline); margin-top: 1.4rem; }
.steps > div { counter-increment: st; display: grid; gap: .3rem 1.1rem; grid-template-columns: auto 1fr;
  padding-block: 1.2rem; border-block-end: 1px solid var(--cline); }
.steps > div::before { content: counter(st, decimal-leading-zero); font-family: var(--mono); font-size: .74rem;
  color: var(--link); grid-row: span 2; padding-top: .2rem; }
.steps h3 { font-size: var(--step-1); }
.steps p { color: var(--muted); font-size: var(--step--1); max-width: 58ch; }

/* Cita destacada. Serif NO: peso y tamaño, que es la voz del sistema. */
.pull { margin-block: 2rem; padding-inline-start: 1.4rem; border-inline-start: 2px solid var(--block); }
.pull p { font-size: var(--step-2); line-height: 1.28; letter-spacing: -.025em; font-weight: 650;
  color: var(--ink); text-wrap: balance; }
.pull cite { display: block; font-family: var(--mono); font-size: .74rem; font-style: normal;
  color: var(--muted); margin-top: .9rem; }

/* Ficha de datos: pares etiqueta/valor. Para la convocatoria de un post. */
.ficha { border: 1px solid var(--cline); border-radius: var(--r); background: var(--card);
  padding: 1.3rem 1.4rem; margin-block: 1.6rem; }
.ficha h3 { font-family: var(--mono); font-size: .69rem; font-weight: 500; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 1rem; }
/* Cada par va en su `<div>` (válido dentro de `<dl>` desde HTML5). Sin él, `dt`
   y `dd` entran como celdas SUELTAS en la rejilla y los datos salen en zigzag. */
.ficha dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1.2rem 1.4rem; margin: 0; }
.ficha dl > div { min-inline-size: 0; }
.ficha dt { font-family: var(--mono); font-size: .7rem; color: var(--muted); }
.ficha dd { margin: .3rem 0 0; font-size: var(--step-1); font-weight: 700; letter-spacing: -.025em;
  font-stretch: 106%; line-height: 1.1; }
.ficha dd small { font-size: var(--step--1); font-weight: 500; color: var(--muted); letter-spacing: 0; }
.ficha .src { margin-top: 1.2rem; padding-top: .9rem; border-top: 1px solid var(--cline);
  font-family: var(--mono); font-size: .72rem; color: var(--muted); }

/* Aviso lateral dentro de la lectura: «qué significa esto para ti». */
.note-box { border: 1px solid var(--cline-strong); border-radius: var(--r); padding: 1.3rem 1.4rem;
  margin-block: 1.8rem; background: var(--block-soft); }
.note-box .lbl { font-family: var(--mono); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--link); margin-bottom: .6rem; }
.note-box p { color: var(--ink); font-size: var(--step--1); line-height: 1.6; }
.note-box p + p { margin-top: .7rem; }

/* Tarjeta de continuación al pie de un post o de una guía. */
.next { display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap;
  border: 1px solid var(--cline); border-radius: var(--r); background: var(--card);
  padding: 1.2rem 1.3rem; margin-block: 2rem; text-decoration: none; }
.next:hover { border-color: var(--block); }
.next .lbl { font-family: var(--mono); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); inline-size: 100%; }
.next .t { font-size: var(--step-1); font-weight: 650; letter-spacing: -.022em; }
.next .go { margin-inline-start: auto; font-family: var(--mono); font-size: .74rem; color: var(--link); }

/* Hilo entre conceptos hermanos: anterior / siguiente. */
.sibs { display: grid; gap: 1px; background: var(--cline); border: 1px solid var(--cline);
  border-radius: var(--r); overflow: hidden; margin-block: 2rem;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.sibs a { background: var(--card); padding: 1.1rem 1.2rem; text-decoration: none; display: block;
  transition: background var(--dur) var(--ease); }
.sibs a:hover { background: var(--block-soft); }
.sibs .lbl { font-family: var(--mono); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); display: block; margin-bottom: .45rem; }
.sibs .t { font-weight: 600; line-height: 1.35; }
.sibs .nx { text-align: right; }

/* ── 9 · Botones ─────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  font: inherit; font-weight: 600; font-size: var(--step--1);
  padding: .78rem 1.35rem; border-radius: var(--r); 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-1 { background: var(--block); color: var(--on-block); }
.btn-1:hover { background: color-mix(in srgb, var(--block) 86%, var(--ink)); }
.btn-2 { border-color: var(--block); color: var(--link); }
.btn-2:hover { background: var(--block-soft); }
/* Premium NO es oro: es el único relleno de tinta del sistema (16,00 sobre papel). */
.btn-pro { background: var(--premium); color: var(--on-premium); }
.btn-pro:hover { background: color-mix(in srgb, var(--premium) 88%, var(--block)); }
.block .btn-1 { background: var(--on-block); color: var(--block); }
.block .btn-2 { border-color: color-mix(in srgb, var(--on-block) 55%, transparent); color: var(--on-block); }
.block .btn-2:hover { background: color-mix(in srgb, var(--on-block) 12%, transparent); }

.badges { display: flex; gap: .75rem; flex-wrap: wrap; }
.badges img { block-size: 44px; inline-size: auto; }
/* Arte de Apple según la skin: sobre placa clara el negro, sobre placa oscura el
   blanco. Es la misma regla que ya tenía la landing. */
.badges .b-wht { display: none; }
:root[data-theme="dark"] .badges .b-blk { display: none; }
:root[data-theme="dark"] .badges .b-wht { display: block; }

/* ── 9b · CTA de cabecera ────────────────────────────────────────────────────
   Va justo debajo del bloque y ANTES del contenido gratis: quien llega a la
   ficha de una oposición desde una búsqueda tiene que poder descargarse la app
   sin haber hecho scroll. El `.appcta-top` de DS1 era una tarjeta con sombra y
   los badges apilados en columna; aquí es una placa de `blockSoft` sin sombra,
   con un titular corto y los badges en fila.

   `.cta-top--slim` es la variante para las 438 páginas de lectura larga: ahí el
   protagonista es el texto y basta con una línea entre filetes. La ficha de opo
   y los índices llevan la placa; la guía de estudio, la línea. */
.cta-top { display: flex; align-items: center; gap: 1rem 1.6rem; flex-wrap: wrap;
  background: var(--block-soft); border-radius: var(--r);
  border-inline-start: 4px solid var(--block);
  padding: 1.2rem 1.3rem; margin-block: 1.8rem 2.2rem; }
.cta-top .txt { flex: 1 1 320px; min-inline-size: 0; }
.cta-top .hd { font-weight: 700; font-size: var(--step-1); letter-spacing: -.022em; line-height: 1.2; }
.cta-top p { color: var(--muted); font-size: var(--step--1); margin-top: .35rem; max-width: 46ch; }
.cta-top .badges img { block-size: 40px; }

/* La compacta es la MISMA pieza con menos aire, no otra cosa. Antes era una
   línea de texto gris entre dos filetes: en las 438 páginas de lectura larga
   —guía, test, examen, post— nadie la leía como un CTA (Oriol 2026-08-13). */
.cta-top--slim { padding: .9rem 1.05rem; margin-block: 1.4rem 1.8rem; }
.cta-top--slim .hd { font-size: var(--step-0); }
.cta-top--slim p { margin-top: .25rem; }
.cta-top--slim .badges img { block-size: 34px; }

/* ── El rótulo que hace reconocible a TODO CTA ───────────────────────────────
   Mismo texto, misma caja, mismo sitio: encima del titular, en mono y en el
   color de bloque. Es lo único que comparten a simple vista la banda de cierre
   (a sangre, sobre color) y las placas de cabecera (sobre papel), y es lo que
   permite reconocer los dos como la misma pieza al hojear la web. El gancho va
   AQUÍ y en ningún otro sitio de la página: repetir «7 días» en el párrafo lo
   convertía en relleno. */
.cta-kicker { font-family: var(--mono); font-size: .68rem; letter-spacing: .15em;
  text-transform: uppercase; line-height: 1; color: var(--link);
  display: inline-flex; align-items: center; gap: .5rem; margin-bottom: .5rem; }
.cta-kicker::before { content: ""; inline-size: 7px; block-size: 7px; border-radius: 50%;
  background: currentColor; flex: none; }
/* La letra pequeña del CTA: lo que NO es el gancho (sin registro, sin rastreo).
   Va debajo de los badges y en el mismo mono, para que el bloque entero se lea
   como una sola pieza en vez de tres cosas apiladas. */
.cta-fine { font-family: var(--mono); font-size: .74rem; color: var(--muted); margin-top: 1rem; }
/* El CTA del hero es EL CTA de la web, y era el que menos se veía: dos badges
   sueltos sobre el papel, al lado de un panel con marco (Oriol 2026-08-13).
   Ahora es una placa con el mismo lenguaje que el resto —filete de bloque,
   rótulo, letra pequeña— y con los badges a mayor tamaño: es la acción, no una
   nota al pie del titular. `inline-block` para que la placa mida lo que mide su
   contenido y no una franja vacía a lo ancho de media pantalla. */
.hero-cta { max-inline-size: 100%; margin-top: 2rem;
  display: flex; flex-direction: column; justify-content: center;
  padding: 1.05rem 1.25rem 1.15rem;
  background: var(--block-soft); border: 1px solid var(--cline);
  border-inline-start: 4px solid var(--block); border-radius: var(--r); }
.hero-cta .badges { margin-top: .1rem; }
.hero-cta .badges img { block-size: 48px; }
.hero-cta .cta-fine { margin-top: .75rem; }

/* Sobre la banda pintada el color de enlace no contrasta: ahí manda la tinta
   del bloque, que es la que DS2 garantiza legible sobre él. */
/* Sobre la banda, la tinta del bloque SIN rebajar. Ni `opacity` —que el medidor
   de contraste no ve, así que un texto flojo aprobaría la auditoría sin que
   nadie lo mire— ni una mezcla al 84 %, que medida daba 3,82 en Grafito contra
   los 4,5 que pide AA. La jerarquía ya la marcan el tamaño y las versalitas. */
.cta .cta-kicker { color: var(--on-block); justify-content: center; }

/* ── 9c · El CTA de cierre: banda de color ───────────────────────────────────
   Antes era papel + texto centrado + badges, y se desvanecía — en Grafito los
   badges negros sobre casi-negro directamente desaparecían. Ahora el cierre es
   una BANDA DE BLOQUE a sangre: el color no decora, le da el peso que necesita
   la única acción de la página.

   Los badges no van sobre el bloque, sino sobre una PLACA de tarjeta: el arte de
   Apple y Google es intocable y sobre índigo perdía separación. La placa se
   solapa al bloque, que es el gesto que DS2 §5.2 pide para la primera tarjeta. */
.cta { background: var(--block); color: var(--on-block); padding-block: clamp(2.6rem, 6vw, 4rem) 0;
  margin-top: clamp(2.6rem, 6vw, 4.4rem); text-align: center; }
.cta h2 { color: var(--on-block); font-size: var(--step-2); }
.cta p { color: var(--on-block); max-width: 52ch; margin: .9rem auto 0; font-size: var(--step-0); }
.cta .mk { --mark: var(--mark-on-block); }
.cta-plate {
  background: var(--card); border-radius: var(--r);
  padding: 1.5rem 1.4rem 1.4rem; margin: clamp(1.6rem, 4vw, 2.4rem) auto 0;
  max-inline-size: 520px; translate: 0 clamp(1.2rem, 3vw, 2rem);
}
.cta-plate .badges { justify-content: center; }
.cta-plate .note { font-family: var(--mono); font-size: .72rem; color: var(--muted); margin-top: 1rem; }
/* La banda termina donde termina la placa: el hueco que deja el `translate` se
   compensa abajo para que no se coma el pie. */
.cta-tail { block-size: clamp(1.2rem, 3vw, 2rem); }

/* Callout del método — va en ~460 páginas, así que es lo más repetido del sitio.
   Filete y mono, nada de tarjeta con sombra. */
.metodo { border-inline-start: 2px solid var(--block); padding: .2rem 0 .2rem 1.4rem;
  margin-block: 2.2rem; }
.metodo .lbl { font-family: var(--mono); font-size: .68rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted); margin-bottom: .5rem; }
.metodo .hd { font-weight: 650; font-size: var(--step-1); letter-spacing: -.02em; }
.metodo p { color: var(--muted); font-size: var(--step--1); margin-top: .6rem; max-width: 62ch; }

/* ── 10 · La cita: el mecanismo, no el eslogan ───────────────────────────── */
/* Una pregunta real al lado del artículo real, con la frase de la que sale
   marcada. Enseña cómo se hace en vez de prometerlo. */
.anchor { display: grid; gap: 1px; background: var(--cline); border: 1px solid var(--cline);
  border-radius: var(--r); overflow: hidden;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); }
.anchor > div { background: var(--card); padding: 1.4rem 1.35rem; }
.anchor .lbl { font-family: var(--mono); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); margin-bottom: .9rem; }
.anchor .q { font-weight: 600; line-height: 1.42; }
.anchor ol { margin: 1rem 0 0; padding: 0; list-style: none; display: grid; gap: .4rem; font-size: var(--step--1); }
.anchor li { display: flex; gap: .65rem; padding: .5rem .6rem; border: 1px solid var(--cline);
  border-radius: var(--r); color: var(--muted); }
.anchor li i { font-style: normal; font-family: var(--mono); font-size: .78rem; }
.anchor li.ok { border-color: var(--success); color: var(--ink);
  background: color-mix(in srgb, var(--success) 10%, var(--card)); }
.anchor li.ok i { color: var(--success); font-weight: 600; }
.anchor .src { font-size: var(--step--1); line-height: 1.6; color: var(--muted); }
.anchor .src strong { color: var(--ink); font-weight: 550; }
.anchor .cite { font-family: var(--mono); font-size: .72rem; color: var(--link); margin-top: 1rem; }

/* ── 11 · Prosa larga (438 páginas de lectura) ───────────────────────────── */
.body-copy { max-width: var(--measure); }
.body-copy > * + * { margin-top: 1.15em; }
.body-copy h2 { font-size: var(--step-2); margin-top: 2.4em; }
.body-copy h3 { margin-top: 1.9em; }
.body-copy a { color: var(--link); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
/* Un `.btn` dentro de la prosa: `.body-copy a` (0,1,1) le gana en especificidad a
   `.btn-1` (0,1,0), así que el botón salía índigo y subrayado sobre relleno
   índigo. Se devuelve al botón lo que es suyo. */
.body-copy a.btn { color: inherit; text-decoration: none; }
.body-copy a.btn-1 { color: var(--on-block); }
.body-copy a.btn-pro { color: var(--on-premium); }

.body-copy ul, .body-copy ol { padding-left: 1.4em; }
.body-copy li + li { margin-top: .4em; }
.body-copy blockquote { border-left: 1px solid var(--cline-strong); padding-left: 1.2em; color: var(--muted); }
.body-copy code { font-family: var(--mono); font-size: .88em; background: var(--block-soft);
  border-radius: 2px; padding: .08em .35em; }
.body-copy hr { border: 0; border-top: 1px solid var(--cline); margin-block: 2.2em; }
.body-copy table { inline-size: 100%; border-collapse: collapse; font-size: var(--step--1); }
.body-copy 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; }
.body-copy td { padding: .75rem .8rem; border-block-end: 1px solid var(--cline); vertical-align: top; }
/* La cita del artículo va pegada al punto de la frase que ancla, pero necesita
   aire propio: sin esto sale «…y las leyes.Art. 56.1 CE». */
.body-copy .art { font-family: var(--mono); font-size: .74rem; color: var(--link); white-space: nowrap;
  margin-left: .45em; }
.body-copy .art::before { content: " "; }

/* ── 12 · Reproductor de audio de la guía ────────────────────────────────────
   Fuera el gradiente dorado de la barra (el oro es de premium) y fuera la sombra.
   La barra es bloque plano sobre filete; el botón, `block`/`onBlock`. */
/* Sin JS la barra no responde: se retira y la guía se queda con el `<audio
   controls>` del `<noscript>`, que suena igual. Enseñar un control muerto es
   peor que no enseñar ninguno. */
html.nojs .ap { display: none; }
.ap { display: flex; align-items: center; gap: 1rem;
  border: 1px solid var(--cline); border-radius: var(--r); background: var(--card);
  padding: .85rem 1rem; margin-block: 1.2rem 2rem; }
.ap-play { flex: none; inline-size: 44px; block-size: 44px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--block); color: var(--on-block); display: grid; place-items: center;
  transition: background var(--dur) var(--ease); }
.ap-play:hover { background: color-mix(in srgb, var(--block) 86%, var(--ink)); }
.ap-play svg { inline-size: 20px; block-size: 20px; }
.ap-play .ap-i-play { margin-left: 2px; }
.ap-play .ap-i-pause { display: none; }
.ap.is-playing .ap-i-play { display: none; }
.ap.is-playing .ap-i-pause { display: block; margin-left: 0; }
.ap-main { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; gap: .55rem; }
.ap-top { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem; }
.ap-label { font-family: var(--mono); font-size: .7rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ap-time { font-family: var(--mono); font-size: .74rem; color: var(--muted); white-space: nowrap; flex: none; }
.ap-cur { color: var(--ink); }
.ap-bar { position: relative; block-size: 6px; background: var(--cline); cursor: pointer; touch-action: none;
  border-radius: 999px; }
.ap-fill { position: absolute; inset-block: 0; inset-inline-start: 0; inline-size: 0;
  background: var(--block); border-radius: 999px; }
.ap-fill::after { content: ""; position: absolute; inset-inline-end: 0; inset-block-start: 50%;
  inline-size: 12px; block-size: 12px; border-radius: 50%; background: var(--block);
  border: 2px solid var(--card); transform: translate(50%, -50%); }
.ap-speed { flex: none; font-family: var(--mono); font-size: .74rem; font-weight: 500; color: var(--ink);
  background: none; border: 1px solid var(--cline-strong); border-radius: 999px;
  padding: .55rem .7rem; min-inline-size: 52px; text-align: center; cursor: pointer;
  transition: border-color var(--dur) var(--ease); }
.ap-speed:hover { border-color: var(--block); color: var(--link); }
@media (max-width: 480px) { .ap-label { display: none; } .ap { gap: .75rem; padding: .75rem .8rem; } }

/* ── 13 · Quiz (test gratis) ─────────────────────────────────────────────── */
.quiz { border: 1px solid var(--cline); border-radius: var(--r); background: var(--card);
  padding: 1.6rem 1.5rem; margin-block: 1.6rem; }
.q-progress { display: flex; justify-content: space-between; font-family: var(--mono);
  font-size: .74rem; color: var(--muted); margin-bottom: 1rem; }
.q-text { font-size: var(--step-1); font-weight: 600; line-height: 1.36; letter-spacing: -.015em; }
.q-opts { display: grid; gap: .55rem; margin-top: 1.4rem; }
/* ⚠️ EL TINTE SEMÁNTICO VA SOBRE `card`, NUNCA SOBRE EL PAPEL, y está medido:
   el glifo ✓/✗ se pinta con el mismo color del que sale el tinte, así que el
   fondo se le acerca. Sobre papel, incluso al 6 % el glifo cae a 4,34 — falla.
   Sobre tarjeta, al 10 % da 4,71 (✓) y 4,69 (✗), y en Grafito 5,78 / 4,76. */
.q-opt { display: flex; gap: .85rem; align-items: flex-start; inline-size: 100%; text-align: left;
  font: inherit; font-size: var(--step--1); color: var(--ink);
  padding: .8rem .9rem; border: 1px solid var(--cline); border-radius: var(--r);
  background: var(--card); cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease); }
.q-opt:hover:not(:disabled) { border-color: var(--block); background: color-mix(in srgb, var(--block) 5%, var(--card)); }
.q-opt:disabled { cursor: default; }
/* Acierto y error: color fijo FUERA de gama + glifo, siempre. El glifo no es
   decoración — contra algunos bloques el tono solo no es inequívoco. */
.q-opt::before { font-family: var(--mono); font-size: .78rem; color: var(--muted); content: attr(data-k); flex: none; }
.q-opt.ok { border-color: var(--success); border-width: 2px; padding: calc(.8rem - 1px) calc(.9rem - 1px);
  background: color-mix(in srgb, var(--success) 10%, var(--card)); }
.q-opt.ok::before { content: "✓"; color: var(--success); font-weight: 700; }
.q-opt.ko { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, var(--card)); }
.q-opt.ko::before { content: "✗"; color: var(--danger); font-weight: 700; }
.q-expl { display: none; margin-top: 1.2rem; padding-top: 1.1rem; border-top: 1px solid var(--cline);
  font-size: var(--step--1); color: var(--muted); line-height: 1.6; }
.q-expl .cite { display: block; margin-top: .7rem; font-family: var(--mono); font-size: .74rem; color: var(--link); }
.q-next { margin-top: 1.3rem; }
.q-result { text-align: center; padding-block: 1rem .3rem; }
.q-result p { color: var(--muted); font-size: var(--step--1); max-width: 46ch; margin-inline: auto; }
.q-score { font-size: var(--step-4); font-weight: 700; font-stretch: 108%; letter-spacing: -.04em;
  line-height: 1; margin-block: .6rem 1rem; color: var(--ink); }
.q-result .btn { margin-top: 1.4rem; }
.q-result .btn + .btn { margin-inline-start: .6rem; }

/* ── El muro del examen: una parada, no un peaje ─────────────────────────────
   Cada 10, 25 y 50 preguntas el examen se detiene y ofrece seguir en la app
   (Oriol 2026-08-13). Es la misma pieza que los CTA de la web —mismo rótulo,
   mismo gancho— pero dentro de la tarjeta del quiz, porque interrumpe algo que
   se estaba haciendo.

   `Continuar` va a la DERECHA y con estilo secundario: la salida existe y se ve,
   pero la acción que se propone son las tiendas. Un muro que esconde la salida
   se lee como un engaño, y esta página es la prueba de que no engañamos. */
.q-wall { text-align: center; padding-block: .4rem; }
.q-wall .cta-kicker { justify-content: center; }
.q-wall h3 { font-size: var(--step-2); letter-spacing: -.03em; text-wrap: balance; }
.q-wall-txt { color: var(--muted); font-size: var(--step--1); line-height: 1.6;
  max-inline-size: 50ch; margin: .8rem auto 0; }
.q-wall-txt b { color: var(--ink); font-weight: 560; }
.q-wall-row { display: flex; align-items: center; justify-content: center;
  gap: 1rem 1.4rem; flex-wrap: wrap; margin-top: 1.5rem; }
.q-wall-row .badges { margin: 0; }
.q-wall-row .badges img { block-size: 42px; }
/* En un móvil los badges se apilan y el botón tiene que quedar DEBAJO, no en
   medio de las dos tiendas. */
@media (max-width: 559.98px) { .q-wall-go { order: 9; } }

/* Las 100 preguntas resueltas, plegadas. Es contenido de la página —el título
   promete «N preguntas con soluciones»— así que no se esconde por JS: va en un
   `<details>` nativo, que se abre sin JavaScript y deja el texto en el HTML.
   Plegada por defecto para no destripar el examen de arriba. */
.exam-todo > summary { cursor: pointer; list-style: none; font-family: var(--mono);
  font-size: .78rem; letter-spacing: .1em; text-transform: uppercase; color: var(--link);
  padding: .95rem 1.1rem; border: 1px solid var(--cline-strong); border-radius: var(--r);
  transition: background var(--dur) var(--ease); }
.exam-todo > summary::-webkit-details-marker { display: none; }
.exam-todo > summary::after { content: " ▾"; }
.exam-todo[open] > summary::after { content: " ▴"; }
.exam-todo > summary:hover { background: var(--block-soft); }
.exam-todo > .sec-p { margin-top: 1.2rem; }
.exam-nota { font-family: var(--mono); font-size: .74rem; color: var(--muted); margin-top: 1.6rem; }

/* ── 14 · Examen oficial resuelto (100 preguntas) ────────────────────────────
   El peor caso de contraste del sitio viejo: 443 de 1.000 nodos fallaban. La
   numeración y el `<summary>` se van a mono, y la solución deja de ser un panel
   verde para ser una opción marcada + explicación en prosa.

   `content-visibility` deja fuera del layout las preguntas que aún no se ven:
   son 100 bloques con `<details>` en una sola página. */
.qa { border-block-end: 1px solid var(--cline); padding-block: 1.3rem;
  content-visibility: auto; contain-intrinsic-size: auto 320px; }
.qa .qn { font-family: var(--mono); font-size: .74rem; color: var(--muted); }
.qa .qt { font-weight: 600; margin-top: .35rem; line-height: 1.45; }
.qa ol { list-style: none; padding: 0; margin: .9rem 0 0; display: grid; gap: .4rem; font-size: var(--step--1); }
.qa li { display: flex; gap: .7rem; padding: .5rem .65rem; border: 1px solid var(--cline);
  border-radius: var(--r); color: var(--muted); background: var(--card); }
.qa li i { font-style: normal; font-family: var(--mono); font-size: .76rem; flex: none; }
.qa li.ok { border-color: var(--success); border-width: 2px; padding: calc(.5rem - 1px) calc(.65rem - 1px);
  background: color-mix(in srgb, var(--success) 10%, var(--card)); color: var(--ink); }
.qa li.ok i { color: var(--success); font-weight: 700; }
.qa summary { cursor: pointer; font-family: var(--mono); font-size: .74rem; color: var(--link);
  margin-top: .9rem; list-style: none; padding-block: .5rem; }
.qa summary::-webkit-details-marker { display: none; }
.qa .sol { font-size: var(--step--1); color: var(--muted); line-height: 1.6; }
.qa .art { font-family: var(--mono); font-size: .74rem; color: var(--link); margin-top: .6rem; }
.qa .anulada { font-family: var(--mono); font-size: .72rem; color: var(--danger); }

/* ── 15 · Calendario de convocatorias ────────────────────────────────────────
   Los cuatro estados se distinguen por FORMA además de por color: relleno,
   contorno, tinta y hueco. Cuatro tonos libres no caben contra un bloque de
   marca fijo — y los cuatro hex de DS1 (`#A65B2A`, `#3E7C4F`…) no obedecían a
   nadie: se veían igual en claro que en oscuro. */
.cal-head { display: flex; align-items: center; justify-content: space-between; gap: .8rem; margin-bottom: 1rem; }
.cal-title { font-size: var(--step-1); font-stretch: 106%; }
.cal-nav { inline-size: 40px; block-size: 40px; border: 1px solid var(--cline-strong); border-radius: var(--r);
  background: none; color: var(--muted); cursor: pointer; display: grid; place-items: center;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.cal-nav svg { inline-size: 17px; block-size: 17px; }
.cal-nav:hover:not(:disabled) { color: var(--ink); border-color: var(--ink); }
.cal-nav:disabled { opacity: .35; cursor: default; }
.cal-filter { display: flex; align-items: center; gap: .7rem; margin-bottom: 1rem;
  font-family: var(--mono); font-size: .74rem; color: var(--muted); flex-wrap: wrap; }
.cal-filter select { font: inherit; font-family: var(--sans); font-size: var(--step--1); color: var(--ink);
  background: var(--card); border: 1px solid var(--cline-strong); border-radius: var(--r);
  padding: .5rem .7rem; max-inline-size: 100%; }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; background: var(--cline);
  border: 1px solid var(--cline); }
.cal-wd { background: var(--block-soft); font-family: var(--mono); font-size: .68rem; color: var(--muted);
  text-align: center; padding-block: .4rem; }
.cal-cell { background: var(--card); min-block-size: 64px; padding: .35rem .45rem; font: inherit;
  text-align: left; color: var(--ink); border: 0; display: flex; flex-direction: column;
  justify-content: space-between; }
.cal-empty { background: var(--paper); }
.cal-day { font-family: var(--mono); font-size: .76rem; color: var(--muted); }
.cal-cell.is-today .cal-day { color: var(--on-block); background: var(--block); border-radius: 2px; padding-inline: .3rem; }
.cal-cell.has-ev { cursor: pointer; }
.cal-cell.has-ev:hover { background: var(--block-soft); }
.cal-cell.has-ev .cal-day { color: var(--ink); }
.cal-cell.is-open { box-shadow: inset 0 0 0 2px var(--block); }
.cal-dots { display: flex; gap: 3px; align-items: center; flex-wrap: wrap; }
.cal-dot { inline-size: 7px; block-size: 7px; border-radius: 50%; display: inline-block; flex: none; }
.dot-publicacion { background: var(--block); }
.dot-fin_instancias { background: none; border: 1.5px solid var(--block); }
.dot-examen { background: var(--ink); }
.dot-otro { background: none; border: 1.5px solid var(--muted); }
.cal-more { font-family: var(--mono); font-size: .64rem; color: var(--muted); }
.cal-legend { display: flex; flex-wrap: wrap; gap: .6rem 1.4rem; margin-top: 1rem;
  font-family: var(--mono); font-size: .72rem; color: var(--muted); }
.cal-legend span { display: inline-flex; align-items: center; gap: .45rem; }
.cal-detail:not(:empty) { margin-top: 1.2rem; padding-top: 1rem; border-top: 1px solid var(--cline);
  display: flex; flex-direction: column; gap: .9rem; }
.cal-ev { display: flex; gap: .7rem; align-items: baseline; font-size: var(--step--1); }
.cal-ev .cal-dot { transform: translateY(-1px); }
.cal-meta-row { display: flex; flex-wrap: wrap; gap: .2rem 1rem; margin-top: .25rem; }
.cal-meta { font-family: var(--mono); font-size: .72rem; color: var(--muted); }
.cal-link { font-family: var(--mono); font-size: .72rem; color: var(--link); text-decoration: none; }
.cal-link:hover { text-decoration: underline; }
.cal-note { font-family: var(--mono); font-size: .72rem; color: var(--muted); margin-top: 1.6rem; }
@media (max-width: 560px) {
  .cal-cell { min-block-size: 48px; padding: .25rem .3rem; }
  .cal-dot { inline-size: 6px; block-size: 6px; }
}

/* ── 16 · FAQ ────────────────────────────────────────────────────────────── */
.faq { border-block-start: 1px solid var(--cline); margin-top: 1.4rem; }
.faq details { border-block-end: 1px solid var(--cline); }
/* El color entra de lado a lado al pasar por encima —y se queda mientras la
   respuesta está abierta— (Oriol 2026-08-12). Es un `scaleX` desde la
   izquierda: se compone en GPU, así que la fila entera no repinta. */
.faq summary { cursor: pointer; font-weight: 600; padding-block: 1.05rem; list-style: none;
  display: flex; align-items: center; gap: 1rem; position: relative; isolation: isolate;
  padding-inline: clamp(.4rem, 1.6vw, 1rem);
  transition: color 260ms var(--ease); }
.faq summary::before { content: ""; position: absolute; z-index: -1; inset: 0;
  background: var(--block-soft); transform: scaleX(0); transform-origin: left center;
  transition: transform 520ms var(--ease); }
.faq summary:hover::before,
.faq summary:focus-visible::before,
.faq details[open] summary::before { transform: scaleX(1); }
.faq summary:hover, .faq details[open] summary { color: var(--ink); }
@media (prefers-reduced-motion: reduce) { .faq summary::before { transition: none; } }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; margin-inline-start: auto; font-family: var(--mono);
  color: var(--link); font-size: 1.1rem; line-height: 1; flex: none; }
.faq details[open] summary::after { content: "–"; }
.faq p { color: var(--muted); font-size: var(--step--1); padding-block: .2rem 1.3rem; max-width: 66ch;
  padding-inline: clamp(.4rem, 1.6vw, 1rem); }

/* ── 17 · Pie ────────────────────────────────────────────────────────────── */
.ftr { border-top: 1px solid var(--cline); padding-block: 2.8rem 2rem; margin-top: 3rem; }
.ftr .cols { display: grid; gap: 2rem 1.5rem; grid-template-columns: 1fr 1fr; }
@media (min-width: 860px) { .ftr .cols { grid-template-columns: 1.8fr 1fr 1fr 1fr; } }
.ftr h4 { font-family: var(--mono); font-size: .69rem; font-weight: 500; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted); margin-bottom: .9rem; }
.ftr a { display: block; color: var(--muted); text-decoration: none; font-size: var(--step--1); padding-block: .28rem; }
@media (max-width: 860px) { .ftr a { padding-block: .7rem; } }
.ftr a:hover { color: var(--ink); }
.ftr .wm { display: inline-block; margin-right: 0; }
.ftr .say { color: var(--muted); font-size: var(--step--1); max-width: 34ch; margin-top: .8rem; }
.ftr .fine { margin-top: 2.4rem; padding-top: 1.2rem; border-top: 1px solid var(--cline);
  color: var(--muted); font-size: .78rem; max-width: 70ch; }
.ftr .fine + .fine { margin-top: .6rem; padding-top: 0; border: 0; }


/* ═══════════════════════════════════════════════════════════════════════════
   LANDING — lo que solo usa la portada

   Portado del prototipo `design/mockups/web_ds2/ds2.css` tal cual (los
   comentarios de medida que trae son mediciones, no opiniones). Vive en la
   MISMA hoja que las 627 páginas SEO a propósito: el fallo que arregla toda
   esta migración es que las dos superficies tuvieran cada una su CSS y su
   arranque de tema, y acabaran pareciéndose solo de lejos.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 6b · Portada ────────────────────────────────────────────────────────── */
/* La promesa y el mecanismo, uno al lado del otro. La columna de texto se topa
   a 15ch para que el titular caiga en tres líneas: con `max-width` en el
   contenedor y no en el h1, un clamp de 4,4 rem parte una palabra por línea. */
.hero {
  display: grid; align-items: start;
  gap: clamp(2rem, 5vw, 4rem);
  padding-block: clamp(2.6rem, 6vw, 4.4rem) clamp(2.4rem, 5vw, 3.4rem);
}
.hero h1 { max-width: 15ch; }
.hero .lead { max-width: 46ch; }
@media (min-width: 940px) {
  .hero { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
  .hero-show { align-self: center; }
  .hero-show .anchor, .hero-show > div { grid-template-columns: 1fr; }
}
/* En la portada la cita va apilada: dos columnas dentro de media pantalla
   dejarían la pregunta en cuatro palabras por línea. */
@media (min-width: 940px) { .hero-show { grid-template-columns: 1fr; } }

/* ── 6c · Actos: el panel que se ancla mientras el texto sigue ───────────── */
/* Es `position: sticky` y nada más — soporte universal desde 2017, cero JS para
   el anclaje. El JS solo decide QUÉ se ve dentro del panel; sin él se ve el
   primer plano y el texto se lee entero igual. La sección nunca depende de que
   una animación se dispare para tener contenido. */
.act { padding-block: clamp(2.4rem, 6vw, 4rem) 0; }
.act-grid { display: grid; gap: clamp(1.6rem, 4vw, 3.4rem); }
/* El tamaño mínimo automático de un item de grid es su `min-content`, y el de una
   fila flex de seis tarjetas de 280 px son 1.680 px: sin esto, el carrusel de
   móvil estiraba la página a 932 px de ancho. */
.act-pin, .act-flow { min-inline-size: 0; }
/* El titular de la sección vive DENTRO de la columna anclada. Puesto arriba a
   todo el ancho se iba con el scroll y dejaba el panel solo, rodeado de blanco:
   a mitad del acto ya no sabías qué estabas leyendo. Aquí acompaña siempre. */
.act-h { font-size: var(--step-2); letter-spacing: -.03em; }
.act-sub { color: var(--muted); margin-top: .7rem; max-width: 40ch; font-size: var(--step--1); }
.act-pin > .dev, .act-pin > .law { margin-top: 1.6rem; }
@media (min-width: 900px) {
  .act-grid { grid-template-columns: minmax(0, .78fr) minmax(0, 1fr); align-items: start; }
  .act-pin { position: sticky; top: 88px; }

  /* El segundo acto se refleja: panel a la derecha, texto a la izquierda. No es
     variedad por variedad — es lo que hace LEGIBLE el punto de ruptura. Cuando
     el panel salta de lado, el cuerpo entiende que ha empezado otro capítulo
     antes de leer el titular; con las dos mitades siempre iguales, los doce
     compases se leían como una sola lista muy larga.
     Se invierte con `grid-column`, no con `order` ni cambiando el HTML: el
     titular de la sección vive dentro del panel, así que en móvil —donde la
     rejilla es de una columna— tiene que seguir yendo el primero. */
  .act--flip .act-grid { grid-template-columns: minmax(0, 1fr) minmax(0, .78fr); }
  .act--flip .act-pin { grid-column: 2; grid-row: 1; }
  .act--flip .act-flow { grid-column: 1; grid-row: 1; }
  /* En el acto reflejado el bloque del panel se alinea con el borde DERECHO,
     igual que el del acto 1 lo hace con el izquierdo. El subtítulo se queda
     pegado a su titular: empujarlo con `margin-left:auto` lo desalineaba. */
  .act--flip .act-pin > .dev { margin-inline: auto 0; }
  /* Cada compás ocupa una pantalla escasa: con menos, el panel se despega antes
     de que dé tiempo a leer; con más, el scroll se hace largo y cansa. */
  .beat { min-block-size: 58vh; display: flex; flex-direction: column; justify-content: center; }
  /* Cola al pie de la columna que scrollea: sin ella, en el último compás el
     panel llega al fondo de su contenedor y empieza a irse hacia arriba
     (medido: -21 px). El panel debe seguir quieto hasta que el compás termine. */
  .act-flow { padding-block-end: 24vh; }
}
.beat { padding-block: 1.6rem; }
.beat h3 { font-size: clamp(1.5rem, 1.2rem + 1.35vw, 2.3rem); font-stretch: 106%; letter-spacing: -.032em; }
.beat p { color: var(--muted); margin-top: .9rem; max-width: 44ch; font-size: var(--step-0); }
.beat .n {
  font-family: var(--mono); font-size: .72rem; letter-spacing: .16em; color: var(--muted);
  display: block; margin-bottom: .8rem;
}
/* El compás activo se marca con la misma gramática del resto: la marca. */
@media (min-width: 900px) {
  .beat { opacity: .42; transition: opacity var(--dur-slow) var(--ease); }
  .beat.on { opacity: 1; }
  .beat.on .n { color: var(--link); }
  html.nojs .beat { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .beat { opacity: 1 !important; transition: none; } }

/* El teléfono: dibujado, no una captura. Una captura de la app envejece con la
   app; esto usa los mismos tokens, así que se actualiza solo. */
.dev {
  position: relative; inline-size: min(336px, 100%); margin-inline: auto;
  aspect-ratio: 300 / 478; border: 1px solid var(--cline-strong); border-radius: 20px;
  background: var(--card); overflow: hidden;
}
.scr { position: absolute; inset: 0; display: flex; flex-direction: column;
  opacity: 0; transition: opacity 420ms var(--ease); }
.scr.on { opacity: 1; }
html.nojs .scr:first-of-type, .dev .scr:only-of-type { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .scr { transition: none; } }
.scr-top { background: var(--block); color: var(--on-block); padding: 1.1rem 1rem .95rem; }
.scr-top .e { font-family: var(--mono); font-size: .6rem; letter-spacing: .16em; text-transform: uppercase; }
.scr-top .t { font-size: 1.05rem; font-weight: 700; letter-spacing: -.025em; line-height: 1.15; margin-top: .45rem; }
.scr-body { flex: 1; padding: .95rem 1rem; display: flex; flex-direction: column; gap: .55rem; font-size: .72rem; line-height: 1.5; }
.scr-body p { color: var(--muted); }
.scr-body .ink { color: var(--ink); }
.scr-line { block-size: 6px; border-radius: 2px; background: var(--cline); }
.scr-line.s { inline-size: 62%; }
.scr-opt { border: 1px solid var(--cline); border-radius: var(--r); padding: .42rem .55rem; display: flex; gap: .5rem; align-items: center; }
.scr-opt i { font-style: normal; font-family: var(--mono); font-size: .64rem; color: var(--muted); }
.scr-opt.ok { border-color: var(--success); border-width: 2px; background: color-mix(in srgb, var(--success) 10%, transparent); }
.scr-opt.ok i { color: var(--success); font-weight: 700; }
.scr-chip { align-self: flex-start; font-family: var(--mono); font-size: .62rem; color: var(--link);
  border: 1px solid var(--cline-strong); border-radius: 999px; padding: .2rem .55rem; }
.scr-bubble { border: 1px solid var(--cline); border-radius: var(--r); padding: .55rem .6rem; }
.scr-bubble.me { background: var(--block); color: var(--on-block); border-color: transparent; margin-left: 2.2rem; }
.scr-bar { block-size: 3px; background: var(--cline); }
.scr-bar i { display: block; block-size: 100%; inline-size: 46%; background: var(--block); }
.scr-foot { margin-top: auto; border-top: 1px solid var(--cline); padding: .6rem 1rem;
  font-family: var(--mono); font-size: .6rem; color: var(--muted); display: flex; justify-content: space-between; }

/* El acto del anclaje: el artículo se queda quieto y la marca se mueve por él.
   El rotulador viaja de una frase a otra mientras el texto de al lado explica
   qué se está mirando — el color hace de puntero, que es su trabajo. */
.law { border: 1px solid var(--cline); border-radius: var(--r); background: var(--card); padding: 1.5rem 1.4rem; }
.law .lbl { font-family: var(--mono); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); margin-bottom: 1rem; }
.law .txt { font-size: var(--step--1); line-height: 1.75; color: var(--muted); }
.law .txt em { font-style: normal; color: var(--ink); }
/* Apagada por defecto, encendida al llegar su compás. Sin JS todas quedan
   encendidas y el párrafo sigue leyéndose igual de bien. */
.law .txt .hl {
  background-image: linear-gradient(to top, var(--mark) 0 76%, transparent 76%);
  background-repeat: no-repeat; background-position: 0 0;
  background-size: 0 100%;
  transition: background-size 520ms var(--ease);
  padding-inline: .05em; box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
.law .txt .hl.on { background-size: 100% 100%; color: var(--ink); }
html.nojs .law .txt .hl { background-size: 100% 100%; color: var(--ink); }
@media (prefers-reduced-motion: reduce) { .law .txt .hl { transition: none; } }
.law .cite { font-family: var(--mono); font-size: .72rem; color: var(--link); margin-top: 1.2rem; }

/* ── En móvil el panel no se ancla: el teléfono se convierte en un carrusel ──
   Sin esto, el panel se quedaba enseñando la PRIMERA pantalla mientras el texto
   de abajo hablaba de seis features distintas: la imagen dejaba de significar
   nada. Como fila deslizable, las seis pantallas siguen viéndose y cada una es
   una tarjeta de pleno derecho. */
@media (max-width: 899.98px) {
  .dev {
    inline-size: auto; aspect-ratio: auto; border: 0; border-radius: 0; overflow: visible;
    background: none;
    display: flex; gap: .75rem;
    overflow-x: auto; scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    /* se sale de la columna para poder deslizar de borde a borde */
    margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter);
    padding-block-end: .5rem;
  }
  .dev::-webkit-scrollbar { display: none; }
  .scr {
    position: static; opacity: 1; flex: 0 0 min(280px, 78%);
    scroll-snap-align: center; block-size: 420px;
    border: 1px solid var(--cline-strong); border-radius: 14px; overflow: hidden;
    background: var(--card);
  }
  .scr-body { overflow: hidden; }
}

/* Concreciones del compás: datos reales en mono, no relleno. Dan cuerpo a la
   columna que scrollea —que se quedaba en un titular y tres líneas— sin inventar
   nada: cada línea es un número o un hecho que la app cumple. */
.beat-meta { list-style: none; padding: 0; margin: 1.3rem 0 0; display: flex; flex-wrap: wrap; gap: .45rem; }
.beat-meta li {
  font-family: var(--mono); font-size: .72rem; color: var(--muted);
  border: 1px solid var(--cline); border-radius: 999px; padding: .35rem .75rem;
}
.beat.on .beat-meta li { border-color: var(--cline-strong); color: var(--ink); }

/* ── 11 · Foto a sangre ──────────────────────────────────────────────────── */
/* El fondo de respaldo es fijo y oscuro A PROPÓSITO, no `var(--ink)`: en Grafito
   la tinta es casi blanca, así que si la foto no carga la sección quedaba crema
   sobre crema (1,03 medido). Estas dos secciones son foto con velo: su mundo es
   oscuro en las dos skins, igual que el texto crema que llevan encima. Es el
   índigo llevado a su extremo oscuro, no un gris cualquiera. */
.shot { position: relative; isolation: isolate; overflow: clip; background: #0C1230; }
.shot img, .shot video { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; object-fit: cover; z-index: -2; }
.shot::after { content: ""; position: absolute; inset: 0; z-index: -1; background: var(--veil); }
.shot .wrap { padding-block: clamp(4rem, 11vw, 7.5rem); }
.shot h2, .shot p { color: #F6F4F0; }
.shot p { color: #D9D5CE; max-width: 46ch; margin-top: 1rem; }
.shot .mk { --mark: rgba(124, 137, 232, .46); }

/* ── 12 · Precios ────────────────────────────────────────────────────────── */
.plans { display: grid; gap: 1.1rem; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); }
.plan { position: relative; border: 1px solid var(--cline); border-radius: var(--r); padding: 1.5rem 1.4rem; background: var(--card); display: flex; flex-direction: column; }
/* El plan recomendado se marcaba con un borde NEGRO más gordo, que es lo mismo
   que decir «este cuadro tiene más borde»: en una página con el color de marca
   por todas partes, el bloque de precios era el único sitio sin él (Oriol
   2026-08-13). Ahora refuerza con COLOR — filete superior, borde teñido y su
   etiqueta en la tinta de marca —, que es lo que el ojo busca cuando compara. */
.plan.on { border-color: var(--block); border-width: 2px; padding: calc(1.5rem - 1px) calc(1.4rem - 1px);
  box-shadow: 0 14px 34px color-mix(in srgb, var(--block) 16%, transparent); }
.plan.on::before { content: ""; position: absolute; inset: -2px -2px auto; block-size: 5px;
  background: var(--block); border-radius: var(--r) var(--r) 0 0; }
.plan .tag { font-family: var(--mono); font-size: .7rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.plan.on .tag { color: var(--link); font-weight: 600; }
/* Y la ventaja exclusiva del plan grande deja de ser un guion gris más. */
.plan.on li::before { color: var(--link); content: "+"; font-weight: 700; }
.plan .amt { font-size: var(--step-3); font-weight: 700; font-stretch: 108%; letter-spacing: -.035em; margin-top: .7rem; line-height: 1; }
.plan .amt small { font-size: var(--step--1); font-weight: 500; color: var(--muted); letter-spacing: 0; }
.plan .yr { font-size: var(--step--1); color: var(--muted); margin-top: .5rem; }
.plan ul { list-style: none; padding: 0; margin: 1.2rem 0 1.5rem; display: grid; gap: .5rem; font-size: var(--step--1); }
.plan li { display: flex; gap: .6rem; color: var(--muted); }
.plan li::before { content: "—"; color: var(--cline-strong); }
.plan .btn { margin-top: auto; }

/* ── 16 · Entrada ────────────────────────────────────────────────────────── */
/* Una sola coreografía de carga en la portada, escalonada. El contenido es
   visible por defecto: la animación solo lo desplaza, nunca lo esconde. */
@media (prefers-reduced-motion: no-preference) {
  .rise { animation: rise 620ms var(--ease) both; }
  .rise:nth-child(2) { animation-delay: 70ms; }
  .rise:nth-child(3) { animation-delay: 140ms; }
  @keyframes rise { from { transform: translateY(14px); opacity: 0; } }
}


/* ═══════════════════════════════════════════════════════════════════════════
   DS2 · portada — el cilindro de capturas y los paneles de los actos

   Viene del prototipo (design/mockups/web_ds2/), donde se diseñó y se afinó
   con Oriol. Va AL FINAL a propósito: varias de estas reglas redefinen `.dev`
   y `.scr`, que arriba siguen describiendo el teléfono DIBUJADO. Por orden de
   cascada ganan estas, y el bloque entero se puede quitar de una pieza sin
   tocar el resto de la hoja.

   Spec: docs/superpowers/specs/2026-08-11-web-cilindro-capturas.md
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 11 bis · El cilindro de pantallas ───────────────────────────────────────
   Dieciséis capturas REALES de la app, montadas en marcos de teléfono y pegadas
   por dentro a la pared de un cilindro que gira con el scroll, con el eslogan
   quieto en el eje. Spec:
   docs/superpowers/specs/2026-08-11-web-cilindro-capturas.md

   Cuatro decisiones que sostienen todo lo demás:

   1. El radio base sale de una cuenta, no del ojo: para que dieciséis paneles de
      `--pw` quepan en la circunferencia, R = (pw/2) / tan(π/16) = pw × 2,5137.
      Encima de esa base, cada panel se desvía lo suyo (`--dz`) — el cilindro es
      irregular a propósito: una rejilla perfecta lee a catálogo, no a montaje.
   2. El anillo se empuja hacia dentro (`translateZ(-R)`), así que los paneles de
      delante caen cerca de z = 0 —tamaño natural, nada de gigantes deformados— y
      los de detrás a −2R, donde la perspectiva ya los reduce sola.
   3. La profundidad la hace un VELO del color del papel en el plano del eje, no
      el JS: lo que pasa por detrás se atenúa al atravesarlo y lo de delante queda
      limpio. El eslogan vive dos píxeles por delante del velo → el cilindro lo
      ENVUELVE: los paneles frontales le pasan por encima.
   4. El marco de teléfono no es decoración: girando, una imagen plana parece una
      carta. El bisel es lo que la convierte en un objeto con volumen. Va sin
      marca ninguna (regla 4: cero oficialidad, y tampoco la de Apple).

   El JS escribe DOS variables: `--giro` y `--fade`. Sin JS, el cilindro se queda
   quieto, en pose y visible. */
.cyl { --n: 16; --pw: 190px; --R: 478px; --giro: -14deg; --fade: 1; --lift: 0px;
  position: relative; block-size: 155vh; }
.cyl-stage { position: sticky; top: 0; block-size: 100svh;
  opacity: var(--fade);
  /* SIN transición: `--fade` y `--lift` se escriben en cada fotograma, así que
     una transición interpola hacia un valor que ya cambió — se notaba como un
     arrastre, como si el cilindro se atascara al pasar por el eslogan.

     Y SIN `overflow: hidden`: recortaba el cilindro con una LÍNEA RECTA en el
     borde del panel, y ese borde entra en pantalla en cuanto el panel se
     despega o sube. Se sustituye por una máscara: los teléfonos se desvanecen
     al llegar arriba y abajo en vez de cortarse a cuchillo, y da igual dónde
     caiga el borde. `contain: layout` se queda; `paint` recortaba igual. */
  contain: layout;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 9%, #000 91%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 9%, #000 91%, transparent 100%); }
/* La perspectiva vive AQUÍ, y `preserve-3d` con ella: anillo, velo y eslogan
   tienen que compartir un mismo espacio para que se ordenen por profundidad
   real y no por orden de aparición en el HTML. */
/* Nada de grid aquí: el velo mide 260 vmax y, como ítem, estiraba la celda y
   empujaba el centro del cilindro fuera del viewport (medido: el anillo se iba
   a x≈1500 en una ventana de 1280). Cada pieza se ancla al CENTRO del espacio y
   se coloca con su propio `translate(-50%, -50%)`, que es lo que hace que el
   eje del cilindro y el centro de la escena sean el mismo punto. */
.cyl-space { position: relative; inline-size: 100%; block-size: 100%;
  perspective: 1150px; transform-style: preserve-3d;
  translate: 0 var(--lift); }
.cyl-ring, .cyl-veil, .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)); }
/* Cada panel: su hueco (`--i`) + su desvío (`--jit`), su nivel de profundidad
   (`--dz`), su altura (`--dy`), su escala (`--sc`) y su inclinación (`--tilt`).
   Los valores van en el HTML, fijos: la irregularidad es un montaje decidido,
   no un aleatorio que cambia en cada carga. */
.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: un teléfono genérico, sin marca ────────────────────────────── */
.fon { position: relative; padding: calc(var(--pw) * .028);
  border-radius: calc(var(--pw) * .135);
  background: linear-gradient(155deg, #3A3F49 0%, #14171C 42%, #0C0E12 100%);
  box-shadow: 0 18px 40px rgba(0, 0, 0, .28), 0 0 0 1px rgba(255, 255, 255, .07) inset; }
:root[data-theme="dark"] .cyl-halo { opacity: .95; }
:root[data-theme="dark"] .fon { box-shadow: 0 18px 44px rgba(0, 0, 0, .6), 0 0 0 1px rgba(255, 255, 255, .06) inset; }
/* La isla del altavoz. Es lo único que hace falta para que se lea «teléfono»;
   más detalle (botones, cámaras) empezaría a parecerse a un modelo concreto. */
.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: 99px; background: #07090C; }
.fon img { display: block; inline-size: 100%; block-size: auto;
  aspect-ratio: 1179 / 2556; object-fit: cover; object-position: top center;
  border-radius: calc(var(--pw) * .108); background: var(--card); }

/* Grande a propósito: tiene que cubrir el viewport A ESA PROFUNDIDAD, donde
   todo se ve más pequeño, o se le verían los bordes al girar. */
.cyl-veil { inline-size: 108vmax; block-size: 108vmax;
  background: var(--paper); opacity: .74; pointer-events: none;
  transform: translate(-50%, -50%) translateZ(calc(-1 * var(--R))); }

/* El claro del centro. Sin él, el eslogan se leía a trozos: los paneles no
   cruzan ya la franja central, pero un cilindro lleno deja fondo detrás de cada
   línea de texto. El degradado abre un hueco de niebla justo detrás de la frase
   —y solo ahí—, así que el cilindro sigue leyéndose lleno por arriba y abajo. */
.cyl-halo { inline-size: 66vmin; block-size: 34vmin; pointer-events: none;
  background: radial-gradient(closest-side, var(--paper) 0 48%, transparent 100%);
  opacity: .90;
  transform: translate(-50%, -50%) translateZ(140px); }

.cyl-say { margin: 0; text-align: center; inline-size: min(15ch, 84vw);
  font-size: var(--step-3); line-height: 1.12; letter-spacing: -.015em;
  font-variation-settings: 'wdth' 112; color: var(--ink);
  /* NO va en el plano del eje, sino a media distancia (−0,42 R). Puesto en el
     eje lo tapaba medio cilindro y se leía a trozos; aquí solo le pasan por
     delante los paneles claramente frontales —que es justo el efecto: el
     cilindro lo ENVUELVE—, y el resto queda detrás del velo.
     El halo del color del papel lo despega cuando un panel del fondo cae
     detrás de una línea de texto. */
  /* DELANTE del cilindro, no en su eje. Puesto en el eje quedaba tapado por el
     panel frontal de turno y la frase se leía a trozos —probado—: el efecto
     dejaba de estar al servicio del mensaje. Aquí el tubo sigue envolviéndola
     por arriba y por abajo, que es de donde viene la sensación de estar dentro.
     La escala compensa el acercamiento (perspectiva 1150 / (1150 − 150)). */
  transform: translate(-50%, -50%) translateZ(150px) scale(.87);
  text-shadow: 0 0 14px var(--paper); }

/* Sin `will-change`: sobre un contenedor `preserve-3d` con dieciséis hijos
   promueve una capa gigantesca y sale más caro que dejar que el compositor
   decida — el `transform` del anillo ya lo promueve. */

/* En móvil SÍ hay cilindro —Oriol 2026-08-11—, pero a su escala: paneles de
   116 px (R recalculado con la misma fórmula), recorrido más corto y el velo
   algo más denso, porque a ese tamaño los del fondo ensucian antes el eslogan.
   Las imágenes que se sirven aquí son las de 220 px (`srcset`). */
@media (max-width: 759.98px) {
  .cyl { --n: 10; --pw: 116px; --R: 188px; block-size: 130vh; }
  /* Diez paneles, no dieciséis: menos texturas que recomponer en el hardware
     más justo. El radio se recalcula con la misma fórmula, R = (pw/2)/tan(π/10). */
  .cyl-ring figure:nth-child(n+11) { display: none; }
  .cyl-say { font-size: var(--step-2); max-inline-size: 13ch; }
  .cyl-veil { opacity: .70; }
  /* Alturas y profundidades se encogen con el cilindro: los valores del HTML
     están pensados para un panel de 190 px. */
  .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) * .55))
    rotateZ(var(--tilt, 0deg))
    scale(var(--sc, 1)); }
}
@media (max-width: 420px) { .cyl { --pw: 96px; --R: 241px; } }

/* ── La costura con lo que viene después ────────────────────────────────────
   El cilindro tiene que quedar POR DELANTE del fondo de la sección siguiente y
   POR DETRÁS de su texto (Oriol 2026-08-12): así se va disolviendo mientras el
   Acto 1 entra por encima, en vez de que un rectángulo opaco lo corte.

   El orden se consigue sin contextos de apilamiento intermedios, que es lo que
   antes lo rompía —la sección llevaba `opacity`, y un contexto aplana lo que
   haya dentro por muy alto que sea su `z-index`—:

     fondo del Acto 1  (::before)   z-index: -1   ← detrás de todo
     el cilindro       (.cyl-stage) z-index:  1
     texto del Acto 1  (.act-grid)  z-index:  2   ← por delante del cilindro

   Y el fundido de entrada de la sección pasa a su CONTENIDO por lo mismo:
   puesto en la sección creaba contexto y volvía a aplanar el orden. */
.cyl { position: relative; }
.cyl-stage { z-index: 1; }
.cyl + .act { position: relative; margin-block-start: -40vh; }
.cyl + .act::before { content: ""; position: absolute; z-index: -1;
  inset-block: 46vh 0; inset-inline: 0; background: var(--paper); }
.cyl + .act > .act-grid { position: relative; z-index: 2; opacity: var(--enter, 1); }
@media (max-width: 759.98px) { .cyl + .act { margin-block-start: -34vh; } }

/* Quieto y en pose: el giro atado al scroll es exactamente lo que esta
   preferencia pide desactivar. Sin recorrido, la sección deja de ser alta. */
@media (prefers-reduced-motion: reduce) {
  .cyl { block-size: auto; --giro: -14deg; --fade: 1; }
  .cyl-stage { position: static; block-size: min(100svh, 760px); transition: none; }
}
/* Sin JS no hay giro ni fundido, pero el cilindro se ve igual: la pose inicial
   ya lo explica. Nada de contenido que dependa de que se dispare una animación. */
.nojs .cyl { block-size: auto; }
.nojs .cyl-stage { position: static; block-size: min(100svh, 760px); }


/* El panel anclado de los actos: un teléfono de verdad con una captura de
   verdad dentro. Antes iba dibujado con los tokens —no había capturas en DS2 y
   las de DS1 habrían envejecido la web nueva—; desde que existe el set
   (design/capturas_web/) se enseña la app, no una ilustración de la app
   (Oriol 2026-08-11). El marco es el mismo `.fon` del cilindro, aquí en grande.
   El aspect-ratio es el del iPhone 16 de las capturas, no un rectángulo
   inventado: cualquier otro las recortaría. */
.dev {
  /* Dimensionado por ALTO, no por ancho: el panel se ancla a 88 px del borde y
     tiene que caber el titular, el subtítulo Y el teléfono. Midiendo por ancho
     —como estaba— un móvil de 272 px pedía 590 px de alto y se salía por abajo
     en cuanto la ventana bajaba de 900 px (Oriol 2026-08-11).
     Y coge TODO el alto disponible: es la pieza que hay que ver bien. Si en una
     ventana muy ancha y baja el ancho resultante se pasara de la columna,
     `max-inline-size` lo corta y el `aspect-ratio` reduce el alto solo. */
  position: relative; block-size: min(68svh, 640px); inline-size: auto;
  max-inline-size: 100%; margin-inline: auto;
  aspect-ratio: 1179 / 2556; padding: 9px; border-radius: 30px;
  background: linear-gradient(155deg, #3A3F49 0%, #14171C 42%, #0C0E12 100%);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .22), 0 0 0 1px rgba(255, 255, 255, .07) inset;
}
:root[data-theme="dark"] .dev { box-shadow: 0 18px 48px rgba(0, 0, 0, .6), 0 0 0 1px rgba(255, 255, 255, .06) inset; }
/* La isla del altavoz, por encima de la captura. */
.dev::after { content: ""; position: absolute; z-index: 2;
  inset-block-start: 21px; inset-inline-start: 50%; inline-size: 24%; block-size: 8px;
  transform: translateX(-50%); border-radius: 99px; background: #07090C; }
/* El relevo va EN LA DIRECCIÓN DEL SCROLL: la que ya pasó se queda arriba
   (`.up`), la que viene espera abajo (`.down`) y la activa está en su sitio. No
   hace falta detectar la dirección — la posición de cada plano respecto al
   compás activo ya la dice. Antes era un fundido plano y el cambio no se leía
   como avance. */
/* El relevo NO es un fundido cruzado —dos pantallas transparentes a la vez se
   leían como un doble expuesto—: es un REDIBUJO en el sentido del scroll. Los
   planos van apilados y cada uno se descubre con una máscara cuyo filo sube por
   encima del anterior —en el sentido del contenido, que al bajar el scroll sube
   (Oriol 2026-08-12)—, y baja si el scroll sube. La mayor parte del recorrido
   se ve UNA pantalla, nítida; la mezcla vive solo en el filo, una banda de un
   9 % de alto.
   `--rev` (0 → 1) lo escribe el JS en cada fotograma. */
.scr { position: absolute; inset: 9px; inline-size: calc(100% - 18px);
  block-size: calc(100% - 18px); object-fit: cover; object-position: top center;
  border-radius: 22px; background: var(--card);
  --rev: 0;
  -webkit-mask-image: linear-gradient(to top,
    #000 calc(var(--rev) * 118% - 9%), transparent calc(var(--rev) * 118%));
  mask-image: linear-gradient(to top,
    #000 calc(var(--rev) * 118% - 9%), transparent calc(var(--rev) * 118%)); }
/* `.on` solo existe para el arranque, el móvil y `nojs`. */
.scr.on { --rev: 1; }
html.nojs .scr:first-of-type, .dev .scr:only-of-type { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .scr { transform: none !important; } }
html.nojs .scr:first-of-type { --rev: 1; }


.act-h { font-size: var(--step-2); letter-spacing: -.03em; }
/* El subtítulo del acto iba a `--step--1` (≈13 px) debajo de un titular de 2 rem:
   el contraste era correcto —7,18 en Grafito, 5,11 en Mármol— pero a ese cuerpo
   se leía como un pie de foto y costaba (Oriol 2026-08-12). Sube al tamaño del
   texto corrido, que es el papel que hace: contexto del acto, no un apunte. */
.act-sub { color: var(--muted); margin-top: .85rem; font-size: var(--step-0);
  line-height: 1.55; }
/* Título, subtítulo y teléfono son UN bloque con la misma medida. Antes el
   texto se topaba a 40ch y el teléfono se centraba respecto a la COLUMNA, así
   que quedaba descolocado bajo su propio titular y sobraba hueco a un lado
   (Oriol 2026-08-11). */
/* En escritorio manda la COLUMNA: el titular, el subtítulo y el teléfono ocupan
   exactamente su ancho, ni uno más. Topando el subtítulo por su cuenta (estaba a
   40ch) quedaba un pasillo muerto a su derecha mientras el titular sí llegaba al
   borde. En móvil, donde la columna es la página entera, vuelve el tope de
   lectura o el párrafo se estira a 900 px.

   Y el texto de los actos va JUSTIFICADO (Oriol 2026-08-12): con las dos
   columnas ocupando su ancho exacto, el borde recto a los dos lados es lo que
   hace que los huecos se lean parejos en vez de deshilachados. `hyphens: auto`
   no es opcional aquí — sin partir palabras, el justificado en castellano abre
   ríos entre palabras largas. */
.act-sub, .beat p { text-align: justify; hyphens: auto; }
@media (max-width: 759.98px) { .act-sub { max-inline-size: 52ch; text-align: left; } }
.act-pin > .dev-box, .act-pin > .law { margin-top: 1.15rem; }


@media (min-width: 760px) {
  .act-grid { grid-template-columns: minmax(0, .78fr) minmax(0, 1fr); align-items: start; }
  .act-pin { position: sticky; top: 88px; }

  /* El segundo acto se refleja: panel a la derecha, texto a la izquierda. No es
     variedad por variedad — es lo que hace LEGIBLE el punto de ruptura. Cuando
     el panel salta de lado, el cuerpo entiende que ha empezado otro capítulo
     antes de leer el titular; con las dos mitades siempre iguales, los doce
     compases se leían como una sola lista muy larga.
     Se invierte con `grid-column`, no con `order` ni cambiando el HTML: el
     titular de la sección vive dentro del panel, así que en móvil —donde la
     rejilla es de una columna— tiene que seguir yendo el primero. */
  .act--flip .act-grid { grid-template-columns: minmax(0, 1fr) minmax(0, .78fr); }
  .act--flip .act-pin { grid-column: 2; grid-row: 1; }
  .act--flip .act-flow { grid-column: 1; grid-row: 1; }
  /* En el acto reflejado se alinea a la derecha el BLOQUE ENTERO —titular,
     subtítulo y teléfono—, no solo el teléfono: empujando únicamente el móvil,
     quedaba fuera del eje del texto que lo explica. */
  .act--flip .act-pin > .dev-box { margin-inline: auto 0; }
  /* Cada compás ocupa poco más de media pantalla: a 58vh quedaban dos pantallas
     de hueco entre bloques y el acto se leía como una lista muy separada
     (Oriol 2026-08-11). Con 42 el panel sigue teniendo tiempo de cambiar. */
  .beat { min-block-size: 42vh; display: flex; flex-direction: column; justify-content: center; }
  /* El desfase de arranque: sin él, el compás 1 cruzaba el centro mientras el
     panel todavía estaba llegando. */
  .act-flow { padding-block-start: 34vh; }
  /* Cola al pie de la columna que scrollea: sin ella, en el último compás el
     panel llega al fondo de su contenedor y empieza a irse hacia arriba
     (medido: -21 px). El panel debe seguir quieto hasta que el compás termine. */
  .act-flow { padding-block-end: 24vh; }
}
.beat { padding-block: 1.6rem; }
.beat h3 { font-size: clamp(1.5rem, 1.2rem + 1.35vw, 2.3rem); font-stretch: 106%; letter-spacing: -.032em; }
.beat p { color: var(--muted); margin-top: .9rem; max-width: 44ch; font-size: var(--step-0); }
.beat .n {
  font-family: var(--mono); font-size: .72rem; letter-spacing: .16em; color: var(--muted);
  display: block; margin-bottom: .8rem;
}
/* El compás activo se marca con la misma gramática del resto: la marca. */
@media (min-width: 900px) {
  .beat { opacity: .42; transition: opacity var(--dur-slow) var(--ease); }
  .beat.on { opacity: 1; }
  .beat.on .n { color: var(--link); }
  html.nojs .beat { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .beat { opacity: 1 !important; transition: none; } }

/* El teléfono: dibujado, no una captura. Una captura de la app envejece con la
   app; esto usa los mismos tokens, así que se actualiza solo. */
/* El panel anclado de los actos: un teléfono de verdad con una captura de
   verdad dentro. Antes iba dibujado con los tokens —no había capturas en DS2 y
   las de DS1 habrían envejecido la web nueva—; desde que existe el set
   (design/capturas_web/) se enseña la app, no una ilustración de la app
   (Oriol 2026-08-11). El marco es el mismo `.fon` del cilindro, aquí en grande.
   El aspect-ratio es el del iPhone 16 de las capturas, no un rectángulo
   inventado: cualquier otro las recortaría. */
.dev {
  /* Dimensionado por ALTO, no por ancho: el panel se ancla a 88 px del borde y
     tiene que caber el titular, el subtítulo Y el teléfono. Midiendo por ancho
     —como estaba— un móvil de 272 px pedía 590 px de alto y se salía por abajo
     en cuanto la ventana bajaba de 900 px (Oriol 2026-08-11).
     Y coge TODO el alto disponible: es la pieza que hay que ver bien. Si en una
     ventana muy ancha y baja el ancho resultante se pasara de la columna,
     `max-inline-size` lo corta y el `aspect-ratio` reduce el alto solo. */
  position: relative; block-size: min(68svh, 640px); inline-size: auto;
  max-inline-size: 100%; margin-inline: auto;
  aspect-ratio: 1179 / 2556; padding: 9px; border-radius: 30px;
  background: linear-gradient(155deg, #3A3F49 0%, #14171C 42%, #0C0E12 100%);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .22), 0 0 0 1px rgba(255, 255, 255, .07) inset;
}
:root[data-theme="dark"] .dev { box-shadow: 0 18px 48px rgba(0, 0, 0, .6), 0 0 0 1px rgba(255, 255, 255, .06) inset; }
/* La isla del altavoz, por encima de la captura. */
.dev::after { content: ""; position: absolute; z-index: 2;
  inset-block-start: 21px; inset-inline-start: 50%; inline-size: 24%; block-size: 8px;
  transform: translateX(-50%); border-radius: 99px; background: #07090C; }
/* El relevo va EN LA DIRECCIÓN DEL SCROLL: la que ya pasó se queda arriba
   (`.up`), la que viene espera abajo (`.down`) y la activa está en su sitio. No
   hace falta detectar la dirección — la posición de cada plano respecto al
   compás activo ya la dice. Antes era un fundido plano y el cambio no se leía
   como avance. */
/* El relevo NO es un fundido cruzado —dos pantallas transparentes a la vez se
   leían como un doble expuesto—: es un REDIBUJO en el sentido del scroll. Los
   planos van apilados y cada uno se descubre con una máscara cuyo filo sube por
   encima del anterior —en el sentido del contenido, que al bajar el scroll sube
   (Oriol 2026-08-12)—, y baja si el scroll sube. La mayor parte del recorrido
   se ve UNA pantalla, nítida; la mezcla vive solo en el filo, una banda de un
   9 % de alto.
   `--rev` (0 → 1) lo escribe el JS en cada fotograma. */
.scr { position: absolute; inset: 9px; inline-size: calc(100% - 18px);
  block-size: calc(100% - 18px); object-fit: cover; object-position: top center;
  border-radius: 22px; background: var(--card);
  --rev: 0;
  -webkit-mask-image: linear-gradient(to top,
    #000 calc(var(--rev) * 118% - 9%), transparent calc(var(--rev) * 118%));
  mask-image: linear-gradient(to top,
    #000 calc(var(--rev) * 118% - 9%), transparent calc(var(--rev) * 118%)); }
/* `.on` solo existe para el arranque, el móvil y `nojs`. */
.scr.on { --rev: 1; }
html.nojs .scr:first-of-type, .dev .scr:only-of-type { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .scr { transform: none !important; } }
html.nojs .scr:first-of-type { --rev: 1; }
.scr-top { background: var(--block); color: var(--on-block); padding: 1.1rem 1rem .95rem; }
.scr-top .e { font-family: var(--mono); font-size: .6rem; letter-spacing: .16em; text-transform: uppercase; }
.scr-top .t { font-size: 1.05rem; font-weight: 700; letter-spacing: -.025em; line-height: 1.15; margin-top: .45rem; }
.scr-body { flex: 1; padding: .95rem 1rem; display: flex; flex-direction: column; gap: .55rem; font-size: .72rem; line-height: 1.5; }
.scr-body p { color: var(--muted); }
.scr-body .ink { color: var(--ink); }
.scr-line { block-size: 6px; border-radius: 2px; background: var(--cline); }
.scr-line.s { inline-size: 62%; }
.scr-opt { border: 1px solid var(--cline); border-radius: var(--r); padding: .42rem .55rem; display: flex; gap: .5rem; align-items: center; }
.scr-opt i { font-style: normal; font-family: var(--mono); font-size: .64rem; color: var(--muted); }
.scr-opt.ok { border-color: var(--success); border-width: 2px; background: color-mix(in srgb, var(--success) 10%, transparent); }
.scr-opt.ok i { color: var(--success); font-weight: 700; }
.scr-chip { align-self: flex-start; font-family: var(--mono); font-size: .62rem; color: var(--link);
  border: 1px solid var(--cline-strong); border-radius: 999px; padding: .2rem .55rem; }
.scr-bubble { border: 1px solid var(--cline); border-radius: var(--r); padding: .55rem .6rem; }
.scr-bubble.me { background: var(--block); color: var(--on-block); border-color: transparent; margin-left: 2.2rem; }
.scr-bar { block-size: 3px; background: var(--cline); }
.scr-bar i { display: block; block-size: 100%; inline-size: 46%; background: var(--block); }
.scr-foot { margin-top: auto; border-top: 1px solid var(--cline); padding: .6rem 1rem;
  font-family: var(--mono); font-size: .6rem; color: var(--muted); display: flex; justify-content: space-between; }

/* El acto del anclaje: el artículo se queda quieto y la marca se mueve por él.
   El rotulador viaja de una frase a otra mientras el texto de al lado explica
   qué se está mirando — el color hace de puntero, que es su trabajo. */
.law { border: 1px solid var(--cline); border-radius: var(--r); background: var(--card); padding: 1.5rem 1.4rem; }
.law .lbl { font-family: var(--mono); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); margin-bottom: 1rem; }
.law .txt { font-size: var(--step--1); line-height: 1.75; color: var(--muted); }
.law .txt em { font-style: normal; color: var(--ink); }
/* Apagada por defecto, encendida al llegar su compás. Sin JS todas quedan
   encendidas y el párrafo sigue leyéndose igual de bien. */
.law .txt .hl {
  background-image: linear-gradient(to top, var(--mark) 0 76%, transparent 76%);
  background-repeat: no-repeat; background-position: 0 0;
  background-size: 0 100%;
  transition: background-size 520ms var(--ease);
  padding-inline: .05em; box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
.law .txt .hl.on { background-size: 100% 100%; color: var(--ink); }
html.nojs .law .txt .hl { background-size: 100% 100%; color: var(--ink); }
@media (prefers-reduced-motion: reduce) { .law .txt .hl { transition: none; } }
.law .cite { font-family: var(--mono); font-size: .72rem; color: var(--link); margin-top: 1.2rem; }

/* ── En móvil el panel no se ancla: el teléfono se convierte en un carrusel ──
   Sin esto, el panel se quedaba enseñando la PRIMERA pantalla mientras el texto
   de abajo hablaba de seis features distintas: la imagen dejaba de significar
   nada. Como fila deslizable, las seis pantallas siguen viéndose y cada una es
   una tarjeta de pleno derecho. */
@media (max-width: 759.98px) {
  .dev {
    inline-size: auto; block-size: auto; aspect-ratio: auto; padding: 0;
    border: 0; border-radius: 0; overflow: visible; box-shadow: none;
    background: none;
    display: flex; gap: .75rem;
    overflow-x: auto; scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    /* se sale de la columna para poder deslizar de borde a borde */
    margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter);
    padding-block-end: .5rem;
  }
  .dev::-webkit-scrollbar { display: none; }
  /* El carrusel desaparece: en móvil las capturas viven DENTRO de su compás
     (las mueve ds2.js), así que cada feature es una tarjeta entera —pantalla,
     titular y párrafo— en vez de una fila de pantallas sueltas arriba y el
     texto huérfano debajo. */
  .dev { display: none; }
  .beat > .scr {
    display: block; position: static; float: none;
    inline-size: min(232px, 62%); block-size: auto; aspect-ratio: 1179 / 2556;
    margin: 0 auto 1.5rem; opacity: 1 !important;
    -webkit-mask-image: none !important; mask-image: none !important;
    object-fit: cover; object-position: top center;
    /* El bisel es el propio borde: a este tamaño un marco con relleno y su isla
       solo restaba pantalla útil. */
    border: 9px solid #14171C; border-radius: 34px;
    box-shadow: 0 14px 34px rgba(0, 0, 0, .22); }
  :root[data-theme="dark"] .beat > .scr { border-color: #23282F; box-shadow: 0 14px 34px rgba(0, 0, 0, .5); }
  .beat { padding-block-end: .25rem; }
  .beat-meta { margin-top: 1rem; }
  /* Sin marco en el carrusel: a este tamaño el bisel se comía 40 px de
     pantalla útil por tarjeta y las capturas ya se leen como teléfono. */
  .dev::after { content: none; }
  .scr {
    position: static; opacity: 1 !important; transform: none !important;
    -webkit-mask-image: none !important; mask-image: none !important;
    flex: 0 0 min(268px, 72%);
    inline-size: min(268px, 72%); block-size: 581px;   /* 268 × 2556/1179 */
    scroll-snap-align: center; object-fit: cover; object-position: top center;
    border: 1px solid var(--cline-strong); border-radius: 18px;
    background: var(--card);
  }
}

/* Concreciones del compás: datos reales en mono, no relleno. Dan cuerpo a la
   columna que scrollea —que se quedaba en un titular y tres líneas— sin inventar
   nada: cada línea es un número o un hecho que la app cumple. */
.beat-meta { list-style: none; padding: 0; margin: 1.3rem 0 0; display: flex; flex-wrap: wrap; gap: .45rem; }
.beat-meta li {
  font-family: var(--mono); font-size: .72rem; color: var(--muted);
  border: 1px solid var(--cline); border-radius: 999px; padding: .35rem .75rem;
}
.beat.on .beat-meta li { border-color: var(--cline-strong); color: var(--ink); }


/* ── En móvil el panel no se ancla: el teléfono se convierte en un carrusel ──
   Sin esto, el panel se quedaba enseñando la PRIMERA pantalla mientras el texto
   de abajo hablaba de seis features distintas: la imagen dejaba de significar
   nada. Como fila deslizable, las seis pantallas siguen viéndose y cada una es
   una tarjeta de pleno derecho. */
@media (max-width: 759.98px) {
  .dev {
    inline-size: auto; block-size: auto; aspect-ratio: auto; padding: 0;
    border: 0; border-radius: 0; overflow: visible; box-shadow: none;
    background: none;
    display: flex; gap: .75rem;
    overflow-x: auto; scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    /* se sale de la columna para poder deslizar de borde a borde */
    margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter);
    padding-block-end: .5rem;
  }
  .dev::-webkit-scrollbar { display: none; }
  /* El carrusel desaparece: en móvil las capturas viven DENTRO de su compás
     (las mueve ds2.js), así que cada feature es una tarjeta entera —pantalla,
     titular y párrafo— en vez de una fila de pantallas sueltas arriba y el
     texto huérfano debajo. */
  .dev { display: none; }
  .beat > .scr {
    display: block; position: static; float: none;
    inline-size: min(232px, 62%); block-size: auto; aspect-ratio: 1179 / 2556;
    margin: 0 auto 1.5rem; opacity: 1 !important;
    -webkit-mask-image: none !important; mask-image: none !important;
    object-fit: cover; object-position: top center;
    /* El bisel es el propio borde: a este tamaño un marco con relleno y su isla
       solo restaba pantalla útil. */
    border: 9px solid #14171C; border-radius: 34px;
    box-shadow: 0 14px 34px rgba(0, 0, 0, .22); }
  :root[data-theme="dark"] .beat > .scr { border-color: #23282F; box-shadow: 0 14px 34px rgba(0, 0, 0, .5); }
  .beat { padding-block-end: .25rem; }
  .beat-meta { margin-top: 1rem; }
  /* Sin marco en el carrusel: a este tamaño el bisel se comía 40 px de
     pantalla útil por tarjeta y las capturas ya se leen como teléfono. */
  .dev::after { content: none; }
  .scr {
    position: static; opacity: 1 !important; transform: none !important;
    -webkit-mask-image: none !important; mask-image: none !important;
    flex: 0 0 min(268px, 72%);
    inline-size: min(268px, 72%); block-size: 581px;   /* 268 × 2556/1179 */
    scroll-snap-align: center; object-fit: cover; object-position: top center;
    border: 1px solid var(--cline-strong); border-radius: 18px;
    background: var(--card);
  }
}


/* ── Hero · el buscador de oposición ─────────────────────────────────────────
   Lo que había aquí eran tres capturas de la app, y justo debajo vienen el
   cilindro y seis compases que enseñan pantallas: era la tercera vez que se
   contaba lo mismo (Oriol 2026-08-12). Lo que sí falta a esa altura es
   responder «¿está la mía?» y dar una razón para quedarse en la web —el tema 1
   se lee gratis— antes de mandar a nadie a la tienda.

   El bloque es el mismo `.finder` + `.rows` de la sección de oposiciones, así
   que hereda su comportamiento. Lo único propio: las filas nacen ocultas y solo
   aparecen al escribir. Se hace con el `data-has` que ds2.js ya pone en el
   buscador, así que sin JS quedan visibles —enlaces a las oposiciones, que es
   el fallo correcto—. */
.hero-find { align-self: start; padding: clamp(1.1rem, 2.4vw, 1.6rem);
  border: 1px solid var(--cline); border-radius: 20px; background: var(--card);
  box-shadow: 0 12px 34px rgba(0, 0, 0, .06); }
:root[data-theme="dark"] .hero-find { box-shadow: 0 12px 34px rgba(0, 0, 0, .34); }
.hf-eyebrow { font-family: var(--mono); font-size: .72rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted); margin-bottom: .8rem; }
.hero-find .finder { margin: 0; }
.hero-find .finder-n { display: none; }
/* Sin consulta no hay lista: si no, el hero abre con cinco filas que nadie ha
   pedido y el panel se come la pantalla. */
.hero-find .finder:not([data-has]) ~ .rows,
.hero-find .finder:not([data-has]) ~ .finder-none { display: none; }
.hero-find .rows { margin-top: .8rem; max-block-size: 46vh; overflow-y: auto; }
/* Lo que se llevan gratis: era un párrafo al pie del buscador y es la
   información más importante de la portada. Ahora abre con su promesa en dos
   pesos, sigue con los tres verbos —leer, escuchar, practicar— en pastillas con
   icono, y cierra con el matiz en letra pequeña. */
.hf-libre { margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid var(--cline); }
.hf-libre-h { font-size: var(--step--1); line-height: 1.4; color: var(--muted); }
.hf-libre-h b { display: block; color: var(--ink); font-size: var(--step-0);
  font-weight: 620; letter-spacing: -.015em; }
.hf-libre ul { list-style: none; margin: .8rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .45rem; }
.hf-libre li { display: inline-flex; align-items: center; gap: .4rem;
  padding: .38rem .7rem; border-radius: 999px;
  background: var(--block-soft); color: var(--link);
  font-family: var(--mono); font-size: .72rem; letter-spacing: .02em; }
.hf-libre-p { margin-top: .7rem; font-size: .78rem; line-height: 1.5; color: var(--muted); }
/* El panel entero pertenece al sistema: mismo filete que la placa de precios. */
.hero-find { border-block-start: 4px solid var(--block); }

/* Las pastillas del hero: la enumeración que antes era un párrafo de cuatro
   líneas. Son ETIQUETAS, no enlaces — no se pulsan, se leen de un barrido.
   Viven en la columna DERECHA, bajo el buscador: ahí el hero dejaba media
   pantalla vacía (Oriol 2026-08-13), y con icono llenan ese hueco con algo que
   además dice lo que hay. */
.hero-col { display: flex; flex-direction: column; }
.hero-tags { list-style: none; margin: .85rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .5rem; }
.hero-tags li { display: inline-flex; align-items: center; gap: .45rem;
  padding: .45rem .8rem; border-radius: 999px;
  border: 1px solid var(--cline-strong); background: var(--card); color: var(--muted);
  font-family: var(--mono); font-size: .74rem; letter-spacing: .01em; }
.hero-tags .ico { color: var(--link); flex: none; }

/* ── El fundador: cara y cita ────────────────────────────────────────────────
   Era un párrafo largo en negrita, volcado a todo el ancho y sin nada que lo
   sostuviera (Oriol, dos veces). Ahora es una PIEZA: tarjeta con su filete de
   color, la comilla como ornamento —lo único decorativo de la portada, y va
   aquí porque una cita es lo único que puede llevarla—, la primera frase
   destacada y la firma con cara y rol al pie. */
/* Centrada y ancha: pegada al margen izquierdo dentro de una columna estrecha
   parecía un aparte, no la voz de quien firma el producto (Oriol 2026-08-12).
   La tarjeta colgaba de `.prose`, que tiene su propia medida y va alineado a la
   izquierda: por eso centrar por dentro no bastaba. */
.funda-sec { text-align: center; }
.funda-sec .funda { text-align: initial; }
.funda-h { max-inline-size: 24ch; margin-inline: auto; text-wrap: balance; }
.funda { margin: clamp(1.4rem, 4vw, 2.2rem) auto 0; max-inline-size: 74ch; position: relative;
  background: var(--card); border: 1px solid var(--cline); border-radius: var(--r);
  border-inline-start: 3px solid var(--block);
  padding: clamp(1.6rem, 4vw, 2.6rem) clamp(1.4rem, 4vw, 2.8rem); }
.funda-q { position: absolute; inset-block-start: -.35em; inset-inline-start: .35em;
  font-size: clamp(4rem, 9vw, 7rem); line-height: 1; color: var(--block); opacity: .18;
  font-family: var(--sans); font-weight: 700; pointer-events: none; }
.funda-cita { margin: 0; padding: 0; border: 0; position: relative; }
.funda-lead { font-size: var(--step-1); font-weight: 560; line-height: 1.35; color: var(--ink);
  letter-spacing: -.02em; text-wrap: balance; text-align: center; }
.funda-cita p + p { margin-top: .9rem; color: var(--muted); font-size: var(--step-0);
  line-height: 1.6; max-inline-size: 62ch; margin-inline: auto;
  text-align: justify; hyphens: auto; }
/* Justificar solo cuando hay ancho para hacerlo: en una columna de móvil salen
   ríos de espacio entre palabras y la partición corta a cada línea. */
@media (max-width: 559.98px) {
  .funda-cita p + p, .act-sub, .beat p, .feat-say p { text-align: start; hyphens: manual; }
}
.funda-firma { display: flex; align-items: center; justify-content: center; gap: .9rem;
  margin-top: 1.6rem; padding-top: 1.2rem; border-top: 1px solid var(--cline); }
.funda-foto { inline-size: 52px; block-size: 52px; border-radius: 50%; object-fit: cover;
  border: 1px solid var(--cline-strong); flex: none; }
.funda-firma span { font-family: var(--mono); font-size: .74rem; color: var(--muted); line-height: 1.5; }
.funda-firma b { display: block; font-weight: 500; color: var(--ink); font-size: .8rem; }

/* ── Precios: el CTA es la tienda, no un ancla ───────────────────────────────
   Los planes se compran DENTRO de la app (IAP), así que un botón «Empezar con
   Solo» que solo hacía scroll al pie prometía un paso que no existe. Un solo
   CTA, con las dos tiendas y la letra pequeña de verdad. */
.plan-cta { margin-top: clamp(1.4rem, 3vw, 2rem); display: grid; gap: 1rem; justify-items: center;
  text-align: center; }
.plan-cta-say { max-inline-size: 56ch; color: var(--muted); font-size: var(--step-0); text-wrap: pretty; }
.plan-cta-say b { color: var(--ink); font-weight: 550; }
.plan-cta-fine { font-family: var(--mono); font-size: .72rem; color: var(--muted); }

/* ── Equilibrio del acto y del CTA de cierre ─────────────────────────────────
   El acto abría con aire arriba y el teléfono llegaba pegado al borde de abajo
   (Oriol). Y en el cierre, el titular y su línea partían mal: `balance` reparte
   las líneas en vez de dejar una huérfana de dos palabras. */
.act { padding-block: clamp(2.4rem, 6vw, 4rem) clamp(2rem, 5vw, 3.4rem); }
.cta h2, .cta p { text-wrap: balance; }

/* ── /la-app · las features que no caben en la portada ───────────────────────
   Una tarjeta por feature: captura, eyebrow, titular y párrafo. Alternan lado
   en escritorio para que la columna no se lea como una lista de fichas. */
.feats { display: grid; gap: clamp(2.4rem, 6vw, 4rem); margin-top: clamp(1.6rem, 4vw, 2.4rem); }
.feat { display: grid; gap: clamp(1.2rem, 3vw, 2.4rem); align-items: center; }
@media (min-width: 760px) {
  .feat { grid-template-columns: 260px minmax(0, 1fr); }
  .feat:nth-child(even) .feat-shot { grid-column: 2; grid-row: 1; }
  .feat:nth-child(even) .feat-say { grid-column: 1; grid-row: 1; }
}
.feat-shot { inline-size: min(260px, 62%); block-size: auto; justify-self: center;
  aspect-ratio: 1179 / 2556; object-fit: cover; object-position: top center;
  border: 8px solid #14171C; border-radius: 30px; background: var(--card);
  box-shadow: 0 14px 34px rgba(0, 0, 0, .2); }
:root[data-theme="dark"] .feat-shot { border-color: #23282F; box-shadow: 0 14px 34px rgba(0, 0, 0, .5); }
.feat-say .n { font-family: var(--mono); font-size: .7rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--link); }
.feat-say h3 { font-size: var(--step-2); letter-spacing: -.03em; margin-top: .5rem; text-wrap: balance; }
.feat-say p { color: var(--muted); margin-top: .8rem; font-size: var(--step-0); line-height: 1.55;
  text-align: justify; hyphens: auto; max-inline-size: 62ch; }


/* ── La cadena de la vigilancia normativa ────────────────────────────────────
   «El contenido no envejece» era un párrafo suelto en media pantalla, y una
   promesa así en prosa se lee como un eslogan (Oriol 2026-08-13). En cuatro
   pasos numerados pasa a leerse como un MECANISMO: qué pasa, en qué orden y
   quién lo hace.

   Es HTML, no una imagen: escala a cualquier ancho, se lee en un lector de
   pantalla, y cuando cambie una palabra no hay que reexportar nada. El hilo que
   une los pasos es un `::before` de un píxel, y en móvil pasa a vertical solo
   —la lista es la misma, cambia el eje—. */
.vig { display: grid; gap: clamp(1.8rem, 4vw, 3rem); align-items: start; }
@media (min-width: 940px) { .vig { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); } }
.cadena { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.cadena li { position: relative; padding: 0 0 1.6rem 3.1rem; font-size: var(--step--1);
  line-height: 1.55; color: var(--muted); }
.cadena li:last-child { padding-bottom: 0; }
/* El hilo: arranca bajo el número y muere en el siguiente. En el último no hay. */
.cadena li::before { content: ""; position: absolute; inset-block: 2.1rem 0; inset-inline-start: .82rem;
  inline-size: 1px; background: var(--cline-strong); }
.cadena li:last-child::before { content: none; }
.cadena b { display: block; color: var(--ink); font-weight: 560; font-size: var(--step-0);
  letter-spacing: -.01em; margin-bottom: .15rem; }
.cad-n { position: absolute; inset-inline-start: 0; inset-block-start: 0;
  inline-size: 1.7rem; block-size: 1.7rem; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: .66rem; letter-spacing: .04em;
  color: var(--on-block); background: var(--block); }


/* ── Las fichas del CTA ──────────────────────────────────────────────────────
   El argumento del cierre era un párrafo con ocho ventajas seguidas: un muro de
   texto, en el último bloque de la página y sobre color, que no lee nadie
   (Oriol 2026-08-13). En fichas se BARRE — icono, nombre y una línea de matiz—
   y de paso el bloque deja de ser dos párrafos y unos badges.

   Van sobre la banda pintada, así que no llevan fondo propio: solo un filete de
   la tinta del bloque. Una tarjeta clara encima del color competiría con la
   placa de los badges, que es la que tiene que ganar. */
.cta-feats { list-style: none; margin: clamp(1.6rem, 4vw, 2.2rem) auto 0; padding: 0;
  display: grid; gap: 1px; max-inline-size: 720px;
  background: color-mix(in srgb, var(--on-block) 22%, transparent);
  border: 1px solid color-mix(in srgb, var(--on-block) 22%, transparent);
  border-radius: var(--r); overflow: hidden;
  grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 760px) { .cta-feats { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.cta-feats li { display: grid; gap: .12rem; padding: 1rem .9rem 1.05rem;
  background: var(--block); text-align: center; justify-items: center;
  font-family: var(--mono); font-size: .68rem; line-height: 1.45; color: var(--on-block); }
.cta-feats b { font-family: var(--sans); font-size: var(--step--1); font-weight: 600;
  letter-spacing: -.01em; line-height: 1.25; }
.cta-feats .ico { margin-bottom: .35rem; }
/* Con las fichas dentro, la placa de los badges ya no necesita separarse tanto. */
.cta-feats + .cta-plate { margin-top: clamp(1.4rem, 3vw, 2rem); }

/* ── La placa de descarga del bloque de precios ──────────────────────────────
   Debajo de dos tarjetas de plan, un párrafo suelto con badges no se lee como el
   remate de la sección: se lee como una nota (Oriol 2026-08-13). Es la MISMA
   pieza que el resto de CTA —rótulo, filete, tarjeta—, solo que centrada. */
.plan-cta { display: grid; gap: 1rem; justify-items: center; text-align: center;
  margin-top: clamp(1.6rem, 3.4vw, 2.4rem); padding: clamp(1.4rem, 3vw, 2rem) 1.3rem;
  border: 1px solid var(--cline); border-block-start: 4px solid var(--block);
  border-radius: var(--r); background: var(--card); }


/* El lienzo de la malla de triángulos (solo portada; lo crea ds2.js).
   `mix-blend-mode` en vez de opacidad plana: sobre el papel oscurece y sobre la
   banda pintada aclara, así el tejido pertenece al fondo que cruza en vez de
   flotar como una capa ajena. Nunca intercepta un clic. */
.estela { position: fixed; inset: 0; z-index: -1; pointer-events: none;
  mix-blend-mode: multiply; }
/* En Grafito el índigo de marca es oscuro sobre casi negro: `screen` lo aclara
   hasta que se ve, que es justo lo que `multiply` hace en Mármol al revés. */
:root[data-theme="dark"] .estela { mix-blend-mode: screen; }
@media (prefers-reduced-motion: reduce) { .estela { display: none; } }


/* ── Retoques del hero y de las cifras (2026-08-13) ──────────────────────────
   Todo viene de la misma nota de Oriol: el panel se leía descentrado, las
   pastillas parecían caídas del cielo y el pie de cada cifra no decía por qué
   importaba el número. */

/* El panel del buscador va centrado: con el titular a la izquierda y las
   pastillas debajo, el eje de la columna derecha es el centro. */
.hf-libre { text-align: center; }
.hf-libre ul { justify-content: center; }
.hero-tags { justify-content: center; }
.hero-tags-h { font-family: var(--mono); font-size: .7rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted); text-align: center; }

/* El pie de la cifra: centrado bajo su número y con la palabra que pesa en
   tinta plena. Antes era una línea gris de tres palabras debajo de un número
   enorme, y el desequilibrio se leía como descuido. */
.figs { align-items: start; }
.figs .fig { text-align: center; flex: 1 1 200px; max-inline-size: 260px; }
.figs .fig span { color: var(--muted); text-wrap: pretty; line-height: 1.45; }
/* `strong`, no `b`: `.fig b` es el número. */
.figs .fig span strong { color: var(--ink); font-weight: 600; }
/* ⚠️ Y dentro de un BLOQUE pintado manda la tinta del bloque: las 637 páginas
   SEO llevan sus cifras dentro del bloque de cabecera, y teñirlas de `muted`
   las dejaba a 1,17 sobre el color (lo cazó el medidor). */
.block .figs .fig span { color: var(--on-block); }
.block .figs .fig span strong { color: var(--on-block); }


/* ── Cuadrar la primera impresión (2026-08-13) ───────────────────────────────
   Dos desajustes que se ven en cuanto abres: el CTA no estaba centrado dentro de
   su propia placa —rótulo, badges y letra pequeña arrimados a la izquierda— y
   quedaba a una altura distinta de su vecino de la derecha, el bloque «En la
   app». Ahora el contenido va centrado y las dos columnas empujan su última
   pieza al fondo, así que CTA y pastillas arrancan a la misma altura.

   `margin-top: auto` en vez de una altura fija: el titular puede crecer o
   encoger con el ancho y la alineación se mantiene sola. */
.hero-cta { text-align: center; }
.hero-cta .badges { justify-content: center; }
.hero-cta .cta-kicker { justify-content: center; }
/* Las dos piezas de abajo son HERMANAS de la misma fila del grid, no dos cosas
   metidas cada una al fondo de su columna: es lo único que las cuadra de verdad
   —mismo inicio y mismo alto— sin fijar una altura a mano (Oriol 2026-08-13,
   dos intentos). La fila de arriba (texto | buscador) se estira sola. */
@media (min-width: 940px) {
  .hero { align-items: stretch;
    grid-template-areas: "say find" "cta tags"; grid-template-rows: auto 1fr; }
  .hero-say { grid-area: say; }
  .hero-find { grid-area: find; }
  .hero-cta { grid-area: cta; }
  .hero-tags-box { grid-area: tags; }
  .hero-say, .hero-find, .hero-cta, .hero-tags-box { min-inline-size: 0; }
  /* Sin margen propio: en la fila del grid las dos cajas se estiran igual, y ese
     margen dejaba al CTA 32 px más bajo que su hermano (medido). */
  .hero-cta { margin-top: 0; }
}

/* La caja de las pastillas: misma tarjeta que el CTA para que se lean como un
   par. Sin ella, «En la app» y sus pastillas flotaban sobre el papel al lado de
   una placa con fondo, y ningún ajuste de altura arreglaba eso. */
.hero-tags-box { margin-top: 1.4rem; padding: 1.05rem 1.25rem 1.15rem;
  background: var(--card); border: 1px solid var(--cline);
  border-inline-start: 4px solid var(--block); border-radius: var(--r);
  display: flex; flex-direction: column; justify-content: center; }
@media (min-width: 940px) { .hero-tags-box { margin-top: 0; } }

/* «En la app» pesa: es el rótulo que explica las pastillas, no un pie de foto. */
.hero-tags-h { color: var(--ink); font-weight: 600; font-size: .74rem; }
.hero-tags-h::before { content: ""; display: inline-block; vertical-align: middle;
  inline-size: 7px; block-size: 7px; border-radius: 50%; background: var(--block);
  margin-inline-end: .5rem; translate: 0 -1px; }
