/* ═══════════════════════════════════════════════════════════════════
   INVITACIONES · base común
   ═══════════════════════════════════════════════════════════════════
   Aquí vive TODO lo que no cambia entre una plantilla y otra: la
   estructura, el ritmo tipográfico, el comportamiento de cada
   componente, las animaciones y la accesibilidad.

   Una plantilla es una hoja de `temas/` que solo define variables.
   Este archivo no contiene un solo color literal: si aparece uno,
   está en el lugar equivocado y el tema no podrá cambiarlo.

   El pago de esto: cuando se mejora la confirmación de asistencia,
   mejoran las ocho plantillas a la vez. Con ocho copias, se arregla
   ocho veces y a la tercera ya se desincronizaron.

   Contrato que todo tema debe cumplir — ver temas/nocturna.css:
     --fondo · --fondo-alto        superficies
     --acento-claro/--acento/--acento-hondo · --degradado
     --texto · --tenue · --linea
     --serif · --sans
     --halo · --ornamento · --grano-opacidad · --grano-mezcla
     --marco-interno · --boton-texto · --sombra-boton
   ═══════════════════════════════════════════════════════════════════ */

:root {
  /* Curvas propias. Las de fábrica de CSS son demasiado blandas: les
     falta el impulso que hace que un movimiento se sienta decidido.
     Estas SÍ son comunes: el carácter del movimiento es de la casa,
     no de la plantilla. */
  --salida:  cubic-bezier(0.23, 1, 0.32, 1);
  --ambas:   cubic-bezier(0.77, 0, 0.175, 1);

  --ancho: 640px;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--fondo);
  color: var(--texto);
  font-family: var(--serif);
  font-size: 18px;
  line-height: 1.7;
  overflow-x: hidden;
  /* Lo decide el tema, no la base. `antialiased` adelgaza el trazo: es
     lo correcto para texto claro sobre fondo oscuro, donde si no se ve
     gordo, y es EXACTAMENTE lo contrario de lo que quiere un tema claro,
     donde adelgaza un texto que ya es fino y lo vuelve gris. */
  -webkit-font-smoothing: var(--suavizado, auto);
  text-rendering: optimizeLegibility;
}

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

/* Grano. Sin esto, un fondo oscuro plano se ve como una pantalla
   apagada; con esto se ve como papel impreso. Es un SVG en línea:
   ningún archivo que descargar y no se puede tocar con el dedo. */
.grano {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9;
  opacity: var(--grano-opacidad);
  mix-blend-mode: var(--grano-mezcla);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ─────────── Tipografía ───────────
   El tracking es específico del tamaño, nunca uno solo para todo.
   El texto grande se abre visualmente al crecer, así que se cierra
   con tracking negativo; el texto diminuto se cierra, así que se
   abre. Un solo valor está mal en alguna parte. */

.etiqueta {
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: 0.42em;   /* muy abierto: es lo que hace que un
                               renglón de 6 letras se lea como sello */
  text-transform: uppercase;
  color: var(--acento);
  margin: 0 0 1.4rem;
  opacity: 0.85;
  /* El tracking añade aire a la derecha; se compensa para que el
     texto quede ópticamente centrado y no un pelo a la izquierda. */
  text-indent: 0.42em;
}

.titulo {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(2.1rem, 8vw, 3rem);
  line-height: 1.08;         /* interlineado corto: el texto grande
                                necesita menos aire, no más */
  letter-spacing: -0.015em;
  margin: 0 0 1.6rem;
}

.titulo--chico { font-size: clamp(1.85rem, 6.5vw, 2.4rem); }

/* ─────────── Apertura ─────────── */

.apertura {
  position: relative;
  min-height: 100svh;        /* svh y no vh: en móvil, vh cuenta la
                                barra del navegador y corta el contenido */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 4rem 1.75rem 6rem;
  overflow: hidden;
}

/* Luz cálida que cae desde arriba. Da profundidad sin ninguna imagen. */
.apertura__halo {
  position: absolute;
  top: -35%;
  left: 50%;
  width: min(120vw, 900px);
  aspect-ratio: 1;
  transform: translateX(-50%);
  background: var(--halo);
  pointer-events: none;
}

.apertura__contenido { position: relative; max-width: var(--ancho); }

.monograma { width: 92px; margin: 0 auto 2.4rem; }
.monograma svg { width: 100%; height: auto; overflow: visible; }

.monograma__letras {
  font-family: var(--serif);
  font-size: 34px;
  font-weight: 300;
  letter-spacing: 0.04em;
  text-anchor: middle;
  dominant-baseline: central;
}

.monograma__amp {
  font-size: 24px;
  font-style: italic;
  opacity: 0.7;
}

/* El anillo se dibuja solo, como si lo trazaran a mano. Es el primer
   gesto de la página y da el tono de todo lo que viene después. */
.js .monograma__anillo {
  stroke-dasharray: 350;
  stroke-dashoffset: 350;
  animation: trazar 2.2s var(--ambas) forwards;
  animation-delay: calc(var(--paso, 2) * 130ms + 200ms);
}

.js .monograma__anillo--interno { animation-duration: 2.6s; }

@keyframes trazar { to { stroke-dashoffset: 0; } }

.nombres {
  font-weight: 300;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Los nombres son el objeto más grande de toda la invitación.
     A este tamaño el tracking negativo es obligatorio. */
  font-size: clamp(3.4rem, 17vw, 6.2rem);
  line-height: 0.94;
  letter-spacing: -0.03em;
}

/* Un solo protagonista ocupa una línea en vez de tres. Al mismo tamaño
   la portada se queda hueca, así que el nombre crece y se cierra más el
   tracking: a mayor tamaño, más negativo. */
.nombres--solo {
  font-size: clamp(4rem, 21vw, 7.4rem);
  letter-spacing: -0.04em;
}

.nombres__amp {
  font-size: 0.36em;
  font-style: italic;
  font-weight: 300;
  letter-spacing: 0;
  margin: 0.22em 0;
  background: var(--degradado);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Filete ornamental: dos líneas y un rombo. Dibujado, no imagen. */
.filete {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  margin: 2.2rem auto;
  max-width: 260px;
  color: var(--acento);
}

.filete span {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--linea), transparent);
}

/* El ornamento se pinta como MÁSCARA sobre el color del acento: así
   hereda el color del tema y cada plantilla trae su propia figura
   —rombo, hoja, flor— sin tocar una línea del HTML compartido. */
.filete i {
  width: 13px;
  height: 13px;
  flex: none;
  opacity: 0.85;
  background: currentColor;
  -webkit-mask: var(--ornamento) center / contain no-repeat;
  mask: var(--ornamento) center / contain no-repeat;
}

.apertura__fecha {
  font-family: var(--sans);
  font-size: 0.9rem;
  font-weight: 300;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin: 0;
  color: var(--texto);
}

.apertura__fecha span {
  display: block;
  margin-top: 0.7rem;
  font-size: 0.76rem;
  letter-spacing: 0.2em;
  color: var(--tenue);
}

.apertura__para {
  margin: 2.4rem auto 0;
  padding-top: 1.6rem;
  border-top: 1px solid var(--linea);
  max-width: 340px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 1.15rem;
  color: var(--acento-claro);
}

/* La invitación a bajar. Una línea que se desliza: dice "hay más"
   sin escribirlo. */
.apertura__baja {
  position: absolute;
  bottom: 2.6rem;
  left: 50%;
  transform: translateX(-50%);
  width: 1px;
  height: 54px;
  overflow: hidden;
  background: color-mix(in srgb, var(--acento) 14%, transparent);
}

.apertura__baja span {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent, var(--acento));
  animation: bajar 2.6s ease-in-out infinite;
}

@keyframes bajar {
  0%       { transform: translateY(-100%); }
  60%,100% { transform: translateY(100%); }
}

/* ─────────── Secciones ─────────── */

main > section {
  max-width: var(--ancho);
  margin: 0 auto;
  padding: 5.5rem 1.75rem;
  text-align: center;
}

.frase p {
  font-size: clamp(1.25rem, 5vw, 1.6rem);
  font-weight: 300;
  line-height: 1.55;
  color: var(--texto);
  margin: 0;
}

.frase em { font-style: italic; }

.frase__cita {
  display: block;
  margin-top: 1.5rem;
  font-family: var(--sans);
  font-size: 0.7rem;
  font-style: normal;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--acento);
  opacity: 0.8;
}

/* ─────────── Cuenta regresiva ─────────── */

.cuenta__reloj {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.5rem;
  max-width: 420px;
  margin: 0 auto;
}

.cuenta__casilla {
  padding: 1.1rem 0.3rem;
  border: 1px solid var(--linea);
  border-radius: 2px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--acento) 5%, transparent), transparent);
}

.cuenta__casilla b {
  display: block;
  font-family: var(--serif);
  font-size: clamp(1.9rem, 8vw, 2.5rem);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.02em;
  /* Cifras de ancho fijo: sin esto la casilla se ensancha y se
     encoge cada segundo, y el reloj entero tiembla. */
  font-variant-numeric: tabular-nums;
  background: var(--degradado);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.cuenta__casilla span {
  display: block;
  margin-top: 0.55rem;
  font-family: var(--sans);
  font-size: 0.6rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--tenue);
}

/* ─────────── Retrato ─────────── */

.retrato { padding-top: 2rem; }

.retrato__marco {
  margin: 0;
  position: relative;
  border-radius: 2px;
  overflow: hidden;
}

/* Marco de oro por dentro del borde: el detalle que separa "una foto
   pegada" de "una foto enmarcada". */
.retrato__marco::after {
  content: "";
  position: absolute;
  inset: 10px;
  border: 1px solid color-mix(in srgb, var(--acento-claro) 28%, transparent);
  pointer-events: none;
}

.retrato__marco img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

.retrato__pie {
  margin: 1.8rem 0 0;
  font-style: italic;
  font-size: 1.1rem;
  color: var(--tenue);
}

/* ─────────── Lugares ─────────── */

.lugares__par {
  display: grid;
  gap: 1rem;
  margin-top: 2.5rem;
}

@media (min-width: 720px) {
  .lugares__par { grid-template-columns: 1fr 1fr; }
}

.tarjeta {
  padding: 2.4rem 1.6rem;
  border: 1px solid var(--linea);
  border-radius: 2px;
  background: linear-gradient(180deg, var(--fondo-alto), var(--fondo));
}

.tarjeta__icono {
  width: 42px;
  height: 42px;
  margin: 0 auto 1.4rem;
  color: var(--acento);
}

.tarjeta__icono svg { width: 100%; height: 100%; }

.tarjeta h3 {
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--tenue);
  margin: 0 0 1rem;
  text-indent: 0.34em;
}

.tarjeta__hora {
  font-size: clamp(2rem, 9vw, 2.6rem);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.02em;
  margin: 0 0 1.1rem;
  background: var(--degradado);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.tarjeta__lugar {
  margin: 0 0 1.8rem;
  font-size: 1.15rem;
  line-height: 1.45;
}

.tarjeta__lugar span {
  display: block;
  margin-top: 0.45rem;
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 300;
  line-height: 1.5;
  color: var(--tenue);
}

/* ─────────── Botones ─────────── */

.boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  min-height: 48px;
  padding: 0.85rem 1.7rem;
  border-radius: 2px;
  border: 1px solid transparent;
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  /* Se nombran las propiedades exactas. `all` anima también cosas que
     no queremos y cuesta cuadros. */
  transition: transform 160ms var(--salida),
              border-color 220ms ease,
              box-shadow 220ms ease,
              background-color 220ms ease;
}

/* La respuesta va en el momento de presionar, no al soltar. Es la
   diferencia entre una interfaz que escucha y una que está muerta. */
.boton:active { transform: scale(0.97); }

.boton svg { width: 16px; height: 16px; flex: none; }

.boton--linea {
  background: transparent;
  border-color: var(--linea);
  color: var(--acento-claro);
}

.boton--oro {
  position: relative;
  overflow: hidden;
  border: none;
  background: var(--degradado);
  color: var(--boton-texto);
  font-weight: 500;
  width: 100%;
  box-shadow: 0 10px 30px -14px color-mix(in srgb, var(--acento) 55%, transparent);
}

.boton--oro .boton__texto { position: relative; z-index: 1; }

/* Barrido de luz al pasar el dedo o el cursor. Solo en dispositivos
   con puntero de verdad: en una pantalla táctil el "hover" se dispara
   al tocar y el efecto se vuelve ruido. */
@media (hover: hover) and (pointer: fine) {
  .boton--linea:hover {
    border-color: color-mix(in srgb, var(--acento) 55%, transparent);
    background: color-mix(in srgb, var(--acento) 6%, transparent);
  }
  .boton--oro::after {
    content: "";
    position: absolute;
    top: 0;
    left: -55%;
    width: 38%;
    height: 100%;
    background: linear-gradient(100deg, transparent, var(--brillo), transparent);
    transform: skewX(-18deg);
    transition: left 620ms var(--salida);
  }
  .boton--oro:hover::after { left: 130%; }
  .boton--oro:hover { transform: translateY(-1px); }
}

.enlace {
  background: none;
  border: none;
  padding: 0.5rem;
  font-family: var(--sans);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tenue);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* ─────────── Vestimenta ─────────── */

.vestimenta__nota { color: var(--tenue); margin: 0 0 2rem; }

.paleta { display: flex; justify-content: center; gap: 0.6rem; }

.paleta span {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--c);
  border: 1px solid color-mix(in srgb, var(--acento-claro) 16%, transparent);
}

/* ─────────── Confirmación ─────────── */

.rsvp__caja {
  padding: 2.8rem 1.6rem;
  border: 1px solid var(--linea);
  border-radius: 2px;
  background: linear-gradient(180deg, var(--fondo-alto), var(--fondo));
  /* Contiene los dos estados (formulario y gracias) sin que la altura
     salte al cambiar de uno a otro. */
  position: relative;
}

.rsvp__plazo { color: var(--tenue); font-size: 0.98rem; margin: 0 0 2.2rem; }
.rsvp__plazo b { color: var(--acento-claro); font-weight: 500; }

.rsvp__forma { display: grid; gap: 1.4rem; text-align: left; }

.campo { display: grid; gap: 0.6rem; }

.campo > span {
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--tenue);
}

.campo > span em { font-style: normal; text-transform: none; letter-spacing: 0.06em; opacity: 0.7; }

.campo input[type="text"],
.campo textarea {
  width: 100%;
  padding: 0.95rem 1rem;
  background: var(--campo-fondo);
  border: 1px solid var(--linea);
  border-radius: 2px;
  color: var(--texto);
  font-family: var(--serif);
  font-size: 1.08rem;
  line-height: 1.5;
  transition: border-color 200ms ease, background-color 200ms ease;
  resize: vertical;
}

.campo input::placeholder,
.campo textarea::placeholder { color: color-mix(in srgb, var(--tenue) 55%, transparent); }

.campo input:focus,
.campo textarea:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--acento) 60%, transparent);
  background: var(--campo-fondo-activo);
}

.campo input[aria-invalid="true"] { border-color: var(--error); }

.opciones {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.7rem;
  border: none;
  margin: 0;
  padding: 0;
}

/* La selección se DESLIZA entre las dos opciones en vez de encenderse
   en una y apagarse en la otra. Es la diferencia entre "hay dos cosas y
   una está activa" y "hay una elección y la moví": el ojo sigue el
   movimiento y entiende que son excluyentes sin leer nada.
   Se mueve con transform, que no toca el diseño y va por GPU. */
.opciones::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: calc((100% - 0.7rem) / 2);
  height: 100%;
  border: 1px solid color-mix(in srgb, var(--acento) 65%, transparent);
  border-radius: 2px;
  background: color-mix(in srgb, var(--acento) 10%, transparent);
  pointer-events: none;
  transition: transform 280ms var(--salida);
}

/* :has() lo resuelve sin una línea de JavaScript. Si el navegador no lo
   soporta, la píldora simplemente no se mueve y el color del texto sigue
   marcando la opción elegida: se pierde el gesto, nunca la información. */
.opciones:has(.opcion:last-of-type input:checked)::before {
  transform: translateX(calc(100% + 0.7rem));
}

.opcion { position: relative; }

.opcion input {
  position: absolute;
  opacity: 0;
  width: 100%; height: 100%;
  margin: 0;
  cursor: pointer;
}

.opcion span {
  position: relative;   /* por encima de la píldora que se desliza */
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  padding: 0.8rem 0.6rem;
  border: 1px solid var(--linea);
  border-radius: 2px;
  font-family: var(--sans);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  color: var(--tenue);
  text-align: center;
  transition: color 220ms ease, border-color 220ms ease, transform 160ms var(--salida);
}

.opcion input:active + span { transform: scale(0.97); }

/* El borde y el fondo los pone la píldora; aquí solo cambia el color del
   texto. Si los pusiera también el span, se verían dos marcos: el que se
   desliza y el que se enciende. */
.opcion input:checked + span {
  border-color: transparent;
  color: var(--acento-claro);
}

.opcion input:focus-visible + span { outline: 2px solid var(--acento); outline-offset: 2px; }

/* El contador de personas desaparece si dicen que no vienen: preguntar
   "¿cuántos?" a quien acaba de decir que no puede ir es descuidado. */
/* Plegado con `grid-template-rows: 1fr → 0fr`, no con `max-height`.
   El max-height obliga a adivinar una altura máxima, y la anterior —140px—
   era exactamente eso: una adivinanza. En un teléfono angosto, con la
   etiqueta partida en dos renglones, el contenido se habría recortado sin
   que nada avisara. Con `fr` el navegador mide el contenido real y no hay
   número que se pueda quedar corto. */
.campo--personas {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 320ms var(--salida), opacity 240ms ease;
}

.campo--personas > * { overflow: hidden; }

.campo--personas[data-oculto="si"] {
  grid-template-rows: 0fr;
  opacity: 0;
  pointer-events: none;
}

/* Si el navegador no sabe animar unidades `fr`, el campo aparece y
   desaparece de golpe. Es un salto, no un contenido perdido. */
@supports not (grid-template-rows: 1fr) {
  .campo--personas[data-oculto="si"] { display: none; }
}

.contador {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border: 1px solid var(--linea);
  border-radius: 2px;
  padding: 0.35rem;
  width: fit-content;
}

.contador__btn {
  width: 46px;
  height: 46px;
  border: none;
  background: var(--campo-fondo);
  color: var(--acento-claro);
  font-family: var(--sans);
  font-size: 1.3rem;
  line-height: 1;
  border-radius: 2px;
  cursor: pointer;
  transition: transform 140ms var(--salida), background-color 200ms ease;
}

.contador__btn:active { transform: scale(0.93); }
.contador__btn:disabled { opacity: 0.3; cursor: default; }

@media (hover: hover) and (pointer: fine) {
  .contador__btn:not(:disabled):hover { background: color-mix(in srgb, var(--acento) 14%, transparent); }
}

.contador output {
  min-width: 56px;
  text-align: center;
  font-family: var(--serif);
  font-size: 1.6rem;
  font-variant-numeric: tabular-nums;
  color: var(--texto);
}

/* El error entra rápido —160ms— porque es información que la persona
   necesita YA. Un error que se toma su tiempo en aparecer se lee como que
   el formulario no reaccionó. Baja apenas 4px: suficiente para que el ojo
   registre que algo llegó, no tanto como para hacerlo esperar. */
.rsvp__error {
  margin: 0;
  font-family: var(--sans);
  font-size: 0.8rem;
  color: var(--error-texto);
  opacity: 1;
  transform: none;
  transition: opacity 160ms var(--salida), transform 160ms var(--salida);
}

.rsvp__error[hidden] { display: none; }

@starting-style {
  .rsvp__error { opacity: 0; transform: translateY(-4px); }
}

/* Estado de gracias. Se cruza con el formulario mediante opacidad y
   un desenfoque leve: sin el desenfoque se ven dos objetos distintos
   superponiéndose; con él, el ojo lee una sola transformación. */
.rsvp__forma,
.rsvp__listo {
  transition: opacity 320ms ease, filter 320ms ease, transform 380ms var(--salida);
}

.rsvp__forma[data-saliendo="si"] {
  opacity: 0;
  filter: blur(6px);
  transform: scale(0.985);
  pointer-events: none;
}

.rsvp__listo[hidden] { display: none; }

.rsvp__listo {
  opacity: 0;
  filter: blur(6px);
  transform: scale(0.985);
}

.rsvp__listo[data-entrando="si"] {
  opacity: 1;
  filter: blur(0);
  transform: none;
}

.rsvp__sello { width: 62px; margin: 0 auto 1.6rem; color: var(--acento); }
.rsvp__sello svg { width: 100%; height: auto; }

/* La palomita se dibuja: es el remate del gesto de confirmar. */
.rsvp__palomita {
  stroke-dasharray: 34;
  stroke-dashoffset: 34;
}

.rsvp__listo[data-entrando="si"] .rsvp__palomita {
  animation: trazar 520ms var(--salida) 180ms forwards;
}

.rsvp__listo h3 {
  font-family: var(--serif);
  font-weight: 300;
  font-size: 1.9rem;
  letter-spacing: -0.01em;
  margin: 0 0 0.7rem;
}

.rsvp__listo p { color: var(--tenue); margin: 0 0 1.6rem; }

/* Escalonado del agradecimiento: primero el sello, después el nombre,
   después el texto. Llegando todo junto se lee como un bloque que
   apareció; en cascada se lee como una respuesta que se está dando.
   Este es de los pocos momentos con presupuesto para deleite: pasa una
   sola vez, y justo cuando la persona acaba de decir que sí. */
.rsvp__listo[data-entrando="si"] > * {
  animation: subir 620ms var(--salida) backwards;
}

.rsvp__listo[data-entrando="si"] > :nth-child(1) { animation-delay: 60ms; }
.rsvp__listo[data-entrando="si"] > :nth-child(2) { animation-delay: 200ms; }
.rsvp__listo[data-entrando="si"] > :nth-child(3) { animation-delay: 270ms; }
.rsvp__listo[data-entrando="si"] > :nth-child(4) { animation-delay: 360ms; }

/* ─────────── Regalos ─────────── */

.regalos__nota { color: var(--tenue); margin: 0 0 2.2rem; }

.regalos__cuenta {
  padding: 2rem 1.4rem;
  border: 1px solid var(--linea);
  border-radius: 2px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--acento) 5%, transparent), transparent);
}

.regalos__banco {
  font-family: var(--sans);
  font-size: 0.68rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--tenue);
  margin: 0 0 0.9rem;
}

/* "Copiar número" → "Copiado". El desenfoque durante el cruce es el mismo
   recurso de la confirmación, y no es adorno: sin él se ven dos palabras
   distintas superpuestas: con él, el ojo lee una sola que se transformó.
   Que las dos transiciones de la página compartan el gesto es lo que hace
   que se sienta una pieza y no partes pegadas. */
.boton[data-copiando] .boton__etiqueta {
  display: inline-block;
  transition: opacity 180ms ease, filter 180ms ease;
}

.boton[data-copiando="si"] .boton__etiqueta {
  opacity: 0;
  filter: blur(4px);
}

.regalos__numero {
  font-size: clamp(1.5rem, 7vw, 1.9rem);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  margin: 0 0 1.6rem;
  background: var(--degradado);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ─────────── Fotos / QR ─────────── */

.fotos__nota { color: var(--tenue); margin: 0 0 2.2rem; }

/* Reemplazó al recuadro del QR. `--oro` viene a ancho completo porque en el
   RSVP ocupa la caja entera; aquí es un remate, no la acción principal de la
   página, así que se le pone techo. */
.fotos__ir {
  width: min(320px, 100%);
  margin: 0 auto 1.4rem;
}

.fotos__pie {
  font-family: var(--sans);
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  color: var(--tenue);
  margin: 0;
}

/* ─────────── Cierre ─────────── */

.cierre {
  text-align: center;
  padding: 4.5rem 1.75rem calc(6.5rem + env(safe-area-inset-bottom));
  border-top: 1px solid color-mix(in srgb, var(--acento) 10%, transparent);
}

.cierre__nombres {
  font-size: 2rem;
  font-weight: 300;
  letter-spacing: -0.02em;
  margin: 0 0 0.4rem;
  background: var(--degradado);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.cierre__fecha {
  font-family: var(--sans);
  font-size: 0.74rem;
  letter-spacing: 0.4em;
  color: var(--tenue);
  margin: 0 0 2.6rem;
  text-indent: 0.4em;
}

.cierre__credito {
  font-family: var(--sans);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  color: color-mix(in srgb, var(--tenue) 50%, transparent);
  margin: 0;
}

/* El relleno agranda el área que se puede tocar sin agrandar el texto:
   visualmente sigue siendo un crédito discreto, pero el dedo lo acierta.
   Un enlace de 16px de alto se falla una de cada tres veces. */
.cierre__credito a {
  display: inline-block;
  padding: 0.85rem 0.6rem;
  margin: -0.85rem -0.6rem;   /* se recupera el espacio para que el
                                 relleno no mueva el diseño */
  color: inherit;
  text-decoration: none;
}

.cierre__credito a > span {
  border-bottom: 1px solid color-mix(in srgb, var(--tenue) 30%, transparent);
  padding-bottom: 2px;
}

/* ─────────── Barra flotante ─────────── */

.barra {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 20;
  padding: 0.85rem 1.25rem calc(0.85rem + env(safe-area-inset-bottom));
  /* Material translúcido: el contenido pasa por debajo en vez de
     chocar contra una franja opaca. */
  background: var(--barra-fondo);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  /* Filo claro arriba: es la luz agarrándose del canto del material */
  border-top: 1px solid color-mix(in srgb, var(--acento-claro) 10%, transparent);
  transform: translateY(100%);
  transition: transform 420ms var(--salida);
}

.barra[hidden] { display: block; visibility: hidden; }
.barra[data-visible="si"] { transform: translateY(0); visibility: visible; }

/* La barra centra a su hijo. `margin: 0 auto` sobre el botón no bastaba:
   el botón es inline-flex y los márgenes automáticos no centran cajas
   en línea — en pantalla ancha quedaba pegado a la izquierda. */
.barra { display: flex; justify-content: center; }

.boton--barra { width: 100%; max-width: 460px; }

/* Si el navegador no sabe desenfocar, el panel se vuelve sólido: un
   fondo semitransparente sin desenfoque deja el texto ilegible. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .barra { background: var(--fondo); }
}

/* ═══════════════ APARICIONES ═══════════════
   El estado oculto vive bajo `html.js`. Sin JavaScript la invitación
   se ve completa: una animación de entrada jamás debe ser lo que
   decide si el contenido existe. */

.js .entra {
  opacity: 0;
  transform: translateY(14px);
  animation: subir 1.1s var(--salida) forwards;
  /* Escalonado. La apertura se permite ser lenta —se ve una sola vez
     y es el momento ceremonial— pero cada paso entra en cascada, no
     todo de golpe. */
  animation-delay: calc(var(--paso, 0) * 130ms + 150ms);
}

@keyframes subir {
  to { opacity: 1; transform: none; }
}

.js .aparece {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 900ms var(--salida), transform 900ms var(--salida);
  transition-delay: calc(var(--paso, 0) * 110ms);
}

.js .aparece[data-visto="si"] { opacity: 1; transform: none; }

/* La foto se revela de abajo hacia arriba, como si se descubriera.
   Nada en el mundo real aparece de la nada.

   OJO AL DETALLE, costó encontrarlo: el recorte va sobre la IMAGEN,
   nunca sobre el elemento que observa el navegador. Un elemento con
   `inset(0 0 100% 0)` tiene área visible cero, y el observador que
   debía revelarlo mide exactamente eso: nunca lo veía entrar en
   pantalla y la foto se quedaba invisible para siempre. Se escondía
   tan bien que se escondía de su propio interruptor. */
.js .revela > img {
  clip-path: inset(0 0 100% 0);
  transform: scale(1.06);
  transition: clip-path 1.3s var(--ambas), transform 1.5s var(--salida);
}

.js .revela[data-visto="si"] > img {
  clip-path: inset(0 0 0 0);
  transform: scale(1);
}

/* ═══════════════ ACCESIBILIDAD ═══════════════ */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  /* Movimiento reducido no es "sin retroalimentación": es la versión
     suave. Se quitan los desplazamientos y el trazado; se conservan
     las opacidades, que ayudan a entender qué cambió. */
  .js .entra,
  .js .aparece,
  .js .revela,
  .js .revela > img {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    animation: none !important;
    transition: opacity 240ms ease !important;
  }

  .js .monograma__anillo,
  .rsvp__palomita { animation: none !important; stroke-dashoffset: 0 !important; }

  .apertura__baja span { animation: none; }

  .rsvp__forma[data-saliendo="si"],
  .rsvp__listo { filter: none !important; transform: none !important; }

  .barra { transition: opacity 240ms ease; }

  /* Lo añadido tras la auditoría también se calma. Movimiento reducido no
     es "sin retroalimentación": la píldora sigue marcando la opción, el
     error sigue apareciendo y el agradecimiento sigue llegando — lo que
     se quita es el desplazamiento, no la información. */
  .opciones::before { transition: none !important; }

  .rsvp__listo[data-entrando="si"] > * { animation: none !important; }

  .rsvp__error { transition: opacity 160ms ease !important; transform: none !important; }

  .boton[data-copiando] .boton__etiqueta { filter: none !important; }

  .campo--personas { transition: opacity 200ms ease !important; }
}

@media (prefers-reduced-transparency: reduce) {
  .barra { background: var(--fondo); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .grano { display: none; }
}

@media (prefers-contrast: more) {
  :root { --tenue: var(--tenue-contraste); --linea: color-mix(in srgb, var(--acento) 50%, transparent); }
  .grano { display: none; }
}

:focus-visible { outline: 2px solid var(--acento); outline-offset: 3px; }
