/*
Theme Name: GeneratePress Txari
Template: generatepress
Version: 1.0
Description: Child theme de GeneratePress con la identidad Bosque de Txari Eleta.
*/

/* ==========================================================================
   TXARI ELETA — Diseño 2 "Bosque"
   Matérico y visual: bloques grandes verde bosque, fotos a color desaturado
   a sangre, ilustraciones linograbado protagonistas, franjas numeradas
   crema/arena/bosque.
   ========================================================================== */

/* ==========================================================================
   TIPOGRAFÍA — JetBrains Mono, auto-alojada.
   Sustituye a Roboto Mono (decisión de Txari con su ilustradora, jul-2026):
   mayor altura de x y formas más abiertas, se lee mejor en párrafo largo.
   Auto-alojada a propósito: servirla desde el CDN de Google manda la IP de
   cada visitante a un tercero sin consentimiento, y eso es incumplimiento
   de RGPD conocido en la UE.
   ========================================================================== */
@font-face {
  font-family: "JetBrains Mono";
  src: url("assets/tipografia/JetBrainsMono-Light.woff2") format("woff2");
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("assets/tipografia/JetBrainsMono-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("assets/tipografia/JetBrainsMono-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("assets/tipografia/JetBrainsMono-SemiBold.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("assets/tipografia/JetBrainsMono-Italic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}

:root {
  /* Paleta "Naturaleza" — HEX exactos del manual */
  --cream:  #f4f3ea;
  --sand:   #dad3c4;
  --ink:    #231f20;
  --forest: #485a3f;
  --olive:  #6a633b;

  --gutter: clamp(1.25rem, 4vw, 3.5rem);
  /* Alto del hero verde. Bajado de 45vh a 34vh: el hero es la entrada, no el
     contenido — a 45vh se comía media pantalla y empujaba fuera lo que la gente
     viene a ver (fechas, horarios, precios). Un solo sitio que tocar. */
  --hero-alto: 34vh;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  background: var(--cream);
  color: var(--ink);
  font-family: "JetBrains Mono", ui-monospace, "SF Mono", monospace;
  font-weight: 300;
  font-size: 15px;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
}
/* 300 -> 400 solo en móvil: a 375px de ancho quedan
   ~37 caracteres por línea (bajo el mínimo cómodo de 45), y con brillo bajo
   en pantalla táctil, Light a 15px se lee fino. Reversible y barato de
   probar — no toca el peso en escritorio, donde el ancho de columna ya está
   bien resuelto (.lead a 54ch, .articulo-cuerpo a 68ch). */
@media (max-width: 640px) { body { font-weight: 400; } }

::selection { background: var(--olive); color: var(--cream); }

img { max-width: 100%; display: block; }
a { color: var(--olive); text-decoration: none; }
/* 500 -> 600 (subió a 700 en una pasada anterior; corregido después): los
   titulares de módulo no se leían bien — en monoespaciada, 500 lee como
   fino. Pero JetBrains Mono auto-alojada (assets/tipografia/) solo trae
   caras hasta SemiBold — no existe un archivo real de peso 700, así que pedir
   700 caía en 600 de todas formas, en silencio (el navegador elige la cara más
   cercana por debajo). El código pedía un peso que no existía; ahora dice la
   verdad de lo que ya se estaba viendo. Si se quiere Bold real, hace falta
   añadir JetBrainsMono-Bold.woff2 (+ ~94KB) — pendiente de decisión, no
   ejecutado aquí. */
h1, h2, h3 { font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; }

/* Accesible por teclado, invisible hasta que recibe foco — primer elemento
   tabulable de cada página. Verificado contra el HTML real servido (no
   había ninguno): header.php/footer.php sustituyen el header/footer de
   GeneratePress y con él se perdió su skip-link nativo. Apunta a #main
   (id real que GeneratePress pone en <main class="site-main" id="main">,
   confirmado con curl) — no a #content, que no existe en ningún punto del
   HTML servido. */
.skip-link {
  position: absolute;
  top: -3rem;
  left: 1rem;
  background: var(--ink);
  color: var(--cream);
  padding: 0.75rem 1.2rem;
  z-index: 100;
  font-size: 0.85rem;
  transition: top 0.15s ease;
}
.skip-link:focus { top: 1rem; }

.wrap { max-width: 1180px; margin: 0 auto; padding-left: var(--gutter); padding-right: var(--gutter); }

/* Subido de 11px/letter-spacing 0.32em/color olive a esto: con el tamaño y
   el contraste viejos no se leía. Cambio global — afecta a las 8 páginas
   del diseño. */
.eyebrow {
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ink);
  font-weight: 500;
  margin-bottom: 1.6rem;
}
.stripe-forest .eyebrow, .stripe-ink .eyebrow { color: var(--cream); }

.link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 5px;
  text-decoration-thickness: 1px;
}
.link:hover { opacity: 0.7; }

/* ---- fotografía: color cálido desaturado, a sangre --------------------------- */

.photo-warm { filter: saturate(0.72) contrast(1.02) brightness(1.01); }

.bleed { width: 100%; }
.bleed img { width: 100%; height: min(72vh, 640px); object-fit: cover; }

/* ---- cabecera ----------------------------------------------------------------- */

.site-header { background: var(--cream); border-bottom: 1px solid rgba(35,31,32,0.14); position: sticky; top: 0; z-index: 20; }
/* flex-wrap:wrap es necesario para el menú móvil (.site-nav usa
   flex-basis:100% para saltar a su propia fila) — inocuo en desktop, los
   items encogen antes de llegar a envolver. */
.site-header .bar { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; padding-top: 1rem; padding-bottom: 1rem; flex-wrap: wrap; }
.brand img { height: 32px; width: auto; }
.site-nav { display: flex; align-items: center; gap: 1.4rem; flex-wrap: wrap; }
.site-nav a { font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.14em; color: var(--ink); opacity: 0.75; }
.site-nav a:hover { opacity: 1; }
.site-nav a[aria-current="page"] { opacity: 1; color: var(--olive); }
/* Área táctil real 24px+ (medía 17x13px, el peor
   control del sitio — bajo el mínimo de 24px de WCAG 2.2 y muy por debajo de
   los 44px de Apple/Material). El padding no cambia el tamaño visual del
   texto, solo agranda la zona donde el dedo puede acertar. Opacity subida de
   0.6 a 0.72 — a 0.6 el contraste sobre crema daba 4.17:1, bajo el 4.5:1 de
   AA para texto pequeño. */
.lang-switch { font-size: 11.5px; letter-spacing: 0.14em; opacity: 0.72; white-space: nowrap; }
.lang-switch strong { font-weight: 600; }
/* Padding en cada enlace, no en el contenedor: "ES" y "EU" son dos blancos
   táctiles distintos y muy próximos — ampliar solo el contenedor exterior no
   ayuda a acertar el correcto, cada uno necesita su propia zona. */
.lang-switch a, .lang-switch strong, .lang-switch span[aria-current] { display: inline-block; padding: 0.6rem 0.35rem; margin: -0.6rem -0.15rem; }
.lang-switch a:focus-visible, .lang-switch span[aria-current]:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Botón hamburguesa: <button> real controlado por JS (footer.php), no un
   checkbox-hack — reset de los estilos nativos del navegador (borde,
   fondo, padding) porque un <button> los trae y un <label> no. Solo existe
   visualmente por debajo de 840px; en desktop el nav sigue en una fila.
   Área táctil 44x44 (mínimo recomendado Apple HIG / Material Design) — el
   icono visual sigue midiendo 24px, el botón entero es más grande que el
   dibujo para que el dedo no falle. */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
}
.nav-toggle span { display: block; width: 24px; height: 2px; background: var(--ink); border-radius: 1px; transition: transform 0.2s ease, opacity 0.2s ease; }
.nav-toggle:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* Bug real corregido 8-ago-2026: el menú nunca colapsaba en móvil (ni
   siquiera en el prototipo HTML original) — con flex-wrap: wrap las 8
   entradas + selector de idioma se apilaban en 3-4 filas SIEMPRE visibles,
   y al ser .site-header sticky, ese bloque quedaba fijo tapando ~40% de
   la pantalla durante todo el scroll, en todas las páginas. */
@media (max-width: 840px) {
  .nav-toggle { display: flex; order: 2; }
  .lang-switch { order: 3; }
  .site-nav {
    order: 5;
    flex-basis: 100%;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: 0.2rem;
    max-height: 0;
    overflow: hidden;
    /* visibility, no solo max-height: con el menú cerrado, los 8 enlaces
       seguían siendo tabulables y visibles para un lector de pantalla pese a
       no verse en pantalla — max-height:0 con
       overflow:hidden los oculta visualmente pero no los saca del árbol de
       accesibilidad ni del recorrido del tabulador. */
    visibility: hidden;
    margin-top: 0;
    transition: max-height 0.25s ease, margin-top 0.25s ease, visibility 0.25s;
  }
  /* El padding del propio link da el área táctil (~44px), no el gap del
     contenedor — antes dependía solo de gap:1.1rem entre texto de 11.5px,
     con un área real de toque de apenas 16-20px, insuficiente para acertar
     el toque de forma fiable. */
  /* En negrita cuando el menú está desplegado: el peso
     300 heredado del body, pensado para una fila de enlaces pequeños en
     desktop, no se lee bien en una lista vertical a pantalla completa.
     600 = SemiBold, el peso más alto que existe como archivo real en
     assets/tipografia/ — no hay 700 auto-alojado, así que no se fuerza un
     bold sintético que quedaría borroso en esta fuente monoespaciada. */
  .site-nav a { display: block; padding: 0.8rem 0; width: 100%; font-weight: 600; }
}
/* Abre/cierra vía la clase .nav-open (footer.php la togglea en el evento
   "change" del checkbox) y NO con el selector :has() — el menú tardaba
   2-3 toques en abrirse a veces, y :has() dentro de un ancestro
   position:sticky tiene bugs de repintado
   conocidos en Safari: el checkbox cambia de estado pero el navegador no
   siempre re-evalúa el selector hasta un evento posterior (scroll, otro
   toque). El evento "change" de un checkbox no tiene esa ambigüedad. */
@media (max-width: 840px) {
  /* max-height:30rem sigue siendo el techo normal; overflow-y:auto es solo
     una red de seguridad (si el euskera trae etiquetas
     más largas que envuelven, o el usuario sube el tamaño de letra del
     navegador, el contenido podría superar el techo — sin esto, el exceso
     desaparecía en silencio, sin scroll ni aviso). */
  .site-header.nav-open .site-nav { max-height: 30rem; overflow-y: auto; margin-top: 1.2rem; visibility: visible; }
  .site-header.nav-open .nav-toggle span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .site-header.nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
  .site-header.nav-open .nav-toggle span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
}

/* ---- hero en bloque bosque ------------------------------------------------------ */

/* Alto del hero: fijado a --hero-alto para que las 5 páginas que lo usan midan
   exactamente lo mismo (index, mantras, cuencos, retiros, conciertos). Antes el
   alto lo dictaba el contenido y variaba 209px entre páginas. */
.hero-forest { background: var(--forest); color: var(--cream); overflow: hidden; }
.hero-forest .inner {
  position: relative;
  display: grid;
  gap: clamp(2.5rem, 6vw, 5rem);
  padding-top: clamp(1.25rem, 2.5vh, 2rem);
  padding-bottom: clamp(1.25rem, 2.5vh, 2rem);
}
@media (min-width: 920px) {
  .hero-forest .inner {
    grid-template-columns: 1.05fr 0.95fr;
    align-items: center;
    /* En móvil el contenido se apila y no cabe en media pantalla: el alto fijo
       solo aplica de tablet hacia arriba, donde vive la retícula de dos columnas. */
    min-height: var(--hero-alto);
  }
}

/* ---- Jerarquía del hero: cuatro escalones, cada uno claramente distinto -------
   1. marca (eyebrow)  ·  2. titular  ·  3. claim  ·  4. entradilla
   Antes el eyebrow y el claim decían lo mismo ("Yoga, voz y silencio") y la
   diferencia de tamaño entre pasos era demasiado corta para leerse como jerarquía.
   Ratios: titular/claim ≈ 2,8 · claim/entradilla ≈ 1,4 · entradilla/marca ≈ 1,4. */

/* 18ch, no 11ch: con 11 el titular largo de mantras rompía en 5 líneas y
   estiraba el hero por encima del resto. A 18ch todos caen en 2 líneas. */
.hero-forest h1 { font-size: clamp(1.9rem, 3.2vw, 2.9rem); max-width: 18ch; line-height: 1.07; }

/* El claim de marca se compone como en el logotipo: mayúsculas y tracking amplio
   (manual de identidad §1). Solo la portada lo lleva — es la firma de la marca,
   no un titular de sección. */
.hero-forest h1.claim-marca {
  font-size: clamp(2.1rem, 5vw, 3.6rem);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  max-width: 13ch;
  line-height: 1.12;
  font-weight: 500;
}
/* El mínimo del claim es 1.16rem, no 1.02: por debajo de eso el claim y la
   entradilla quedan a 16 y 15px en móvil (ratio 1,07) y la jerarquía se aplana
   justo donde menos sitio hay. Con 1.16/0.92 el escalón se mantiene en 1,26. */
.hero-forest .sub {
  margin-top: 1.1rem;
  font-size: clamp(1.16rem, 1.7vw, 1.3rem);
  letter-spacing: 0.02em;
  opacity: 0.9;
  font-weight: 400;
}
/* 54ch: la entradilla cae en menos líneas y el hero cuadra en pantallas bajas. */
.hero-forest .intro { margin-top: 1.15rem; max-width: 54ch; opacity: 0.82; font-size: 0.92rem; }
.hero-forest .hero-links { margin-top: 1.8rem; }

/* ilustración dentro del bosque: se muestra dentro de una lámina crema */
.plate {
  background: var(--cream);
  padding: clamp(1.5rem, 4vw, 3rem);
  display: grid;
  place-items: center;
}
.plate img { width: 100%; max-width: 420px; height: auto; }

/* Sin lámina: la lámina crema de fondo (el "taco blanco" tras la
   ilustración) sobraba para las ilustraciones ya sustituidas por su versión
   negativa (_B, trazo claro): se ven bien directamente sobre el fondo
   bosque, sin necesitar contraste extra. Solo se aplica página a página,
   donde de verdad existe el negativo — no todas las ilustraciones lo tienen
   todavía. */
.plate.sin-marco { background: transparent; }

/* Ilustraciones sin versión negativa (_B) todavía: LINEAS_03, GONG, PLANTA_03.
   Se invierten por CSS en vez de esperar el asset — son trazo negro puro
   sobre transparente, así que invert(1) da un negativo limpio sin artefactos.
   Válido sobre cualquier fondo oscuro (ink o bosque), no depende de la sección. */
.plate.sin-negativo img { filter: invert(1); }

/* Ilustración más grande dentro del hero: quedaba pequeña.
   --hero-alto NO se toca (bajado a propósito de 45vh a 34vh, ver
   arriba). Primer intento con align-self:stretch + max-height:100% acabó
   agrandando la fila entera (feedback loop de Grid con alturas en %) — se
   revirtió. La fórmula fija de abajo no puede crecer la fila porque no
   depende de lo que la fila mida: son restas fijas sobre --hero-alto, igual
   que antes, solo que restan menos para dejar más aire a la imagen. */
@media (min-width: 920px) {
  .hero-forest .plate { max-height: calc(var(--hero-alto) - 1rem); padding: clamp(1rem, 2vw, 1.75rem); }
  .hero-forest .plate img { max-height: calc(var(--hero-alto) - 3rem); width: auto; object-fit: contain; }
}

.hero-links { margin-top: 2.6rem; display: flex; gap: 1.2rem; flex-wrap: wrap; font-size: 0.92rem; }
.hero-links .dot { opacity: 0.4; }
/* Medía 158.8x16.9px de área táctil — padding en el
   enlace, no en .link global, para no desplazar los muchos otros usos de
   esa clase en el resto del sitio. */
.hero-links a.link { display: inline-block; padding: 0.5rem 0.1rem; margin: -0.5rem -0.1rem; }

/* ---- secciones ------------------------------------------------------------------

   REPLANTEADO: antes cada sección llevaba su color y se alternaba crema/arena.
   Como crema (#f4f3ea) y arena (#dad3c4) son casi el mismo tono, alternarlos no
   separaba nada — solo producía bandas horizontales. Con los bloques fuertes
   (verde, negro) intercalados, la página se leía como una bandera.

   Ahora el color SIGNIFICA algo y por eso escasea:
   · crema (base) — el contenido. La mayoría de las secciones.
   · arena        — un solo bloque por página, para lo que de verdad destaca.
   · verde/negro  — la entrada y el cierre. Nada más.

   Las secciones se separan con AIRE y una línea fina, no cambiando de color.
   -------------------------------------------------------------------------------- */

.stripe { position: relative; background: var(--cream); }
.stripe .inner { padding-top: clamp(3.5rem, 8vh, 5.5rem); padding-bottom: clamp(3.5rem, 8vh, 5.5rem); }

/* Hero montado sobre una sección (yoga, sobre mí): relleno corto. */
.stripe.es-hero .inner { padding-top: clamp(1.6rem, 3.5vh, 2.4rem); padding-bottom: clamp(1.6rem, 3.5vh, 2.4rem); }

/* Dos secciones del mismo color seguidas se separan con una línea fina, que es
   más silenciosa que un cambio de fondo y no rompe la continuidad. */
.stripe + .stripe::before {
  content: "";
  position: absolute; top: 0; left: var(--gutter); right: var(--gutter);
  height: 1px; background: rgba(35,31,32,0.13);
}
.stripe-sand + .stripe::before, .stripe + .stripe-sand::before,
.stripe-forest + .stripe::before, .stripe + .stripe-forest::before,
.stripe-ink + .stripe::before, .stripe + .stripe-ink::before { display: none; }

.stripe-cream { background: var(--cream); }
.stripe-sand  { background: var(--sand); }
.stripe-forest { background: var(--forest); color: var(--cream); }
.stripe-ink { background: var(--ink); color: var(--cream); }

.stripe-num {
  position: absolute;
  top: clamp(1rem, 3vw, 2.2rem);
  right: var(--gutter);
  font-size: clamp(3rem, 9vw, 6.5rem);
  font-weight: 500;
  line-height: 1;
  opacity: 0.12;
  pointer-events: none;
}

/* 30ch, no 18: con 18 un titular de 46 caracteres ("Concierto, sesión de sonido,
   o las dos cosas.") caía en tres líneas y ocupaba un tercio del ancho, con el
   resto de la franja vacío. A 30 los titulares largos caen en dos líneas y el
   bloque llega hacia el borde. En monoespaciada 1ch = 1 carácter exacto, así que
   el número es literal: 30 caracteres por línea. */
.stripe h2 { font-size: clamp(1.9rem, 4.4vw, 3rem); max-width: 30ch; }
.stripe .lead { margin-top: 1.6rem; max-width: 54ch; font-size: 1rem; }
/* Escape puntual del ancho de lectura: una frase corta de datos (60 min +
   dirección) va en una sola línea, no partida por el 54ch. */
.stripe .lead.micro-ancho { max-width: none; }
/* Mismo escape para .note (62ch): en conciertos.html el texto
   estira a lo ancho en vez de partirse en columna estrecha. */
.stripe .note.micro-ancho { max-width: none; }

/* ---- mosaico de puertas (tarjetas grandes con ilustración) -------------------------- */

.doors-grid { display: grid; gap: 1.4rem; margin-top: 3rem; }
@media (min-width: 720px) { .doors-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .doors-grid { grid-template-columns: repeat(3, 1fr); } }

.door-card {
  background: var(--cream);
  color: var(--ink);
  padding: 2.2rem 2rem 2rem;
  display: flex;
  flex-direction: column;
  gap: 1.2rem;
  min-height: 300px;
  transition: transform 0.25s ease;
}
.door-card:hover { transform: translateY(-4px); }
.door-card .ill { height: 130px; display: flex; align-items: center; }
.door-card .ill img { max-height: 130px; width: auto; max-width: 80%; }
.door-card h3 { font-size: 1.35rem; }
.door-card p { font-size: 0.88rem; opacity: 0.72; flex: 1; }
.door-card .go { color: var(--olive); font-size: 0.9rem; }

/* variante de tarjeta sobre fondo crema (páginas interiores) */
.stripe-cream .door-card, .stripe-sand .door-card { background: #fff; background: color-mix(in srgb, var(--cream) 40%, #ffffff); }
.stripe-sand .door-card { background: var(--cream); }

/* ---- manifiesto con figura ----------------------------------------------------------- */

.manifesto-split { display: grid; gap: clamp(2.5rem, 6vw, 5rem); align-items: center; }
@media (min-width: 920px) { .manifesto-split { grid-template-columns: 0.8fr 1.2fr; } }
/* Bug real corregido 8-ago-2026: el hero "01 La práctica" de yoga.html /
   yoga-eu.html (variante "invertida", texto más ancho que la figura) trae
   un style="grid-template-columns:1.2fr 0.8fr" INLINE en el HTML de la
   página — un inline style gana siempre a un @media de la hoja externa, así
   que en móvil nunca se apilaba: texto e ilustración quedaban aplastados
   uno al lado del otro. Con !important recuperamos el control por debajo
   de 920px sin tener que tocar el HTML de la página dentro de WordPress
   (haría falta editarlo dos veces, ES y EU). No afecta a las páginas que no
   llevan ese inline (index, sobre-mí): ahí ya heredaban 1 columna en móvil. */
@media (max-width: 919px) { .manifesto-split { grid-template-columns: 1fr !important; } }
.manifesto-split .figure img { width: 100%; max-width: 360px; height: auto; margin: 0 auto; }
/* Dentro de un hero la ilustración no manda sobre el alto: se acota igual que la
   lámina del hero verde. Sin esto, yoga y sobre mí quedaban mucho más altas que
   el resto solo por el tamaño del dibujo. */
/* 46vh -> 26vh: el hero quedaba demasiado alto — una ilustración no puede
   condicionar todo el diseño, así que se limita su tamaño en vez del hero
   entero. Afecta a yoga.html y sobre-mi.html. */
.es-hero .manifesto-split .figure img { max-height: 26vh; max-width: 230px; width: auto; object-fit: contain; }
.es-hero .split .figure img { max-height: 26vh; max-width: 230px; width: auto; object-fit: contain; }
.manifesto-split blockquote {
  font-size: clamp(1.2rem, 2.8vw, 1.75rem);
  font-weight: 400;
  line-height: 1.55;
  max-width: 30ch;
}

/* ---- formularios sobre bosque/arena ------------------------------------------------------ */

.newsletter-form { margin-top: 2.2rem; display: flex; gap: 0.8rem; flex-wrap: wrap; max-width: 520px; }
.newsletter-form input {
  flex: 1 1 240px;
  background: transparent;
  border: 1px solid currentColor;
  color: inherit;
  padding: 0.9rem 1rem;
  font: inherit;
  font-size: 0.9rem;
}
.newsletter-form input::placeholder { color: inherit; opacity: 0.55; }
.newsletter-form button {
  background: var(--ink);
  color: var(--cream);
  border: 0;
  padding: 0.9rem 1.7rem;
  font: inherit;
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  cursor: pointer;
}
.stripe-forest .newsletter-form button, .stripe-ink .newsletter-form button { background: var(--cream); color: var(--forest); }
.newsletter-form button:hover { opacity: 0.85; }
/* opacity 0.6 -> 0.72: a 0.6 el contraste sobre crema
   daba 4.17:1, bajo el 4.5:1 mínimo AA para texto de 11.5px. */
.micro { font-size: 11.5px; opacity: 0.72; margin-top: 0.9rem; }

/* ---- bloques interiores ---------------------------------------------------------------------- */

.split { display: grid; gap: clamp(2rem, 5vw, 4.5rem); align-items: center; }
@media (min-width: 900px) {
  .split { grid-template-columns: 1fr 1fr; }
  .split.rev > .split-media { order: 2; }
}

/* Tratamiento para una foto suelta que queda demasiado cuadrada:
   un bloque de color desplazado detrás, técnica editorial de toda la vida — le
   da profundidad sin recurrir a esquinas redondeadas (ajenas al resto del
   sistema visual, todo de bloques rectos). Reutilizable en cualquier .split. */
.foto-con-aire { position: relative; }
.foto-con-aire::before {
  content: "";
  position: absolute;
  inset: 16px -16px -16px 16px;
  background: var(--forest);
  z-index: -1;
}
.foto-con-aire img { position: relative; display: block; }

.card-grid { display: grid; gap: 1.4rem; margin-top: 2.6rem; }
@media (min-width: 760px) { .card-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1020px) { .card-grid.cols-3 { grid-template-columns: repeat(3, 1fr); } }

.card { background: var(--cream); padding: 2rem; }
.stripe-cream .card { background: var(--sand); }
.card h3 { font-size: 1.1rem; margin-bottom: 0.7rem; }
.card p { font-size: 0.88rem; opacity: 0.8; }
.card .price { display: block; margin-top: 1.1rem; font-size: 1.45rem; font-weight: 500; }

/* pasos con número grande al margen */
.steps { list-style: none; counter-reset: step; margin-top: 2.6rem; }
.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: clamp(3.5rem, 8vw, 5.5rem) 1fr;
  gap: 1.5rem;
  padding: 1.1rem 0;
  align-items: start;
}
.steps li + li { border-top: 1px solid rgba(35,31,32,0.16); }
.stripe-forest .steps li + li, .stripe-ink .steps li + li { border-top-color: rgba(244,243,234,0.22); }
.steps li::before {
  content: counter(step, decimal-leading-zero);
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  font-weight: 500;
  opacity: 0.25;
  line-height: 1;
  grid-row: 1 / span 2; /* el número ocupa las dos filas: título y párrafo quedan en la col. 2 */
}
.steps li > strong, .steps li > p { grid-column: 2; }
.steps strong { display: block; font-weight: 500; margin-bottom: 0.35rem; }
.steps p { font-size: 0.9rem; opacity: 0.85; }

/* La nota deja de ser una caja de color: el fondo arena sobre crema creaba un
   recuadro gris que pesaba más que el texto. Ahora se marca con una regla arriba
   y aire, y ocupa el ancho de lectura completo. */
.note {
  padding: 2rem 0 0;
  border-top: 2px solid var(--olive);
  max-width: 62ch;
  font-size: 1.08rem;
  line-height: 1.75;
}
.stripe-forest .note, .stripe-ink .note { border-top-color: color-mix(in srgb, var(--cream) 55%, transparent); }


.placeholder {
  border: 1px dashed currentColor;
  opacity: 0.65;
  padding: 1.6rem;
  font-size: 0.85rem;
}

/* ---- cabecera y pie de landing standalone (fichas de retiro/clase/evento) --------------------------
   Para páginas pensadas como destino único de un anuncio de Facebook/Instagram: sin el site-nav
   de 6-7 puertas ni la columna de secciones del pie, para no competir con el contenido concreto
   que trajo a quien llega. Reutiliza tokens y el resto de componentes tal cual. */

.landing-header { background: var(--cream); border-bottom: 1px solid rgba(35,31,32,0.14); }
/* La ficha lleva un enlace de vuelta al listado junto al logo: los dos extremos,
   nunca centrados (así se montaban encima). */
.landing-header .bar { display: flex; justify-content: space-between; align-items: center; gap: 1.5rem; padding-top: 1.4rem; padding-bottom: 1.4rem; }
.landing-header .link { font-size: 0.85rem; }

.landing-footer { background: var(--ink); color: var(--cream); }
.landing-footer .inner { padding-top: 2.6rem; padding-bottom: 2.2rem; text-align: center; }
.landing-footer img { height: 48px; width: auto; margin: 0 auto; }
.landing-footer p { font-size: 0.85rem; opacity: 0.75; margin-top: 1.1rem; }
.landing-footer a { color: var(--cream); opacity: 0.75; }
.landing-footer a:hover { opacity: 1; }
/* .legal se reutiliza tal cual bajo .landing-footer (mismo bg/color heredado) */

/* ---- bloque de conversión (fichas de retiro/clase/evento) ----------------------------
   El dato-decisión (cuándo/dónde/precio/plazas) y el CTA viven DENTRO del hero, no en
   una sección aparte más abajo: quien llega desde un anuncio decide o se informa desde
   el primer scroll, no al final de una historia larga. */

/* ==========================================================================
   FICHA DE PRODUCTO — retiro, clase, evento.
   Esto se lee de noche, en el sofá, por alguien que quiere saber en diez
   segundos si le encaja. La conversión aquí viene de resolver dudas, no de
   empujar: el orden es qué / cuándo / dónde / qué implica / cuánto, y luego
   el detalle. Sin sombras ni degradados: la profundidad es de bloque plano.
   ========================================================================== */

/* --- bloque de decisión: todo lo que hace falta para decir sí o no ---------- */
.decision { background: var(--cream); }
.decision .inner {
  display: grid;
  gap: clamp(2.2rem, 5vw, 4rem);
  padding-top: clamp(2.5rem, 6vh, 4.5rem);
  padding-bottom: clamp(2.5rem, 6vh, 4.5rem);
}
@media (min-width: 940px) { .decision .inner { grid-template-columns: 1.05fr 0.95fr; align-items: start; } }

.decision h1 { font-size: clamp(2.1rem, 4.6vw, 3.4rem); max-width: 16ch; line-height: 1.06; }
.decision .frase { margin-top: 1.3rem; max-width: 46ch; font-size: clamp(1rem, 1.6vw, 1.12rem); line-height: 1.68; }

.decision-media { position: relative; }
.decision-media img { width: 100%; aspect-ratio: 4 / 3.2; object-fit: cover; filter: saturate(0.8) contrast(1.02); }

/* Variante para cuando la ficha no tiene foto propia y usa la misma ilustración
   que su tarjeta en yoga.html: fondo arena + contain, igual que .anuncio-foto.ilustracion. */
.decision-media.ilustracion {
  aspect-ratio: 4 / 3.2;
  background: var(--sand);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2.4rem;
}
.decision-media.ilustracion img {
  width: auto; height: auto; max-width: 72%; max-height: 72%;
  aspect-ratio: auto; object-fit: contain; filter: none;
}

/* Variante para cuando la pieza es un CARTEL y no una foto de ambiente. Un cartel
   se enseña entero: el encuadre 4/3.2 con recorte se comía la franja del título y
   el pie con el lugar, que es justo donde el cartel dice qué es y dónde. Tampoco
   se le baja la saturación: lleva los colores y los logos de la marca. */
.decision-media.cartel img {
  width: auto;
  max-width: 100%;
  max-height: 78vh;
  aspect-ratio: auto;
  object-fit: contain;
  filter: none;
  display: block;
  margin: 0 auto;
}

/* --- los datos que deciden: aire generoso, etiqueta arriba, valor grande ---- */
.datos-clave {
  margin-top: clamp(2rem, 4vw, 2.8rem);
  border-top: 1px solid rgba(35,31,32,0.18);
}
.datos-clave > div {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.35rem;
  padding: 1.35rem 0;
  border-bottom: 1px solid rgba(35,31,32,0.13);
}
@media (min-width: 560px) {
  .datos-clave > div { grid-template-columns: 8.5rem 1fr; gap: 1.8rem; align-items: baseline; }
}
.datos-clave dt {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--olive);
  padding-top: 0.28rem;
}
.datos-clave dd { font-size: 1.06rem; line-height: 1.55; }
/* El apunte nunca pesa más que el dato al que acompaña: resetea peso y tamaño
   aunque el dd venga en negrita (caso del precio). */
.datos-clave dd .apunte { display: block; font-size: 0.84rem; font-weight: 400; line-height: 1.55; opacity: 0.85; margin-top: 0.4rem; }

/* el precio pesa más que un dato normal, pero no más que un título: normal, no negrita */
.datos-clave .precio dd { font-size: clamp(1.5rem, 3vw, 1.9rem); font-weight: 400; line-height: 1.15; }

/* --- llamada a la acción: grande, clara, sin urgencia fabricada ------------- */
.ficha-cta { margin-top: clamp(1.8rem, 3.5vw, 2.4rem); }
.ficha-cta .fila { display: flex; flex-wrap: wrap; gap: 1rem 1.8rem; align-items: center; }
/* La nota de "sin compromiso" es lo que desbloquea a quien duda: pequeña, pero
   legible. No se esconde con opacidad baja. */
.ficha-cta .sin-compromiso {
  display: block; margin-top: 1.2rem;
  font-size: 0.87rem; line-height: 1.6; max-width: 46ch; opacity: 0.8;
}
/* .ficha-cta .btn-primary duplicaba línea por línea el .btn-primary de abajo
   — nunca había una diferencia real entre
   ambos, así que el botón dentro de .ficha-cta ya hereda todo del genérico
   sin necesidad de repetirlo aquí. */

/* Botón de conversión. En verde bosque, no negro: el negro es el color del texto
   de toda la web, así que un botón negro no se lee como botón — se lee como una
   caja de texto más. El verde es el color de bloque de la marca y aquí hace de
   acento de acción. */
.btn-primary {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  background: var(--forest);
  color: var(--cream);
  padding: 1.05rem 2.1rem;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  text-decoration: none;
  border: 0;
  transition: background 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
/* La flecha da la pista de que esto lleva a algún sitio. Se mueve ella al pasar
   por encima, no la caja: animar la caja empujaría el resto de la fila. */
.btn-primary::after { content: "\2192"; transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1); }
.btn-primary:hover { background: var(--ink); }
.btn-primary:hover::after { transform: translateX(4px); }
.btn-primary:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .btn-primary, .btn-primary::after { transition: none; }
}

.hero-forest .btn-primary,
.stripe-forest .btn-primary,
.stripe-ink .btn-primary { background: var(--cream); color: var(--forest); }

/* CTA secundario de refuerzo tras una franja de contenido (mismo botón, más pequeño) */
.cta-repeat { margin-top: 2.4rem; display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.cta-repeat .btn-primary { padding: 0.8rem 1.7rem; font-size: 0.85rem; }

/* ---- footer ------------------------------------------------------------------------------------ */

.site-footer { background: var(--ink); color: var(--cream); }
.site-footer .inner { padding-top: 3.5rem; padding-bottom: 3rem; display: grid; gap: 2.5rem; }
@media (min-width: 800px) { .site-footer .inner { grid-template-columns: 1fr auto; align-items: start; } }
.site-footer img { height: 56px; width: auto; }
.site-footer p { font-size: 0.85rem; opacity: 0.75; margin-top: 1.2rem; }
.site-footer a { color: var(--cream); opacity: 0.75; }
.site-footer a:hover { opacity: 1; }
/* Correo e Instagram medían 228.5x15.6px de alto real — mismo criterio
   que .hero-links, padding local en vez de tocar .link global. */
.site-footer p a.link { display: inline-block; padding: 0.4rem 0.1rem; margin: -0.4rem -0.1rem; }
/* gap reducido (0.55rem -> 0.15rem) porque el padding de cada enlace ya da
   la separación visual — antes de esto los 7 enlaces medían ~23.8px de alto
   sin relleno propio, bajo el mínimo de 24px de WCAG 2.2. */
.footer-links { display: flex; flex-direction: column; gap: 0.15rem; font-size: 0.85rem; }
.footer-links a { display: block; padding: 0.5rem 0; }
.legal { border-top: 1px solid rgba(244,243,234,0.18); padding-top: 1.4rem; padding-bottom: 1.6rem; font-size: 11.5px; opacity: 0.55; display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
/* Los 3 enlaces legales (Aviso legal/Privacidad/Cookies) medían 13.2px de
   alto — son los que la normativa de protección de datos obliga a
   mantener accesibles, y eran de los peores del sitio en área táctil.
   opacity:1 en el enlace es necesario, no cosmético: .legal ya va a 0.55 y
   .site-footer a a 0.75 — sin anularlo aquí, ambos se multiplican
   (0,4125) y el texto queda a 3,63:1, bajo el 4,5:1 mínimo AA. El "©
   Txari Eleta" de al lado no es enlace, así que a él el 0.55 solo sí le
   basta (5,36:1). */
.legal a { display: inline-block; padding: 0.5rem 0.2rem; margin: -0.5rem -0.2rem; opacity: 1; }

/* ==========================================================================
   ANUNCIOS — lo que está disponible ahora mismo. Dos formatos, porque son dos
   decisiones distintas:

   · .anuncios.en-fila (clases) — horizontal. Quien elige clase COMPARA horarios
     y precios entre todas: en fila los lee en columna y los compara de un
     vistazo. El cuándo es el dato que decide, así que va primero y grande.
   · .anuncios.en-tarjeta (retiros) — vertical con foto grande. Un retiro no se
     compara, se elige: manda la imagen y el relato.

   Fotos de ambiente, del archivo de Txari — no del retiro concreto.
   ========================================================================== */
.anuncios { list-style: none; display: grid; gap: clamp(1.4rem, 2.6vw, 2rem); margin-top: 2.6rem; }
.anuncio { background: var(--cream); display: flex; flex-direction: column; overflow: hidden; }
.stripe-cream .anuncio { background: #fbfaf4; }
/* En stripe-sand (yoga.html): #fbfaf4 se leía "en blanco" por apenas
   diferenciarse de --cream — aquí SÍ hay contraste real
   contra el fondo arena, así que --cream de lleno cumple "crema suave, casi
   blanco, pero no del todo". No toca el caso stripe-cream (cuencos.html, ya
   aprobado): ahí el mismo valor se pegaría al fondo y perdería el taco. */
.stripe-sand .anuncio { background: var(--cream); }

/* ---------- formato en fila (clases) ---------------------------------------- */
@media (min-width: 880px) {
  .anuncios.en-fila .anuncio {
    display: grid;
    /* auto -> minmax(0, 11rem) (5-ago-2026): un correo largo sin espacios
       ("info@gzultzamaldea.org") no puede partirse, así que con "auto" la
       rejilla ensanchaba SOLO esa columna para no cortarlo. Con un máximo
       fijo, la columna mide igual que en el resto de tarjetas y el texto que
       no cabe se parte (ver .anuncio-cierre a continuación). */
    grid-template-columns: 14rem 16rem 1fr minmax(0, 11rem);
    align-items: stretch;
    gap: 0;
  }
}
.anuncios.en-fila .anuncio-foto { aspect-ratio: 16 / 10; }
@media (min-width: 880px) { .anuncios.en-fila .anuncio-foto { aspect-ratio: auto; height: 100%; min-height: 12rem; } }

/* EL CUÁNDO: es lo que decide si puedes ir o no. Va en su propia columna, en
   verde bosque y a tamaño de titular — antes iba dentro de un taco negro
   pequeño, en 10px, y era ilegible. */
.anuncio-cuando {
  padding: clamp(1.4rem, 2.4vw, 1.8rem);
  border-bottom: 1px solid rgba(35,31,32,0.12);
  display: flex; flex-direction: column; justify-content: center;
}
@media (min-width: 880px) {
  .anuncio-cuando { border-bottom: 0; border-right: 1px solid rgba(35,31,32,0.12); }
}
.anuncio-dia {
  display: block;
  font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--ink); font-weight: 600; margin-bottom: 0.55rem;
}
.anuncio-hora {
  display: block;
  font-size: clamp(1.2rem, 2.2vw, 1.5rem);
  font-weight: 500;
  color: var(--forest);
  line-height: 1.2;
  white-space: nowrap;
}
.anuncio-hora + .anuncio-hora { margin-top: 0.15rem; }
/* Pares día+hora repetidos: cuando hay más de un día,
   cada uno lleva su propio .anuncio-dia seguido de su .anuncio-hora, en vez
   de un día combinado ("Lunes y miércoles") con las horas apiladas debajo sin
   distinguir cuál es de cuál. Este margen separa un par del siguiente. */
.anuncio-hora + .anuncio-dia { margin-top: 3rem; }
.anuncio-dura { display: block; font-size: 0.8rem; opacity: 0.7; margin-top: 0.6rem; }

/* ---------- cuerpo, común a los dos formatos -------------------------------- */
.anuncio-cuerpo { padding: clamp(1.4rem, 2.6vw, 2rem); display: flex; flex-direction: column; flex: 1; gap: 0.7rem; }
.anuncio-nombre { font-size: clamp(1.2rem, 2.2vw, 1.5rem); font-weight: 500; line-height: 1.2; }
.anuncio-donde { display: block; font-size: 0.87rem; opacity: 0.75; }
/* Reserva 2 líneas aunque el lugar quepa en 1: si no, el gancho de cada
   tarjeta arranca a una altura distinta según lo largo que sea el nombre
   del sitio — sin esto, los textos quedan a diferentes alturas. */
.anuncios.en-tarjeta .anuncio-donde { min-height: 2.5em; }
.anuncio-gancho { font-size: 0.93rem; line-height: 1.6; opacity: 0.88; }
.anuncio-datos {
  display: flex; flex-wrap: wrap; gap: 0.45rem 1.1rem;
  font-size: 0.82rem; opacity: 0.78;
  padding-top: 0.85rem; margin-top: auto;
  border-top: 1px solid rgba(35,31,32,0.12);
}

/* ---------- precio y llamada a la acción, en su columna --------------------- */
.anuncio-cierre {
  padding: clamp(1.4rem, 2.6vw, 2rem);
  display: flex; flex-direction: column; gap: 1.1rem;
  justify-content: center; align-items: flex-start;
  border-top: 1px solid rgba(35,31,32,0.12);
  min-width: 0; /* deja que la columna se ajuste al ancho fijado por la
  rejilla en vez de crecer para no partir una cadena larga sin espacios */
}
@media (min-width: 880px) {
  .anuncio-cierre { border-top: 0; border-left: 1px solid rgba(35,31,32,0.12); min-width: 15rem; }
}
.anuncio-precio { font-size: clamp(1.7rem, 3vw, 2.1rem); font-weight: 500; line-height: 1; }
/* opacity 0.62 -> 0.75 (4.43:1, bajo el 4.5:1 AA). */
.anuncio-precio span { display: block; font-size: 0.7rem; opacity: 0.75; letter-spacing: 0.09em; text-transform: uppercase; margin-top: 0.4rem; }
.anuncio-acciones { display: flex; flex-direction: column; gap: 0.75rem; align-items: flex-start; }
.anuncio-acciones .btn-primary { padding: 0.95rem 1.7rem; font-size: 0.8rem; }
.anuncio-acciones .link { font-size: 0.85rem; }

/* ---------- .en-fila en móvil: compacta, no el desktop apilado (8-ago-2026) --
   Bug real corregido: por debajo de 880px .anuncio caía en el flex-column
   genérico de la línea de arriba, pero cada una de sus 4 secciones (foto,
   cuándo, cuerpo, cierre) conservaba el padding/aspect-ratio pensado para
   ocupar una COLUMNA de escritorio — foto a 16/10 con mucho aire alrededor
   de una ilustración pequeña, "cuándo" en su propio bloque de ancho
   completo con separador, etc. Resultado: cada tarjeta medía ~1038px en un
   viewport de 812px (yoga.html, "Yoga y sonido") — más de una pantalla y
   media por CADA clase del listado. Aquí no se apila igual que el desktop,
   se rediseña: foto pequeña (thumbnail) + cuándo comparten la fila de
   arriba, cuerpo y cierre debajo a ancho completo. Afecta a yoga.html
   (+ EU) — su .anuncio tiene 4 hijos (foto/cuándo/cuerpo/cierre).
   Corregido 8-ago-2026: cuencos.html también lleva la clase .en-fila, PERO
   además .tres-columnas, con solo 2 hijos (foto/cuerpo; el "cuándo" va
   fundido dentro del cuerpo, en .anuncio-pie) — sin el :not() de abajo, el
   grid de 2 columnas se aplicaba igual y dejaba 184px vacíos junto a la
   foto porque no hay nada que ocupe esa celda — una revisión estática del
   código la encontró donde la verificación visual en pantalla no. */
@media (max-width: 879px) {
  .anuncios.en-fila:not(.tres-columnas) .anuncio { display: grid; grid-template-columns: 6.5rem 1fr; }
  .anuncios.en-fila:not(.tres-columnas) .anuncio-foto { aspect-ratio: 1 / 1; }
  /* .anuncio-foto.ilustracion (yoga.html) hereda 1.2rem de padding + 78% de
     max-width/height pensados para un contenedor de escritorio — sobre este
     thumbnail de 6.5rem (104px) eso dejaba la ilustración a ~50px reales,
     una mancha. Padding y límite propios, solo aquí. */
  .anuncios.en-fila:not(.tres-columnas) .anuncio-foto.ilustracion { padding: 0.4rem; }
  .anuncios.en-fila:not(.tres-columnas) .anuncio-foto.ilustracion img { max-width: 92%; max-height: 92%; }
  .anuncios.en-fila:not(.tres-columnas) .anuncio-cuando {
    padding: 0.9rem 1.2rem 0.9rem 0.2rem;
    border-bottom: 0;
  }
  /* 3rem pensado para la columna ancha de escritorio; en el thumbnail
     estrecho de móvil sobra tanto aire entre pares día+hora repetidos. */
  .anuncios.en-fila:not(.tres-columnas) .anuncio-hora + .anuncio-dia { margin-top: 1rem; }
  .anuncios.en-fila:not(.tres-columnas) .anuncio-cuerpo,
  .anuncios.en-fila:not(.tres-columnas) .anuncio-cierre {
    grid-column: 1 / -1;
    border-top: 1px solid rgba(35,31,32,0.12);
  }
}

/* ---------- variante de tres columnas (cuencos) -----------------------------
   Cuatro columnas dejaban el bloque leyéndose como una lista continua: las
   tarjetas se tocaban, ninguna se cerraba, y como cada texto tiene un largo
   distinto, todas medían diferente. Tres cambios, el mismo lenguaje que ya usa
   el listado del blog:
     1. Cada sesión se cierra en su propia caja (borde + separación amplia).
     2. Todas miden lo mismo: `grid-auto-rows: 1fr` iguala las filas del
        contenedor a la más alta, en vez de dejar que cada una caiga por su
        contenido.
     3. La cuarta columna se va: el botón vive dentro del texto que lo justifica.
        La foto se queda con el ancho que soltó.
   yoga.html usa solo `.en-fila` (sus cuatro columnas, porque ahí sí se
   comparan horarios y precios entre clases); cuencos.html usa `.en-fila
   tres-columnas` a la vez — ver el bloque `:not(.tres-columnas)` de arriba,
   que evita que el rediseño móvil de yoga se aplique también aquí. */
/* DOS columnas, no tres: la columna central "cuándo" se
   funde dentro del cuerpo, en una fila propia al fondo (duración + dirección
   | separador | CTA). Solo quedan foto y cuerpo. */
.anuncios.tres-columnas { gap: clamp(2.2rem, 4vw, 3.2rem); }

.anuncios.tres-columnas .anuncio {
  border: 1px solid rgba(35,31,32,0.14);
}

@media (min-width: 880px) {
  /* Todas las tarjetas, la misma altura: la de la más alta. */
  .anuncios.tres-columnas { grid-auto-rows: 1fr; }
  .anuncios.tres-columnas .anuncio {
    grid-template-columns: 22rem 1fr;
    min-height: 17rem;
  }
  .anuncios.tres-columnas .anuncio-foto { min-width: 0; }
}

/* Fila del pie: datos prácticos a la izquierda, separador, CTA a la derecha.
   Es una sugerencia de layout, no un requisito — si no aporta, se quita sin
   más. */
.anuncios.tres-columnas .anuncio-pie {
  display: flex;
  align-items: center;
  gap: 1.2rem;
  margin-top: auto;
  padding-top: 1rem;
}
.anuncios.tres-columnas .anuncio-pie .datos-practicos {
  font-size: 0.82rem;
  line-height: 1.55;
  opacity: 0.8;
  flex: 1;
}
.anuncios.tres-columnas .anuncio-pie .separador {
  width: 1px;
  align-self: stretch;
  background: rgba(35,31,32,0.16);
  flex: none;
}

/* La foto NO decide la altura de la tarjeta: la decide el texto.
   Con la imagen en el flujo normal, un `height: 100%` dentro de una celda de
   altura automática se resuelve al alto natural de la imagen: la foto vertical
   de la sesión individual (1365x2048) pedía 528px a 352px de ancho y estiraba
   la fila al doble que las demás, dejando un agujero en medio de las otras tres.
   Sacándola del flujo con posición absoluta, la celda toma la altura de la fila
   y la imagen la rellena recortando. Es la misma trampa que ya apareció en el
   listado del blog, resuelta aquí de raíz en vez de compensándola con medidas. */
.anuncios.tres-columnas .anuncio-foto { position: relative; overflow: hidden; }
.anuncios.tres-columnas .anuncio-foto img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}

/* El botón, al final del texto y separado de los datos, para que la tarjeta
   cierre siempre igual aunque el texto de arriba sea más corto. */
.anuncios.tres-columnas .anuncio-accion { margin-top: 1.1rem; }
.anuncios.tres-columnas .anuncio-datos { margin-top: auto; }
.anuncios.tres-columnas .anuncio-accion .btn-primary {
  padding: 0.95rem 1.7rem; font-size: 0.8rem;
}

/* ---------- formato en tarjeta (retiros) ------------------------------------ */
@media (min-width: 900px) { .anuncios.en-tarjeta { grid-template-columns: repeat(2, 1fr); } }
.anuncios.en-tarjeta .anuncio-foto { position: relative; aspect-ratio: 16 / 10; overflow: hidden; }
.anuncio-foto img { width: 100%; height: 100%; object-fit: cover; display: block; filter: saturate(0.88) contrast(1.03); }
/* Ilustración en vez de foto (yoga.html, 3-ago-2026): contain + fondo propio,
   no cover — recortar un dibujo de líneas lo destroza, a una foto no se nota. */
/* Sin fondo arena propio: con --sand iba a juego con el
   fondo de stripe-sand y la ilustración quedaba "flotando al lado" de la
   tarjeta en vez de dentro — ahora hereda el mismo crema que el resto del
   taco y lee como una sola pieza. */
.anuncio-foto.ilustracion { display: flex; align-items: center; justify-content: center; padding: 1.2rem; }
.anuncio-foto.ilustracion img { width: auto; height: auto; max-width: 78%; max-height: 78%; object-fit: contain; filter: none; }

/* La fecha del retiro, sobre la foto: es el dato que decide */
.anuncio-fecha {
  position: absolute; left: 0; bottom: 0;
  background: var(--forest); color: var(--cream);
  padding: 0.9rem 1.4rem 1rem; line-height: 1; text-align: center; min-width: 7rem;
}
.anuncio-mes { display: block; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.15em; opacity: 0.85; margin-bottom: 0.45rem; }
.anuncio-dias { display: block; font-size: clamp(1.5rem, 3vw, 1.95rem); font-weight: 500; }
/* opacity 0.7 -> 0.8 (4.24:1, bajo el 4.5:1 AA). */
.anuncio-anio { display: block; font-size: 0.75rem; opacity: 0.8; margin-top: 0.35rem; }
.anuncios.en-tarjeta .anuncio-cierre { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 1.2rem; border-left: 0; border-top: 1px solid rgba(35,31,32,0.12); min-width: 0; }

/* ==========================================================================
   CANTOS — mosaico de cuadrados, uno por canto (vuelve al planteamiento
   anterior a la lista en filas). Doce en gris, sin ficha ni compra todavía:
   llevan a la sección de precios, no fingen un carrito que no existe. El
   bilingüe es el único a color: se abre y se escucha aquí mismo, sin pedir
   el correo a cambio — ver primero, sin fricción; la carta se ofrece aparte.
   ========================================================================== */
.cantos-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.9rem;
  margin-top: 2.2rem;
}
@media (min-width: 640px) { .cantos-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 900px) { .cantos-grid { grid-template-columns: repeat(4, 1fr); } }

.canto-tile {
  background: var(--sand);
  color: var(--ink);
  padding: 1.2rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  min-height: 112px;
  text-decoration: none;
}
/* opacity 0.42/0.55 -> 0.7: sobre los 12 bloqueados (.mudo, abajo) el
   filtro de opacidad del contenedor se multiplicaba con esta, dejando la
   numeración a contraste 1.71:1 y la duración a 2.05:1 — muy por debajo
   del 4.5:1 mínimo AA. Esto sube el número, pero lo que de verdad decide
   si se cierra el hallazgo es quitar la opacidad del contenedor (ver
   .canto-tile.mudo abajo): con las dos cosas a la vez seguía en 3.54:1. */
.canto-tile .num { font-size: 0.78rem; opacity: 0.7; font-variant-numeric: tabular-nums; }
.canto-tile .nombre { font-size: 0.95rem; font-weight: 500; line-height: 1.32; }
.canto-tile .dura { font-size: 0.78rem; opacity: 0.7; font-variant-numeric: tabular-nums; margin-top: auto; }

/* Los doce bloqueados: cada uno solo nombre + enlace a su ficha, el precio se
   explica una vez encima del mosaico, no repetido en cada cuadrado.
   Siguen apagados visualmente para distinguirse del libre — pero solo con
   grayscale(1), sin opacity: la opacidad del contenedor se multiplicaba
   con la de .num/.dura de arriba y el conjunto no llegaba a AA (3.54:1
   con ambos ajustes). Sin la opacidad del contenedor: 4.84:1, y el efecto
   "apagado" se sostiene solo con el desaturado. */
.canto-tile.mudo { filter: grayscale(1); }

/* el cuadro que cierra el mosaico: el cuaderno completo. Mismo lenguaje visual
   que los cuadrados (no las tarjetas .card de antes), en ink para separarlo
   del verde (que aquí significa "muestra gratis", no "cómpralo"). */
.canto-tile.completo {
  grid-column: 1 / -1;
  background: var(--ink);
  color: var(--cream);
  padding: 1.6rem 1.8rem;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.8rem;
}
.canto-tile.completo .nombre { font-size: 1.15rem; font-weight: 600; flex: 1 1 auto; }
.canto-tile.completo p { font-size: 0.85rem; opacity: 0.75; flex: 1 1 220px; margin: 0; }
.canto-tile.completo .precio-completo { font-size: 1.4rem; font-weight: 600; white-space: nowrap; }
.canto-tile.completo .precio-completo span {
  display: block; font-size: 0.68rem; font-weight: 400; opacity: 0.65;
  text-transform: none; letter-spacing: 0;
}
.canto-tile.completo .btn-primary { background: var(--cream); color: var(--ink); flex: none; }
.canto-tile.completo .btn-primary:hover { background: var(--olive); color: var(--cream); }

/* el bilingüe: a color, con presencia propia y espacio para su ficha */
.canto-tile.libre {
  grid-column: span 2;
  background: var(--forest);
  color: var(--cream);
}
@media (min-width: 640px) { .canto-tile.libre { grid-column: span 3; } }
@media (min-width: 900px) { .canto-tile.libre { grid-column: span 4; } }
.canto-tile.libre .nombre { font-size: 1.15rem; }
.canto-tile.libre .bilingue-etiqueta {
  display: inline-block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  border: 1px solid currentColor;
  padding: 0.2rem 0.5rem;
  margin-left: 0.5rem;
  vertical-align: 0.14em;
}
/* Botón grande a propósito: esta pieza es distinta a todas las demás y
   tiene que verse clarísimo — el enlace de texto subrayado se quedaba
   corto. Lleva a la ficha propia del canto (texto, audio, propuesta de
   práctica), no reproduce inline: "no solamente se reproduzca la canción". */
.canto-tile.libre .escuchar-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.9rem;
  margin-top: 1.2rem;
  padding: 1rem 1.8rem;
  background: var(--cream);
  color: var(--forest);
  font-size: 1.1rem;
  font-weight: 600;
  border-radius: 999px;
}
.canto-tile.libre .escuchar-btn .icono {
  width: 34px; height: 34px;
  flex: none;
  border-radius: 50%;
  background: var(--forest);
  color: var(--cream);
  display: grid;
  place-items: center;
  font-size: 0.85rem;
}
.canto-tile.libre:hover .escuchar-btn { background: var(--olive); color: var(--cream); }
.canto-tile.libre:hover .escuchar-btn .icono { background: var(--cream); color: var(--olive); }

/* reutilizable: invitación a la carta, aquí y en la página de ficha */
.newsletter-cta {
  display: inline-block;
  margin-top: 1.1rem;
  padding: 0.7rem 1.3rem;
  background: var(--forest);
  color: var(--cream);
  font-size: 0.82rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ---- cuerpo de un artículo (ficha de "Entre árboles") --------------------
   Ancho de lectura más corto que .lead (54ch): un artículo entero se lee
   mejor en una columna más estrecha que una frase suelta. */
.articulo-cuerpo { max-width: 68ch; margin-top: 2.4rem; }
.articulo-cuerpo p { margin-top: 1.3rem; line-height: 1.75; }
.articulo-cuerpo h2 { margin-top: 2.8rem; font-size: clamp(1.35rem, 2.4vw, 1.7rem); }

/* ---- mockup del cuaderno digital (boceto, no diseño final) --------------- */
.cuaderno-mockup { text-align: center; }
.cuaderno-mockup .portada {
  background: var(--sand);
  aspect-ratio: 3 / 4;
  max-width: 300px;
  margin: 0 auto;
  padding: 2rem 1.5rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  position: relative;
  transform: rotate(-1.2deg);
  box-shadow: 0 18px 34px rgba(35,31,32,0.16);
}
.cuaderno-mockup .portada::before {
  content: "";
  position: absolute; left: 0; top: 0; bottom: 0; width: 10px;
  background: var(--olive); opacity: 0.5;
}
/* opacity 0.55 -> 0.72 (3.62:1, bajo el 4.5:1 AA). */
.cuaderno-mockup .marca { font-size: 11px; text-transform: uppercase; letter-spacing: 0.28em; opacity: 0.72; }
.cuaderno-mockup .numero { font-size: clamp(3.2rem, 9vw, 4.6rem); font-weight: 500; line-height: 1; color: var(--forest); }
.cuaderno-mockup .titulo { font-size: 1rem; text-transform: uppercase; letter-spacing: 0.18em; }
.cuaderno-mockup figcaption { margin-top: 1.1rem; font-size: 0.78rem; opacity: 0.6; font-style: italic; }

/* ==========================================================================
   DATO PENDIENTE — marca inequívoca de lo que aún no está confirmado.
   Se ve a la primera para que nunca se publique sin rellenar.
   ========================================================================== */
.pendiente-dato {
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  background: rgba(106,99,59,0.14);
  border: 1px dashed var(--olive);
  color: var(--olive);
  padding: 0.15rem 0.5rem;
  white-space: nowrap;
}
.stripe-forest .pendiente-dato, .stripe-ink .pendiente-dato {
  background: rgba(244,243,234,0.14);
  border-color: rgba(244,243,234,0.5);
  color: var(--cream);
}

/* ==========================================================================
   VÍDEO — respeta su proporción real, que es 4:3 y NO 16:9 (medido sobre el
   fotograma que sirve YouTube: el contenido ocupa 959 de 1280px, el resto son
   barras negras que el propio archivo lleva grabadas — no las pinta esta
   página). Forzar el marco a 16:9 no elimina esas barras: solo las traslada
   de "fuera del marco" a "dentro del marco", donde siguen siendo negras.
   Antes se forzaba a una banda de altura fija y se recortaba simulando
   "cover": un vídeo no es una foto de fondo, y recortarlo se come justo lo
   que la cámara encuadró (en pantallas anchas y bajas el recorte era brutal).
   Con el marco en la proporción real del vídeo no hay recorte ni barras.
   ========================================================================== */
.video-banda { width: 100%; background: var(--forest); }
/* El techo se pone al ANCHO, no al alto: un max-height sobre un elemento con
   aspect-ratio gana a la proporción y vuelve a deformar el vídeo. Limitando
   el ancho en función de la altura disponible, la proporción se mantiene
   exacta en cualquier pantalla. */
.video-banda .marco {
  position: relative;
  width: min(100%, calc(66vh * 4 / 3));
  aspect-ratio: 4 / 3;
  margin: 0 auto;
}
.video-banda iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }

/* ==========================================================================
   QUÉ INCLUYE / QUÉ LLEVAR — la objeción práctica, en dos columnas con aire.
   Cada línea respira: es una lista que se consulta, no un párrafo que se lee.
   ========================================================================== */
.incluye { display: grid; gap: clamp(2.4rem, 5vw, 4.5rem); margin-top: 3rem; }
@media (min-width: 780px) { .incluye { grid-template-columns: repeat(2, 1fr); } }

.incluye h3 {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--olive);
  font-weight: 500;
  margin-bottom: 1.5rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid rgba(35,31,32,0.18);
}
.stripe-forest .incluye h3, .stripe-ink .incluye h3 {
  color: color-mix(in srgb, var(--cream) 70%, var(--forest));
  border-bottom-color: rgba(244,243,234,0.28);
}

.incluye ul { list-style: none; }
.incluye li {
  padding: 1.05rem 0 1.05rem 1.9rem;
  position: relative;
  font-size: 0.98rem;
  line-height: 1.65;
}
.incluye li + li { border-top: 1px solid rgba(35,31,32,0.09); }
.stripe-forest .incluye li + li, .stripe-ink .incluye li + li { border-top-color: rgba(244,243,234,0.16); }
.incluye li::before {
  content: "";
  position: absolute;
  left: 0; top: 1.55rem;
  width: 7px; height: 7px;
  border: 1px solid var(--olive);
  opacity: 0.85;
}
.stripe-forest .incluye li::before, .stripe-ink .incluye li::before { border-color: var(--cream); }

/* ==========================================================================
   BARRA DE RESUMEN de la ficha — entra al dejar atrás el bloque de decisión,
   para que el dato y el botón sigan a mano sin volver arriba.
   ========================================================================== */
.barra-ficha {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  background: var(--ink);
  color: var(--cream);
  z-index: 40;
  transform: translateY(100%);
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.barra-ficha.visible { transform: translateY(0); }
.barra-ficha .bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.2rem; flex-wrap: wrap;
  padding-top: 0.9rem; padding-bottom: 0.9rem;
}
.barra-ficha .que { font-size: 0.95rem; font-weight: 500; }
.barra-ficha .cuando { display: block; font-size: 0.8rem; opacity: 0.72; margin-top: 0.15rem; }
.barra-ficha .btn-primary { background: var(--cream); color: var(--ink); padding: 0.85rem 1.8rem; font-size: 0.78rem; }
.barra-ficha .btn-primary:hover { background: var(--sand); }
@media (prefers-reduced-motion: reduce) { .barra-ficha { transition: none; } }

/* ==========================================================================
   AVISO DE TRABAJO — bloque temporal para revisiones con Txari y su ilustradora.
   Se ve a propósito y no se parece a nada del diseño: no debe confundirse con
   contenido de la web. TODOS estos bloques se retiran antes de publicar.
   Rediseñado 3-ago-2026 (la versión anterior, borde discontinuo + tinte
   apenas visible, no se sostenía como pieza con intención propia). */
.aviso-trabajo {
  margin-top: 2.8rem;
  padding: 1.8rem 2rem;
  border-left: 4px solid var(--olive);
  background: var(--sand);
  max-width: 68ch;
}
.aviso-trabajo-titulo {
  font-size: 1rem;
  color: var(--ink);
  font-weight: 600;
  margin-bottom: 1rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.aviso-trabajo-titulo::before { content: "●"; color: var(--olive); font-size: 0.65em; }
.aviso-trabajo p { font-size: 0.95rem; line-height: 1.7; }
.aviso-trabajo p + p { margin-top: 0.9rem; }
.aviso-trabajo strong { color: var(--forest); }

/* ==========================================================================
   TEXTOS — el listado del blog ("Errekatxari", nombre provisional).
   Dos columnas: imagen y todo lo demás.
   No cuatro: un texto no tiene horario ni precio que comparar, así que partirlo
   en columnas solo dejaba huecos. La imagen va en 4:3, la misma proporción que
   tendrá dentro del artículo, para que no cambie de forma al entrar.
   ========================================================================== */
.textos { list-style: none; display: grid; gap: clamp(2.2rem, 4vw, 3.2rem); margin-top: 2.8rem; }

.texto {
  display: grid;
  grid-template-columns: 1fr;
  background: #fbfaf4;
  border: 1px solid rgba(35,31,32,0.14);
  overflow: hidden;
}
@media (min-width: 820px) {
  .texto { grid-template-columns: 24rem 1fr; align-items: stretch; min-height: 19rem; }
}

/* Dos cosas que hacían que la foto se saliera de su columna y tapara el texto:
   1. min-width auto (por defecto en grid) impedía que la celda se encogiera por
      debajo del ancho natural de la imagen.
   2. aspect-ratio con la celda estirada a lo alto calcula el ANCHO a partir del
      alto de la fila, no al revés: con una fila de 320px pedía 426px de ancho en
      una columna de 352.
   En escritorio manda la columna y la foto llena la celda; la proporción 4:3 se
   guarda para móvil, donde la foto va sola a todo el ancho. */
.texto-foto { aspect-ratio: 4 / 3; min-width: 0; overflow: hidden; }
@media (min-width: 820px) { .texto-foto { aspect-ratio: auto; height: 100%; } }
.texto-foto img { width: 100%; height: 100%; object-fit: cover; display: block; filter: saturate(0.9) contrast(1.02); }
.texto-cuerpo { min-width: 0; }

.texto-cuerpo {
  padding: clamp(1.8rem, 3.2vw, 2.6rem);
  display: flex; flex-direction: column; gap: 0.9rem;
  justify-content: center;
}
.texto-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.7rem 1.1rem; }
.texto-cat { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.15em; color: var(--olive); }
.texto-titulo { font-size: clamp(1.3rem, 2.4vw, 1.7rem); font-weight: 500; line-height: 1.22; max-width: 30ch; }
.texto-extracto { font-size: 0.95rem; line-height: 1.65; opacity: 0.85; max-width: 62ch; }
.texto-cta { margin-top: 0.5rem; }
.texto-cta .btn-primary { padding: 0.9rem 1.7rem; font-size: 0.8rem; }

/* ── LISTADO DEL BLOG EN MÓVIL (8-sep-2026) ─────────────────────────────────
   Medido en 375x812 sobre producción: cada artículo ocupaba 738-864px de alto
   en una pantalla de 812 — uno por pantalla, y 5.700px para cuatro. El reparto
   de esos ~810px: foto 216, extracto 276, y casi 200 de aire interno. Sobraba
   texto, no imagen.

   Debajo de 820px (mismo corte que ya usa .texto) el listado pasa a fila
   compacta: foto cuadrada pequeña, categoría y título, y toda la tarjeta
   pinchable. El extracto se cae — en un listado se decide por el título, y el
   extracto entero espera dentro del artículo. En escritorio no cambia nada.

   La tarjeta se hace pinchable estirando el enlace que YA existe, sin tocar el
   HTML de las páginas: así vale para ES, EU y para los artículos que vengan. */
@media (max-width: 819px) {
  .textos { gap: 0; margin-top: 2rem; }

  .texto {
    position: relative;                   /* ancla del enlace estirado */
    grid-template-columns: 5.5rem 1fr;
    gap: 1rem;
    align-items: center;
    padding: 0.85rem 0;
    border-bottom: 1px solid rgba(0, 0, 0, 0.12);
  }
  .texto:first-child { border-top: 1px solid rgba(0, 0, 0, 0.12); }

  .texto-foto { aspect-ratio: 1 / 1; }

  /* El aire de la fila NO estaba entre filas: `.texto-cuerpo` trae un padding de
     clamp(1.8rem,…) que en 375px son casi 58px verticales metidos DENTRO de cada
     fila. En una tarjeta grande ese respiro es correcto; en una fila de listado
     duplica su alto. Aquí el respiro lo da el padding de la fila, no el cuerpo. */
  .texto-cuerpo { display: grid; gap: 0.2rem; padding: 0; }
  /* En producción, WordPress añade márgenes propios a los párrafos y títulos que
     en el HTML estático no existen: medido en txari.com, 42px por fila solo de
     margen entre la categoría y el título. El espaciado de esta fila lo decide su
     `gap`, no los márgenes heredados. */
  .texto-cuerpo > * { margin: 0; }
  .texto-titulo { font-size: 1.05rem; line-height: 1.28; max-width: none; }
  .texto-extracto { display: none; }

  /* El botón deja de ocupar alto y pasa a cubrir la tarjeta entera. El texto
     sigue en el DOM (color transparente, no display:none) para que un lector
     de pantalla lo siga anunciando. */
  .texto-cta { margin: 0; }
  .texto-cta .btn-primary {
    position: absolute;
    inset: 0;
    padding: 0;
    border: 0;
    background: none;
    color: transparent;
    font-size: 0;
    line-height: 0;
  }
  /* Si el enlace es invisible, el foco de teclado tiene que verse sí o sí. */
  .texto-cta .btn-primary:focus-visible {
    outline: 2px solid var(--olive);
    outline-offset: 2px;
  }
}

/* ---- preferencia de movimiento reducido, cobertura completa (8-ago-2026) ---
   Antes solo .btn-primary y .barra-ficha la respetaban — quedaban fuera el
   scroll suave (el que más molesta a sensibilidad vestibular), la apertura
   del menú, la animación del icono hamburguesa y el hover de .door-card.
   Un bloque global al final, en vez de ir componente a componente. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .site-nav,
  .nav-toggle span,
  .door-card,
  .skip-link {
    transition: none !important;
  }
  /* Anular solo la transition no basta: sin ella el salto de
     translateY(-4px) sigue pasando, solo que de golpe — instantáneo es
     peor que suave para sensibilidad vestibular, no mejor. */
  .door-card:hover { transform: none; }
}
