/* =============================================================
   Invitación Samantha & Alejandro
   Réplica del diseño de Claude Design (tableros Main, Sistema, S0–S6).

   Todo el color se pide por ROL. Los hexadecimales solo viven en
   los bloques [data-theme]; ningún componente u ornamento los usa.
   Las mezclas del diseño (color-mix) están precalculadas para que
   funcionen también en navegadores viejos.
   ============================================================= */

/* ---------- 1 · ROLES DE COLOR ---------- */

[data-theme="DG"] {
  --c-fondo: #EDE4D3;
  --c-principal: #8A8F5C;
  --c-secundario: #B7C6D4;
  --c-apoyo: #6E8397;
  --c-acento: #C9A96E;
  --c-texto: #3A3F2E;

  --rgb-texto: 58 63 46;
  --rgb-apoyo: 110 131 151;
  --rgb-secundario: 183 198 212;
  --rgb-acento: 201 169 110;

  /* derivados (equivalen a los color-mix del diseño) */
  --c-suave: #535645;         /* texto 86% + fondo */
  --c-card: #D6D7D3;          /* secundario 42% + fondo */
  --c-card-fuerte: #CFD4D4;   /* secundario 55% + fondo */
  --c-card-suave: #D8D9D3;    /* secundario 38% + fondo */
  --c-sobre-lados: #C6CED4;   /* secundario 72% + fondo */
  --c-sobre-solapa: #ADBDCB;  /* secundario 86% + apoyo  */
  --c-sello-aro: #AC9461;     /* acento 80% + texto */
  --c-sello-icono: #82744E;   /* acento 50% + texto */
  --c-panel-label: #C0BBAA;   /* fondo 75% + texto */
}

[data-theme="G1"] {
  --c-fondo: #EEEDE6;
  --c-principal: #6E8397;
  --c-secundario: #B7C6D4;
  --c-apoyo: #2E3B55;
  --c-acento: #7A3E4B;
  --c-texto: #2E3B55;

  --rgb-texto: 46 59 85;
  --rgb-apoyo: 46 59 85;
  --rgb-secundario: 183 198 212;
  --rgb-acento: 122 62 75;

  --c-suave: #495469;
  --c-card: #D7DDDE;
  --c-card-fuerte: #D0D8DC;
  --c-card-suave: #D9DEDF;
  --c-sobre-lados: #C6D1D9;
  --c-sobre-solapa: #A4B3C2;
  --c-sello-aro: #6B3D4D;
  --c-sello-icono: #543D50;
  --c-panel-label: #BEC1C2;
}

/* Tema MAR · fondo casi blanco, azul marino, cielo, arena y oro.

   La paleta llegó con cinco colores y el sistema pide seis roles. El que
   faltaba es `apoyo`, y se deriva: texto 50 % + secundario. Es lo que
   `apoyo` es en los otros dos temas —el azul grisáceo de los bordes de
   UI— y sale a 4.2:1 contra el fondo, así que los campos y los botones
   secundarios cumplen el 3:1 que pide el sistema.

   La arena es `principal` y, además, el color de los filetes y los
   marcos (más abajo). A 1.6:1 es un trazo decorativo y nada más: no
   lleva texto ni es el borde de ningún control. Los derivados salen con
   las mismas fórmulas de los otros temas. */
[data-theme="MAR"] {
  --c-fondo: #FFFDF8;
  --c-principal: #D9C6A5;
  --c-secundario: #8FC5DC;
  --c-apoyo: #53809E;
  --c-acento: #C7A86B;
  --c-texto: #163A5F;

  --rgb-texto: 22 58 95;
  --rgb-apoyo: 83 128 158;
  --rgb-secundario: 143 197 220;
  --rgb-acento: 199 168 107;
  --rgb-linea: 217 198 165;   /* solo lo lee MAR, más abajo */

  --c-suave: #375574;
  --c-card: #D0E5EC;
  --c-card-fuerte: #C1DEE9;
  --c-card-suave: #D4E8ED;
  --c-sobre-lados: #AED5E4;
  --c-sobre-solapa: #87BBD3;
  --c-sello-aro: #A49269;
  --c-sello-icono: #6F7165;
  --c-panel-label: #C5CCD2;
}

:root {
  --f-serif: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
  --f-sans: 'Jost', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* filetes y bordes del diseño */
  --filete: rgb(var(--rgb-texto) / .16);
  --filete-fuerte: rgb(var(--rgb-texto) / .22);
  --borde-sutil: rgb(var(--rgb-texto) / .15);
  --borde: rgb(var(--rgb-texto) / .20);
  --borde-solapa: rgb(var(--rgb-texto) / .22);
  --borde-muestra: rgb(var(--rgb-texto) / .22);
  --aro-respuesta: rgb(var(--rgb-apoyo) / .35);
  --riel: rgb(var(--rgb-texto) / .25);
  --sombra-sobre: rgb(var(--rgb-texto) / .40);
  --sombra-sello: rgb(var(--rgb-texto) / .45);

  /* medidas: móvil primero, diseñado a 390 px */
  --col: 440px;                              /* columna máxima en tableta/escritorio */
  --pad: clamp(16px, 8.2vw, 32px);           /* margen lateral 16–32 px */
  /* Curvas y tiempos del movimiento, en un solo sitio. La de salida es
     fuerte a propósito: las integradas del navegador son demasiado
     suaves para que un movimiento deliberado se note resuelto. */
  --ease-out: cubic-bezier(.23, 1, .32, 1);
  --ease-in-out: cubic-bezier(.77, 0, .175, 1);
  --dur-entra: 380ms;

  --sa-t: env(safe-area-inset-top, 0px);
  --sa-b: env(safe-area-inset-bottom, 0px);
  --sa-l: env(safe-area-inset-left, 0px);
  --sa-r: env(safe-area-inset-right, 0px);

  /* El ritmo vertical se estira con la pantalla. Cada pantalla mide una
     pantalla, y a 667 px de alto los 28 px de margen que sobraban a 812
     son lo que hace que no quepa. `--dvh` cae a `vh` donde no hay `dvh`. */
  --dvh: 1vh;
  --v1: clamp(4px, calc(var(--dvh) * 1), 10px);
  --v2: clamp(8px, calc(var(--dvh) * 2), 18px);
  --v3: clamp(12px, calc(var(--dvh) * 3), 28px);
  --v4: clamp(16px, calc(var(--dvh) * 4.5), 44px);
  --flecha: 68px;   /* 56 de la flecha con su texto y 12 de aire */
}
@supports (height: 100dvh) { :root { --dvh: 1dvh; } }

/* MAR · los filetes y los marcos en arena. `html[data-theme]` le gana a
   `:root` en especificidad, así que el orden no importa. Solo se toca lo
   decorativo: lo que separa objetos (bordes de la carta, del sobre, las
   sombras) se queda en tinta, porque en arena no se vería. */
html[data-theme="MAR"] {
  --filete: rgb(var(--rgb-linea));
  --filete-fuerte: rgb(var(--rgb-linea));
  --borde-muestra: rgb(var(--rgb-linea));
  --aro-respuesta: rgb(var(--rgb-linea));
}
/* La rama de olivo de «No podré asistir» es el ornamento que identifica
   la opción, y en arena sobre la tarjeta celeste (1.3:1) desaparece. */
[data-theme="MAR"] .opcion__ico--no,
[data-theme="MAR"] .respuesta__ico--olivo { color: var(--c-apoyo); }

/* ---------- 2 · BASE ---------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  background: var(--c-fondo);
  -webkit-text-size-adjust: 100%;
  /* El desplazamiento lo lleva js/app.js, que sí controla la duración.
     Dejarlo en `smooth` aquí haría que los dos tiraran a la vez. */
  scroll-behavior: auto;
  overflow-x: hidden;
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--c-fondo);
  color: var(--c-texto);
  font-family: var(--f-sans);
  font-weight: 400;
  font-size: 16px;
  line-height: 1.5;
  overflow-x: hidden;
  overflow-x: clip;
  padding-left: var(--sa-l);
  padding-right: var(--sa-r);
  -webkit-font-smoothing: antialiased;
}

html.sin-scroll, body.sin-scroll { overflow: hidden; height: 100%; overscroll-behavior: none; }

p, h1, h2, h3, ul { margin: 0; }
ul { padding: 0; list-style: none; }
img { display: block; max-width: 100%; }
button { font: inherit; color: inherit; cursor: pointer; }
a { color: inherit; }

button:focus-visible, a:focus-visible {
  outline: 2px solid var(--c-texto);
  outline-offset: 3px;
}

/* El sprite debe seguir renderizado (sin display:none) para que
   <use href="#…"> lo encuentre en todos los navegadores. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Cambio de tema: 400 ms en color, fondo, borde, fill y stroke.
   Se activa solo durante el cambio para no cargar el scroll. */
html.cambiando-tema, html.cambiando-tema * {
  transition: background-color 400ms ease, color 400ms ease,
              border-color 400ms ease, fill 400ms ease, stroke 400ms ease !important;
}

/* Columna centrada en tableta y escritorio; el fondo, la niebla
   y las olas se extienden a todo el ancho de la ventana. */
.inv {
  position: relative;
  width: 100%;
  max-width: var(--col);
  margin-inline: auto;
}

.pantalla { position: relative; }

/* Ornamentos de ambiente a todo el ancho de la ventana */
.bleed, .niebla, .luz, .olas {
  position: absolute;
  left: 0;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  pointer-events: none;
}

/* ---------- 3 · TIPOGRAFÍA ---------- */

.eyebrow {
  font-size: 12px;
  font-weight: 400;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--c-suave);
}
.eyebrow--card { font-size: 11px; letter-spacing: .2em; color: var(--c-texto); }
.eyebrow--evitar { margin-top: 18px; font-size: 11px; letter-spacing: .2em; color: var(--c-suave); }

.titulo {
  font-family: var(--f-serif);
  font-weight: 500;
  line-height: 1.04;
  letter-spacing: -.015em;
  margin-top: 8px;
}
.titulo--italica { font-style: italic; }
.titulo--m   { font-size: clamp(28px, min(10.3vw, calc(var(--dvh) * 5.2)), 40px); line-height: 1.1; }
.titulo--l   { font-size: clamp(30px, min(11.8vw, calc(var(--dvh) * 5.3)), 46px); line-height: 1.05; }
.titulo--xl  { font-size: clamp(32px, min(12.8vw, calc(var(--dvh) * 6.2)), 50px); line-height: 1.02; }
.titulo--xxl { font-size: clamp(34px, min(13.8vw, calc(var(--dvh) * 6)), 54px); line-height: 1.02; }

.saludo {
  font-family: var(--f-serif);
  font-style: italic;
  font-weight: 500;
  text-align: center;
}

.seccion__titulo { position: relative; text-align: center; }

.divisor {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  color: var(--c-apoyo);
}
.divisor span { width: 36px; height: 1px; background: currentColor; }
.divisor i { width: 5px; height: 5px; background: currentColor; transform: rotate(45deg); }
.divisor--acento { color: var(--c-acento); }
.divisor--acento i { width: 6px; height: 6px; }

.filete-acento { width: 40px; height: 2px; background: var(--c-acento); margin: var(--v2) auto 0; }

/* ---------- 4 · ORNAMENTOS ---------- */

/* Olas · principal */
.olas {
  bottom: 0;
  height: 100px;
  overflow: hidden;
  color: var(--c-principal);
}
.olas__capa {
  position: absolute;
  width: 200vw;
  height: 70px;
}
.olas__capa--1 { left: 0;     bottom: 30px; opacity: .16; animation: drift 22s linear infinite; }
.olas__capa--2 { left: -8vw;  bottom: 14px; opacity: .26; animation: driftr 17s linear infinite; }
.olas__capa--3 { left: -16vw; bottom: 0;    opacity: .42; animation: drift 13s linear infinite; }

/* Niebla · secundario */
.niebla {
  --op: .75;
  background: radial-gradient(closest-side, rgb(var(--rgb-secundario) / var(--op)), transparent);
  filter: blur(13px);
}

/* Luz · acento
   La niebla es `secundario`, un azul frío, y en la portada se leía como
   una mancha gris detrás de la foto. Esta es la misma idea en el rol
   cálido: la luz de las seis de la tarde en una playa, que es la hora de
   la fotografía y de la canción. */
.luz {
  --op: .28;
  background: radial-gradient(closest-side, rgb(var(--rgb-acento) / var(--op)), transparent);
  filter: blur(26px);
}

/* Motas · acento
   Partículas suspendidas en esa luz. Son un SVG de círculos a distintas
   opacidades; existen para tener algo que respire en la portada que no
   sea la niebla. */
.motas { position: absolute; color: var(--c-acento); pointer-events: none; }
.motas svg { width: 100%; height: 100%; display: block; }

/* Rama de olivo · principal */
.olivo { position: absolute; color: var(--c-principal); pointer-events: none; }

/* Mariposa · apoyo */
.mariposa { color: var(--c-apoyo); }
.mariposa:not(.respuesta__ico) { position: absolute; pointer-events: none; }

/* ---------- 5 · ANIMACIONES ---------- */

@keyframes drift    { to   { transform: translate3d(-50%, 0, 0); } }
@keyframes driftr   { from { transform: translate3d(-50%, 0, 0); } to { transform: none; } }
@keyframes mist {
  0%   { transform: translate3d(-4%, 0, 0) scale(1); }
  50%  { transform: translate3d(6%, -3%, 0) scale(1.08); }
  100% { transform: translate3d(-4%, 0, 0) scale(1); }
}
@keyframes flutter {
  0%, 100% { transform: translateY(0) rotate(-5deg); }
  50%      { transform: translateY(-7px) rotate(5deg); }
}
@keyframes flutter-esp {
  0%, 100% { transform: scaleX(-1) translateY(0) rotate(-5deg); }
  50%      { transform: scaleX(-1) translateY(-7px) rotate(5deg); }
}
@keyframes rvin     { from { opacity: 0; transform: translateY(26px); } to { opacity: 1; transform: none; } }
@keyframes softin   { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes envfloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
@keyframes pulse    { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }

/* Aparición al scroll: fade + 26 px.
   Vía nativa (Chrome/Edge) y respaldo con IntersectionObserver
   para iPhone y Android (lo activa js/app.js con la clase .js). */
@media (prefers-reduced-motion: no-preference) {
  /* Vía nativa */
  @supports (animation-timeline: view()) {
    html:not(.sin-vt) .rv {
      animation: rvin linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 55%;
    }
  }
  /* Respaldo: mismo efecto, disparado por IntersectionObserver */
  html.sin-vt .rv {
    opacity: 0;
    transform: translateY(26px);
    transition: opacity 700ms ease, transform 700ms ease;
  }
  html.sin-vt .rv.visible { opacity: 1; transform: none; }
}

/* prefers-reduced-motion: menos movimiento, no menos información.
   Se apagan los bucles de ambiente y todo desplazamiento espacial, pero
   se conservan los cambios de opacidad y color que confirman una acción:
   quien pide menos movimiento sigue necesitando saber que su toque
   registró. Por eso la regla ataca `transform`, no `transition`. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation: none !important;
    /* Limitar las PROPIEDADES que pueden transicionar, no `transform` a
       secas: hay transforms estáticos que son estructura, no movimiento
       —la rama de olivo espejada, el anillo del reproductor girado— y
       anularlos los rompería. */
    transition-property: opacity, color, background-color, border-color, fill, stroke !important;
    transition-duration: 160ms !important;
  }
  .rv { opacity: 1 !important; transform: none !important; }

  /* El toque sigue acusándose, sin moverse: quien pide menos movimiento
     igual necesita saber que su botón registró. */
  .btn:active, .btn--texto:active, .btn--quitar:active, .musica__btn:active,
  .vitrina__btn:active {
    opacity: .72;
  }
}

/* ---------- 6 · SOBRE DE ENTRADA (S0) ---------- */

.sobre {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: calc(var(--sa-t) + 24px) var(--pad) calc(var(--sa-b) + 110px);
  background: var(--c-fondo);
  overflow: hidden;
}
.sobre[hidden] { display: none; }

/* La apertura, de un vistazo (ms desde el toque):

     0 ·  320   el paisaje del sobre se apaga: saludo, pista, olas, niebla
    60 · 1160   las dos puertas se abren por el centro, como un libro
   500 · 1400   la carta se asoma entre ellas
   650 · 1800   la cámara entra: el zoom llena la pantalla con la carta
  1250 · 1700   el texto de la carta se disuelve
  1450 · 2000   el sobre entero se desvanece y deja ver la portada
  1500 ·        la portada empieza su cascada (ver `[data-vibe="portada"]`)

   El fondo de la carta y el del sobre son el mismo color. Cuando el zoom
   termina no queda ningún objeto en pantalla: lo que se desvanece es el
   cambio de escena, no un sobre. Es el momento de más peso del sitio y
   dura 2 s —antes eran 2.5—, que es lo que puede durar algo que se ve una
   vez por visita. */
.sobre.abriendo {
  pointer-events: none;
  animation: sobre-sale 550ms var(--ease-out) 1450ms both;
}

.sobre__saludo {
  font-size: clamp(22px, 6.7vw, 26px);
  margin-bottom: clamp(48px, 18vw, 84px);
}

.sobre__zona { position: relative; width: 100%; max-width: 300px; }

.sobre__abrir {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  perspective: 900px;
  animation: envfloat 5s ease-in-out infinite;
}
/* El zoom parte de donde el sobre quedó flotando, no de su reposo: app.js
   pasa esa posición en `--z-desde` y la carta llega al centro de la
   pantalla (`--zx`, `--zy`) a la escala que la hace llenarla (`--zoom`).
   Es movimiento sobre la pantalla, así que va con la curva de entrada y
   salida fuerte: arranca despacio, acelera, y se asienta. */
.sobre.abriendo .sobre__abrir {
  animation: sobre-zoom 1150ms var(--ease-in-out) 650ms both;
}

.sobre__caja {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  transform-style: preserve-3d;
}

/* El interior del sobre: lo que se ve cuando las puertas se abren. Va
   un poco atrás (translateZ) para que las puertas, que giran delante,
   nunca queden por detrás de él. */
.sobre__cuerpo {
  position: absolute;
  inset: 0;
  border-radius: 4px;
  background: var(--c-sobre-solapa);
  box-shadow: 0 18px 32px -14px var(--sombra-sobre);
  transform: translateZ(-4px);
}

.sobre__carta {
  position: absolute;
  left: 4.667%;
  top: 6%;
  width: 90.667%;
  height: 88%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  background: var(--c-fondo);
  border: 1px solid var(--borde-sutil);
  color: var(--c-texto);
  transform: translateZ(-2px);
}
.sobre.abriendo .sobre__carta { animation: carta-asoma 900ms var(--ease-out) 500ms both; }

.sobre__nombres {
  font-family: var(--f-serif);
  font-weight: 500;
  font-size: clamp(22px, 7.2vw, 28px);
  line-height: 1.05;
  letter-spacing: -.015em;
}
.sobre__fecha {
  margin-top: 10px;
  font-size: 11px;
  letter-spacing: .22em;
  text-transform: uppercase;
}
/* El texto se disuelve antes que la carta: a la escala final unas
   letras de 130 px recortadas por los bordes de la pantalla serían lo
   único que se vería, y no es lo que se quiere recordar del sobre. */
.sobre.abriendo .sobre__nombres,
.sobre.abriendo .sobre__fecha { animation: texto-sale 450ms var(--ease-out) 1250ms both; }

/* Las dos puertas. Cada una es la mitad del sobre cerrado —el mismo
   dibujo de antes, partido por el centro—, bisagra en el borde de fuera
   y canto libre en el centro. Al girar enseñan su dorso, que es el forro
   del sobre. */
.sobre__puerta {
  position: absolute;
  top: 0;
  width: 50%;
  height: 100%;
  transform-style: preserve-3d;
}
.sobre__puerta--izq { left: 0;  transform-origin: 0 50%; }
.sobre__puerta--der { right: 0; transform-origin: 100% 50%; }

/* Las puertas responden en el acto y se asientan despacio. Con la curva
   de entrada y salida fuerte pasaban ~300 ms sin moverse tras el toque,
   que se lee como un sobre que no oyó; con la salida fuerte del sitio
   (easeOutQuint) todo el giro ocurría en 250 ms, un chasquido. Esta es
   easeOutCubic: arranca ya, pero deja ver la puerta girar. */
.sobre.abriendo .sobre__puerta--izq { animation: puerta-izq 1100ms cubic-bezier(.215, .61, .355, 1) 60ms both; }
.sobre.abriendo .sobre__puerta--der { animation: puerta-der 1100ms cubic-bezier(.215, .61, .355, 1) 60ms both; }

.sobre__frente, .sobre__dorso {
  position: absolute;
  inset: 0;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
.sobre__frente > svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
/* La puerta derecha es la izquierda, espejada. */
.sobre__puerta--der .sobre__frente > svg { transform: scaleX(-1); }
.sobre__lado   { fill: var(--c-sobre-lados);  stroke: var(--borde);        stroke-width: 1; vector-effect: non-scaling-stroke; }
.sobre__solapa { fill: var(--c-sobre-solapa); stroke: var(--borde-solapa); stroke-width: 1; vector-effect: non-scaling-stroke; }
.sobre__dorso  { background: var(--c-card-fuerte); transform: rotateY(180deg); }

/* Al girar, la puerta se oscurece: es lo que le da volumen. */
.sobre__frente::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--c-texto);
  opacity: 0;
  pointer-events: none;
}
.sobre.abriendo .sobre__frente::after { animation: puerta-sombra 1100ms cubic-bezier(.215, .61, .355, 1) 60ms both; }

/* El sello está sobre la costura, así que cada puerta lleva su mitad: el
   mismo sello entero recortado por el centro. Al abrirse, el sello se
   parte solo, sin animarlo. El recorte deja margen arriba, abajo y por
   fuera para no comerse la sombra. */
.sobre__sello {
  position: absolute;
  top: 47%;
  width: 37.33%;
  aspect-ratio: 1;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--c-acento);
  box-shadow: inset 0 0 0 3px var(--c-sello-aro), 0 4px 10px -3px var(--sombra-sello);
  animation: pulse 3.2s ease-in-out infinite;
}
.sobre__puerta--izq .sobre__sello { left: 81.33%;  clip-path: inset(-20px 50% -20px -20px); }
.sobre__puerta--der .sobre__sello { left: -18.67%; clip-path: inset(-20px -20px -20px 50%); }
.sobre.abriendo .sobre__sello { animation: none; }
.sobre__sello-ico { width: 53.6%; aspect-ratio: 48 / 40; color: var(--c-sello-icono); }

.sobre__pista {
  margin-top: 46px;
  font-size: 12px;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--c-suave);
}

/* Lo que no es el sobre se apaga enseguida: la cámara entra en un sobre,
   no en una pantalla con un sobre en medio. */
.sobre.abriendo .niebla--sobre,
.sobre.abriendo .olas,
.sobre.abriendo .sobre__saludo,
.sobre.abriendo .sobre__pista,
.sobre.abriendo .mariposa--sobre {
  opacity: 0;
  transition: opacity 320ms var(--ease-out);
}

@keyframes puerta-izq   { to { transform: rotateY(-108deg); } }
@keyframes puerta-der   { to { transform: rotateY(108deg); } }
@keyframes puerta-sombra { to { opacity: .16; } }
@keyframes carta-asoma  { to { transform: translateZ(-2px) scale(1.035); } }
@keyframes texto-sale   { to { opacity: 0; } }
@keyframes sobre-zoom {
  from { transform: var(--z-desde, none); }
  to   { transform: translate(var(--zx, 0px), var(--zy, 0px)) scale(var(--zoom, 1)); }
}
@keyframes sobre-sale   { to { opacity: 0; visibility: hidden; } }

.niebla--sobre { top: 18%; height: 340px; --op: .80; filter: blur(14px); animation: mist 28s ease-in-out infinite; }

/* Mariposa colocada respecto al sobre, como en el tablero S0 */
.mariposa--sobre {
  width: 10%;
  aspect-ratio: 48 / 40;
  left: 81.7%;
  top: -20%;
  z-index: 7;
  animation: flutter 6s ease-in-out infinite;
}

/* ---------- 7 · SELECTOR DE TEMA (solo ?admin) ---------- */

.admin {
  position: fixed;
  top: calc(var(--sa-t) + 8px);
  right: calc(var(--sa-r) + 8px);
  z-index: 50;
}
.admin[hidden] { display: none; }
.admin button {
  min-height: 44px;
  min-width: 44px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--c-apoyo);
  background: var(--c-fondo);
  color: var(--c-texto);
  font-size: 12px;
  letter-spacing: .12em;
  text-transform: uppercase;
}

/* ---------- 8 · PANTALLA 1 · PORTADA ---------- */

.portada {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: calc(var(--sa-t) + 44px) var(--pad) calc(var(--sa-b) + 104px);
}
.portada__cuerpo { position: relative; z-index: 1; text-align: center; }

.portada__saludo { font-size: clamp(19px, 5.6vw, 22px); }

/* La foto manda: el arco pasa de 230 a 300 px. Las ramas se recogen a la
   base en vez de abrirse a los lados, porque a este tamaño se salían de
   la pantalla en un teléfono. */
.arco-zona {
  position: relative;
  /* El arco es lo que manda y lo que cede cuando falta alto: a 812 mide
     los 300 de siempre y baja con la pantalla. */
  width: min(300px, 78vw, calc(var(--dvh) * 34.5));
  margin: var(--v1) auto 0;
}

.arco { position: relative; }

/* La foto y, en la portada, su contorno viven dentro de un solo SVG.
   El recorte está en el mismo viewBox que el trazo, así que escalan
   juntos: no hay dos cajas que un navegador pueda medir distinto. */
.arco__svg { display: block; width: 100%; height: 100%; }
.arco__foto { filter: saturate(.82); }
/* La de detalles va un poco desenfocada, a pedido del dueño: se
   reconoce la foto, pero no compite con los datos que bajan de ella. */
.arco__foto--desenfocada { filter: saturate(.82) blur(2px); }

.arco--portada {
  width: 100%;
  aspect-ratio: 230 / 300;
}
/* El marco sobresale 8 px por lado respecto de la foto; en el viewBox
   eso ya está contemplado (246×316 contra 230×300), así que aquí solo
   se agranda la caja en esa misma proporción. */
.arco--portada .arco__svg {
  position: absolute;
  top: -2.67%;
  left: -3.48%;
  width: 106.96%;
  height: 105.34%;
  color: var(--c-acento);
  pointer-events: none;
}

.olivo--izq, .olivo--der { width: 42%; aspect-ratio: 150 / 90; top: 80%; }
.olivo--izq { left: -12%; }
.olivo--der { right: -12%; transform: scaleX(-1); }

.mariposa--p1 { width: 11%;  aspect-ratio: 48 / 40; left: 93%; top: 3%;  animation: flutter 6s ease-in-out infinite; }
.mariposa--p2 { width: 8.5%; aspect-ratio: 48 / 40; left: -8%; top: 42%; animation: flutter 7.5s ease-in-out 1.2s infinite; }

.portada__intro {
  margin-top: var(--v3);
  font-family: var(--f-serif);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(18px, 5.1vw, 20px);
  color: var(--c-suave);
}
/* El tracking negativo es el mismo de `.titulo`: estos nombres son del
   mismo escalón de tamaño aunque no lleven esa clase. */
.portada__nombres {
  margin-top: 4px;
  font-family: var(--f-serif);
  font-weight: 500;
  font-size: clamp(34px, min(13.3vw, calc(var(--dvh) * 6.4)), 52px);
  line-height: 1;
  letter-spacing: -.015em;
}
.portada .divisor { margin-top: var(--v3); }
.portada__fecha {
  margin-top: var(--v2);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
}
.portada__lugar {
  margin-top: var(--v1);
  font-family: var(--f-serif);
  font-weight: 500;
  font-size: clamp(20px, 5.6vw, 22px);
}


/* Hora dorada: una sola fuente de luz detrás del arco, en vez de las dos
   manchas de niebla azul que había. */
.luz--p1 { top: -40px; height: 420px; --op: .30; animation: mist 34s ease-in-out infinite; }
.luz--p2 { top: 380px; height: 300px; --op: .18; animation: mist 27s ease-in-out infinite reverse; }

/* Motas suspendidas en esa luz. Suben muy despacio y vuelven a entrar por
   abajo; `flotar` dura casi un minuto a propósito, para que sea algo que
   se nota al segundo vistazo y no a la primera. */
/* Acotadas a la zona del arco. A pantalla completa caían sobre los
   nombres y ahí no se leen como partículas en el aire sino como
   suciedad en la pantalla. */
.motas--p1 { top: 0; left: 0; right: 0; height: 54%; animation: flotar 54s linear infinite; }
.motas--p2 { top: 4%; left: 0; right: 0; height: 46%; animation: flotar 78s linear infinite reverse; opacity: .6; }
@keyframes flotar {
  from { transform: translate3d(0, 4%, 0); }
  to   { transform: translate3d(0, -4%, 0); }
}

/* ---------- 9 · PANTALLA 2 · DETALLES ---------- */

.detalles { padding: 72px 0 64px; }

.niebla--d1 { top: 20px; height: 240px; --op: .75; filter: blur(12px); animation: mist 28s ease-in-out infinite; }

.olivo--d1 {
  width: clamp(90px, 28vw, 110px);
  aspect-ratio: 150 / 90;
  right: 8px;
  top: 22px;
  transform: scaleX(-1) rotate(-12deg);
}

.arco--detalles {
  width: calc(100% - 2 * var(--pad));
  /* Lineal en el alto: 326 a 812, 230 a 667. Con `40dvh` bajaba demasiado
     poco y la sección tenía que reducirse entera para caber. */
  max-width: min(326px, max(150px, calc(var(--dvh) * 66.2 - 211px)));
  margin: var(--v3) auto 0;
  aspect-ratio: 326 / 210;
}

/* Itinerario. La línea arranca arriba del todo —pegada al borde de la
   foto— para que el arco se lea como el primer punto del recorrido y no
   como una imagen suelta encima de una lista. */
.datos {
  position: relative;
  /* La misma caja que la foto, al píxel: por encima de 326 px la foto se
     topa y se centra, y si el itinerario siguiera a lo ancho la línea
     dejaría de caer bajo su esquina. */
  width: calc(100% - 2 * var(--pad));
  max-width: 326px;
  margin: 0 auto;
  padding: var(--v2) 0 0 26px;
}
.datos::before {
  content: '';
  position: absolute;
  left: 5px;
  top: 0;
  bottom: 18px;
  width: 1px;
  background: var(--filete-fuerte);
}

.fila {
  --fila-v: clamp(4px, calc(var(--dvh) * 4.7 - 26px), 16px);
  position: relative;
  padding: var(--fila-v) 0;
}
.fila::before {
  content: '';
  position: absolute;
  left: -26px;
  top: calc(var(--fila-v) + 6px);
  width: 11px; height: 11px;
  border-radius: 50%;
  border: 1.2px solid var(--c-principal);
  background: var(--c-fondo);
}
.fila__etiqueta {
  font-size: 11px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-suave);
}
.fila__valor {
  margin-top: 4px;
  font-family: var(--f-serif);
  font-weight: 500;
  font-size: clamp(21px, 6.2vw, 24px);
  line-height: 1.1;
}

/* Cards · secundario */
.card { position: relative; border-radius: 8px; background: var(--c-card); }

.card--cuenta {
  margin: var(--v3) var(--pad) 0;
  padding: clamp(12px, calc(var(--dvh) * 2.9), 24px) 14px clamp(10px, calc(var(--dvh) * 2.7), 22px);
  background: var(--c-card-fuerte);
  text-align: center;
}
.card__filete-acento { width: 28px; height: 2px; background: var(--c-acento); margin: 0 auto var(--v2); }

.cuenta {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
  margin-top: clamp(6px, calc(var(--dvh) * 1.5), 12px);
}
.cuenta__cifra {
  display: block;
  font-family: var(--f-serif);
  font-weight: 600;
  font-size: clamp(28px, min(10.8vw, calc(var(--dvh) * 5.2)), 42px);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.cuenta__unidad {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
}

/* ---------- 10 · PANTALLA 3 · HOSPEDAJE Y DRESS CODE ---------- */

.hospedaje { padding: 64px var(--pad) 84px; }

.card--hospedaje {
  margin-top: var(--v2);
  padding: clamp(12px, calc(var(--dvh) * 2.5), 26px) 24px clamp(12px, calc(var(--dvh) * 2.4), 24px);
}

.mariposa--card {
  width: 30px; height: 25px;
  right: 20px; top: 20px;
  animation: flutter 6.5s ease-in-out infinite;
}

.hotel__nombre {
  font-family: var(--f-serif);
  font-weight: 600;
  font-size: clamp(26px, 7.7vw, 30px);
  line-height: 1.05;
  max-width: 200px;
}
.hotel__ciudad { margin-top: 6px; font-size: 14px; }

.estancia { display: flex; align-items: center; gap: 10px; margin-top: var(--v2); }
.estancia__fecha {
  font-family: var(--f-serif);
  font-weight: 600;
  font-size: clamp(24px, 7.2vw, 28px);
  line-height: 1.1;
}
.estancia__puente {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  letter-spacing: .08em;
}
.estancia__puente i { width: 100%; height: 1px; background: var(--c-apoyo); }
.estancia__fin { text-align: right; }

.seccion__titulo--dress { margin-top: var(--v2); }

.dress { position: relative; margin-top: 32px; }
.dress + .dress { margin-top: 40px; }
/* Dentro del respaldo, el bloque de mujeres respira lo mismo que
   respiraba cuando eran dos secciones hermanas. */
.dress__bloque { margin-top: 40px; }
.dress__titulo { font-family: var(--f-serif); font-weight: 600; font-size: clamp(25px, 7.2vw, 28px); }
.dress__texto { margin-top: 6px; font-size: 16px; line-height: 1.55; }

.muestras {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 0;
  margin-top: 16px;
}
.muestras li { width: 74px; text-align: center; }
.muestra__color {
  display: block;
  position: relative;
  width: 44px; height: 44px;
  margin: 0 auto;
  border-radius: 50%;
  border: 1px solid var(--borde-muestra);
}
.muestra__color svg { position: absolute; left: -1px; top: -1px; width: 44px; height: 44px; color: var(--c-texto); }
.muestra__nombre {
  display: block;
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.25;
  color: var(--c-suave);
}

.niebla--h1 { top: 360px; height: 260px; --op: .70; filter: blur(14px); animation: mist 26s ease-in-out infinite; }

/* ---------- 11 · PANTALLAS 4 Y 5 · RSVP ---------- */

.rsvp {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: 84px var(--pad) 150px;
}

.olivo--r1 {
  width: clamp(90px, 28vw, 110px);
  aspect-ratio: 150 / 90;
  left: 8px; top: 24px;
  transform: rotate(-8deg);
}
.mariposa--r1 {
  width: 28px; height: 23px;
  right: 44px; top: 70px;
  animation: flutter 6s ease-in-out infinite;
}

.rsvp__pregunta { position: relative; }
.rsvp__pregunta[hidden] { display: none; }
.rsvp__nota {
  margin: 16px auto 0;
  max-width: 280px;
  font-size: 16px;
  line-height: 1.5;
  color: var(--c-suave);
}
.rsvp .divisor--acento { margin-top: var(--v3); }
.rsvp__botones { position: relative; margin-top: var(--v3); display: grid; gap: clamp(8px, calc(var(--dvh) * 1.5), 12px); }

/* Opción de respuesta.
   Las tres son la misma pieza: mismo fondo, mismo borde, mismo tamaño.
   La jerarquía la llevaba antes el relleno del botón primario, y eso
   decía que una respuesta valía más que las otras. El ornamento de cada
   una es el que luego reaparece en su tarjeta, así que el invitado
   reconoce a dónde llegó. */
.opcion {
  display: grid;
  grid-template-columns: 44px 1fr;
  align-items: center;
  gap: 16px;
  width: 100%;
  padding: clamp(12px, calc(var(--dvh) * 2.2), 18px) 20px;
  border: 1px solid var(--aro-respuesta);
  border-radius: 10px;
  background: var(--c-card);
  color: var(--c-texto);
  text-align: left;
  transition: transform 110ms var(--ease-out), border-color 160ms ease;
}
.opcion:active { transform: scale(.985); }
@media (hover: hover) and (pointer: fine) {
  .opcion:hover { border-color: var(--c-texto); }
}

.opcion__ico { width: 44px; height: 36px; display: block; }
.opcion__ico--si, .opcion__ico--pienso { color: var(--c-apoyo); }
.opcion__ico--no { color: var(--c-principal); }

.opcion__texto {
  display: block;
  font-family: var(--f-serif);
  font-weight: 600;
  font-size: 22px;
  line-height: 1.15;
}
.opcion__pie {
  display: block;
  margin-top: 3px;
  font-size: 13px;
  line-height: 1.35;
  color: var(--c-suave);
}

/* A 320 px el icono roba ancho que el texto necesita. */
@media (max-width: 359px) {
  .opcion { grid-template-columns: 34px 1fr; gap: 12px; padding: 16px; }
  .opcion__ico { width: 34px; height: 28px; }
  .opcion__texto { font-size: 20px; }
}

.rsvp__estado {
  position: relative;
  margin: 14px auto 0;
  max-width: 34ch;
  min-height: 1.2em;
  text-align: center;
  font-size: 12.5px;
  line-height: 1.45;
  letter-spacing: .04em;
  color: var(--c-suave);
}
/* Lo que está guardado solo en este dispositivo todavía no lo sabe
   nadie. No puede leerse como un susurro bajo un "gracias". */
.rsvp__estado--pendiente {
  font-size: 14px;
  font-weight: 500;
  color: var(--c-texto);
}

/* Botones */
.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 56px;
  border-radius: 999px;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: .02em;
  text-decoration: none;
  text-align: center;
}
.btn + .btn { margin-top: 14px; }
.btn { padding-inline: 14px; }
/* `display: flex` gana sobre el `display: none` que el navegador le da a
   [hidden]. Sin esto, un botón oculto se sigue viendo. */
.btn[hidden] { display: none; }
@media (max-width: 374px) { .btn { font-size: 15px; } }
.btn--primario   { border: 1.5px solid var(--c-texto); background: var(--c-texto); color: var(--c-fondo); }
.btn--secundario { border: 1.5px solid var(--c-apoyo); background: transparent; color: var(--c-texto); }
.btn--icono svg  { width: 16px; height: 16px; }
.btn--texto {
  min-height: 44px;
  margin-top: 6px;
  border: 0;
  background: transparent;
  color: var(--c-texto);
  font-size: 14px;
  font-weight: 400;
  text-decoration: underline;
}
.btn[aria-busy="true"] { opacity: .7; }

/* Cards de respuesta (pantalla 5) */
.card--respuesta {
  position: relative;
  padding: 30px 26px 28px;
  border-radius: 10px;
  text-align: center;
  border: 1px solid var(--aro-respuesta);
  /* Sin entrada propia: la entrada la decide quien la muestra. Cuando
     tenía un `softin` fijo aquí, al responder se encadenaban dos
     animaciones —la del cambio y esta— y la tarjeta se quedaba a media
     opacidad. Al restaurar una respuesta guardada no hay animación
     porque nadie acaba de tocar nada. */
}
.card--respuesta[hidden] { display: none; }
.card--respuesta .btn { min-height: 52px; margin-top: 22px; }
.card--respuesta .btn--texto { min-height: 44px; margin-top: 6px; }

/* El ornamento llega con un tiempo propio, 80 ms después de su tarjeta:
   es el mismo que acabas de tocar en la opción, y ese retraso es lo que
   lo convierte en «este, ahora más grande» en vez de un dibujo que
   vino de paquete.
   La caja existe porque el aleteo anima el `transform` del SVG y una
   animación le gana a la transición de la misma propiedad. Dos
   transforms, dos elementos: la caja crece, el SVG aletea dentro. */
.respuesta__ico-caja {
  display: block;
  width: fit-content;
  margin: 0 auto;
  transition: opacity var(--dur-entra) var(--ease-out) 80ms,
              transform var(--dur-entra) var(--ease-out) 80ms;
}
.card--respuesta.desde .respuesta__ico-caja { opacity: 0; transform: scale(.8); }

.respuesta__ico { width: 40px; height: 33px; margin: 0 auto; animation: flutter 5s ease-in-out infinite; }
.respuesta__ico--olas  { width: 44px; height: 22px; color: var(--c-apoyo); animation: none; }
.respuesta__ico--olivo { width: 58px; height: 36px; color: var(--c-principal); animation: none; }
.card--respuesta .eyebrow--card { margin-top: 12px; }
.respuesta__texto {
  margin-top: 10px;
  font-family: var(--f-serif);
  font-weight: 600;
  font-size: clamp(26px, 7.7vw, 30px);
  line-height: 1.12;
}

.niebla--r1 { top: 80px; height: 300px; --op: .75; filter: blur(14px); animation: mist 27s ease-in-out infinite; }

/* ---------- 12 · PANTALLA 6 · QUIÉNES VIENEN ---------- */
/* Ya no hay precios ni CLABE: el planner lleva el dinero. Esta pantalla
   solo recoge a nombre de quién va la reserva y quién lo acompaña. */

.confirmacion { position: relative; padding: 64px var(--pad) calc(var(--sa-b) + 72px); }
.card--confirmacion {
  padding: 30px 26px 28px;
  border-radius: 10px;
  border: 1px solid var(--aro-respuesta);
}
/* Las dos caras de la tarjeta: el formulario y el agradecimiento.

   La transición vive aquí dentro y NO en `.card--confirmacion`, que lleva
   `cuelga` con `animation-fill-mode: backwards` mientras entra, y una
   animación le gana a la transición de las mismas propiedades: es
   exactamente el choque que ya dejó una tarjeta a media opacidad en las
   respuestas del RSVP. Así además cada cosa hace lo suyo: la tarjeta
   cambia de alto, la cara entra y sale. */
.confirmacion__cara {
  transition: opacity var(--dur-entra) var(--ease-out),
              transform var(--dur-entra) var(--ease-out);
}
.confirmacion__cara.desde { opacity: 0; transform: translateY(12px); }
/* Un fotograma sin transición, para colocar la cara en su punto de
   partida sin que el viaje hasta ahí se vea. Ver el porqué en
   `cambiarCara`, en js/app.js. */
.confirmacion__cara.sin-transicion { transition: none; }
/* Lo que se va no se hace esperar: sale más rápido de lo que entra. */
.confirmacion__cara.saliendo {
  opacity: 0;
  transform: translateY(-10px);
  transition-duration: 150ms;
}

.confirmacion__intro { margin-top: 12px; font-size: 16px; line-height: 1.55; }
.confirmacion__pie {
  margin-top: 14px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--c-suave);
  text-align: center;
}

/* ---------- el formulario ---------- */

.quienes { margin-top: 22px; }

.campo { display: flex; flex-direction: column; gap: 5px; }
.campo__et {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--c-suave);
}
.campo__input {
  width: 100%;
  min-height: 48px;
  padding: 0 14px;
  border: 1.5px solid var(--c-apoyo);
  border-radius: 6px;
  background: var(--c-fondo);
  color: var(--c-texto);
  font-family: var(--f-sans);
  font-size: 16px;   /* 16px o menos hace que iOS haga zoom al enfocar */
}
.campo__input:focus-visible { outline: 2px solid var(--c-texto); outline-offset: 2px; }

.acomp { margin-top: 18px; display: flex; flex-direction: column; gap: 14px; }
.acomp__fila {
  display: grid;
  grid-template-columns: 1fr 76px auto;
  grid-template-areas:
    'et     et     et'
    'nombre edad   quitar';
  column-gap: 8px;
  row-gap: 6px;
  align-items: end;
  padding-top: 14px;
  border-top: 1px solid var(--filete);
}
.acomp__et { grid-area: et; }
.acomp__fila .campo--nombre { grid-area: nombre; }
.acomp__fila .campo--edad { grid-area: edad; }
.acomp__fila .btn--quitar { grid-area: quitar; }
.btn--quitar {
  min-height: 48px;
  padding: 0 10px;
  border: 0;
  background: transparent;
  color: var(--c-texto);
  font-family: var(--f-sans);
  font-size: 13px;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.btn--agregar { margin-top: 16px; min-height: 48px; font-size: 15px; }

.quienes__cupo {
  margin-top: 12px;
  font-size: 13px;
  text-align: center;
  color: var(--c-suave);
}
.quienes__error {
  margin-top: 12px;
  min-height: 1.3em;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.45;
  text-align: center;
  color: var(--c-texto);
}
.quienes .btn--primario { margin-top: 18px; }

/* ---------- el resumen de lo enviado ---------- */

.resumen {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--filete);
  text-align: left;
}
.resumen__et {
  margin-top: 14px;
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--c-suave);
}
.resumen__et:first-child { margin-top: 0; }
.resumen__titular {
  margin-top: 3px;
  font-family: var(--f-serif);
  font-weight: 600;
  font-size: clamp(22px, 6.6vw, 26px);
  line-height: 1.1;
}
.resumen__lista { margin-top: 6px; }
.resumen__lista li {
  padding: 7px 0;
  border-bottom: 1px solid var(--filete);
  font-size: 15px;
}
.resumen .btn--texto { margin-top: 16px; }

.card--confirmacion .btn--secundario { margin-top: 18px; }

/* El cupo se ve ANTES de decidir, para que vaya pensando a quién llevar */
.rsvp__cupo {
  margin: 14px auto 0;
  max-width: 32ch;
  padding: 10px 14px;
  border: 1px solid var(--aro-respuesta);
  border-radius: 8px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--c-texto);
}
.rsvp__cupo[hidden] { display: none; }

/* ---------- 13 · ≥ 480 px · tableta y escritorio ---------- */

/* ≥ 480 px: la columna se centra en var(--col); la niebla y las olas
   siguen llegando al ancho de la ventana gracias a width:100vw. */

/* Impresión / respaldo sin JS */
@media print {
  .sobre, .admin, .olas, .niebla, .bajar-caja { display: none !important; }
}

/* El contenedor principal recibe el foco al abrir el sobre: sin aro visible */
.inv:focus { outline: none; }

/* =============================================================
   Música de fondo · control mínimo

   Una sola pieza: un botón redondo con el anillo de avance pintado
   encima. No hay barra de tiempo ni volumen — la canción es fondo,
   y lo único que alguien quiere decidir es si suena o no.
   ============================================================= */

.musica {
  position: fixed;
  right: calc(var(--pad) + var(--sa-r));
  bottom: calc(var(--pad) + var(--sa-b));
  z-index: 40;
}

.musica__btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  /* Mezcla opaca por tema, no color-mix: se ve sobre la foto y sobre el
     fondo liso sin depender de soporte de mezcla en el navegador. */
  background: var(--c-card);
  color: var(--c-texto);
  box-shadow: 0 2px 10px rgb(var(--rgb-texto) / .14);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* El anillo va por fuera del área táctil de 48 px, girado para empezar
   arriba. `non-scaling-stroke` lo mantiene fino a cualquier densidad. */
.musica__anillo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
  overflow: visible;
  pointer-events: none;
}
.musica__pista, .musica__avance {
  fill: none;
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}
.musica__pista { stroke: var(--filete); }
.musica__avance {
  stroke: var(--c-acento);
  stroke-linecap: round;
  /* 2πr con r=22. El avance lo mueve app.js. */
  stroke-dasharray: 138.23;
  stroke-dashoffset: 138.23;
  opacity: 0;
  transition: opacity 300ms ease;
}
.musica--sonando .musica__avance { opacity: 1; }

.musica__icono { width: 22px; height: 22px; fill: currentColor; }
.musica__play { display: block; }
.musica__pausa { display: none; }
.musica--sonando .musica__play { display: none; }
.musica--sonando .musica__pausa { display: block; }

/* Solo para lectores de pantalla: el icono ya dice qué hace. */
.musica__texto {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Al aparecer, una vez. Nada de latidos: es un control, no una alarma. */
.musica:not([hidden]) { animation: softin 600ms ease both; }

@media (prefers-reduced-motion: reduce) {
  .musica:not([hidden]) { animation: none; }
  .musica__avance { transition: none; }
}

/* =============================================================
   Estados de los controles

   El sobre es el único momento autorado de la invitación; nada de
   aquí abajo compite con él. Esto es lo otro: acuse de recibo. Un
   control que no responde al tocarlo se siente roto, y en un
   teléfono —donde no hay hover que avise— el toque es la única
   conversación que existe.
   ============================================================= */

/* Dos cosas que el navegador hace por su cuenta y estorban al acuse de
   recibo que sí diseñamos.

   El resaltado gris del toque pinta un RECTÁNGULO encima de controles
   que son círculos y píldoras, y llega además después de la escala de
   aquí abajo: dos acuses para un solo toque, y el que se ve es el que no
   es nuestro. Se apaga en la raíz, que es heredable, porque todo lo que
   se toca en este sitio ya tiene su propio estado de pulsado.

   `touch-action: manipulation` apaga el doble toque para hacer zoom, que
   es lo único que obliga a Safari en iPhone a esperar ~350 ms antes de
   dar por buen un toque. El pinch para ampliar se queda: lo que se
   quita es la espera, no la posibilidad de acercar. */
html { -webkit-tap-highlight-color: transparent; }
a, button { touch-action: manipulation; }

.btn, .btn--texto, .btn--quitar, .musica__btn {
  transition: transform 110ms cubic-bezier(.16, 1, .3, 1),
              opacity 160ms ease,
              border-color 160ms ease,
              background-color 160ms ease;
}

/* Hundir, no rebotar: la escala baja y vuelve sin pasarse de 1. */
.btn:active, .musica__btn:active { transform: scale(.975); }
.btn--texto:active, .btn--quitar:active { transform: scale(.96); }

/* Hover solo donde existe de verdad. En un teléfono, `:hover` se queda
   pegado después de tocar y deja el botón en un estado que nadie pidió. */
@media (hover: hover) and (pointer: fine) {
  .btn--secundario:hover { border-color: var(--c-texto); }
  .btn--texto:hover { text-underline-offset: 4px; }
  .musica__btn:hover { box-shadow: 0 3px 14px rgb(var(--rgb-texto) / .20); }
}

/* Enviando: la opacidad ya no salta, se desvanece. */
.btn[aria-busy="true"] { opacity: .7; transform: none; }

/* -------------------------------------------------------------
   Cambio de respuesta

   Es el momento que importa del sitio entero: el único clic que la
   pareja necesita. Pasaba de golpe —la pregunta desaparecía y la
   tarjeta aparecía en su sitio— y un corte seco se lee como un error
   de la página, no como "te oímos". Ahora la pregunta se retira y la
   respuesta llega, en ese orden.
   ------------------------------------------------------------- */

/* Transiciones y no keyframes: un keyframe reinicia desde cero si se
   vuelve a disparar a media animación —responder, cambiar de respuesta y
   volver a responder— mientras que una transición retoma desde donde
   está. Para que la entrada tenga de dónde venir, el elemento se muestra
   un fotograma en su estado inicial y se suelta. */
.rsvp__pregunta, .card--respuesta {
  transition: opacity var(--dur-entra) var(--ease-out),
              transform var(--dur-entra) var(--ease-out);
}
.rsvp__pregunta.desde, .card--respuesta.desde {
  opacity: 0;
  transform: translateY(12px);
}
/* Lo que se va no se hace esperar: sale más rápido de lo que entra. */
.rsvp__pregunta.saliendo, .card--respuesta.saliendo {
  opacity: 0;
  transform: translateY(-10px);
  transition-duration: 150ms;
}

/* -------------------------------------------------------------
   Filas de acompañante

   Una fila que aparece de golpe no se lee como "agregaste a alguien",
   se lee como que la página dio un salto. Entra con su altura; al
   quitarla, se va más rápido de lo que vino.
   ------------------------------------------------------------- */

/* Mismo motivo que arriba, y aquí pesa más: dos toques seguidos en
   «Agregar» son normales, y con keyframes la segunda fila reiniciaba la
   animación de la primera. */
.acomp__fila {
  transition: opacity 240ms var(--ease-out), transform 240ms var(--ease-out);
}
.acomp__fila.desde { opacity: 0; transform: translateY(-6px); }
.acomp__fila.saliendo {
  opacity: 0;
  transform: translateY(-6px);
  transition-duration: 130ms;
}

/* El error no regaña: no tiembla ni parpadea, solo llega y se queda. */
.quienes__error:not(:empty) {
  animation: rsvp-entra 200ms var(--ease-out) both;
}
@keyframes rsvp-entra { from { opacity: 0; transform: translateY(12px); } }
/* El campo señalado se marca mientras tiene el foco, no para siempre. */
.campo__input:invalid:focus-visible { border-color: var(--c-texto); }

/* =============================================================
   Flecha al pie de sección

   Dice que hay más abajo y lleva allí. Respira al ritmo del resto del
   paisaje —4.2 s, el compás de las mariposas y el sello—, no al de una
   notificación. Va en la tinta apagada, no en `acento`: cada pantalla
   ya gastó el suyo, y la regla del acento único no se rompe por una
   señal de navegación.
   ============================================================= */

@keyframes respirar {
  0%, 100% { transform: translateY(0);   opacity: .55; }
  50%      { transform: translateY(5px); opacity: 1; }
}

.bajar {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: max-content;
  min-width: 44px;
  margin: 0 auto;
  padding: 8px 14px 11px;
  border-radius: 22px;
  color: var(--c-suave);
  text-decoration: none;
}
/* «Toca para seguir»: el mismo rótulo que «Toca para abrir» del sobre.
   Lo pidió el dueño para que nadie se quede pensando que la invitación
   termina ahí. No respira: solo la flecha se mueve, y un texto que sube
   y baja se lee mal. Es corto a propósito: a 320 px, «continuar» ya
   chocaba con el botón de la música. El margen negativo compensa el
   espaciado que la última letra deja a su derecha. */
.bajar__texto {
  font-size: 11px;
  line-height: 1;
  letter-spacing: .22em;
  margin-right: -.22em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Siempre en el mismo sitio, al pie de la pantalla: es la llave de la
   siguiente y no debe moverse de una a otra ni depender del contenido. */
.bajar-caja {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--sa-b) + var(--v2));
  z-index: 2;
}
.bajar svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  animation: respirar 4.2s ease-in-out infinite;
}
/* Al tocarla deja de respirar: ya respondió, y seguir latiendo mientras
   la página viaja es ruido. */
.bajar:active svg { animation: none; opacity: 1; transform: translateY(3px); }
.bajar:active { color: var(--c-texto); }
@media (hover: hover) and (pointer: fine) {
  .bajar:hover { color: var(--c-texto); }
  .bajar:hover svg { animation: none; opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  /* Sigue siendo un control visible y utilizable; solo deja de latir. */
  .bajar svg { animation: none; opacity: .8; }
}

/* -------------------------------------------------------------
   Ornamentos dormidos

   Una sección fuera de pantalla no mueve nada. Se pausa en vez de
   apagarse para que al volver retome donde estaba y no dé un salto.
   Se nombra pieza por pieza y NO con `*`: los elementos con
   `animation-timeline: view()` llevan su propio reloj atado al scroll
   y pausarlos rompería la aparición.
   ------------------------------------------------------------- */

.dormido .olas__capa,
.dormido .niebla,
.dormido .luz,
.dormido .motas,
.dormido .mariposa,
.dormido .respuesta__ico,
.dormido .bajar svg,
.dormido .sobre__abrir,
.dormido .sobre__sello {
  animation-play-state: paused;
}

/* =============================================================
   La vitrina del dress code

   Un escenario y un conmutador. Se viste a una persona a la vez: la
   sección mide la mitad y el probador pasa de apoyo a protagonista.
   Nace oculta en el marcado; la enseña js/app.js.
   ============================================================= */

.vitrina { position: relative; margin-top: var(--v2); }
.vitrina[hidden] { display: none; }

/* La píldora del sistema: 999px y borde de 1.5px en apoyo, el mismo
   del botón secundario. La luz que se desliza lleva el relleno de
   `texto` y la etiqueta en `fondo`, que es una de las dos únicas
   combinaciones que permite la Regla del Texto en Dos Sitios. */
.vitrina__seg {
  position: relative;
  display: flex;
  width: 100%;
  max-width: 300px;
  margin: 0 auto;
  padding: 3px;
  border: 1.5px solid var(--c-apoyo);
  border-radius: 999px;
}
.vitrina__luz {
  position: absolute;
  top: 3px;
  left: 3px;
  height: calc(100% - 6px);
  border-radius: 999px;
  background: var(--c-texto);
}
/* El deslizamiento se habilita tras el primer pintado: así la luz toma
   su sitio sin animarse al cargar. */
.vitrina__seg[data-listo] .vitrina__luz {
  transition: transform 250ms var(--ease-out), width 250ms var(--ease-out);
}
.vitrina__btn {
  position: relative;        /* por encima de la luz */
  flex: 1;
  min-height: 42px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font-family: var(--f-sans);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .06em;
  color: var(--c-suave);
  transition: color 160ms var(--ease-out),
              transform 110ms cubic-bezier(.16, 1, .3, 1);
}
.vitrina__btn[aria-selected="true"] { color: var(--c-fondo); }
/* Era el único control del sitio que no acusaba el toque: la luz tarda
   250 ms en llegar y, hasta que llegaba, el botón no decía nada. */
.vitrina__btn:active { transform: scale(.97); }
.vitrina__btn:focus-visible { outline: 2px solid var(--c-texto); outline-offset: 3px; }

.vitrina__panel[hidden] { display: none; }
.vitrina__texto { margin-top: var(--v1); text-align: center; }

/* =============================================================
   Probador del dress code

   Un contorno de prenda y los mismos colores que el texto acaba de
   pedir. No es un configurador: es para que alguien vea «ah, arena
   con camisa blanca» antes de abrir su clóset. Por eso no guarda
   nada en el servidor ni se le enseña a la pareja.
   ============================================================= */

.probador {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: stretch;
  gap: 22px;
  margin-top: 24px;
  padding-top: 22px;
  border-top: 1px solid var(--filete);
}
.probador[hidden] { display: none; }

/* Dentro de la vitrina el filete sobra —la píldora ya separó— y el
   conjunto se centra como una pieza: las dos columnas miden lo suyo
   y es el par el que queda al medio, no la figura sola. */
.probador--vitrina {
  grid-template-columns: auto auto;
  justify-content: center;
  margin-top: var(--v1);
  padding-top: 0;
  border-top: 0;
}

.probador__prenda { width: min(96px, calc(var(--dvh) * 11.8)); height: auto; display: block; }
/* El trazo es el mismo gris azulado de los bordes de UI; el relleno es
   el color elegido, que es el único dato que cambia. */
.probador__pieza {
  stroke: var(--c-apoyo);
  stroke-width: 1.3;
  stroke-linejoin: round;
  transition: fill 220ms var(--ease-out);
}

/* Repartidos a lo alto del dibujo: con dos prendas, cada fila queda a la
   altura de la suya. El centro de la camisa cae al 26 % del viewBox y el
   del pantalón al 75 %, que es justo donde `space-around` los pone. */
.probador__controles {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
}

.probador__et {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--c-suave);
}
.probador__et + .probador__dots { margin-top: 2px; }

.probador__dots { display: flex; flex-wrap: wrap; gap: 2px; }

/* El botón mide 44 px aunque la muestra se vea de 26: el objetivo táctil
   es el del sistema, el círculo es solo lo que se ve. */
.probador__dot {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: 50%;
}
.probador__muestra {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--borde-sutil);
  transition: transform 140ms var(--ease-out), box-shadow 140ms ease;
}
.probador__dot[aria-pressed="true"] .probador__muestra {
  box-shadow: 0 0 0 2px var(--c-fondo), 0 0 0 3.5px var(--c-texto);
}
.probador__dot:active .probador__muestra { transform: scale(.9); }
.probador__dot:focus-visible { outline: 2px solid var(--c-texto); outline-offset: -4px; }

/* ---------- La fila de lo que hay que evitar ----------
   Estaba al pie de la sección, fuera de donde el ojo mira mientras
   viste a alguien, y se perdía. Ahora va en la misma columna, justo
   después de los colores que sí se pueden elegir.
   Sigue sin ofrecerse como opción: no es un botón, no tiene objetivo
   táctil y lleva el tache encima. Lo único que comparte con la fila de
   arriba es el paso de 46 px, para que los dos primeros círculos caigan
   en la misma vertical y las dos filas se lean como una sola columna. */
.probador__et--evitar { margin-top: 16px; }
.probador__dots--evitar {
  margin: 0;
  padding: 0 0 0 9px;        /* 9 px = el hueco que deja el círculo de 26 dentro del botón de 44 */
  gap: 20px;                 /* 26 + 20 = el mismo paso de 46 px de la fila de arriba */
  list-style: none;
}
.probador__evitar {
  position: relative;
  display: block;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--borde-muestra);
}
.probador__evitar svg {
  position: absolute;
  left: -1px;
  top: -1px;
  width: 26px;
  height: 26px;
  color: var(--c-texto);
}

@media (max-width: 359px) {
  .probador { gap: 14px; }
  .probador__prenda { width: 78px; }
  .probador__dot { width: 40px; height: 40px; }
  /* El botón baja a 40 px: el hueco del círculo y el paso bajan con él. */
  .probador__dots--evitar { padding-left: 7px; gap: 16px; }
}

/* =============================================================
   Secciones: numeradas, bloqueadas, a pantalla completa y en cascada

     1 · Hola <invitado>            la portada
     2 · Welcome                    Beach wedding y cuenta regresiva
     3 · Hospedaje y dress code
     4 · Respuesta                  la pregunta y sus tres estados
     5 · Quiénes vienen             no existe hasta responder «sí»

   Cada una mide una pantalla, y para pasar a la siguiente hay que tocar
   la flecha que respira al pie: esa es la llave. Nadie se salta una
   sección porque lo que todavía no se desbloqueó no está en la página.
   Lo ya visto sí se puede recorrer con scroll, en cualquier orden.

   Lo que llega no aparece de golpe: llega en cascada, una pieza detrás de
   otra y en el orden en que se lee. Cada pantalla tiene su carácter
   (`data-vibe`): cambia de dónde vienen las piezas y a qué compás.

   Todo cuelga de `html.js`. Sin JavaScript no hay bloqueo ni cascada: la
   invitación se lee de arriba abajo como una página.
   ============================================================= */

/* Los cuatro números que definen una cascada. Cada pantalla cambia los
   que necesite: de dónde parten las piezas, cuánto tardan y el compás
   entre una y la siguiente. */
[data-vibe] {
  --c-desde: translateY(14px);
  --c-ms: 520ms;
  --c-paso: 80ms;
  --c-base: 0ms;
}

/* Oculto hasta que se abre. */
html.js [data-vibe]:not(.abierta) [data-c] {
  opacity: 0;
  transform: var(--c-desde);
}
/* Al abrir, cada pieza llega con el retraso de su turno. `--c` lo escribe
   app.js en el orden del documento. Son transiciones y no keyframes por
   lo mismo que en el cambio de respuesta: retoman desde donde están.

   `data-c` va en cajas, NUNCA en un control: la cascada es dueña de la
   `transition` y del `transform` del elemento, y un botón con ellas
   puestas pierde su acuse de pulsado. */
html.js [data-vibe].abierta [data-c] {
  transition: opacity var(--c-ms) var(--ease-out) calc(var(--c-base) + var(--c, 0) * var(--c-paso)),
              transform var(--c-ms) var(--ease-out) calc(var(--c-base) + var(--c, 0) * var(--c-paso));
}

/* ---------- Bloqueo ---------- */

/* Lo que todavía no se desbloqueó se quita del flujo: ni se ve, ni se
   alcanza con scroll, con teclado o con un lector de pantalla, y el
   documento termina donde termina lo ya visto. Es la forma más simple de
   que nadie se salte una sección: no hay nada que saltar. */
html.js .pantalla[data-seccion].bloqueada { display: none; }

/* ---------- A pantalla completa ---------- */

/* Cada una de las cuatro primeras mide EXACTAMENTE una pantalla y se
   detiene ahí: el imán de app.js la alinea con el borde al soltar.
   NO es `scroll-snap`: el nativo, en iPhone, regresaba la vista a la
   pantalla anterior después de cada flecha (ver «Desplazamiento e imán»
   en app.js). No lo vuelvas a poner.

   El contenido va en `.fit`, centrado. Lo que no cabe a tamaño natural se
   reduce hasta un 80 % (app.js), y por debajo de eso la pantalla deja de
   ser fija y crece: se prefiere una página que se desplaza a un
   contenido recortado e inalcanzable —un teléfono acostado, por
   ejemplo—. */
html.js .pagina {
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  padding-top: calc(var(--sa-t) + var(--v2));
  padding-bottom: calc(var(--sa-b) + var(--v2));
  /* Los destellos y la niebla de una pantalla no pasan a la siguiente ni
     alargan el documento: a 667 de alto la luz de la portada se salía 13 px
     por abajo y el bloqueo dejaba de medir exactamente una pantalla. Solo
     en vertical: la niebla sigue desbordando a lo ancho de la ventana. */
  overflow-y: clip;
}
/* La flecha vive al pie de la pantalla, siempre en el mismo sitio: se
   reserva su lugar para que el contenido no la pise. */
html.js .pagina.con-flecha { padding-bottom: calc(var(--sa-b) + var(--v2) + var(--flecha)); }
/* La respuesta no lleva flecha, pero sí olas al pie. */
html.js .rsvp.pagina { padding-bottom: calc(var(--sa-b) + clamp(56px, calc(var(--dvh) * 9), 96px)); }
/* No cupo ni reducida: la pantalla crece y se desplaza. */
html.js .pagina[data-libre] { height: auto; min-height: 100vh; min-height: 100dvh; }

.fit {
  width: 100%;
  transform: scale(var(--fit, 1));
  transform-origin: 50% 50%;
}

/* La quinta no es de pantalla fija: su formulario crece con cada
   acompañante y no se puede reducir sin volver ilegible lo que se
   escribe. Se alinea con el borde y se desplaza si hace falta. */
html.js .confirmacion {
  min-height: 100vh;
  min-height: 100dvh;
}

/* ---------- Portada · «Revelado» ----------
   Llega desde el zoom del sobre, así que arranca cuando el sobre empieza
   a desvanecerse (`--c-base`) y es la más lenta: despacio, de abajo
   arriba, como una foto que se revela. El contorno del arco se dibuja
   solo, después de que la foto ya está. */
[data-vibe="portada"] {
  --c-desde: translateY(18px);
  --c-ms: 800ms;
  --c-paso: 110ms;
  --c-base: 1500ms;
}
/* Los trazos del contorno son `non-scaling-stroke`, así que el guion se
   mide en píxeles de pantalla: 1800 es más que cualquier recorrido. */
html.js [data-vibe="portada"] .arco__contorno { stroke-dasharray: 1800; }
html.js [data-vibe="portada"]:not(.abierta) .arco__contorno { stroke-dashoffset: 1800; }
html.js [data-vibe="portada"].abierta .arco__contorno {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1500ms var(--ease-in-out) calc(var(--c-base) + 300ms);
}

/* ---------- Detalles · «Trazo» ----------
   Es un itinerario, y un itinerario se recorre: la línea baja desde la
   foto y cada dato enciende su nodo cuando la línea llega. */
[data-vibe="detalles"] {
  --c-desde: translateY(16px);
  --c-ms: 560ms;
  --c-paso: 90ms;
}
.datos::before { transform-origin: 50% 0; }
html.js [data-vibe="detalles"]:not(.abierta) .datos::before { transform: scaleY(0); }
html.js [data-vibe="detalles"].abierta .datos::before {
  transition: transform 600ms linear calc(var(--c-base) + 2 * var(--c-paso));
}
html.js [data-vibe="detalles"]:not(.abierta) .fila::before { transform: scale(.4); opacity: 0; }
html.js [data-vibe="detalles"].abierta .fila::before {
  transition: transform 420ms var(--ease-out) calc(var(--c-base) + var(--c, 0) * var(--c-paso) + 140ms),
              opacity 300ms var(--ease-out) calc(var(--c-base) + var(--c, 0) * var(--c-paso) + 140ms);
}

/* ---------- Hospedaje y dress code · «Marea» ----------
   Todo llega empujado desde la izquierda, más lento y con más recorrido:
   es la pantalla del mar. La línea entre la llegada y la salida se
   tiende de una orilla a la otra, y las siluetas se visten al final. */
[data-vibe="hospedaje"] {
  --c-desde: translateX(-22px);
  --c-ms: 640ms;
  --c-paso: 100ms;
}
.estancia__puente i { transform-origin: 0 50%; }
html.js [data-vibe="hospedaje"]:not(.abierta) .estancia__puente i { transform: scaleX(0); }
html.js [data-vibe="hospedaje"].abierta .estancia__puente i {
  transition: transform 700ms var(--ease-in-out) calc(var(--c-base) + var(--c, 0) * var(--c-paso) + 320ms);
}
/* Las siluetas empiezan vacías y se llenan de su color. Es una animación
   y no una transición para que, pasado el estreno, elegir otro color
   siga siendo instantáneo y no herede ningún retraso. Solo la figura de
   hombres, que es la que se ve al abrir; la de mujeres no hace falta. */
@keyframes vestir { from { fill: var(--c-fondo); } }
[data-vibe="hospedaje"].estreno #panel-hombres .probador__pieza {
  animation: vestir 800ms var(--ease-out) 1300ms backwards;
}

/* ---------- RSVP · «Conversación» ----------
   La pregunta llega, y las respuestas contestan: una desde cada lado,
   alternadas, como los mensajes de una charla. Cada ornamento llega
   después de su tarjeta, no con ella. */
[data-vibe="rsvp"] {
  --c-desde: translateY(20px);
  --c-ms: 560ms;
  --c-paso: 110ms;
}
html.js [data-vibe="rsvp"] .rsvp__botones > [data-c]:nth-child(odd)  { --c-desde: translateX(-24px); }
html.js [data-vibe="rsvp"] .rsvp__botones > [data-c]:nth-child(even) { --c-desde: translateX(24px); }
@keyframes ico-llega { from { opacity: 0; transform: scale(.6) rotate(-12deg); } }
[data-vibe="rsvp"].estreno .opcion__ico {
  animation: ico-llega 520ms var(--ease-out) calc(var(--c-base) + var(--c, 0) * var(--c-paso) + 280ms) backwards;
}

/* ---------- Confirmación · «Cordel» ----------
   La tarjeta cuelga de un hilo: el hilo baja, la tarjeta llega
   columpiándose un poco y se asienta. Es lo último que se abre y el
   agradecimiento, así que es la más suave. No tiene sello: ya se llegó
   hasta aquí con un «Continuar». */
.confirmacion .cordel {
  position: absolute;
  left: 50%;
  top: 0;
  width: 1px;
  height: 64px;
  background: var(--riel);
  transform-origin: 50% 0;
  transition: transform 650ms var(--ease-out);
}
html.js [data-vibe="confirmacion"]:not(.abierta) .cordel { transform: scaleY(0); }
html.js [data-vibe="confirmacion"]:not(.abierta) .card--confirmacion { opacity: 0; }
[data-vibe="confirmacion"].abierta .card--confirmacion {
  transform-origin: 50% 0;
  animation: cuelga 1000ms var(--ease-out) 350ms backwards;
}
@keyframes cuelga {
  0%   { opacity: 0; transform: translateY(-18px) rotate(-1.6deg); }
  55%  { opacity: 1; transform: translateY(2px) rotate(.7deg); }
  100% { opacity: 1; transform: none; }
}

/* Lo de dentro de la tarjeta también llega en cascada, cada vez que se
   cambia de cara y no solo al abrir. `backwards` y no `both`: al terminar
   el elemento vuelve a su estilo normal y el `:active` de un botón deja
   de estar tapado por la animación. */
.confirmacion__cara > :nth-child(1)  { --n: 0; }
.confirmacion__cara > :nth-child(2)  { --n: 1; }
.confirmacion__cara > :nth-child(3)  { --n: 2; }
.confirmacion__cara > :nth-child(4)  { --n: 3; }
.confirmacion__cara > :nth-child(5)  { --n: 4; }
.confirmacion__cara > :nth-child(6)  { --n: 5; }
.confirmacion__cara > :nth-child(7)  { --n: 6; }
.confirmacion__cara > :nth-child(8)  { --n: 7; }
[data-vibe="confirmacion"].abierta .confirmacion__cara > * {
  animation: cara-llega 460ms var(--ease-out) calc(var(--n, 0) * 70ms) backwards;
}
/* La primera vez esperan a que el hilo y la tarjeta hayan llegado. */
[data-vibe="confirmacion"].inicio.abierta .confirmacion__cara > * {
  animation-delay: calc(800ms + var(--n, 0) * 70ms);
}
@keyframes cara-llega { from { opacity: 0; transform: translateY(10px); } }

/* Pedir menos movimiento no es pedir menos información: todo aparece,
   sin recorrido y sin esperar su turno. */
@media (prefers-reduced-motion: reduce) {
  html.js [data-vibe] [data-c] { transform: none !important; }
  html.js [data-vibe] *,
  html.js [data-vibe] *::before,
  html.js [data-vibe] *::after { transition-delay: 0s !important; }
}

@media print {
  [data-c] { opacity: 1 !important; transform: none !important; }
}
