/* ==========================================================================
   Boda Yaritzy & Christian — hoja de estilos única.

   Organización:
     1. Tokens de diseño
     2. Reset y base
     3. Composición (contenedores, secciones, rejillas)
     4. Tipografía
     5. Componentes
     6. Utilidades
     7. Accesibilidad y preferencias del usuario
   ========================================================================== */

/* ==========================================================================
   1. TOKENS DE DISEÑO
   ========================================================================== */

:root {
    /* --- Color --- */
    /* Paleta de la boda: blanco y beige como base, verde como único acento y
       en dosis corta — la galería, el pie y las piezas pequeñas. Titulares,
       barra de navegación y cuenta regresiva van en tinta sobre papel o beige.
       El verde está medido contra los fondos reales de la página (blanco,
       `--color-cream` y `--color-beige`) y supera el 4.5:1 de la AA en todos
       los sitios donde lleva texto encima. Si se cambia, hay que volver a
       comprobarlo. */
    --color-ink: #232821;          /* Texto principal (casi negro, en verde) */
    --color-ink-soft: #5a6157;     /* Texto secundario */
    /* El verde de la boda es uno solo y no tiene variantes: #353f29. Se usa tal
       cual en los acentos (texto pequeño, iconos, filetes, botones) y como
       fondo de las secciones oscuras. Sobre ese fondo nunca va otro verde: lo
       que se apoya encima es crema o beige, que son los otros dos colores de la
       paleta. Si algún día hace falta un verde más claro, no lo inventes aquí:
       revisa antes si lo que pide el diseño es en realidad un beige. */
    --color-green: #353f29;
    --color-paper: #ffffff;
    --color-cream: #f7f2e8;        /* Beige claro: tarjetas y secciones alternas */
    --color-beige: #efe7d8;        /* Beige medio: secciones destacadas y bloqueadas */
    --color-line: #e0d7c5;
    /* Deshabilitado y marcas de agua: beige apagado y un gris con verde
       dentro, para que nada del sistema caiga en un gris frío ajeno. */
    --color-muted-bg: #e6ded0;
    --color-muted-ink: #8b8f84;
    --color-dark: #353f29;         /* El mismo verde, como fondo de sección */

    /* --- Tierra ---
       La novia pidió tierra junto al verde, el blanco y el beige. Son cuatro
       tonos sacados del mismo eje cálido que el beige, así que no abren una
       familia nueva: continúan la que ya había.

       Cada uno tiene su sitio medido contra los fondos reales de la página
       (blanco, `--color-cream`, `--color-beige` y el verde). Los números son
       ratios de contraste; el mínimo es 4.5:1 para texto y 3:1 para lo que no
       lleva letra (filetes, iconos, contornos):

         · earth  → texto. 7.08 blanco / 6.35 crema / 5.76 beige. AA en los tres.
         · umber  → titulares. 11.24 / 10.07 / 9.15. El más oscuro de los cuatro.
         · clay   → SOLO filetes e iconos. Sobre beige se queda en 3.99:1, por
                    debajo del 4.5 que pide el texto: si lo pones en una letra,
                    la fallas. Para trazo cumple de sobra.
         · sand   → contornos sobre fondo claro (decorativo, 1.5-2:1) y texto
                    sobre el verde, donde sí sube a 5.69:1.

       Si cambias alguno, vuelve a medirlo antes de usarlo en texto. */
    --color-earth: #6b543f;
    --color-umber: #4a3728;
    --color-clay: #8c6a4e;
    --color-sand: #cbb79b;
    --color-on-dark: #f3efe6;      /* Crema sobre verde */
    --color-on-dark-soft: #d9d3c4; /* Beige sobre verde (antes era verdoso) */

    /* Estados */
    --color-error: #a3282f;
    --color-error-bg: #fdf2f2;
    /* El aviso de «confirmado» usaba un verde propio. Ahora reutiliza el de la
       boda; lo que lo distingue del error sigue siendo el color (verde frente a
       rojo), y el fondo pasa a beige para no meter un cuarto tono. */
    --color-success: #353f29;
    --color-success-bg: #f4f2e9;

    /* --- Tipografía ---
       Tres familias con papeles bien separados. Para cambiarle el carácter al
       sitio basta con tocar estas tres líneas y el <link> de Google Fonts del
       HTML; el resto de la hoja va por variables.

         · display → titulares y cifras grandes. Cormorant es elegantísima a
                     tamaño grande, pero tiene la altura de x muy pequeña y el
                     trazo fino: en texto corrido obliga a forzar la vista, y
                     por eso ya no se usa ahí.
         · body    → todo el texto seguido, el formulario y los botones. Lora
                     está dibujada para pantalla (altura de x alta y grosor más
                     parejo) y mantiene el aire de serif clásica.
         · script  → solo los nombres y los remates. Nunca en texto largo. */
    --font-display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
    --font-body: 'Lora', Georgia, 'Times New Roman', serif;
    --font-script: 'Great Vibes', 'Segoe Script', cursive;

    /* Las etiquetas en versalitas y los botones piden `--font-ui` en media
       hoja. Ahora también es Lora: a tamaño pequeño y con letterspacing se lee
       mejor que la sans del sistema y no rompe la unidad tipográfica. */
    --font-ui: var(--font-body);

    /* Escala fluida: se adapta al ancho sin saltos bruscos.
       Los mínimos están puestos para que nada baje de 15 px salvo las
       micro-etiquetas: por debajo de ahí el texto empieza a costarle a los
       invitados de más edad, que en una boda son bastantes. */
    --text-xs: clamp(0.8125rem, 0.15vw + 0.78rem, 0.875rem);
    --text-sm: clamp(0.9375rem, 0.25vw + 0.88rem, 1rem);
    --text-base: clamp(1.0625rem, 0.45vw + 0.97rem, 1.1875rem);
    --text-lg: clamp(1.1875rem, 0.7vw + 1.05rem, 1.375rem);
    --text-xl: clamp(1.375rem, 1vw + 1.15rem, 1.625rem);
    --text-script-md: clamp(2.25rem, 4vw + 1rem, 3.25rem);
    --text-script-lg: clamp(2.75rem, 6vw + 1rem, 4.5rem);
    --text-hero: clamp(2.25rem, 6vw + 0.5rem, 4rem);
    --text-counter: clamp(2.25rem, 5vw + 0.75rem, 3.75rem);

    /* --- Espaciado (escala de 4 px) --- */
    --space-2xs: 0.25rem;
    --space-xs: 0.5rem;
    --space-sm: 0.75rem;
    --space-md: 1rem;
    --space-lg: 1.5rem;
    --space-xl: 2rem;
    --space-2xl: 3rem;
    --space-3xl: 4rem;
    --space-4xl: 6rem;

    /* --- Layout --- */

    /* Alto de la barra fija. Es el desfase exacto con el que tiene que llegar
       cada sección al pulsar un enlace del menú (ver `scroll-margin-top`).
       El valor real lo mide el JavaScript al cargar y lo reescribe aquí, así
       que este es solo el punto de partida: la barra en móvil, donde el botón
       de menú (2.75rem) es lo más alto que hay dentro. */
    --header-height: 4.75rem;

    --container-width: 72rem;
    --container-narrow: 46rem;
    --container-pad: clamp(1rem, 4vw, 2rem);
    --section-pad: clamp(1.75rem, 4.5vw, 3.25rem);

    /* --- Otros --- */
    --radius-sm: 6px;
    --radius-md: 10px;
    --radius-pill: 999px;
    /* Las sombras van teñidas del verde de la boda, no de negro puro. */
    --shadow-sm: 0 1px 3px rgb(53 63 41 / 10%);
    --shadow-md: 0 6px 20px rgb(53 63 41 / 12%);
    --shadow-lg: 0 12px 36px rgb(53 63 41 / 16%);
    --transition: 200ms ease;

    /* --- Textura ---
       «Que no sea lisa»: las superficies llevan un grano de papel en lugar de
       un color plano. Es un SVG en línea (`feTurbulence`) embebido como
       data-URI, no un archivo: no añade petición de red, pesa medio kilobyte y
       al ser vectorial no pixela en pantallas de alta densidad.

       El grano es GRIS NEUTRO Y OPACO, y se mezcla con el fondo mediante
       `background-blend-mode: soft-light` en cada superficie. Esto es lo
       importante y no es un capricho técnico:

       La primera versión era ruido translúcido (blanco sobre el verde, tinta
       sobre el papel). Funcionaba como textura, pero un ruido blanco al 25 %
       sube la luminosidad media del fondo, y el verde de la boda —que es UN
       color concreto, #353f29, elegido y medido— se aclaraba hasta un salvia
       que ya no era el suyo. Con ruido neutro el problema desaparece por
       construcción: `feComponentTransfer` comprime el ruido al rango
       [0.275, 0.725], centrado en el gris medio, y `soft-light` aclara donde el
       grano supera 0.5 y oscurece donde no llega. La media se queda donde
       estaba, así que el color base sobrevive intacto y el mismo dibujo sirve
       para el blanco, el crema, el beige y el verde.

       Dos números lo gradúan:
         · `baseFrequency` (0.7) es el grosor de la fibra. Cuanto más bajo, más
           gruesa y más se parece a un papel hecho a mano; subiéndolo se afina
           hasta parecer ruido de televisión. Se probó a 0.9 y no se veía.
         · `slope` (0.6) es el contraste del grano. Súbelo para más textura;
           a partir de 0.8 el ruido empieza a competir con el texto.

       Y un detalle que cuesta media tarde si se pasa por alto:
       `color-interpolation-filters='sRGB'` en el filtro NO es opcional. Los
       filtros SVG trabajan en linearRGB por defecto, así que el 0.5 «gris
       medio» que produce el ruido sale convertido a ~0.74 en pantalla: un gris
       claro. Con `soft-light` eso no textura, aclara —el verde #353f29 se subía
       a un salvia visiblemente más pálido, comparado lado a lado contra el
       plano—. Con el atributo puesto, los valores se quedan donde deben y el
       color base no se mueve.

       IMPORTANTE: una superficie con este grano necesita SIEMPRE su propio
       `background-color` y su `background-blend-mode`. Sin color de fondo, el
       grano no tiene con qué mezclarse y se queda como una plancha gris. */
    --texture-grain:
        url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='220'%20height='220'%3E%3Cfilter%20id='t'%20color-interpolation-filters='sRGB'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.7'%20numOctaves='5'%20stitchTiles='stitch'/%3E%3CfeColorMatrix%20type='saturate'%20values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR%20type='linear'%20slope='0.6'%20intercept='0.2'/%3E%3CfeFuncG%20type='linear'%20slope='0.6'%20intercept='0.2'/%3E%3CfeFuncB%20type='linear'%20slope='0.6'%20intercept='0.2'/%3E%3CfeFuncA%20type='linear'%20slope='0'%20intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect%20width='220'%20height='220'%20filter='url(%23t)'/%3E%3C/svg%3E");

    /* Trama de lino: dos rayados cruzados a 45°. Va SOLO sobre el beige, que es
       la superficie que hace de «papel hecho a mano»; puesta en todas a la vez,
       el cruce de líneas produce muaré al hacer zoom. */
    --texture-linen:
        repeating-linear-gradient(45deg,  rgb(53 63 41 / 3.5%) 0 1px, transparent 1px 6px),
        repeating-linear-gradient(-45deg, rgb(53 63 41 / 3.5%) 0 1px, transparent 1px 6px);

    /* --- Relieve ---
       El relieve es una convención de dibujo, no un efecto: la luz cae desde
       arriba, así que lo que sobresale lleva el filo superior iluminado y el
       inferior en sombra. Invertido, el mismo par hunde la pieza en el papel.
       Todo va en sombras internas para que no altere ni el tamaño ni la
       posición de nada.

       La luz es blanca; la sombra, el verde de la paleta. Nunca negro puro:
       sobre beige el negro grisea y rompe el tono cálido. */
    --relief-raised:
        inset 0 1px 0 rgb(255 255 255 / 70%),
        inset 0 -1px 0 rgb(53 63 41 / 9%);
    --relief-raised-strong:
        inset 0 2px 0 rgb(255 255 255 / 85%),
        inset 0 -2px 3px rgb(53 63 41 / 12%);
    /* Hundido: para lo que el invitado rellena y para los huecos. */
    --relief-sunken:
        inset 0 2px 4px rgb(53 63 41 / 13%),
        inset 0 -1px 0 rgb(255 255 255 / 65%);
    /* Grabado en seco, para titulares sobre beige y crema. */
    --relief-letterpress: 0 1px 0 rgb(255 255 255 / 70%);

    /* Foto de la portada. Es vertical 2:3, como el resto de las de la pareja,
       y de ahí sale el hero partido de escritorio (ver `.hero`). Para cambiarla
       basta con tocar estas rutas; si la nueva tiene el motivo descentrado,
       ajusta también `--hero-focus`. */
    --hero-image: url('../img/novios-atardecer-1200.webp');
    /* En el hero partido la foto se ve entera y ocupa media pantalla, así que
       ahí hace falta más resolución que en el recorte de móvil. */
    --hero-image-lg: url('../img/novios-atardecer-1600.webp');

    /* La pareja ocupa la franja central, así que el recorte lateral de `cover`
       en móvil no se lleva nada por delante. */
    --hero-focus: 50% 50%;
}

/* ==========================================================================
   2. RESET Y BASE
   ========================================================================== */

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

* {
    margin: 0;
}

html {
    /* El desplazamiento suave de los enlaces del menú se desactiva más abajo
       para quien prefiere movimiento reducido. */
    scroll-behavior: smooth;
    -webkit-text-size-adjust: 100%;
}

/* Destinos de los enlaces del menú. El desfase va en el destino
   (`scroll-margin-top`) y no en el contenedor (`scroll-padding-top`): así no
   depende de quién sea el elemento que hace scroll, que con `overflow-x` en el
   <body> no siempre es el mismo.

   La medida es justo el alto de la barra, ni un píxel más: el borde superior de
   la sección queda pegado al borde inferior de la barra. Si el desfase se pasa
   —antes eran 5rem fijos, unos 16px de más en escritorio— asoma por debajo una
   franja del final de la sección anterior. */
section[id],
#contenido,
.form__status {
    scroll-margin-top: var(--header-height);
}

/* Lo mismo para lo que recibe el foco con el teclado: al tabular, el navegador
   trae el elemento a la vista y sin esto la barra puede taparlo. Va con
   `:where()` para no añadir especificidad a nada. */
:where(a, button, input, select, textarea, [tabindex]) {
    scroll-margin-top: var(--header-height);
}

body {
    background-color: var(--color-paper);
    /* El grano se fija a la ventana: al desplazarse, el papel se queda quieto y
       el contenido corre por encima. Si se moviera con el scroll, un ruido de
       frecuencia tan alta produce un parpadeo desagradable. */
    background-image: var(--texture-grain);
    background-blend-mode: soft-light;
    background-attachment: fixed;
    color: var(--color-ink);
    font-family: var(--font-body);
    font-size: var(--text-base);
    /* Lora tiene la altura de x alta, así que agradece algo más de aire entre
       líneas del que necesitaba Cormorant. */
    line-height: 1.7;
    min-height: 100vh;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Los titulares heredaban la familia del cuerpo. Ahora tienen la suya, con el
   interlineado ceñido y el tracking ligeramente negativo que pide una serif de
   display: a tamaño grande abre demasiado y la palabra se deshilacha. */
h1,
h2,
h3,
h4 {
    font-family: var(--font-display);
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: -0.01em;
    /* Reparte las palabras entre las líneas en lugar de dejar una suelta al
       final, que es lo que más afea un titular a dos líneas en móvil. */
    text-wrap: balance;
    text-rendering: optimizeLegibility;
}

/* Evita la línea final con una sola palabra en los párrafos. */
p {
    text-wrap: pretty;
}

img,
svg,
picture {
    display: block;
    max-width: 100%;
    height: auto;
}

/* El atributo `hidden` debe ganar siempre. Sin esta regla, cualquier clase que
   fije `display` (grid, flex…) lo anula por especificidad y el elemento sigue
   viéndose pese a estar marcado como oculto. */
[hidden] {
    display: none !important;
}

input,
button,
select,
textarea {
    font: inherit;
    color: inherit;
}

button {
    cursor: pointer;
}

a {
    color: var(--color-green);
    text-underline-offset: 0.2em;
}

a:hover {
    color: var(--color-ink);
}

/* Anillo de foco visible y consistente en todo el sitio. Nunca se elimina
   sin sustituirlo: es la única guía para quien navega con teclado. */
:where(a, button, input, [tabindex]):focus-visible {
    outline: 3px solid var(--color-green);
    outline-offset: 3px;
    border-radius: var(--radius-sm);
}

/* ==========================================================================
   3. COMPOSICIÓN
   ========================================================================== */

.container {
    width: 100%;
    max-width: var(--container-width);
    margin-inline: auto;
    padding-inline: var(--container-pad);
}

.container--narrow {
    max-width: var(--container-narrow);
}

.section {
    padding-block: var(--section-pad);
}

.section--cream {
    background-color: var(--color-cream);
    background-image: var(--texture-grain);
    background-blend-mode: soft-light;
    background-attachment: fixed;
}

/* Secciones que ocupan el alto completo de la pantalla.
   `svh` usa el alto visible más pequeño, así la sección no «salta» cuando el
   navegador móvil oculta o muestra su barra; `vh` queda como reserva para
   navegadores que aún no entienden la unidad.
   El centrado se hace con `margin-block: auto` en el hijo y no con
   `justify-content`, porque los márgenes automáticos se anulan solos cuando el
   contenido es más alto que la pantalla (móvil en horizontal, tipografía
   grande) y entonces nada queda recortado fuera del área con scroll. */
.section--full {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100svh;
}

.section--full > .container {
    width: 100%;
}

/* Centrado óptico en las secciones a pantalla completa.

   `margin-block: auto` centraba el bloque en la sección, y sobre el papel salía
   perfecto: 181 px por arriba y 181 por abajo en un móvil de 812. Pero la barra
   de navegación va fija encima y se come los primeros 62, así que de esos 181
   de arriba solo se veían 119 contra 181 limpios abajo, y el bloque parecía
   caído.

   Estos dos rellenos flexibles reparten el hueco sobrante, con la diferencia de
   que el de arriba arranca con el alto de la barra ya puesto: el contenido
   queda centrado en la franja que de verdad se ve. Y como pueden encogerse
   (`flex-shrink: 1`), en las secciones donde el contenido llega justo no suman
   ni un píxel y nada desborda. Tienen que ser flexibles y no un `padding`: el
   padding no se encoge y esas secciones se saldrían de pantalla. */
.section--full::before,
.section--full::after {
    content: '';
    flex: 1 1 0;
}

/* Corrección óptica: el hueco de abajo crece algo más que el de arriba, así que
   el bloque queda un poco por encima del centro exacto.

   No es capricho. Estas secciones pesan abajo —tarjetas con fondo, borde y
   sombra— contra un titular caligráfico de trazo fino arriba, y el ojo centra
   por masa visual, no por caja. Repartido al 50 % el resultado se mide
   perfecto (141 px de aire sobre la tinta del título contra 143 bajo la última
   tarjeta) y aun así se ve caído, con demasiado aire sobre el titular. El
   reparto queda en 46/54. */
.section--full::after {
    flex-grow: 1.25;
}

/* El desplazamiento solo entra en pantallas con alto de sobra. En una de 640 px
   el hueco libre es menor que la propia barra, así que reservarle sitio no
   centraba nada: empujaba el contenido y estiraba la sección un 11 % por debajo
   del borde. Por eso va condicionado y no puesto siempre. */
@media (height >= 45rem) {
    .section--full::before {
        flex-basis: var(--header-height);
    }
}

/* Estas secciones se alinean con el borde de la pantalla y no por debajo de la
   barra, al revés que el resto.

   El desfase general existe para que a una sección de alto normal no le tape la
   barra su primera línea. Pero estas miden justo 100 svh: empujarlas 76 px las
   saca por abajo esa misma cantidad, y el hueco que sobra se acumula arriba.
   Sumado al centrado interno —que ya descuenta la barra— daba 219 px de aire
   por arriba contra 67 por abajo al llegar desde el menú. Con el desfase a
   cero, la sección coincide con la pantalla y las cuentas cuadran. */
.section--full[id] {
    scroll-margin-top: 0;
}

/* Pantalla completa solo en el móvil, que es donde se pide que cada sección
   ocupe el alto entero. En escritorio estas mismas secciones vuelven a su alto
   natural: con el contenido repartido a lo ancho, forzarles 100vh dejaba
   huecos de hasta 455 px (medido a 1440x820 en la cuenta regresiva). Las dos
   que llevan `--full` a secas lo son en cualquier tamaño, por diseño. */
@media (width < 48em) {
    .section--full-mobile {
        display: flex;
        flex-direction: column;
        min-height: 100vh;
        min-height: 100svh;
    }

    .section--full-mobile > .container {
        width: 100%;
    }

    /* Mismo centrado óptico que en `.section--full`: ver el comentario largo
       de allí. */
    .section--full-mobile::before,
    .section--full-mobile::after {
        content: '';
        flex: 1 1 0;
    }

    /* La corrección óptica va aquí dentro y no arriba con la de `.section--full`
       por el orden: el `flex: 1 1 0` de la regla anterior es un atajo que
       reescribe también `flex-grow`, así que puesta antes se perdía y estas
       secciones se quedaban con el reparto al 50 %. */
    .section--full-mobile::after {
        flex-grow: 1.25;
    }
}

/* El desplazamiento, igual que arriba, solo donde hay alto de sobra. */
@media (width < 48em) and (height >= 45rem) {
    .section--full-mobile::before {
        flex-basis: var(--header-height);
    }
}

/* Y el mismo desfase a cero, por el mismo motivo, mientras son de pantalla
   completa. En escritorio vuelven a su alto normal y recuperan el desfase. */
@media (width < 48em) {
    .section--full-mobile[id] {
        scroll-margin-top: 0;
    }
}

/* Bloque destacado sin recurrir al verde: el beige medio pesa lo justo para
   separarse del blanco y de `--cream`, y los filetes le dan el remate. Se usa
   en la cuenta regresiva, que antes era una segunda sección verde. */
/* El beige es la superficie con más cuerpo de la página, así que se lleva el
   tratamiento completo: grano encima, trama de lino debajo. El orden importa;
   la primera capa listada es la de arriba. */
.section--beige {
    background-color: var(--color-beige);
    background-image: var(--texture-grain), var(--texture-linen);
    /* Un valor por capa: el grano se mezcla en luz suave y el lino multiplica
       sobre el beige. La lista de `background-attachment` va en el mismo orden. */
    background-blend-mode: soft-light, multiply;
    background-attachment: fixed, scroll;
    /* Los filetes de arriba y abajo pasan de una línea plana a un canto con
       volumen: la arena marca el borde y la luz blanca por dentro lo levanta. */
    border-block: 1px solid var(--color-sand);
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 60%),
        inset 0 -1px 0 rgb(255 255 255 / 45%);
}

.section--dark {
    background-color: var(--color-dark);
    background-image: var(--texture-grain);
    background-blend-mode: soft-light;
    background-attachment: fixed;
    color: var(--color-on-dark);
}

.section__header {
    text-align: center;
    margin-bottom: var(--space-2xl);
}

.section__intro {
    max-width: 42rem;
    margin-inline: auto;
    color: var(--color-ink-soft);
}

.section--dark .section__intro {
    color: var(--color-on-dark-soft);
}

/* Rejilla auto-ajustable: pasa de una a dos columnas sin media queries. */
.grid-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
    gap: var(--space-xl);
}

/* ==========================================================================
   4. TIPOGRAFÍA
   ========================================================================== */

/* Los titulares van en tinta, no en verde: con la caligráfica a este tamaño el
   verde se adueñaba de la página entera. El acento se reserva para las piezas
   pequeñas —volantillas, filetes, iconos y cifras—, que es donde «un poco de
   verde» se lee como intención y no como color corporativo. */
.script-title {
    font-family: var(--font-script);
    font-size: var(--text-script-lg);
    font-weight: 400;
    line-height: 1.2;
    /* Los titulares pasan de la tinta casi negra al tierra profundo. Es donde
       mejor se ve el color nuevo sin tocar el texto corrido, que sigue en
       `--color-ink` para no perder contraste en párrafos largos. El umber da
       10.07:1 sobre crema y 9.15:1 sobre beige, muy por encima del 4.5 de la AA.
       Ver el bloque de tokens para el resto de las medidas. */
    color: var(--color-umber);
    /* Grabado en seco: una línea de luz justo debajo del trazo hace que la
       letra parezca hundida en el papel. Funciona porque el fondo es claro; en
       las secciones verdes se anula más abajo. */
    text-shadow: var(--relief-letterpress);
    /* La fuente caligráfica tiene descendentes largos que se recortarían. */
    padding-bottom: 0.15em;
}

.script-title--sm {
    font-size: var(--text-script-md);
}

/* Sobre el verde no hay grabado que valga: la luz blanca por debajo del trazo
   se convierte en un halo sucio. */
.section--dark .script-title {
    color: var(--color-on-dark);
    text-shadow: none;
}

.eyebrow {
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    /* En versalitas la serif necesita algo de peso para no verse desvaída, y
       menos tracking que una sans: sus remates ya separan las letras. */
    font-weight: 500;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--color-green);
}

.section--dark .eyebrow {
    color: var(--color-on-dark-soft);
}

.lead {
    font-size: var(--text-lg);
}

.text-muted {
    color: var(--color-ink-soft);
}

/* Filete decorativo entre bloques. */
.rule {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    margin-block: var(--space-lg);
    color: var(--color-green);
}

.rule::before,
.rule::after {
    content: '';
    height: 1px;
    width: min(5rem, 18vw);
    background-color: currentcolor;
}

.rule__dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: currentcolor;
}

/* ==========================================================================
   5. COMPONENTES
   ========================================================================== */

/* --- 5.1 Barra de navegación --------------------------------------------- */

.site-header {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: 50;
    /* Sobre el hero arranca transparente; al hacer scroll el JS añade
       `.site-header--solid`. */
    background-color: transparent;
    transition: background-color var(--transition), box-shadow var(--transition);
}

/* Al abandonar el hero la barra no se vuelve verde, sino papel: el verde queda
   reservado para la galería y el pie. Con fondo claro los enlaces tienen que
   cambiar de color y soltar la sombra de texto que llevaban sobre la foto. */
.site-header--solid {
    background-color: rgb(255 255 255 / 94%);
    backdrop-filter: blur(6px);
    border-bottom: 1px solid var(--color-line);
    box-shadow: var(--shadow-sm);
}

.site-header--solid .site-header__brand,
.site-header--solid .nav__link,
.site-header--solid .nav-toggle {
    color: var(--color-ink);
    text-shadow: none;
}

.site-header--solid .nav-toggle {
    filter: none;
}

.site-header--solid .site-header__brand:hover,
.site-header--solid .nav__link:hover,
.site-header--solid .nav__link:focus-visible {
    color: var(--color-green);
}

.site-header__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    padding-block: var(--space-md);
}

.site-header__brand {
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--color-paper);
    text-decoration: none;
    text-shadow: 0 1px 4px rgb(0 0 0 / 55%);
}

.site-header__brand:hover {
    color: var(--color-on-dark);
}

.nav__list {
    display: flex;
    gap: clamp(var(--space-md), 2.5vw, var(--space-xl));
    list-style: none;
    padding: 0;
}

.nav__link {
    color: var(--color-paper);
    font-size: var(--text-base);
    text-decoration: none;
    text-shadow: 0 1px 4px rgb(0 0 0 / 55%);
    transition: color var(--transition);
}

.nav__link:hover,
.nav__link:focus-visible {
    color: var(--color-on-dark);
}

/* Botón hamburguesa: solo en móvil. */
.nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem; /* Objetivo táctil mínimo recomendado. */
    padding: 0;
    background: none;
    border: 0;
    color: var(--color-paper);
    filter: drop-shadow(0 1px 3px rgb(0 0 0 / 55%));
}

.nav-toggle__icon {
    width: 1.75rem;
    height: 1.75rem;
    stroke: currentcolor;
    stroke-width: 1.75;
    fill: none;
}

/* Menú desplegable móvil. */
.nav--mobile {
    background-color: var(--color-paper);
    border-top: 1px solid var(--color-line);
    box-shadow: var(--shadow-md);
    /* Con siete destinos la lista puede pasar de la altura de la pantalla en un
       móvil apaisado; así los últimos siguen alcanzándose. */
    max-height: calc(100dvh - var(--header-height));
    overflow-y: auto;
    overscroll-behavior: contain;
}

.nav--mobile .nav__list {
    flex-direction: column;
    gap: 0;
    padding-block: var(--space-xs);
}

/* El desplegable es claro también cuando la barra sigue transparente sobre el
   hero, así que su color no puede depender de `.site-header--solid`. */
.nav--mobile .nav__link {
    display: block;
    padding-block: var(--space-sm);
    border-bottom: 1px solid var(--color-line);
    color: var(--color-ink);
    text-shadow: none;
}

.nav--mobile .nav__link:hover,
.nav--mobile .nav__link:focus-visible {
    color: var(--color-green);
}

.nav--mobile .nav__item:last-child .nav__link {
    border-bottom: 0;
}

/* El menú cambia a hamburguesa antes que el resto de la página (60em en vez de
   los 48em habituales de la hoja) porque son siete destinos: en una tablet, con
   los nombres y el logotipo en la misma línea, no caben sin apelotonarse. */
@media (width < 60em) {
    .nav--desktop {
        display: none;
    }
}

@media (width >= 60em) {
    /* Sin el botón de menú la barra encoge, y con ella el desfase de los
       enlaces del menú. Valor de partida: el JavaScript mide el alto real. */
    :root {
        --header-height: 4rem;
    }

    .nav-toggle,
    .nav--mobile {
        display: none;
    }
}

/* --- 5.2 Hero ------------------------------------------------------------ */

.hero {
    position: relative;
    display: grid;
    place-items: center;
    min-height: 100svh; /* `svh` evita el salto por la barra del navegador móvil. */
    padding: var(--space-4xl) var(--container-pad) var(--space-2xl);
    text-align: center;
    color: var(--color-paper);
    overflow: hidden;
}

.hero__media {
    position: absolute;
    inset: 0;
    background-image: var(--hero-image);
    background-size: cover;
    background-position: var(--hero-focus);
    z-index: 0;
}

/* Sombra de cabecera. Lo único que vela la foto entera, y solo por el menú: el
   logotipo cae sobre el cielo, que en esta imagen es blanco puro, y hace falta
   un 70 % largo de velo para llegar al 4.5:1. Se mantiene firme mientras dura
   la barra y solo después se disuelve; con un degradado que cayera desde el
   borde, a la altura del texto ya andaba por el 57 % y el logotipo se quedaba
   en 2.45:1.

   El tono no es negro sino el verde de la boda: sobre un atardecer el negro
   apaga los dorados y deja la imagen gris.

   El contraste del resto del texto NO se resuelve aquí, sino en el propio
   bloque (ver `.hero__content` más abajo). Antes esta capa velaba la foto de
   arriba abajo y, para que se leyera la cita, había que oscurecer también la
   franja de las caras, que está tres dedos por encima. */
.hero__media::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
        to bottom,
        rgb(53 63 41 / 78%) 0%,
        rgb(53 63 41 / 74%) calc(var(--header-height) * 0.85),
        rgb(53 63 41 / 0%) calc(var(--header-height) * 2.6)
    );
}

.hero__content {
    position: relative;
    z-index: 1;
    max-width: 48rem;
}

/* Mientras el texto va encima de la foto, el velo que lo hace legible viaja con
   él y no con la imagen: así la foto solo se oscurece donde hay letras, y las
   caras —que quedan justo por encima del primer renglón— se mantienen limpias.
   El bloque ocupa todo el ancho para que el degradado llegue a los bordes; el
   respiro lateral lo pone él, no la sección. */
@media (width < 64em) {
    .hero {
        padding-inline: 0;
    }

    .hero__content {
        width: 100%;
        max-width: none;
        padding: var(--space-4xl) var(--container-pad) var(--space-3xl);
        /* Aquí NO va grano. El velo es un degradado transparente por arriba, y
           una capa opaca mezclada contra la nada se queda en gris sólido en esa
           franja. Tampoco hace falta: en móvil el hero es casi todo fotografía,
           que de liso no tiene nada. */
        background: linear-gradient(
            to bottom,
            rgb(53 63 41 / 0%) 0%,
            rgb(53 63 41 / 72%) 18%,
            rgb(53 63 41 / 88%) 100%
        );
    }
}

/* Móvil en vertical: la foto y la pantalla tienen casi la misma forma, así que
   `cover` apenas recorta por los lados y la composición llega entera. El texto
   se apoya abajo del todo. */
@media (width < 64em) and (orientation: portrait) {
    .hero {
        align-items: end;
        padding-bottom: 0;
    }
}

/* --- Escritorio: la portada se parte en dos ---------------------------------
   Una foto vertical 2:3 a sangre en una pantalla apaisada solo deja ver una
   franja: en 1920×900 se pierde el 69 % del alto, y con ello el gesto entero.
   Así que a partir de aquí la foto deja de ser fondo y pasa a ocupar su propia
   mitad, entera y sin recortar, con el texto al lado sobre el verde de la boda.

   El corte va en 64em y no en las 48em del resto de la hoja: entre esas dos
   medidas están las tabletas en vertical, donde la pantalla sigue siendo más
   alta que ancha y la foto a sangre funciona bien.
   ------------------------------------------------------------------------- */
@media (width >= 64em) {
    /* La columna de la foto mide exactamente lo que mide la foto puesta a todo
       lo alto: dos tercios de la altura de la pantalla, que es su proporción.
       Así llena su mitad hasta el borde, sin franjas de fondo asomando a los
       lados y sin que `cover` tenga nada que recortar. El tope del 45 % del
       ancho es para las pantallas raras, más altas que anchas, donde la foto se
       comería el sitio del texto. */
    .hero {
        grid-template-columns: min(50vw, calc(100svh * 2 / 3)) 1fr;
        place-items: stretch;
        padding: 0;
        text-align: left;
    }

    /* Deja de ser una capa absoluta para ocupar su columna. */
    .hero__media {
        position: relative;
        inset: auto;
        grid-column: 1;
        background-color: var(--color-dark);
        background-image: var(--hero-image-lg);
    }

    /* El velo de contraste general sobra: aquí el texto ya no va encima de la
       foto. Lo único que queda es una sombra arriba, porque el logotipo del
       menú sí cae sobre la foto y justo ahí está el cielo, que es lo más claro
       de toda la imagen.

       Se mantiene firme mientras dura la barra y solo después se disuelve: con
       un degradado que caía desde el borde, a la altura del texto ya estaba al
       30 % y el logotipo se sostenía únicamente por su sombra. Oscurecer esa
       franja no cuesta nada, porque ahí no hay más que cielo vacío. */
    .hero__media::after {
        background: linear-gradient(
            to bottom,
            rgb(53 63 41 / 78%) 0%,
            rgb(53 63 41 / 74%) calc(var(--header-height) * 0.85),
            rgb(53 63 41 / 0%) calc(var(--header-height) * 2.6)
        );
    }

    .hero__content {
        grid-column: 2;
        display: grid;
        align-content: center;
        justify-items: start;
        max-width: none;
        padding: var(--space-4xl) clamp(var(--space-2xl), 6vw, var(--space-4xl));
        /* El panel verde del hero partido es la superficie plana más grande de
           toda la página: si algo tenía que dejar de ser liso, era esto. */
        background-color: var(--color-dark);
        background-image: var(--texture-grain);
        background-blend-mode: soft-light;
        color: var(--color-on-dark);
    }

    /* El panel puede pasar de mil píxeles en un monitor ancho, y sin freno el
       titular se estiraba en una sola línea de lado a lado. Con este tope
       vuelve a partirse en dos y el bloque se lee como un párrafo, no como una
       pancarta. */
    .hero__content > * {
        max-width: 34rem;
    }

    /* La fecha va en versalitas muy espaciadas y necesita algo más de sitio: con
       el tope del titular se partía en dos y dejaba el «·» colgando al final de
       la primera línea. */
    .hero__content > .hero__date {
        max-width: 40rem;
    }

    /* Sobre el panel liso las sombras de texto no pintan nada. */
    .hero__content :is(.hero__quote, .hero__title, .hero__date) {
        text-shadow: none;
    }

    /* Va con `.hero__content` delante a propósito: `.hero__ornament` se declara
       más abajo en la hoja, y a igual especificidad ganaba por orden. */
    .hero__content .hero__ornament {
        margin-inline: 0;
    }
}

/* La cita de apertura es decorativa, no texto de lectura: aquí la cursiva de
   Cormorant luce, y a este tamaño se lee sin problema. */
.hero__quote {
    font-family: var(--font-display);
    font-style: italic;
    font-size: var(--text-xl);
    line-height: 1.4;
    color: var(--color-on-dark);
    margin-bottom: var(--space-xl);
}

.hero__ornament {
    width: min(150px, 40vw);
    margin: 0 auto var(--space-xl);
    color: var(--color-paper);
}

.hero__title {
    font-size: var(--text-hero);
    font-weight: 600;
    line-height: 1.12;
    text-shadow: 0 2px 16px rgb(0 0 0 / 45%);
}

.hero__names {
    display: block;
    font-family: var(--font-script);
    font-size: var(--text-script-lg);
    font-weight: 400;
    line-height: 1.25;
    padding-bottom: 0.1em;
    margin-top: var(--space-sm);
}

.hero__date {
    margin-top: var(--space-xl);
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    letter-spacing: 0.22em;
    text-transform: uppercase;
}

/* --- 5.3 Botones --------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    min-height: 2.875rem;
    padding: var(--space-sm) var(--space-xl);
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    background-color: var(--color-green);
    color: var(--color-paper);
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    text-decoration: none;
    text-align: center;
    /* Botón en relieve. La luz de arriba NO usa `--relief-raised`: aquel blanco
       al 70 % está calculado para el papel claro y sobre el verde profundo se
       convierte en una raya de tiza. Aquí basta un 22 % para insinuar el canto,
       y la sombra de abajo hace el resto del volumen. */
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 22%),
        inset 0 -2px 0 rgb(0 0 0 / 16%),
        var(--shadow-sm);
    transition:
        background-color var(--transition),
        box-shadow var(--transition),
        transform var(--transition);
}

.btn:hover:not(:disabled) {
    background-color: var(--color-ink);
    color: var(--color-paper);
    transform: translateY(-1px);
}

/* Al pulsar, el relieve se invierte y el botón se hunde: es la mitad de la
   sensación táctil, y sin ella el `translateY` del hover se queda a medias. */
.btn:active:not(:disabled) {
    transform: translateY(1px);
    box-shadow:
        inset 0 2px 4px rgb(0 0 0 / 22%),
        inset 0 -1px 0 rgb(255 255 255 / 12%);
}

/* Deshabilitado: sin relieve. Un botón que sigue pareciendo pulsable es una
   promesa que la página no puede cumplir. */
.btn:disabled {
    background-color: var(--color-muted-bg);
    color: var(--color-muted-ink);
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

/* Por defecto se dibuja para fondo claro; sobre `.section--dark` cambia al
   verde claro, que es el único que se lee sobre el verde profundo. */
.btn--outline {
    background-color: transparent;
    border-color: currentcolor;
    color: var(--color-green);
}

.btn--outline:hover:not(:disabled) {
    background-color: var(--color-green);
    color: var(--color-paper);
}

.section--dark .btn--outline {
    color: var(--color-on-dark);
}

.section--dark .btn--outline:hover:not(:disabled) {
    background-color: var(--color-on-dark);
    color: var(--color-ink);
}

.btn--block {
    display: flex;
    width: 100%;
    padding-block: var(--space-md);
}

.btn__icon {
    width: 1.125rem;
    height: 1.125rem;
    stroke: currentcolor;
    stroke-width: 1.75;
    fill: none;
}

/* --- 5.4 Cuenta regresiva ------------------------------------------------ */

.countdown {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: clamp(var(--space-md), 5vw, var(--space-3xl));
    margin-block: var(--space-2xl);
}

.countdown__unit {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 4rem;
}

/* Las cifras son el sitio donde mejor cae el acento: ocupan poca superficie y
   son lo primero que se mira al llegar a la sección. */
.countdown__value {
    font-family: var(--font-display);
    font-size: var(--text-counter);
    font-weight: 600;
    line-height: 1;
    color: var(--color-green);
    /* Evita que el ancho baile al cambiar los dígitos cada segundo. */
    font-variant-numeric: tabular-nums;
}

.countdown__label {
    margin-top: var(--space-xs);
    font-family: var(--font-ui);
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--color-ink-soft);
}

/* La cuenta regresiva vive sobre beige, pero los colores siguen siendo
   contextuales por si el bloque vuelve a un fondo oscuro. */
.section--dark .countdown__value {
    color: var(--color-on-dark);
}

.section--dark .countdown__label {
    color: var(--color-on-dark-soft);
}

/* Móvil: las cuatro unidades en una sola fila.

   Con `flex-wrap` la última se caía sola a un segundo renglón: a 390 px cada
   unidad disponía de unos 75 px y «SEGUNDOS», que en versalitas espaciadas mide
   más de 80, no cabía. La rejilla de cuatro columnas fija el reparto para que
   no dependa del ancho de la palabra más larga, y el espaciado se aprieta para
   que quepa con holgura. `min-width: 0` es necesario: el suelo de 4rem de las
   unidades impedía que las columnas encogieran. */
@media (width < 48em) {
    .countdown {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
        gap: var(--space-xs);
        margin-block: var(--space-xl);
    }

    .countdown__unit {
        min-width: 0;
    }

    /* «SEGUNDOS» es la palabra que manda: con el cuerpo y el espaciado de
       escritorio mide 81 px y la columna da 62 en un móvil estrecho, así que se
       salía y llegaba a rozar la etiqueta de al lado. Cuerpo fluido y menos
       tracking para que la más larga entre en su columna en todo el rango. El
       suelo de 11 px solo se toca por debajo de 360 px de ancho; de 375 en
       adelante se queda en 12-13, y a partir de tablet vuelve a `--text-xs`. */
    .countdown__label {
        font-size: clamp(0.6875rem, 3.2vw, 0.875rem);
        letter-spacing: 0.04em;
    }
}

.countdown__done {
    font-family: var(--font-script);
    font-size: var(--text-script-lg);
}

/* --- 5.5 Tarjetas de lugar ----------------------------------------------- */

.venue-card {
    display: flex;
    flex-direction: column;
    background-color: var(--color-cream);
    /* El contorno pasa a arena: es el mismo papel, un tono más tostado, y marca
       el canto de la tarjeta sin la frialdad de la línea anterior. */
    border: 1px solid var(--color-sand);
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
    transition: box-shadow var(--transition), transform var(--transition);
}

.venue-card:hover {
    box-shadow: var(--shadow-lg);
    transform: translateY(-2px);
}

/* La foto se recorta por proporción y no por un alto fijo.

   Con `height: 20rem` el recorte dependía del ancho de la tarjeta: cuanto más
   ancha, más se ampliaba la foto y menos se veía de ella. La de la parroquia es
   vertical (382 × 510) y en una tarjeta de escritorio de 560 px la ampliación
   la dejaba en 748 px de alto, de los que solo entraban 320: se veía el 43 % de
   la imagen, una franja del centro que se comía las torres por arriba y la
   entrada por abajo. De ahí que apenas se viera fachada.

   Con `aspect-ratio` la fracción visible ya no depende del ancho: se ve el mismo
   56 % de la foto en cualquier pantalla, y en la parroquia eso alcanza desde las
   cruces hasta el atrio. Para ver más fachada, baja el segundo número (5 / 4 o
   1 / 1 dan una tarjeta más alta y más foto); es el único valor que hay que
   tocar. */
.venue-card__image {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    /* Sube el recorte respecto al centro geométrico. Solo tiene efecto en las
       fotos verticales, que son las que sobran de alto: en las horizontales,
       como la de Villa Cantabria, lo que sobra es ancho y este valor no cuenta.
       El 55 % es lo que mantiene las cruces de las torres dentro del encuadre
       en lugar de recortarlas. */
    object-position: 50% 55%;
}

/* El relieve va en el cuerpo y no en `.venue-card`: la tarjeta recorta con
   `overflow: hidden` y la foto ocupa la franja de arriba, así que una sombra
   interna puesta en la tarjeta quedaría tapada justo donde tiene que verse la
   luz. Aquí, en cambio, el filo superior del cuerpo es el canto real. */
.venue-card__body {
    display: flex;
    flex-direction: column;
    flex: 1;
    gap: var(--space-md);
    padding: var(--space-xl);
    /* El color se repite aquí a propósito: el cuerpo lo heredaba visualmente de
       la tarjeta, pero el grano necesita una superficie propia con la que
       mezclarse (ver la nota del token). */
    background-color: var(--color-cream);
    background-image: var(--texture-grain);
    background-blend-mode: soft-light;
    box-shadow: var(--relief-raised);
    text-align: center;
}

.venue-card__type {
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--color-green);
}

.venue-card__name {
    font-family: var(--font-script);
    font-size: var(--text-script-md);
    font-weight: 400;
    line-height: 1.2;
    padding-bottom: 0.1em;
}

/* El nombre del lugar es el enlace al mapa, en sustitución del botón «Ir al
   mapa» que había al pie de la tarjeta.

   Hereda la caligráfica del titular, así que no puede llevar el verde de los
   enlaces normales: a este tamaño el color tiñe media tarjeta y el nombre deja
   de leerse como un nombre. La señal de que se puede pulsar es el subrayado, en
   arena y de un píxel para que acompañe al trazo en vez de pelearse con él, y
   al pasar por encima se enciende entero en verde.

   No hace falta `text-decoration-skip-ink`: es el comportamiento por defecto, y
   es lo que hace que el subrayado esquive la cola de la «q» de Parroquia. */
.venue-card__name a {
    color: inherit;
    text-decoration-line: underline;
    text-decoration-color: var(--color-sand);
    text-decoration-thickness: 1px;
    transition: color var(--transition), text-decoration-color var(--transition);
}

.venue-card__name a:hover {
    color: var(--color-green);
    text-decoration-color: var(--color-green);
}

.venue-card__meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-lg);
    padding-block: var(--space-md);
    border-block: 1px solid var(--color-line);
}

.venue-card__meta-item {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    text-align: left;
    font-size: var(--text-sm);
}

.venue-card__meta-item svg {
    flex-shrink: 0;
    width: 1.25rem;
    height: 1.25rem;
    stroke: var(--color-green);
    stroke-width: 1.6;
    fill: none;
}

.venue-card__meta-value {
    font-weight: 600;
    letter-spacing: 0.04em;
}

.venue-card__address {
    color: var(--color-ink-soft);
    font-size: var(--text-sm);
}

/* --- 5.6 Línea de tiempo (itinerario) ------------------------------------ */

.timeline {
    position: relative;
    list-style: none;
    padding: 0;
    margin-inline: auto;
    max-width: 56rem;

    /* Diámetro del círculo del icono. Vive aquí, y no escrito a mano en cada
       regla, porque de él dependen cuatro medidas que TIENEN que cuadrar: el
       ancho de la columna del marcador (en móvil y en escritorio), por dónde
       pasa el riel y dónde se corta para no meterse dentro del círculo. Estaban
       las cuatro puestas a mano como `3rem` y `1.5rem`; bastaba con cambiar el
       tamaño en un sitio y no en los otros para que la línea volviera a cruzar
       el icono. */
    --timeline-icono: 3rem;
    --timeline-radio: calc(var(--timeline-icono) / 2);
}

.timeline__item {
    position: relative;
    display: grid;
    grid-template-columns: var(--timeline-icono) 1fr;
    /* Centrado, para que el icono quede a la altura del centro de su tarjeta y
       no pegado al renglón de la hora. */
    align-items: center;
    gap: var(--space-md);
    padding-bottom: var(--space-2xl);

    /* Distancia del borde superior del evento al centro de su icono. El
       `padding-bottom` queda fuera del área donde se centra la fila, así que
       hay que descontarlo. */
    --timeline-centro: calc((100% - var(--space-2xl)) / 2);
}

.timeline__item:last-child {
    padding-bottom: 0;
    --timeline-centro: 50%;
}

/* Riel vertical, dibujado por tramos en cada evento y no como una barra única.
   Así nace y muere exactamente en el centro de los iconos, sea cual sea el alto
   de las tarjetas. Antes era un `::before` en la lista con `inset-block:
   0.75rem`: un margen fijo que no sabía nada de dónde caen los centros, y a
   375 px se pasaba 115 px por debajo del último icono. */
.timeline__item::before,
.timeline__item::after {
    content: '';
    position: absolute;
    inset-inline-start: var(--timeline-radio);  /* centro de la columna del marcador */
    width: 1px;
    background-color: var(--color-line);
}

/* Los dos tramos se detienen en el BORDE del círculo, no en su centro.
   Antes iban de punta a punta pasando por el medio del icono, y como son
   pseudo-elementos absolutos se pintaban por encima del marcador: la línea
   cruzaba el círculo por dentro y lo partía en dos.

   Restar y sumar el radio es lo que deja la línea besando la circunferencia por
   arriba y por abajo. El radio es exacto —no lleva holgura— porque el borde de
   1 px del marcador entra dentro de `--timeline-icono`: la hoja aplica
   `box-sizing: border-box` a todo, así que el diámetro declarado ya es el de
   fuera del trazo. */

/* Tramo de entrada: del borde superior del evento al alto del círculo. */
.timeline__item::before {
    top: 0;
    height: calc(var(--timeline-centro) - var(--timeline-radio));
}

/* Tramo de salida: de la base del círculo al borde inferior del evento. */
.timeline__item::after {
    top: calc(var(--timeline-centro) + var(--timeline-radio));
    bottom: 0;
}

/* El primero no entra de ningún sitio y el último no va a ninguno. */
.timeline__item:first-child::before,
.timeline__item:last-child::after {
    content: none;
}

.timeline__marker {
    display: grid;
    place-items: center;
    width: var(--timeline-icono);
    height: var(--timeline-icono);
    border: 1px solid var(--color-green);
    border-radius: 50%;
    background-color: var(--color-paper);
    color: var(--color-green);
}

.timeline__marker svg {
    width: 1.5rem;
    height: 1.5rem;
    stroke: currentcolor;
    stroke-width: 1.5;
    fill: none;
}

.timeline__card {
    background-color: var(--color-cream);
    background-image: var(--texture-grain);
    background-blend-mode: soft-light;
    border: 1px solid var(--color-sand);
    border-radius: var(--radius-md);
    padding: var(--space-lg);
    box-shadow: var(--relief-raised), var(--shadow-sm);
}

.timeline__time {
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.14em;
    color: var(--color-green);
}

/* La caligráfica queda para el título de la sección y nada más. Aquí va la
   serif de titulares: a 36 px la Great Vibes competía con el «Itinerario» de
   arriba y hacía que cada tarjeta pareciera un encabezado de sección. */
.timeline__title {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 600;
    line-height: 1.25;
}

.timeline__place {
    color: var(--color-ink-soft);
}

/* Safari en iOS detecta solo algunos nombres de lugar como direcciones y los
   convierte en enlaces azules del sistema, con lo que un evento salía en azul y
   el otro no. El `format-detection` del <head> lo desactiva; esto es la red por
   si algún navegador inyecta el enlace de todos modos. */
.timeline__place a {
    color: inherit;
    text-decoration: none;
}

/* A partir de tablet: alternado a dos columnas con el riel centrado. */
@media (width >= 48em) {
    /* Los tramos del riel se mudan al centro del evento, que es por donde pasa
       la columna del marcador en este reparto de tres columnas. */
    .timeline__item::before,
    .timeline__item::after {
        inset-inline-start: 50%;
        transform: translateX(-50%);
    }

    .timeline__item {
        grid-template-columns: 1fr var(--timeline-icono) 1fr;
        gap: var(--space-xl);
        align-items: center;
    }

    .timeline__marker {
        grid-column: 2;
        grid-row: 1;
    }

    .timeline__card {
        grid-column: 1;
        grid-row: 1;
        text-align: right;
    }

    /* Los elementos pares se colocan a la derecha del riel. */
    .timeline__item:nth-child(even) .timeline__card {
        grid-column: 3;
        text-align: left;
    }
}

/* --- 5.6b Panel del itinerario ------------------------------------------ */

/* El itinerario va dentro de un marco de arco: `img/background_itinerario.svg`.

   El marco se pinta con `border-image` y no como imagen de fondo. La diferencia
   importa: una imagen de fondo estirada al 100 % del panel escala los dos arcos
   con el alto del contenido, y como el panel mide de sobra 1000 px, cada arco se
   comía 150 px largos. Había que apartar el texto esa misma distancia para que
   no se subiera encima del filete, y el bloque acababa midiendo el doble de lo
   que pide su contenido.

   `border-image` recorta el dibujo en nueve piezas: las cuatro esquinas se
   dibujan tal cual, los cuatro lados se estiran solo en su dirección y el centro
   —el `fill` del recorte— rellena el hueco. Así los arcos miden lo que digan
   `border-block-width` y no crecen con la lista, y el texto empieza justo por
   dentro del filete claro.

   La textura —grano y viñeta— va dentro del SVG y no en esta hoja. Es la única
   forma de que cubra el marco ENTERO: en CSS solo se puede pintar la caja del
   elemento, que es un rectángulo, y dentro de una silueta con arcos eso deja
   fuera los dos arcos y corta la viñeta por el canto, que se ve —probado—.
   Recortada con la propia silueta, la textura llega hasta el borde del dibujo.

   Y el recorte en nueve piezas no la rompe: en cada junta, las dos piezas
   comparten la misma columna o la misma fila del original, así que los valores
   empalman exactos aunque cada pieza se estire distinto. Lo único que cambia de
   una zona a otra es el tamaño del grano —los filetes laterales se comprimen
   más que el centro—, y a esta finura no se distingue.

   Los cortes van en porcentaje del propio dibujo: 21,4 % a los lados es la
   línea vertical del SVG donde arranca el arco (x = 173 de 810), y 14,8 % y
   15,8 % arriba y abajo son las alturas donde el filete claro deja de curvarse
   (y = 150 y y = 852 de 1012). Si se reexporta el archivo con otro encuadre,
   estos cuatro números hay que volver a sacarlos. */
.itinerary-panel {
    max-width: 52rem;
    margin-inline: auto;

    border-style: solid;
    /* Color de reserva: si la imagen no carga, el marco desaparece sin dejar un
       recuadro negro alrededor del itinerario. */
    border-color: transparent;
    border-image-source: url('../img/background_itinerario.svg');
    border-image-slice: 14.8% 21.4% 15.8% 21.4% fill;
    border-image-repeat: stretch;

    /* El grosor del marco es lo que fija el tamaño de los arcos y de los filetes
       laterales. Va en `vw` para que acompañe al ancho de la pantalla, con tope
       arriba —el panel deja de crecer en 52rem— y suelo abajo, porque por debajo
       de unos 56 px el dibujo del lateral se aplasta y el filete claro se pega
       al borde. Arriba y abajo no miden igual: el arco de abajo es más profundo
       que el de arriba en el original, y respetarlo evita que el marco parezca
       torcido. */
    border-inline-width: clamp(3rem, 12vw, 9.75rem);
    border-block-width: clamp(4rem, 18vw, 8.5rem) clamp(4.25rem, 19vw, 9rem);

    /* Aire entre el filete y el contenido, solo arriba y abajo: ahí el filete
       claro pasa casi rozando el borde interior del recorte. A los lados no hay
       relleno ninguno porque el propio marco ya deja 60 px por dentro de la
       línea en escritorio y 19 px en el móvil, y cada píxel que se le quite al
       ancho se lo quita a las tarjetas, que es donde va el texto. */
    padding-block: var(--space-xl);
    padding-inline: 0;

}

/* Sobre el verde del marco, el mismo trato que en `.section--dark`: crema y sin
   grabado en seco, que ahí se convierte en un halo sucio. */
.itinerary-panel .script-title {
    color: var(--color-on-dark);
    text-shadow: none;
}

/* El riel de la línea de tiempo se dibuja en `--color-line`, un beige pensado
   para fondos claros que sobre el verde casi desaparece. Aquí sube al crema
   apagado, que es el que la hoja ya usa para lo que va encima del verde. */
.itinerary-panel .timeline__item::before,
.itinerary-panel .timeline__item::after {
    background-color: var(--color-on-dark-soft);
}

/* Dentro del marco, la línea de tiempo se queda en una columna también en
   escritorio. El reparto alternado a los dos lados del riel necesita ancho, y
   aquí no lo hay: los dos filetes laterales se llevan cerca de 300 px, así que
   las tarjetas caían por debajo de 220 y «Parroquia Nuestra Señora del Rosario
   de Fátima» salía en cinco líneas. Con una sola columna las tarjetas doblan el
   ancho, y de paso el panel se mantiene vertical, que es la proporción para la
   que está dibujado el arco. */
@media (width >= 48em) {
    .itinerary-panel .timeline__item {
        grid-template-columns: var(--timeline-icono) 1fr;
        gap: var(--space-md);
    }

    .itinerary-panel .timeline__item::before,
    .itinerary-panel .timeline__item::after {
        inset-inline-start: var(--timeline-radio);
        transform: none;
    }

    .itinerary-panel .timeline__marker {
        grid-column: 1;
    }

    .itinerary-panel .timeline__card,
    .itinerary-panel .timeline__item:nth-child(even) .timeline__card {
        grid-column: 2;
        text-align: left;
    }
}

/* En el móvil los dos filetes laterales se llevan unos 110 px. Si además el
   panel respeta el margen del contenedor, a 375 px las tarjetas se quedan en
   180 y el nombre de la parroquia baja a cinco líneas. Sacando el panel hasta el
   borde de la pantalla se recuperan esos dos márgenes; el dibujo ya trae su
   propio aire transparente alrededor, así que el marco no llega a tocar el
   canto. */
@media (width < 48em) {
    .itinerary-panel {
        margin-inline: calc(var(--container-pad) * -1);
    }

    /* Y aun así hay que apretar un poco lo de dentro: icono más pequeño y
       tarjetas con menos relleno, para que al texto le quede ancho de sobra. */
    .itinerary-panel .timeline {
        --timeline-icono: 2.5rem;
    }

    .itinerary-panel .timeline__card {
        padding: var(--space-md);
    }
}

/* --- 5.7 Código de vestimenta -------------------------------------------- */

.dress-code {
    text-align: center;
}

.dress-code__values {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    margin-bottom: var(--space-xl);
}

.dress-code__icons {
    display: flex;
    justify-content: center;
    gap: var(--space-2xl);
}

.dress-code__icon {
    width: clamp(5rem, 18vw, 8rem);
    height: auto;
}

/* --- Colores a evitar --- */

/* Separado de los iconos por un filete, para que se lea como una advertencia
   aparte y no como una coletilla del código de vestimenta.

   El aire es más corto de lo que pediría un bloque así: el aviso en versalitas
   ocupa una línea más que en redonda y la sección, que va a pantalla completa,
   se salía por abajo. */
.avoid {
    margin-top: var(--space-xl);
    padding-top: var(--space-md);
    border-top: 1px solid var(--color-line);
}

/* Se dibujan igual que los tonos sugeridos de abajo —misma rejilla, mismo
   tamaño, y el mismo `.eyebrow` encima— para que la sección se lea como dos
   listas del mismo tipo. La separación con el texto la pone ya `.palette`. */

/* El contorno es un borde sólido con color propio, no el filo translúcido de
   la paleta sugerida. Aquí casi todos los tonos son claros y el blanco puro se
   queda en 1.12:1 contra el fondo crema, o sea invisible; un filo proporcional
   al propio relleno no lo salva, porque ni al 50 % llega al 3:1 que pide la
   norma para elementos no textuales. Con `--color-ink-soft` el contorno da
   5.73:1 pase lo que pase con el relleno. */
.palette--avoid .palette__dot {
    border: 1px solid var(--color-ink-soft);
    box-shadow: var(--shadow-sm);
}

.palette__dot--blanco {
    background-color: #ffffff;
}

.palette__dot--marfil {
    background-color: #f2ebdd;
}

.palette__dot--champan {
    background-color: #e8d9b5;
}

.palette__dot--dorado {
    background-color: #d9bd7e;
}

.palette__dot--nude {
    background-color: #e3c8b4;
}

.palette__dot--beige {
    background-color: #dcc9a8;
}

.palette__dot--plata {
    background-color: #c9ced3;
}

.palette__dot--marino {
    background-color: #1f2d4d;
}

/* --- Paleta sugerida --- */

.palette__title {
    margin-top: var(--space-xl);
}

/* Rejilla y no `flex-wrap`: el corte tiene que caer siempre en el mismo sitio.
   En móvil, dos filas de cuatro; en cuanto hay ancho, los ocho en una línea. */
.palette {
    display: grid;
    grid-template-columns: repeat(4, auto);
    justify-content: center;
    gap: var(--space-md);
    margin-top: var(--space-md);
    padding: 0;
    list-style: none;
}

@media (width >= 48em) {
    .palette {
        grid-template-columns: repeat(8, auto);
    }
}

.palette__dot {
    width: clamp(2.75rem, 9vw, 3.5rem);
    aspect-ratio: 1;
    border-radius: 50%;
    /* Dos capas: un filo por dentro que recorta el círculo contra el fondo, y
       la sombra suave del resto de la página.

       El filo era blanco cuando los tonos eran casi negros, para que no se
       fundieran entre sí. Con los empolvados el problema es el contrario: la
       salvia, que es la más clara, tiene 2.22:1 de relleno contra el fondo
       crema y el círculo se desdibuja. Como los círculos llevan información
       —el color que se sugiere— les toca el 3:1 de la norma para elementos no
       textuales, y ese contraste lo aporta el contorno: al 28 % la salvia sube
       a 4.05:1, que es el caso peor de los ocho. Si aclaras algún tono, vuelve
       a comprobarlo. */
    box-shadow:
        inset 0 0 0 1px rgb(0 0 0 / 28%),
        var(--shadow-sm);
}

/* Los ocho tonos otoñales. Son contenido —lo que se sugiere a los invitados
   que se pongan—, no colores del sistema de diseño, así que viven aquí y no
   entre los tokens: cámbialos sin miedo.

   Los cinco últimos son un otoño empolvado —llevan algo de gris encima, que es
   lo que los hace suaves— y van de claro a oscuro y de cálido a frío. Los tres
   primeros, en cambio, son tonos plenos elegidos por los novios: dos granates
   profundos y un ámbar, sin gris de por medio. Abren la fila con el contraste
   más fuerte y dejan que el resto baje el tono.

   Todos guardan más de 10 de distancia perceptual entre sí y con la lista de
   colores desaconsejados: por debajo de esa cifra el ojo deja de separarlos y
   la invitación acabaría sugiriendo un color que ella misma desaconseja. Si
   cambias uno, comprueba esa distancia.

   De ahí viene el caoba: en su sitio hubo un «arena» que quedaba a 7 del beige
   desaconsejado. El caoba está a 45 del prohibido más cercano.

   La salvia es la más clara y va sobre crema, así que se sostiene por el filo
   del contorno más que por el relleno: si la tocas, comprueba que el círculo
   se sigue distinguiendo del fondo. */
.palette__dot--granate {
    background-color: #3b0b12;
}

.palette__dot--guinda {
    background-color: #5c0612;
}

.palette__dot--ambar {
    background-color: #c76e00;
}

.palette__dot--caoba {
    background-color: #7d4a3d;
}

.palette__dot--vino {
    background-color: #8a5560;
}

.palette__dot--ciruela {
    background-color: #6f5064;
}

.palette__dot--salvia {
    background-color: #9ca98b;
}

.palette__dot--oliva {
    background-color: #7e8560;
}

/* --- 5.8 Regalos --------------------------------------------------------- */

.registry__icon {
    width: 2.5rem;
    height: 2.5rem;
    stroke: var(--color-green);
    stroke-width: 1.5;
    fill: none;
}

.registry__name {
    font-size: var(--text-lg);
    font-weight: 600;
}

/* Separador grabado: la línea de arena hace el surco y la de luz, justo
   debajo, el canto. Dos píxeles que dan la misma sensación que un filete
   estampado en seco. */
.bank-details {
    max-width: 26rem;
    margin-inline: auto;
    padding-top: var(--space-xl);
    border-top: 1px solid var(--color-sand);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 75%);
    text-align: center;
}

.bank-details__icon {
    margin-inline: auto;
    margin-bottom: var(--space-sm);
}

.bank-details__list {
    display: grid;
    gap: var(--space-xs);
    margin-block: var(--space-md);
    text-align: left;
}

.bank-details__row {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--space-sm);
    padding-block: var(--space-2xs);
    border-bottom: 1px dashed var(--color-line);
}

.bank-details__label {
    color: var(--color-ink-soft);
    font-size: var(--text-sm);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.bank-details__value {
    font-family: var(--font-ui);
    font-weight: 600;
    /* Los números largos (CLABE) no deben desbordar en móvil. */
    overflow-wrap: anywhere;
}

.gift-envelope {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
    max-width: 26rem;
    margin-inline: auto;
    margin-top: var(--space-xl);
    padding-top: var(--space-xl);
    border-top: 1px solid var(--color-line);
    text-align: center;
}

/* --- 5.9 Hospedaje ------------------------------------------------------- */

/* Mismas medidas que .grid-cards para que las tarjetas de hotel se vean como
   las de la ceremonia y la recepción. */
.lodging {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
    gap: var(--space-xl);
}

.lodging__item {
    display: flex;
    flex-direction: column;
    background-color: var(--color-paper);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-md);
    /* La foto llega hasta el borde, así que el relleno vive en el cuerpo. */
    overflow: hidden;
    text-align: center;
    box-shadow: var(--shadow-sm);
}

.lodging__image {
    width: 100%;
    /* Las fotos de hotel son casi 3:2, así que con esta proporción se ven
       enteras en vez de recortadas por una altura fija. */
    aspect-ratio: 3 / 2;
    height: auto;
    object-fit: cover;
}

.lodging__body {
    display: flex;
    flex-direction: column;
    flex: 1;
    padding: var(--space-xl);
}

.lodging__name {
    font-size: var(--text-lg);
    font-weight: 600;
    margin-bottom: var(--space-2xs);
}

.lodging__detail {
    color: var(--color-ink-soft);
    font-size: var(--text-sm);
    margin-bottom: var(--space-md);
}

.lodging__body .btn {
    /* Empuja el botón al pie de la tarjeta para que todas se alineen. */
    margin-top: auto;
    align-self: center;
}

/* --- 5.10 Fotos dentro de la sección --------------------------------------- */

/* Cada sección lleva su foto de la pareja debajo del texto. Son decorativas: no
   se pueden ampliar ni llevan pie.

   Va en la misma columna que el texto y con las esquinas y la sombra de las
   tarjetas de lugares, para que se lea como una pieza más de la retícula y no
   como una banda incrustada entre dos secciones. Al estar dentro de la sección
   hereda su fondo, así que no hay cortes de color entre el texto y la foto.

   El archivo es el recorte apaisado 3:2 hecho a partir del original vertical;
   las verticales sin recortar no se usan aquí, porque debajo de un texto centrado
   una foto alta empuja el resto de la página hacia abajo sin aportar nada. */
/* Va dentro del `.container` de su sección, así que el ancho y el aire lateral
   ya se los da él: la foto mide exactamente lo que mide el texto que tiene
   encima, sea la columna estrecha (46rem) o la ancha (72rem). Por eso aquí solo
   queda la separación con lo que va justo antes. */
.section__photo {
    margin-block-start: var(--section-pad);
}

/* --- Retrato en cartela ---
   El ornamento y la foto vienen juntos dentro del SVG, así que al CSS solo le
   toca darle el ancho y reservarle el hueco con la proporción correcta. */
.cartela {
    width: min(32rem, 74vw);
    /* La del `viewBox` del archivo (810 × 1012.5), que es 4:5 exacto. Reservar
       el hueco antes de que cargue evita que el texto de abajo dé un salto. */
    aspect-ratio: 4 / 5;
    margin-block-start: var(--section-pad);
    margin-inline: auto;
}

.cartela__img {
    display: block;
    width: 100%;
    height: 100%;
}

.section__photo-img {
    display: block;
    width: 100%;
    height: auto;
    /* Los `width`/`height` del HTML son los del archivo y reservan el hueco
       antes de descargarlo; la proporción es la misma, así que `object-fit` no
       llega a recortar nada. */
    aspect-ratio: 3 / 2;
    object-fit: cover;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
}

/* --- Aparición al hacer scroll ---
   Cada bloque de una sección —el título, un párrafo, la línea de tiempo, la
   foto— entra desvanecido y subiendo un poco cuando asoma en pantalla.

   El selector es estructural a propósito, en vez de una clase que reparta el
   JavaScript: el estado inicial tiene que estar aplicado en el primer pintado.
   Puesto después, el navegador entiende el salto a `opacity: 0` como algo que
   hay que animar, y en lugar de aparecer, el bloque se ve entero y se desvanece.

   Los hijos directos del contenedor son la unidad adecuada: bloques enteros que
   entran de una pieza. Bajar al nivel de cada línea partiría los párrafos, y
   subir a la sección entera revelaría de golpe cosas que aún no se ven.

   La única excepción son los eventos del itinerario, que entran de uno en uno.
   Ahí el bloque es la lista entera —y desde que va dentro del marco, el marco
   entero—, así que las tres tarjetas aparecían a la vez y de golpe. Cada evento
   es una unidad con sentido propio, y verlos llegar en fila mientras se baja
   acompaña a la lectura del horario. El escalonado no está escrito en ninguna
   parte: lo pone el propio scroll, porque cada tarjeta cruza el punto de
   disparo cuando le toca.

   El hero se queda fuera porque no es `.section`: es la primera pantalla, ya
   tiene su propia entrada y no hay scroll que la dispare.

   El movimiento es corto y lento a propósito: en una invitación el contenido
   tiene que aparecer, no llamar la atención sobre la animación. */
.js-reveal .section > .container > *,
.js-reveal .timeline__item {
    opacity: 0;
    transform: translateY(1.5rem);
    /* `--transition` son 200 ms, correctos para un hover pero secos para una
       entrada; de ahí que aquí el tiempo vaya escrito aparte. */
    transition: opacity 800ms ease-out, transform 800ms ease-out;
    /* Avisa al navegador de lo que va a cambiar para que lo pinte en su propia
       capa y la transición no dé tirones en móviles modestos. */
    will-change: opacity, transform;
}

.js-reveal .section > .container > .is-visible,
.js-reveal .timeline__item.is-visible {
    opacity: 1;
    transform: none;
    /* Terminada la entrada, la capa extra ya no hace falta. */
    will-change: auto;
}

/* --- 5.11 Formulario RSVP ------------------------------------------------ */

/* Visita sin enlace personal: la invitación no se dirige a nadie en concreto,
   así que la sección de confirmación y sus enlaces del menú desaparecen. La
   clase la pone un script en línea del <head> (ver index.html). */
.sin-invitacion #rsvp,
.sin-invitacion .nav__item--rsvp {
    display: none;
}

/* Esta sección no lleva `.container` dentro, así que el margen lateral lo pone
   ella: sin él la tarjeta llega a los bordes de la pantalla en cualquier móvil
   (mide como mucho 34rem) y el borde redondeado queda pegado al canto. */
/* En columna y no en rejilla: como `grid`, esta sección convertía los rellenos
   de centrado de `.section--full-mobile` en celdas y los centraba en lugar de
   dejarlos crecer, con lo que la tarjeta quedaba 76 px por encima de su sitio. */
.rsvp {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding-inline: var(--container-pad);
}

.rsvp__card {
    width: 100%;
    max-width: 34rem;
    padding: clamp(var(--space-lg), 5vw, var(--space-2xl));
    background-color: var(--color-cream);
    background-image: var(--texture-grain);
    background-blend-mode: soft-light;
    border: 1px solid var(--color-sand);
    border-radius: var(--radius-md);
    box-shadow: var(--relief-raised), var(--shadow-md);
    text-align: center;
}

/* Mismo criterio que `.script-title`: caligráfica grande en tinta. */
.rsvp__title {
    font-family: var(--font-script);
    font-size: var(--text-script-lg);
    font-weight: 400;
    line-height: 1.1;
    padding-bottom: 0.1em;
    color: var(--color-ink);
}

.rsvp__intro {
    color: var(--color-ink-soft);
    margin-bottom: var(--space-xl);
}

/* Los cuatro estados de la tarjeta: cargando, enlace no válido, formulario y
   respuesta ya registrada. El JavaScript deja visible solo uno. */
.rsvp__state {
    display: block;
}

.rsvp__greeting {
    font-size: var(--text-lg);
    margin-bottom: var(--space-xs);
}

.rsvp__greeting strong {
    font-family: var(--font-script);
    font-size: var(--text-script-md);
    font-weight: 400;
    display: block;
    line-height: 1.2;
    padding-bottom: 0.08em;
    color: var(--color-green);
}

.form {
    display: grid;
    gap: var(--space-lg);
    text-align: left;
}

.field {
    display: grid;
    gap: var(--space-2xs);
}

.field__label {
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.04em;
}

/* Las ayudas son frases que el invitado tiene que leer de verdad (el cupo de su
   invitación, por ejemplo), no una etiqueta decorativa: se quedan en `sm`. El
   tamaño `xs` es solo para las versalitas del contador. */
.field__hint {
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--color-ink-soft);
}

/* Los campos van al revés que los botones: hundidos. El invitado escribe
   «dentro» del papel, y el contraste entre las dos piezas —una que sobresale y
   otra que se hunde— es lo que hace que el relieve se lea como tal en lugar de
   como un adorno repetido por toda la página. */
.field__input {
    width: 100%;
    min-height: 3rem;
    padding: var(--space-sm) var(--space-md);
    background-color: var(--color-paper);
    border: 1px solid var(--color-sand);
    border-radius: var(--radius-sm);
    box-shadow: var(--relief-sunken);
    color: var(--color-ink);
    transition: border-color var(--transition), box-shadow var(--transition);
}

.field__input::placeholder {
    color: var(--color-muted-ink);
}

/* El aro de foco se suma al hundido en lugar de sustituirlo: si lo reemplaza,
   el campo salta hacia fuera al recibir el foco y el movimiento distrae. */
.field__input:focus-visible {
    outline: none;
    border-color: var(--color-green);
    box-shadow: var(--relief-sunken), 0 0 0 3px rgb(53 63 41 / 32%);
}

.field__input:disabled {
    background-color: var(--color-muted-bg);
    color: var(--color-muted-ink);
    cursor: not-allowed;
}

/* Campo de solo lectura: el dato es importante y debe leerse bien, así que no
   se atenúa como un campo deshabilitado, pero sí se distingue del resto. */
.field__input--locked {
    background-color: var(--color-beige);
    border-style: dashed;
    font-weight: 600;
    cursor: default;
}

.field__input--locked:focus-visible {
    box-shadow: var(--relief-sunken);
}

.field__input[type='number'] {
    text-align: center;
    font-size: var(--text-lg);
    font-variant-numeric: tabular-nums;
}

/* Marca de campo con error, sincronizada con `aria-invalid`. */
.field__input[aria-invalid='true'] {
    border-color: var(--color-error);
}

/* Autocompletado accesible (patrón combobox). */
.combobox {
    position: relative;
}

.combobox__list {
    position: absolute;
    inset-inline: 0;
    top: calc(100% + 4px);
    z-index: 20;
    max-height: 15rem;
    overflow-y: auto;
    margin: 0;
    padding: 0;
    list-style: none;
    background-color: var(--color-paper);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
}

.combobox__list[hidden] {
    display: none;
}

.combobox__option {
    padding: var(--space-sm) var(--space-md);
    cursor: pointer;
    border-bottom: 1px solid var(--color-line);
}

.combobox__option:last-child {
    border-bottom: 0;
}

.combobox__option:hover,
.combobox__option[aria-selected='true'] {
    background-color: var(--color-green);
    color: var(--color-paper);
}

.combobox__empty {
    padding: var(--space-sm) var(--space-md);
    color: var(--color-ink-soft);
    font-size: var(--text-sm);
}

/* Grupo de opciones sí/no. */
.choice-group {
    border: 0;
    padding: 0;
    margin: 0;
}

.choice-group__legend {
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.04em;
    margin-bottom: var(--space-sm);
    padding: 0;
}

.choice-group__options {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: var(--space-sm);
}

/* La etiqueta completa es el objetivo clicable, no solo el círculo. */
.choice {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    min-height: 3rem;
    padding: var(--space-sm) var(--space-md);
    background-color: var(--color-paper);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: border-color var(--transition), background-color var(--transition);
}

.choice__input {
    width: 1.125rem;
    height: 1.125rem;
    accent-color: var(--color-green);
    cursor: inherit;
}

.choice:has(.choice__input:checked) {
    border-color: var(--color-green);
    background-color: var(--color-beige);
}

.choice:has(.choice__input:focus-visible) {
    outline: 3px solid var(--color-green);
    outline-offset: 2px;
}

.choice:has(.choice__input:disabled) {
    opacity: 0.55;
    cursor: not-allowed;
}

/* Mensaje de resultado del envío. Va fuera del formulario, de ahí el margen
   superior que lo separa del botón de enviar. */
.form__status {
    margin-top: var(--space-lg);
    padding: var(--space-md);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-size: var(--text-sm);
    text-align: center;
}

/* El foco se lleva aquí tras enviar; sin esto se dibujaría un anillo
   inesperado alrededor del mensaje. */
.form__status:focus {
    outline: none;
}

.form__status--error {
    background-color: var(--color-error-bg);
    border-color: var(--color-error);
    color: var(--color-error);
}

.form__status--success {
    background-color: var(--color-success-bg);
    border-color: var(--color-success);
    color: var(--color-success);
}

/* --- 5.12 Pie ------------------------------------------------------------ */

.site-footer {
    padding-block: var(--space-2xl);
    /* Mismo papel que el resto: el pie es la última mancha de verde de la
       página y sin grano se notaba que era la única lisa. */
    background-color: var(--color-dark);
    background-image: var(--texture-grain);
    background-blend-mode: soft-light;
    color: var(--color-on-dark-soft);
    text-align: center;
}

.site-footer__names {
    font-family: var(--font-script);
    font-size: var(--text-script-md);
    color: var(--color-on-dark);
    padding-bottom: 0.1em;
}

.site-footer__note {
    font-size: var(--text-sm);
}

/* --- 5.13 Densidad en móvil ----------------------------------------------
   Todas las secciones llevan `.section--full`, pero `min-height` solo estira:
   las dos que se pasaban de pantalla («¿Cuándo & dónde?» y «Regalos») hay que
   apretarlas a mano. Aquí se recorta lo que no es información —relleno, huecos
   y alto de foto—, nunca un dato: las direcciones, los horarios y los datos
   bancarios se quedan enteros.

   El alto de las fotos pasa a medirse en `svh` y no en `rem`: lo que tiene que
   caber es la pantalla, así que la foto cede su espacio al texto y no al revés.
   ------------------------------------------------------------------------- */

@media (width < 48em) {
    .grid-cards {
        gap: var(--space-md);
    }

    .cartela {
        /* La sección de la bienvenida ya va justa de alto en móvil. El retrato
           se mide contra la pantalla, como las fotos de las tarjetas, para no
           ser quien la desborde. */
        width: min(20rem, 80vw);
        margin-block-start: var(--space-lg);
    }

    .venue-card__image {
        /* De la proporción fija a un cuarto de pantalla, con suelo para que la
           foto siga leyéndose como foto y no como una franja. Aquí manda que la
           sección quepa en un pantallazo, así que se renuncia a la proporción:
           con un alto y un ancho definidos, `aspect-ratio` se ignora, pero se
           anula a mano para que quede escrito que es a propósito. */
        aspect-ratio: auto;
        height: max(7rem, min(20rem, 24svh));
    }

    .venue-card__body {
        gap: var(--space-sm);
        padding: var(--space-md);
    }

    .venue-card__meta {
        gap: var(--space-md);
        padding-block: var(--space-sm);
    }

    .registry {
        gap: var(--space-md);
        margin-top: var(--space-md);
        margin-bottom: var(--space-md);
    }

    .bank-details {
        padding-top: var(--space-md);
    }

    .bank-details__list {
        gap: var(--space-2xs);
        margin-block: var(--space-sm);
    }

    /* Vestimenta es la sección que más piezas apila —título, subtítulo, los dos
       iconos, el aviso de colores y la paleta de ocho— y al añadir el aviso se
       salía de pantalla en los móviles de 640 px de alto (716 px, un 112 %).
       Aquí se recorta relleno y tamaño de icono, nunca contenido: los seis
       colores a evitar y los ocho sugeridos siguen enteros. */
    .dress-code__values {
        margin-bottom: var(--space-md);
    }

    .dress-code__icons {
        gap: var(--space-xl);
    }

    .dress-code__icon {
        width: clamp(4rem, 16vw, 8rem);
    }

    .avoid {
        margin-top: var(--space-md);
        padding-top: var(--space-sm);
    }

    /* Dos listas de ocho, a cuatro por fila, son cuatro filas de círculos: en
       una pantalla de 640 px de alto cada píxel del diámetro cuenta. */
    .palette {
        gap: var(--space-xs);
    }

    .palette__dot {
        width: clamp(2.5rem, 9vw, 3.5rem);
    }

    .palette--avoid {
        margin-top: var(--space-sm);
    }

    .palette__title {
        margin-top: var(--space-md);
    }
}

/* ==========================================================================
   6. UTILIDADES
   ========================================================================== */

.stack > * + * {
    margin-top: var(--space-md);
}

.text-center {
    text-align: center;
}

.parents {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
    gap: var(--space-xl);
    margin-block: var(--space-2xl);
    text-align: center;
}

/* En las pantallas de 640 px de alto la bienvenida se pasaba por doce píxeles,
   y el aire sobrante estaba justo aquí. Va pegado a la regla de arriba y no en
   el bloque de densidad móvil (5.13) porque `.parents` se declara después: allí
   perdía por orden, con la misma especificidad. */
@media (width < 48em) {
    .parents {
        margin-block: var(--space-lg);
    }
}

.parents__name {
    font-size: var(--text-lg);
    font-weight: 600;
}

.blessing {
    max-width: var(--container-narrow);
    margin-inline: auto;
    padding-block: var(--space-lg);
    border-block: 1px solid var(--color-line);
    font-style: italic;
}

/* El reset deja todo a cero, y la despedida quedaba pegada al filete de abajo,
   como si formara parte del bloque. Un salto la separa y la deja respirar por
   su cuenta. Va en el selector adyacente y no en `.lead` porque el respiro
   nace de ir detrás del filete, no de ser texto destacado. */
.blessing + .lead {
    margin-top: var(--space-xl);
}

/* ==========================================================================
   7. ACCESIBILIDAD Y PREFERENCIAS DEL USUARIO
   ========================================================================== */

/* Oculta visualmente sin ocultar a los lectores de pantalla. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Enlace para saltar directo al contenido con el teclado. */
.skip-link {
    position: absolute;
    inset-inline-start: var(--space-md);
    inset-block-start: -100%;
    z-index: 100;
    padding: var(--space-sm) var(--space-md);
    background-color: var(--color-paper);
    color: var(--color-ink);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    text-decoration: none;
}

.skip-link:focus-visible {
    inset-block-start: var(--space-md);
}

/* Respeta la configuración del sistema de quien prefiere menos animación. */
@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }

    .btn:hover:not(:disabled),
    .venue-card:hover {
        transform: none;
    }

    /* El script no llega a arrancar la aparición cuando se pide menos
       animación, pero si la clase estuviera puesta por cualquier motivo, esto
       garantiza que no se quede nada invisible. */
    .js-reveal .section > .container > *,
    .js-reveal .timeline__item {
        opacity: 1;
        transform: none;
    }
}

/* Modo de alto contraste. Quien lo activa suele hacerlo porque el ruido visual
   le dificulta leer, así que aquí el grano y la trama sobran: se quitan todas
   las texturas y se dejan los fondos planos. El relieve de las sombras internas
   sí se conserva, porque no interfiere con el texto. */
@media (prefers-contrast: more) {
    :root {
        --color-ink-soft: #3c423a;
        --color-line: #8a9184;
        --texture-grain: none;
        --texture-linen: none;
    }

    .script-title {
        text-shadow: none;
    }
}

/* Modo de colores forzados (alto contraste de Windows). Ahí el sistema decide
   los colores del borde, y el marco del itinerario es un borde de 150 px: sin
   esto se convertiría en un recuadro macizo del color del sistema alrededor de
   la lista. Con `border-style: none` el grosor pasa a cero y el itinerario se
   queda como el resto de la página. */
@media (forced-colors: active) {
    .itinerary-panel {
        border-style: none;
        padding: 0;
    }
}

/* Impresión: la invitación como recordatorio en papel. */
@media print {
    .site-header,
    .nav-toggle,
    .hero__media,
    .rsvp,
    .skip-link,
    .btn {
        display: none !important;
    }

    body {
        color: #000;
        background: #fff;
        font-size: 11pt;
    }

    /* En papel, la textura y el relieve no aportan nada: el papel de verdad ya
       tiene el suyo. Y un grano al 10 % impreso se convierte en una mancha gris
       que se come la tinta de la impresora. */
    *,
    *::before,
    *::after {
        background-image: none !important;
        box-shadow: none !important;
        text-shadow: none !important;
    }

    .section {
        padding-block: 1rem;
        break-inside: avoid;
    }

    /* El marco del itinerario es decoración pura, y la regla de arriba no lo
       apaga: el `border-image` es un borde, y ella solo quita imágenes de fondo.
       En papel se quita entero, y con él se van el verde y la viñeta, así que el
       titular vuelve a tinta —en pantalla va en crema, que sobre el blanco no se
       vería— y el riel recupera el beige que usa el resto de la hoja. */
    .itinerary-panel {
        border: 0;
        padding: 0;
    }

    .itinerary-panel .script-title {
        color: #000;
    }

    .itinerary-panel .timeline__item::before,
    .itinerary-panel .timeline__item::after {
        background-color: var(--color-line);
    }
}