/* ==========================================================================
   HAVEN — Ciudad de Panamá
   Design source: Adobe XD spec (1280px artboards / iPhone 14 Pro Max 430px)
   ========================================================================== */

/* ---- Fuentes --------------------------------------------------------- */
/* Autohospedadas: Haven no usa Typekit. woff2 subsetado a latín (tarea 2).
   OJO licencia: los .otf de escritorio normalmente NO cubren uso web, y un
   Vercel público YA es uso web. Se publican igualmente desde el 2026-09-10
   por decisión de Chus (el preview tiene que verse con su tipografía), pero
   la autorización escrita sigue sin constar (spec §9, criterio 20):
   HAVEN_FUENTES_LICENCIA_PENDIENTE bloquea --publish hasta que conste. */
@font-face { font-family: "New Atten"; src: url("../assets/fonts/attennewbook.woff2") format("woff2"); font-weight: 350; font-style: normal; font-display: swap; }
@font-face { font-family: "New Atten"; src: url("../assets/fonts/attennewregular.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "New Atten"; src: url("../assets/fonts/attennewregularitalic.woff2") format("woff2"); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: "New Atten"; src: url("../assets/fonts/attennewmedium.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "New Atten"; src: url("../assets/fonts/attennewbold.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Bodoni 72"; src: url("../assets/fonts/bodoni72book.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Bodoni 72"; src: url("../assets/fonts/bodoni72bookitalic.woff2") format("woff2"); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: "Bodoni 72"; src: url("../assets/fonts/bodoni72bold.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }

/* ---- Tokens ---------------------------------------------------------- */
:root {
  --bg: #7a9b83;        /* sage — fondo de página (XD) */
  --ink: #380606;       /* maroon — texto, secciones oscuras, logo (XD #380606) */
  --ink-deep: #2c0000;  /* maroon profundo — refuerzos puntuales */
  --ink-25: rgba(56, 6, 6, 0.25);  /* --ink al 25%; ver .plan-details-block */
  --accent: #d6ff01;    /* lima — acento, botones, círculos del slider */
  --accent-2: #30212b;  /* plum — acento secundario */
  --grey: #707070;      /* neutro secundario */
  --offwhite: #ececeb;  /* superficies muy claras */

  --font-sans: "New Atten", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-display: "Bodoni 72", "Bodoni MT", Didot, "Times New Roman", serif;
  /* Solo para el toggle ES/EN, tal como pide el XD. No hay archivo licenciado
     de Helvetica Neue: stack de sistema a propósito. */
  --font-helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif;

  --container: 1184px;
  --gutter: 48px;
}

/* ---- Reset / base ---------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);   /* Haven: la sans es la implícita */
  font-size: 20px;
  line-height: 1.6;                /* 32px */
  font-weight: 400;
  /* AA en escala de grises para que el texto se vea más ligero/nítido en
     macOS, igual que en los mockups del XD. El subpíxel por defecto del
     navegador engorda los trazos. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
img {
  display: block;
  max-width: 100%;
}
a {
  color: inherit;
  text-decoration: none;
}
button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
@media (min-width: 1280px) {
  .container {
    padding-inline: 0;
  }
}

/* ---- Header / nav ----------------------------------------------------- */
.site-header {
  max-width: var(--container);
  margin-inline: auto;
  padding: 0 var(--gutter);
  /* Sin padding-top, como en el XD. Lo llevó prestado (14px) mientras el
     toggle ES/EN estuvo fuera del DOM, porque sin el primer elemento del
     header el logo tocaba el borde de la ventana; su propio comentario
     avisaba de que al volver el toggle el hueco se duplicaría. El toggle ha
     vuelto, así que el aire lo aportan otra vez sus 30px y .nav-row recupera
     los 14px que declara el XD. */
  position: relative;
}
@media (min-width: 1280px) {
  .site-header {
    padding-inline: 0;
  }
}
.lang-toggle {
  display: flex;
  justify-content: flex-end;
  gap: 0.35em;
  padding-top: 32px;
  font-family: var(--font-helvetica);
  font-size: 16px;
  line-height: 1.2;
  color: var(--ink);
}
.lang-toggle a,
.lang-toggle-mobile a {
  font-family: inherit;
  font-size: inherit;
  font-weight: 400;
  opacity: 0.55;
}
.lang-toggle a.is-active,
.lang-toggle-mobile a.is-active {
  font-weight: 700;     /* New Atten Bold real: 600 no existe como cara y el
                            navegador lo sintetizaba engordando los trazos */
  opacity: 1;
}
.nav-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  padding-top: 16px;    /* XD 14px; a rejilla (2026-09-22). Volvió aquí al reponerse .lang-toggle */
  padding-bottom: 44px; /* XD 45px (logo top y63, hero top y144); a rejilla (2026-09-22) */
}
.logo-link img,
.logo-link svg {
  /* 165px era el valor de Blur ($BLUR/css/styles.css, .logo-link svg); el XD
     de Haven pide 290px en escritorio (/tmp/xd.css .logo-link img, y el
     propio width="290" height="36" del <img> del build archivado). */
  width: 290px;
  height: auto;
  /* margin-bottom: -18px (Blur) retirado: estaba calculado para el logo a
     165px y el archivo (/tmp/xd.css) no declara ningún margin-bottom para
     .logo-link img a 290px — con align-items: flex-end en .nav-row (mismo
     valor que el archivo), el ajuste natural del flexbox ya coloca el logo
     donde el XD lo marca, sin nudge manual. Si a 290px se ve desalineado
     respecto al nav, es la primera propiedad a revisar. */
}
/* El logo del header se AUTODIBUJA al cargar, calcando el arranque de
   HAVEN_VIDEO (web assets/HAVEN _ VIDEO_ESCENAS/00 HAVEN_VIDEO): sin fundido,
   cada trazo aparece a opacidad plena con un frente perpendicular que avanza a
   velocidad constante. Va inline (no <img>) para que el reloj arranque en el
   primer pintado y transitions.js pueda retenerlo bajo el velo y relanzarlo
   (holdHeaderLogo / releaseHeaderLogo). Ver .logo-draw-line más abajo. */

/* El wordmark inline (los 5 <path> de HAVEN, artwork oficial de Muchagana,
   01 ID GRAFICA/02 LOGO/SVG/HEAVEN_Logo-01.svg, recortado a su bbox de tinta
   1222.66 x 140.63) lleva fill="currentColor" en el marcado y toma el color
   del contexto AQUÍ. Los PNG anteriores traían el maroon cocido junto al
   fondo sage, así que el color no era una decisión sino un accidente del
   archivo; ahora es un token. Sobre el panel del velo el color lo pone
   .vt-veil-logo / .vt-intro-logo (var(--accent), lima) en transitions.css.
   Si la paleta migra al manual de marca (#33202c), se cambia el token y el
   logo la sigue solo, en las 40 instancias de las 8 páginas. */
.logo-link svg,
.sticky-nav .sticky-logo svg,
.footer-logos .logo-haven {
  fill: var(--ink-deep);
  /* Un <img> caía en el reset `img { display: block }` de arriba; un <svg> no,
     así que al pasar el wordmark a inline volvía a ser un elemento de línea y
     arrastraba la caja de línea de su enlace (y el hueco del descendente).
     En la sticky nav eso engordaba la barra respecto al PNG anterior. Los dos
     logos del velo no lo necesitan: van en posición absoluta y eso ya los
     convierte en bloque. */
  display: block;
}
/* Los 10 trazos de línea central del <mask id="logo-draw-mask"> del header
   (uno por segmento del wordmark). Cada uno lleva pathLength="1", así que
   dasharray/dashoffset 1→0 lo revela de principio a fin. Anchura 32 sobre un
   trazo de tinta de 22.66 (viewBox): margen de ~4.7 por lado para el
   antialias sin llegar a la letra vecina (hueco mínimo 34). Linecap butt para
   que el frente sea un corte perpendicular como en el vídeo, no una punta
   redonda; linejoin round para las esquinas de A, V, E y N.
   Los tiempos por :nth-child están MEDIDOS del vídeo fotograma a fotograma
   (30 fps, primera aparición de cada píxel), redondeados a centésimas y con
   el origen en el primer trazo de la H. Se solapan a propósito: la A empieza
   con el último palo de la H, la E empieza mientras la V remata, etc. Total
   1.47 s. Si se cambia el total, hay que mover con él INTRO_MIN_MS de
   transitions.js: el velo de bienvenida calca estos tiempos. */
.logo-draw-line {
  fill: none;
  stroke: #fff;
  stroke-width: 32;
  stroke-linecap: butt;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: logo-draw 0.2s linear forwards;
}
.logo-draw-line:nth-child(1)  { animation-delay: 0s;    animation-duration: 0.13s; } /* H palo izq.  */
.logo-draw-line:nth-child(2)  { animation-delay: 0.13s; animation-duration: 0.27s; } /* H barra      */
.logo-draw-line:nth-child(3)  { animation-delay: 0.43s; animation-duration: 0.2s;  } /* H palo dcho. */
.logo-draw-line:nth-child(4)  { animation-delay: 0.43s; animation-duration: 0.34s; } /* A arco       */
.logo-draw-line:nth-child(5)  { animation-delay: 0.67s; animation-duration: 0.2s;  } /* A barra      */
.logo-draw-line:nth-child(6)  { animation-delay: 0.7s;  animation-duration: 0.3s;  } /* V            */
.logo-draw-line:nth-child(7)  { animation-delay: 0.9s;  animation-duration: 0.47s; } /* E contorno   */
.logo-draw-line:nth-child(8)  { animation-delay: 1.23s; animation-duration: 0.24s; } /* E barra      */
.logo-draw-line:nth-child(9)  { animation-delay: 1.2s;  animation-duration: 0.1s;  } /* N barra+diag */
.logo-draw-line:nth-child(10) { animation-delay: 1.33s; animation-duration: 0.07s; } /* N palo       */
@keyframes logo-draw {
  to { stroke-dashoffset: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .logo-draw-line {
    animation: none;
    stroke-dashoffset: 0;
  }
  .carousel-arrow--prev:hover svg,
  .carousel-arrow--prev:focus-visible svg,
  .carousel-arrow--next:hover svg,
  .carousel-arrow--next:focus-visible svg {
    animation: none;
  }
  /* El subrayado del nav NO se anula aquí: sus tres reglas normales viven más
     abajo (.main-nav a::after, .sticky-nav nav a::after y .mobile-menu > a::after)
     y con la misma especificidad gana la última. El override va al final del
     fichero; ver "Movimiento reducido, segunda pasada" ahí abajo. */
}
.main-nav {
  display: flex;
  align-items: baseline;
  gap: 40px;            /* el XD pide 48px; se baja a 40 a petición de Chus en la
                            ronda de pulido, con el toggle ES/EN ya repuesto la
                            fila iba justa a 1024px. Divergencia documentada en
                            KNOWN_INTENTIONAL_DIVERGENCES de check-style-xd.py. */
}
.main-nav a {
  font-size: 20px;         /* XD New Atten Regular 20px */
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.06em;  /* XD character spacing 0.06em */
  text-transform: uppercase;
  color: var(--ink);
  position: relative;      /* ancla del subrayado de hover/foco (::after) */
}
.main-nav a.is-active {
  font-weight: 700;       /* New Atten Bold real */
}
/* Hover/foco del nav: «cortina en miniatura», elegida por Chus el 2026-09-22
   entre las seis del banco docs/bancos/nav-hover.html. Es el velo llevado al
   nav: dos filetes de 1px que se despliegan desde la izquierda, maroon
   (::after, a -6px) y lima (::before, 2px más abajo). Al entrar, el lima va
   en cabeza y el maroon lo sigue 90 ms después; al salir, el maroon se retira
   primero y el lima es el último, igual que las bandas del velo (lima en
   cabeza al cubrir, lima la última al retirarse). Los delays se cruzan en las
   reglas de hover a propósito: la transición declarada en reposo es la de
   SALIDA y la declarada bajo :hover es la de ENTRADA.
   Sigue sin cambiar el color del texto: el lima (--accent) da 2,65:1 sobre
   sage, por debajo del mínimo WCAG para texto, pero vale para un filete
   decorativo que acompaña al maroon. Tampoco usa font-weight, para no competir
   con .is-active (700 real). :focus-visible recibe el mismo tratamiento (con
   outline:none, mismo patrón que .btn-pill:focus-visible más arriba) para que
   el sitio sea navegable por teclado, no solo con ratón.
   El mismo bloque vive tres veces (aquí, .sticky-nav nav a y .mobile-menu > a)
   y en el override de movimiento reducido del final del fichero. */
.main-nav a::before {
  content: "";
  position: absolute;
  left: 0;
  bottom: -8px;
  width: 100%;
  height: 1px;
  background: var(--accent);   /* el filete lima: en cabeza al entrar, el último al salir */
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)) 0.09s;
}
.main-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -6px;
  width: 100%;
  height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
}
.main-nav a:hover::before,
.main-nav a:focus-visible::before {
  transform: scaleX(1);
  transition-delay: 0s;
}
.main-nav a:hover::after,
.main-nav a:focus-visible::after {
  transform: scaleX(1);
  transition-delay: 0.09s;
}
.main-nav a:focus-visible {
  outline: none;
}
.nav-burger {
  display: none;
}

/* ---- Buttons ---------------------------------------------------------- */
/* Hover: «halo escalonado», elegido por Chus el 2026-09-22 entre las diez del
   banco docs/bancos/btn-hover.html. Sustituye al barrido maroon con cascada
   por letras heredado de Blur (la «Opción 8»), y con él se fue el troceado
   por letras de main.js (initButtonLetters), que ya no tenía a quién servir.
   La píldora no cambia: aparece a su alrededor un halo de dos bandas
   concéntricas de lima translúcido (5px al 40% y 5px más al 15%), un
   degradado hecho con escalones duros, sin desenfoque. Las dos sombras se
   declaran también en reposo, a 0px, para que la lista interpole.
   :focus-visible recibe el mismo halo (con outline:none) para que sea
   navegable por teclado. --accent es #d6ff01; el rgba lo repite a mano
   porque una sombra no admite color-mix en todos los motores que soportamos. */
.btn-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 224px;
  height: 48px;
  padding: 0 32px;
  border-radius: 24px;
  white-space: nowrap;
  background: var(--accent);
  color: var(--ink);
  font-weight: 500;      /* XD Botones: New Atten Medium */
  font-size: 20px;       /* XD Botones 20px */
  line-height: 1;
  text-transform: uppercase; /* buttons are all-caps in the design; markup stays natural case */
  box-shadow: 0 0 0 0 rgba(214, 255, 1, 0.4), 0 0 0 0 rgba(214, 255, 1, 0.15);
  transition: box-shadow 0.35s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)), transform 0.25s ease;
}
.btn-pill:hover,
.btn-pill:focus-visible {
  box-shadow: 0 0 0 5px rgba(214, 255, 1, 0.4), 0 0 0 10px rgba(214, 255, 1, 0.15);
  outline: none;
}
.btn-pill:active {
  transform: scale(0.98);
}

/* Movimiento reducido: el halo aparece sin transición */
@media (prefers-reduced-motion: reduce) {
  .btn-pill {
    transition: none !important;
  }
}

/* ---- Hero / image bands ---------------------------------------------- */
.img-band {
  position: relative;
  overflow: hidden; /* frame for the parallax image */
}
.img-band img {
  width: 100%;
  aspect-ratio: 1184 / 656;
  object-fit: cover;
}
.img-band--tall img {
  aspect-ratio: 575 / 704;
}

/* ---- Parallax frames --------------------------------------------------- */
/* Images sit "underneath" their frame and drift slightly slower than the
   scroll. JS sets the translate; the scale gives it headroom to move. */
.frame {
  overflow: hidden;
}
.parallax-img {
  will-change: transform;
  transform: scale(1.16);
}

/* ---- Carousel --------------------------------------------------------- */
.carousel {
  position: relative;
}
.carousel .slides {
  position: relative;
  overflow: hidden;
  display: grid;
}
.carousel .slide {
  grid-area: 1 / 1; /* slides stack; visibility picks the current one */
  width: 100%;
  visibility: hidden;
  position: relative;
  overflow: hidden;
}
.carousel .slide.is-current {
  visibility: visible;
}
.carousel .slide img {
  width: 100%;
  aspect-ratio: 1184 / 656;
  object-fit: cover;
}
.carousel .slide.slide--pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 32px;
}
.carousel .slide.slide--pair img {
  aspect-ratio: 576 / 656;
}
/* curtain reveal (classes driven by main.js): la imagen entrante se revela
   con un clip-path que barre en la dirección del viaje mientras asienta
   debajo; la saliente queda quieta y se atenúa ligeramente */
.carousel .slide::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #000;
  opacity: 0;
  pointer-events: none;
}
.carousel .slide.from,
.carousel .slide.to {
  visibility: visible;
}
.carousel .slide.to {
  z-index: 2;
}
.carousel.dir-next .slide.to { clip-path: inset(0 0 0 100%); }
.carousel.dir-prev .slide.to { clip-path: inset(0 100% 0 0); }
/* transitions only under .run — start states must commit without animating */
.carousel.run .slide.to {
  clip-path: inset(0 0 0 0);
  transition: clip-path 1s cubic-bezier(0.77, 0, 0.175, 1);
}
.carousel.run .slide.to img {
  transition: transform 1s cubic-bezier(0.77, 0, 0.175, 1);
}
.carousel.run .slide::after {
  transition: opacity 1s cubic-bezier(0.77, 0, 0.175, 1);
}
.carousel.run .slide.from::after {
  opacity: 0.18;
}
/* XD slider button: solid accent circle (~50px, hoy 48 por rejilla) with an ink chevron */
.carousel-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--ink);        /* ink chevron */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  /* Hover «halo escalonado» (F1 del banco docs/bancos/aptos-botones.html,
     elegida por Chus el 2026-09-22): el mismo halo que .btn-pill, a la misma
     escala (5px al 40% y 5px al 15%). El círculo se queda lima con la flecha
     en maroon; hasta ese día se invertía a maroon con la flecha en lima. */
  box-shadow: 0 0 0 0 rgba(214, 255, 1, 0.4), 0 0 0 0 rgba(214, 255, 1, 0.15);
  transition: box-shadow 0.35s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
}
.carousel-arrow svg {
  width: 24px;
  height: 24px;
  display: block;
}
/* Hover: el halo aparece y la flecha da un único empujón contenido en la
   dirección a la que apunta, y vuelve. */
.carousel-arrow:hover,
.carousel-arrow:focus-visible {
  box-shadow: 0 0 0 5px rgba(214, 255, 1, 0.4), 0 0 0 10px rgba(214, 255, 1, 0.15);
  outline: none;
}
.carousel-arrow--prev:hover svg,
.carousel-arrow--prev:focus-visible svg {
  animation: arrow-nudge-prev 0.5s ease;
}
.carousel-arrow--next:hover svg,
.carousel-arrow--next:focus-visible svg {
  animation: arrow-nudge-next 0.5s ease;
}
@keyframes arrow-nudge-prev {
  50% { transform: translateX(-4px); }
}
@keyframes arrow-nudge-next {
  50% { transform: translateX(4px); }
}
.carousel-arrow--prev {
  left: -24px;              /* medio círculo fuera: 48/2 = 24, el centro justo en el borde de la
                               imagen. XD pide -26px (Blur -25px); la rejilla de 4 lo deja clavado */
}
.carousel-arrow--next {
  right: -24px;
}

/* Hero carousel: desktop design shows no arrows on the hero */
.carousel--hero .carousel-arrow {
  display: none;
}

/* ---- Copy sections ---------------------------------------------------- */
.copy-section {
  text-align: center;
  padding-top: 48px;
}
.copy-section h1,
.copy-section h2 {
  font-family: var(--font-display);
  font-weight: 400;        /* XD Títulos: New Atten Regular */
  font-size: 56px;         /* XD Títulos 56px */
  line-height: 1.07;       /* XD line spacing 60px */
  color: var(--ink);
}
.copy-section .lead {
  max-width: 992px;        /* 1088px era el valor de Blur; el XD pide 990px (/tmp/xd.css) */
  margin: 12px auto 0;
  font-size: 20px;         /* XD Párrafos 20px */
  line-height: 1.6;        /* XD line spacing 32px */
  color: var(--ink);
}
.copy-section .btn-pill {
  margin: 48px auto 0;
}
/* ---- Ritmo vertical entre bloques de contenido (CONVENCIÓN) -------------
   El espacio ENTRE bloques de la columna principal lo posee el propio bloque,
   marcado con la clase `.media-block`, anclado a su contenedor padre — NO al
   selector frágil de hermano adyacente `.copy-section + X` (que obligaba a
   enumerar a mano cada tipo que pudiera ir después: si se reordenaba, envolvía
   o añadía un tipo nuevo sin acordarse, el gap desaparecía — el bug recurrente
   del "botón pegado al vídeo"). Cualquier hijo directo de un `.container`
   (no-hero) que lleve `.media-block` recibe su gap superior automáticamente,
   venga después de lo que venga. `:not(:first-child)` evita un gap espurio si el
   bloque abre el contenedor; `.container--hero` queda excluido (su multimedia va
   a tope arriba). Las .copy-section conservan su propio `padding-top` para el gap
   de ARRIBA. Al añadir un bloque multimedia nuevo, NO añadas CSS: dale la clase
   `.media-block` (junto a su clase de tipo) y esta convención lo espacia. */
.container:not(.container--hero) > .media-block:not(:first-child) {
  margin-top: 64px;
}

/* ---- Two-column feature (home bottom) --------------------------------- */
.feature-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 36px;
  row-gap: 0;
  margin-top: 64px;
}
.feature-grid figure {
  overflow: hidden; /* frame for the parallax image */
}
.feature-grid figure img {
  width: 100%;
  aspect-ratio: 573 / 704;
  object-fit: cover;
}
.feature-grid h2 {
  font-family: var(--font-display);
  font-weight: 400;        /* XD Títulos */
  font-size: 56px;         /* XD 56px */
  line-height: 1.07;       /* XD 60px */
  color: var(--ink);
  margin-top: 48px;
}
.feature-grid p {
  margin-top: 12px;
  /* Sin margin-bottom a propósito. Lo llevaba (30px) de cuando el botón usaba
     `margin-top: auto`; desde que el botón fija 30px del XD, y como .feature-cell
     es flex y ahí los márgenes no colapsan, los dos se sumaban y dejaban 60px
     entre párrafo y botón. El archivo del XD tampoco lo declara. */
  font-size: 20px;         /* XD Párrafos 20px */
  line-height: 1.6;        /* XD 32px */
}
.feature-grid .btn-pill {
  /* margin-top: auto (Blur) pineaba el botón al fondo de la celda para que
     los dos botones alinearan aunque los párrafos tuvieran alturas
     distintas. El XD pide un margin-top:30px fijo (/tmp/xd.css); aplicado
     tal cual por instrucción explícita de esta ronda — si los dos párrafos
     de .feature-grid difieren en longitud, los botones podrían quedar
     desalineados entre sí. Ver informe de pulido visual. */
  margin-top: 32px;
  margin-left: auto;
  display: flex;
}
.feature-cell {
  display: flex;
  flex-direction: column;
}

/* ---- Footer ------------------------------------------------------------ */
.site-footer {
  max-width: var(--container);
  margin: 96px auto 48px;    /* margen inferior: 24px era el valor de Blur; el XD pide 48px (/tmp/xd.css) */
  border: 1px solid var(--ink);
  padding: 56px 48px 48px;
}
/* Self-drawing border (js/main.js · initFooterBorder). JS overlays an SVG that
   traces the box and hides the static border via .has-border-draw; the stroke
   draws in from the top centre once the box scrolls into view. No-JS /
   reduced-motion keeps the plain 1px border above. */
.site-footer.has-border-draw {
  position: relative;
  border-color: transparent; /* keep the 1px box geometry, hide the line */
}
.footer-border-svg {
  position: absolute;
  top: -1px;                 /* offset by the 1px border so the stroke traces the outline */
  left: -1px;
  overflow: visible;
  pointer-events: none;
}
.footer-border-svg path {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1;
  /* 0.55s delay lets the footer's fade+rise reveal settle before the frame draws */
  transition: stroke-dashoffset 1.4s ease 0.55s;
}
/* Créditos / copyright — bajo la caja del footer, alineados a su contenido */
.footer-copyright {
  max-width: var(--container);
  margin: 0 auto 48px;       /* no lateral padding: text aligns to the footer box edges */
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  font-size: 12px;
  line-height: 1.4;
  letter-spacing: 0.01em;
  color: color-mix(in srgb, var(--ink) 70%, transparent); /* subtle, a touch more contrast */
}
.footer-copyright a {
  color: var(--ink);         /* links: full ink so they stand out from the credit text */
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  transition: text-decoration-thickness 0.2s ease;
}
.footer-copyright a:hover,
.footer-copyright a:focus-visible {
  text-decoration-thickness: 2px;
}
.footer-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 48px;
}
.footer-contact {
  font-style: normal;        /* address default is italic */
  font-weight: 350;          /* XD Book */
  font-size: 21px;           /* XD 21px */
  line-height: 1.34;         /* XD line spacing 28px */
  color: var(--ink);
}
.footer-contact p {
  margin-bottom: 0.5rem;
}
/* Teléfono y dirección clicables. El reset global de `a` ya da color: inherit y
   text-decoration: none, así que por defecto NO cambia nada — decisión de Chus;
   el subrayado aparece solo en hover/focus. */
.footer-contact a:hover,
.footer-contact a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.footer-address {
  display: block;   /* el ancla envuelve dos <p>: caja de bloque = área táctil completa */
}
.footer-note {
  font-family: var(--font-display);
  font-weight: 400;          /* no declarado en el XD de Haven para este selector; se deja tal cual */
  font-size: 20px;           /* XD 20px */
  line-height: 1.2;         /* XD line spacing 24px */
  color: var(--ink);
  max-width: 512px;
}
.footer-logos {
  display: grid;
  grid-template-columns: 1fr 1fr;   /* mirror .footer-cols so the partner logos sit
                                       in the right column, aligned to the note above */
  column-gap: 48px;
  align-items: end;                 /* brand + partners share one baseline */
  margin-top: 92px;
}
.footer-brand {
  display: flex;
  align-items: center;              /* social icons centred to the logo */
}
/* Los dos iconos sociales van en su propio grupo. En escritorio no cambia nada
   (sigue siendo wordmark + iconos en una fila); existe para que en móvil el
   wordmark pueda separarse y subir al principio del footer mientras los iconos
   se quedan abajo, haciendo de separador. Se queda con el hueco del XD (90px,
   hoy 88 por rejilla), que antes vivía en .icon-youtube. */
.footer-social {
  display: flex;
  align-items: center;
  margin-left: 88px;                /* XD 90px; a rejilla (2026-09-22) */
}
.footer-partners {
  display: flex;
  align-items: flex-end;            /* partner logos on a shared baseline */
}
/* Los tres logos de partners van envueltos en un <a>. `flex` elimina la caja de
   línea del ancla: si no, el hueco del descendente (~4px bajo la imagen) los
   descolocaría de la línea base compartida. Sirve para las dos maquetaciones —
   el <a> es ítem flex en escritorio e ítem de rejilla en móvil, y en ambas se
   ajusta al logo. */
.footer-partners a {
  display: flex;
}
.footer-logos .logo-haven {
  width: 180px;              /* 120px era el valor de Blur, etiquetado "XD" por error; el XD real pide 178px (/tmp/xd.css), a rejilla 180 (2026-09-22) */
  height: auto;              /* un <img> cae en auto solo; un <svg> con viewBox no
                                lo hace de forma fiable en todos los motores, así que
                                desde que el wordmark es inline hay que declararlo.
                                Mismo motivo por el que ya lo declaran .logo-link svg,
                                .sticky-logo svg y los dos logos del velo. */
}
.footer-logos .icon-youtube {
  width: 36px;               /* el hueco (88px) lo pone ahora .footer-social */
}
.footer-logos .icon-instagram {
  width: 24px;
  margin-left: 16px;
}
.footer-logos .logo-grupo-vivir {
  width: 96px;
}
.footer-logos .logo-muchagana {
  width: 120px;
}
.footer-logos .logo-mallol {
  width: 84px;
}
/* LOS MÁRGENES VAN EN EL ANCLA, NO EN EL <img>. Desde que los tres socios están
   enlazados, el ítem flex de la fila es el <a>, y un `margin-left: auto` puesto
   en el <img> de dentro NO funciona: el ancla está ajustada al contenido, así que
   dentro no hay espacio libre que absorber y el hueco flexible se colapsa — los
   tres logos se apelotonan a la izquierda. (Hallazgo de Codex en la PR #34: se
   coló al envolver Muchagana, que hasta entonces era el único <img> suelto y por
   eso seguía funcionando.)
   `:has()` en lugar de `:nth-child()` a propósito: así no depende del orden de
   los socios. Si algún día hay que soportar un navegador sin `:has()`, el hueco
   se colapsa pero nada se rompe. Ojo: el bloque de ≤700px tiene que anular estos
   márgenes, porque allí la fila es una rejilla y un `auto` empujaría el logo
   dentro de su columna. */
.footer-partners a:has(.logo-grupo-vivir) {
  margin-left: 0;            /* first partner: left edge aligns to the note above */
}
.footer-partners a:has(.logo-muchagana) {
  margin-left: auto;        /* flexible gap: keeps Muchagana + Mallol grouped at the
                               right of the column and absorbs width as it narrows
                               (≈192px at 1280, shrinks instead of overflowing) */
}
.footer-partners a:has(.logo-mallol) {
  margin-left: 24px;
}
.footer-wrap {
  padding-inline: var(--gutter);
}
@media (min-width: 1280px) {
  .footer-wrap {
    padding-inline: 0;
  }
}

/* ---- Concepto: video -------------------------------------------------- */
.video-block {
  position: relative;
  overflow: hidden; /* frame for the parallax poster */
}
.video-block video,
.video-block .poster {
  width: 100%;
  aspect-ratio: 1184 / 656;
  object-fit: cover;
}
/* .video-block .video-cta se retiró con el botón de play: los huecos de vídeo
   arrancan solos y mudos, así que no hay nada que pulsar. Sin marcado que la
   lleve, la regla no se aplicaría a nada, y código que no puede correr es de
   lo que este proyecto deja constancia en CLAUDE.md, no algo que se conserve
   "por si acaso". El CSS del XD sigue en archive/v1-xd-original y
   check-style-xd lo listará como "solo en el archivo". */

/* ---- Concepto: renders + map ------------------------------------------ */
.render-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 32px;
  margin-top: 48px;
}
.render-pair img {
  width: 100%;
  aspect-ratio: 575 / 704;
  object-fit: cover;
}
.map-band {
  margin-top: 96px;
}
.map-band img {
  width: 100%;
}
.ubicacion-section {
  padding-top: 48px;
}
.ubicacion-section h2 {
  font-family: var(--font-display);
  font-weight: 400;        /* XD Títulos */
  font-size: 56px;         /* XD 56px */
  line-height: 1.07;       /* XD 60px */
  text-align: center;
  color: var(--ink);
}
.ubicacion-section .body {
  margin-top: 40px;
  padding-inline: 16px; /* text margin x64 vs image margin x48 */
}
.ubicacion-section .body p + p {
  margin-top: 32px;
}

/* ---- Apartamentos: plan viewer ----------------------------------------- */
.plan-viewer-outer {
  position: relative;
  margin-top: 28px;
}
.plan-viewer {
  border: 1px solid var(--ink);
  /* 540px de caja y 24/36 de padding vertical: decisión de Chus del 2026-09-10,
     revisando en local. El XD pide 766px, y esta rama pasó por ahí un momento
     para arreglar el síntoma que se reportó — que la planta iba pegada a los
     dos bordes — pero 766 agrandaba mucho la sección, así que el margen se
     resuelve en una caja de 540.

     Los tres números van acoplados y hay que moverlos juntos: 540 de caja,
     menos 24 arriba y 36 abajo, deja exactamente los 480px de `max-height` de
     .plan-figure img. Ni uno de los 19 planos se recorta ahí (la más ancha, la
     H, cabe de sobra a 596px en una pista de 750), así que todos se dibujan a
     480 y la caja mide 540 en las 19: 24px de aire arriba y 36px abajo.
     Los 25/35 originales pasaron a 24/36 el 2026-09-22 al llevar la hoja a la
     rejilla de 4 (la suma sigue siendo 60, así que el encaje no se mueve);
     540, 480, 24 y 36 son además múltiplos de 12.

     El pecado que NO hay que repetir es igualar la caja al max-height con
     padding vertical 0, que es como llegó de Blur: entonces toda planta se
     dibujaba a 540 dentro de una caja de 540 y tocaba los dos bordes. La
     justificación que traía escrita ("Blur plans are landscape") era falsa
     aquí: 15 de las 19 plantas de Haven son verticales, y las cuatro
     I/I1/J/J1 bajan a 0,48 de proporción, el doble de altas que de anchas. */
  min-height: 540px;
  padding: 24px 48px 36px;
  display: grid;
  grid-template-columns: 290px 1fr;
  column-gap: 48px;
  align-items: center;
}
.plan-info {
  /* Ancho común de divisores, fila de niveles y key plan: el de la columna,
     que es el que ya tiene el botón "Mas información" — como hijo de este
     flex en columna, align-items:stretch lo estira a los 290px de la pista
     (su contenido mide 249). El XD daba 240 a los divisores y el botón
     sobresalía. Chus (2026-09-10): igualar lo de arriba al botón, no al
     revés. Un primer intento puso 252px midiendo el contenido del botón, sin
     contar el estirado; Chus lo cazó en pantalla. */
  --plan-col-w: 100%;
  display: flex;
  flex-direction: column;
  align-self: stretch; /* fill the row height so the title sits top-left and the button bottom-left (XD) */
  /* Sin padding vertical propio, como en el XD archivado. Hasta el 2026-09-10
     llevaba 24px arriba y abajo, de cuando .plan-viewer no tenía padding
     vertical; hoy el visor ya pone 24/36 y el key plan necesita ese aire
     para caber en los 480px de la fila sin agrandar la caja. */
}
.plan-title {
  font-size: 48px;               /* XD 48px */
  line-height: 1.2;
  color: var(--ink);
}
.plan-title .plan-letter {
  font-weight: 700;              /* New Atten Bold real: 600 no existe como cara y el
                                     navegador lo sintetizaba engordando los trazos */
}
.plan-title .plan-area {
  font-weight: 400;
}
.plan-title .plan-unit {
  font-size: 0.5em;              /* XD: "M²" unit is 24px against the 48px figure; scales with the title on mobile */
}
.plan-title sup {
  font-size: 0.55em;
}
.plan-details-block {
  margin-top: 24px;              /* gap below the title (Blur's XD has no divider rules) */
}
/* ---- plan-divider: EXCLUSIVO de Haven (Blur no lo tiene: su plan-info usa
   .plan-info-actions para el toggle planta/isométrica en su lugar). Cosechado
   de archive/v1-xd-original:css/styles.css (.plan-divider / .plan-divider--bottom,
   antes con un borde del tono oscuro de marca de aquel XD) y retokenizado a
   var(--ink-25), el token al 25% que --ink-25 ya declaraba "ver
   .plan-details-block" en :root desde una tarea anterior — un divisor sutil,
   no la línea a toda fuerza de --ink que usa el borde de .plan-viewer.
   Anchura y márgenes verticales SÍ vienen literales del XD archivado
   (240px / 22px / 28px), no de Blur (que no tiene esta pieza). */
.plan-divider {
  border: 0;
  border-top: 1px solid var(--ink-25);
  width: var(--plan-col-w);      /* XD 240px; ahora el ancho de la columna, ver --plan-col-w en .plan-info */
  margin: 0 0 28px;               /* el hueco respecto al título ya lo da .plan-details-block (24px) */
}
.plan-divider--bottom {
  margin: 28px 0 0;
}
.plan-details {
  list-style: none;
  font-size: 18px;               /* XD 18px */
  line-height: 1.34;             /* XD line spacing 24px */
}
.plan-details li {
  display: grid;
  /* Tres pistas: icono, cantidad, palabra.

     Los 28px de la cantidad no son los 14px del XD, porque aquel solo tuvo que
     colocar dígitos sueltos (la única tipología con datos era la G, "1 Recámara
     / 1 Baño"). El programa real trae medios baños — "2.5 Baños" en B, B1, B2,
     I, I1, J y J1 — y "2.5" mide ~25px a 18px de cuerpo, así que en una pista
     de 14px se desbordaba encima de la palabra.

     Las tres son fijas y no max-content a propósito: cada <li> es su propia
     rejilla, así que una pista elástica alinearía cada fila en un sitio
     distinto y dejaría la lista en bandera. Con pistas fijas, las filas sin
     cantidad (jardín, balcón, espacio flex) alinean su palabra con las que sí
     la tienen. */
  grid-template-columns: 16px 28px 1fr;
  column-gap: 8px;              /* indentation between the number and the word; XD (era 10px) */
  align-items: center;
}
.plan-details li .qty {
  /* A la derecha, no a la izquierda como el XD: decisión de Chus del
     2026-09-10. La pista de la cantidad es fija (28px, para que quepa "2.5"),
     así que la alineación decide cuál de los dos huecos queda variable. Con
     text-align: left, un "1" dejaba ~19px de aire hasta su palabra y un "2.5"
     solo ~3px, y las filas no se leían igual. Pegando el número a la derecha,
     el hueco constante es el de número->palabra, que es el que importa porque
     ahí hay una frase ("2.5 Baños"); el que varía pasa a ser icono->número, y
     ahí un poco más de aire hasta separa mejor el icono del texto. */
  text-align: right;
}
/* Iconos de programa (recámaras, baños, espacio flex, jardín, balcón). Van como
   <symbol> en un sprite dentro de apartamentos.html y se referencian con <use>,
   en vez de repetir los <path> en cada <li>: el marcado del tipo A está escrito
   a mano además de generarse en JS (ver js/main.js), así que cuanto más corto
   sea, menos hay que mantener en dos sitios.

   fill/stroke se declaran aquí y no como atributos del <symbol> por el mismo
   motivo, y porque son propiedades heredables: cruzan al árbol sombra del <use>
   solas. currentColor las ata al color del texto, que es var(--ink). */
.plan-icon {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.plan-icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}
/* .plan-info-actions: sin uso en el marcado actual de Haven (el toggle que
   envuelve va desactivado, ver js/main.js) pero se conserva a propósito —
   forma parte de la receta de reactivación del toggle documentada allí (paso
   4: reponer .plan-view-toggle dentro de .plan-info). Borrarla dejaría esa
   receta incompleta. */
.plan-info-actions {
  margin-top: auto;            /* pin the toggles + button to the bottom of the info column */
  display: flex;
  flex-direction: column;
  align-items: flex-start;     /* left-align, matching title and details */
}
/* Key plan: niveles + posición de la tipología en la huella del edificio,
   entre los detalles y el botón. Los <symbol> viven en el sprite de
   apartamentos.html; fill/stroke se declaran aquí sobre los <use> porque son
   heredables y cruzan al árbol sombra solos (igual que en .plan-icon).
   Tres capas: la unidad rellena en --offwhite (decisión de Chus, 2026-09-10:
   el fondo del visor es el sage de la página, así que el verde del PDF no
   servía), las particiones de los vecinos y la huella, ambas en --ink. Los
   grosores van en unidades del viewBox (429 de ancho en 132px de caja, así
   que 1 unidad ≈ 0,3px): la huella a 4.5 (≈1,4px) y los vecinos a 3 (≈0,9px).
   La primera versión llevaba la huella a 3 y al 35%, y Chus no la veía.
   Medidas en múltiplos de 4/12 (regla nueva, ver follow-ups.md). */
.plan-keyplan {
  /* Anclado al pie junto al botón, no colgando de los detalles: las listas
     de detalles tienen 2, 3 o 4 filas, y con el key plan pegado a ellas el
     dibujo saltaba de altura al cambiar de tipología (Chus, 2026-09-10). El
     margin-top:auto que antes llevaba el botón pasa aquí; el padding es el
     hueco mínimo con los detalles cuando la columna se llena. */
  width: var(--plan-col-w);
  margin-top: auto;
  padding-top: 24px;
  margin-bottom: 24px;           /* hueco con el botón */
}
.plan-levels {
  display: flex;
  justify-content: space-between;
  column-gap: 12px;
  /* El ancho del divisor, y no los 132px del dibujo: C1 y G1 escriben
     "4 / 5 / 9 / 10 / 12 al 14", que a 14px no cabe junto a "Niveles" en 132
     y se partía en dos filas empujando el dibujo (lo cazó Codex en el PR #5).
     La cifra queda alineada al borde del divisor, no al del dibujo. */
  width: var(--plan-col-w);
  margin-bottom: 8px;
  font-size: 14px;
  line-height: 1.34;             /* mismo interlineado que .plan-details */
  color: var(--ink);
}
.plan-levels-list {
  white-space: nowrap;           /* la lista no se parte por sus barras; si no cabe, desborda visible en vez de apilarse */
}
.plan-keyplan-svg {
  display: block;
  width: 132px;
  height: auto;                  /* un <svg> con viewBox no cae en auto solo — ver CLAUDE.md, wordmark */
  margin-inline: auto;           /* centrado en la columna, bajo la fila de niveles (Chus, 2026-09-10) */
}
.plan-keyplan-unit {
  fill: var(--offwhite);
}
/* Un solo grosor y una sola opacidad para todo el plano: perímetro, pasillo,
   núcleos, patio y particiones. La versión anterior llevaba dos capas (huella
   a 4,5 y vecinos a 3) y las cajas vecinas dibujaban su pared común dos
   veces, y Chus veía trazos desiguales; ahora el <symbol> de cada
   distribución trae cada pared una vez (ver el sprite). 4 unidades ≈ 1,2px.
   linecap square para que las esquinas de aristas sueltas cierren. Acoplado
   al viewBox del <svg> visible (-2 -2 433 380): media anchura de trazo por
   lado, para que el perímetro no pierda la mitad exterior. */
.plan-keyplan-nivel {
  fill: none;
  stroke: var(--ink);
  stroke-opacity: 0.8;
  stroke-width: 4;
  stroke-linecap: square;
}
/* El botón "Mas información" ocupa, en Haven, el lugar que .plan-info-actions
   ocuparía si el toggle estuviera activo. El anclaje al pie de la columna
   (margin-top:auto, heredado del XD archivado como .plan-info .btn-pill) lo
   lleva ahora .plan-keyplan, justo encima, para que dibujo y botón bajen
   juntos; el botón solo sigue al key plan. Su ancho es el de la columna sin
   declararlo: align-items:stretch del flex (ver --plan-col-w). */
.plan-figure-col {
  display: flex;
  flex-direction: column;
  align-items: center;   /* image + toggles centered horizontally */
  justify-self: center;  /* center the whole column within its grid cell */
  align-self: center;
}
.plan-figure {
  margin: 0;
}
.plan-figure img {
  /* 480px: decisión de Chus del 2026-09-10. Acoplado al min-height y al padding
     de .plan-viewer (540 - 25 - 35 = 480) — ver el comentario largo de allí
     antes de tocar cualquiera de los tres. */
  max-height: 480px;
  width: auto;
  max-width: 100%;
}
.plan-viewer-outer .carousel-arrow--prev {
  left: -24px;              /* medio círculo fuera, como en el carrusel: 48/2 = 24 */
}
.plan-viewer-outer .carousel-arrow--next {
  right: -24px;
}

/* ---- Tira del visor de plantas -------------------------------------------
   Al cambiar de tipología, una tira (ink con el frente en lima, --strip de la
   anchura de la caja) barre el visor de lado a lado en un solo movimiento de
   0,9 s con la curva de la casa: la velocidad del barrido de 1 s del
   carrusel de fotos. Detrás de la tira js/main.js deja una copia del
   contenido saliente (.plan-ghost: un cloneNode de .plan-viewer, encima del
   real) que se recorta con clip-path al paso de la tira, así que a un lado
   se ve lo viejo, al otro lo nuevo y el corte va siempre tapado por la tira.
   Chus (2026-09-11): la primera versión cubría la caja entera con dos
   paneles y había un instante todo oscuro; con una tira basta.
   Sincronía tira/copia: la tira recorre caja + tira mientras el recorte de la
   copia recorre la caja, ambos con la misma curva y duración, así que el
   borde del recorte queda siempre dentro de la tira (por detrás de su
   frente y por delante de su cola), nunca al descubierto. Duración acoplada
   a VEIL_MS en js/main.js. Transiciones solo bajo is-run, para que el
   arranque se asiente sin animar. */
.plan-viewer-outer > .plan-ghost {
  position: absolute;
  inset: 0;
  z-index: 1;               /* encima del visor real, debajo de la tira y las flechas */
  background: var(--bg);    /* opaca: lo nuevo solo asoma por donde la tira ya pasó */
  overflow: hidden;
  pointer-events: none;
  clip-path: inset(0 0 0 0);
}
.plan-veil {
  position: absolute;
  inset: 1px;               /* dentro del borde de .plan-viewer */
  z-index: 1;               /* después de la copia en el DOM: se pinta encima */
  overflow: hidden;
  display: none;
  pointer-events: none;
  --strip: 0.33;            /* anchura de la tira, fracción de la caja: 0.22 al nacer,
                               un 50 % más ancha a petición de Chus (2026-09-11) */
}
.plan-veil.is-armed { display: block; }
.plan-veil-strip {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: calc(var(--strip) * 100%);
  background: var(--ink);
  /* dir-next: espera pegada al borde derecho (su 100% es su propia anchura,
     así que la caja son 100% / --strip) */
  transform: translateX(calc(100% / var(--strip)));
}
.plan-veil-strip::before {  /* el frente en lima */
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 22%;
  background: var(--accent);
}
.plan-veil.dir-prev .plan-veil-strip { transform: translateX(-100%); } /* espera al izquierdo */
.plan-veil.dir-prev .plan-veil-strip::before { left: auto; right: 0; } /* el frente mira a la derecha */
/* en marcha: la tira cruza y la copia se recorta tras ella. VEIL_MS = 0.9s. */
.plan-veil.is-run .plan-veil-strip {
  transform: translateX(-100%);
  transition: transform 0.9s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
}
.plan-veil.dir-prev.is-run .plan-veil-strip { transform: translateX(calc(100% / var(--strip))); }
.plan-viewer-outer > .plan-ghost.is-run {
  clip-path: inset(0 100% 0 0);   /* dir-next: se va por la derecha, detrás de la tira */
  transition: clip-path 0.9s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
}
.plan-viewer-outer > .plan-ghost.dir-prev.is-run { clip-path: inset(0 0 0 100%); }
@media (prefers-reduced-motion: reduce) {
  /* main.js ya cambia en seco sin armar la tira; esto es el cinturón */
  .plan-veil-strip, .plan-viewer-outer > .plan-ghost { transition: none !important; }
}

.plan-selector {
  display: grid;
  grid-template-columns: max-content 1fr; /* label column sizes to the wider word ("Apartamentos"), so both button rows start at the same x */
  align-items: center;
  gap: 20px;              /* 28px (row-gap/column-gap) era el valor de Blur; el XD pide 20px (/tmp/xd.css) */
  /* 32px, no los 57px del XD: decisión de Chus del 2026-09-10. Y de paso el
     primer valor puesto con la regla nueva de la rejilla (múltiplos de 3 o 4,
     preferiblemente 6 o 12), que está pendiente de aplicar al resto de la hoja
     — ver docs/superpowers/plans/follow-ups.md. 57 no es múltiplo de nada. */
  margin-top: 32px;
}
.selector-row {
  display: contents; /* label + button group become direct grid items, so letters align in columns across rows */
}
.selector-buttons {
  display: flex;
  flex-wrap: wrap;   /* Haven: 19 botones en una sola fila (Blur: máx. 10) no
                         caben en los 1184px del container ni a escritorio —
                         a diferencia de Blur, aquí el wrap no puede depender
                         solo del breakpoint móvil de abajo. */
  /* 12px: decisión de Chus del 2026-09-10, acoplada a los botones de 48px de
     abajo. El XD pide 14px sobre botones de 61px; con 19 botones a 48px el
     aire relativo se mantiene y la fila entra más holgada. */
  gap: 12px;
}
.plan-selector .selector-label {
  font-size: 28px;               /* XD 28px */
  color: var(--ink);
  white-space: nowrap;
  margin-right: 4px;             /* XD (ausente hasta esta ronda) */
}
.plan-selector button {
  /* 48px: decisión de Chus del 2026-09-10, revisando en local. El XD pide 61px,
     pensados para el puñado de tipologías de aquel diseño; Haven tiene 19
     botones y a 61px la fila se parte y pesa demasiado para lo que es, un
     selector. Coincide con el valor de Blur por casualidad, no por herencia:
     aquí es una decisión tomada, no un número sin revisar. */
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid var(--ink);
  color: var(--ink);
  font-weight: 500;      /* XD New Atten Medium */
  font-size: 24px;       /* XD 24px */
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Hover «halo escalonado» (S1 del banco docs/bancos/aptos-botones.html,
     elegida por Chus el 2026-09-22): el halo de .btn-pill a escala del
     círculo, 4px al 40% y 4px al 15%, para que a 12px de separación los halos
     vecinos no se toquen. Hasta ese día el selector no tenía hover. */
  box-shadow: 0 0 0 0 rgba(214, 255, 1, 0.4), 0 0 0 0 rgba(214, 255, 1, 0.15);
  transition: box-shadow 0.35s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)), background 0.15s ease, color 0.15s ease;
}
/* La activa no lleva halo: ya no es un destino. */
.plan-selector button:not(.is-active):hover,
.plan-selector button:not(.is-active):focus-visible {
  box-shadow: 0 0 0 4px rgba(214, 255, 1, 0.4), 0 0 0 8px rgba(214, 255, 1, 0.15);
  outline: none;
}
.plan-selector button.is-active {
  background: var(--ink);
  color: var(--accent);
}
/* ---- Plan view toggle (Planta / Isométrico + Planta Alta / Baja) -------- */
.plan-view-toggle,
.plan-floor-toggle {
  display: flex;
  gap: 8px;
  margin-top: 20px;
  margin-bottom: 8px;
}
.plan-toggle-btn {
  padding: 8px 12px;
  border-radius: 20px;
  border: 1px solid var(--ink);
  color: var(--ink);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  background: transparent;
  transition: background 0.15s ease, color 0.15s ease;
}
.plan-toggle-btn.is-active {
  background: var(--ink);
  color: var(--accent);
}
.plan-area-detail {
  font-size: 0.42em;
  font-weight: 400;
  opacity: 0.75;
  white-space: nowrap;
}

/* ---- Invierte ----------------------------------------------------------- */
.invierte-hero-copy {
  padding-top: 48px;
  padding-inline: 16px;
}
.invierte-hero-copy h1 {
  font-family: var(--font-display);
  font-weight: 400;        /* XD Títulos */
  font-size: 56px;         /* XD 56px */
  line-height: 1.07;       /* XD 60px */
  color: var(--ink);
}
.lead-form {
  margin-top: 60px;        /* 90px era el valor de Blur; el XD pide 60px (/tmp/xd.css) */
  padding-inline: 16px;
}
.lead-form .form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 64px;
  row-gap: 48px;
}
.lead-form .field--full {
  grid-column: 1 / -1;
}
.lead-form .field {
  position: relative; /* anchors the floating <label> */
}
.lead-form .field input {
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink);
  border-radius: 0;
  font-family: var(--font-display); /* XD: var(--font-serif), Bodoni 72 */
  font-size: 32px;       /* XD 32px */
  line-height: 1.3;
  color: var(--ink);
  padding: 0 0 12px;
}
/* Placeholder is a single blank space (see invierte.html) — kept invisible so
   only the floating <label> is ever the visible cue. */
.lead-form .field input::placeholder {
  color: transparent;
}
/* Hover/foco del campo: «dos filetes con icono dentro», elegido por Chus el
   2026-09-22 entre las seis del banco docs/bancos/form-hover.html. Es la
   cortina en miniatura del nav llevada al formulario: en reposo la línea del
   input va al 25 %; al pasar el ratón o con el foco, el maroon (::before,
   sobre la línea) se despliega desde la izquierda con el lima (::after, 2px
   más abajo) en cabeza, y al salir el lima es el último. Los delays se
   cruzan como en el nav: la transición declarada en reposo es la de SALIDA
   y la declarada bajo hover/foco es la de ENTRADA. Hasta ese día la línea
   pasaba de maroon a lima, sin más.
   El icono del campo (persona, sobre, teléfono: <svg class="field-icon">
   inline en invierte.html, antes del input) sale SOLO con el ratón encima,
   a petición de Chus: el foco no lo muestra, pero sí los filetes, para que
   quien escribe o navega con teclado tenga un estado. Al entrar, el texto y
   la etiqueta se apartan 40px para dejarle sitio; con el campo relleno, icono
   y desplazamiento se quedan. */
.lead-form .field input:focus {
  outline: none;
}
.lead-form .field input,
.lead-form .field input:hover,
.lead-form .field input:focus {
  border-bottom-color: var(--ink-25);
  transition: padding-left 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
}
.lead-form .field::before,
.lead-form .field::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
}
.lead-form .field::before {
  bottom: 0;                      /* sobre la línea del input */
  background: var(--ink);
  transition: transform 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
}
.lead-form .field::after {
  bottom: -3px;                   /* el filete lima, 2px por debajo */
  background: var(--accent);
  transition: transform 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)) 0.09s;
}
.lead-form .field:hover::before,
.lead-form .field:focus-within::before {
  transform: scaleX(1);
  transition-delay: 0.09s;
}
.lead-form .field:hover::after,
.lead-form .field:focus-within::after {
  transform: scaleX(1);
  transition-delay: 0s;
}
.lead-form .field-icon {
  position: absolute;
  left: 0;
  top: 8px;                       /* a ojo centrado con la línea de texto del input (32px × 1,3 = 41,6; el centro exacto cae en 8,8) */
  width: 24px;
  height: 24px;
  color: var(--ink);
  opacity: 0;
  transform: translateX(-8px);
  pointer-events: none;
  transition: opacity 0.3s ease, transform 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
}
/* Con el campo relleno, el icono se queda y el texto sigue apartado (Chus,
   2026-09-22): solo desaparece si el input sigue vacío. `:has()` ya es
   requisito del sitio (ver .plan-details-block). */
.lead-form .field:hover .field-icon,
.lead-form .field:has(input:not(:placeholder-shown)) .field-icon {
  opacity: 1;
  transform: none;
}
.lead-form .field:hover input,
.lead-form .field input:not(:placeholder-shown) {
  padding-left: 40px;
}
.lead-form .field:hover label,
.lead-form .field input:not(:placeholder-shown) + label {
  left: 40px;
}
/* Floating label: sits exactly over the input text at rest, then lifts up and
   shrinks on focus — or once the field holds content — leaving room to type.
   Material-inspired, but restrained to match the site's motion north star. */
.lead-form .field label {
  position: absolute;
  left: 0;
  top: 0;                /* aligns with the input text (input has no top padding) */
  font-family: var(--font-display); /* matches the input; no XD rule for this
                                        selector on its own, kept in sync with
                                        .lead-form .field input (ver informe
                                        de la tarea 5, ronda 2) */
  font-size: 32px;       /* matches the input so it overlays the text at rest */
  line-height: 1.3;
  color: var(--ink);
  white-space: nowrap;
  pointer-events: none;  /* clicks fall through to the input */
  transform-origin: left top;
  transition: transform 0.3s cubic-bezier(0.65, 0, 0.35, 1),
              color 0.3s ease,
              opacity 0.3s ease,
              left 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)); /* se aparta del icono en hover */
}
.lead-form .field input:focus + label,
.lead-form .field input:not(:placeholder-shown) + label {
  transform: translateY(-0.9em) scale(0.55); /* sits just above its own input */
  color: var(--ink);  /* --grey (#707070) sobre sage casi no contrastaba (Chus,
                         2026-09-22); maroon al 70 % sigue siendo discreta */
  opacity: 0.7;
}
/* Honeypot anti-spam field — pushed off-screen instead of display:none so naive
   bots still render and fill it (the JS then drops those submissions before they
   are POSTed). aria-hidden + tabindex=-1 keep it away from humans and AT. */
.lead-form .hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.lead-form .form-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 56px;
}
/* Submit button while a lead is being sent — dim + not-allowed cursor. */
.lead-form button[aria-busy="true"] {
  opacity: 0.6;
  cursor: not-allowed;
}
.lead-form .form-feedback {
  font-size: 20px;
  margin-top: 16px;
  text-align: right;
  min-height: 32px;
}

.invest-section {
  margin-top: 80px;
  padding-inline: 16px;
}
.invest-section .intro-rule {
  border: 0;
  border-top: 1px solid var(--ink);
  margin-bottom: 28px;
}
.invest-section h2 {
  font-family: var(--font-display);
  font-weight: 400;        /* XD Títulos */
  font-size: 56px;         /* XD 56px */
  line-height: 1.07;       /* XD 60px */
  color: var(--ink);
}
.invest-section .intro {
  margin-top: 24px;
  font-size: 32px;         /* XD 32px */
  line-height: 1.32;       /* XD line spacing 42px */
  color: var(--ink);
  max-width: 1152px;
}
.invest-rows {
  margin-top: 64px;
}
.invest-row {
  display: grid;
  grid-template-columns: 400px 1fr;
  column-gap: 64px;
  border-top: 1px solid var(--ink);
  padding: 36px 0 48px;
}
.invest-row h3 {
  font-family: var(--font-display);
  font-weight: 400;        /* XD Subtítulos: New Atten Regular */
  font-size: 48px;         /* XD Subtítulos 48px */
  line-height: 1;          /* XD line spacing 48px */
  color: var(--ink);
}
.invest-row p {
  font-size: 24px;         /* XD Párrafos 24px */
  line-height: 1.34;       /* XD 32px */
  color: var(--ink);
  max-width: 684px;
}

/* ---- Scroll-reveal ------------------------------------------------------ */
/* .reveal is added by JS (progressive enhancement) so content is never
   hidden when scripts don't run. */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}
.reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* ---- Cortina de entrada de fotos y vídeos ------------------------------
   Elegida por Chus el 2026-09-22 entre las nueve del banco
   docs/bancos/img-reveal.html (la «cortina lateral», la tira del visor de
   plantas) y validada con variación por bloque en el estudio de la home.
   Sustituye al desenfoque blur(16px)→0 («Opción 6») que había desde Blur.

   Cómo funciona: cada bloque de media que se revela lleva en el marcado
   `data-cortina="<dir> <ms> [<retardo ms>]"` (dir: ltr, rtl, ttb, btt).
   initReveal (js/main.js) le mete una cubierta .ct-cover (tira maroon con
   el frente lima, 33 % de la caja, más un panel sage) y la clase ct-<dir>
   con --ct-ms y --ct-delay; cuando el observador añade .is-visible, la
   cubierta cruza la caja y la foto asoma por donde la tira ya pasó. La
   dirección, la duración y el retardo son DECISIÓN POR BLOQUE, escritas en
   el marcado: la transición es notoria a propósito y no debe repetirse igual
   dos veces seguidas; dos fotos que entran a la vez van enfrentadas y la
   segunda con retardo (ver los data-cortina de cada página).

   CRÍTICO — nada de esto toca el transform de la <img>/<video>: initParallax
   escribe style.transform en cada frame sobre esas mismas imágenes en
   escritorio, y transicionarlo produciría movimiento borroso. La cortina vive
   en la cubierta. Con movimiento reducido no hay cubierta (initReveal ni
   siquiera corre) y el bloque simplemente está. */

/* El bloque con cortina no se funde ni sube: su entrada es la cortina. En las
   celdas de la home, el texto sí conserva el fundido+subida de .reveal. */
[data-cortina].reveal {
  opacity: 1;
  transform: none;
}
.feature-cell[data-cortina].reveal h2,
.feature-cell[data-cortina].reveal p,
.feature-cell[data-cortina].reveal .btn-pill {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}
.feature-cell[data-cortina].reveal.is-visible h2,
.feature-cell[data-cortina].reveal.is-visible p,
.feature-cell[data-cortina].reveal.is-visible .btn-pill {
  opacity: 1;
  transform: none;
}
.ct-host { position: relative; }   /* el anfitrión de la cubierta (figure, .frame o el propio .video-block) */
.ct-cover {
  position: absolute;
  inset: 0;
  display: flex;
  pointer-events: none;
  z-index: 3;
  transition: transform var(--ct-ms, 0.9s) var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)) var(--ct-delay, 0s);
}
.ct-strip { position: relative; flex: 0 0 25%; background: var(--ink); }   /* 25 % de la cubierta = 33 % de la caja */
.ct-strip::after { content: ""; position: absolute; background: var(--accent); }  /* el frente lima, 22 % de la tira, como en el visor */
.ct-sage { flex: 1 1 auto; background: var(--bg); }
/* izquierda → derecha */
.ct-ltr .ct-cover { left: -33.4%; width: 133.4%; flex-direction: row; }
.ct-ltr .ct-strip { order: 0; }
.ct-ltr .ct-sage  { order: 1; }
.ct-ltr .ct-strip::after { top: 0; bottom: 0; right: 0; width: 22%; }
.ct-ltr.is-visible .ct-cover { transform: translateX(100%); }
/* derecha → izquierda */
.ct-rtl .ct-cover { left: 0; width: 133.4%; flex-direction: row; }
.ct-rtl .ct-strip { order: 1; }
.ct-rtl .ct-sage  { order: 0; }
.ct-rtl .ct-strip::after { top: 0; bottom: 0; left: 0; width: 22%; }
.ct-rtl.is-visible .ct-cover { transform: translateX(-100%); }
/* arriba → abajo */
.ct-ttb .ct-cover { top: -33.4%; height: 133.4%; flex-direction: column; }
.ct-ttb .ct-strip { order: 0; }
.ct-ttb .ct-sage  { order: 1; }
.ct-ttb .ct-strip::after { left: 0; right: 0; bottom: 0; height: 22%; }
.ct-ttb.is-visible .ct-cover { transform: translateY(100%); }
/* abajo → arriba */
.ct-btt .ct-cover { top: 0; height: 133.4%; flex-direction: column; }
.ct-btt .ct-strip { order: 1; }
.ct-btt .ct-sage  { order: 0; }
.ct-btt .ct-strip::after { left: 0; right: 0; top: 0; height: 22%; }
.ct-btt.is-visible .ct-cover { transform: translateY(-100%); }

/* ---- Sticky nav (appears on scroll-up) ---------------------------------- */
.sticky-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--bg);
  border-bottom: 1px solid var(--ink-25);
  transform: translateY(-100%);
  visibility: hidden;
  transition: transform 0.3s ease, visibility 0.3s;
}
.sticky-nav.is-visible {
  transform: translateY(0);
  visibility: visible;
}
.sticky-nav-inner {
  max-width: var(--container);
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px var(--gutter);   /* XD: 10px. 14px en la ronda de pulido; a 24px el
                                     2026-09-11 porque a Chus la barra le seguía
                                     pareciendo estrecha. Con el logo a 164px (~19px
                                     de alto) queda en ~68px frente a los ~38px del
                                     XD. 24 = múltiplo de 12 (rejilla). */
}
@media (min-width: 1280px) {
  .sticky-nav-inner {
    padding-inline: 0;
  }
}
.sticky-nav .sticky-logo img,
.sticky-nav .sticky-logo svg {
  width: 164px;   /* 85px era el de Blur y el XD pide 150px; 165px fue petición de
                     Chus en la ronda de pulido, junto con el padding de
                     .sticky-nav-inner; 164 es ese mismo 165 llevado a la rejilla
                     de 4 el 2026-09-22 (0,6 % más estrecho, ~18,9px de alto). */
  height: auto;
}
.sticky-nav nav {
  display: flex;
  align-items: baseline;
  gap: 36px;
}
.sticky-nav nav a {
  font-size: 16px;         /* XD New Atten Regular 16px */
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.06em;  /* XD character spacing 0.06em */
  text-transform: uppercase;
  color: var(--ink);
  position: relative;      /* ancla del subrayado de hover/foco (::after) */
}
.sticky-nav nav a.is-active {
  font-weight: 700;        /* New Atten Bold real */
}
/* Misma cortina en miniatura que .main-nav a; ver el comentario de esa regla
   para el razonamiento completo. */
.sticky-nav nav a::before {
  content: "";
  position: absolute;
  left: 0;
  bottom: -7px;
  width: 100%;
  height: 1px;
  background: var(--accent);   /* el filete lima: en cabeza al entrar, el último al salir */
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)) 0.09s;
}
.sticky-nav nav a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -5px;
  width: 100%;
  height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
}
.sticky-nav nav a:hover::before,
.sticky-nav nav a:focus-visible::before {
  transform: scaleX(1);
  transition-delay: 0s;
}
.sticky-nav nav a:hover::after,
.sticky-nav nav a:focus-visible::after {
  transform: scaleX(1);
  transition-delay: 0.09s;
}
.sticky-nav nav a:focus-visible {
  outline: none;
}
.sticky-nav .nav-burger {
  /* Menos aire que la hamburguesa del header (4px), que es para lo que existe
     esta regla. Era 2px; en la rejilla de 4 el empate 0/4 se resuelve a 0 para
     no igualarla al header y dejar la regla sin efecto (2026-09-22). */
  padding: 0;
}

/* ---- Mobile menu overlay ------------------------------------------------ */
.mobile-menu {
  display: none;
}

/* ---- Mobile menu open/close veil (echoes the page-transition veil) -------
   A sage panel with ink + accent bands behind rises from below to cover, then
   lifts to reveal — the same choreography as css/transitions.css, minus the
   parallax logo (misma paleta que la cortina de página, decisión de Chus del
   2026-09-10).
   The .menu-veil layer is injected into <body> once by initMenu() in
   js/main.js and driven by the same is-armed / is-cover / is-reveal flips.
   Shares --vt-ease with a literal fallback so this file doesn't hard-depend on
   transitions.css. Durations mirror the COVER_MS / REVEAL_MS in js/main.js. */
.menu-veil {
  position: fixed;
  inset: 0;
  z-index: 9000;           /* over the menu (30) + header; under the nav veil (9999) */
  display: none;
  pointer-events: none;
}
.menu-veil.is-armed { display: block; }
.menu-veil.is-cover,
.menu-veil.is-reveal { pointer-events: auto; }  /* block leak-through while active */

.menu-veil-panel {
  position: absolute;
  inset: 0;
  transform: translateY(101vh);   /* waits just below the viewport */
}
.menu-veil-accent { z-index: 1; background: var(--accent); }
.menu-veil-ink    { z-index: 2; background: var(--ink); }
.menu-veil-sage   { z-index: 3; background: var(--bg); }   /* sage in front, it is what covers */

/* cover: rise from below to cover; accent leads, then ink, sage last
   (COVER_MS in js/main.js = the sage) */
.menu-veil.is-cover .menu-veil-sage   { transform: translateY(0); transition: transform 0.64s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)); }
.menu-veil.is-cover .menu-veil-ink    { transform: translateY(0); transition: transform 0.53s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)); }
.menu-veil.is-cover .menu-veil-accent { transform: translateY(0); transition: transform 0.43s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)); }
/* reveal: sage lifts first, ink and then accent linger. Declared AFTER cover
   so it wins source-order while is-cover is still applied during the reveal
   (REVEAL_MS in js/main.js = the accent). */
.menu-veil.is-reveal .menu-veil-sage   { transform: translateY(-101vh); transition: transform 0.64s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)); }
.menu-veil.is-reveal .menu-veil-ink    { transform: translateY(-101vh); transition: transform 0.77s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)); }
.menu-veil.is-reveal .menu-veil-accent { transform: translateY(-101vh); transition: transform 0.90s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)); }

/* Reduced motion: initMenu() skips the veil (instant toggle); belt-and-braces
   so no panel or menu link ever animates. */
@media (prefers-reduced-motion: reduce) {
  .menu-veil-panel { transition: none !important; }
  .mobile-menu.is-open > a,
  .mobile-menu.is-open .lang-toggle-mobile { transition: none !important; }
}

/* ==========================================================================
   Responsive — per iPhone 14 Pro Max artboard (430×932)
   ========================================================================== */
@media (max-width: 900px) {
  .copy-section h1,
  .copy-section h2,
  .feature-grid h2,
  .ubicacion-section h2,
  .invierte-hero-copy h1,
  .invest-section h2 {
    font-size: 44px; /* desktop XD 56px, escalado para tablet */
  }
  .invest-row {
    grid-template-columns: 1fr;
    row-gap: 16px;
  }
  .invest-row h3 {
    font-size: 38px; /* desktop XD 48px */
  }
  .plan-viewer {
    grid-template-columns: 1fr;
    /* row-gap 0 y no los 32px del XD: apilado, el orden que pidió Chus
       (2026-09-10) es título, detalles, PLANTA, key plan, botón, y key plan y
       botón viven dentro de .plan-info mientras la planta es su hermana. Para
       intercalarla, .plan-info disuelve su caja (display: contents) y sus
       hijos entran en esta rejilla con `order`; los huecos los ponen los
       márgenes de cada pieza (26 título->detalles, 32 el resto), no la
       rejilla, que si no los duplicaba. Registrado en check-style-xd.py. */
    row-gap: 0;
    min-height: 0;
  }
  .plan-info {
    display: contents;
  }
  .plan-title          { order: 1; }
  .plan-details-block  { order: 2; }
  .plan-figure-col     { order: 3; margin-top: 32px; }
  .plan-keyplan        { order: 4; }
  .plan-info > .btn-pill { order: 5; }
  /* Apilado a 1 columna: margin-top:auto ya no tiene fila que llenar (el grid
     pasa a alto de contenido), así que el pin al pie se sustituye por un
     margen fijo — mismo valor que el XD archivado en este breakpoint. */
  .plan-info > .btn-pill {
    margin-top: 32px;
  }
  /* Apilado, el anclaje al pie no aplica: el key plan cuelga de los detalles
     con un margen fijo, y el botón es el único dueño del hueco de abajo (los
     márgenes de dos flex items no colapsan, y 24 + 32 daban 56px; Codex,
     PR #5). */
  .plan-keyplan {
    margin-top: 32px;
    padding-top: 0;
    margin-bottom: 0;
  }
  /* Half-width column can't fit all three partner logos — stack the wordmark/social
     group over the partners so each gets the full width. */
  .footer-logos {
    grid-template-columns: 1fr;
    row-gap: 32px;
  }
}

@media (max-width: 1279px) {
  .main-nav {
    gap: 32px;
  }
}

@media (max-width: 1024px) {
  /* Punto de corte deliberado, NO un descuido: el XD de Haven solo trae
     artboards de 1280 y 430, así que no dice nada sobre dónde debe aparecer
     el burger (su equivalente, `.mobile-menu`, vive en el XD a partir de
     700px). 1024px es el valor que trajo Blur, ya pulido en esa rama, y es
     justo lo que este rebase existe para heredar (Tarea 5, ronda de
     correcciones 3). No lo "corrijas" a 700px sin una decisión explícita. */
  /* header: replace text nav with burger */
  .nav-row {
    align-items: center;
  }
  .sticky-nav nav {
    display: none;
  }
  .main-nav {
    display: none;
  }
  .nav-burger {
    display: block;
    width: 32px;
    padding: 4px 0;
  }
  .nav-burger span {
    display: block;
    height: 2px;
    background: var(--ink);
    margin: 8px 0;
  }

  .mobile-menu.is-open {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 36px;
    position: fixed;
    inset: 0;
    background: var(--bg);
    z-index: 30;
  }
  .mobile-menu > a {
    font-size: 24px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink);
    position: relative;    /* ancla del subrayado de hover/foco (::after) */
  }
  .mobile-menu > a.is-active {
    font-weight: 700;      /* New Atten Bold real */
  }
  /* Mismo tratamiento de hover/foco que .main-nav a (ver comentario junto a
     esa regla) — aquí importa sobre todo para :focus-visible, ya que en
     móvil el hover real no aplica (no hay puntero). */
  .mobile-menu > a::before {
    content: "";
    position: absolute;
    left: 0;
    bottom: -8px;
    width: 100%;
    height: 1px;
    background: var(--accent);   /* el filete lima: en cabeza al entrar, el último al salir */
    transform: scaleX(0);
    transform-origin: 0 50%;
    transition: transform 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)) 0.09s;
  }
  .mobile-menu > a::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -6px;
    width: 100%;
    height: 1px;
    background: var(--ink);
    transform: scaleX(0);
    transform-origin: 0 50%;
    transition: transform 0.3s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
  }
  .mobile-menu > a:hover::before,
  .mobile-menu > a:focus-visible::before {
    transform: scaleX(1);
    transition-delay: 0s;
  }
  .mobile-menu > a:hover::after,
  .mobile-menu > a:focus-visible::after {
    transform: scaleX(1);
    transition-delay: 0.09s;
  }
  .mobile-menu > a:focus-visible {
    outline: none;
  }
  .mobile-menu .lang-toggle-mobile {
    font-family: var(--font-helvetica);
    font-size: 14px;
    display: flex;
    gap: 0.35em;
  }
  .mobile-menu .menu-close {
    position: absolute;
    top: 20px;
    right: 24px;
    font-size: 32px;
    line-height: 1;
    color: var(--ink);
  }

  /* Links cascade in as the veil clears (staggered fade + rise). The hidden
     base state applies only while open; .links-in (added by initMenu() once
     the reveal starts) relays them in. The × stays visible so it's reachable
     immediately. */
  /* Direct-child scoped: the language toggle's ES/EN are <a> too, but they live
     inside .lang-toggle-mobile and must NOT pick up nav sizing, the faux-bold, or
     the per-link stagger — as nth-of-type children of their own wrapper they would
     otherwise fire at 0.05s/0.11s, ahead of the 0.35s their container animates on. */
  .mobile-menu.is-open > a,
  .mobile-menu.is-open .lang-toggle-mobile {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity 0.5s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1)),
                transform 0.5s var(--vt-ease, cubic-bezier(0.77, 0, 0.175, 1));
  }
  .mobile-menu.is-open.links-in > a,
  .mobile-menu.is-open.links-in .lang-toggle-mobile {
    opacity: 1;
    transform: translateY(0);
  }
  .mobile-menu.is-open.links-in > a:nth-of-type(1) { transition-delay: 0.05s; }
  .mobile-menu.is-open.links-in > a:nth-of-type(2) { transition-delay: 0.11s; }
  .mobile-menu.is-open.links-in > a:nth-of-type(3) { transition-delay: 0.17s; }
  .mobile-menu.is-open.links-in > a:nth-of-type(4) { transition-delay: 0.23s; }
  .mobile-menu.is-open.links-in > a:nth-of-type(5) { transition-delay: 0.29s; }
  .mobile-menu.is-open.links-in .lang-toggle-mobile { transition-delay: 0.35s; }
}

@media (max-width: 700px) {
  :root {
    --gutter: 24px;
  }
  body {
    font-size: 18px;
    line-height: 1.55;
  }

  /* header: compact mobile sizing */
  .lang-toggle {
    display: none;
  }
  .sticky-nav .sticky-logo img,
  .sticky-nav .sticky-logo svg {
    width: 108px;   /* XD: 100px. Sube en la misma proporción que el valor de
                       escritorio (164px) para que la barra no quede apretada en
                       móvil comparada con la de escritorio. Era 110px; 108 es el
                       múltiplo de 12 del empate 108/112 y además conserva mejor
                       esa proporción (0,659 frente a 0,667). */
  }
  .sticky-nav-inner {
    padding: 16px var(--gutter);   /* XD: 8px. 11px en la ronda de pulido; 18px el
                                      2026-09-11, subiendo con escritorio (24px) para
                                      que móvil no quede apretado en comparación.
                                      16px desde el 2026-09-22 (rejilla de 4). */
  }
  .nav-row {
    padding-top: 16px;
    padding-bottom: 16px;
  }
  .logo-link img,
  .logo-link svg {
    /* 90px era el valor de Blur; el XD pide 112px en móvil
       (/tmp/xd.css .logo-link img a max-width:700px). margin-bottom:-14px
       (Blur) retirado por el mismo motivo que en escritorio — el archivo no
       lo declara. OJO aparte: el archivo pone .nav-row { align-items:
       center } en este breakpoint y aquí se mantiene flex-end (heredado del
       root, sin override) — no se ha tocado por quedar fuera del alcance de
       esta ronda; si el logo se ve desalineado en móvil, es la primera
       propiedad a revisar. */
    width: 112px;
  }

  /* hero: full bleed per mobile artboard */
  .container--hero {
    padding-inline: 0;
  }
  /* DOS RATIOS MÓVILES, deliberadamente (CONVENCIÓN) — el 1184/656 de
     escritorio es el ancho del artboard de 1280 y en el móvil no significa
     nada: dejaba los bloques en ~212px de alto, un sello de correos justo
     donde va el material más caro. Se sustituye por:

       HERO      430/500 vertical (≈0.86) — como lo dibujó el XD. Es lo
                 primero que se ve y llena la pantalla; que sea el bloque
                 más vertical del móvil es una decisión, no un descuido.
       CONTENIDO 500/430 apaisado (≈1.16) — la misma proporción girada,
                 a sangre (ver el full-bleed abajo). 430×370.

     El apaisado no es solo estética: los renders de recursos/ son TODOS
     16:9, así que cada punto de verticalidad se paga tirando ancho con
     object-fit: cover. El 430/500 solo dejaba ver el 48% del render; el
     500/430 a sangre enseña el 65%.
     Los pares (.slide--pair, .render-pair) y el feature-grid quedan FUERA:
     ya se apilan a 1 columna y traen su propio ratio vertical (~0.82). */
  .img-band--hero img,
  .container--hero .carousel .slide img,
  .container--hero .video-block video,
  .container--hero .video-block .poster {
    aspect-ratio: 430 / 500;
  }
  .media-block.carousel .slide:not(.slide--pair) img,
  .media-block.video-block video,
  .media-block.video-block .poster {
    aspect-ratio: 500 / 430;
  }
  /* A SANGRE — los bloques de media de contenido se comen el gutter del
     .container y tocan los bordes de la pantalla, mientras el texto
     conserva el suyo. No es un patrón nuevo: el hero ya sangra (ver
     .container--hero arriba); esto lo extiende al resto del media.
     Se usa margin negativo y no width:100vw a propósito — 100vw incluye
     la barra de scroll en algunos navegadores y provoca scroll horizontal.
     El margen negativo compensa exactamente el padding del contenedor. */
  .container:not(.container--hero) > .media-block {
    margin-inline: calc(var(--gutter) * -1);
  }
  /* mobile artboard shows the accent circle arrows on the hero */
  .carousel--hero .carousel-arrow {
    display: inline-flex;
  }
  /* Círculo también en móvil: decisión de Chus (2026-09-10, PR #5). El XD
     pide aquí 72x24 (la flecha plana), y una ronda anterior lo aplicó tal
     cual dejando el border-radius:50% de escritorio, con lo que salía un
     óvalo. 48 y no los 44 de Blur: múltiplo de 12 (ver follow-ups.md) y el
     mismo lado que los botones del selector en este breakpoint. El chevron
     vuelve a los 24px de escritorio. Registrado en KNOWN_INTENTIONAL_DIVERGENCES. */
  .carousel-arrow {
    width: 48px;
    height: 48px;
  }
  .carousel-arrow svg {
    width: 24px;
    height: 24px;
  }
  .carousel-arrow--prev {
    left: 12px;
  }
  .carousel-arrow--next {
    right: 12px;
  }

  .copy-section {
    padding-top: 32px;
  }
  .copy-section h1,
  .copy-section h2,
  .feature-grid h2,
  .ubicacion-section h2,
  .invierte-hero-copy h1,
  .invest-section h2 {
    font-size: 38px; /* Bodoni 72 Book 38px per mobile artboard */
    line-height: 1.15;
  }
  .copy-section .lead {
    margin-top: 16px;
  }
  .copy-section .btn-pill {
    margin-top: 32px;
  }
  /* Ritmo vertical (CONVENCIÓN) — versión móvil; ver nota en la regla desktop. */
  .container:not(.container--hero) > .media-block:not(:first-child) {
    margin-top: 40px;
  }

  .carousel .slide.slide--pair {
    grid-template-columns: 1fr;
    gap: 24px;
  }
  .feature-grid {
    grid-template-columns: 1fr;
    row-gap: 48px;
    margin-top: 40px;
  }
  .feature-grid .btn-pill {
    margin-left: 0;
    align-self: flex-start;
  }
  .render-pair {
    grid-template-columns: 1fr;
    gap: 24px;
  }
  .map-band {
    margin-top: 48px;
  }
  /* A SANGRE — el mapa se come el gutter del .container y toca ambos bordes de
     la pantalla, igual que los .media-block. El enlace .map-fallback es hermano
     de .map-embed, así que conserva su margen. */
  .map-band .map-embed {
    margin-inline: calc(var(--gutter) * -1);
  }
  .ubicacion-section .body,
  .invierte-hero-copy,
  .lead-form,
  .invest-section {
    padding-inline: 0;
  }

  .lead-form .form-grid {
    grid-template-columns: 1fr;
    row-gap: 40px;
  }
  .lead-form .field--bare {
    display: none; /* desktop-only second underline from the XD layout */
  }
  .lead-form .field input,
  .lead-form .field label {
    font-size: 24px; /* label matches input so it stays aligned (float is em-based) */
  }
  .invest-section .intro {
    font-size: 22px;
  }
  .invest-row p {
    font-size: 18px;
  }

  .plan-viewer {
    padding: 24px;
  }
  .plan-title {
    font-size: 36px;
  }
  .plan-selector {
    grid-template-columns: 1fr; /* stack label above its buttons on small screens */
    gap: 12px;
  }
  /* .selector-buttons ya no repite su gap aquí: desde el 2026-09-10 escritorio
     declara esos mismos 12px, y .selector-buttons no existe en el XD, así que
     la copia era el mismo número en dos sitios sin nadie que lo pidiera.
     Los 48px de .plan-selector button SÍ se quedan, aunque escritorio declare
     ahora lo mismo: el XD archivado los declara en este breakpoint, y
     borrarlos abriría una divergencia contra el archivo a cambio de nada. */
  .plan-selector .selector-label {
    width: 100%;              /* XD (ausente hasta esta ronda) */
    margin-bottom: 4px;
  }
  .plan-selector button {
    width: 48px;
    height: 48px;
    font-size: 20px;
  }
  .plan-viewer-outer .carousel-arrow--prev {
    left: 8px;
  }
  .plan-viewer-outer .carousel-arrow--next {
    right: 8px;
  }

  .site-footer {
    /* no lateral margin: the .footer-wrap gutter already insets the box, so it
       lines up with the container/images instead of doubling the inset —
       divergencia deliberada frente al XD (que trae var(--gutter) lateral),
       documentada en tools/check-style-xd.py KNOWN_INTENTIONAL_DIVERGENCES.
       El margen inferior sí se alineó al XD en esta ronda: 16px -> 24px. */
    margin: 64px 0 24px;
    padding: 32px 24px;
    /* ORDEN MÓVIL DEL FOOTER (decisión de Chus): el wordmark HAVEN abre el footer,
       encima de los teléfonos, y los iconos sociales se quedan abajo haciendo de
       separador entre el texto y los logos de partners. Para poder reordenar hay
       que aplanar la jerarquía: el footer pasa a ser la rejilla y sus nietos
       suben a ser ítems suyos (ver `display: contents` más abajo). El SVG del
       borde autodibujado NO se ve afectado — es position:absolute, o sea que
       está fuera de flujo y nunca llega a ser ítem de la rejilla. */
    display: grid;
    row-gap: 28px;
  }
  /* Se disuelven los dos wrappers para que .logo-haven, .footer-social y
     .footer-partners pasen a ser hermanos de .footer-cols y `order` pueda
     colocarlos. Son <div> sin semántica, así que `display: contents` no quita
     nada al árbol de accesibilidad. Los selectores `.footer-logos .x` siguen
     funcionando: contents saca la caja, no el nodo del DOM. */
  .footer-logos,
  .footer-brand {
    display: contents;
  }
  /* XD (era ausente en este breakpoint, heredaba los 92px del root). Dado
     el display:contents de arriba, este margin-top es inerte en la
     práctica — la caja se disuelve y no queda nada que margine — pero se
     declara igual porque es uno de los diez valores que diste literalmente
     en esta ronda; el espaciado real de esta zona del footer en mobile lo
     dan .footer-social/.footer-partners y la rejilla del padre. */
  .footer-logos {
    margin-top: 48px;
  }
  .footer-copyright {
    margin: 0 0 24px;   /* aligns to the mobile footer box edges */
    flex-direction: column;    /* stack so the two credits don't collide */
    align-items: flex-start;
    gap: 8px;
    font-size: 11px;
  }
  .footer-cols {
    grid-template-columns: 1fr;
    row-gap: 28px;
  }
  .footer-contact {
    font-size: 18px;
    /* Los datos de contacto van DEBAJO de la descripción de HAVEN (decisión de
       Chus). Solo aplica aquí: a partir de 700px .footer-cols vuelve a ser de
       dos columnas (contacto izquierda / nota derecha) y no hay "debajo".
       .footer-cols conserva su propia rejilla — NO lleva `display: contents` —
       así que basta con reordenar sus dos hijos, sin tocar el aplanado del
       resto del footer. La nota no es interactiva, así que el orden de foco no
       se resiente: los únicos enlaces de la zona están dentro de este bloque y
       entre ellos mantienen su orden. */
    order: 1;
  }
  .footer-note {
    font-size: 17px;
  }
  /* El wordmark abre el footer. Es el único que necesita `order`: una vez
     aplanada la jerarquía el resto ya queda en el orden correcto por DOM
     (.footer-cols -> .footer-social -> .footer-partners). */
  .footer-logos .logo-haven {
    order: -1;
    width: 140px;       /* XD (/tmp/xd.css a max-width:700px). Antes width:100%
                            + max-width:140px — en la práctica ya renderizaba a
                            140px en cualquier viewport móvil realista (≥320px),
                            así que el resultado visual no cambia; se deja el
                            valor fijo del XD por ser el más simple de los dos
                            que produce el mismo pixel. */
  }
  /* Ya no va pegado al wordmark (que se ha ido arriba), así que se le quita el
     hueco lateral del XD y se le da aire por encima: hace de separador entre el
     párrafo y los logos de partners. */
  .footer-social {
    margin-left: 0;
    margin-top: 20px;
  }
  .footer-logos .icon-instagram {
    margin-left: 16px;
  }
  .footer-logos .icon-youtube,
  .footer-logos .icon-instagram {
    flex: none;             /* los iconos no se encogen; el wordmark sí */
  }
  /* Rejilla proporcional: 97fr/118fr/82fr SON los anchos del XD, así que los tres
     escalan por el mismo factor (se conserva la proporción del diseño) y siempre
     caben, hasta 320px. `end` mantiene la misma línea base inferior que en
     escritorio (.footer-partners usa align-items: flex-end). */
  .footer-partners {
    display: grid;
    grid-template-columns: 97fr 118fr 82fr;
    column-gap: 20px;
    align-items: end;
  }
  /* Aquí la fila es una rejilla y el hueco lo da `column-gap`, así que hay que
     anular los márgenes de escritorio: el `margin-left: auto` de Muchagana
     empujaría su logo al borde derecho DE SU COLUMNA, y los 24px de Mallol le
     comerían ancho. Misma especificidad (0,2,1) que las reglas `:has()` de
     arriba, pero van después, así que ganan. */
  .footer-logos .footer-partners a {
    margin: 0;
  }
  .footer-logos .footer-partners img {
    width: 100%;            /* especificidad 0,2,1 — tiene que ganar a las reglas
                               .footer-logos .logo-* (0,2,0) del bloque desktop */
    height: auto;
    margin: 0;
  }
}

/* ==========================================================================
   Chrome — disabled nav link (Brokers)
   ========================================================================== */
.nav-link--disabled { opacity: .45; cursor: default; pointer-events: none; }

/* ---- Ubicación: Google Maps embed -------------------------------------- */
.map-embed { position: relative; aspect-ratio: 16 / 9; background: var(--offwhite); }
.map-embed iframe,
.map-embed .map-canvas { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* Calm on-brand surface while the Maps script loads (and if the key is missing/blocked). */
.map-canvas { background: var(--offwhite); }
/* Default state of #map (Tarea 7, concepto.html): the static plan of Obarrio the
   XD asked for, filling the same box the interactive map will take over. initMap()
   removes it only once Google has actually responded; showMapError() re-adds it if
   Google never does. Lives inside .map-canvas, not .map-fallback — that class
   already means the "Ver en Google Maps" text link below the map (see below). */
.map-canvas .map-static { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.map-canvas--error { display: grid; place-items: center; padding: 24px; text-align: center; }
.map-canvas--error::after {
  content: "El mapa no se pudo cargar. Usa el enlace de abajo para ver la ubicación en Google Maps.";
  max-width: 34ch; font-size: 15px; line-height: 1.4; color: var(--grey);
}
/* The failure text above is Spanish; /en/ pages declare lang="en" and get this instead.
   Only CSS-generated copy on the site — everything else lives in markup or js/main.js. */
html[lang="en"] .map-canvas--error::after {
  content: "The map couldn’t load. Use the link below to view the location on Google Maps.";
}
.map-fallback { margin-top: 12px; font-size: 16px; text-align: center; }
.map-fallback a { text-decoration: underline; }
@media (max-width: 700px) { .map-embed { aspect-ratio: 4 / 3; } }

/* La altura de #map la da `aspect-ratio` en .map-embed, arriba — NUNCA la
   imagen estática, que es `position: absolute` y no ocupa flujo. Importa el
   día que entre la clave: Google sustituye el contenido de #map por su mapa
   y la caja tiene que seguir midiendo lo mismo. Si la caja se quedara en
   altura 0 no habría nada en pantalla que lo delatase.

   El .webp está recortado al plano (1487x1182, ver README), más alto en
   proporción que la caja 16/9, así que `object-fit: cover` le recorta un
   ~15% por arriba y otro tanto por abajo. El pin de HAVEN cae al 46% de la
   altura del plano y sobrevive al recorte. */

/* ==========================================================================
   Reduced motion — disable decorative animation, keep everything usable
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  /* belt-and-braces: initReveal no corre con reducedMotion=true, así que no
     hay cubiertas; si alguna llegara a existir, no debe tapar nada. */
  .ct-cover {
    display: none !important;
  }
  .feature-cell[data-cortina].reveal h2,
  .feature-cell[data-cortina].reveal p,
  .feature-cell[data-cortina].reveal .btn-pill {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .parallax-img {
    transform: none !important;
  }
  .carousel.run .slide.to,
  .carousel.run .slide.to img,
  .carousel.run .slide::after {
    transition: none !important;
  }
  .sticky-nav {
    transition: none;
  }
  .lead-form .field label,
  .lead-form .field input,
  .lead-form .field::before,
  .lead-form .field::after,
  .lead-form .field-icon {
    transition: none !important; /* label still floats, just no animation */
  }
}

/* Ensure the hidden attribute always wins over component display rules
   (plan view/floor toggles + details block toggle their `hidden` attr in JS). */
[hidden] { display: none !important; }

/* ---- Movimiento reducido, segunda pasada -------------------------------
   Va al FINAL del fichero por cascada, no por gusto: el subrayado de hover/foco
   del nav (dos filetes, ::before lima y ::after maroon) se declara en tres
   sitios repartidos (.main-nav a arriba, .sticky-nav nav a en el bloque de la
   sticky, y .mobile-menu > a dentro de la media query de móvil), todos con la misma especificidad que este
   override. Una @media no aporta especificidad, así que anularlo desde el bloque
   de arriba no servía de nada: ganaba la última regla del fichero. Emitido aquí,
   gana este. Al añadir un subrayado nuevo, añadirlo también a esta lista. */
@media (prefers-reduced-motion: reduce) {
  .main-nav a::before,
  .main-nav a::after,
  .sticky-nav nav a::before,
  .sticky-nav nav a::after,
  .mobile-menu > a::before,
  .mobile-menu > a::after,
  .carousel-arrow,            /* el halo de las flechas aparece sin transición */
  .plan-selector button {     /* ídem el del selector de tipologías */
    transition: none;
  }
}
