@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');

/* ═══════════════════════════════════════════════════════════════════════
   SKRATCH — DESIGN SYSTEM
   Mobile-first. Único tema (claro/marca). Sin imágenes externas.
   La única URL externa es el @import de Google Fonts de arriba.
   ═══════════════════════════════════════════════════════════════════════ */

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

  /* Marca */
  --deep:#2E0A5E;
  --deep-2:#3A1370;
  --violet:#5B2AE8;
  --violet-soft:#EDE7FE;

  /* Texto y superficies */
  --ink:#16162B;
  --ink-2:#3C3C50;
  --muted:#9B9BA5;
  --line:#EDEDF1;
  --surface:#FFFFFF;
  --track:#F2F2F5;
  --band:#F5F5F7;

  /* Acentos */
  --blue:#1B8FE3;
  --green:#57B947;
  --orange:#F5A11B;
  --red:#FF3B30;
  --cyan:#22D3EE;

  /* Radios y espaciado */
  --r-sheet:24px;
  /* Cuánto se mete la hoja por debajo de su cabecera. Tiene que ser MAYOR
     que --r-sheet: la esquina redondeada se dibuja recortando la hoja, y lo
     que asoma por el hueco es lo que haya detrás. Si la cabecera acaba antes
     de que termine la curva, los últimos píxeles quedan blanco sobre blanco
     y la esquina se ve cortada en recto — como si encima de la hoja
     redondeada hubiera otra cuadrada con las puntas asomando. */
  --sheet-lap:26px;
  --r-card:14px;
  --r-thumb:12px;
  --r-pill:999px;
  --pad:20px;

  /* Extras del sistema (derivados, no sustituyen a los de arriba) */
  --black:#0D0A16;
  --chevron:#C7C7CE;
  --hairline:rgba(255,255,255,.18);
  --sh-1:0 1px 3px rgba(0,0,0,.12);
  --sh-2:0 4px 16px rgba(22,22,43,.10);
  --sh-3:0 8px 24px rgba(22,22,43,.18);
  --tab-h:62px;
  /* No es negro puro a propósito. Debajo de la tabbar, en Android, queda la
     barra de navegación del sistema, que la página no puede pintar: no le
     ceden el inset, así que `--safe-b` vale 0 y ahí no llega el CSS. Con la
     tabbar en #000 contra esa franja se veía la costura entre los dos tonos.
     Medido sobre una captura del móvil: la franja del sistema es #171719.
     Es el tono de un Android en oscuro; si el sistema va en claro, la
     costura vuelve — pero al revés se veía siempre. */
  --tabbar:#171719;
  --safe-b:env(safe-area-inset-bottom, 0px);
  --ease:cubic-bezier(.2,.7,.3,1);
  /* Curva del asentamiento de la hoja. Medida sobre grabaciones de la
     app de referencia a 90fps: de cinco candidatas es la que menos se
     desvía de las trazas reales (2,9 % de error medio, frente al 5,0 %
     de --ease). La duración NO es fija: la pone el JS. */
  --ease-settle:cubic-bezier(.2,0,0,1);
}

/* ── Reset ───────────────────────────────────────────────────────────── */
*,*::before,*::after{box-sizing:border-box}

html{-webkit-text-size-adjust:100%}

html,body{
  margin:0;
  padding:0;
  overflow-x:hidden;
  overscroll-behavior-y:none;
}

body{
  background:var(--deep);
  color:var(--ink);
  font-family:'Plus Jakarta Sans', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size:16px;
  line-height:1.35;
  font-weight:500;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}

h1,h2,h3,h4,p,figure{margin:0}
ul,ol{margin:0;padding:0;list-style:none}

button{
  font:inherit;
  color:inherit;
  border:0;
  margin:0;
  background:none;
  cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}

a{color:inherit;text-decoration:none;-webkit-tap-highlight-color:transparent}

/* Foco de teclado. El sistema no lo tenía y el navegador dibujaba un anillo
   negro de 1px que sobre la tabbar negra y el header morado no se ve.
   Violeta sobre claro (8.3:1 con el blanco) y blanco sobre los fondos
   oscuros de marca. Sólo :focus-visible: el ratón no lo dispara. */
:focus-visible{
  outline:3px solid var(--violet);
  outline-offset:2px;
}
.hdr :focus-visible,
.tabbar :focus-visible,
.hero :focus-visible,
.modal__back:focus-visible{
  outline:3px solid #fff;
  outline-offset:2px;
}
/* El buscador es un pill blanco: el anillo va al pill, no al input de dentro */
.searchbar:has(:focus-visible){
  outline:3px solid var(--violet);
  outline-offset:2px;
}
.searchbar input:focus-visible{outline:none}
/* Los que recortan por dentro necesitan el anillo hacia adentro */
.tile:focus-visible,
.hcard:focus-visible,
.hcard > :focus-visible{outline-offset:-3px}

input,textarea,select{font:inherit;color:inherit}

svg{display:block;flex:none}

img{max-width:100%;display:block}

/* Los SVG del catálogo heredan el color del contenedor. Los de trazo van
   con fill:none; los rellenos declaran fill="currentColor" y no llevan
   stroke, así que no entran en esta regla. */
svg[stroke="currentColor"],
svg.icon{stroke:currentColor;fill:none}
svg[fill="currentColor"]{fill:currentColor;stroke:none}

/* El JUEGO DE TRAZO por defecto, para los iconos que llegan del sprite
   (`<svg><use href="#i-…"/></svg>`, ver src/sprite.html). El `<symbol>`
   lleva sólo id, viewBox y geometría, así que estos cinco valores tienen que
   venir de aquí. Un `<svg>` sin atributo `fill` es, por definición, uno del
   juego de trazo: el de relleno lo pide con `fill="currentColor"` y cae en la
   regla de arriba.

   Y TIENE QUE ESTAR AQUÍ ARRIBA, delante de las reglas de componente. Su
   especificidad es 0,1,1 —la misma que `.gcard__check svg{stroke-width:3.6}`,
   `.chip svg{2.6}`, `.tile__icon svg{2.1}`, `.modal__back svg{2.6}`…—, así
   que a igualdad manda el ORDEN: puesta abajo les ganaría a todas y el tick
   de la parrilla adelgazaría de 3,6 a 2. Medido: 231 píxeles de diferencia.
   Por lo mismo hay dos reglas más abajo que llevan el prefijo `svg` para
   subir a 0,1,1 y no perder contra esto: `svg.season__chevron` y
   `svg.row__chevron`, que si no se quedarían en 0,1,0. */
svg:not([fill]){
  fill:none;
  stroke:currentColor;
  stroke-width:2;
  stroke-linecap:round;
  stroke-linejoin:round;
}

/* ── Shell / Layout ──────────────────────────────────────────────────── */
.app{
  max-width:480px;
  margin:0 auto;
  min-height:100dvh;
  position:relative;
  background:var(--surface);
  overflow:hidden;
  isolation:isolate;
}

.view{display:none}
.view--active{display:block}

.scroll{
  min-height:100dvh;
  overflow-y:auto;
  overflow-x:hidden;
  overscroll-behavior-y:contain;
  -webkit-overflow-scrolling:touch;
  padding-bottom:calc(var(--tab-h) + var(--safe-b) + 18px);
  background:var(--surface);
}

/* Variante para pantallas con cabecera morada: el fondo del scroll es morado
   para que el rebote superior no muestre blanco */
.scroll--deep{background:var(--deep)}

/* ── Posters ─────────────────────────────────────────────────────────────
   El degradado con las iniciales. Es el cartel de TODO lo que no tiene imagen
   —las series, que no tienen de dónde sacarla— y, desde que los libros traen
   portada de Open Library, además el RESPALDO de ésta: la imagen se dibuja
   encima y el degradado es lo que queda si el libro no tiene portada, si la
   petición no llega o si estamos dentro del artifact, cuya CSP corta las
   imágenes externas sin decir nada. Por eso no se quita nunca. */
.poster{
  display:grid;
  place-items:center;
  position:relative;
  width:100%;
  height:100%;
  min-height:100%;
  color:#fff;
  font-size:20px;
  font-weight:800;
  letter-spacing:.01em;
  text-shadow:0 1px 3px rgba(0,0,0,.28);
  background:linear-gradient(145deg,#7C3AED,#DB2777);
  user-select:none;
}
.poster--a{background:linear-gradient(145deg,#7C3AED 0%,#DB2777 100%)}
.poster--b{background:linear-gradient(145deg,#0EA5E9 0%,#22D3EE 100%)}
.poster--c{background:linear-gradient(145deg,#F59E0B 0%,#EF4444 100%)}
.poster--d{background:linear-gradient(145deg,#10B981 0%,#0891B2 100%)}
.poster--e{background:linear-gradient(145deg,#6366F1 0%,#22D3EE 100%)}
.poster--f{background:linear-gradient(145deg,#EC4899 0%,#F59E0B 100%)}
.poster--g{background:linear-gradient(145deg,#8B5CF6 0%,#2563EB 100%)}
.poster--h{background:linear-gradient(145deg,#14B8A6 0%,#84CC16 100%)}
.poster--sm{font-size:15px}
.poster--lg{font-size:28px}

/* La portada de verdad, cuando el libro tiene. Va absoluta y no como celda de
   la rejilla para que TAPE las iniciales sin tener que esconderlas: si la
   imagen no carga, `app.js` la quita del DOM y debajo está el cartel de
   siempre, intacto. `object-fit:cover` porque una portada no es exactamente
   2:3 y estirarla se nota más que recortarle dos milímetros.
   Aquí UNA vez y no en cada pantalla: la pintan Buscar, Estanterías, Leyendo
   y la ficha, y el día que cambie el recorte tiene que cambiar en las cuatro. */
.poster img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

/* ── Header morado ───────────────────────────────────────────────────── */
.hdr{
  background:var(--deep);
  color:#fff;
  padding:12px var(--pad) 30px;
  position:relative;
}

.hdr__top{
  display:flex;
  align-items:center;
  gap:14px;
  min-height:60px;
}

.hdr__avatar{
  width:56px;
  height:56px;
  flex:none;
  border-radius:var(--r-pill);
  background:#fff;
  display:grid;
  place-items:center;
  overflow:hidden;
  color:#B4B4C4;
}
.hdr__avatar svg{width:34px;height:34px;stroke-width:2.2}
.hdr__avatar .poster{border-radius:var(--r-pill)}

.hdr__name{
  display:block;
  font-size:18px;
  font-weight:800;
  color:#fff;
  line-height:1.2;
  letter-spacing:-.01em;
}

.hdr__handle{
  display:block;
  font-size:16px;
  font-weight:500;
  color:rgba(255,255,255,.65);
  line-height:1.25;
  margin-top:1px;
}

.hdr__action{
  margin-left:auto;
  display:inline-flex;
  align-items:center;
  gap:8px;
  height:46px;
  padding:0 20px 0 16px;
  border-radius:var(--r-pill);
  background:var(--black);
  color:#fff;
  font-size:17px;
  font-weight:700;
  transition:transform 120ms var(--ease);
}
.hdr__action svg{width:20px;height:20px}

/* Bloque libre para el mapa del pasaporte */
.hdr__map{
  margin:18px 0 4px;
  aspect-ratio:16/11;
  display:grid;
  place-items:center;
  color:rgba(255,255,255,.28);
}
.hdr__map svg{width:60%;height:auto}

/* Variante clara del header (menú de perfil) */
.hdr--light{
  background:var(--band);
  color:var(--ink);
  padding-top:8px;
}
.hdr--light .hdr__name{color:var(--ink);font-size:26px}
.hdr--light .hdr__handle{color:var(--muted);font-size:20px}
.hdr--light .hdr__avatar{background:#DEDDE6;width:68px;height:68px}
.hdr--light .hdr__avatar svg{color:#FBFBFD;width:44px;height:44px}

/* ── Stats ───────────────────────────────────────────────────────────── */
.stats{
  display:flex;
  align-items:stretch;
  margin-top:20px;
}

.stat{
  flex:1 1 0;
  min-width:0;
  padding:2px 6px;
  text-align:center;
  border-left:1px solid var(--hairline);
}
.stat:first-child{border-left:0}

.stat__num{
  font-size:26px;
  font-weight:800;
  color:#fff;
  line-height:1.15;
  letter-spacing:-.02em;
  display:flex;
  align-items:flex-start;
  justify-content:center;
  gap:3px;
  /* Una cifra no parte: a 360px «9d 4h» caía a dos líneas y desalineaba la
     etiqueta de esa columna con la de las demás. */
  white-space:nowrap;
}
.stat__num svg{width:14px;height:14px;margin-top:2px;opacity:.75}

.stat__label{
  display:block;
  font-size:13px;
  font-weight:500;
  color:rgba(255,255,255,.6);
  margin-top:4px;
  line-height:1.2;
}

/* ── Sheet blanco ────────────────────────────────────────────────────── */
.sheet{
  position:relative;
  z-index:2;
  background:var(--surface);
  border-radius:var(--r-sheet) var(--r-sheet) 0 0;
  padding-top:8px;
  min-height:40vh;
}

/* El solape y el padding de la cabecera son UNA pareja, y por eso los dos
   selectores van emparejados: lo que se ve entre el contenido de la cabecera
   y el borde de la hoja es la RESTA de ambos, y son 12px pase lo que pase
   con --sheet-lap. Una hoja que no venga detrás de una cabecera no tiene
   nada que solapar, así que tampoco lleva el margen negativo. */
.hdr:has(+ .sheet){padding-bottom:calc(12px + var(--sheet-lap))}
.hdr + .sheet{margin-top:calc(var(--sheet-lap) * -1)}

/* Acoplada arriba del todo: sin el radio, la cabecera morada que queda
   detrás del solape de -18px asomaría por las esquinas. */
.is-docked .sheet{border-radius:0}

/* Barra pegajosa de la hoja. El segmented se queda arriba al hacer scroll
   y el contenido pasa por debajo. La barra de filtro NO va aquí: se marcha
   con el scroll, igual que en la app de referencia. */
.sheet__nav{
  position:relative;
  z-index:5;
  flex:none;
  background:var(--surface);
  padding:10px 0;
}

/* ── Hoja de dos posiciones ──────────────────────────────────────────
   La .view (o el .modal) recorta. `.slide` es la capa que se desplaza
   entre reposo y acoplada. Sólo `.sheet__list` scrollea, y sólo cuando
   está acoplada: en reposo el gesto no scrollea nada, mueve la hoja.
   `--dock` lo mide el JS = distancia de la hoja al borde superior.
   ─────────────────────────────────────────────────────────────────── */
.view{position:relative}

.slide{
  position:absolute;
  inset:0;
}

/* Se desplaza LA HOJA, no la capa entera: la cabecera morada se queda
   quieta y la hoja se le pone por encima. Medido sobre las grabaciones de
   la app de referencia: comparando la franja de cabecera que sigue
   descubierta, la diferencia es 0,00 con desplazamiento 0px en todas las
   posiciones de la hoja. Moviendo las dos cosas juntas aparecía un
   parallax que la referencia no tiene. */
.slide > .sheet{
  height:100dvh;
  min-height:0;
  display:flex;
  flex-direction:column;
  transform:translateY(0);
  /* El redondeo viaja con la hoja, no por su cuenta: misma duración y misma
     curva que el desplazamiento. Durante el arrastre lo lleva el JS atado a
     la posición; esto es sólo para el último tramo, el que se anima al
     soltar, y para los cambios que no vienen de un dedo (rueda, cambio de
     pantalla). Con una duración propia de 180ms la esquina llegaba antes o
     después que la hoja y se notaba el desajuste. */
  transition:transform var(--settle,240ms) var(--ease-settle),
             border-radius var(--settle,240ms) var(--ease-settle);
  /* Arrastrar por encima de un texto no debe empezar una selección: se
     comía el gesto justo donde más contenido hay. */
  user-select:none;
  -webkit-user-select:none;
}
.is-docked .slide > .sheet{transform:translateY(calc(var(--dock, 0px) * -1))}

.sheet__list{
  flex:1 1 auto;
  min-height:0;
  overflow:hidden;                 /* en reposo el scroll NO existe */
  /* `none` y no `contain`: además de no encadenar el scroll al fondo, quita
     el rebote propio de la lista. Sin ese rebote el JS ya no necesita un
     `touchmove` no pasivo para frenarlo, y sin ese listener el scroll de la
     lista puede vivir entero en el compositor. */
  overscroll-behavior:none;
  -webkit-overflow-scrolling:touch;
  padding-bottom:calc(var(--tab-h) + var(--safe-b) + 18px);
}
.is-docked .sheet__list{overflow-y:auto}

/* .is-locked NO toca el overflow. Lo hizo, y era la causa de que a veces
   la lista no scrollease: el bloqueo duraba 360ms y congelaba también los
   gestos legítimos. Ahora es sólo una bandera de JS para la rueda. */

/* Quién se queda el gesto táctil. La zona de agarre (todo lo de la hoja que
   no es la lista, más la cabecera) no debe dejar que el navegador paneé:
   sus punteros son nuestros. La lista sí paneá, pero sólo estando acoplada.
   Ojo: no se puede poner touch-action:none en `.slide`, porque el valor
   efectivo es la intersección con los ancestros y anularía el pan-y. */
.slide > .sheet > *:not(.sheet__list){touch-action:none}

/* Y nada de la hoja que no sea la lista debe encoger. `.sheet` es un flex
   column de 100dvh y el reparto del encogimiento va ponderado por el tamaño
   base de cada hijo, así que el asa de 5px perdía su parte proporcional: se
   veía de 5px, 4,7 o 2,6 según lo largo que fuera el panel abierto. La que
   tiene que absorber es la lista, que para eso lleva `min-height:0` y su
   propio scroll. */
.slide > .sheet > *:not(.sheet__list){flex:none}

.sheet__list{touch-action:none}
.is-docked .sheet__list{touch-action:pan-y}

@media (prefers-reduced-motion: reduce){
  .slide > .sheet{transition:none}
}

.sheet__handle{
  width:40px;
  height:5px;
  border-radius:var(--r-pill);
  background:#DDDDE3;
  margin:6px auto 12px;
}

/* ── Segmented control ───────────────────────────────────────────────── */
.seg{
  position:relative;
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:0;
  background:var(--track);
  border-radius:14px;
  /* Medido en la captura original: track 50,6dp, pastilla 46,8dp, o sea
     1,9dp de margen. Con 4px la pastilla se veía pequeña dentro del gris. */
  padding:2px;
  margin:6px var(--pad) 0;
}

/* La pastilla es UN elemento que se desplaza, no el fondo de cada botón:
   así el cambio es un movimiento horizontal y no un apagado y encendido.
   La coloca el JS midiendo el botón activo. */
.seg__thumb{
  position:absolute;
  top:0;
  left:0;
  border-radius:12px;
  background:var(--surface);
  box-shadow:var(--sh-1);
  transition:transform 240ms var(--ease-settle),
             width     240ms var(--ease-settle);
  pointer-events:none;
  z-index:0;
}
.seg__thumb--quieta{transition:none}
.seg--2{grid-template-columns:repeat(2,1fr)}
.seg--4{grid-template-columns:repeat(4,1fr)}

.seg__btn{
  position:relative;
  display:flex;
  align-items:center;
  justify-content:center;
  gap:5px;
  height:47px;
  min-width:0;
  padding:0 2px;
  z-index:1;
  border-radius:12px;
  font-size:14px;
  white-space:nowrap;
  font-weight:700;
  letter-spacing:-.01em;
  color:var(--muted);
  background:transparent;
  transition:background-color 140ms var(--ease), color 140ms var(--ease), transform 120ms var(--ease);
}
.seg__btn svg{width:18px;height:18px;color:currentColor}
/* 2 opciones: cabe la escala grande del diseño original */
.seg--2 .seg__btn{font-size:16px;gap:9px;padding:0 6px}
.seg--2 .seg__btn svg{width:20px;height:20px}

/* A 360px cada botón mide 104px. Con el icono a 18 y una etiqueta de diez
   caracteres («Pendientes» = 75.5px) sólo quedan 2.7px por lado y el divisor
   del vecino se pega a la última letra. Se recupera aire encogiendo el icono
   y el hueco, no la tipografía. */
@media (max-width:380px){
  .seg__btn{gap:3px;padding:0;letter-spacing:-.02em}
  .seg__btn svg{width:16px;height:16px}
}

/* Divisor fino sólo entre dos botones inactivos contiguos */
.seg__btn:not(.is-active) + .seg__btn:not(.is-active)::before{
  content:"";
  position:absolute;
  left:0;
  top:10px;
  bottom:10px;
  width:1px;
  background:#DEDEE4;
}

/* El fondo y la sombra los pone .seg__thumb; aquí sólo el color del texto. */
.seg__btn.is-active{color:var(--ink)}

/* Acentos de icono cuando el segmento está activo */
.seg__btn.is-active svg{color:var(--ink)}
.seg__btn--orange.is-active svg{color:var(--orange)}
.seg__btn--green.is-active svg{color:var(--green)}
.seg__btn--violet.is-active svg{color:var(--violet)}

/* ── Barra de filtro ─────────────────────────────────────────────────── */
.filterbar{
  display:flex;
  align-items:center;
  gap:10px;
  padding:16px var(--pad);
  border-bottom:1px solid var(--line);
  /* La lista empieza DESPUÉS de un hueco, no pegada al divisor. Sin esto la
     primera fila de carteles toca la línea y el bloque de arriba —pastilla
     gris + barra de filtro— se lee como si formara parte de la parrilla.
     Va aquí y no en cada biblioteca porque el patrón es el mismo en las dos:
     filtro y luego lista. */
  margin-bottom:14px;
  color:var(--ink);
}
.filterbar > svg{width:22px;height:22px;color:var(--ink);flex:none}

.filterbar__label,
.filterbar > span:not(.filterbar__count){
  font-size:17px;
  font-weight:700;
  letter-spacing:-.01em;
}

.filterbar__count{
  font-size:17px;
  font-weight:700;
  color:var(--ink-2);
}

.filterbar__action{
  margin-left:auto;
  display:inline-flex;
  align-items:center;
  color:var(--ink);
  transition:transform 120ms var(--ease);
}
.filterbar__action svg{width:24px;height:24px}

/* ── Chips y bandas ──────────────────────────────────────────────────── */
.chip{
  display:inline-flex;
  align-items:center;
  gap:3px;
  height:34px;
  padding:0 14px;
  border-radius:var(--r-pill);
  background:var(--ink);
  color:#fff;
  font-size:15px;
  font-weight:700;
  letter-spacing:-.01em;
  white-space:nowrap;
}
.chip svg{width:16px;height:16px;stroke-width:2.6;margin-right:-4px}
.chip--blue{background:var(--blue);color:#fff}
.chip--dark{background:#000;color:#fff;padding:0 18px;height:38px;font-size:17px;font-weight:800}
.chip--soft{background:var(--violet-soft);color:var(--violet)}
.chip--green{background:var(--green);color:#fff}
.chip--orange{background:var(--orange);color:#fff}

.band{
  background:var(--band);
  border-radius:12px;
  padding:14px 18px;
  font-size:15px;
  font-weight:700;
  color:var(--ink-2);
  letter-spacing:-.01em;
}

/* Banda de separación entre grupos de filas (imagen 1) */
.band--gap{
  border-radius:0;
  height:34px;
  padding:0;
  margin:0;
}

/* Fila de cabecera de grupo: chip a la izquierda, contador a la derecha */
.chiprow{
  display:flex;
  align-items:center;
  gap:12px;
  padding:18px var(--pad) 12px;
}
.chiprow__count{
  margin-left:auto;
  font-size:17px;
  font-weight:700;
  color:var(--ink);
}

/* ── Filas de lista ──────────────────────────────────────────────────── */
.row{
  display:flex;
  align-items:center;
  gap:16px;
  padding:16px var(--pad);
  min-height:76px;
  border-bottom:1px solid var(--line);
  background:var(--surface);
  width:100%;
  text-align:left;
  transition:transform 120ms var(--ease), background-color 120ms var(--ease);
}
.row--last{border-bottom:0}
.row--tight{min-height:60px;padding-top:10px;padding-bottom:10px}
.row--tall{min-height:88px;padding-top:20px;padding-bottom:20px}

/* Sin icono: sólo texto + chevron */
.row--plain{gap:12px;min-height:70px}

.row__icon{
  width:48px;
  height:48px;
  flex:none;
  border-radius:var(--r-pill);
  background:var(--violet);
  display:grid;
  place-items:center;
  color:#fff;
}
.row__icon svg{width:24px;height:24px}
.row__icon--ghost{background:var(--track);color:var(--ink)}
.row__icon--soft{background:var(--violet-soft);color:var(--violet)}

.row__thumb{
  width:52px;
  height:52px;
  flex:none;
  border-radius:var(--r-thumb);
  overflow:hidden;
  background:var(--track);
  display:grid;
  place-items:center;
  color:var(--ink);
  box-shadow:0 1px 3px rgba(22,22,43,.10);
}
.row__thumb svg{width:26px;height:26px}
/* Bandera: rectángulo 40x28 con esquinas suaves (imagen 4) */
.row__thumb--flag{
  width:40px;
  height:28px;
  border-radius:5px;
  box-shadow:0 0 0 1px rgba(22,22,43,.08);
}

.row__poster{
  width:40px;
  height:58px;
  flex:none;
  border-radius:8px;
  overflow:hidden;
  /* sin background: el degradado lo pone .poster, que va antes en el archivo */
  box-shadow:0 1px 4px rgba(22,22,43,.14);
}

.row__body{flex:1 1 auto;min-width:0}

.row__title{
  display:block;
  font-size:19px;
  font-weight:600;
  color:var(--ink);
  letter-spacing:-.015em;
  line-height:1.25;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

.row__sub{
  display:block;
  font-size:15px;
  font-weight:500;
  color:var(--muted);
  margin-top:3px;
  line-height:1.3;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

/* Fila con progreso: la anatomía de `.row` es [medio] + body + [meta], y ahí
   no hay hueco para una barra. Ésta va al pie del body, a lo ancho del texto. */
.row--progress .row__body{display:flex;flex-direction:column;gap:8px}
.row--progress .progress{width:100%}

/* Línea superior pequeña dentro del body (p.ej. "3 destinations") */
.row__kicker{
  display:flex;
  align-items:center;
  gap:8px;
  font-size:15px;
  font-weight:700;
  color:var(--muted);
  margin-bottom:2px;
}

.row__meta{
  margin-left:auto;
  display:flex;
  align-items:center;
  gap:12px;
  flex:none;
  font-size:16px;
  font-weight:700;
  color:var(--ink-2);
}

/* La clase va sobre el `<svg>` o sobre un `<span>` que lo envuelve, así que
   la caja y el color se piden sin prefijo y el grosor CON él: sobre el `<svg>`
   un 0,1,0 perdería contra `svg:not([fill])` (0,1,1) y el chevron pasaría de
   2,6 a 2. Sobre el `<span>` el grosor no le llega igual —el `<svg>` de dentro
   tiene su propia declaración, y una declaración gana a lo heredado—, que es
   lo que hace que los nueve chevrones de menú y perfil vayan a 2. */
.row__chevron{
  width:20px;
  height:20px;
  flex:none;
  color:var(--chevron);
}
svg.row__chevron{stroke-width:2.6}

.row__badge{
  width:22px;
  height:22px;
  flex:none;
  border-radius:var(--r-pill);
  background:var(--red);
  color:#fff;
  display:grid;
  place-items:center;
  font-size:14px;
  font-weight:800;
  line-height:1;
}

.row__action{
  flex:none;
  color:var(--muted);
  display:inline-flex;
  transition:transform 120ms var(--ease);
}
.row__action svg{width:24px;height:24px}

/* ── Parrilla de carteles ────────────────────────────────────────────── */
/* La alternativa a la lista cuando lo que importa es abarcar la colección de
   un vistazo: el cartel ocupa la tarjeta entera y el estado se lee en la
   barra del borde inferior, sin tener que leer ni una palabra.
   Tres columnas: a 360px salen carteles de 103px, el mínimo en el que el
   rótulo aún entra en dos líneas. La calle es de 6px, la misma del mosaico
   del header, para que las dos rejillas de Biblioteca rimen. */
.grid{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:6px;
  padding:0 var(--pad);
}

/* Más alta que un cartel 2:3 a propósito: la franja de abajo es rótulo y
   barra, así que sin ese alto extra el texto se comería la ilustración.
   La tarjeta NO es el botón: lleva uno dentro que la cubre entera. Si fuese
   ella el <button>, el de «visto» quedaría anidado dentro de otro botón,
   que es HTML inválido y en móvil se traga uno de los dos toques. */
.gcard{
  position:relative;
  width:100%;
  /* La proporción es 5/9 para un cartel de serie —más alta que un 2:3 porque
     la franja de abajo es rótulo y barra—, pero sale a custom property porque
     una PORTADA DE LIBRO es 2:3 y le sobraría un 17 % de alto. Se cambia con
     el modificador, NUNCA aquí: este 5/9 está citado por su nombre en la
     calibración del arrastre de app.js. */
  --gcard-ratio:5/9;
  aspect-ratio:var(--gcard-ratio);
  /* 8px, el radio de cartel del sistema (`.row__poster`), no los 12 de
     `.row__thumb`: a 103px de ancho un radio de 12 se come la esquina del
     cartel y la parrilla empieza a leerse como una fila de pastillas. */
  border-radius:8px;
  overflow:hidden;
  background:var(--track);
  transition:transform 120ms var(--ease);
}

/* Portada de libro: 2:3 más la franja de rótulo y barra. */
.gcard--libro{--gcard-ratio:10/17}

.gcard__poster{position:absolute;inset:0}

/* Velo de legibilidad. El cartel es un degradado saturado y el blanco se
   pierde encima; con la sombra de texto sola no basta a estos tamaños. */
.gcard__veil{
  position:absolute;
  inset:0;
  z-index:1;
  background:linear-gradient(to top,
    rgba(13,10,22,.90) 0%,
    rgba(13,10,22,.45) 30%,
    rgba(13,10,22,0)   58%);
  pointer-events:none;
}

/* El botón cubre la tarjeta entera y además coloca el rótulo abajo: así toda
   la superficie abre la ficha y el texto va dentro del enlace, sin más aria.
   13px abajo = los 7 de la barra + 6 de aire, para que el rótulo no la toque. */
.gcard__open{
  position:absolute;
  inset:0;
  z-index:2;
  display:flex;
  flex-direction:column;
  justify-content:flex-end;
  padding:0 8px 13px;
  text-align:left;
}

.gcard__title{
  display:-webkit-box;
  -webkit-box-orient:vertical;
  -webkit-line-clamp:2;
  line-clamp:2;
  overflow:hidden;
  font-size:13px;
  font-weight:800;
  line-height:1.15;
  letter-spacing:-.015em;
  color:#fff;
  text-shadow:0 1px 3px rgba(0,0,0,.85);
}

/* Por dónde vas. Es el dato que la lista daba en el `.row__sub` y lo único
   que no se puede deducir mirando la barra. */
.gcard__meta{
  display:block;
  margin-top:3px;
  font-size:11px;
  font-weight:700;
  color:rgba(255,255,255,.78);
  text-shadow:0 1px 3px rgba(0,0,0,.85);
}

/* A 360px la tarjeta baja a 103px y «T1E8 · faltan 20» se recortaba: a 11px
   pide 94 y sólo quedan 87. Se recupera encogiendo el dato secundario y el
   sangrado, nunca el rótulo — es el mismo remedio que el `.seg__btn` a este
   ancho. El `.trunc` del markup sigue de red por si el dato viene largo. */
@media (max-width:380px){
  .gcard__open{padding:0 6px 13px}
  .gcard__meta{font-size:10px;letter-spacing:-.02em}
}

/* Insignia de esquina: valoración, nº de temporadas… dos o tres caracteres */
.gcard__badge{
  position:absolute;
  left:6px;
  top:6px;
  z-index:4;
  pointer-events:none;
  display:inline-flex;
  align-items:center;
  gap:3px;
  padding:3px 7px;
  border-radius:var(--r-pill);
  background:rgba(13,10,22,.62);
  color:#fff;
  font-size:11px;
  font-weight:800;
  line-height:1;
}
.gcard__badge svg{width:11px;height:11px}
.gcard__badge--rating svg{color:var(--orange)}

/* «Visto el siguiente» sin entrar en la ficha. Va sólo en las tarjetas de
   series empezadas: en las demás no hay siguiente que marcar.
   NO es violeta, y a propósito: en una parrilla de doce salen doce, y el
   sistema reserva `--violet` para LA acción de la pantalla, que aquí es el
   `.fab`. Va del mismo gris translúcido que la insignia — el tick blanco ya
   dice qué hace — y sólo se pinta al marcarlo. */
.gcard__check{
  position:absolute;
  right:6px;
  top:6px;
  z-index:4;         /* por encima de .gcard__open, que cubre la tarjeta */
  display:grid;
  place-items:center;
  width:30px;
  height:30px;
  padding:0;               /* el UA le mete 1px 6px y descentra el tick */
  border-radius:var(--r-pill);
  background:rgba(13,10,22,.62);
  color:#fff;
  transition:background-color 140ms var(--ease), transform 120ms var(--ease);
}
/* El mismo peso de trazo que el tick de `.check` a este diámetro: al lado del
   otro, el de 17/2,6 se veía flaco y parecía otra marca distinta. */
.gcard__check svg{width:19px;height:19px;stroke-width:3.6}
.gcard__check.is-on{background:var(--green)}

/* La barra va a sangre contra el borde inferior y su pista es el MISMO color
   rebajado, no el gris de `--line`: así la barra entera lee como estado y no
   como un riel neutro con algo dentro.
   El rebaje se mezcla con blanco, no con transparente: debajo está la parte
   más oscura del velo y un violeta al 24 % sobre casi negro desaparecía —
   se veía cuánto llevas pero no sobre cuánto.
   `pointer-events:none` porque la barra tapa la franja inferior de
   `.gcard__open` y ahí también hay que poder tocar. */
.gcard .progress{
  position:absolute;
  inset:auto 0 0;
  z-index:3;
  height:7px;
  border-radius:0;
  background:color-mix(in srgb,var(--bar) 40%,rgba(255,255,255,.32));
  pointer-events:none;
}
.gcard .progress__bar{border-radius:0}

/* Cabecera de grupo dentro de la parrilla: nombre a la izquierda y cuántas
   hay a la derecha. Mismo aire que el `.sec__title`, media escala menos. */
.gridhead{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:12px;
  padding:0 var(--pad);
  margin-bottom:10px;
}
.gridhead__title{
  font-size:17px;
  font-weight:800;
  color:var(--ink);
  letter-spacing:-.02em;
}
.gridhead__count{
  font-size:15px;
  font-weight:700;
  color:var(--muted);
}

/* ── Tarjeta promo (borde degradado) ─────────────────────────────────── */
.promo{
  display:flex;
  align-items:center;
  gap:16px;
  width:auto;
  margin:18px var(--pad);
  padding:18px 18px;
  border:2px solid transparent;
  border-radius:16px;
  background:
    linear-gradient(var(--surface),var(--surface)) padding-box,
    linear-gradient(105deg,var(--cyan) 0%,#7A6BF0 48%,#8B2FE8 100%) border-box;
  box-shadow:0 6px 18px rgba(46,10,94,.10);
  text-align:left;
  transition:transform 120ms var(--ease);
}

.promo__icon{
  width:52px;
  height:52px;
  flex:none;
  display:grid;
  place-items:center;
  border:2px solid transparent;
  border-radius:var(--r-pill);
  background:
    linear-gradient(var(--surface),var(--surface)) padding-box,
    linear-gradient(150deg,var(--cyan) 0%,#7A6BF0 55%,#8B2FE8 100%) border-box;
  color:var(--violet);
}
.promo__icon svg{width:26px;height:26px;stroke-width:2.2}

.promo__title{
  display:block;
  font-size:19px;
  font-weight:800;
  color:var(--ink);
  letter-spacing:-.02em;
  line-height:1.2;
}

.promo__sub{
  display:block;
  font-size:15px;
  font-weight:600;
  color:var(--muted);
  margin-top:3px;
  line-height:1.3;
}

/* ── Explore: searchbar + hero ───────────────────────────────────────── */
.hero{
  position:relative;
  width:100%;              /* sin esto el aspect-ratio deriva el ancho de la
                              altura y desborda el viewport */
  aspect-ratio:16/11;
  min-height:300px;
  display:flex;
  flex-direction:column;
  justify-content:flex-end;
  padding:0 var(--pad) 26px;
  overflow:hidden;
  color:#fff;
  background:
    linear-gradient(180deg,rgba(0,0,0,0) 42%,rgba(0,0,0,.45) 100%),
    linear-gradient(180deg,#1E8FD6 0%,#63C1E6 38%,#E8C79A 72%,#C98F63 100%);
}

.hero__title{
  font-size:40px;
  font-weight:800;
  line-height:1.05;
  letter-spacing:-.03em;
  text-shadow:0 2px 12px rgba(0,0,0,.38);
}

.hero__sub{
  font-size:16px;
  font-weight:500;
  line-height:1.35;
  margin-top:8px;
  max-width:34ch;
  text-shadow:0 1px 8px rgba(0,0,0,.42);
}

.dots{
  display:flex;
  align-items:center;
  gap:8px;
  margin-top:18px;
}
.dot{
  width:34px;
  height:3px;
  border-radius:var(--r-pill);
  background:rgba(255,255,255,.42);
  transition:background-color 160ms var(--ease);
}
.dot.is-active{background:#fff}

.searchbar{
  position:absolute;
  top:14px;
  left:var(--pad);
  right:var(--pad);
  z-index:3;
  display:flex;
  align-items:center;
  justify-content:center;
  gap:12px;
  height:54px;
  padding:0 20px;
  border-radius:var(--r-pill);
  background:var(--surface);
  box-shadow:0 6px 20px rgba(13,10,22,.18);
  color:var(--muted);
  font-size:18px;
  font-weight:600;
  letter-spacing:-.01em;
  transition:transform 120ms var(--ease);
}
.searchbar svg{width:24px;height:24px;color:var(--violet);stroke-width:2.4}
.searchbar input{
  flex:1 1 auto;
  min-width:0;
  border:0;
  outline:0;
  background:transparent;
  font-size:18px;
  font-weight:600;
  color:var(--ink);
}
.searchbar input::placeholder{color:var(--muted);font-weight:600}

/* Envoltorio para colocar la searchbar flotando sobre el hero */
.hero-wrap{position:relative}

/* `.searchbar` es absoluta porque nació flotando sobre el hero de Explorar.
   Sin un ancestro posicionado se sube encima de la primera fila de
   resultados: esta variante la devuelve al flujo, para una pantalla de
   búsqueda a secas. */
.searchbar--static{position:static;margin:14px var(--pad);width:auto}

/* ── Explore: secciones, tiles, hcards ───────────────────────────────── */
.sec{padding:26px 0 4px}

.sec__title{
  font-size:22px;
  font-weight:800;
  color:var(--ink);
  letter-spacing:-.025em;
  padding:0 var(--pad);
  margin-bottom:14px;
}

.tiles{
  display:grid;
  grid-template-columns:repeat(2,1fr);
  gap:12px;
  padding:0 var(--pad);
}

.tile{
  position:relative;
  min-height:118px;
  border-radius:12px;
  overflow:hidden;
  padding:16px 16px 14px;
  text-align:left;
  background:linear-gradient(135deg,#CFC1F7 0%,#B9C7F3 100%);
  transition:transform 120ms var(--ease);
}
/* Una columna: tarjeta ancha, para un menú donde cada opción es una fila
   grande. `.tiles` es de dos y tres tarjetas caen en dos filas, dejando un
   hueco del ancho de una tarjeta al lado de la tercera. */
.tiles--1{grid-template-columns:1fr}

.tile--a{background:linear-gradient(135deg,#CFC1F7 0%,#B9C7F3 100%)}
.tile--b{background:linear-gradient(135deg,#C4E5F8 0%,#A2D4F1 100%)}
.tile--c{background:linear-gradient(135deg,#FBD9C4 0%,#F6BFA6 100%)}
.tile--d{background:linear-gradient(135deg,#C8EFD8 0%,#A8E2C6 100%)}

.tile__title{
  display:block;
  font-size:18px;
  font-weight:800;
  color:#0D0A16;
  line-height:1.15;
  letter-spacing:-.02em;
  max-width:8ch;
}

/* Desborda por ABAJO, no por la derecha: con right negativo el .tile lo
   recorta en vertical y el cuadrado pierde sus esquinas redondeadas, que es
   justo lo que se ve en la app de referencia (allí queda ~10px de aire a la
   derecha y sólo se corta contra el borde inferior). */
.tile__icon{
  position:absolute;
  right:10px;
  bottom:-14px;
  width:74px;
  height:74px;
  border-radius:18px;
  background:#000;
  color:#fff;
  display:grid;
  place-items:center;
  transform:rotate(-6deg);
}
.tile__icon svg{width:34px;height:34px;stroke-width:2.1;transform:rotate(6deg)}

.hcards{
  display:flex;
  gap:12px;
  padding:0 var(--pad);
  overflow-x:auto;
  overflow-y:hidden;
  scroll-snap-type:x mandatory;
  scroll-padding-left:var(--pad);
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  -ms-overflow-style:none;
}
.hcards::-webkit-scrollbar{display:none;width:0;height:0}

.hcard{
  flex:none;
  width:150px;
  scroll-snap-align:start;
  text-align:left;
  transition:transform 120ms var(--ease);
  --hc-art:linear-gradient(160deg,#D2900F,#B87A0B);
  --hc-label:#FBF2C0;
}
.hcard--a{--hc-art:linear-gradient(160deg,#D2900F,#B87A0B);--hc-label:#FBF2C0}
.hcard--b{--hc-art:linear-gradient(160deg,#3C93D6,#2470B8);--hc-label:#C4F4CE}
.hcard--c{--hc-art:linear-gradient(160deg,#E9705A,#D14E3C);--hc-label:#F8E8D9}
.hcard--d{--hc-art:linear-gradient(160deg,#7B57E8,#5B2AE8);--hc-label:#E6DDFC}
.hcard--e{--hc-art:linear-gradient(160deg,#12A096,#0B7C74);--hc-label:#D4F2EC}
.hcard--f{--hc-art:linear-gradient(160deg,#EC6AA6,#D93F86);--hc-label:#FBDDEB}

.hcard__art{
  display:block;
  height:118px;
  border-radius:12px 12px 0 0;
  overflow:hidden;
  background:var(--hc-art);
}
.hcard__art .poster{border-radius:0}

.hcard__label{
  min-height:86px;
  border-radius:0 0 12px 12px;
  background:var(--hc-label);
  padding:12px 12px 14px;
  font-size:22px;
  font-weight:800;
  color:#0D0A16;
  line-height:1.08;
  letter-spacing:-.03em;
  display:-webkit-box;
  -webkit-line-clamp:2;
  line-clamp:2;
  -webkit-box-orient:vertical;
  overflow:hidden;
}

.hcard__desc{
  margin-top:10px;
  font-size:15px;
  font-weight:500;
  color:var(--muted);
  line-height:1.32;
  display:-webkit-box;
  -webkit-line-clamp:2;
  line-clamp:2;
  -webkit-box-orient:vertical;
  overflow:hidden;
}

/* ── Mapa / botones flotantes ────────────────────────────────────────── */
.fab{
  width:56px;
  height:56px;
  border-radius:var(--r-pill);
  background:var(--violet);
  color:#fff;
  display:grid;
  place-items:center;
  box-shadow:0 8px 22px rgba(91,42,232,.42);
  transition:transform 120ms var(--ease);
}
.fab svg{width:30px;height:30px;stroke-width:2.6}

.pill-btn{
  display:inline-flex;
  align-items:center;
  gap:12px;
  height:60px;
  padding:0 26px 0 22px;
  border-radius:var(--r-pill);
  background:var(--surface);
  color:var(--ink);
  font-size:20px;
  font-weight:700;
  letter-spacing:-.02em;
  box-shadow:var(--sh-3);
  transition:transform 120ms var(--ease);
}
.pill-btn svg{width:26px;height:26px}

/* Pill pequeño de acción dentro de una tarjeta. NO es un flotante sobre el
   mapa: hereda de .pill-btn, así que hay que quitarle la sombra de 24px,
   que sobre una banda gris parece un botón despegado. */
.pill-btn--sm{
  height:34px;
  padding:0 14px;
  font-size:14px;
  font-weight:700;
  flex:none;
  box-shadow:var(--sh-1);
}

.round-btn{
  width:44px;
  height:44px;
  border-radius:var(--r-pill);
  background:var(--surface);
  color:var(--ink);
  display:grid;
  place-items:center;
  box-shadow:var(--sh-3);
  transition:transform 120ms var(--ease);
}
.round-btn svg{width:24px;height:24px}

/* Lienzo del mapa: placeholder sin imágenes */
.mapcanvas{
  position:relative;
  min-height:calc(100dvh - var(--tab-h) - var(--safe-b));
  background:
    radial-gradient(120% 80% at 20% 12%, rgba(255,255,255,.35), transparent 60%),
    linear-gradient(180deg,#59C0F5 0%,#4FB8F0 55%,#48ADE8 100%);
}
.mapcanvas__ui{
  position:absolute;
  inset:0;
  padding:16px var(--pad) calc(20px + var(--tab-h) + var(--safe-b));
  display:flex;
  flex-direction:column;
  pointer-events:none;
}
.mapcanvas__ui > *{pointer-events:auto}
.mapcanvas__bottom{
  margin-top:auto;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
}

/* ── Tabbar ──────────────────────────────────────────────────────────── */
.tabbar{
  position:fixed;
  left:0;
  right:0;
  bottom:0;
  z-index:40;
  margin-inline:auto;
  width:100%;
  max-width:480px;
  display:grid;
  /* minmax(0,1fr) y no 1fr: `1fr` es `minmax(auto,1fr)`, así que una etiqueta
     más larga que su cuarta parte ENSANCHA su columna y descuadra la barra en
     silencio. Con 0 de mínimo, el peor caso es un recorte, que se ve. */
  grid-template-columns:repeat(4,minmax(0,1fr));
  align-items:center;
  height:calc(var(--tab-h) + var(--safe-b));
  padding-bottom:var(--safe-b);
  background:var(--tabbar);
}

.tab{
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:5px;
  height:var(--tab-h);
  color:#8E8E99;
  transition:color 140ms var(--ease), transform 120ms var(--ease);
}
.tab svg{width:26px;height:26px}
.tab.is-active{color:#fff}

.tab__label{
  white-space:nowrap;
  font-size:11px;
  font-weight:600;
  letter-spacing:.01em;
  line-height:1;
}

/* ── Progreso y check ────────────────────────────────────────────────── */
/* El color de la barra ES el estado de la serie, así que no lo escribe el
   markup: lo pone un modificador en `--bar` y de ahí lo leen el relleno y,
   en la parrilla, la pista. Reparto (el mismo criterio que los acentos del
   segmented, para que pestaña y barra digan lo mismo):
     en curso   → --violet   viendo, leyendo: es la acción del sistema
     al día     → --green    positivo, igual que el corazón de «Want»
     cerrado    → --orange   terminada, leído: ya no debe nada
     parado     → --muted    drop, aparcado; no es una alarma
   Los nombres son de ESTADO y no de series a propósito: un libro terminado no
   puede pedir `.progress--vista`.
   El rojo NO entra aquí: `--red` es de `.row__badge` y sólo de ahí. */
.progress{
  --bar:var(--violet);
  height:6px;
  border-radius:var(--r-pill);
  background:var(--line);
  overflow:hidden;
}
.progress--aldia  {--bar:var(--green)}
.progress--cerrado{--bar:var(--orange)}   /* terminada, leída: ya no debe nada */
.progress--parado {--bar:var(--muted)}    /* drop, abandonado */

.progress__bar{
  display:block;          /* suele ser un <span>: sin esto ignora ancho y alto */
  height:100%;
  width:0;
  border-radius:var(--r-pill);
  background:var(--bar,var(--violet));
  transition:width 260ms var(--ease);
}

/* El tick lo dibuja ::after. Si además llega un <svg> dentro del botón,
   hereda el color del texto (--ink) y se ve una marca oscura incluso sin
   marcar: la casilla vacía parece marcada. El sistema no necesita ese SVG. */
.check svg{display:none}

.check{
  /* UNA medida manda: el diámetro. De ahí salen el tick (medio diámetro de
     alto, un octavo de grosor) y la corrección de centrado, así que un check
     más grande cambia SÓLO `--check` — no el `width`, y desde luego no la
     posición del tick a mano, que es lo que lo dejaba torcido. */
  --check:24px;
  --tick:calc(var(--check) * .5);   /* alto del tick; el palo corto, la mitad */
  /* Grosor: un octavo del diámetro, redondeado HACIA ABAJO a píxeles enteros
     a propósito. Chrome hace eso mismo con cualquier `border-width`
     fraccionario —2,5 pinta 2, y 3,75 pinta 3, también a DPR 3—, así que sin
     redondearlo aquí el valor declarado y el pintado no coinciden: la
     corrección de centrado de abajo se calcularía sobre un grosor que no
     existe y el tick volvería a quedar torcido un cuarto de píxel. */
  --tick-trazo:round(down, calc(var(--check) * .125), 1px);
  position:relative;
  display:grid;
  place-items:center;
  width:var(--check);
  height:var(--check);
  padding:0;               /* el UA le mete 1px 6px y saca el svg de la caja */
  flex:none;
  border:2px solid #D5D5DD;
  border-radius:50%;
  background:transparent;
  transition:background-color 140ms var(--ease), border-color 140ms var(--ease), transform 120ms var(--ease);
}

/* El tick es una caja con SÓLO el borde derecho y el inferior, girada 42°. Su
   tinta está pegada a dos lados, así que NO está centrada en su propia caja:
   poniendo la caja en el centro, el tick se va. Medido antes de esto: 1,5px a
   la izquierda en el círculo de 24 y 2,25 en el de 30 del Calendario — a
   simple vista, torcido, y en todas las pantallas a la vez.
   En horizontal no hay nada que corregir: los extremos de la tinta girada los
   marcan dos esquinas de la caja entera, y ésas sí son simétricas respecto al
   centro. En vertical la tinta cae (ancho − grosor)·sen42°/2 por debajo del
   centro de la caja, y eso es exactamente lo que se le sube a la caja. */
.check::after{
  content:"";
  position:absolute;
  left:50%;
  top:50%;
  width:calc(var(--tick) / 2);
  height:var(--tick);
  border:0 solid #fff;
  border-right-width:var(--tick-trazo);
  border-bottom-width:var(--tick-trazo);
  border-radius:1px;
  transform:translate(-50%, calc(-50% - .3346 * (var(--tick) / 2 - var(--tick-trazo)))) rotate(42deg);
  /* El «pop» al marcar va en `scale`, que es una propiedad aparte: así la
     colocación de arriba se escribe UNA vez y el estado encendido sólo toca
     el tamaño. Se aplica antes que el `transform` y con origen en el centro
     de la caja, que ya está donde tiene que estar, así que no descoloca. */
  scale:.6;
  opacity:0;
  transition:opacity 120ms var(--ease), scale 140ms var(--ease);
}
.check.is-on{
  background:var(--violet);
  border-color:var(--violet);
}
.check.is-on::after{opacity:1;scale:1}

/* ── Episodios y temporadas ──────────────────────────────────────────── */
/* La banda gris de la cabecera de temporada tiene DOS formas y UNA sola
   regla, para que no puedan desviarse una de la otra:

   · `.season`    — la banda ES el botón de plegar. Una línea de título y,
                    si hace falta, `.season__meta` a la derecha.
   · `.seasonbar` — la banda CONTIENE los controles, porque la cabecera lleva
                    además un «Marcar todos»: un `<button>` dentro de otro es
                    HTML inválido y en el móvil se traga uno de los dos
                    toques. Mismo reparto que `.gcard`/`.gcard__open` y
                    `.cal-row`/`.cal-open`; el toque se lo queda
                    `.seasonbar__btn` y ahí va también su `:active`.

   Son DOS NOMBRES y no `.season` + modificador por una razón concreta:
   `mark-season` sube con `closest('.det-season, .season')` buscando el
   contenedor de la temporada —el que tiene los checks dentro—. Si la banda
   llevase la clase `.season`, `closest` se pararía en ella y «Marcar todos»
   no encontraría ni un episodio. El markup no puede decir dos cosas
   distintas con la misma clase. */
.season,
.seasonbar{
  display:flex;
  align-items:center;
  gap:12px;
  width:100%;
  padding:16px var(--pad);
  background:var(--band);
  border-radius:12px;
  font-size:17px;
  font-weight:800;
  color:var(--ink);
  letter-spacing:-.02em;
  text-align:left;
}
/* El plegado de una `.seasonbar`: se queda el ancho que no pide el botón de
   al lado y NO vuelve a vestir el texto —tamaño, peso y tracking los hereda
   de la banda, así que el título de la cabecera no necesita clase—. */
.seasonbar__btn{
  flex:1 1 auto;
  min-width:0;
  display:flex;
  align-items:center;
  gap:12px;
  padding:0;
  /* Estas dos NO son adorno: el navegador le pone a todo `<button>`
     `text-align:center` y `letter-spacing:normal`, y eso gana a lo que
     herede de la banda. Sin ellas el título sale centrado y sin apretar.
     Tamaño, peso y color sí llegan solos, por el `font:inherit` del reset. */
  text-align:left;
  letter-spacing:inherit;
}
/* Lo pulsable de cada forma, que es lo que encoge al tocarlo. */
.season,
.seasonbar__btn{transition:transform 120ms var(--ease)}
.season__meta{
  margin-left:auto;
  font-size:14px;
  font-weight:600;
  color:var(--muted);
}
/* `svg.` y no `.season__chevron` a secas: la clase va SOBRE el `<svg>`, y un
   selector de clase sola (0,1,0) pierde contra `svg:not([fill])` (0,1,1) de
   arriba del fichero, que le pondría el grosor 2 del sistema en vez de 2,6.
   Con el prefijo empatan y manda el orden, que aquí es el correcto. */
svg.season__chevron{
  width:20px;
  height:20px;
  flex:none;
  color:var(--ink-2);
  stroke-width:2.6;
  transition:transform 200ms var(--ease);
}
/* Dos formas de decir «abierta», porque el estado vive en sitios distintos:
   la banda-botón lo lleva en la clase y el plegado de la ficha lo lleva en
   `aria-expanded`, que es lo que alterna `toggle-season` (contrato `data-*`).
   Mejor seguir al atributo que pedirle al JS una clase que duplique lo que
   ya dice el ARIA. */
.season.is-open .season__chevron,
[aria-expanded="true"] > .season__chevron{transform:rotate(180deg)}

.ep{
  display:flex;
  align-items:center;
  gap:14px;
  padding:14px var(--pad);
  border-bottom:1px solid var(--line);
  transition:transform 120ms var(--ease), background-color 120ms var(--ease);
}
.ep:last-child{border-bottom:0}

.ep__num{
  flex:none;
  min-width:34px;
  font-family:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size:15px;
  font-weight:600;
  font-variant-numeric:tabular-nums;
  color:var(--muted);
  letter-spacing:-.02em;
}

.ep__body{flex:1 1 auto;min-width:0}

.ep__title{
  display:block;
  font-size:16px;
  font-weight:600;
  color:var(--ink);
  letter-spacing:-.01em;
  line-height:1.3;
}

.ep__sub{
  display:block;
  font-size:13px;
  font-weight:500;
  color:var(--muted);
  margin-top:2px;
  line-height:1.3;
}

/* ── Botones ─────────────────────────────────────────────────────────── */
.btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:10px;
  width:100%;
  height:52px;
  padding:0 22px;
  border-radius:var(--r-pill);
  font-size:17px;
  font-weight:700;
  letter-spacing:-.015em;
  background:var(--track);
  color:var(--ink);
  transition:transform 120ms var(--ease), filter 120ms var(--ease);
}
.btn svg{width:20px;height:20px}
.btn--violet{background:var(--violet);color:#fff}
.btn--dark{background:#000;color:#fff}
.btn--ghost{background:var(--track);color:var(--ink)}
.btn--auto{width:auto}

/* ── Modal (hoja a pantalla completa) ────────────────────────────────── */
.modal{
  position:fixed;
  inset:0;
  z-index:60;
  margin-inline:auto;
  width:100%;
  max-width:480px;
  background:var(--surface);
  overflow:hidden;
  transform:translateY(100%);
  visibility:hidden;
  transition:transform 300ms var(--ease), visibility 0s linear 300ms;
}
.modal.is-open{
  transform:translateY(0);
  visibility:visible;
  transition:transform 320ms var(--ease), visibility 0s;
}

.modal__back{
  position:absolute;
  top:16px;
  left:16px;
  z-index:5;
  width:40px;
  height:40px;
  border-radius:var(--r-pill);
  display:grid;
  place-items:center;
  background:rgba(13,10,22,.42);
  color:#fff;
  backdrop-filter:blur(10px);
  -webkit-backdrop-filter:blur(10px);
  transition:transform 120ms var(--ease);
}
.modal__back svg{width:22px;height:22px;stroke-width:2.6}

/* ── Ficha — `.det-*` ────────────────────────────────────────────────
   El cromo común de TODA ficha: la cabecera de hero con cartel, título y
   acciones, y el bloque de progreso que abre la hoja. Lo usan la ficha de
   serie y la de libro, así que vive aquí y no en el `<style>` de una de las
   dos: nació dentro de la de serie y la de libro habría heredado en silencio
   el estilo de otra pantalla, que es la forma más rápida de que tocar una
   rompa la otra.

   Lo que NO está aquí es lo que sólo tiene una: las temporadas y la lista de
   episodios se quedan en la ficha de serie, y los estantes en la de libro. */
.hdr.det-hdr{
  background:
    radial-gradient(110% 80% at 88% 0%, rgba(139,92,246,.55) 0%, rgba(139,92,246,0) 60%),
    linear-gradient(180deg,#3a1a86 0%,#2a1063 55%,#22084f 100%);
}
/* `.modal__back` es absoluto por defecto (pensado para flotar sobre un hero).
   Aquí vive dentro del `.hdr__top`, así que entra en el flujo: si no, se
   superpone al botón de «más opciones» y se ven dos círculos. */
.det-hdr .modal__back{position:static}
.det-more{margin-left:auto;background:rgba(255,255,255,.16);color:#fff}

/* El `.hdr` ya pone --pad a los lados: estos bloques no deben añadir más, o
   el cartel y el CTA quedan 16px más adentro que la hoja. */
.det-head{display:flex;align-items:flex-end;gap:16px;padding:18px 0 4px}
/* Radio 8: es el de TODO cartel del sistema (.row__poster, .gcard). */
.det-poster{width:110px;height:160px;border-radius:8px;flex:none}
.det-headtxt{min-width:0;flex:1;padding-bottom:2px}
.det-title{margin:0 0 4px;font-size:26px;font-weight:800;letter-spacing:-.02em;line-height:1.1;color:#fff}
/* Hereda de `.row__sub`, que trunca a una línea; aquí tiene que poder partir:
   «2022 · Apple TV+ · En emisión» mide 207px y a 360px no cabe. */
.det-sub{color:rgba(255,255,255,.72);margin:0;white-space:normal;overflow:visible;text-overflow:clip}
.det-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}

.det-actions{display:flex;align-items:stretch;gap:10px;padding:18px 0 22px}
.det-mark{flex:1;min-width:0}
.det-fav{width:60px;flex:none;display:grid;place-items:center;padding-inline:0}

/* Progreso, lo primero de la hoja en las dos fichas. */
.det-top{padding-top:6px;padding-bottom:14px}
.det-progline{display:flex;align-items:baseline;justify-content:space-between;gap:12px;font-size:15px;font-weight:700}
.det-progpct{color:var(--violet);font-variant-numeric:tabular-nums}

.det-synopsis{font-size:16px;font-weight:500;line-height:1.55;margin:14px 0 18px}
/* El hueco del final: la tabbar tapa la franja inferior de la hoja. */
.det-foot{padding-top:22px;padding-bottom:110px}

/* ── Utilidades ──────────────────────────────────────────────────────── */

/* Un <button> que tiene que verse como lo que lleva dentro y no como un
   botón: una fila de ajustes, la cabecera del menú. Va JUNTO a la clase que
   maqueta (`.row`, `.hdr__top`) y sólo la deja pasar.
   Aquí no se repite lo que ya hace el reset de `button` de arriba —tipo,
   color, borde, fondo, cursor—: sólo lo que el navegador sigue imponiendo a
   un botón que hace de fila, que es el ancho encogido, el centrado del texto
   y el estilo nativo del control. */
.btn-reset{
  width:100%;
  box-sizing:border-box;
  text-align:left;
  appearance:none;
  -webkit-appearance:none;
}

.stack-8{display:flex;flex-direction:column;gap:8px}
.stack-12{display:flex;flex-direction:column;gap:12px}
.stack-16{display:flex;flex-direction:column;gap:16px}

.px{padding-left:var(--pad);padding-right:var(--pad)}

.muted{color:var(--muted)}

.trunc{
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  min-width:0;
}

.sr-only{
  position:absolute;
  width:1px;
  height:1px;
  padding:0;
  margin:-1px;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  white-space:nowrap;
  border:0;
}

/* Debe ir al final de las utilidades: gana a cualquier display de clase */
[hidden]{display:none}

/* ── Micro-interacciones ─────────────────────────────────────────────── */
.row:active,
.row--plain:active,
.tab:active,
.btn:active,
.chip:active,
.fab:active,
.tile:active,
.hcard:active,
.gcard:active,
.gcard__check:active,
.promo:active,
.pill-btn:active,
.round-btn:active,
.seg__btn:active,
.season:active,
.seasonbar__btn:active,
.ep:active,
.check:active,
.searchbar:active,
.hdr__action:active,
.modal__back:active,
.row__action:active,
.filterbar__action:active{
  transform:scale(.97);
}

/* ── Transiciones de pantalla ─────────────────────────────────────────
   EMPUJE lateral, y el atrás es EL MISMO EMPUJE DEL REVÉS. Una pantalla se
   apila encima entrando entera por la derecha; la de debajo se aparta un
   poco y se apaga a medias. Ese desajuste —100 % contra 24 %— es lo que las
   hace leer como una ENCIMA de otra y no como dos que se cruzan.

   Ida y vuelta son la misma película pasada al derecho y al revés:
   `vista-llega` es `vista-va` invertida y `vista-tapada` es `vista-vuelve`
   invertida. Por eso son cuatro fotogramas clave de dos movimientos, y no
   dos animaciones que se parecen.

   El recorrido de arriba es del 100 % a propósito. Con menos, la que llega
   aparece de golpe cubriendo ya casi toda la pantalla y sólo se coloca los
   últimos píxeles: eso es un salto con un deslizamiento detrás. Y al revés
   es peor —la que se va terminaría tapando tres cuartos de la pantalla y
   desaparecería de golpe—, así que un empuje reversible obliga a que la de
   arriba haga el viaje entero.

   Son animaciones y no transiciones a propósito: se limpian solas en
   `animationend` y no dejan estilo en línea que luego haya que quitar. */

/* La que se marcha ha dejado de ser la activa, así que sin esto ya estaría
   en `display:none` antes del primer fotograma. */
.view--fuera{
  display:block;
  position:absolute;
  inset:0;
  height:100dvh;
  overflow:hidden;
  pointer-events:none;      /* mientras se va, no se toca */
}

/* Encima va siempre la pantalla que se APILA o se DESAPILA: al entrar, la que
   llega; al volver, la que se marcha destapando lo de debajo. Las dos son
   elementos posicionados (`.view{position:relative}`), así que manda el
   z-index y no el orden en el HTML. */
@keyframes vista-llega {from{transform:translateX(100%)}                to{transform:none}}
@keyframes vista-va    {from{transform:none}                            to{transform:translateX(100%)}}
@keyframes vista-tapada{from{transform:none;opacity:1}                  to{transform:translateX(-24%);opacity:.5}}
@keyframes vista-vuelve{from{transform:translateX(-24%);opacity:.5}     to{transform:none;opacity:1}}
.view--llega {animation:vista-llega  280ms var(--ease-settle) both;z-index:2}
.view--va    {animation:vista-va     280ms var(--ease-settle) both;z-index:2}
.view--tapada{animation:vista-tapada 280ms var(--ease-settle) both;z-index:1}
.view--vuelve{animation:vista-vuelve 280ms var(--ease-settle) both;z-index:1}

/* La barra no existe en la pantalla principal: sube y baja CON la pantalla,
   no aparece y desaparece de golpe. */
@keyframes barra-sube{from{transform:translateY(100%)}to{transform:none}}
@keyframes barra-baja{from{transform:none}to{transform:translateY(100%)}}
.tabbar--entra{animation:barra-sube 280ms var(--ease-settle) both}
.tabbar--va   {animation:barra-baja 280ms var(--ease-settle) both}

/* ── Pantallas sin barra ──────────────────────────────────────────────
   La principal y el perfil: no hay secciones entre las que conmutar y el
   «atrás» de la barra llevaría a la propia pantalla en la que estás.
   La excepción de `--va` no es un parche: al volver al menú la pantalla de
   destino se activa en el primer fotograma, así que sin ella `display:none`
   se lleva la barra de golpe y la bajada no llega a verse nunca. */
.view--sola .scroll{padding-bottom:8px}
.app:has(.view--sola.view--active) .tabbar{display:none}
.app:has(.view--sola.view--active) .tabbar.tabbar--va{display:grid}

/* Durante un arrastre nada debe verse «pulsado»: el dedo está moviendo la
   hoja, no tocando la fila que tiene debajo. Va después del bloque de
   arriba para ganarle a igualdad de especificidad. */
.is-dragging :active{transform:none}

/* `will-change` SÓLO mientras se arrastra. Permanente promovía una capa de
   100dvh que contiene el scroller de la lista, y eso le cuesta caro al
   scroll en el móvil. Es una promesa de cambio inminente, no un ajuste
   que se deje puesto. */
.sheet.is-dragging{will-change:transform}

.row:active,
.ep:active{background-color:#FAFAFC}

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    transition-duration:1ms;
    animation-duration:1ms;
    animation-iteration-count:1;
    scroll-behavior:auto;
  }
  .row:active,
  .tab:active,
  .btn:active,
  .chip:active,
  .fab:active,
  .tile:active,
  .hcard:active,
  .gcard:active,
  .gcard__check:active,
  .promo:active,
  .pill-btn:active,
  .round-btn:active,
  .seg__btn:active,
  .season:active,
  .seasonbar__btn:active,
  .ep:active,
  .check:active,
  .searchbar:active,
  .hdr__action:active,
  .modal__back:active,
  .row__action:active,
  .filterbar__action:active{
    transform:none;
  }
  .modal{transition:none}
}
