/* =========================================================
   PLANTILLA "LACRE" — boda, carta de amor lacrada.
   Hoja de estilo AUTOCONTENIDA (NO depende de base.css/temas.css).
   Comparte el contrato de datos del motor (motor.js): mismas clases/IDs/
   data-rol/data-modulo que el resto; cambia el copy y el envoltorio visual.

   Dirección (deploy/_work/diseno-lacre.md): el invitado recibe un SOBRE de
   papel perlado sellado en cera vino (portada .ti-portada--sobre), lo abre
   con un toque y dentro encuentra papelería fina en terracota, crema y oro
   viejo. El sistema de marcas nace del sello: mini-sellos de cera (divisor,
   nodos del timeline, dots de galería), gotas de cera (dress code) y
   filetes dorados hairline.
   Paleta: papel crema perlado / cacao / terracota / vino / oro viejo.
   Pinyon Script (display caligráfico) + Lora (texto/títulos).
   Mobile-first 360px · AA verificado · sin dependencias.
   ========================================================= */

:root {
  /* Papeles y tintas (AA verificado en la spec de diseño) */
  --papel:        #f7f1e8;  /* fondo base, crema perlado */
  --papel-2:      #efe6d9;  /* superficie alterna / arena */
  --crema:        #fdfaf4;  /* highlight de papel, cards, texto sobre vino */
  --cacao:        #4a342c;  /* tinta principal  → 10.3:1 sobre --papel (AAA) */
  --cacao-2:      #6e5548;  /* tinta secundaria →  6.1:1 (AA) */
  --cacao-3:      #83654f;  /* metadatos        →  4.75:1 (AA justo) */
  --terracota:      #a85535;  /* 4.66:1 sobre --papel: títulos y texto ≥600 */
  --terracota-prof: #8f4630;  /* hover/activo; crema encima = 6.5:1 (AA) */
  --terracota-hover:#7c3a27;  /* pressed/hover profundo del botón WA 2 */
  --terracota-lav:  rgba(168, 85, 53, .12);
  --vino:       #6f2b38;  /* 9.05:1 sobre --papel; --crema encima = 9.7:1 */
  --vino-prof:  #5a2130;  /* pie, pressed */
  --vino-claro: #8a4152;  /* SOLO decorativo: blob del colofón sobre --vino-prof */
  --oro:        #c9a45c;  /* metal claro: filetes, anillos, texto GRANDE sobre vino */
  --oro-tinta:  #8a6428;  /* oro legible sobre papel: 4.76:1 (AA texto normal) */
  --oro-filete: rgba(179, 135, 62, .38);
  --crema-85:   rgba(253, 250, 244, .85);
  --error:      #9c3535;  /* estado de error del RSVP (AA sobre --crema) */
  --sombra-calida: 0 18px 40px -18px rgba(111, 43, 56, .28);
  --sombra-card:   0 6px 22px -10px rgba(74, 52, 44, .25);

  /* --- Mesa y papel (rediseño del hero: "la carta ya estaba ahí") ---
     Estructura de capas mesa → papel → tinta. El crema de la carta lee
     ELEVADO porque debajo hay una superficie más oscura (--mesa). */
  /* La mesa se declara por CANALES para que su velo translúcido
     (.hero__overlay) NO re-teclee el RGB: literal y token pintan la misma
     superficie, así que retocar --mesa debe arrastrar su propio velo. */
  --mesa-rgb:   226, 212, 192;
  --mesa:       rgb(var(--mesa-rgb));  /* superficie bajo la carta (#e2d4c0) */
  /* Costura del final del hero: la mesa se disuelve en el papel del resto de
     la invitación en los últimos 140px. Sin esto, donde acaba el hero queda
     un corte recto de 1.74:1 a lo ancho del viewport (franja cosida) — y el
     degradado de la mesa, que oscurece hacia abajo, lo hacía máximo justo en
     el borde. Es un TOKEN porque lo consumen dos reglas (base y ≥900px). */
  --mesa-fundido: linear-gradient(to top, var(--papel), transparent 140px);
  --oro-marco:  rgba(158, 116, 46, .62); /* filete EXTERIOR: 2.22:1 sobre --crema */
  /* --oro-filete (.38 → 1.47:1) se CONSERVA para el filete INTERIOR: la
     diferencia entre ambos es la que crea el "doble filete grabado". */
  --sombra-papel:
     0 1px 0 rgba(253, 250, 244, .95) inset,   /* canto iluminado = grosor del papel */
     0 2px 5px rgba(74, 52, 44, .10),
     0 20px 38px -16px rgba(74, 52, 44, .34);
  /* ⚠️ drop-shadow() NO admite spread (4ª longitud): con `-4px` el filtro
     entero se invalida y el sello quedaría plano. Se define sin spread y con
     el alfa bajado (.42 → .38) para compensar la sombra algo mayor. */
  --sombra-sello: 0 8px 14px rgba(74, 52, 44, .38);
  /* 23vw @360 = 82.8px ⇒ por debajo del mínimo: el valor efectivo es 84px.
     El tramo fluido arranca en 366px (23vw = 84px) y topa en 452px (= 104px). */
  --sello-hero: clamp(84px, 23vw, 104px); /* 84px ≤365 · fluido · 104px ≥452 */
  --marco-in:   var(--esp-2); /* 8px: inset del filete interior y las esquineras (en escala 4/8) */
  --carta-max:  420px;    /* ancho de la carta en móvil */
  /* FORMATO DE LA HOJA. La papelería de boda es VERTICAL: 5×7" (.714) es el
     tamaño real de una invitación impresa y coincide casi al milímetro con
     la serie A (A6/A5 = .707). Sin esto la altura la dictaba el CONTENIDO y
     la carta salía cuadrada (358×358 @390) o apaisada (560×481 en
     escritorio): un formato que NO existe como invitación, y que hacía leer
     la pieza como "un recuadro con el texto centrado" en vez de como papel.
     Se declara en DOS formas porque se consume en dos sitios: la razón para
     `aspect-ratio` y el factor numérico para el calc() del tope de ancho. */
  --carta-ratio: 5 / 7;
  --carta-w-h:   .714;    /* ancho ÷ alto: el MISMO 5/7, usable en calc() */
  /* Alto del hero que NO es carta (padding superior + inferior + holgura del
     chevron). Es lo que se descuenta de 100svh para calcular cuánta hoja
     cabe: así en pantallas bajas la carta ESTRECHA (conservando el formato)
     en vez de desbordar el viewport y empujar el chevron fuera del pliegue. */
  --hero-pad-top: max(var(--esp-5), calc(var(--sello-hero) / 2 + var(--esp-2)));
  --carta-reserva: calc(var(--hero-pad-top) + var(--esp-6) + var(--esp-4));

  /* Tipografías (las enlaza el render en <head>; aquí solo se declaran) */
  --f-display: "Pinyon Script", "Segoe Script", cursive;
  --f-texto:   "Lora", Georgia, "Times New Roman", serif;

  /* Escala de espaciado */
  --esp-1:4px; --esp-2:8px; --esp-3:16px; --esp-4:24px;
  --esp-5:40px; --esp-6:64px; --esp-7:96px; --esp-8:128px;

  --radio: 6px;
  --blob: 47% 53% 50% 50% / 52% 48% 55% 45%;  /* forma orgánica de la cera */
  --ls:   .3em;            /* tracking de versalitas (títulos) */
  --ls-2: .18em;           /* tracking medio (etiquetas) */
  --transicion: .35s cubic-bezier(.22, 1, .36, 1);
  --ancho: 640px;

  /* --sec-*: fichas dinámicas + saludo (secciones.css, SPEC 0006) */
  --sec-bg:            var(--crema);
  --sec-borde:         var(--oro-filete);
  --sec-radio:         6px;
  --sec-titulo-font:   var(--f-texto);
  --sec-titulo-color:  var(--terracota);
  --sec-texto-color:   var(--cacao-2);
  --sec-acento:        var(--vino);
  --sec-acento-bg:     var(--vino);      /* saludo: crema sobre vino 9.7:1 */
  --sec-acento-tinta:  var(--crema);
  --sec-acento-boton-bg:    var(--crema);
  --sec-acento-boton-tinta: var(--vino);

  /* --portada-*: capa de apertura (secciones.css, SPEC 0008) */
  --portada-bg:          var(--papel);
  --portada-tinta:       var(--cacao);
  --portada-acento:      rgba(179, 135, 62, .55);
  --portada-titulo-font: var(--f-display);
  --portada-radio:       999px;
  --portada-cta-bg:      var(--vino);
  --portada-cta-tinta:   var(--crema);

  /* --gal-*: modos de galería (secciones.css, SPEC 0013) */
  --gal-pantalla-bg: var(--cacao);           /* #4a342c — letterbox cálido, no gris */
  --gal-ctrl-bg:     rgba(111, 43, 56, .82); /* vino translúcido */
  --gal-ctrl-tinta:  var(--crema);           /* #fdfaf4 → 10.4:1 sobre el vino compuesto */
  --gal-gap:         var(--esp-2);           /* 8px: escala de lacre (el fallback 10px es off-scale) */
  --gal-radio:       var(--radio);           /* 6px explícito (hoy llega por --sec-radio) */
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }

body {
  font-family: var(--f-texto);
  font-size: 16px; line-height: 1.85;
  color: var(--cacao-2);
  background-color: var(--papel);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Lienzo: viñetas cálidas de terracota en las esquinas del papel, fijas */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background:
    radial-gradient(58% 48% at 6% 2%, var(--terracota-lav), transparent 62%),
    radial-gradient(52% 42% at 96% 12%, rgba(111, 43, 56, .08), transparent 60%),
    radial-gradient(58% 48% at 94% 98%, var(--terracota-lav), transparent 62%),
    radial-gradient(50% 42% at 4% 90%, rgba(179, 135, 62, .10), transparent 60%),
    var(--papel);
}
/* Grano de papel perlado (textura fractal muy tenue) */
body::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  opacity: .45; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}

img { max-width: 100%; display: block; }
iframe { border: 0; }
/* Los controles de formulario y botones NO heredan la fuente por defecto
   (caerían en Arial del user-agent): se fuerza la del cuerpo. */
button, input, select, textarea { font-family: inherit; }

/* Foco visible universal; sobre superficies vino el outline pasa a --oro */
a:focus-visible, button:focus-visible, input:focus-visible,
textarea:focus-visible, [tabindex]:focus-visible {
  outline: 3px solid var(--oro-tinta);
  outline-offset: 3px;
  border-radius: var(--radio);
}
.pie a:focus-visible, .musica:focus-visible,
.lightbox button:focus-visible, .boton:focus-visible {
  outline-color: var(--oro);
}

/* =========================================================
   REVEAL POR SCROLL — la papelería se despliega
   (transición afinada + escalonado ≤150ms en hijos directos)
   ========================================================= */
.fade-up {
  opacity: 0; transform: translateY(22px);
  transition: opacity .9s ease, transform .9s cubic-bezier(.22, 1, .36, 1);
}
.fade-up.is-visible { opacity: 1; transform: none; }
.fade-up > * {
  opacity: 0; transform: translateY(12px);
  transition: opacity .9s ease, transform .9s cubic-bezier(.22, 1, .36, 1);
}
.fade-up.is-visible > * { opacity: 1; transform: none; }
.fade-up.is-visible > *:nth-child(2) { transition-delay: .05s; }
.fade-up.is-visible > *:nth-child(3) { transition-delay: .1s; }
.fade-up.is-visible > *:nth-child(n+4) { transition-delay: .15s; }

/* =========================================================
   HERO — "la carta ya estaba ahí"
   Estructura de capas: MESA (z0/z1) → PAPEL (z2, .hero__contenido)
   → TINTA. El sobre se levanta y descubre, sobre una mesa, una hoja
   de papel crema con volumen real: filete dorado doble, esquineras y
   el mismo sello de cera aún pegado, a caballo del borde superior.
   El texto vive SIEMPRE sobre la carta opaca ⇒ nunca toca la foto.
   ========================================================= */
.hero {
  position: relative; isolation: isolate;
  min-height: 100svh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
  /* Canaleta de mesa (--esp-4, el MISMO canal lateral que usa <main>: así la
     carta y los tarjetones de abajo comparten eje vertical y el scroll no
     bambolea 8px). El bottom mayor sube la carta al centro
     óptico (~46%). NUNCA full-bleed: la canaleta sostiene el concepto.
     ⚠️ El padding-top NO puede ser menor que la mitad del sello: el sello va
     a caballo del borde superior de la carta (translate -50%) y .hero tiene
     overflow:hidden, así que en cuanto la carta alcanza la caja de padding
     —viewport corto, típicamente móvil en horizontal— la mitad que sobresale
     se recortaría. Con esp-5 (40px) frente a 42px de media altura @360 y
     52px a partir de 600px, el recorte era real (2px y 12px). El max() lo
     resuelve de forma AUTO-AJUSTABLE: sigue a --sello-hero en cada
     breakpoint, sin media queries de altura, y conserva la asimetría
     top<bottom que sube la carta al centro óptico. Vive en el token
     --hero-pad-top porque el tope de ancho de la carta también lo descuenta
     de 100svh (una sola fuente para los dos usos). */
  padding: var(--hero-pad-top) var(--esp-4) var(--esp-6);
  background-color: var(--mesa);
  background-size: cover; background-position: center;
  /* Gradiente de mesa. ⚠️ El motor PISA background-image por estilo inline
     cuando hay CFG.fotos.hero: entonces la FOTO es la mesa (deseado). Sin
     foto el motor asigna "" y esta declaración vuelve a aplicar. */
  background-image:
    radial-gradient(120% 76% at 50% 0%, rgba(253, 250, 244, .20), transparent 60%),
    /* El oscurecido de la mesa CULMINA AL 64% y se afloja hacia abajo, en
       vez de ser máximo justo en la costura: así el fundido al papel (ver
       .hero::after) tiene menos salto que absorber. */
    linear-gradient(rgba(74, 52, 44, .05) 0, rgba(74, 52, 44, .13) 64%, rgba(74, 52, 44, .04) 100%);
  overflow: hidden;
}
/* Velo sobre la mesa/foto: baja el ruido de la fotografía y la unifica con
   la paleta. z-index 0 (por DEBAJO del grano y la luz) a propósito: así el
   backdrop-filter sepia trata la FOTO y no el grano de la propia hoja.
   Sin soporte de backdrop-filter se pierde el sepia, nunca el velo. */
.hero__overlay {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(rgba(var(--mesa-rgb), .55), rgba(var(--mesa-rgb), .30));
  -webkit-backdrop-filter: sepia(.35) saturate(.72) blur(2px);
  backdrop-filter: sepia(.35) saturate(.72) blur(2px);
}
/* Grano de la mesa + viñeta (antes eran el marco: LIBERADOS por el rediseño)
   ⚠️ JERARQUÍA TÁCTIL: la mesa va comparativamente LISA y el PAPEL lleva el
   grano (ver .hero__contenido). Estaba al revés —fibra bajo la carta, crema
   plano encima— y eso es justo lo que delata "un div imitando papel": la
   sombra daba volumen, pero ninguna capa daba MATERIA a la hoja. Se baja
   SOLO la opacidad del ruido (0.06 → 0.03), nunca la de la viñeta, que es
   estructura (redondea la mesa y sostiene el fundido de la costura). Grano
   efectivo resultante: mesa 0.03 × .5 = .015 · papel .028 ⇒ el papel lee
   casi 2× más fibroso que la superficie que lo sostiene. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  opacity: .5; mix-blend-mode: multiply;
  background-image:
    radial-gradient(88% 70% at 50% 50%, transparent 52%, rgba(74, 52, 44, .22)),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
}
/* Luz cenital (la carta recibe luz desde arriba) + FUNDIDO DE LA COSTURA:
   la mesa se disuelve en el --papel del resto de la invitación, así que el
   documento se lee como UNA sola superficie continua y no como dos hojas
   cosidas. Va en el pseudo-elemento, NO en background-image de .hero, a
   propósito: el motor pisa background-image por estilo inline cuando hay
   foto de fondo, y el fundido debe sobrevivir también con foto. */
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    radial-gradient(60% 45% at 50% -10%, rgba(253, 250, 244, .5), transparent 60%),
    var(--mesa-fundido);
}

/* ---- LA CARTA (una HOJA de papel: formato vertical + materia) ---- */
.hero__contenido {
  position: relative; z-index: 2;
  display: flex; flex-direction: column; align-items: center;
  /* La tinta se centra en la hoja y SOBRA papel arriba y abajo: eso es lo
     que convierte el bloque en una hoja impresa (antes el texto llenaba la
     caja de borde a borde). De paso consume el aire muerto de la mesa. */
  justify-content: center;
  width: 100%;
  /* TOPE DE ANCHO DOBLE. (1) --carta-max, el ancho de diseño; (2) el ancho
     que permite la ALTURA disponible: con formato fijo, alto = ancho ÷ .714,
     así que en un viewport bajo (escritorio 900px de alto, tablet apaisada)
     una carta a 560px pediría 784px de alto y empujaría el chevron fuera del
     pliegue. Estrechar conserva el FORMATO —que es el punto— en vez de
     romperlo con un max-height, que aplastaría la hoja a apaisada otra vez.
     El max() interior es el SUELO: por debajo de ~360px la hoja dejaría de
     ser una carta para volverse un marcapáginas, y la columna de tinta se
     quedaría sin sitio para el display. Un max-width mayor que el ancho
     disponible es inocuo (manda el width:100%) ⇒ el suelo NUNCA provoca
     desbordamiento horizontal en pantallas estrechas. */
  max-width: min(
    var(--carta-max),
    max(360px, calc((100svh - var(--carta-reserva)) * var(--carta-w-h)))
  );
  aspect-ratio: var(--carta-ratio);
  /* Válvula dura del pliegue: en la franja corta-y-ancha donde el suelo de
     360px manda (portátiles de ~640px de alto), la hoja cedería unos píxeles
     de formato antes que empujar el chevron fuera de pantalla. Muerde poco
     (.73 en vez de .714) y solo ahí; en el resto ni se activa. `min-height`
     le gana a `max-height` por regla de cascada ⇒ la tinta nunca se recorta. */
  max-height: calc(100svh - var(--carta-reserva));
  /* Válvula de escape (NO declarada a propósito): si la tinta no cabe en el
     formato —viewport muy estrecho, un nombre largo que envuelve—, la hoja
     CRECE en vez de recortar, porque .hero__contenido es flex item de un
     contenedor en columna y su `min-height: auto` es el mínimo AUTOMÁTICO
     basado en contenido (el mismo mecanismo del gotcha "min-height:auto anula
     max-height:100%", aquí jugando a favor). ⚠️ No pongas `min-height: 0` ni
     `min-height: min-content` aquí: el 0 deja que la tinta desborde la hoja, y
     min-content puede transferirse a través del aspect-ratio y devolver la
     altura del formato en vez de la del contenido — o sea, anular la válvula. */
  /* padding-top holgado: despeja el sello que va a caballo del borde. Sigue
     siendo el suelo de seguridad en el caso degradado de arriba, donde ya no
     hay papel sobrante que separe la tinta del sello. */
  padding: var(--esp-6) var(--esp-4) var(--esp-5);
  /* MATERIA DEL PAPEL (capas, de arriba abajo):
     1. grano fractal FINÍSIMO — el mismo patrón de la hoja, a 0.028 de
        opacidad y frecuencia más alta (1.1) que el de la mesa: fibra, no
        ruido. Se compone normal (no multiply): la turbulencia en escala de
        grises aporta motas claras Y oscuras, que es como se comporta la
        fibra real. A 2× rasteriza más fino todavía.
     2. iluminación diagonal — la hoja deja de ser UN valor plano: luz por el
        borde superior izquierdo (blanco puro sobre crema, sube el contraste)
        y una caída cálida mínima en la esquina inferior derecha.
     ⚠️ CONTRASTE: el oscurecido topa en .03 y solo en el último 38% de la
     diagonal (zona de filete, sin tinta). Peor caso local del papel ≈ 9.3:1
     contra --vino (nominal 9.7:1) y ≈ 6.4:1 contra --cacao-2; en la columna
     de texto la desviación es < .01 de alfa ⇒ los ratios se conservan. */
  background-color: var(--crema);
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)' opacity='0.028'/%3E%3C/svg%3E"),
    linear-gradient(152deg,
      rgba(255, 255, 255, .5) 0%,
      rgba(255, 255, 255, 0) 34%,
      rgba(122, 90, 64, 0) 62%,
      rgba(122, 90, 64, .03) 100%);
  border: 1px solid var(--oro-marco);   /* filete EXTERIOR */
  border-radius: 2px;                   /* el papel no tiene esquinas redondeadas */
  box-shadow: var(--sombra-papel);
}
/* Filete INTERIOR: más tenue que el exterior a propósito — el contraste
   entre ambos es lo que se lee como "doble filete grabado" (dos líneas
   iguales se leían como una sola borrosa). */
.hero__contenido::before {
  content: ""; position: absolute; inset: var(--marco-in); pointer-events: none;
  border: 1px solid var(--oro-filete);
}
/* Esquineras: solo los ángulos (4 brazos horizontales de 14×1px + 4
   verticales de 1×14px anclados a las esquinas del filete interior). */
.hero__contenido::after {
  content: ""; position: absolute; inset: var(--marco-in); pointer-events: none;
  background:
    linear-gradient(var(--oro-tinta), var(--oro-tinta)) left  top    / 14px 1px no-repeat,
    linear-gradient(var(--oro-tinta), var(--oro-tinta)) right top    / 14px 1px no-repeat,
    linear-gradient(var(--oro-tinta), var(--oro-tinta)) left  bottom / 14px 1px no-repeat,
    linear-gradient(var(--oro-tinta), var(--oro-tinta)) right bottom / 14px 1px no-repeat,
    linear-gradient(var(--oro-tinta), var(--oro-tinta)) left  top    / 1px 14px no-repeat,
    linear-gradient(var(--oro-tinta), var(--oro-tinta)) right top    / 1px 14px no-repeat,
    linear-gradient(var(--oro-tinta), var(--oro-tinta)) left  bottom / 1px 14px no-repeat,
    linear-gradient(var(--oro-tinta), var(--oro-tinta)) right bottom / 1px 14px no-repeat;
}

/* ---- EL SELLO, a caballo del borde superior de la carta ----
   Rompe el marco: es lo que distingue "papelería real" de "recuadro con
   contenido centrado". Estado por defecto (S3) = cera PRENSADA: blob vino,
   labio inferior en sombra, anillo dorado y monograma Pinyon (texto grande
   sobre vino = AA large). El sello NO levita aquí (el de la portada sí):
   dos objetos levitando en dos pantallas seguidas abarata. */
.hero__sello {
  position: absolute; top: 0; left: 50%; z-index: 3;
  transform: translate(-50%, -50%);
  display: grid; place-items: center;
  /* Pista acotada: sin minmax(0,1fr) la fila/columna crece hasta la altura
     INTRÍNSECA del hijo (min-height auto del grid item) y el medallón deja de
     ser una caja fija. Cubre a CUALQUIER hijo —incluido .hero__sello-foto,
     que dimensiona la capa compartida— sin depender de que cada uno se
     acuerde de declarar min-*: 0. */
  grid-template-rows: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
  width: var(--sello-hero); height: var(--sello-hero);
  border-radius: var(--blob);
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(1.5rem, 6vw, 1.9rem); color: var(--oro); letter-spacing: .02em;
  background:
    radial-gradient(circle at 50% 118%, rgba(0, 0, 0, .28), transparent 46%),
    radial-gradient(circle at 38% 32%, rgba(253, 250, 244, .18), transparent 55%),
    var(--vino);
  box-shadow:
    inset 0 0 0 2px rgba(201, 164, 92, .5),
    inset 0 -3px 6px rgba(0, 0, 0, .22);
  filter: drop-shadow(var(--sombra-sello));
}
.hero__sello:empty { display: none; }
/* S1 · SELLO REAL (motor-created: <img class="hero__sello-cera"> dentro de
   [data-rol="hero-sello"], SPEC 0008 §2.F.2). Es un PNG/WebP con alfa:
   CUALQUIER fondo, anillo o recorte lo delata ⇒ se apaga todo el blob y
   queda solo el drop-shadow heredado de la regla base. */
.hero__sello:has(.hero__sello-cera) {
  background: none; box-shadow: none; border-radius: 0;
}
.hero__sello-cera {
  width: 100%; height: 100%;
  object-fit: contain;  /* un sello se CONTIENE entero, nunca se recorta */
  /* ⚠️ min-*: 0 NO es decorativo, es lo que hace que el 100% signifique algo.
     El medallón es display:grid y el <img> es grid item, cuyo min-height
     AUTOMÁTICO es su altura intrínseca: con un sello vertical (p. ej.
     1200×1500) la FILA crece a 124px, el `height:100%` se resuelve contra la
     fila crecida —no contra el medallón de 99px— y object-fit no tiene nada
     que contener. El sello desbordaba hacia abajo y tapaba "¡Nos casamos!".
     Verificado en vivo: min-height:0 corrige; align-self:stretch y
     height:auto NO. Se declaran AMBOS ejes porque un sello apaisado rompe
     igual por el ancho. Esta regla es más específica que la contención
     compartida de secciones.css, así que el arreglo tiene que estar TAMBIÉN
     aquí: si solo estuviera en la capa compartida, esta la volvería a pisar. */
  min-width: 0; min-height: 0;
}
/* S2 · RETRO-COMPAT únicamente. Por decisión de producto la foto de la
   pareja YA NO va al medallón (va al fondo del hero, "la mesa"); esta regla
   solo sostiene invitaciones viejas sin sello subido. */
.hero__sello:has(.hero__sello-foto) {
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--oro), var(--sombra-card);
  background: var(--crema);
}

/* ---- Tinta sobre la carta (ritmo cerrado: es papelería, no una landing) ---- */
.hero__intro {
  text-transform: uppercase; letter-spacing: var(--ls);
  font-size: .72rem; font-weight: 500;
  color: var(--cacao-2); margin-bottom: var(--esp-2); /* antetítulo: pega al nombre */
}
.hero__nombres {
  font-family: var(--f-display); font-weight: 400;
  /* Clamp reducido: la carta come ~48px de ancho ⇒ columna de ~280px @360,
     donde un nombre largo en Pinyon a 50px desborda.
     El `min(12.5vw, 9vh)` es consecuencia del formato de hoja: como el ancho
     de la carta ahora lo puede dictar la ALTURA disponible (ver
     .hero__contenido), en una pantalla ancha pero baja —portátil de 1366×640,
     tablet apaisada— la columna se estrecha mientras 12.5vw seguía pidiendo
     el tope de 4.6rem, y el nombre desbordaba. Al escalar también por la
     dimensión escasa, el display sigue a la carta. En vertical 9vh siempre es
     mayor (relación de aspecto > 1.39) ⇒ móvil INTACTO. */
  font-size: clamp(2.7rem, min(12.5vw, 9vh), 4.6rem); line-height: 1.12;
  color: var(--vino);
  /* Letterpress: Pinyon deja de flotar y queda hundido en la fibra del papel.
     (Contorno con text-shadow; -webkit-text-stroke deja letras HUECAS.) */
  text-shadow:
    0 1px 0 rgba(253, 250, 244, .95),
    0 2px 3px rgba(111, 43, 56, .14);
}
.hero__nombres > span { display: block; }
.hero__amp {
  font-family: var(--f-texto); font-style: italic; font-weight: 400;
  font-size: .38em; color: var(--oro-tinta);
  margin: .1em 0; line-height: 1;
}
.hero__fecha {
  display: inline-flex; align-items: center; gap: var(--esp-3);
  margin-top: var(--esp-4);
  font-style: italic; font-size: 1.05rem;
  color: var(--cacao-2);
}
.hero__fecha::before, .hero__fecha::after {
  content: ""; width: 32px; height: 1px; background: var(--oro-marco);
}
/* Pie de la carta: sede del evento. Lo rellena el motor reusando
   ceremonia.lugar / recepcion.lugar (SPEC 0008 §2.F) y lo deja [hidden]
   si no hay texto — por eso aquí no hace falta ningún :empty. */
.hero__lugar {
  margin-top: var(--esp-3);
  text-transform: uppercase; letter-spacing: var(--ls-2);
  font-size: .7rem; font-weight: 500;
  color: var(--cacao-2);
}

/* ---- El chevron vive EN LA MESA, no en la carta ----
   Separa la pieza de papelería del control de interfaz (por eso el partial
   lo saca de .hero__contenido y lo deja como hermano dentro de .hero). */
.hero__scroll {
  position: absolute; bottom: var(--esp-4); left: 50%; z-index: 2;
  transform: translateX(-50%);
  display: grid; place-items: center;
  width: 48px; height: 48px; border-radius: 50%;
  border: 1px solid var(--oro-marco);
  color: var(--vino); text-decoration: none;
  font-size: 1.4rem; line-height: 1;
  transition: border-color var(--transicion), color var(--transicion), transform var(--transicion);
}
/* El hover COMPONE el centrado (un translateY suelto lo descolocaría) */
@media (hover: hover) {
  .hero__scroll:hover {
    border-color: var(--vino); color: var(--terracota-prof);
    transform: translate(-50%, 2px);
  }
}
/* Chevron dibujado en CSS (el glifo ⌄ del partial se anula con font-size:0:
   como "v" tipográfica se veía débil — hallazgo menor 3 del revisor UX). */
.hero__flecha {
  display: inline-block; font-size: 0;
  width: 12px; height: 12px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  animation: lacre-rebote 2.4s ease-in-out infinite;
}
/* El rebote CONSERVA el rotate del chevron (un translateY suelto lo pisaría) */
@keyframes lacre-rebote {
  0%, 100% { transform: translateY(-2px) rotate(45deg); }
  50%      { transform: translateY(4px) rotate(45deg); }
}

/* =========================================================
   PARALLAX DE SALIDA — repuntado al rediseño
   .hero::before/::after ya NO son el marco (ahora son mesa y luz), así que
   el parallax pasa a la CARTA y al SELLO: la carta se rezaga, el sello va
   más rápido y eso lee como profundidad.
   ⚠️ REGLAS DURAS (gotcha "título transparente", liga-heroes v0.14.5):
   solo `transform`, JAMÁS `opacity` dentro de una scroll-driven animation,
   y acotado a la fase `exit` (arranca al SALIR el hero, no al cargar).
   ========================================================= */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero__contenido, .hero__sello {
      animation: lacre-hero-exit linear both;
      animation-timeline: view();
      animation-range: exit 0% exit 100%;
    }
    .hero__sello { animation-name: lacre-sello-exit; }
    @keyframes lacre-hero-exit  { to { transform: translateY(-30px); } }
    /* El sello carga el translate(-50%,-50%) del straddle: los keyframes lo
       COMPONEN (un translateY suelto lo pisaría y lo descolocaría). */
    @keyframes lacre-sello-exit { to { transform: translate(-50%, calc(-50% - 52px)); } }
  }
}

/* =========================================================
   ENTRADA AL ABRIRSE LA PORTADA (SPEC 0008 §2.G)
   Señal 100% declarativa, sin una línea de JS: la capa .ti-portada es hijo
   DIRECTO de <body> (motor.js la inserta con body.appendChild), así que
   `body:not(:has(.ti-portada:not(.ti-portada--abriendo))))` casa cuando la
   portada no existe todavía, cuando se está abriendo, cuando ya se removió
   y cuando la plantilla no lleva portada. Es animación POR TIEMPO (one-shot),
   nunca scroll-timeline ⇒ el gotcha del "título transparente" no aplica.

   ⚠️ REGLA DURA §2.G.2: `animation-fill-mode: backwards`, NUNCA
   `forwards`/`both`, y NUNCA un `opacity: 0` estático en el estado base. Con
   backwards, si el navegador no soporta :has() o el usuario pide menos
   movimiento, el elemento queda en su estado natural = VISIBLE.
   Fail-safe = visible, siempre.

   LA CARTA NO SE ANIMA: ya estaba ahí bajo el sobre. Es el punto conceptual.
   ========================================================= */
@media (prefers-reduced-motion: no-preference) {
  /* 1 · el sello se ESTAMPA. Se anima el HIJO (<img>), no el <span>: el span
     carga el translate del straddle y el parallax de salida — separar entrada
     (hijo) de salida (span) evita el error de listas mixtas. */
  body:not(:has(.ti-portada:not(.ti-portada--abriendo))) .hero__sello-cera {
    animation: lacre-entra-sello .42s cubic-bezier(.34, 1.3, .5, 1) .38s backwards;
  }
  /* S3 · el monograma es TEXTO suelto (el motor lo pinta por textContent):
     no hay nodo hijo que animar, así que se anima el propio <span>. Como el
     span ya tiene comprometido el `transform` (straddle + parallax de
     salida), la entrada del blob es SOLO opacidad: nada de scale/rotate, o
     se pisarían entre sí.
     ⚠️ Consecuencia ASUMIDA: esta regla es más específica que la del
     parallax, así que en S3 el sello pierde su translateY(-52px) propio y
     sale con la carta (-30px). Se prefiere eso a una lista mixta
     animation-name/timeline sobre el mismo nodo, que es justo el patrón
     frágil que ya falló en el idle de la portada. En S1 —el caso real, con
     sello subido— entrada (hijo) y salida (span) van en nodos distintos y
     ambas se conservan íntegras. */
  body:not(:has(.ti-portada:not(.ti-portada--abriendo)))
    .hero__sello:not(:has(.hero__sello-cera)):not(:has(.hero__sello-foto)) {
    animation: lacre-entra-fundido .42s cubic-bezier(.34, 1.3, .5, 1) .38s backwards;
  }
  /* 2 · antetítulo */
  body:not(:has(.ti-portada:not(.ti-portada--abriendo))) .hero__intro {
    animation: lacre-entra-6 .4s cubic-bezier(.22, 1, .36, 1) .62s backwards;
  }
  /* 3 · nombres */
  body:not(:has(.ti-portada:not(.ti-portada--abriendo))) .hero__nombres {
    animation: lacre-entra-10 .6s cubic-bezier(.22, 1, .36, 1) .72s backwards;
  }
  /* 4 · fecha + sus filetes flanqueantes, que se trazan de dentro afuera */
  body:not(:has(.ti-portada:not(.ti-portada--abriendo))) .hero__fecha {
    animation: lacre-entra-6 .46s cubic-bezier(.22, 1, .36, 1) .9s backwards;
  }
  body:not(:has(.ti-portada:not(.ti-portada--abriendo))) .hero__fecha::before,
  body:not(:has(.ti-portada:not(.ti-portada--abriendo))) .hero__fecha::after {
    animation: lacre-entra-filete .46s cubic-bezier(.22, 1, .36, 1) .9s backwards;
  }
  /* 5 · sede */
  body:not(:has(.ti-portada:not(.ti-portada--abriendo))) .hero__lugar {
    animation: lacre-entra-6 .4s cubic-bezier(.22, 1, .36, 1) 1.08s backwards;
  }
  /* 6 · el chevron, ya sobre la mesa. Los keyframes COMPONEN su
     translateX(-50%) de centrado. */
  body:not(:has(.ti-portada:not(.ti-portada--abriendo))) .hero__scroll {
    animation: lacre-entra-scroll .38s cubic-bezier(.22, 1, .36, 1) 1.18s backwards;
  }

  @keyframes lacre-entra-sello {
    from { opacity: 0; transform: scale(1.18) rotate(-6deg); }
  }
  @keyframes lacre-entra-fundido { from { opacity: 0; } }
  @keyframes lacre-entra-6  { from { opacity: 0; transform: translateY(6px); } }
  @keyframes lacre-entra-10 { from { opacity: 0; transform: translateY(10px); } }
  @keyframes lacre-entra-filete { from { opacity: 0; transform: scaleX(0); } }
  @keyframes lacre-entra-scroll {
    from { opacity: 0; transform: translate(-50%, 10px); }
  }
}

/* =========================================================
   ESTRUCTURA — la columna de papelería
   ========================================================= */
main { padding: var(--esp-6) var(--esp-4) var(--esp-6); }
.bienvenida, .contador, .historia, .evento, .itinerario,
.dress-code, .mesa-regalos, .hospedaje, .rsvp {
  max-width: var(--ancho); margin: 0 auto var(--esp-8);
  text-align: center;
}
/* La última sección no suma su margen al padding de main (evita ~250px
   muertos antes del pie — hallazgo menor 7 del revisor UX). Solo
   margin-bottom: el margin-inline:auto del centrado se conserva. */
main > :last-child { margin-bottom: 0; }

/* Divisor: MINI-SELLO de cera (blob vino + punto oro) flanqueado por
   dos filetes hairline dorados de 64px. CSS puro. */
.divisor {
  position: relative; display: block;
  width: 200px; height: 30px;
  margin: 0 auto var(--esp-5); font-size: 0;
  background:
    linear-gradient(var(--oro-filete), var(--oro-filete)) left center / 64px 1px no-repeat,
    linear-gradient(var(--oro-filete), var(--oro-filete)) right center / 64px 1px no-repeat;
}
.divisor::before {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 30px; height: 30px;
  transform: translate(-50%, -50%);
  border-radius: var(--blob);
  background:
    radial-gradient(circle at 50% 46%, var(--oro) 0 3.5px, transparent 4.5px),
    radial-gradient(circle at 36% 30%, rgba(253, 250, 244, .16), transparent 55%),
    var(--vino);
  box-shadow: 0 3px 8px rgba(74, 52, 44, .25);
}

/* Títulos de sección: versalitas Lora terracota */
.seccion__titulo, .bienvenida__titulo {
  font-family: var(--f-texto); font-weight: 500;
  text-transform: uppercase; letter-spacing: var(--ls);
  font-size: clamp(1.05rem, 4.5vw, 1.3rem); line-height: 1.5;
  color: var(--terracota);
  margin-bottom: var(--esp-4);
}

/* Bienvenida: carta con letra capital Pinyon */
.bienvenida__texto {
  max-width: 34ch; margin: 0 auto;
  font-size: 1.05rem; line-height: 1.85; color: var(--cacao-2);
  text-align: center;
}
.bienvenida__texto::first-letter {
  font-family: var(--f-display);
  font-size: 2.6em; line-height: .8;
  color: var(--terracota);
  float: left; margin: .08em .12em 0 0;
}

/* =========================================================
   CUENTA REGRESIVA — 4 estampillas postales
   ========================================================= */
.contador__lista {
  list-style: none;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
}
.contador__item {
  padding: var(--esp-3) var(--esp-1);
  background: var(--crema);
  border: 1px solid var(--oro-filete);
  outline: 1px solid var(--oro-filete); outline-offset: -6px; /* doble borde de timbre */
  border-radius: 3px;
  box-shadow: var(--sombra-card);
}
.contador__num {
  display: block; font-weight: 600;
  font-size: clamp(1.6rem, 7vw, 2.4rem); line-height: 1.15;
  color: var(--vino);
  font-variant-numeric: tabular-nums lining-nums;
}
.contador__label {
  display: block; margin-top: var(--esp-1);
  color: var(--cacao-3); letter-spacing: var(--ls-2); font-size: .68rem;
  text-transform: uppercase; font-weight: 500;
}

/* =========================================================
   HISTORIA / TIMELINE — línea dorada con nodos de cera
   ========================================================= */
.historia__linea {
  list-style: none; max-width: 520px; margin: 0 auto; text-align: left;
  position: relative; padding-left: 28px;
}
.historia__linea::before {
  content: ""; position: absolute; left: 6px; top: 6px; bottom: 6px;
  width: 1px; background: var(--oro-filete);
}
.historia__item { position: relative; padding: 0 0 var(--esp-6); }
.historia__item:last-child { padding-bottom: 0; }
.historia__item::before {
  /* nodo: mini-sello de cera vino */
  content: ""; position: absolute; left: -29px; top: 6px;
  width: 14px; height: 14px;
  border-radius: var(--blob);
  background: var(--vino);
  box-shadow: 0 0 0 4px var(--papel);
}
.historia__fecha {
  text-transform: uppercase; letter-spacing: var(--ls-2);
  font-size: .7rem; font-weight: 600;
  color: var(--oro-tinta); margin-bottom: var(--esp-1);
}
.historia__titulo {
  font-weight: 600; font-size: 1.3rem; line-height: 1.3;
  color: var(--cacao); margin-bottom: var(--esp-2);
}
.historia__texto { color: var(--cacao-2); }
.historia__img {
  width: 100%; margin-top: var(--esp-3);
  aspect-ratio: 3/2; object-fit: cover;
  padding: 8px; background: var(--crema);
  border-radius: 3px;
  box-shadow: var(--sombra-card);
  transform: rotate(-1.5deg);
}
.historia__item:nth-child(even) .historia__img { transform: rotate(1.5deg); }

/* =========================================================
   EVENTOS (ceremonia / recepción) — tarjetones con solapa
   ========================================================= */
.evento {
  position: relative;
  background: var(--crema);
  border: 1px solid var(--oro-filete);
  border-radius: var(--radio);
  box-shadow: var(--sombra-calida);
  padding: var(--esp-6) var(--esp-4) var(--esp-5);
  overflow: hidden;
}
.evento::before {
  /* solapa triangular arena asomando del borde superior */
  content: ""; position: absolute; top: 0; left: 50%;
  transform: translateX(-50%);
  width: 132px; height: 26px;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  background: var(--papel-2);
}
.evento__hora {
  font-style: italic; font-size: 1.5rem; line-height: 1.4;
  color: var(--terracota-prof); margin-bottom: var(--esp-1);
}
.evento__lugar {
  font-weight: 600; font-size: clamp(1.2rem, 5vw, 1.5rem);
  color: var(--cacao); line-height: 1.35;
}
.evento__direccion { color: var(--cacao-2); margin: var(--esp-2) 0 var(--esp-4); }
.evento__mapa {
  border: 1px solid var(--oro-filete); border-radius: var(--radio);
  overflow: hidden; background: var(--papel-2);
}
.evento__mapa iframe {
  width: 100%; height: 280px; display: block;
  filter: sepia(.12) saturate(.85);
}

/* Botón secundario de papelería: contorno vino */
.evento__boton, .hospedaje__link {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; margin-top: var(--esp-4);
  padding: 10px var(--esp-5);
  border: 1.5px solid var(--vino); border-radius: 999px;
  color: var(--vino); text-decoration: none;
  font-weight: 600; font-size: .95rem;
  background: transparent;
  transition: background var(--transicion), color var(--transicion), transform var(--transicion);
}
.evento__boton:hover, .hospedaje__link:hover {
  background: var(--vino); color: var(--crema); transform: translateY(-2px);
}

/* Sheen perlado al pasar el cursor por un tarjetón (solo puntero fino) */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .evento::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: linear-gradient(105deg, transparent 42%, rgba(253, 250, 244, .4) 50%, transparent 58%);
    transform: translateX(-130%);
    transition: transform .8s ease;
  }
  .evento:hover::after { transform: translateX(130%); }
}

/* =========================================================
   ITINERARIO — horas con leader punteado dorado
   ========================================================= */
.itinerario__lista { list-style: none; max-width: 480px; margin: 0 auto; }
.itinerario__item {
  display: flex; align-items: baseline; gap: 10px;
  padding: var(--esp-3) 0;
  border-bottom: 1px solid var(--papel-2);
  text-align: left;
}
.itinerario__item:last-child { border-bottom: none; }
.itinerario__item::after {
  /* leader punteado entre hora y actividad (flex item, orden intermedio) */
  content: ""; flex: 1 1 auto; min-width: 16px;
  border-bottom: 1px dotted var(--oro-filete);
  transform: translateY(-4px);
}
.itinerario__hora {
  order: -1; flex: 0 0 88px; white-space: nowrap; /* "10:00 p.m." en UNA línea (nota UX ⑥b) */
  font-weight: 600; font-size: 1rem;
  color: var(--terracota-prof);
  font-variant-numeric: tabular-nums;
}
.itinerario__actividad { order: 1; color: var(--cacao-2); }

/* =========================================================
   DRESS CODE — gotas de cera
   ========================================================= */
.dress-code__descripcion { max-width: 46ch; margin: 0 auto var(--esp-4); color: var(--cacao-2); }
.dress-code__colores { display: flex; flex-wrap: wrap; gap: var(--esp-3); justify-content: center; }
/* Gotas de cera con VOLUMEN: brillo superior (background-image convive con el
   background-color inline del motor), sombra interior de curvatura, sombra
   cálida presente, y anillo cacao-3 translúcido para que las gotas claras
   (marfil) no desaparezcan sobre el papel. */
.dress-code__color {
  width: 44px; height: 44px;
  border-radius: var(--blob);
  background-image: radial-gradient(circle at 34% 28%, rgba(253, 250, 244, .55), transparent 46%);
  box-shadow:
    inset 0 -5px 9px rgba(74, 52, 44, .24),
    0 0 0 1px rgba(131, 101, 79, .55),   /* --cacao-3 al 55% */
    0 10px 18px -8px rgba(111, 43, 56, .45);
}

/* =========================================================
   MESA DE REGALOS — membretes
   ========================================================= */
.mesa-regalos__mensaje { max-width: 46ch; margin: 0 auto var(--esp-4); color: var(--cacao-2); }
.mesa-regalos__lista { display: grid; gap: var(--esp-3); }
.mesa-regalos__link {
  display: flex; align-items: center; justify-content: space-between; gap: var(--esp-3);
  min-height: 52px; padding: 12px var(--esp-4);
  background: var(--crema);
  border: 1px solid var(--oro-filete); border-radius: var(--radio);
  color: var(--vino); font-weight: 600; text-decoration: none;
  transition: transform var(--transicion), box-shadow var(--transicion);
}
.mesa-regalos__link::after {
  content: "\2197"; /* ↗ */
  color: var(--oro-tinta); font-weight: 400; font-size: 1.05rem;
}
.mesa-regalos__link:hover { transform: translateY(-2px); box-shadow: var(--sombra-card); }

/* =========================================================
   HOSPEDAJE — tarjetas de cortesía
   ========================================================= */
.hospedaje__lista { list-style: none; display: grid; gap: var(--esp-4); grid-template-columns: 1fr; }
.hospedaje__item {
  padding: var(--esp-4); text-align: center;
  background: var(--crema);
  border: 1px solid var(--oro-filete); border-radius: var(--radio);
}
.hospedaje__nombre { font-weight: 600; font-size: 1.2rem; color: var(--cacao); margin-bottom: var(--esp-1); }
.hospedaje__direccion { color: var(--cacao-2); margin-bottom: var(--esp-2); }
.hospedaje__tel {
  display: inline-block; color: var(--vino); font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px;
}
.hospedaje__link { margin-top: var(--esp-3); min-height: 44px; padding: 8px var(--esp-4); font-size: .85rem; }

/* =========================================================
   GALERÍA INMERSIVA — full-bleed, scroll-snap nativo
   ========================================================= */
.galeria {
  width: 100vw; max-width: none;
  margin: 0 auto var(--esp-8);
  margin-inline: calc(50% - 50vw);
  text-align: center;
}
.carrusel { position: relative; }
.carrusel__track {
  display: flex; gap: 12px;
  overflow-x: auto; scroll-snap-type: x mandatory;
  scroll-behavior: smooth; -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-inline: 7vw; scroll-padding-inline: 7vw;
  padding-block: 10px; /* aire para la sombra cálida */
}
.carrusel__track::-webkit-scrollbar { display: none; }
.carrusel__slide {
  flex: 0 0 86vw; scroll-snap-align: center;
  aspect-ratio: 4/5; max-height: 72svh;
  padding: 6px; background: var(--crema);
  border-radius: 4px;
  box-shadow: var(--sombra-calida);
  transform: scale(.985);
  transition: transform var(--transicion);
}
.carrusel__slide:hover, .carrusel__slide:focus-within { transform: scale(1); }
.carrusel__slide img {
  width: 100%; height: 100%; object-fit: cover; border-radius: 2px;
}
/* Flechas: solo con puntero fino y pantalla ancha (móvil: swipe nativo) */
.carrusel__nav {
  display: none;
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 48px; height: 48px; border-radius: 50%;
  border: 1px solid var(--oro-filete); background: var(--crema);
  color: var(--vino); font-family: var(--f-texto); font-size: 1.4rem; cursor: pointer;
  place-items: center;
  transition: background var(--transicion), color var(--transicion);
}
.carrusel__nav:hover { background: var(--vino); color: var(--crema); }
.carrusel__nav--prev { left: 18px; } .carrusel__nav--next { right: 18px; }

/* Dots = gotas de cera (10px visual, área táctil 44px) + contador CSS puro */
.carrusel__dots {
  display: flex; justify-content: center; align-items: center;
  margin-top: var(--esp-2);
  counter-reset: foto-total foto-actual;
}
.carrusel__dot {
  position: relative; width: 44px; height: 44px;
  background: none; border: 0; padding: 0; cursor: pointer;
  counter-increment: foto-total foto-actual;
}
.carrusel__dot::before {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 10px; height: 10px;
  transform: translate(-50%, -50%);
  border-radius: var(--blob);
  background: var(--papel-2);
  box-shadow: 0 0 0 1px var(--cacao-3);
  transition: background var(--transicion), box-shadow var(--transicion);
}
.carrusel__dot.is-active::before { background: var(--vino); box-shadow: 0 0 0 1px var(--vino); }
.carrusel__dot.is-active ~ .carrusel__dot { counter-increment: foto-total; }
.carrusel__dots::after {
  content: counter(foto-actual) " / " counter(foto-total);
  margin-left: var(--esp-2);
  text-transform: uppercase; letter-spacing: var(--ls-2);
  font-size: .72rem; color: var(--cacao-3);
}

/* Lightbox: visor cacao inmersivo */
.lightbox {
  position: fixed; inset: 0; z-index: 50;
  display: flex; align-items: center; justify-content: center;
  background: rgba(74, 52, 44, .94);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  opacity: 0; visibility: hidden;
  transition: opacity .35s ease, visibility .35s ease;
}
.lightbox.is-open { opacity: 1; visibility: visible; }
.lightbox__img {
  max-width: 100vw; max-height: 100svh; object-fit: contain;
  transform: scale(.96);
  transition: transform .35s cubic-bezier(.22, 1, .36, 1);
}
.lightbox.is-open .lightbox__img { transform: scale(1); }
.lightbox__cerrar {
  position: absolute; top: 14px; right: 14px; z-index: 2;
  width: 48px; height: 48px; border-radius: 50%;
  border: 0; background: rgba(253, 250, 244, .14);
  color: var(--crema); font-family: var(--f-texto); font-size: 1.8rem;
  line-height: 1; cursor: pointer;
  display: grid; place-items: center;
}
.lightbox__nav {
  position: absolute; bottom: 24px; z-index: 2;
  width: 48px; height: 48px; border-radius: 50%;
  border: 1px solid rgba(253, 250, 244, .3); background: rgba(253, 250, 244, .14);
  color: var(--crema); font-family: var(--f-texto); font-size: 1.6rem;
  line-height: 1; cursor: pointer;
  display: grid; place-items: center;
}
.lightbox__nav--prev { left: calc(50% - 60px); }
.lightbox__nav--next { right: calc(50% - 60px); }
/* Contador del visor: versalitas crema discreta sobre el cacao.
   bottom:auto resetea el anclaje inferior del fallback compartido
   (sin él la caja queda estirada top→bottom). */
.lightbox__contador {
  position: absolute; top: 26px; bottom: auto; left: 50%; transform: translateX(-50%);
  text-transform: uppercase; letter-spacing: var(--ls-2);
  font-size: .78rem; color: var(--crema-85);
}
body:has(.lightbox.is-open) { overflow: hidden; }

/* =========================================================
   RSVP — tarjetón de confirmación (mismo lenguaje que eventos)
   ========================================================= */
.rsvp {
  position: relative;
  background: var(--crema);
  border: 1px solid var(--oro-filete);
  border-radius: var(--radio);
  box-shadow: var(--sombra-calida);
  padding: var(--esp-5) var(--esp-4) var(--esp-5);
  overflow: hidden;
}
.rsvp .divisor { margin-bottom: var(--esp-4); }
.rsvp__nota {
  text-transform: uppercase; letter-spacing: var(--ls-2);
  font-size: .78rem; color: var(--cacao-3);
  margin-bottom: var(--esp-5);
}
.rsvp__form { max-width: 440px; margin: 0 auto; text-align: left; }
.rsvp__auto { margin: calc(-1 * var(--esp-2)) 0 var(--esp-4); font-style: italic; color: var(--terracota-prof); font-size: .95rem; }
.rsvp__bloqueado { margin-top: var(--esp-3); }
.campo { margin-bottom: var(--esp-5); border: none; }
.campo__label {
  display: block; margin-bottom: var(--esp-2);
  color: var(--oro-tinta); font-size: .7rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--ls-2);
}
.campo__input {
  width: 100%; padding: var(--esp-2) 2px;
  font-family: var(--f-texto); font-size: 1.1rem; color: var(--cacao);
  background: transparent; border: none;
  border-bottom: 1px solid var(--cacao-3);
  border-radius: 0;
  transition: border-color var(--transicion);
}
textarea.campo__input {
  border: 1px solid var(--cacao-3); border-radius: var(--radio);
  padding: var(--esp-2) var(--esp-3); line-height: 1.6;
  background: var(--papel);
}
.campo__input::placeholder { color: var(--cacao-3); opacity: .75; }
.campo__input:focus-visible { outline: none; border-color: var(--terracota-prof); }
.rsvp__opciones { display: flex; flex-direction: column; gap: var(--esp-3); }
.rsvp__radio {
  display: flex; align-items: center; gap: var(--esp-2); cursor: pointer;
  font-style: italic; font-size: 1.05rem; color: var(--cacao-2);
}
.rsvp__radio input { accent-color: var(--vino); width: 1.15em; height: 1.15em; }

.boton {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 52px; margin-top: var(--esp-4);
  padding: 14px var(--esp-4);
  border: 0; border-radius: 999px;
  background: var(--vino); color: var(--crema);
  font-family: var(--f-texto); font-size: 1.02rem; font-weight: 600;
  letter-spacing: .02em; cursor: pointer;
  transition: background var(--transicion), transform var(--transicion), box-shadow var(--transicion);
}
.boton:hover { background: var(--vino-prof); transform: translateY(-2px); box-shadow: var(--sombra-calida); }
.boton:active { transform: scale(.98); box-shadow: none; }
.boton:disabled { opacity: .55; cursor: default; transform: none; }
.rsvp__estado { margin-top: var(--esp-3); text-align: center; font-weight: 600; min-height: 1.4em; }
.rsvp__estado--ok { color: var(--vino); }
.rsvp__estado--error { color: var(--error); }

/* --- Confirmación "WhatsApp directo" (el motor la conmuta por config) ---
   Markup real (motor.js initRsvpWhatsapp/repintarRsvpWa):
     .rsvp-wa > p.rsvp-wa__intro + nav.rsvp-wa__botones > a.rsvp-wa__boton
   y cada ancla YA trae span.rsvp-wa__ico (SVG de WhatsApp en currentColor)
   + span.rsvp-wa__label (con .rsvp-wa__nombre). El microcopy "por WhatsApp"
   lo pinta SOLO esta hoja (::after; el motor no lo imprime). */
.rsvp-wa { max-width: 440px; margin: 0 auto; }
/* Intro del motor OCULTO en lacre (veredicto UX ⑥b): el h2 de la sección ya
   dice "Confirma tu asistencia" — el intro triplicaba el mensaje. */
.rsvp-wa__intro { display: none; }
.rsvp-wa__botones { display: grid; gap: 12px; }
.rsvp-wa__boton {
  display: grid; grid-template-columns: 28px auto;
  grid-template-rows: auto auto; column-gap: 12px;
  align-items: center; align-content: center; justify-content: center;
  min-height: 56px; padding: 14px 22px;
  border-radius: 999px; text-decoration: none;
  background: var(--vino); color: var(--crema);
  font-family: var(--f-texto); font-weight: 600; font-size: 1.05rem;
  line-height: 1.3; text-align: left;
  transition: background var(--transicion), transform var(--transicion), box-shadow var(--transicion);
}
/* Ícono del motor: círculo crema translúcido 28px; el SVG hereda currentColor
   (crema del ancla) — CERO verde chillón (diseño §6) */
.rsvp-wa__ico {
  grid-column: 1; grid-row: 1 / 3;
  display: grid; place-items: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: rgba(253, 250, 244, .18);
}
.rsvp-wa__ico svg { width: 15px; height: 15px; display: block; }
.rsvp-wa__label { grid-column: 2; grid-row: 1; }
.rsvp-wa__boton::after {
  content: "por WhatsApp"; grid-column: 2; grid-row: 2;
  text-transform: uppercase; letter-spacing: var(--ls-2);
  font-size: .68rem; font-weight: 500;
  color: var(--crema-85); /* AA sobre vino y sobre terracota-prof */
}
.rsvp-wa__boton:hover { background: var(--vino-prof); transform: translateY(-2px); box-shadow: var(--sombra-calida); }
.rsvp-wa__boton:active { transform: scale(.98); box-shadow: none; }
.rsvp-wa__boton:focus-visible { outline: 3px solid var(--oro-tinta); outline-offset: 3px; }
/* Segunda ancla: terracota profundo (crema encima 6.5:1 AA) */
.rsvp-wa__boton:nth-of-type(2) { background: var(--terracota-prof); }
.rsvp-wa__boton:nth-of-type(2):hover { background: var(--terracota-hover); }

/* =========================================================
   SECCIÓN FAMILIA (dinámica, SPEC 0011) — el remitente de la carta
   Markup real (motor.js renderFamilia): .ti-sec__cuerpo > div.ti-sec__grupo
   (+ --destacado) > h3.ti-sec__grupo-etiqueta + ul.ti-sec__lista > li.
   ========================================================= */
/* Título de TODA sección dinámica alineado al sistema (versalitas Lora
   terracota, como .seccion__titulo); pisa el clamp grande de secciones.css
   a igual especificidad porque esta hoja carga después. */
.ti-sec__titulo {
  text-transform: uppercase; letter-spacing: var(--ls);
  font-weight: 500;
  font-size: clamp(1.05rem, 4.5vw, 1.3rem); line-height: 1.5;
}
.ti-sec[data-modulo="familia"] .ti-sec__grupo { padding: 18px 0; }
.ti-sec[data-modulo="familia"] .ti-sec__grupo + .ti-sec__grupo {
  /* separador: hairline dorado 96px con gota de cera al centro */
  margin-top: 10px; padding-top: 30px;
  background:
    radial-gradient(circle, var(--vino) 0 4px, transparent 4.5px) top center / 9px 9px no-repeat,
    linear-gradient(var(--oro-filete), var(--oro-filete)) top 4px center / 96px 1px no-repeat;
}
.ti-sec__grupo-etiqueta {
  display: inline-flex; align-items: center; gap: 12px;
  margin-bottom: var(--esp-2);
  text-transform: uppercase; letter-spacing: var(--ls-2);
  font-size: .78rem; font-weight: 500;
  color: var(--oro-tinta);
}
.ti-sec__grupo-etiqueta::before, .ti-sec__grupo-etiqueta::after {
  content: ""; width: 28px; height: 1px; background: var(--oro-filete);
}
/* Padres: :not(--destacado) para NO pisar a los hijos (sin el :not, esta
   regla de 0,3,1 le ganaba al selector del destacado y los hijos salían
   a 1.1rem — hallazgo M3 del revisor UX). */
.ti-sec[data-modulo="familia"] .ti-sec__grupo:not(.ti-sec__grupo--destacado) .ti-sec__lista li {
  font-weight: 500; font-size: 1.1rem; line-height: 1.6;
  color: var(--cacao); padding: 2px 0;
}
/* Hijos: la posdata protagonista, en Pinyon vino con mini-sello arriba.
   Selectores anclados a [data-modulo="familia"] para ganar SIEMPRE a la
   regla de padres, independiente del orden. */
.ti-sec[data-modulo="familia"] .ti-sec__grupo--destacado::before {
  content: ""; display: block;
  width: 22px; height: 22px; margin: 0 auto 10px;
  border-radius: var(--blob);
  background:
    radial-gradient(circle at 50% 46%, var(--oro) 0 3px, transparent 3.5px),
    var(--vino);
}
.ti-sec[data-modulo="familia"] .ti-sec__grupo--destacado .ti-sec__grupo-etiqueta::before,
.ti-sec[data-modulo="familia"] .ti-sec__grupo--destacado .ti-sec__grupo-etiqueta::after { content: none; }
.ti-sec[data-modulo="familia"] .ti-sec__grupo--destacado .ti-sec__lista {
  display: flex; flex-wrap: wrap; justify-content: center;
  column-gap: .55em; row-gap: 2px;
}
.ti-sec[data-modulo="familia"] .ti-sec__grupo--destacado .ti-sec__lista li {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(1.7rem, 8vw, 2.3rem); line-height: 1.25;
  color: var(--vino); padding: 0;
}
.ti-sec[data-modulo="familia"] .ti-sec__grupo--destacado .ti-sec__lista li + li::before {
  content: "\00B7"; /* · */
  font-family: var(--f-texto); color: var(--oro-tinta);
  margin-right: .55em;
}

/* =========================================================
   MÚSICA (botón flotante) — sello sonoro
   El pulso .musica--invita es compartido (secciones.css): no se redefine.
   ========================================================= */
/* Contorno dorado, NO disco vino relleno. El relleno hacía tres cosas malas:
   era el elemento más contrastado de la pantalla tras el sello, DUPLICABA el
   rojo de cera —contra la decisión de que el sello fuera el único acento de
   cera— y a la misma altura que el chevron los dos discos se leían como un
   par de controles. Papel translúcido + filete de oro lo devuelve al lenguaje
   de papelería y lo baja de jerarquía sin esconderlo.
   Se conservan los 52px de área táctil y el glifo en --vino sobre --crema
   (9.7:1). El pulso .musica--invita sigue siendo compartido: no se redefine. */
.musica {
  position: fixed; bottom: var(--esp-4); right: var(--esp-4); z-index: 40;
  width: 52px; height: 52px; border-radius: 50%;
  border: 1px solid var(--oro-marco);
  background: var(--crema-85);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  color: var(--vino); cursor: pointer; display: grid; place-items: center;
  font-size: 1.25rem;
  box-shadow: var(--sombra-card);
  transition: background var(--transicion), border-color var(--transicion), color var(--transicion);
}
@media (hover: hover) {
  .musica:hover {
    background: var(--crema); border-color: var(--vino); color: var(--vino-prof);
  }
}
/* Sonando: el filete se cierra a oro tinta (afordancia de estado sin relleno) */
.musica[aria-pressed="true"] { border-color: var(--oro-tinta); }
.musica[aria-pressed="true"] .musica__icono { animation: lacre-girar 4s linear infinite; }
@keyframes lacre-girar { to { transform: rotate(360deg); } }

/* =========================================================
   PIE — banda vino a sangre, colofón lacrado
   ========================================================= */
.pie {
  position: relative; text-align: center;
  padding: var(--esp-7) var(--esp-4) calc(var(--esp-7) + 70px);
  background: var(--vino-prof);
  color: var(--crema);
  overflow: hidden;
}
.pie::before {
  /* grano sobre la banda vino */
  content: ""; position: absolute; inset: 0; pointer-events: none;
  opacity: .25;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.14'/%3E%3C/svg%3E");
}
.pie > * { position: relative; }
/* Mini-sello del colofón: blob VINO-CLARO (visible sobre la banda vino-prof
   en cualquier viewport) + punto oro central + anillo dorado. Sin el fondo
   claro y el punto, el blob se confundía con la banda y solo se veía el
   anillo "hueco" — hallazgo menor 8 del revisor UX. */
.pie .divisor { background: none; }
.pie .divisor::before {
  background:
    radial-gradient(circle at 50% 46%, var(--oro) 0 3.5px, transparent 4.5px),
    radial-gradient(circle at 38% 30%, rgba(253, 250, 244, .22), transparent 55%),
    var(--vino-claro);
  box-shadow: inset 0 0 0 2px var(--oro), 0 3px 8px rgba(0, 0, 0, .25);
}
.pie__nombres {
  font-family: var(--f-display); font-weight: 400;
  font-size: 2.2rem; line-height: 1.2; color: var(--crema);
}
.pie__fecha {
  margin: var(--esp-3) 0 var(--esp-2);
  text-transform: uppercase; letter-spacing: var(--ls-2);
  font-size: .82rem; color: var(--oro); /* texto grande+tracking sobre vino: AA large */
}
.pie__credito { font-size: .8rem; color: var(--crema-85); }

/* =========================================================
   BARRA DE PREVIEW (dev)
   ========================================================= */
.preview {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 60;
  background: rgba(74, 52, 44, .96); color: var(--crema);
  padding: var(--esp-2) var(--esp-3);
  display: flex; gap: var(--esp-3); flex-wrap: wrap; justify-content: center;
  font-family: system-ui, sans-serif;
}
.preview__chip {
  background: rgba(253, 250, 244, .12); color: var(--crema);
  border: 1px solid transparent; padding: 4px 10px; border-radius: 999px;
  font-size: .8rem; cursor: pointer;
}
.preview__chip.is-active { background: var(--oro); color: var(--cacao); }

/* =========================================================
   PORTADA DE APERTURA (SPEC 0008) — variante SOBRE LACRADO
   Sobrescribe el andamiaje de secciones.css por cascada (esta hoja
   carga DESPUÉS): fondo papel + viñeta + grano, masthead, y el sobre
   (.ti-portada--sobre) con su sello de cera que se despega al abrir.
   ========================================================= */
.ti-portada {
  background:
    radial-gradient(58% 46% at 8% 4%, var(--terracota-lav), transparent 62%),
    radial-gradient(56% 46% at 94% 96%, var(--terracota-lav), transparent 62%),
    var(--papel);
  transition: opacity .5s ease .3s; /* paso 3: la capa revela al final */
}
.ti-portada::before {
  /* grano de papel de la portada */
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: .4; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}
/* El lienzo (sobre + sello + TODOS los textos de la portada) se apaga ANTES
   de que la capa empiece a revelar. Es el arreglo de la "doble exposición":
   había un tramo (300-650ms) en que la capa ya iba camino de transparente
   MIENTRAS el sobre y el "¡Tienes una invitación!" seguían pintados, así que
   el texto de la portada se leía ATRAVESANDO el papel de la carta. Ahora el
   lienzo llega a opacidad 0 justo cuando la capa arranca su fundido (300ms):
   a partir de ahí solo se disuelve un fondo plano, sin nada encima. */
.ti-portada__lienzo {
  position: relative; z-index: 1;
  transition: opacity .16s ease .14s;   /* 140→300ms */
}
.ti-portada--abriendo .ti-portada__lienzo { opacity: 0; }

/* El masthead ("Una carta para ti") quedó RETIRADO a pedido del cliente
   (v0.26.6): la portada-sobre lleva un solo texto (el título de estado).
   Se hereda el display:none de secciones.css — no re-declararlo aquí. */
.ti-portada__titulo {
  font-family: var(--f-texto); font-weight: 500;
  text-transform: uppercase; letter-spacing: var(--ls-2);
  font-size: .95rem; color: var(--cacao-2);
}
.ti-portada__nombre {
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(2.6rem, 13vw, 4.4rem); line-height: 1.1;
  color: var(--vino);
}
.ti-portada__cta {
  min-height: 52px; padding: 14px 34px;
  font-family: var(--f-texto); font-weight: 600; font-size: 1.02rem;
}

/* --- El sobre (variante .ti-portada--sobre; SPEC 0008 §4.1.b) ---
   El markup lo crea motor.js (montarPortadaSobre): una capa .ti-portada__sobre
   como PRIMER hijo del lienzo; dentro, el sello es DIRECTAMENTE
   <img class="ti-portada__sello"> (PNG por invitación) o
   <span class="ti-portada__sello-generico"> (fallback con el monograma).
   secciones.css ya pinta el asset (portada/sobre-beige.webp) y centra el sello
   con transform: translate(-50%, -50%). Aquí SOLO se afina: tamaño/sombra
   cálida del sobre, sello sobre la PUNTA de la solapa (top 58%, width 29%:
   el asset 792×487 trae un embose circular horneado en la punta ≈58% de la
   altura — el sello debe CUBRIRLO o se ve "doble sello") y los tiempos de
   apertura del diseño §4 — misma especificidad que secciones.css
   (0,2,0 / 0,3,0), esta hoja carga después y gana por cascada. */
.ti-portada--sobre .ti-portada__sobre {
  /* Tercer término = CUPO DE ALTURA, mismo mecanismo (y misma constante de 140px)
     que el default de secciones.css, pero con el aspect-ratio de ESTA plantilla:
     792/487 = 1.626. `.ti-portada` es overflow:hidden y centra por flex, así que
     el contenido que no cabe se recorta arriba y abajo, y abajo está el CTA.
     Medido en el render de dev 0.30.0 (844px de ancho, sobre a 400px): el lienzo
     medía 353.4px de alto, así que por debajo de 353px de viewport el CTA empezaba
     a perderse y por debajo de ~262px quedaba entero fuera de pantalla —
     inalcanzable, porque la capa no scrollea y el touchmove va con preventDefault.
     140px = lo que mide todo lo que NO es el sobre (gap 14 + título 33.4 + gap 14 +
     margen 2 + CTA 54.1 = 117.5) más ~22px de aire. Ese aire es deliberado: sin él
     el CTA entraba pero quedaba pegado al borde inferior (medido: 2.2px).
     El min() deja de morder por encima de 140 + 400/1.626 = 386px de alto, así que
     retrato y landscape holgado conservan el sobre a 400px; por debajo el sobre
     cede altura para que el CTA siempre entre con margen. */
  width: min(78vw, 400px, calc((100svh - 140px) * 1.626));
  aspect-ratio: 792 / 487;               /* proporción real del .webp */
  filter: drop-shadow(0 22px 30px rgba(111, 43, 56, .22));
  /* Geometría del sello por TOKEN (secciones.css) en vez de top/width sueltos:
     el halo del refuerzo la consume igual, así que sigue centrado en el sello
     sin repetir aquí sus medidas.
     58% = la punta de la solapa (el .webp 792×487 trae un embose circular
     horneado ahí: el sello debe cubrirlo o se ve "doble sello").
     ⚠️ El paso a tokens NO fue neutro para el sello GENÉRICO (el blob con
     monograma, visible solo si el cliente no subió PNG o si este falla al
     cargar): antes tomaba el `height: clamp(72px, 24vw, 96px)` de la capa
     compartida y salía ELÍPTICO (116 × 96 a partir de 513px de ancho, donde el
     clamp ya está topado); ahora `aspect-ratio: 1` lo hace CIRCULAR, 116 × 116,
     o sea +20.8% de alto. Es intencional —un sello de cera es redondo— y quedó
     validado por el guardián del tema; se anota porque el look NO es idéntico al
     histórico en esa rama. El sello PNG del cliente no cambió nada. */
  --portada-sello-y: 58%;
  --portada-sello-w: 29%;
  /* Anillo del refuerzo en VINO y no en el --portada-acento de esta plantilla
     (oro rgba(179,135,62,.55)): sobre el crema del papel el oro tiene casi la
     misma luminancia y el halo no se distinguía. El vino sí separa (#6f2b38 da
     9.05:1 sobre --papel como texto) y a alfa baja dentro de un anillo difuso
     lee como una sombra de cera, no como un aro duro.
     ⚠️ Va DECLARADO AQUÍ, en `.ti-portada__sobre`, y no en la regla del CTA:
     las custom properties solo bajan por herencia, y el halo es hijo del sobre,
     NO del botón. Declarado en el CTA el token no llegaba al halo y este seguía
     pintándose con el oro del fallback (comprobado midiendo: cambiar el alfa no
     movía un solo nivel de píxel). Mismo motivo por el que --portada-sello-y/-w
     viven en esta regla. */
  /* Alfa calibrada por A/B de píxeles (capturar el frame del pico, currentTime
     1200ms, contra el mismo frame con el halo en display:none): pico de 30
     niveles RGB de diferencia al 56% del radio, cayendo a 3 en el 80% y a 0-1
     fuera. Referencia: la versión anterior medía 0 niveles en TODO el anillo.
     Con .16 el pico se quedaba en 22 y con .26 subía a ~48 (demasiado duro para
     papelería); 30 se ve sin dudar y sigue leyendo como cera, no como neón. */
  --portada-halo: rgba(111, 43, 56, .22);
  /* Comprimido de 200-650ms a 20-280ms: el gesto es el MISMO (mismos
     transform y familia de easing), solo cabe entero antes de que la capa
     empiece a revelar en 300ms. Sin esto el sobre seguía en pantalla
     mientras la carta ya se veía debajo. */
  transition: transform .26s ease .02s, opacity .22s ease .02s;
}
/* El CTA en la variante SOBRE: VISIBLE (v0.29.x).
   Antes iba oculto con sr-only, apostando a que el sello se leyera como llamada
   a la acción. El uso real lo desmintió: hubo invitados que se quedaron mirando
   el sobre sin tocarlo. Ahora secciones.css lo pinta como pie discreto con un
   copy que nombra el gesto ("Toca el sello para abrir") y aquí solo se afina la
   tipografía de marca. El tap sigue abriendo en CUALQUIER punto de la capa
   (motor.js liga click/touchend/pointerup a .ti-portada entera): el botón no es
   la única diana, es el significante. */
.ti-portada--sobre .ti-portada__cta {
  font-family: var(--f-texto);
  /* .95rem = 15.2px, el MISMO cuerpo que `.ti-portada__titulo` de arriba: los dos
     renglones quedan a ~30px uno de otro con la misma familia, peso, color,
     versalitas y tracking (--ls-2), así que se leen como un pareado de papelería
     en vez de como dos escalas discordantes.
     TENSIÓN DECLARADA con `compartido/estandares.md` §4 ("Base ≥ 16px"): la base
     del documento sigue en 16px y quien la fija es `body`; esto es una ETIQUETA,
     no el cuerpo de lectura, y baja un 5% respecto al piso. Se acepta a cambio de
     tres compensaciones MEDIDAS sobre el render: versalitas + .18em de tracking
     (que suben la legibilidad efectiva a cuerpo pequeño), 6.07:1 de contraste en
     reposo y 10.19:1 en el refuerzo, y una diana táctil de 44px de alto. Si el
     guardián prefiere la letra del estándar, basta borrar esta declaración: el
     default compartido ya es 1rem. */
  font-size: .95rem;
  font-weight: 500;
  /* FILETE PERMANENTE. Antes el único recuadro visible alrededor del CTA era el
     anillo de FOCO (motor.js enfoca el botón al montar la portada, línea ~1781),
     y salía en --portada-cta-tinta = --crema #fdfaf4, casi el mismo valor que el
     papel. Dos problemas: (a) la señal más fuerte de "esto es un botón" dependía
     de un estado de foco que en un móvil real no se pinta (:focus-visible no casa
     con un focus() programático tras un toque), así que en el teléfono del
     invitado el CTA quedaba más desnudo que en las capturas de escritorio; (b)
     ese anillo casi-blanco sobre crema tampoco servía a quien navega con teclado.
     El filete de oro resuelve (a) de forma permanente y lee como grabado de
     papelería; el foco se trata aparte, abajo, para que sigan siendo distinguibles. */
  border: 1px solid var(--oro-filete);
  border-radius: 2px;
  padding: 12px 22px;
  /* Tracking en la ESCALA de la plantilla (--ls-2 = .18em) y no en el default de
     la capa compartida (.22em): el `.ti-portada__titulo` queda a ~30px del CTA con
     la misma familia, peso, color y versalitas, y con dos trackings distintos se
     leían como dos renglones gemelos desalineados. El estado --insiste sigue
     abriendo +.04em por el calc() de secciones.css ⇒ .22em, sin literal aquí. */
  --portada-cta-tracking: var(--ls-2);
  /* Contraste medido en la POSICIÓN REAL del CTA (centrado abajo, x=50%). OJO, los
     velos que cuentan son los de `.ti-portada` (definidos arriba en esta misma
     hoja), NO los del `body` (que están en 6%,2% / 94%,98%): la capa es fixed e
     inset:0, así que tapa el fondo del documento por completo. Esos dos velos
     --terracota-lav están en (8%,4%) y (94%,96%) y su distancia radial
     normalizada hasta ahí es 1.51 y 1.04 — ambas >1, o sea aporte nulo. El fondo
     efectivo es --papel #f7f1e8 con el grano del ::before (~2% más oscuro):
       reposo   --cacao-2 #6e5548 → 6.0:1  (AA texto normal)
       --insiste --cacao  #4a342c → 10.1:1 (AAA)
     En el punto MÁS velado de la capa (esquinas, doble velo #e5cec0) --cacao-2
     daría 4.4:1, pero el CTA nunca se pinta ahí; --cacao aguanta 7.6:1 en ese
     punto, así que el estado reforzado es AA en toda la superficie. */
  --portada-cta-suave: var(--cacao-2);
}

/* Foco del CTA en la variante SOBRE. El default compartido pinta el anillo en
   --portada-cta-tinta, que aquí es --crema #fdfaf4: sobre el papel crema es casi
   invisible, así que el usuario de teclado no veía dónde estaba. En vino se
   distingue del filete de oro permanente (color distinto, 3px contra 1px y con
   offset), de modo que "enfocado" y "en reposo" no se confunden. */
.ti-portada--sobre .ti-portada__cta:focus-visible {
  outline: 3px solid var(--vino);
  outline-offset: 3px;
}

/* Geometría común del sello (img o genérico). El centrado translate(-50%,-50%)
   lo da secciones.css y el tamaño/posición salen de --portada-sello-y/-w
   (declarados arriba en .ti-portada__sobre) — aquí NO se toca transform ni se
   suma la propiedad `translate` (duplicaría el desplazamiento). */
.ti-portada--sobre .ti-portada__sello,
.ti-portada--sobre .ti-portada__sello-generico {
  /* 0-260ms (antes 0-350): el despegue del sello abre la secuencia y cierra
     dentro de la ventana previa al fundido de la capa. */
  transition: transform .26s cubic-bezier(.34, 1.2, .5, 1), opacity .22s cubic-bezier(.34, 1.2, .5, 1);
}
/* Sello PNG subido: la imagen va LIMPIA (sin blob de fondo), solo sombra */
.ti-portada--sobre .ti-portada__sello {
  height: auto;
  filter: drop-shadow(0 6px 10px rgba(74, 52, 44, .35));
}
/* Fallback sin PNG: blob de cera vino, anillo dorado interior y monograma
   Pinyon --oro (texto grande sobre vino = 4.33:1, AA large; diseño §4) */
.ti-portada--sobre .ti-portada__sello-generico {
  height: auto;
  aspect-ratio: 1;
  border-radius: var(--blob);
  background:
    radial-gradient(circle at 36% 30%, rgba(253, 250, 244, .18), transparent 55%),
    var(--vino);
  box-shadow: inset 0 0 0 2px rgba(201, 164, 92, .55);
  font-family: var(--f-display); font-weight: 400;
  font-size: clamp(1.2rem, 6vw, 1.7rem); line-height: 1;
  letter-spacing: .02em;
  color: var(--oro);
  filter: drop-shadow(0 6px 10px rgba(74, 52, 44, .35));
}

/* El micro-idle propio (`lacre-sello-idle`, levitación de 3px del sello) se
   RETIRÓ. Lo sustituye el LATIDO compartido de secciones.css, que desde v0.30.x
   ya NO se aplica al sello sino a `.ti-portada__sobre` (escala 1→1.02, 2.4s):
   mueve el sobre, el sello y el halo como una sola pieza, que es lo que se lee
   como affordance táctil — el sello solo era ~29% del ancho y su movimiento no
   se apreciaba.
   Por qué no podían convivir cuando ambos vivían en el sello: eran el MISMO
   selector con la MISMA especificidad (0,3,0) y esta hoja carga después, así que
   mantener la regla de aquí dejaba muerto el mecanismo compartido justo en la
   única plantilla que usa el sobre. Hoy ya ni comparten selector, pero el sello
   debe seguir SIN animación propia: se compondría con la del padre y latiría el
   doble.
   Si lacre quisiera su propio ritmo, basta sobrescribir `animation-duration` en
   `.ti-portada--sobre .ti-portada__sobre`. Para cambiar el MOVIMIENTO (p. ej.
   recuperar una levitación) sí hay que sustituir la declaración `animation`
   entera y declarar sus propios @keyframes: un keyframe ajeno no se puede
   reescribir por partes. */

/* Apertura (≤900ms total, dentro del timeout de respaldo de 1.4s; solo
   transform/opacity sobre capas propias, jamás el hero). Mismo estado
   --abriendo de secciones.css, con el gesto del diseño §4:
   1) el sello se despega  2) el sobre se suelta  3) la capa revela */
.ti-portada--abriendo.ti-portada--sobre .ti-portada__sello,
.ti-portada--abriendo.ti-portada--sobre .ti-portada__sello-generico {
  transform: translate(-50%, calc(-50% - 16px)) rotate(-9deg) scale(1.07);
  opacity: 0;
}
.ti-portada--abriendo.ti-portada--sobre .ti-portada__sobre {
  transform: translateY(44px) rotate(1.5deg) scale(.96);
  opacity: 0;
}

/* =========================================================
   RESPONSIVE
   ========================================================= */
@media (min-width: 600px) {
  /* Hero: la carta crece y el sello se fija a su tamaño máximo */
  :root { --carta-max: 480px; --sello-hero: 104px; }
  .hero__contenido { padding: var(--esp-7) var(--esp-5) var(--esp-6); }

  /* El tope de 440px del contenedor dejaba columnas de ~208px y el label
     envolvía (nota UX ⑥b): a lo ancho del tarjetón cada botón respira. */
  .rsvp-wa { max-width: none; }
  .rsvp-wa__botones { grid-template-columns: 1fr 1fr; }
}

@media (min-width: 720px) {
  /* Historia: línea al centro, hitos alternados */
  .historia__linea { max-width: 640px; padding-left: 0; }
  .historia__linea::before { left: 50%; }
  .historia__item { width: calc(50% - 36px); padding: 0 0 var(--esp-6); }
  .historia__item:nth-child(odd)  { margin-right: auto; text-align: right; }
  .historia__item:nth-child(odd)::before  { left: auto; right: -43px; }
  .historia__item:nth-child(even) { margin-left: auto; text-align: left; }
  .historia__item:nth-child(even)::before { left: -43px; }

  .evento__mapa iframe { height: 340px; }
  .hospedaje__lista { grid-template-columns: 1fr 1fr; }
  .mesa-regalos__lista { max-width: 480px; margin: 0 auto; }

  /* Familia: padres en columnas gemelas; hijos full-width debajo.

     El separador vertical NO puede ser un `border-left` sobre el grupo de la
     derecha: un borde se pinta en el borde de la caja, así que el `column-gap`
     entero queda a su izquierda y la línea sale descentrada media canaleta
     (medido: eje real 713, borde en 765 ⇒ 54px a la derecha, porque encima el
     grupo iba encogido y centrado en su columna). Se resuelve en dos pasos:

     1) Se anula el `margin-inline:auto` que `secciones.css` pone en
        `.ti-sec__grupo`: ESO era lo que encogía cada grupo a su ancho
        intrínseco (201 vs 206) y lo centraba dentro de su pista. Las pistas
        SIEMPRE fueron gemelas (medido: 279px 279px); el desajuste lo metía el
        encogido, no la retícula. Con margen 0 los dos grupos de padres miden
        EXACTAMENTE la pista y el contenido sigue centrado por el
        `text-align:center` que hereda de .ti-sec.
     2) El separador pasa a un `::after` ABSOLUTO tirado sobre el eje real de la
        canaleta (`left: -canaleta/2` desde el borde de la pista derecha). Así
        la línea no depende del ancho del contenido, solo de la retícula.

     Se conserva `auto-fit`: con UN solo grupo de padres colapsa la pista vacía
     y el grupo queda centrado a todo lo ancho (con `repeat(2,1fr)` explícito se
     quedaba pegado a la izquierda: centro 557 contra 713 del cuerpo).

     PARIDAD: el renderer `grupos` antepone un <p class="ti-sec__texto"> cuando
     hay `mensaje`, y ese nodo también es grid item, así que corre la paridad
     una posición y la línea caería sobre el grupo de la IZQUIERDA (huérfana).
     Se corrige con el combinador `~` (dos reglas de refuerzo), NO con
     `:nth-child(even of S)`: ese selector resuelve el estilo pero Chrome 150
     NO GENERA LA CAJA del pseudo-elemento (verificado en render real: computed
     `position:absolute` pero `height:auto` y cero píxeles pintados, mientras un
     `nth-child` simple sí pinta). Cobertura: 1 grupo ⇒ sin línea; 2 ⇒ una en el
     eje; 3 ⇒ solo entre el 1º y el 2º (el 3º baja solo a la fila 2, sin línea
     suelta); 4 ⇒ una por fila. */
  .ti-sec[data-modulo="familia"] .ti-sec__cuerpo {
    --fam-canaleta: 32px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 8px var(--fam-canaleta);
    align-items: start;
  }
  /* El mensaje opcional ocupa la fila entera: no se cuela como columna. */
  .ti-sec[data-modulo="familia"] .ti-sec__cuerpo > .ti-sec__texto {
    grid-column: 1 / -1;
  }
  .ti-sec[data-modulo="familia"] .ti-sec__grupo + .ti-sec__grupo {
    margin-top: 0; padding-top: 18px; background: none;
  }
  /* Padres: llenan su pista (gemelas) y anclan el separador absoluto.
     `margin: 0` (no solo `margin-inline`) porque el grupo que va detrás del <p>
     del mensaje NO casa con `.ti-sec__grupo + .ti-sec__grupo` y conservaba los
     22px de `secciones.css`, quedando 22px más abajo que su pareja de fila (y
     el separador arrancaba desalineado). El aire entre filas ya lo dan el
     `row-gap` y el `padding-top`. */
  .ti-sec[data-modulo="familia"] .ti-sec__grupo:not(.ti-sec__grupo--destacado) {
    position: relative;
    margin: 0;
  }
  /* Un ÚNICO grupo de padres ocupa la fila entera. `auto-fit` solo colapsa la
     2ª pista si NADIE más la usa: con hijos destacados (grid-column 1/-1) o con
     mensaje, la pista sigue viva y el grupo solitario se quedaba pegado a la
     izquierda (medido: centro 557 contra 713 del cuerpo). El selector lee
     "cuerpo SIN dos grupos de padres hermanos" ⇒ hay como mucho uno. */
  .ti-sec[data-modulo="familia"] .ti-sec__cuerpo:not(:has(.ti-sec__grupo:not(.ti-sec__grupo--destacado) ~ .ti-sec__grupo:not(.ti-sec__grupo--destacado))) .ti-sec__grupo:not(.ti-sec__grupo--destacado) {
    grid-column: 1 / -1;
  }
  /* Separador en el EJE de la canaleta. Caso normal (sin mensaje): el grupo par
     es el de la columna derecha. Caso con mensaje: la paridad se corre y el de
     la derecha pasa a ser el impar. */
  .ti-sec[data-modulo="familia"] .ti-sec__grupo:not(.ti-sec__grupo--destacado):nth-child(even)::after,
  .ti-sec[data-modulo="familia"] .ti-sec__texto ~ .ti-sec__grupo:not(.ti-sec__grupo--destacado):nth-child(odd)::after {
    content: "";
    position: absolute;
    top: 0; bottom: 0;
    left: calc(var(--fam-canaleta) / -2);
    width: 1px;
    background: var(--oro-filete);
  }
  /* Con mensaje, el par es el de la IZQUIERDA: se apaga (gana por
     especificidad al selector base, que no lleva el `.ti-sec__texto ~`). */
  .ti-sec[data-modulo="familia"] .ti-sec__texto ~ .ti-sec__grupo:not(.ti-sec__grupo--destacado):nth-child(even)::after {
    content: none;
  }
  /* Hijos: full-width, sin separador vertical y con su encogido centrado
     intacto (conserva el margin:auto de secciones.css). */
  .ti-sec__grupo--destacado { grid-column: 1 / -1; padding-top: var(--esp-4); }
}

@media (min-width: 900px) {
  /* Hero de escritorio: carta más ancha, luz cenital más presente y sombra
     más larga (la mesa se lee más profunda). La canaleta de mesa se conserva
     SIEMPRE: nunca full-bleed. */
  :root {
    --carta-max: 560px;
    --sombra-papel:
       0 1px 0 rgba(253, 250, 244, .95) inset,
       0 2px 5px rgba(74, 52, 44, .10),
       0 30px 56px -20px rgba(74, 52, 44, .34);
  }
  /* ⚠️ Este override RE-DECLARA el fundido de la costura: `background` es
     shorthand y sin var(--mesa-fundido) la costura volvería en escritorio. */
  .hero::after {
    background:
      radial-gradient(60% 45% at 50% -10%, rgba(253, 250, 244, .62), transparent 60%),
      var(--mesa-fundido);
  }

  /* Galería de escritorio: slides contenidos + máscara de desvanecido */
  .carrusel__slide { flex-basis: min(520px, 44vw); }
  .carrusel__track {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  }
  @media (hover: hover) {
    .carrusel__nav { display: grid; }
  }
  .lightbox__nav { bottom: auto; top: 50%; transform: translateY(-50%); }
  .lightbox__nav--prev { left: 24px; }
  .lightbox__nav--next { right: 24px; }
}

/* =========================================================
   HERO EN ALTURA CORTA (móvil en horizontal)
   Va DESPUÉS de los breakpoints de ancho a propósito: tiene que ganarle a
   --carta-max (480px ≥600 · 560px ≥900) y al padding esp-7/esp-5/esp-6 de
   la carta, que son justo lo que la engorda en horizontal. A 740×360 el
   hero medía 605px contra un viewport de 360: el primer pantallazo cortaba
   bajo el segundo nombre y fecha, lugar y chevron quedaban fuera — y con
   ellos la canaleta inferior, o sea el concepto de "carta sobre la mesa".
   Se bajan CONJUNTAMENTE sello, ancho, padding vertical y escala del
   display hasta que padTop + carta + padBottom ≤ 100svh.
   La carta se mantiene ANCHA (520px): los nombres van en <span> de bloque,
   así que estrecharla no quita líneas — solo quitaría aire lateral.
   ========================================================= */
@media (orientation: landscape) and (max-height: 500px) {
  :root {
    --sello-hero: 60px;   /* ⇒ padding-top del hero = 40px, con 10px de holgura */
    --carta-max:  520px;
  }
  /* ⚠️ EL FORMATO DE HOJA SE DESACTIVA AQUÍ. Con 360px de alto útil el
     formato 5/7 pediría una carta de ~166px de ancho: la hoja se volvería una
     tira y los nombres desbordarían. En horizontal corto no cabe una hoja
     vertical, punto — se vuelve al comportamiento dictado por el contenido,
     que es el que ya cumplía padTop + carta + padBottom ≤ 100svh (332px a
     740×360). `aspect-ratio:auto` + `max-width/max-height` explícitos anulan
     las tres declaraciones de la regla base. */
  .hero__contenido {
    padding: var(--esp-4) var(--esp-4) var(--esp-3);
    aspect-ratio: auto;
    max-width: var(--carta-max);
    max-height: none;
  }
  /* El display escala por ALTURA (vh), que es la dimensión escasa aquí:
     por ancho (12.5vw) un landscape de 740px pedía los 4.6rem del tope. */
  .hero__nombres { font-size: clamp(1.9rem, 9vh, 2.6rem); }
  .hero__fecha,
  .hero__lugar   { margin-top: var(--esp-2); }
  /* El chevron se compacta pero NUNCA baja de 44px de área táctil. Con
     bottom esp-2 ocupa 8..52px desde el borde inferior, dentro de los 64px
     de padding-bottom del hero ⇒ no invade la carta. */
  .hero__scroll { width: 44px; height: 44px; bottom: var(--esp-2); }
}

/* =========================================================
   ACCESIBILIDAD — reduce motion
   Apaga TODA animación/transición; el contenido queda visible y en
   reposo. Las transiciones de la portada las restituye secciones.css
   (regla !important de mayor especificidad) para que la revelación no
   caiga al timeout de respaldo.
   ========================================================= */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .fade-up, .fade-up > * { opacity: 1; transform: none; transition-delay: 0s; }
  .carrusel__track { scroll-behavior: auto; }
  .carrusel__slide { transform: none; }
  .lightbox__img, .lightbox.is-open .lightbox__img { transform: none; }
}
