@font-face {
  font-family: "Baloo 2";
  src: url("../admin/fonts/baloo2-600.woff2") format("woff2");
  font-style: normal;
  font-weight: 600;
  font-display: swap;
}

@font-face {
  font-family: "Baloo 2";
  src: url("../admin/fonts/baloo2-800.woff2") format("woff2");
  font-style: normal;
  font-weight: 800;
  font-display: swap;
}

/* ============================================================
   Invitación inmersiva. La página completa se tematiza con las
   variables que ya publica cb_theme_css_vars(): esta hoja no
   conoce ninguna temática por nombre.
   ============================================================ */

* {
  box-sizing: border-box;
}

/* Botón de silenciar música — MISMO estilo que .mute-btn del kiosco
   (src/styles.css) a propósito: un invitado que ya jugó en el kiosco
   reconoce el mismo control acá. */
.mute-btn {
  position: fixed;
  bottom: 20px;
  left: 20px;
  z-index: 999;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, .75);
  backdrop-filter: blur(8px);
  font-size: 22px;
  cursor: pointer;
  box-shadow: 0 3px 12px rgba(0, 0, 0, .18);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform .15s;
}

.mute-btn:active {
  transform: scale(.92);
}

.mute-btn[hidden] {
  display: none;
}

/* Respaldo cuando el navegador bloquea una rueda/scroll sin activación.
   Un toque aquí sí autoriza audio; al iniciar, invitation.js lo vuelve a ocultar. */
.inv-audio-activate {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 1000;
  max-width: calc(100vw - 36px);
  min-height: 46px;
  padding: 10px 16px;
  border: 1px solid rgba(255, 255, 255, .8);
  border-radius: 999px;
  background: linear-gradient(135deg, var(--inv-accent, #7c3aed), var(--inv-highlight, #fbbf24));
  color: #fff;
  font: 800 16px/1.1 "Baloo 2", system-ui, sans-serif;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .26);
}

.inv-audio-activate:active {
  transform: scale(.97);
}

.inv-audio-activate[hidden] {
  display: none;
}

/* La invitación parte detrás de esta portada para conseguir un gesto real de
   audio. No aparece si JavaScript está desactivado. */
.inv-entry-gate {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 2000;
  place-items: center;
  padding: 24px;
  background: linear-gradient(160deg, rgba(9, 7, 20, .82), rgba(28, 13, 55, .72));
  backdrop-filter: blur(8px);
}

html.inv-entry-gate-active,
body.inv-entry-gate-active {
  overflow: hidden;
  overscroll-behavior: none;
}

body.inv-entry-gate-active .inv-entry-gate {
  display: grid;
}

/* Intro cinematográfico por temática. El video ocupa el viewport completo y
   la interfaz queda deliberadamente mínima: marca real, progreso y salida. */
.inv-theme-intro {
  position: fixed;
  inset: 0;
  z-index: 2100;
  overflow: hidden;
  background: #050509;
  opacity: 1;
  transition: opacity 320ms ease;
}

.inv-theme-intro[hidden] {
  display: none;
}

.inv-theme-intro.is-leaving {
  opacity: 0;
  pointer-events: none;
}

html.inv-theme-intro-active,
body.inv-theme-intro-active {
  overflow: hidden;
  overscroll-behavior: none;
}

.inv-theme-intro-media,
.inv-theme-intro-shade {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.inv-theme-intro-media {
  object-fit: cover;
  background: #050509;
}

.inv-theme-intro-shade {
  pointer-events: none;
  background: linear-gradient(180deg, rgba(0, 0, 0, .28) 0%, transparent 24%, transparent 72%, rgba(0, 0, 0, .22) 100%);
}

/* ── Lockup de CumpleClick ───────────────────────────────────────────────────
   Espejo de src/brand/lockup.css, que usan el album y el cartel QR. No se
   comparte archivo porque aquella vive dentro del bundle de Vite y esta la
   sirve PHP directo, pero las clases, las proporciones y el markup
   (cb_lockup_html() en lib.php) son los mismos para que se vean identicos.

   El nombre va en HTML y no dentro del SVG: un SVG en un <img> no ve las
   @font-face de la pagina, y Baloo 2 no viene instalada en Windows ni en iOS.
   Ver el comentario largo en cb_lockup_html().

   Proporciones del archivo oficial (viewBox 640x160): globo 1.93em, aire
   0.37em, palabra 5.59em de ancho. Todo cuelga del font-size. */

.cc-lockup {
  display: inline-flex;
  align-items: center;
  gap: 0.37em;
  line-height: 1;
  white-space: nowrap;
}

.cc-lockup__mark {
  width: 1.93em;
  height: 1.93em;
  display: block;
  flex: none;
}

.cc-lockup__nombre {
  font-family: "Baloo 2", "Segoe UI Rounded", system-ui, sans-serif;
  font-weight: 800;
  font-size: 1em;
  letter-spacing: 0;
  text-transform: none;
  transform: translateY(0.04em);
}

.cc-lockup--marca .cc-lockup__nombre { color: #4C2882; }
.cc-lockup--marca .cc-lockup__click { color: #D6307F; }

/* Sobre fondo oscuro el globo va tal cual y la palabra en blanco: el manual de
   marca prohibe recuadrar el logo en una caja blanca. */
.cc-lockup--claro .cc-lockup__nombre,
.cc-lockup--claro .cc-lockup__click { color: #fff; }

/* Marca de agua sobre el video de intro. El ancho total del lockup es 7.89em
   (globo + aire + palabra), asi que el font-size sale de dividir el ancho que
   se queria —de 86 a 118 px— por esa cifra. */
.inv-theme-intro-brand {
  position: absolute;
  top: max(18px, calc(10px + env(safe-area-inset-top)));
  left: max(16px, calc(10px + env(safe-area-inset-left)));
  z-index: 2;
  font-size: clamp(10.9px, 3.04vw, 15px);
  opacity: .58;
  /* Dos sombras y no una. Antes la palabra venia en violeta oscuro dentro del
     SVG y se leia sobre los videos claros, pero desaparecia en los oscuros.
     Ahora va en blanco, que es lo correcto para una marca de agua, y el
     problema se invierte: sobre el hielo se pierde. Un contorno corto y opaco
     mas un halo ancho la sostienen sobre cualquiera de los dos sin subir la
     opacidad, que es lo que la volveria un cartel en vez de una filigrana. */
  filter:
    drop-shadow(0 1px 1.5px rgba(0, 0, 0, .58))
    drop-shadow(0 2px 10px rgba(0, 0, 0, .45));
}

.inv-theme-intro-skip {
  position: absolute;
  top: max(18px, calc(10px + env(safe-area-inset-top)));
  right: max(16px, calc(10px + env(safe-area-inset-right)));
  z-index: 3;
  min-height: 44px;
  padding: 9px 15px;
  border: 1px solid rgba(255, 255, 255, .72);
  border-radius: 999px;
  background: rgba(14, 13, 21, .48);
  color: #fff;
  font: 800 15px/1 'Baloo 2', system-ui, sans-serif;
  letter-spacing: .01em;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .2);
  backdrop-filter: blur(12px) saturate(1.1);
}

.inv-theme-intro-skip:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

.inv-theme-intro-skip:active {
  transform: scale(.96);
}

.inv-theme-intro-progress {
  position: absolute;
  right: max(18px, calc(12px + env(safe-area-inset-right)));
  bottom: max(18px, calc(12px + env(safe-area-inset-bottom)));
  left: max(18px, calc(12px + env(safe-area-inset-left)));
  z-index: 2;
  height: 3px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(255, 255, 255, .3);
}

.inv-theme-intro-progress span {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  background: #fff;
  transform: scaleX(0);
  transform-origin: left center;
}

.inv-body[data-theme='hielo'] .inv-theme-intro-progress span {
  background: #dff8ff;
  box-shadow: 0 0 12px rgba(143, 223, 255, .9);
}

.inv-body[data-theme='carreras'] .inv-theme-intro-progress span {
  background: #ffd34f;
  box-shadow: 0 0 12px rgba(255, 178, 28, .85);
}

@media (prefers-reduced-motion: reduce) {
  .inv-theme-intro {
    transition: none;
  }
}

.inv-entry-gate-card {
  width: min(100%, 340px);
  padding: 28px 22px;
  border: 1px solid rgba(255, 255, 255, .6);
  border-radius: 28px;
  background: color-mix(in srgb, var(--inv-deep, #180d30) 86%, #000);
  color: #fff;
  text-align: center;
  box-shadow: 0 20px 50px rgba(0, 0, 0, .4);
}

/* ---------- Sobre animado de la portada ----------
   Toda la tarjeta es el botón: tocar en cualquier parte cuenta como el gesto
   que desbloquea audio (mejor blanco táctil en mobile que un botón chico). El
   sobre es CSS puro — sin imagen, sin librería — porque es una sola
   micro-animación de apertura, no vale la pena una dependencia para esto. */
.inv-envelope-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  width: 100%;
  padding: 6px;
  border: none;
  background: none;
  cursor: pointer;
  color: #fff;
  font: 800 18px/1.1 "Baloo 2", system-ui, sans-serif;
}

.inv-envelope-btn:active .inv-envelope {
  transform: scale(.97);
}

.inv-envelope {
  position: relative;
  width: min(72vw, 220px);
  aspect-ratio: 3 / 2;
  perspective: 700px;
  transition: transform .15s;
}

.inv-envelope-letter,
.inv-envelope-body,
.inv-envelope-pocket,
.inv-envelope-flap {
  position: absolute;
  inset: 0;
}

/* La carta: empieza casi escondida detrás del sobre y sube+asoma al abrir. */
.inv-envelope-letter {
  top: 6%;
  bottom: 10%;
  left: 8%;
  right: 8%;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10%;
  padding: 14% 12%;
  border-radius: 10px;
  background: color-mix(in srgb, var(--inv-highlight, #fbbf24) 18%, #fff);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .22);
  transform: translateY(6%) scale(.92);
  transition: transform .55s cubic-bezier(.2, .8, .2, 1);
}

.inv-envelope-letter-line {
  height: 10%;
  border-radius: 999px;
  background: color-mix(in srgb, var(--inv-accent, #7c3aed) 55%, transparent);
}

.inv-envelope-letter-line--short {
  width: 60%;
}

/* Cuerpo del sobre (rectángulo de fondo) y el bolsillo triangular de abajo,
   que se queda quieto — solo la solapa de arriba se mueve. */
.inv-envelope-body {
  z-index: 2;
  border-radius: 14px;
  background: linear-gradient(160deg, var(--inv-accent, #7c3aed), color-mix(in srgb, var(--inv-accent, #7c3aed) 70%, #000));
  box-shadow: 0 14px 30px rgba(0, 0, 0, .3);
}

.inv-envelope-pocket {
  z-index: 3;
  clip-path: polygon(0 100%, 50% 42%, 100% 100%);
  background: color-mix(in srgb, var(--inv-accent, #7c3aed) 82%, #000);
}

/* La solapa: un triángulo que gira hacia atrás sobre su borde superior, como
   tapa de sobre real. Cerrada tapa la carta (z-index más alto que la carta);
   al abrir gira y queda "detrás" visualmente por la perspectiva. */
.inv-envelope-flap {
  z-index: 4;
  clip-path: polygon(0 0, 100% 0, 50% 62%);
  background: color-mix(in srgb, var(--inv-highlight, #fbbf24) 65%, var(--inv-accent, #7c3aed));
  transform-origin: top center;
  transition: transform .5s cubic-bezier(.3, .7, .3, 1);
  backface-visibility: hidden;
}

/* Abierto: la solapa gira hacia arriba/atrás y la carta sube y asoma por
   encima del sobre. Se activa agregando .is-opening (invitation.js), con un
   pequeño desfase para que se vea "la solapa abre y RECIÉN ahí sale la carta". */
.inv-envelope.is-opening .inv-envelope-flap {
  transform: rotateX(-150deg);
  z-index: 1;
}

.inv-envelope.is-opening .inv-envelope-letter {
  transition-delay: .12s;
  transform: translateY(-62%) scale(1.04);
  z-index: 5;
}

.inv-entry-gate-title {
  font-size: 22px;
}

.inv-entry-gate-hint {
  font-size: 15px;
  font-weight: 600;
  opacity: .82;
}

/* Toda la tarjeta se desvanece cuando ya se abrió, antes de ocultarse. */
.inv-entry-gate-card.is-leaving {
  transition: opacity .35s ease, transform .35s ease;
  opacity: 0;
  transform: scale(.96);
}

@media (prefers-reduced-motion: reduce) {
  .inv-envelope-letter,
  .inv-envelope-flap,
  .inv-entry-gate-card.is-leaving {
    transition: none !important;
  }
}

.inv-body {
  --inv-accent: var(--pink, #7c3aed);
  --inv-highlight: var(--yellow, #fbbf24);
  --inv-dark-1: var(--dark1, #24103f);
  --inv-dark-2: var(--dark2, #10081f);
  --inv-light-1: var(--bg-light1, #fdf6ff);
  --inv-light-2: var(--bg-light2, #eef6ff);
  --inv-ink: var(--ink, #1f172a);
  /* Igual que en la ficha del protagonista: base neutra teñida por el tema,
     porque algunas paletas traen sus "dark" en color saturado. */
  --inv-deep: color-mix(in srgb, var(--inv-dark-2) 58%, #0a0912);
  --inv-deep-raise: color-mix(in srgb, var(--inv-dark-1) 42%, #0f0e1a);
  margin: 0;
  background: var(--inv-deep);
  color: var(--inv-ink);
  font-family: "Baloo 2", "Segoe UI Rounded", system-ui, sans-serif;
  overflow-x: hidden;
}

.inv {
  display: flex;
  width: 100%;
  flex-direction: column;
}

/* ---------- Hero ---------- */

.inv-hero {
  position: relative;
}

/* El hero de inmersión mide varias pantallas y su escenario queda pegado:
   ese recorrido extra es el que consume el video cuadro a cuadro, así que
   el invitado "avanza" dentro de la escena antes de llegar a la ficha.
   La clase solo se aplica cuando existe el video de entrada. */
.inv-hero--dive {
  height: 320svh;
}

.inv-hero--dive .inv-hero-stage {
  position: sticky;
  top: 0;
  height: 100svh;
  min-height: 0;
}

/* Los textos se apartan a medida que se entra: el nombre sube y se disuelve,
   dejando la escena limpia justo antes de la invitación. */
.inv-hero--dive .inv-hero-copy,
.inv-hero--dive .inv-scroll-hint {
  transition: opacity 200ms linear;
  will-change: opacity, transform;
}

/* `--dive-clear` va de 0 a 1 con el scroll (lo escribe invitation.js). El
   desenfoque y el velo se abren con él: al entrar, la escena se revela
   nítida en vez de quedar como un borrón oscuro. */
.inv-hero--dive {
  --dive-clear: 0;
}

.inv-hero--dive .inv-hero-media {
  filter:
    blur(calc(var(--inv-hero-blur, 1px) * (1 - var(--dive-clear))))
    saturate(calc(1.02 + var(--dive-clear) * .18))
    brightness(calc(var(--inv-hero-brightness, .94) + var(--dive-clear) * .3));
  transform: scale(calc(1.08 + var(--dive-clear) * .1));
}

.inv-hero--dive .inv-hero-stage::after {
  /* Antes iba de 1 a .22: al cargar, antes de que el invitado toque el
     scroll, el velo tapaba casi toda la imagen del poster ("la imagen de la
     invitación" que pidió Luis) y se veía como un borrón sin forma. Ahora
     arranca en .5 —suficiente para que el nombre siga siendo legible sobre
     un fondo claro— y se sigue abriendo igual con el scroll. */
  opacity: calc(.5 - var(--dive-clear) * .3);
}

/* ---------------------------------------------------------------------
   "Momento" de video en scroll — un segundo tramo con el mismo efecto de
   inmersión del hero (desenfoque que se abre, velo que se despeja, zoom que
   se asienta), pero más corto y sin nombre/fecha: solo una leyenda breve.
   Se usa para revivir un instante puntual (p. ej. Rayo cruzando la meta) en
   medio de los capítulos, con video real controlado por el dedo. --------- */

.inv-moment {
  position: relative;
}

.inv-moment--dive {
  /* Más corto que el hero (320svh): acá es un instante, no la apertura de
     toda la invitación. */
  height: 220svh;
  --dive-clear: 0;
}

.inv-moment-stage {
  position: relative;
  height: 100svh;
  overflow: hidden;
  isolation: isolate;
}

/* El hero posiciona su video a través de `.inv-hero-bg`; acá no hay ese
   contenedor de por medio, así que el video se posiciona directo. */
.inv-moment-stage > .inv-hero-media {
  position: absolute;
  inset: 0;
  z-index: -1;
}

.inv-moment--dive .inv-moment-stage {
  position: sticky;
  top: 0;
}

.inv-moment--dive .inv-hero-media {
  filter:
    blur(calc(var(--inv-hero-blur, 2px) * (1 - var(--dive-clear))))
    saturate(calc(1.02 + var(--dive-clear) * .18))
    brightness(calc(var(--inv-hero-brightness, .78) + var(--dive-clear) * .3));
  transform: scale(calc(1.08 + var(--dive-clear) * .1));
}

.inv-moment-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(0, 0, 0, .38) 0%,
    rgba(0, 0, 0, 0) 32%,
    rgba(0, 0, 0, 0) 60%,
    rgba(0, 0, 0, .82) 100%);
}

.inv-moment--dive .inv-moment-stage::after {
  opacity: calc(.5 - var(--dive-clear) * .3);
}

.inv-moment-caption {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: max(48px, calc(32px + env(safe-area-inset-bottom)));
  z-index: 2;
  margin: 0;
  text-align: center;
  color: #fff;
  font-weight: 800;
  font-size: clamp(1.1rem, 5.5vw, 1.7rem);
  text-shadow: 0 4px 20px rgba(0, 0, 0, .6), 0 1px 3px rgba(0, 0, 0, .8);
}

@media (prefers-reduced-motion: reduce) {
  .inv-moment--dive {
    height: 100svh;
  }
}

.inv-hero-stage {
  position: relative;
  display: grid;
  min-height: 100svh;
  align-content: center;
  justify-items: center;
  padding:
    max(56px, calc(32px + env(safe-area-inset-top))) 20px
    max(96px, calc(72px + env(safe-area-inset-bottom)));
  isolation: isolate;
  overflow: hidden;
  text-align: center;
}

.inv-hero-bg {
  position: absolute;
  z-index: -2;
  inset: 0;
  overflow: hidden;
}

/* Spidey: los tres personajes del fondo ocupan justo el centro, donde el
   grid centra el texto — el nombre y la fecha les tapaban la cara (Luis lo
   reportó con captura). El bloque baja al pavimento de la escena; el resto
   de temáticas conserva el centrado de siempre. */
[data-theme="spidey"] .inv-hero-stage {
  align-content: end;
}

/* El fondo temático es atmósfera, no ilustración: desenfocado y desaturado
   deja de competir con el nombre. La pieza que sí se ve nítida es la
   invitación misma, más abajo. */
.inv-hero-media {
  width: 100%;
  height: 100%;
  /* Suficiente desenfoque para que ningún personaje del fondo sea el foco,
     pero no tanto como para perder la temática: el hero debe seguir
     leyéndose como "esta es la fiesta de K-Pop", no como un degradado.
     El brillo lo fija el preset, porque `hielo` y `carreras` son fondos
     claros donde el texto blanco se perdía. */
  filter: blur(var(--inv-hero-blur, 1px)) saturate(1.12) brightness(var(--inv-hero-brightness, .94));
  object-fit: cover;
  transform: scale(1.08);
}

/* El desenfoque escala con el viewport, pero se mantiene bajo: la escena debe
   leerse nítida como una portada, no como un fondo desvanecido tipo marca de
   agua. Solo lo justo para que el texto encima no compita con el detalle. */
@media (min-width: 720px) {
  .inv-hero-media {
    --inv-hero-blur: 2px;
  }
}

@media (min-width: 1200px) {
  .inv-hero-media {
    --inv-hero-blur: 3px;
  }
}

/* El video "auto" (Rayo cruzando la meta) es el contenido, no un fondo
   decorativo: el blur/dim de arriba está pensado para fotos estáticas y
   dejaba a Rayo casi invisible detrás del nombre. Aquí se ve nítido. */
.inv-hero-media[data-inv-autoplay-once] {
  filter: saturate(1.12) brightness(.96);
  transform: scale(1.02);
}

.inv-hero-stage::after {
  position: absolute;
  z-index: -1;
  inset: 0;
  /* El velo se concentra donde va el texto y se abre arriba, para que el
     fondo temático respire; abajo cierra sólido para empalmar con la
     siguiente sección sin costura. */
  background:
    radial-gradient(96% 52% at 50% 60%,
      color-mix(in srgb, var(--inv-deep) calc(60% + var(--inv-hero-dim, .38) * 55%), transparent),
      transparent 78%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--inv-deep) calc(var(--inv-hero-dim, .38) * 62%), transparent),
      color-mix(in srgb, var(--inv-deep) calc(var(--inv-hero-dim, .38) * 54%), transparent) 42%,
      color-mix(in srgb, var(--inv-deep) 76%, transparent) 84%,
      var(--inv-deep) 100%);
  content: "";
}

.inv-hero-copy {
  position: relative;
  z-index: 1;
  /* 22rem alcanzaba de sobra para el overline corto de antes; la frase
     completa del kicker necesita más ancho para no quedar en 3 líneas
     apretadas. */
  max-width: 27rem;
  color: #fff;
}

.inv-kicker {
  /* Antes era un overline corto ("Estás invitado a celebrar") en mayúsculas
     con tracking ancho. Ahora es una frase completa ("Tenemos el agrado de
     invitarte a celebrar el cumpleaños de:") — en mayúsculas con ese tracking
     se volvía un bloque de 3 líneas difícil de leer, así que baja a caja
     normal y tracking casi nulo, como una oración de verdad. */
  margin: 0 0 10px;
  max-width: 26rem;
  color: color-mix(in srgb, var(--inv-title, var(--inv-highlight)) 46%, white);
  font-size: clamp(.82rem, 3.6vw, .98rem);
  font-weight: 700;
  line-height: 1.35;
  letter-spacing: .01em;
  text-shadow: 0 2px 14px rgba(0, 0, 0, .8), 0 1px 3px rgba(0, 0, 0, .9);
  text-transform: none;
}

.inv-name {
  margin: 0;
  font-size: clamp(2.6rem, 15vw, 4.6rem);
  font-weight: 800;
  line-height: .95;
  overflow-wrap: anywhere;
  text-shadow: 0 8px 40px rgba(0, 0, 0, .75), 0 2px 8px rgba(0, 0, 0, .85);
  text-wrap: balance;
}

.inv-hero-when {
  margin: 16px auto 0;
  padding: 9px 18px;
  border: 1px solid color-mix(in srgb, var(--inv-highlight) 55%, transparent);
  border-radius: 999px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--inv-accent) 88%, black) 0%,
    color-mix(in srgb, var(--inv-accent) 62%, var(--inv-deep-raise)) 100%);
  /* Texto amarillo sobre píldora roja: es el par de color de la marca (auto
     rojo + rayo amarillo) y resalta mucho más que el blanco plano de antes. */
  color: var(--inv-highlight);
  font-size: .96rem;
  font-weight: 800;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .55);
  backdrop-filter: blur(8px);
}

.inv-scroll-hint {
  position: absolute;
  bottom: max(24px, calc(14px + env(safe-area-inset-bottom)));
  display: grid;
  justify-items: center;
  gap: 8px;
  min-height: 48px;
  padding: 10px 20px;
  /* Vive fuera del velo radial, así que lleva su propia superficie: sobre
     fondos claros se volvía ilegible. */
  border-radius: 999px;
  background: color-mix(in srgb, var(--inv-deep) 72%, transparent);
  color: #fff;
  font-size: .8rem;
  font-weight: 800;
  letter-spacing: .04em;
  text-decoration: none;
  backdrop-filter: blur(8px);
}

.inv-scroll-arrow {
  width: 12px;
  height: 12px;
  border-right: 2.5px solid currentColor;
  border-bottom: 2.5px solid currentColor;
  transform: rotate(45deg);
  animation: inv-nudge 1.9s ease-in-out infinite;
}

@keyframes inv-nudge {
  0%, 100% { transform: rotate(45deg) translate(0, 0); opacity: .65; }
  50% { transform: rotate(45deg) translate(3px, 3px); opacity: 1; }
}

/* Solo la comparación "auto" la usa: marca el instante en que el video de
   entrada terminó y ya se puede seguir bajando. Antes de eso la pista está
   quieta a propósito, para no competir con el video mientras corre. */
.inv-scroll-hint--ready {
  animation: inv-hint-pop 480ms cubic-bezier(.34, 1.56, .64, 1);
  box-shadow: 0 0 0 2px var(--inv-highlight, #fbbf24);
}

/* Video intro locks navigation until the clip has completed. */
html.inv-scroll-locked,
body.inv-scroll-locked {
  overflow: hidden;
  overscroll-behavior: none;
}

body.inv-scroll-locked {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
}

.inv-scroll-hint--waiting {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
}

/* El hint del hero también puede ser un <button> (variante "toca para
   seguir"): mismo aspecto que el <a>/<span>, sin el traje de botón del
   navegador. La transición hace que a los 3 segundos aparezca en un fundido
   en vez de saltar. */
button.inv-scroll-hint {
  border: 0;
  font: inherit;
  /* Blanco EXPLICITO, no inherit: `button.inv-scroll-hint` es mas especifico
     que `.inv-scroll-hint`, asi que un inherit aca pisaba el blanco de la
     base con el color apagado del contenedor y el texto se veia opaco
     (reporte de Luis 2026-09-01). */
  color: #fff;
  opacity: 1;
  cursor: pointer;
  transition: opacity 320ms ease, visibility 320ms ease;
}

/* La regla de arriba puso opacity:1 con mas especificidad que la base: el
   estado oculto lo tiene que volver a ganar, o el boton se ve desde el
   primer segundo. */
button.inv-scroll-hint.inv-scroll-hint--waiting {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
}

@keyframes inv-hint-pop {
  0% { transform: scale(1); }
  40% { transform: scale(1.12); }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .inv-scroll-hint--ready {
    animation: none;
  }
}

/* ---------- Secciones sobre fondo profundo ---------- */

.inv-art-section,
.inv-facts,
.inv-message,
.inv-save,
.inv-finale {
  width: min(100%, 560px);
  margin: 0 auto;
  padding: 34px 20px;
}

.inv-art-frame {
  margin: 0;
  border: 1px solid color-mix(in srgb, var(--inv-highlight) 26%, transparent);
  border-radius: 26px;
  background: var(--inv-deep-raise);
  box-shadow: 0 26px 60px rgba(0, 0, 0, .5);
  overflow: hidden;
}

.inv-art,
.inv-art-video {
  display: block;
  width: 100%;
  height: auto;
}

/* ---------- Plantilla + datos ----------
   La imagen base se genera una sola vez por temática, con un panel vacío. El
   nombre y la fecha se componen aquí en HTML sobre ese panel, con coordenadas
   normalizadas que vienen del preset. Cambiar de fiesta no exige regenerar
   nada. Las unidades son porcentajes del propio marco, así que la
   superposición se mantiene alineada en cualquier ancho. */
.inv-art-frame--template {
  position: relative;
}

.inv-plate {
  position: absolute;
  top: var(--plate-y);
  left: var(--plate-x);
  display: flex;
  width: var(--plate-w);
  height: var(--plate-h);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2% 4%;
  color: var(--plate-tone);
  text-align: center;
  /* cqw, no vw: el lienzo es el marco de la invitación, no la ventana. */
  container-type: inline-size;
}

/* Placa sobre la foto grupal: antes usaba solo --plate-tone (un color oscuro
   por tema, pensado para legibilidad, pero plano). Ahora el nombre y el kicker
   usan el rojo vivo de la temática y fecha/lugar quedan en placas rojo+amarillo
   —el mismo par que ya lleva la píldora del hero— así resalta de verdad sobre
   la foto en vez de perderse. */
.inv-plate-kicker {
  /* Mismo motivo que .inv-kicker: frase completa, no un overline de 2-3
     palabras. Mayúsculas + tracking ancho la volvían ilegible en la placa,
     que ya es angosta de por sí. */
  margin: 0;
  font-size: clamp(.4rem, 3.4cqw, .82rem);
  font-weight: 700;
  letter-spacing: .01em;
  line-height: 1.22;
  color: var(--inv-accent, var(--plate-tone));
  text-shadow: 0 1px 3px rgba(0, 0, 0, .3);
  text-transform: none;
}

.inv-plate-name {
  margin: .18em 0 0;
  font-size: clamp(1.1rem, 17cqw, 4rem);
  font-weight: 800;
  line-height: .96;
  overflow-wrap: anywhere;
  text-wrap: balance;
  color: var(--inv-accent, var(--plate-tone));
  text-shadow: 0 2px 0 rgba(255, 255, 255, .3), 0 4px 16px color-mix(in srgb, var(--inv-highlight) 55%, transparent);
}

.inv-plate-when,
.inv-plate-where {
  display: inline-flex;
  align-items: center;
  gap: .3em;
  margin: .32em .2em 0;
  padding: .22em .7em;
  border-radius: 999px;
  background: var(--inv-accent, var(--plate-tone));
  color: var(--inv-highlight, #fff);
  font-size: clamp(.46rem, 4.4cqw, 1.1rem);
  font-weight: 800;
  line-height: 1.3;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .45);
}

.inv-art-caption {
  padding: 12px 16px;
  color: rgba(255, 255, 255, .6);
  font-size: .78rem;
  text-align: center;
}

.inv-art-video {
  background: #000;
}

.inv-facts {
  display: grid;
  gap: 12px;
}

.inv-fact {
  padding: 18px 20px;
  border: 1px solid rgba(255, 255, 255, .14);
  border-left: 4px solid var(--inv-accent);
  border-radius: 8px 20px 20px 8px;
  background: color-mix(in srgb, var(--inv-deep-raise) 92%, transparent);
  color: #fff;
}

.inv-fact[data-fact="donde"] {
  border-left-color: var(--inv-highlight);
}

.inv-fact-label {
  margin: 0 0 4px;
  color: rgba(255, 255, 255, .74);
  font-size: .74rem;
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.inv-fact-value {
  margin: 0;
  font-size: 1.22rem;
  font-weight: 800;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.inv-fact-extra {
  margin: 4px 0 0;
  color: color-mix(in srgb, var(--inv-highlight) 76%, white);
  font-size: 1rem;
  font-weight: 700;
}

.inv-message blockquote {
  position: relative;
  margin: 0;
  padding: 26px 22px 22px;
  border-radius: 24px;
  background: color-mix(in srgb, var(--inv-deep-raise) 84%, transparent);
  color: #fff;
  font-size: 1.1rem;
  line-height: 1.55;
  text-align: center;
}

.inv-message blockquote::before {
  display: block;
  margin-bottom: 6px;
  color: color-mix(in srgb, var(--inv-highlight) 80%, white);
  content: "\201C";
  font-size: 2.6rem;
  line-height: .6;
}

.inv-save {
  text-align: center;
}

.inv-save-label {
  margin: 0 0 14px;
  color: rgba(255, 255, 255, .78);
  font-size: .78rem;
  font-weight: 800;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.inv-save-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}

.inv-button {
  display: inline-flex;
  min-height: 48px;
  align-items: center;
  /* Todos los botones llevan icono, así que la separación va acá y no solo
     en el de WhatsApp. */
  gap: 9px;
  padding: 13px 24px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--inv-accent);
  color: #fff;
  font-weight: 800;
  text-decoration: none;
}

.inv-button-ghost {
  border-color: rgba(255, 255, 255, .32);
  background: transparent;
}

/* Verde de WhatsApp: es el color por el que la gente reconoce la acción.
   Contraste con blanco = 4,6:1, sobre el umbral AA. */
.inv-button-wa {
  background: #1fa855;
}

/* Los iconos son SVG inline y heredan el color del botón, así el mismo
   glifo sirve en el botón verde de WhatsApp y en los fantasma. */
.inv-wa-glyph,
.inv-glyph {
  width: 19px;
  height: 19px;
  flex: 0 0 auto;
}

.inv-button[disabled] {
  cursor: progress;
  opacity: .6;
}

.inv-share-status:empty {
  display: none;
}

.inv-share-status {
  margin: 14px auto 0;
  max-width: 24rem;
  color: rgba(255, 255, 255, .84);
  font-size: .88rem;
  line-height: 1.4;
}

.inv-button:focus-visible,
.inv-scroll-hint:focus-visible {
  outline: 3px solid var(--inv-highlight);
  outline-offset: 4px;
}

/* ---------- Cierre: acceso al protagonista ---------- */

.inv-finale {
  display: grid;
  justify-items: center;
  padding-top: 44px;
  padding-bottom: 52px;
  text-align: center;
}

.inv-finale-title {
  margin: 0;
  color: #fff;
  font-size: clamp(1.7rem, 8vw, 2.5rem);
  font-weight: 800;
  line-height: 1.05;
  text-wrap: balance;
}

.inv-finale-lede {
  margin: 12px auto 22px;
  max-width: 26rem;
  color: rgba(255, 255, 255, .84);
  font-size: 1rem;
  line-height: 1.5;
}

.inv-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 22px 20px max(24px, calc(14px + env(safe-area-inset-bottom)));
  color: rgba(255, 255, 255, .55);
  font-size: .82rem;
  text-align: center;
}

/* 26 px de alto los marca el globo, que mide 1.93em: 26 / 1.93 = 13.5px. */
.inv-footer-logo {
  font-size: 13.5px;
  opacity: .85;
}

.inv-footer-credit {
  margin: 0;
}

.inv-footer-credit a {
  color: inherit;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ---------- Aparición al hacer scroll ---------- */

/* Aditiva a propósito: el contenido es visible por defecto y el JS solo
   añade la animación. Si el script no carga, la invitación se lee igual. */
.inv-js .inv-reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 620ms ease, transform 620ms cubic-bezier(.2, .8, .2, 1);
}

.inv-js .inv-reveal.is-visible {
  opacity: 1;
  transform: none;
}

@media (min-width: 720px) {
  .inv-art-section,
  .inv-facts,
  .inv-message,
  .inv-save,
  .inv-finale {
    width: min(100%, 680px);
    padding-right: 28px;
    padding-left: 28px;
  }

  .inv-facts {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  }

  .inv-hero-copy {
    max-width: 34rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .inv-scroll-arrow {
    animation: none;
  }

  .inv-js .inv-reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* El loop de fondo lo pausa invitation.js: `animation-play-state` no afecta
     a la reproducción de un <video>. */

  html {
    scroll-behavior: auto;
  }
}

@media (forced-colors: active) {
  .inv-fact,
  .inv-button,
  .inv-art-frame {
    border: 2px solid ButtonText;
  }

  .inv-hero-media {
    display: none;
  }
}

/* ---------------------------------------------------------------------
   Mapa y acciones dentro de las tarjetas de datos
   --------------------------------------------------------------------- */

.inv-map {
  position: relative;
  margin: 14px 0 12px;
  border-radius: 14px;
  /* El iframe trae su propio fondo blanco: sin recorte se ven las esquinas
     cuadradas asomando por debajo del radio de la tarjeta. */
  overflow: hidden;
  aspect-ratio: 16 / 10;
  background: rgba(255, 255, 255, .08);
}

.inv-map iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.inv-fact-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.inv-chip {
  display: inline-flex;
  /* 44px es el mínimo de blanco de toque: por debajo, el dedo falla y hay que
     reintentar. Medido en 375px de ancho, estos chips daban 40 y 42 px de alto,
     y son justo los que aprieta el invitado desde el celular — agendar la
     fiesta, abrir el mapa. El ancho ya pasaba de sobra; el alto no. */
  min-height: 44px;
  align-items: center;
  gap: 7px;
  padding: 11px 15px;
  border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 999px;
  background: rgba(255, 255, 255, .07);
  color: #fff;
  font-size: .86rem;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}

.inv-chip .inv-glyph {
  width: 16px;
  height: 16px;
}

/* El atributo `hidden` solo vale `display: none` por la hoja del navegador,
   que pierde contra cualquier selector de clase. Dar `display: inline-flex` a
   un botón que a veces va oculto lo hacía aparecer siempre: pasó con
   "Compartir imagen", que debe salir solo si el navegador puede compartir
   archivos. Esta regla cierra la familia completa de ese bug. */
[hidden] {
  display: none !important;
}

/* ---------------------------------------------------------------------
   Capítulos ilustrados en scroll (versión 2, comparación con el hero de
   video). Es la misma idea que Voyarelle (higgsfield.app): capas de imagen
   fija que se funden entre sí a medida que se hace scroll, en vez de un
   video continuo. Cada capa es una imagen real ya aprobada del kiosco —
   nada se genera para esto — así que no hereda el riesgo de moderación de
   un modelo de video.
   --------------------------------------------------------------------- */

.inv-chapters {
  position: relative;
  /* 90svh por unidad de recorrido (no por slot): una imagen vale 1 unidad,
     un video puede valer más — necesita ese ancho extra de scroll para que
     el dedo lo recorra cuadro a cuadro en vez de solo cruzarlo de golpe. */
  height: calc(var(--inv-chapters-units, var(--inv-chapters-count, 3)) * 90svh);
}

.inv-chapters-stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  isolation: isolate;
}

.inv-chapters-media {
  position: absolute;
  inset: 0;
}

.inv-chapters-layer {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  /* La opacidad y el `transform` los fija JS en cada frame de scroll. La
     transición corta solo amortigua el paso entre frames de rAF; NO se
     transiciona `transform`, porque competiría con el control por scroll y
     dejaría el zoom siempre un paso atrás del dedo. */
  transition: opacity 80ms linear;
  will-change: opacity, transform;
  /* Punto de fuga bajo: el zoom se siente como avanzar por la pista, no
     como acercarse al techo. */
  transform-origin: 50% 62%;
}

.inv-chapters-layer:first-child {
  opacity: 1;
}

.inv-chapters-layer img,
.inv-chapters-layer video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(.8) saturate(1.05);
}

/* El slot de video no necesita el mismo apagado que las fotos: el momento
   en que Rayo cruza la meta es el punto más alto del recorrido. */
.inv-chapters-layer--video video {
  /* Encima le pega el mismo velo oscuro que a las fotos (para que se lea el
     texto): sin este empujón extra de brillo, Rayo se veía casi apagado. */
  filter: brightness(1.18) saturate(1.12);
}

/* Los "conectores" son el paso intermedio, no el momento fuerte: un poco
   más apagados para que las escenas principales se sientan como las
   protagonistas del recorrido. */
.inv-chapters-layer--connector img {
  filter: brightness(.7) saturate(.92);
}

.inv-chapters-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(0, 0, 0, .35) 0%,
    rgba(0, 0, 0, 0) 30%,
    rgba(0, 0, 0, 0) 62%,
    rgba(0, 0, 0, .78) 100%);
}

/* El despeje del último capítulo.

   La pastilla "Ver invitación" solo aparece en el último clip, y ahí se
   montaba sobre el texto del capítulo por unos pocos píxeles. Las dos
   medidas salen de acá para que no vuelvan a separarse: cambiar la altura
   de la pastilla corre el texto solo.

   Sube solo cuando la pastilla está visible. En los capítulos anteriores no
   hay nada abajo con qué chocar, y moverlo siempre cambiaría el encuadre de
   Hielo y Carreras, que ya está aprobado. Sin `:has()` la página queda como
   está hoy en producción. */
.inv-chapters-stage,
.inv-playlist-stage {
  --inv-hint-bottom: max(24px, calc(14px + env(safe-area-inset-bottom)));
  --inv-hint-alto: 60px; /* medido en el navegador, no estimado */
  --inv-caption-despeje: calc(var(--inv-hint-bottom) + var(--inv-hint-alto) + 14px);
}

.inv-chapters-stage:has(.inv-scroll-hint.is-visible) .inv-chapters-copy,
.inv-playlist-stage:has(.inv-scroll-hint.is-visible) .inv-playlist-caption {
  bottom: var(--inv-caption-despeje);
}

.inv-chapters-copy {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: max(76px, calc(52px + env(safe-area-inset-bottom)));
  z-index: 2;
  height: 2.6em;
  text-align: center;
}

.inv-chapters-caption {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  transition: opacity 200ms linear;
  color: #fff;
  font-weight: 800;
  font-size: clamp(1.05rem, 5vw, 1.5rem);
  text-shadow: 0 4px 20px rgba(0, 0, 0, .6), 0 1px 3px rgba(0, 0, 0, .8);
}

.inv-chapters-caption.is-active {
  opacity: 1;
}

.inv-chapters-progress {
  position: absolute;
  top: max(20px, env(safe-area-inset-top));
  left: 24px;
  right: 24px;
  z-index: 2;
  height: 3px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .25);
  overflow: hidden;
}

.inv-chapters-progress span {
  display: block;
  height: 100%;
  width: 100%;
  transform-origin: left center;
  transform: scaleX(0);
  background: #fff;
  border-radius: inherit;
}

.inv-chapters-dots {
  position: absolute;
  top: max(34px, calc(env(safe-area-inset-top) + 14px));
  left: 0;
  right: 0;
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: 8px;
}

.inv-chapters-dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .4);
  transition: background 160ms linear, transform 160ms linear;
}

.inv-chapters-dot.is-active {
  background: #fff;
  transform: scale(1.35);
}

@media (prefers-reduced-motion: reduce) {
  .inv-chapters {
    height: 100svh;
  }

  .inv-chapters-layer {
    transition: none;
  }
}

/* ---------------------------------------------------------------------
   Lista de reproducción de videos reales (versión "todo video" sin scroll).
   A diferencia de los capítulos, acá no hay fundido entre capas: es un
   único <video> que va cambiando de fuente. --------------------------- */

.inv-playlist {
  position: relative;
  height: 100svh;
}

.inv-playlist-stage {
  position: relative;
  height: 100%;
  overflow: hidden;
  background: #000;
}

.inv-playlist-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.inv-playlist-progress {
  position: absolute;
  top: max(20px, env(safe-area-inset-top));
  left: 24px;
  right: 24px;
  z-index: 2;
  height: 3px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .25);
  overflow: hidden;
}

.inv-playlist-progress span {
  display: block;
  height: 100%;
  width: 100%;
  transform-origin: left center;
  transform: scaleX(0);
  background: #fff;
  border-radius: inherit;
  transition: transform 300ms ease;
}

.inv-playlist-caption {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: max(76px, calc(52px + env(safe-area-inset-bottom)));
  z-index: 2;
  margin: 0;
  text-align: center;
  color: #fff;
  font-weight: 800;
  font-size: clamp(1.05rem, 5vw, 1.5rem);
  text-shadow: 0 4px 20px rgba(0, 0, 0, .6), 0 1px 3px rgba(0, 0, 0, .8);
}

.inv-playlist-caption:empty {
  display: none;
}

/* El hint reutiliza exactamente el mismo componente del hero (misma pastilla,
   misma flecha que rebota), pero acá vive dentro de una pista de scroll o de
   una lista de reproducción, no del grid centrado del hero: necesita su
   propio centrado horizontal. Empieza invisible y solo se revela al llegar
   al último capítulo o al último clip ("¡Te esperamos!"). */
.inv-chapters-stage .inv-scroll-hint,
.inv-playlist-stage .inv-scroll-hint {
  /* La misma medida que despeja el texto del capítulo, para que no se
     separen nunca. El hero conserva su literal: ahí no hay stage. */
  bottom: var(--inv-hint-bottom);
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  opacity: 0;
  pointer-events: none;
  transition: opacity 260ms ease;
}

.inv-chapters-stage .inv-scroll-hint.is-visible,
.inv-playlist-stage .inv-scroll-hint.is-visible {
  opacity: 1;
  pointer-events: auto;
}

/* Hielo abre como una mañana de cristal: celeste luminoso, no una cortina
   nocturna. El contraste queda concentrado en la tarjeta, no en un fondo
   negro que contradiga la temática. */
.inv-body[data-theme="hielo"] .inv-entry-gate {
  background:
    radial-gradient(90% 60% at 18% 8%, rgba(255, 255, 255, .86), transparent 58%),
    radial-gradient(56% 42% at 86% 85%, rgba(189, 229, 255, .76), transparent 65%),
    linear-gradient(150deg, #effbff 0%, #bfeeff 48%, #6dc9ed 100%);
  backdrop-filter: blur(8px) saturate(1.08);
}

.inv-body[data-theme="hielo"] .inv-entry-gate-card {
  background: linear-gradient(150deg, rgba(255, 255, 255, .94), rgba(214, 244, 255, .88));
  color: #0d3a5c;
  border-color: rgba(255, 255, 255, .94);
  box-shadow: 0 24px 56px rgba(13, 93, 139, .27);
}

.inv-body[data-theme="hielo"] .inv-envelope-body {
  background: linear-gradient(160deg, #77d7fa, #269bc9);
  box-shadow: 0 14px 30px rgba(23, 125, 171, .28);
}

.inv-body[data-theme="hielo"] .inv-envelope-pocket {
  background: #2ba7d6;
}

.inv-body[data-theme="hielo"] .inv-hero-stage,
.inv-body[data-theme="hielo"] .inv-playlist-stage {
  background: #bfeeff;
}
/* Placa de Hielo: el cuadro deja de verse vacío y pasa a ser una tarjeta de
   cristal con jerarquía de invitación real. El contenido sigue siendo HTML
   dinámico para que cada cumpleaños use el mismo fondo reutilizable. */
.inv-body[data-theme="hielo"] .inv-envelope-btn {
  color: #07527b;
  font-size: 20px;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .95), 0 4px 18px rgba(20, 128, 176, .2);
}

.inv-body[data-theme="hielo"] .inv-entry-gate-title {
  color: #07527b;
  font-size: 27px;
  letter-spacing: .01em;
}

.inv-body[data-theme="hielo"] .inv-entry-gate-hint {
  padding: 8px 15px;
  border: 1px solid rgba(18, 132, 181, .25);
  border-radius: 999px;
  background: rgba(255, 255, 255, .7);
  color: #0d3a5c;
  font-size: 16px;
  font-weight: 800;
  opacity: 1;
}

.inv-body[data-theme="hielo"] .inv-plate {
  box-sizing: border-box;
  justify-content: center;
  gap: clamp(3px, 1.2cqw, 10px);
  padding: clamp(6px, 4cqw, 26px) clamp(5px, 3cqw, 20px);
  /* Altura MINIMA, no fija: con una direccion larga (dos lineas) el contenido
     superaba la caja y overflow:hidden se comia "Quinta Normal" (se vio con
     la invitacion real de Samantha). La tarjeta crece con su contenido; el
     borde y el fondo la siguen, asi que nada queda cortado. */
  height: auto;
  min-height: var(--plate-h);
  overflow: hidden;
  border: 1px solid rgba(86, 174, 215, .34);
  border-radius: clamp(12px, 3cqw, 22px);
  background:
    radial-gradient(75% 42% at 50% 0%, rgba(255, 255, 255, .92), transparent 72%),
    linear-gradient(155deg, rgba(242, 252, 255, .94), rgba(192, 235, 251, .82));
  box-shadow: inset 0 0 22px rgba(255, 255, 255, .8), 0 8px 20px rgba(19, 112, 155, .16);
}

.inv-body[data-theme="hielo"] .inv-plate-snow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .55em;
  color: #1593c8;
  font-size: clamp(.82rem, 7cqw, 1.55rem);
  line-height: 1;
  text-shadow: 0 2px 10px rgba(80, 184, 229, .3);
}

.inv-body[data-theme="hielo"] .inv-plate-snow span:nth-child(2) {
  color: #b485d1;
  font-size: .72em;
}

.inv-body[data-theme="hielo"] .inv-plate-kicker {
  color: #126e9d;
  font-size: clamp(.64rem, 4.6cqw, 1.05rem);
  font-weight: 800;
  letter-spacing: .015em;
  text-shadow: none;
}

.inv-body[data-theme="hielo"] .inv-plate-name {
  margin: 0;
  color: #0879b1;
  /* 19cqw y sin cortes a mitad de palabra: a 22cqw un nombre de 8 letras
     ("Samantha") no cabia y overflow-wrap:anywhere lo partia en "Samanth-a".
     Los nombres compuestos siguen quebrando por el espacio. */
  font-size: clamp(1.5rem, 19cqw, 4.4rem);
  overflow-wrap: normal;
  word-break: normal;
  line-height: .91;
  text-shadow: 0 2px 0 rgba(255, 255, 255, .85), 0 5px 16px rgba(35, 151, 203, .24);
}

.inv-body[data-theme="hielo"] .inv-plate-details {
  display: grid;
  width: 100%;
  gap: clamp(3px, 1.25cqw, 8px);
}

.inv-body[data-theme="hielo"] .inv-plate-chip {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: .5em;
  min-width: 0;
  margin: 0;
  padding: .43em .72em;
  border: 1px solid rgba(48, 157, 204, .26);
  border-radius: .75em;
  background: rgba(255, 255, 255, .76);
  color: #0d4f73;
  font-size: clamp(.6rem, 4.45cqw, 1rem);
  font-weight: 800;
  line-height: 1.16;
  text-align: left;
  text-shadow: none;
}

.inv-body[data-theme="hielo"] .inv-plate-chip > span:first-child {
  flex: 0 0 auto;
  font-size: 1.1em;
}

.inv-body[data-theme="hielo"] .inv-plate-chip > span:last-child {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* Hielo: capa celeste anti-fondo-oscuro en los capítulos de personajes.
   El fondo #bfeeff ya lo pone la regla combinada de arriba (comparte con
   .inv-hero-stage); acá solo hace falta el stacking context para que el
   overlay ::after de abajo quede acotado a este bloque. */
.inv-body[data-theme="hielo"] .inv-playlist-stage {
  isolation: isolate;
}

.inv-body[data-theme="hielo"] .inv-playlist-stage::after {
  position: absolute;
  z-index: 1;
  inset: 0;
  pointer-events: none;
  content: "";
  background: linear-gradient(160deg, rgba(213, 247, 255, 0.38), rgba(73, 183, 229, 0.3));
  mix-blend-mode: screen;
}

.inv-body[data-theme="hielo"] .inv-playlist-media {
  z-index: 0;
  background: #bfeeff;
}

/* ==========================================================================
   BABY SHOWER — "la espera"
   ==========================================================================
   Las temáticas infantiles se apoyan en video: los personajes llegan, hablan,
   cruzan la meta. Las dos de baby shower no tienen —ni van a tener— ese
   material, y copiar el mismo formato con una foto fija habría dado una
   versión pobre de la misma idea.

   Así que el registro es otro a propósito. Un baby shower no es una fiesta más
   chica: es una espera. Una pieza a la que todavía le falta llegar. Todo lo de
   abajo persigue eso — una fotografía que respira, aire con algo flotando
   dentro, y un solo número que importa: cuántos días faltan.

   Nada de esto pide un asset nuevo; es la misma foto del kiosco. Y todo cuelga
   de [data-theme], así que las invitaciones infantiles no cambian un píxel.
   ========================================================================== */

/* ---------- 1. La fotografía respira ----------
   26 segundos de ida y otros 26 de vuelta. Es deliberadamente más lento que
   cualquier "Ken Burns" que se note: el invitado no debería ver que la imagen
   se mueve, solo que la pantalla no está muerta. */
@keyframes inv-baby-respira {
  from { transform: scale(1.06); }
  to   { transform: scale(1.14); }
}

.inv-body[data-theme="baby-nube"] .inv-hero-media,
.inv-body[data-theme="baby-safari"] .inv-hero-media,
.inv-body[data-theme="baby-rosas"] .inv-hero-media {
  animation: inv-baby-respira 26s ease-in-out infinite alternate;
}

/* ---------- 2. El aire tiene algo adentro ----------
   `.inv-sparks` ya existía en el HTML de todas las temáticas —doce <span> con
   `--i` de 1 a 12— pero no tenía una sola regla de CSS: se imprimía invisible.
   Acá recibe cuerpo, y solo en baby shower, para no mover unas invitaciones
   infantiles que ya están aprobadas.

   El `--i` escalona sin escribir doce reglas. Repartir la posición con una
   sola multiplicación deja una grilla regular que el ojo detecta al toque, así
   que los múltiplos de 3 y de 4 salen corridos y con otro tamaño. */
.inv-body[data-theme="baby-nube"] .inv-sparks,
.inv-body[data-theme="baby-safari"] .inv-sparks,
.inv-body[data-theme="baby-rosas"] .inv-sparks {
  position: absolute;
  z-index: 0;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.inv-body[data-theme="baby-nube"] .inv-spark,
.inv-body[data-theme="baby-safari"] .inv-spark,
.inv-body[data-theme="baby-rosas"] .inv-spark {
  position: absolute;
  bottom: -12%;
  left: calc(var(--i) * 8.1% - 3%);
  width: 7px;
  height: 7px;
  border-radius: 50%;
  opacity: 0;
  animation:
    inv-baby-sube calc(20s + var(--i) * 1.6s) linear infinite,
    inv-baby-vaiven calc(6s + var(--i) * .7s) ease-in-out infinite alternate;
  animation-delay: calc(var(--i) * -2.7s), calc(var(--i) * -.9s);
}

/* Rompe la grilla: sin esto los doce suben en columnas parejas. */
.inv-body[data-theme="baby-nube"] .inv-spark:nth-child(3n),
.inv-body[data-theme="baby-safari"] .inv-spark:nth-child(3n),
.inv-body[data-theme="baby-rosas"] .inv-spark:nth-child(3n) {
  left: calc(var(--i) * 8.1% + 4.5%);
  width: 4px;
  height: 4px;
}

.inv-body[data-theme="baby-nube"] .inv-spark:nth-child(4n),
.inv-body[data-theme="baby-safari"] .inv-spark:nth-child(4n),
.inv-body[data-theme="baby-rosas"] .inv-spark:nth-child(4n) {
  left: calc(var(--i) * 8.1% - 6.5%);
  width: 10px;
  height: 10px;
}

@keyframes inv-baby-sube {
  0%   { opacity: 0; transform: translateY(0); }
  12%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(-118vh); }
}

@keyframes inv-baby-vaiven {
  from { margin-left: -14px; }
  to   { margin-left: 14px; }
}

/* Nubes: luz fría, como polvo de estrellas sobre la cuna. */
.inv-body[data-theme="baby-nube"] .inv-spark {
  background: radial-gradient(circle at 40% 35%, #fff, #dce9f5 55%, rgba(143, 190, 221, 0) 78%);
  box-shadow: 0 0 12px 2px rgba(220, 233, 245, .55);
}

/* Safari: luciérnagas de tarde. Luz cálida y todavía más lentas. */
.inv-body[data-theme="baby-safari"] .inv-spark {
  background: radial-gradient(circle at 40% 35%, #fff8e6, #d9b36b 52%, rgba(217, 179, 107, 0) 78%);
  box-shadow: 0 0 14px 3px rgba(217, 179, 107, .42);
  animation-duration: calc(26s + var(--i) * 1.9s), calc(7.5s + var(--i) * .8s);
}

/* Rosas: pétalos que caen, no luz. Rosa polvo sobre crema, y el vaivén
   más ancho que en las otras dos porque un pétalo se mece al caer. */
.inv-body[data-theme="baby-rosas"] .inv-spark {
  background: radial-gradient(circle at 38% 32%, #fff6f8, #dfa2b6 54%, rgba(196, 112, 140, 0) 80%);
  box-shadow: 0 0 13px 2px rgba(223, 162, 182, .48);
  border-radius: 60% 40% 55% 45%;
  animation-duration: calc(24s + var(--i) * 1.7s), calc(8s + var(--i) * .9s);
}

/* ---------- 3. Los dos contadores ----------
   Al principio esto era una sola línea: "FALTAN 39 DÍAS PARA CONOCER A
   VALENTINA". Decía una mentira. En 39 días es el baby shower, no el
   nacimiento; cuántos faltan para conocerla no lo sabe nadie, y es
   exactamente de lo que trata la fiesta.

   Así que son dos, uno al lado del otro. El de la izquierda cuenta lo que sí
   se sabe y el invitado necesita. El de la derecha ocupa el mismo lugar donde
   iría un número y muestra "¿?", con el mismo tamaño y la misma tipografía:
   ahí el signo no es un adorno, es el dato. Ponerlo más chico o más tenue lo
   convertiría en una nota al pie, y es la mitad más importante del bloque. */
.inv-hero-countdown {
  position: relative;
  display: flex;
  max-width: 23rem;
  align-items: flex-start;
  justify-content: center;
  margin: 20px auto 0;
  padding-top: 16px;
}

.inv-hero-countdown::before {
  position: absolute;
  top: 0;
  left: 50%;
  width: 46px;
  height: 1px;
  transform: translateX(-50%);
  background: linear-gradient(90deg,
    transparent,
    color-mix(in srgb, var(--inv-highlight) 75%, transparent),
    transparent);
  content: "";
}

.inv-hero-count {
  display: flex;
  max-width: 10rem;
  flex: 1 1 0;
  flex-direction: column;
  align-items: center;
  margin: 0;
  padding: 0 clamp(9px, 3.4vw, 18px);
  gap: .12em;
}

/* El filete separa los dos contadores sin encerrarlos en cajas. Va en el
   segundo y no como elemento propio para que, si algún día uno de los dos no
   se muestra, no quede una línea suelta en el aire. */
.inv-hero-count + .inv-hero-count {
  border-left: 1px solid color-mix(in srgb, var(--inv-highlight) 32%, transparent);
}

.inv-hero-count-num {
  color: color-mix(in srgb, var(--inv-title, var(--inv-highlight)) 82%, white);
  font-size: clamp(2rem, 10.5vw, 3rem);
  font-weight: 300;
  line-height: 1;
  /* Las cifras tabulares evitan que el bloque se corra de ancho cuando el
     contador baja de 30 a 29 y el "3" es más ancho que el "2". */
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  text-shadow: 0 6px 26px rgba(0, 0, 0, .7), 0 1px 4px rgba(0, 0, 0, .85);
}

/* Los signos abren y cierran con el mismo peso liviano del número, pero el
   par "¿?" es más angosto que dos dígitos y se veía flotando: un poco de
   tracking lo devuelve al mismo ancho óptico. */
.inv-hero-count--incognita .inv-hero-count-num {
  letter-spacing: .04em;
}

.inv-hero-count-label {
  color: color-mix(in srgb, var(--inv-title, var(--inv-highlight)) 62%, white);
  font-size: clamp(.6rem, 2.6vw, .74rem);
  font-weight: 600;
  letter-spacing: .07em;
  line-height: 1.3;
  text-shadow: 0 2px 12px rgba(0, 0, 0, .8), 0 1px 3px rgba(0, 0, 0, .9);
  text-transform: uppercase;
  text-wrap: balance;
}

/* El día del baby shower el contador de la izquierda llega sin número
   ("¡El baby shower es hoy!"). Sin esto quedaría como una nota al pie del día
   más importante. */
.inv-hero-count-label:only-child {
  color: color-mix(in srgb, var(--inv-title, var(--inv-highlight)) 88%, white);
  font-size: clamp(.86rem, 4vw, 1.1rem);
  letter-spacing: .03em;
}

/* ---------- 4. La lámina, escrita dentro del marco ----------
   `fondo-sala.jpg` trae un marco decorativo REAL colgado en la pared, con el
   lienzo en blanco — el mismo que en la fiesta encuadra la foto del invitado.
   Las coordenadas salen del `frameBox` ya calibrado contra ese marco, así que
   los datos caen exactamente adentro.

   Por eso esta lámina NO pinta una tarjeta encima, como sí hace Hielo: taparía
   justo el marco que la hace bonita. Va tinta sobre el lienzo y nada más. */
.inv-body[data-theme="baby-nube"] .inv-plate,
.inv-body[data-theme="baby-safari"] .inv-plate,
.inv-body[data-theme="baby-rosas"] .inv-plate {
  box-sizing: border-box;
  justify-content: center;
  gap: clamp(2px, 1.1cqw, 9px);
  padding: clamp(4px, 3cqw, 20px) clamp(6px, 4cqw, 26px);
}

/* Filete + rombo, sin un solo glifo tipográfico. Un carácter decorativo suelto
   (❦, ✦, ◆) se ve como un cuadrado vacío en varias fuentes de Android — ya
   pasó con los iconos de las secciones del perfil. Con bordes no puede pasar. */
.inv-body[data-theme="baby-nube"] .inv-plate-orn,
.inv-body[data-theme="baby-safari"] .inv-plate-orn,
.inv-body[data-theme="baby-rosas"] .inv-plate-orn {
  display: flex;
  width: min(62%, 15em);
  align-items: center;
  justify-content: center;
  gap: .7em;
  margin-bottom: clamp(2px, 1.4cqw, 10px);
}

.inv-body[data-theme="baby-nube"] .inv-plate-orn span:nth-child(odd),
.inv-body[data-theme="baby-safari"] .inv-plate-orn span:nth-child(odd),
.inv-body[data-theme="baby-rosas"] .inv-plate-orn span:nth-child(odd) {
  height: 1px;
  flex: 1;
  background: currentcolor;
  opacity: .46;
}

.inv-body[data-theme="baby-nube"] .inv-plate-orn span:nth-child(2),
.inv-body[data-theme="baby-safari"] .inv-plate-orn span:nth-child(2),
.inv-body[data-theme="baby-rosas"] .inv-plate-orn span:nth-child(2) {
  width: clamp(4px, 1.5cqw, 9px);
  height: clamp(4px, 1.5cqw, 9px);
  flex: 0 0 auto;
  transform: rotate(45deg);
  background: currentcolor;
  opacity: .6;
}

.inv-body[data-theme="baby-nube"] .inv-plate-kicker,
.inv-body[data-theme="baby-safari"] .inv-plate-kicker,
.inv-body[data-theme="baby-rosas"] .inv-plate-kicker {
  color: color-mix(in srgb, var(--plate-tone) 78%, white);
  /* Mínimo .6rem, no .5: el marco ocupa ~59% del ancho de la foto, así que
     el cqw es chico y el clamp cae siempre al piso. Con .5rem el kicker
     medía 8px reales en un teléfono. */
  font-size: clamp(.6rem, 3.8cqw, .95rem);
  font-weight: 600;
  letter-spacing: .04em;
  text-shadow: none;
  text-transform: none;
}

.inv-body[data-theme="baby-nube"] .inv-plate-name,
.inv-body[data-theme="baby-safari"] .inv-plate-name,
.inv-body[data-theme="baby-rosas"] .inv-plate-name {
  margin: .04em 0 0;
  color: var(--plate-tone);
  font-size: clamp(1.6rem, 20cqw, 4.6rem);
  line-height: .94;
  /* El lienzo del marco es blanco liso: una sombra dura acá se ve sucia.
     Basta un realce mínimo para despegar la letra del papel. */
  text-shadow: 0 1px 0 rgba(255, 255, 255, .7);
}

.inv-body[data-theme="baby-nube"] .inv-plate-details,
.inv-body[data-theme="baby-safari"] .inv-plate-details,
.inv-body[data-theme="baby-rosas"] .inv-plate-details {
  display: grid;
  width: 100%;
  margin-top: clamp(3px, 1.6cqw, 12px);
  gap: clamp(2px, .9cqw, 6px);
  justify-items: center;
}

/* Sin píldora rellena: sobre un lienzo de papel, un chip de color se ve
   pegoteado. Renglón limpio, como escrito a mano en el paspartú. */
.inv-body[data-theme="baby-nube"] .inv-plate-chip,
.inv-body[data-theme="baby-safari"] .inv-plate-chip,
.inv-body[data-theme="baby-rosas"] .inv-plate-chip {
  display: flex;
  min-width: 0;
  align-items: baseline;
  justify-content: center;
  gap: .45em;
  margin: 0;
  color: color-mix(in srgb, var(--plate-tone) 88%, white);
  font-size: clamp(.6rem, 3.7cqw, .92rem);
  font-weight: 600;
  line-height: 1.25;
  text-align: center;
  text-shadow: none;
}

/* Los emoji de fecha/hora/lugar son los mismos que usa Hielo, y ahí funcionan
   porque la lámina es una tarjeta de color. Sobre un lienzo de papel se ven
   como calcomanías pegadas. En gris quedan como sellitos de tinta, que es lo
   que corresponde acá — y sigue siendo el mismo carácter, así que ningún
   Android se queda sin él. */
.inv-body[data-theme="baby-nube"] .inv-plate-chip > span:first-child,
.inv-body[data-theme="baby-safari"] .inv-plate-chip > span:first-child,
.inv-body[data-theme="baby-rosas"] .inv-plate-chip > span:first-child {
  flex: 0 0 auto;
  font-size: .9em;
  filter: grayscale(1);
  opacity: .62;
}

.inv-body[data-theme="baby-nube"] .inv-plate-chip > span:last-child,
.inv-body[data-theme="baby-safari"] .inv-plate-chip > span:last-child,
.inv-body[data-theme="baby-rosas"] .inv-plate-chip > span:last-child {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* La dirección es la línea larga: "Av. Los Leones 455, Providencia,
   Santiago" llenaba el renglón de lado a lado y tocaba la moldura del marco.
   Se le deja aire y se parte en dos líneas parejas en vez de una larga con
   una huérfana debajo. No se achica la letra: en un teléfono ya está al
   límite de lo legible. */
.inv-body[data-theme="baby-nube"] .inv-plate-chip--place,
.inv-body[data-theme="baby-safari"] .inv-plate-chip--place,
.inv-body[data-theme="baby-rosas"] .inv-plate-chip--place {
  max-width: 88%;
  margin-inline: auto;
}

.inv-body[data-theme="baby-nube"] .inv-plate-chip--place > span:last-child,
.inv-body[data-theme="baby-safari"] .inv-plate-chip--place > span:last-child,
.inv-body[data-theme="baby-rosas"] .inv-plate-chip--place > span:last-child {
  text-wrap: balance;
  /* La dirección chilena trae comas: cortar por palabra se lee natural,
     partir un nombre de calle por la mitad no. `anywhere` queda solo como
     último recurso si una sola palabra no cabe. */
  overflow-wrap: break-word;
}

/* La cuenta regresiva otra vez, ahora dentro del marco. Es la línea que la
   familia va a mirar cada vez que abra el enlace. */
.inv-body[data-theme="baby-nube"] .inv-plate-countdown,
.inv-body[data-theme="baby-safari"] .inv-plate-countdown,
.inv-body[data-theme="baby-rosas"] .inv-plate-countdown {
  margin: clamp(4px, 2cqw, 14px) 0 0;
  color: color-mix(in srgb, var(--plate-tone) 62%, white);
  /* Medía 6,7px. Sobraban 87px de aire arriba y abajo del bloque, así que
     el piso sube hasta donde se lee de verdad. */
  font-size: clamp(.56rem, 3.1cqw, .78rem);
  font-weight: 700;
  letter-spacing: .08em;
  line-height: 1.3;
  text-transform: uppercase;
}

/* ---------- 5. Sin movimiento ----------
   Todo lo de arriba es atmósfera: apagarlo no le quita información a nadie. */
@media (prefers-reduced-motion: reduce) {
  .inv-body[data-theme="baby-nube"] .inv-hero-media,
  .inv-body[data-theme="baby-safari"] .inv-hero-media,
  .inv-body[data-theme="baby-nube"] .inv-spark,
  .inv-body[data-theme="baby-safari"] .inv-spark {
    animation: none;
  }

  .inv-body[data-theme="baby-nube"] .inv-spark,
  .inv-body[data-theme="baby-safari"] .inv-spark {
    opacity: .5;
  }
}

/* ---------- 6. "Para cuando llegue" — la lista de regalos ----------
   El resto de la invitación es contemplativa: se mira. Esta sección es la
   única donde el invitado hace algo, así que tiene que verse accionable sin
   romper el tono — tarjetas de papel, no filas de tabla, y un solo botón por
   tarjeta.

   Lo tomado no desaparece ni se tacha: se atenúa y se va al final (el orden lo
   decide el servidor). Que siga visible es parte del punto — se ve que la
   lista está viva sin decir quién tomó qué. */
.inv-gifts {
  padding: 0 20px;
  margin: 0 auto;
  max-width: 34rem;
  text-align: center;
}

.inv-gifts-title {
  margin: 6px 0 0;
  color: #fff;
  font-size: clamp(1.3rem, 6vw, 1.8rem);
  font-weight: 800;
  line-height: 1.15;
  text-wrap: balance;
}

.inv-gifts-lede {
  margin: 10px 0 0;
  color: rgba(255, 255, 255, .74);
  font-size: .95rem;
  line-height: 1.45;
}

/* El pulso de la lista, sin nombres: cuántos ya tienen dueño. Es lo que
   reemplaza al "Ya lo lleva Camila" que se descartó. */
.inv-gifts-count {
  margin: 14px 0 0;
  color: color-mix(in srgb, var(--inv-highlight) 78%, white);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.inv-gifts-list {
  display: grid;
  margin: 18px 0 0;
  padding: 0;
  gap: 10px;
  list-style: none;
}

.inv-gift {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid color-mix(in srgb, var(--inv-highlight) 24%, transparent);
  border-radius: 16px;
  background: var(--inv-deep-raise);
  text-align: left;
  transition: opacity .25s ease, border-color .25s ease;
}

.inv-gift-texto {
  min-width: 0;
  flex: 1 1 auto;
}

.inv-gift-title {
  margin: 0;
  color: #fff;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

/* La condición de los papás. "Coche" no le sirve a nadie; "Coche, liviano, que
   quepa en el auto" sí — por eso la nota tiene el mismo peso visual que un
   subtítulo y no el de una letra chica escondida. */
.inv-gift-notes {
  margin: 4px 0 0;
  color: rgba(255, 255, 255, .62);
  font-size: .82rem;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.inv-gift-btn {
  flex: 0 0 auto;
  padding: 10px 16px;
  border: 0;
  border-radius: 999px;
  background: var(--inv-accent);
  color: #fff;
  font: inherit;
  font-size: .85rem;
  font-weight: 800;
  line-height: 1.1;
  cursor: pointer;
  transition: transform .16s ease, opacity .16s ease;
}

.inv-gift-btn:hover { transform: translateY(-1px); }
.inv-gift-btn:disabled,
.inv-gift-btn.is-esperando { opacity: .55; cursor: progress; transform: none; }

.inv-gift-btn--soltar {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, .3);
  color: rgba(255, 255, 255, .8);
  font-size: .76rem;
  font-weight: 700;
}

/* Tomado por otra persona. Nunca dice por quién. */
.inv-gift--tomado {
  border-color: rgba(255, 255, 255, .1);
  opacity: .5;
}

.inv-gift-estado {
  flex: 0 0 auto;
  color: rgba(255, 255, 255, .7);
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .03em;
}

/* Lo que tomó este navegador. Se despega del resto —vuelve a opacidad plena y
   toma el color del tema— porque para el invitado es lo único suyo en toda la
   página. */
.inv-gift--mio {
  border-color: color-mix(in srgb, var(--inv-highlight) 60%, transparent);
  opacity: 1;
}

.inv-gift--mio .inv-gift-texto { flex: 1 1 100%; }

.inv-gift-estado--mio {
  color: color-mix(in srgb, var(--inv-highlight) 85%, white);
}

.inv-gifts-status {
  margin: 14px 0 0;
  min-height: 1.2em;
  color: rgba(255, 255, 255, .78);
  font-size: .85rem;
}

.inv-gifts-status.is-error {
  color: color-mix(in srgb, var(--inv-highlight) 70%, #ffd9d9);
}

.inv-gifts-otro {
  margin: 6px auto 0;
  padding: 11px 20px;
  border: 1px solid rgba(255, 255, 255, .26);
  border-radius: 999px;
  background: transparent;
  color: rgba(255, 255, 255, .88);
  font: inherit;
  font-size: .86rem;
  font-weight: 700;
  cursor: pointer;
}

/* El costo de no tener cuentas, dicho en pantalla. Esconderlo sería peor: el
   invitado que borra sus datos descubriría solo que ya no puede soltar nada. */
.inv-gifts-nota {
  margin: 14px auto 0;
  max-width: 26rem;
  color: rgba(255, 255, 255, .45);
  font-size: .72rem;
  line-height: 1.45;
}

@media (min-width: 560px) {
  .inv-gift { padding: 16px 20px; }
  .inv-gift--mio { flex-wrap: nowrap; }
}

/* Nombre e ítem nuevo, en línea. Los dos nacen ocultos y los muestra el JS:
   sin JS no hay reserva posible, así que un campo que no hace nada sería
   peor que su ausencia. */
.inv-gifts-yo {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 8px 10px;
  margin: 18px 0 4px;
}

.inv-gifts-yo label {
  color: rgba(255, 255, 255, .7);
  font-size: .84rem;
  font-weight: 600;
}

.inv-gifts-yo input,
.inv-gifts-nuevo input {
  min-width: 0;
  flex: 0 1 12rem;
  padding: 9px 14px;
  border: 1px solid rgba(255, 255, 255, .26);
  border-radius: 999px;
  background: rgba(0, 0, 0, .22);
  color: #fff;
  font: inherit;
  font-size: .9rem;
}

.inv-gifts-yo input::placeholder,
.inv-gifts-nuevo input::placeholder { color: rgba(255, 255, 255, .38); }

.inv-gifts-yo input:focus-visible,
.inv-gifts-nuevo input:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--inv-highlight) 70%, transparent);
  outline-offset: 2px;
}

.inv-gifts-yo-nota {
  flex: 1 0 100%;
  color: rgba(255, 255, 255, .42);
  font-size: .72rem;
}

.inv-gifts-nuevo {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin: 10px 0 0;
}

/* Modo abierto con la lista todavía vacía. No es un error ni un estado a
   esconder: al principio siempre está vacía, y el aviso es justamente la
   invitación a anotar. */
.inv-gifts-vacio {
  margin: 18px auto 0;
  max-width: 24rem;
  padding: 16px 18px;
  border: 1px dashed color-mix(in srgb, var(--inv-highlight) 32%, transparent);
  border-radius: 16px;
  color: rgba(255, 255, 255, .62);
  font-size: .9rem;
  line-height: 1.4;
}

/* Aviso "sigue bajando" tras 5 s quieto en la lámina (todas las temáticas):
   acompaña al auto-scroll para que el invitado sepa qué viene más abajo. */
.inv-descubre {
  position: fixed;
  z-index: 40;
  left: 50%;
  bottom: max(18px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  max-width: min(92%, 30rem);
  padding: 10px 18px;
  border-radius: 999px;
  background: rgba(10, 18, 40, 0.88);
  color: #fff;
  font-size: 0.92rem;
  line-height: 1.25;
  text-align: center;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(8px);
  opacity: 1;
  transition: opacity 500ms ease;
}
.inv-descubre--fuera {
  opacity: 0;
}

/* ── Confirmar asistencia ─────────────────────────────────────────────── */
.inv-rsvp {
  text-align: center;
  padding: 28px 20px 8px;
}
.inv-rsvp-open {
  margin-top: 6px;
}
.inv-rsvp-dialog {
  width: min(92vw, 26rem);
  border: none;
  border-radius: 20px;
  padding: 24px 22px;
  background: #101a3d;
  color: #fff;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.5);
}
.inv-rsvp-dialog::backdrop {
  background: rgba(4, 8, 24, 0.72);
  backdrop-filter: blur(4px);
}
.inv-rsvp-title {
  margin: 0 0 14px;
  font-size: 1.25rem;
  text-align: center;
}
.inv-rsvp-campo {
  display: block;
  margin-bottom: 14px;
  text-align: left;
}
.inv-rsvp-campo span {
  display: block;
  margin-bottom: 5px;
  font-size: 0.88rem;
  color: #b9c7f5;
}
.inv-rsvp-campo input {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 14px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font: inherit;
}
.inv-rsvp-campo input:focus {
  outline: 2px solid rgba(255, 255, 255, 0.5);
}
.inv-rsvp-error {
  color: #ff9d9d;
  font-size: 0.88rem;
}
.inv-rsvp-acciones {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 6px;
}
.inv-rsvp-ok {
  text-align: center;
}
.inv-rsvp-ok-emoji {
  font-size: 2.4rem;
  margin: 0;
}

/* Fundido entre capítulos del recorrido: el video y su gorrito bajan la luz
   un instante al cambiar de personaje (pedido de la clienta: transiciones
   menos bruscas). Con prefers-reduced-motion el JS ni aplica la clase. */
[data-playlist-video],
[data-playlist-caption] {
  transition: opacity 260ms ease;
}
.inv-clip-fundido {
  opacity: 0 !important;
}
