/* ============================================================
   HOME JOURNEY — Sarah & Arturo, main immersive journey
   Loads after styles.css and reuses its tokens (--marfil, --lino,
   --olivo, --peonia, --miel, --dusty-pink, --ink, --ink-deep,
   --font-display, --font-script, --font-body, --margin-h). No new
   colors or fonts are introduced here.

   ARCHITECTURE (structural rebuild — replaces the old one-sticky-
   section-per-scene system): #home-journey is one tall section whose
   height (--journey-h) IS the whole scroll budget. #home-stage pins
   inside it (position:sticky) for that entire budget and never
   un-pins until the journey ends. Every beat is a .world, absolutely
   positioned filling the stage; home-scenes.js buildMasterTimeline()
   drives all of them off ONE scrubbed GSAP timeline, with adjacent
   worlds' in/out tweens overlapping in time so nothing hard-cuts.
   #stage-atmosphere is a second absolutely-positioned stack, same
   technique, sitting behind the worlds — see "ATMOSPHERE SYSTEM".
   ============================================================ */

/* ----------------------------------------------------------
   JOURNEY / STAGE SHELL
---------------------------------------------------------- */
:root {
  /* Total scroll budget — see the label table in home-scenes.js
     buildMasterTimeline(); keep these in sync (1 unit = 1vh on
     mobile). Desktop gets more room per beat than mobile (--journey-h
     below is the mobile value, widened in the @media block further
     down). Bumped 2750 -> 3020 (correction #29) to match
     CP.rsvpClosing.end's own +270 shift — added scroll for Explore's
     new clean hold, not a rescale of the existing budget. */
  --journey-h: 3020vh;
  /* Countdown legibility patch — deep near-black green, requested
     specifically for the countdown text over sky/volcano artwork
     where the palette's existing --ink (warm near-black) still read
     too warm/light against the illustration. */
  --countdown-deep: #1e2418;
  /* Convento Santa Clara typographic layout patch — dark, opaque
     tone shared by title, meta, and CTA, for legibility over this
     world's open-sky illustration. */
  --convento-ink: #1c1714;
}

#home-journey {
  position: relative;
  height: var(--journey-h);
  /* Was var(--marfil) (cream) — that's also <body>'s own background
     (styles.css), so ANY subpixel rounding gap between this element's
     own bottom edge and .footer beginning (a sticky-unpin + fractional
     --journey-h vh-to-px rounding is enough) let a thin cream line
     show through against the RSVP scrim / footer's dark brown —
     correction #35. ink-deep matches both neighbors, so even if a
     stray sub-pixel gap exists, nothing shows through, cream or
     otherwise. */
  background: var(--ink-deep);
}

#home-stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  pointer-events: none; /* just a clipping/pin container — only a world's own links (below) re-enable this */
}

/* ----------------------------------------------------------
   ATMOSPHERE SYSTEM — one full-bleed tone layer per beat, crossfaded
   by the master timeline, always covering the whole stage so no
   page background ever shows at the edges of a world's own artwork.
   Gradients, not flat fills — "richer than flat color" per the brief.
---------------------------------------------------------- */
.stage-atmosphere {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none; /* decorative only — never intercept clicks meant for a world's own links */
}
.atmosphere-layer {
  position: absolute;
  inset: -2vh; /* slight overscan so parallax/scale never reveals an edge */
  opacity: 0;
  background-size: cover;
  background-position: center;
  pointer-events: none;
}
/* Base references from the client: blue #C2DBF0, grey #E7E0DE, night
   #5D6374, green #C3D26F — used as the dominant mid-tone of each
   tonal family below, never as a flat fill (each field still gets a
   lighter top / richer bottom and a soft directional wash). */
.atmosphere-sky {
  background-image:
    radial-gradient(120% 70% at 50% 0%, rgba(255,255,255,0.35) 0%, rgba(255,255,255,0) 55%),
    linear-gradient(180deg, #d8eaf6 0%, #c2dbf0 45%, #9fbedb 100%);
}
.atmosphere-stone {
  /* Legibility/mood patch: the original tones here (#efe9e6/#e7e0de/
     #cfc4bf, all R>G>B by ~9-16) read as warm beige/paper, not grey.
     Neutralized to near-equal R/G/B at each stop (same lightness
     progression, light top to richer bottom) for a proper cool-neutral
     grey wash — scoped to this tone only, .atmosphere-grey (Story's
     own tone, later section) is untouched. */
  background-image:
    radial-gradient(100% 60% at 50% 10%, rgba(255,255,255,0.28) 0%, rgba(255,255,255,0) 60%),
    linear-gradient(180deg, #e9e9e7 0%, #dcdcda 50%, #b8b8b6 100%);
}
.atmosphere-convento {
  background-image:
    radial-gradient(110% 65% at 50% 0%, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 55%),
    linear-gradient(180deg, #ede4d8 0%, #ddd0bf 55%, #c3b39d 100%);
}
.atmosphere-grey {
  background-image:
    radial-gradient(100% 60% at 50% 15%, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0) 60%),
    linear-gradient(180deg, #ece6e3 0%, #e7e0de 45%, #cdc4bf 100%);
}
.atmosphere-dusk {
  background-image:
    linear-gradient(180deg, #7c8598 0%, #5d6374 45%, #363b49 100%);
}
/* Villa's own transitional tone (correction #27) — night at the top,
   green at the bottom, in ONE gradient, so the top/bottom split is
   spatial (baked into the gradient itself) rather than relying on two
   flat-color layers crossfading in time. Endpoints deliberately reuse
   .atmosphere-dusk's own 45% mid-tone (#5d6374) and .atmosphere-olive's
   own 45% mid-tone (#c3d26f) so both handoffs (dusk->villa-mix and
   villa-mix->olive, see home-scenes.js) land on a color each
   neighboring layer already has, instead of introducing new tones at
   the seams. */
.atmosphere-villa-mix {
  background-image:
    radial-gradient(100% 60% at 50% 8%, rgba(255,255,255,0.14) 0%, rgba(255,255,255,0) 55%),
    linear-gradient(180deg, #5d6374 0%, #748497 22%, #9daf91 50%, #b3c378 74%, #c3d26f 100%);
}
.atmosphere-olive {
  background-image:
    radial-gradient(100% 65% at 50% 10%, rgba(255,255,255,0.16) 0%, rgba(255,255,255,0) 60%),
    linear-gradient(180deg, #d6dd9a 0%, #c3d26f 45%, #9aa751 100%);
}

/* ----------------------------------------------------------
   WORLD SHELL — a beat's full layer stack. Absolutely positioned
   inside the pinned stage (not its own sticky frame); the master
   timeline tweens opacity/transform per world, never a hard show/hide.
---------------------------------------------------------- */
.world {
  position: absolute;
  inset: 0;
  z-index: 1;
  opacity: 0;
  pointer-events: none;
  overflow: hidden;
}
/* Only a world's own links/buttons are ever clickable — never the
   whole full-bleed box (see .world's own pointer-events:none above).
   They stay permanently pointer-events:auto and rely on their own
   autoAlpha-driven visibility to gate real interactivity: GSAP's
   autoAlpha sets visibility:hidden at opacity 0, and a hidden element
   is never hit-testable regardless of pointer-events — so a link that
   isn't currently faded in simply can't be clicked, with no need to
   track "which world is active" separately. */
.world a,
.world button { pointer-events: auto; }

.layer {
  position: absolute;
  inset: -4vh; /* overscan for parallax travel without exposing an edge */
  width: calc(100% + 8vh);
  height: calc(100% + 8vh);
  object-fit: cover;
  object-position: center;
  display: block;
  will-change: transform;
}
/* Volcán's own landscape sits lower in its box (correction #4) —
   biasing the crop down instead of moving the whole layer keeps the
   peak from being pushed off the top edge. */
.layer--paisaje { object-position: center 75%; }
/* volcan kept at the SAME default object-position as humo/mas-humo
   (no independent crop bias) — an earlier pass shifted only volcan's
   crop upward, which broke its alignment with the smoke layers next
   to it since they stayed at the default. All three now share the
   same crop so the artists' own built-in alignment holds. */

/* Volcano -> Arco patch: soft blue-to-grey atmospheric blend, scoped
   to Arco only. Sits right above Arco's own cielo in paint order so it
   tints the sky without a hard rectangular seam — top ~75% stays
   untouched blue, the lower quarter eases toward grey where
   casas-frente/the street sit. Faded in by home-scenes.js, not a flat
   color swap. */
.arco-grey-blend {
  position: absolute;
  inset: -4vh;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(
    180deg,
    rgba(184, 181, 172, 0) 0%,
    rgba(184, 181, 172, 0) 62%,
    rgba(178, 174, 164, 0.6) 82%,
    rgba(162, 158, 148, 0.88) 100%
  );
}

/* Volcano -> Arco patch: deterministic stacking for the Arco world's
   own layers once #volcano-persist is reparented in front of them
   (see home-scenes.js) at z-index 1. Each of these needs to render
   IN FRONT of that old volcano group, in this order, so they each get
   an explicit z-index higher than 1 and higher than the one before —
   relying on plain DOM order alone left this non-deterministic. Only
   these five; cielo/grey-blend stay at the default (behind everything
   here, correctly). Direct-child combinator (>), not a plain
   descendant selector — #volcano-persist's OWN [data-layer="volcan"]
   (the old volcano peak) also lives inside this world once reparented,
   nested one level deeper, and must NOT match these rules. */
[data-world="arco"] > [data-layer="volcan"] { z-index: 2; }
[data-world="arco"] > [data-layer="nubes"] { z-index: 3; }
[data-world="arco"] > [data-layer="casas-fondo"] { z-index: 4; }
[data-world="arco"] > [data-layer="arco"] { z-index: 5; }
[data-world="arco"] > [data-layer="casas-frente"] { z-index: 6; }

/* Paisaje (the lower landscape) is a bottom-anchored STRIP now, not a
   full-height layer — it must only ever cover the bottom of the
   frame, never compete with the volcano above it. Overrides the
   shared .layer box sizing (top/height) for this instance only; left/
   right/bottom overscan stay inherited from .layer's own inset. */
/* Sky -> Volcano patch: volcan-volcan's own rest position nudged ~1vh
   (~8px at mobile height) higher than the shared .layer default, so
   the crater sits closer under the smoke instead of leaving a visible
   gap between them. `top` (not `transform`) because GSAP's own `y`
   animation on this element overwrites `transform` on every tick. */
.volcano-persist [data-layer="volcan"] {
  top: -5vh;
}

.volcano-persist [data-layer="paisaje"] {
  top: auto;
  /* -4vh (original) minus another 50% of its own 34vh height, moved
     via `bottom` (not `transform`) — home-scenes.js's own GSAP `y`
     animation on this element fully overwrites `transform` on every
     tick, so a transform:translateY() here would get silently
     discarded the moment the entrance tween runs. `bottom` is a plain
     positional property GSAP never touches, so it's safe. */
  bottom: -21vh;
  height: 34vh;
  object-position: center 100%;
}

/* FULL-BLEED SCALE TARGETS — oversized so a painted illustration edge
   never becomes visible as the layer moves/settles during its own
   entrance. Static baseline scale; home-scenes.js's own entrance
   transforms multiply on top of this, not replace it. */
[data-layer="cielo"] { transform: scale(1.25); } /* covers Arco/Convento/Villa/final-Arco cielo in one rule */
[data-layer="ruinas"] { transform-origin: center bottom; }
[data-world="transition"] [data-layer="night-sky"] { transform: scale(1.25); }
/* RSVP's old separate paisaje (scale 2) + casas-fondo (scale 3) zoom
   treatment removed (correction #31) — replaced by one combined
   "rsvp" illustration (images/home/rsvp/rsvp.png) that already bakes
   both together at a natural scale. No extra zoom here on purpose:
   the whole point of the new asset is a calmer, less zoomed-in
   closing composition, using the same plain cover treatment as every
   other .layer instead of a bespoke multiplier. */

/* GRIS / GRIS 2 — the actual prepped watercolor wash images (not a
   flat CSS fill) behind Nos Vemos' and Story's own photos. */
.layer--wash {
  transform: scale(1.25);
  object-position: center;
}

/* Opening handoff clouds (checkpoint 01) — two independent groups,
   each mostly off their own edge, so roughly half of each is visible
   inside the frame while the rest bleeds off-screen. */
[data-layer="cloud-left"],
[data-layer="cloud-right"] {
  width: 70%;
  height: 45%;
  inset: auto;
  object-fit: contain;
  object-position: center;
}
[data-layer="cloud-left"] { left: -35%; top: 8%; }
[data-layer="cloud-right"] { right: -35%; top: 22%; }

/* ----------------------------------------------------------
   FLOATING OBJECTS — content that must paint above every .world
   regardless of which is active: either it spans a transition
   (boton_historia) or it must stay visually static while a sibling
   .world's own content changes underneath it (volcano-persist). Two
   .world elements can never achieve the latter between each other —
   same stacking level, and an animated opacity<1 forces its own
   stacking context — so this layer exists specifically for that.
---------------------------------------------------------- */
.floating-objects {
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
}

/* VOLCANO-PERSIST — smoke + peak, shared unmoving across the whole
   Volcano -> Arco handoff. Sized/positioned exactly like a world's own
   .layer stack so it lines up with sky-volcano's cielo/paisaje
   underneath it without any per-world alignment tuning. */
.volcano-persist {
  position: absolute;
  inset: 0;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}
.volcano-persist .layer { pointer-events: none; }

/* ----------------------------------------------------------
   COPY STAGE — every text block, above ALL illustration layers
   including volcano-persist's dynamic z-index switch (correction #3).
   Deliberately not a .world: nothing here should ever be at risk of
   sitting under a floating group again.
---------------------------------------------------------- */
.copy-stage {
  position: absolute;
  inset: 0;
  z-index: 8;
  pointer-events: none;
}
.copy-stage a,
.copy-stage button { pointer-events: auto; }

.floating-badge {
  width: min(150px, 38vw);
  opacity: 0;
  pointer-events: auto; /* gated by its own autoAlpha visibility, same reasoning as .world a above */
  transition: transform 0.25s ease;
}
.floating-badge img { display: block; width: 100%; height: auto; }
/* scale only — .floating-badge--historia's resting position no longer
   uses `transform` for centering (correction #18: left/right/margin
   instead, to stay clear of its own GSAP y-tween), so a translateX()
   here would have shifted it sideways on hover instead of just
   scaling it. */
.floating-badge:hover, .floating-badge:focus-visible { transform: scale(1.05); }

/* ----------------------------------------------------------
   HANDOFF CLOUDS — correction #1. Sits above #invitation-intro's own
   z-index:300 so clouds can visually rise over the still-disappearing
   envelope, synced by home-scenes.js to the same scroll range as
   invitation-intro.js's own exit.
---------------------------------------------------------- */
.handoff-clouds {
  position: fixed;
  inset: 0;
  z-index: 310;
  opacity: 0;
  pointer-events: none;
  overflow: hidden;
}
.handoff-cloud {
  position: absolute;
  width: 70%;
  height: 45%;
  object-fit: contain;
}
.handoff-cloud--left { left: -35%; top: 55%; }
.handoff-cloud--right { right: -35%; top: 42%; }

/* ----------------------------------------------------------
   HOME STORY PROGRESS — persistent scroll/story cue, fixed to the
   viewport at one stable location for the entire index experience.
   Unchanged from the previous architecture — still a global overlay,
   not scoped to any one world.
---------------------------------------------------------- */
.home-story-progress {
  position: fixed;
  left: 50%;
  bottom: calc(clamp(20px, 4vh, 48px) + 75px);
  bottom: calc(clamp(20px, 4vh, 48px) + 75px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 310;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  background: none; border: none; padding: 0; margin: 0;
  cursor: pointer;
  text-align: center;
  color: var(--ink);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.6s ease, color 0.5s ease;
}
.home-story-progress.is-visible { opacity: 0.68; pointer-events: auto; }
.home-story-progress.is-visible:hover,
.home-story-progress.is-visible:focus-visible { opacity: 0.95; }
.home-story-progress.is-dark { color: var(--white); }

.home-story-progress-label {
  font-family: var(--font-body);
  font-size: 13px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  white-space: nowrap;
}
.home-story-progress-arrow {
  display: block;
  font-size: 13px;
  line-height: 1;
  animation: home-story-drift 3.6s ease-in-out infinite;
}
@keyframes home-story-drift {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(4px); }
}
@media (prefers-reduced-motion: reduce) {
  .home-story-progress-arrow { animation: none; }
}

/* ----------------------------------------------------------
   COPY — one poetic thought, at most one CTA, per world. Replaces
   the old .scene-content (same visual language, new container).
---------------------------------------------------------- */
.copy {
  position: absolute;
  z-index: 5;
  left: 0; right: 0;
  top: 50%;
  transform: translateY(-50%);
  margin: 0 auto;
  padding: 0 var(--margin-h);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}
/* Two copy blocks can coexist in the same world (sky-volcano merges
   two beats) — position each explicitly instead of both centering
   on top of each other. */
.copy--sky { top: 30%; }
/* Volcano copy keeps the SAME position as the sky copy it replaces —
   patch pass correction #1: the text shouldn't jump to the bottom,
   only the illustration underneath it should change. */
.copy--volcano { top: 30%; }
/* Patch (Volcano -> Arco transition): the countdown belongs to the
   street/casas-frente foreground, not floating up in the sky. */
.copy--countdown { top: auto; bottom: 18vh; transform: none; }
.copy--lower { top: auto; bottom: 12vh; transform: none; }
/* RSVP's houses are scaled ~200% (checkpoint 12) and now reach much
   higher into the frame than a default-centered copy block clears. */
.copy[data-copy-world="rsvp"] { top: 15%; transform: none; }
.copy--on-dark { color: var(--white); }
.copy--on-dark .scene-detail { color: rgba(255, 255, 255, 0.75); }

/* ----------------------------------------------------------
   CONVENTO SANTA CLARA — typographic layout patch. This world's
   illustration (cielo -> nubes -> ruinas) has three distinct empty
   zones — open sky above, the ruin's own central arch/opening, and
   the ground plane at the bottom — so its copy is laid out as three
   independently-positioned pieces keyed to those zones, instead of
   the shared .copy centered-flex-column pattern every other section
   uses. Scoped entirely to .convento-copy/-title/-meta/-cta so
   nothing here reaches Villa or any other .copy--lower/.copy user.
   Visibility/timing is untouched — home-scenes.js's textBeat() still
   only ever tweens the outer .copy div's autoAlpha/y; these rules
   only change where things sit once visible. */
.convento-copy {
  position: absolute;
  /* Override the base .copy centered-flex-column layout entirely —
     children below position themselves independently against this
     box's edges. top AND bottom both need to be 0 (not "auto" on
     either side) for this to actually STRETCH to fill the frame —
     with any side left auto, the box collapses to its own content
     height (0, since every child is position:absolute and removed
     from flow) and children's top/bottom percentages end up measured
     from the wrong edge entirely. */
  inset: 0;
  z-index: 20;
  pointer-events: none;
  transform: none;
  display: block;
  padding: 0;
  gap: 0;
}
.convento-copy .convento-cta { pointer-events: auto; }

/* Qualified as .convento-copy .convento-X (specificity 0,2,0), not
   bare .convento-X — the shared .scene-headline/.scene-detail/
   .scene-cta base rules these elements ALSO carry are defined later
   in this file, and at equal specificity source order wins, which
   silently discarded color/font-size/line-height/text-shadow here.
   The extra ancestor qualifier guarantees these win regardless of
   where the base rules happen to sit. */
.convento-copy .convento-title {
  position: absolute;
  top: calc(8vh + 60px);
  left: 50%;
  transform: translateX(-50%);
  /* Widened (82%->92%) and font-size lowered slightly — "AQUÍ
     COMENZARÁ" (line 1's own <br>-forced content) was wrapping into
     a 4th line at 82%/2.4rem-min on a 375px viewport, breaking the
     required exact 3-line split. max-width:none is the actual fix
     for the wrap, though — .scene-headline's own max-width:16ch (13ch
     under 720px) was silently capping this box's real width to ~2/3
     of what `width` asked for, since max-width and width don't
     conflict via specificity (different properties) and the base
     rule's max-width was never overridden until now. */
  width: min(92%, 520px);
  max-width: none;
  text-align: center;
  color: var(--convento-ink);
  font-size: clamp(2rem, 6.6vw, 4.3rem);
  line-height: 0.95;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  text-shadow: 0 2px 18px rgba(255, 251, 242, 0.7);
  z-index: 21;
}
.convento-copy .convento-meta {
  position: absolute;
  top: 40vh;
  left: 50%;
  transform: translateX(-50%);
  width: min(58%, 300px);
  text-align: center;
  /* Same tone as .convento-title (--convento-ink), not the softer
     --convento-ink-soft — requested to match exactly. */
  color: var(--convento-ink);
  /* 12% bigger than the original clamp(1.1rem, 3.2vw, 1.8rem). */
  font-size: clamp(1.23rem, 3.58vw, 2.02rem);
  line-height: 1.35;
  letter-spacing: 0.02em;
  font-style: normal;
  text-shadow: 0 2px 14px rgba(255, 251, 242, 0.6);
  z-index: 21;
}
.convento-copy .convento-cta {
  position: absolute;
  left: 50%;
  bottom: 9vh;
  transform: translateX(-50%);
  top: auto;
  margin-top: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  color: var(--convento-ink);
  font-size: clamp(1.2rem, 3.8vw, 2rem);
  line-height: 1;
  letter-spacing: 0.02em;
  text-align: center;
  z-index: 21;
  pointer-events: auto;
}
@media (max-width: 430px) {
  /* Same ancestor-qualified selectors as above, and for the same
     reason — font-size here would otherwise lose to .scene-headline's
     own font-size at equal specificity. */
  .convento-copy .convento-title {
    top: calc(7.5vh + 60px);
    width: 92%;
    font-size: clamp(1.9rem, 7.6vw, 3.6rem);
  }
  .convento-copy .convento-meta {
    top: 39vh;
    width: 62%;
    /* 12% bigger than the original clamp(1rem, 3.3vw, 1.4rem). */
    font-size: clamp(1.12rem, 3.7vw, 1.57rem);
  }
  .convento-copy .convento-cta {
    bottom: 8vh;
    font-size: clamp(1.1rem, 4vw, 1.5rem);
  }
}

.scene-eyebrow {
  font-family: var(--font-body);
  font-size: 16px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--muted);
}
.copy--on-dark .scene-eyebrow { color: rgba(255, 255, 255, 0.7); }

.scene-headline {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(28px, 4.2vw, 46px);
  line-height: 1.3;
  max-width: 16ch;
  color: var(--ink);
  text-shadow: 0 2px 18px rgba(255, 251, 242, 0.6);
}
.copy--on-dark .scene-headline {
  color: var(--white);
  text-shadow: 0 2px 20px rgba(20, 18, 12, 0.5);
}

.scene-detail {
  font-family: var(--font-body);
  font-style: italic;
  font-size: 22px;
  color: var(--lino);
  letter-spacing: 0.02em;
}

.scene-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  font-family: var(--font-body);
  font-size: 18px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-deep);
  border-bottom: 1px solid currentColor;
  padding-bottom: 3px;
  transition: gap 0.25s ease, opacity 0.25s ease, color 0.25s ease;
}
.scene-cta span { transition: transform 0.25s ease; }
.scene-cta:hover, .scene-cta:focus-visible { gap: 14px; color: var(--lino); }
.scene-cta:hover span, .scene-cta:focus-visible span { transform: translateX(2px); }
.copy--on-dark .scene-cta { color: var(--white); }
.copy--on-dark .scene-cta:hover { color: var(--miel); }

.scene-cta--muted {
  border-bottom-color: transparent;
  opacity: 0.8;
  font-size: 15px;
}

.scene-cta--badge {
  border-bottom: none;
  padding-bottom: 0;
  margin-top: 8px;
}
.scene-cta--badge img {
  display: block;
  /* width:100% (was its own independent min(168px,45vw)) — that value
     didn't match .villa-copy-group__cta's own width (min(168px,38vw)),
     the only element this rule is used on, so the image quietly
     overflowed its anchor at viewports where vw (not the px cap)
     was binding, throwing off the careful centering over copy-badge.
     100% makes the anchor's own width the single source of truth. */
  width: 100%;
  height: auto;
  transition: transform 0.25s ease;
}
.scene-cta--badge:hover img,
.scene-cta--badge:focus-visible img { transform: scale(1.05); }

/* CIELO-BOKEH — Villa's own sky (renamed off the generic
   [data-layer="cielo"] every other world uses — correction #26),
   fully overriding the generic .layer box instead of just its
   object-fit — correction #27. The generic box (inset:-4vh, full
   viewport height) plus object-fit:contain left most of that tall box
   empty BELOW the actual letterboxed image, which meant a bottom-edge
   mask (below) landed on empty space instead of the image itself —
   its fade stops never touched anything visible. Locking this
   element's own box to the asset's real aspect-ratio (1080/909) makes
   the box's rendered height track the image's own height directly, so
   there's no leftover empty space for the mask math to miss — the
   gradient now dissolves the image's OWN bottom edge, not empty air
   below it. object-position:top keeps it anchored to the sky's place
   in the composition; the mask is what removes its rectangular
   canvas edge so it dissolves into whatever atmosphere layer is
   currently behind it instead of hard-cutting into it. */
[data-world="villa"] [data-layer="cielo-bokeh"] {
  position: absolute;
  inset: auto;
  top: -2vh;
  left: -2vh;
  right: -2vh;
  width: calc(100% + 4vh);
  height: auto;
  aspect-ratio: 1080 / 909;
  object-fit: cover;
  object-position: top center;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 55%, rgba(0, 0, 0, 0.85) 70%, rgba(0, 0, 0, 0.35) 88%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 55%, rgba(0, 0, 0, 0.85) 70%, rgba(0, 0, 0, 0.35) 88%, transparent 100%);
}

/* JARDIN — its own group (correction #9, badge moved out in #24 — see
   .villa-copy-group below) so it can still be animated as its own
   unit independent of the copy card above it. */
.villa-jardin-group { position: absolute; inset: 0; }

/* VILLA-COPY-GROUP — copy-badge (Villa's headline as one lace/paper
   decorative piece, text baked into copy-badge.png, replacing the old
   plain .copy[data-copy-world="villa"] text block) with the "Ver
   todos los eventos" CTA overlapping its lower edge — correction #24,
   same mounting technique as Historia's own photo-frame + CTA earlier
   in the journey. This wrapper is a plain, static frame of reference
   (never touched by GSAP, no transform of its own) — top-anchored and
   sized to sit in the open sky above casa/jardin, with room on every
   side instead of crowding the architecture below it. Both children
   are independently GSAP-animated against this shared box. */
.villa-copy-group {
  position: absolute;
  z-index: 2;
  left: 0;
  right: 0;
  top: clamp(64px, 11vh, 112px);
  margin-inline: auto;
  width: min(320px, 74vw);
  aspect-ratio: 1 / 1;
}
/* Horizontal centering is left:0/right:0/margin-inline:auto — NOT
   left:50%+transform — this element gets a GSAP y-rise entrance, and
   a static transform on the same element would be silently
   overwritten the moment GSAP composes its own (same reasoning as
   .story-photo-object--tray and .floating-badge--historia earlier
   this project) — correction #23. No rotation (removed per request —
   correction #26; was `rotate(10deg)` via gsap.set in home-scenes.js). */
.copy-badge {
  position: absolute;
  inset: 0;
  transform: none;
  filter: drop-shadow(0 14px 26px rgba(20, 18, 12, 0.25));
}
.copy-badge img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
/* CTA mounted on the copy card's own bottom edge, ~30% of the card's
   own height overlapping upward onto it (bottom is a plain positional
   property, not transform, for the same GSAP-coexistence reason as
   copy-badge above — this element's own entrance also animates `y`).
   z-index above copy-badge so it visibly paints in front, reading as
   physically attached rather than floating separately below it. */
.villa-copy-group__cta {
  position: absolute;
  z-index: 3;
  left: 0;
  right: 0;
  bottom: -69px; /* was -29px, moved 40px down on request */
  margin-inline: auto;
  width: min(168px, 38vw); /* +12% over 150px/34vw — Villa CTA enlarged on request */
  /* Soft ornamental lift so the badge reads as a physical card sitting
     above copy-badge, not flat against it. */
  filter: drop-shadow(0 10px 18px rgba(20, 18, 12, 0.25));
}

/* ----------------------------------------------------------
   COUNTDOWN — typography-as-composition, no boxed widget. Reuses the
   live countdown engine already in site.js (initCountdown ->
   #cd-days/#cd-hours/#cd-minutes).
---------------------------------------------------------- */
.scene-countdown-value {
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  column-gap: 0.35em;
  /* Split from a single `gap` shorthand: on narrow viewports this row
     wraps ("246 días · 23 horas" / "22 min" on two lines), and the
     default 0.35em row-gap left too much air between those two
     wrapped lines — tightened separately from the horizontal spacing
     between day/hour/min groups, which stays the same. */
  row-gap: 0.05em;
  font-family: var(--font-body);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.scene-countdown-value strong {
  font-size: clamp(40px, 8vw, 84px);
  font-weight: 500;
  min-width: 2ch;
  text-align: center;
  display: inline-block;
}
.scene-countdown-unit {
  font-size: 16px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* Legibility patch: var(--muted) (#8a7f63, pale olive-beige) was
     hard to read over sky/smoke — swapped for --ink, the same warm
     near-black used for headlines elsewhere, not a new color. */
  color: var(--ink);
  margin-right: 0.4em;
  /* Legibility patch: thin text-stroke reinforces this small
     uppercase label's thin strokes against busy sky/smoke — same
     dark tone as the fill, so it just thickens the letterforms
     slightly rather than adding a visible outline color. */
  -webkit-text-stroke: 0.5px var(--ink);
}
.scene-countdown-dot { color: var(--olivo); opacity: 0.7; }
.scene-countdown-sub {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 22px;
  color: var(--lino);
}
/* Volcano->Arco patch: stronger vertical hierarchy. "Faltan" + the
   numbers sit up in the sky/cloud area, well clear of the arch below;
   the supporting line sits well below that, opening real breathing
   room between the two. Legibility patch: pulled the group's own lift
   back by ~18vh (~140px mobile), from -35vh to -18vh — at -35vh it sat
   glued to the top edge, right against the nav; -18vh keeps it clearly
   in the upper sky with real margin instead, still nowhere near the
   volcano smoke. Both are safe as plain CSS transforms/offsets —
   textBeat() only ever tweens the parent .copy div, never these
   children directly. Soft light text-shadow added on both pieces —
   same treatment .scene-headline already uses elsewhere for text over
   illustrations — since this sits directly over sky/smoke of varying
   brightness. */
.copy--countdown {
  /* Refinement: tighter column gap for this block specifically (the
     base .copy gap is 18px) — "Faltan" and the numbers should read as
     one compact, connected group, not spread out. Cut further (8px ->
     2px) since "Faltan" now also moves independently above the
     numbers (see below) and needs to read as close to them as the
     layout gap can get. */
  gap: 2px;
}
.copy--countdown .scene-eyebrow {
  /* Refinement: darkened to match the numbers/labels — was still
     using the base .scene-eyebrow's var(--muted) (pale olive-beige). */
  color: var(--countdown-deep);
  /* Refinement: "Faltan" now moves independently, 75px higher than
     the numbers below it (which stay exactly where they were — see
     .scene-countdown-value) — no longer sharing one rigid transform
     with them. calc() keeps it relative to the same -40vh baseline
     instead of a separate hardcoded value. */
  transform: translateY(calc(-40vh - 75px));
  text-shadow: 0 2px 18px rgba(255, 251, 242, 0.7);
}
.copy--countdown .scene-countdown-value {
  /* Refinement: the requested 80px-more-on-top-of-55px would total
     -135px, but the measured gap at -55px was only 22px (eyebrow
     bottom at y=184, value top at y=206) — another 80px would flip
     the numbers above "Faltan" again, same break as the earlier 200px
     attempt. Capped at 20px more (75px total) instead: closes the gap
     to ~2px, as tight as it can get without the two blocks colliding.
     "Faltan" and "para encontrarnos" both stay exactly where they
     were. */
  transform: translateY(calc(-40vh - 75px));
  text-shadow: 0 2px 18px rgba(255, 251, 242, 0.7);
}
.copy--countdown .scene-countdown-sub {
  /* Refinement: 30px lower than the previous pass's 15px (now 45px)
     — still a plain transform (margin-top doesn't work on this last
     child since .copy--countdown is bottom-anchored with auto height:
     it only grows the column upward instead of moving this element). */
  transform: translateY(45px);
  /* Refinement: matches "Faltan" — deep near-black green instead of
     var(--lino), which was too light against sky/smoke. */
  color: var(--countdown-deep);
  text-shadow: 0 2px 18px rgba(255, 251, 242, 0.7);
}
/* Line break only forced on mobile — desktop keeps its own natural
   wrap/one-line reading, unaffected. */
.scene-countdown-sub-break { display: inline; }

/* ----------------------------------------------------------
   INVITATION REVEAL CARD — legacy typography reference for the
   envelope's own card (styles.css .intro-reveal echoes this), kept
   as-is; unrelated to the journey rebuild.
---------------------------------------------------------- */
.invitation-reveal-card {
  position: relative;
  z-index: 5;
  width: min(420px, 78vw);
  aspect-ratio: 4 / 4.3;
  margin: auto;
  padding: 15% 12%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  text-align: center;
  border-radius: 2px;
  background: linear-gradient(165deg, #fffdf8 0%, var(--marfil) 55%, #f4ecdb 100%);
  box-shadow: 0 4px 14px rgba(35, 28, 16, 0.16);
}
.invitation-reveal-kicker {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 17px;
  color: var(--lino);
}
.invitation-reveal-names {
  font-family: var(--font-script);
  font-weight: 400;
  font-size: clamp(32px, 6vw, 52px);
  line-height: 1.15;
  color: var(--ink);
}
.invitation-reveal-names .script { color: var(--lino); }
.invitation-reveal-date {
  font-family: var(--font-body);
  font-size: 16px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ----------------------------------------------------------
   FRAMED PHOTOS — Nos Vemos' photo-frame.webp (its own tray position,
   correction #6) and Story's own, distinct photo-alt.webp, ~75% larger
   than the shared default (correction #8) and grouped with
   boton_historia so they travel together — see .story-photo-group.
---------------------------------------------------------- */
.story-photo-object {
  position: absolute;
  z-index: 4;
  left: 50%;
  top: 40%;
  width: min(240px, 56vw);
  aspect-ratio: 736 / 1104;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 18px 30px rgba(20, 18, 12, 0.28));
}
.story-photo-object img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
/* TRAY — its own resting position, not the shared centered default:
   high, near the nav, tilted, with negative space below for text
   (correction #6). */
.story-photo-object--tray {
  top: 0;
  width: min(310px, 68vw); /* ~30% larger than the 240px shared default — patch pass correction #9 */
  /* transform is owned entirely by GSAP now (xPercent:-50 + rotation:15,
     set in home-scenes.js right before its riseIn tween) — correction
     #17. A static translateX(-50%) here fought GSAP's own y-animation
     for the same computed transform: once GSAP composed its first
     frame it discarded this rule's contribution, so the tray lost its
     horizontal centering the moment its rise-in tween ran. */
  transform: none;
  margin-top: calc(80px + env(safe-area-inset-top));
}

/* CAMINAR + BOTON_HISTORIA — one physical group (correction #8),
   editorial-composition patch: the group is now top-anchored (was
   vertically centered at 40%, landing in the same zone as the
   headline above it) so it reads as headline -> frame -> badge, top
   to bottom, with real separation from the text. Top-anchoring
   (top:320px + transform:translateX(-50%) only, no Y-transform
   component) also coexists cleanly with this element's own GSAP
   riseIn() entrance below — that tween animates y from 12vh to 0, and
   a rest state of y:0 lines up exactly with this being purely
   top-anchored; a translateY(-50%) here would have been silently
   discarded the first time GSAP touched the element anyway (its own
   y:0 rest value would win), same lesson as the volcano-persist
   paisaje fix much earlier in this project. */
.story-photo-group {
  position: absolute;
  z-index: 4;
  left: 50%;
  top: 272px;
  width: min(677px, 96vw); /* +10% over the previous 615px/87vw (correction #19) */
  aspect-ratio: 736 / 1104;
  /* transform owned entirely by GSAP (xPercent:-50, set in
     home-scenes.js right before its riseIn tween) — same fix as
     .story-photo-object--tray above (correction #17): a static
     translateX(-50%) here was overwritten the moment GSAP's own
     y-animation composed the transform, shifting the group off-center. */
  transform: none;
}
.story-photo-object--caminar {
  position: absolute;
  inset: 0;
  transform: none;
  width: auto;
  z-index: 1; /* explicitly behind the badge below — correction #21 */
  filter: drop-shadow(0 18px 30px rgba(20, 18, 12, 0.28));
}
/* Badge mounted ON TOP of the frame's lower portion, not hanging below
   it — bottom:-45px (a plain positional property, not transform) drops
   it 75px lower than the previous 30px pass, further into/over the
   photo (correction #21). z-index:2, explicitly above the caminar
   photo's own z-index:1, so stacking no longer depends on DOM order
   alone.
   Horizontal centering is `left:0; right:0; margin-inline:auto` —
   NOT `left:50%+ transform:translateX(-50%)` — because this element's
   own entrance animates `y` via GSAP (14px -> 0, then a fade-out);
   left/right/margin centering never touches `transform`, so GSAP's
   y-only tween can never overwrite it (correction #18).
   Size +25% over the previous min(150px,34vw) (correction #20). Drop
   shadow added so the enlarged badge reads as layered above the photo
   rather than flat against it — same warm shadow tone as the frame's
   own filter above, softer/tighter since the badge is much smaller. */
.floating-badge--historia {
  position: absolute;
  left: 0;
  right: 0;
  top: auto;
  bottom: -45px;
  z-index: 2;
  margin-inline: auto;
  width: min(188px, 43vw);
  filter: drop-shadow(0 10px 18px rgba(20, 18, 12, 0.28));
}

/* Nos Vemos' text sits a bit higher than before, balancing against the
   now-larger tray above it — patch pass correction #9. */
.copy[data-copy-world="nos-vemos"] { top: auto; bottom: 15vh; transform: none; }

/* ----------------------------------------------------------
   STORY ("Después de encontrarnos...") — editorial layout patch.
   Was vertically centered (same default as everything else), landing
   in almost the same zone as the photo group below it and reading as
   "text glued to the photo." Now an explicit top-anchored headline,
   then the frame further down, then the badge mounted on the frame's
   own bottom edge — headline -> frame -> badge, in that vertical
   order, with real air between each.
---------------------------------------------------------- */
.copy[data-copy-world="story"] {
  top: 85px;
  bottom: auto;
  transform: none;
}
.copy[data-copy-world="story"] .scene-headline {
  /* max-width:none overrides .scene-headline's own max-width (16ch,
     13ch under 720px) — same fix as Convento's title needed: width
     and max-width are different properties, so max-width silently
     caps this box regardless of what width says unless explicitly
     cleared here. */
  width: min(84%, 520px);
  max-width: none;
  margin: 0 auto;
  text-align: center;
  text-transform: uppercase;
}

/* ----------------------------------------------------------
   EXPLORE — a numbered editorial list (not cards, not a button grid).
   Structural logic (row grid, border rhythm, generous touch targets)
   referenced from the Jatnna & Rainerd project's own "Explora la
   boda" section — colors/typography are this project's own, only the
   spacing/grid pattern was reused. Rebuilt (correction #28) because
   .explore-frame had no top padding at all, so the list started at
   y:0 and collided with .copy.explore-heading (absolutely positioned
   at top:14% in #copy-stage, a separate stacking layer) instead of
   sitting cleanly below it.
---------------------------------------------------------- */
.explore-frame {
  position: relative;
  z-index: 5;
  max-width: 640px;
  margin: 0 auto;
  /* top clears the heading (top:14%, ~2 lines) with real air below it;
     Jatnna's own reference used a plain fixed top padding + margin-top
     on the list for this same gap, done here as one clamp so it scales
     with viewport height instead of a fixed px value. */
  padding: clamp(150px, 26vh, 230px) var(--margin-h) 40px;
  text-align: center;
}
.copy.explore-heading {
  position: absolute;
  top: 14%;
  left: 0; right: 0;
  transform: none;
  display: block;
  width: min(82%, 620px);
  max-width: none;
  margin: 0 auto;
  font-family: var(--font-display);
  font-size: clamp(30px, 6vw, 44px);
  line-height: 1.05;
  font-weight: 500;
  text-align: center;
  color: var(--ink-deep);
}
.explore-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  margin-top: 24px;
  border-top: 1px solid rgba(37, 26, 21, 0.2);
  text-align: left;
}
/* Each row is its own grid — number / eyebrow+title / arrow columns,
   never overlapping. Correction #30: the number column is now a real
   graphic column (not a small metadata digit), so row height/columns
   were rebuilt around it — min-height/padding increased so a large
   01-05 has room to breathe alongside the category+title stack. */
.explore-list__item {
  display: grid;
  grid-template-columns: clamp(72px, 20vw, 110px) 1fr clamp(28px, 7vw, 42px);
  align-items: center;
  column-gap: clamp(12px, 3vw, 20px);
  min-height: 112px;
  padding: 20px 0;
  border-bottom: 1px solid rgba(37, 26, 21, 0.2);
  text-align: left;
}
/* Large graphic numeral — the row's main visual anchor, not a small
   label. font-weight left at normal (400): --font-display already
   reads elegant at this size without needing extra weight. */
/* font-family swapped to --font-script (New Icon Script) for a more
   decorative/fancy numeral — size/column/alignment/rest color
   untouched. */
.explore-list__num {
  font-family: var(--font-script);
  font-size: clamp(64px, 14vw, 92px);
  line-height: 0.82;
  letter-spacing: -0.03em;
  font-weight: 400;
  color: rgba(37, 26, 21, 0.55);
}
.explore-list__link { display: flex; flex-direction: column; gap: 4px; }
.explore-list__eyebrow {
  font-family: var(--font-body);
  font-size: clamp(17px, 4vw, 23px);
  line-height: 1;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(37, 26, 21, 0.65);
}
.explore-list__title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(25px, 5.2vw, 36px);
  color: var(--ink-deep);
  line-height: 1;
}
.explore-list__arrow {
  font-family: var(--font-body);
  font-size: clamp(26px, 6vw, 36px);
  color: var(--ink-deep);
  opacity: 0.7;
  text-align: right;
}
/* Smooth color transition on every interactive piece of the row, not
   just the arrow's own transform/opacity. */
.explore-list__num,
.explore-list__eyebrow,
.explore-list__title,
.explore-list__arrow {
  transition: color 0.25s ease, transform 0.25s ease, opacity 0.25s ease;
}
.explore-list__item:hover .explore-list__arrow,
.explore-list__link:focus-visible ~ .explore-list__arrow { transform: translateX(4px); opacity: 1; }
/* --dusty-pink still didn't hold enough contrast against
   wash-green-yellow's own yellow-green tones — swapped for
   --white, applied across the whole row (number, eyebrow, title,
   arrow) on both hover AND keyboard focus (:focus-within on the row),
   so it reads as one coherent state reacting together, not a single
   line changing color while its neighbors stay put. */
.explore-list__item:hover .explore-list__num,
.explore-list__item:hover .explore-list__eyebrow,
.explore-list__item:hover .explore-list__title,
.explore-list__item:hover .explore-list__arrow,
.explore-list__item:focus-within .explore-list__num,
.explore-list__item:focus-within .explore-list__eyebrow,
.explore-list__item:focus-within .explore-list__title,
.explore-list__item:focus-within .explore-list__arrow {
  color: var(--white);
}

/* ----------------------------------------------------------
   RSVP CLOSING — the one world allowed a primary CTA
   ("Confirmar asistencia") plus one secondary text link ("Ver los
   detalles"). Illustration is just cielo + clouds + the combined
   rsvp.png (correction #31) — no per-layer object-position/zoom
   overrides needed, plain .layer cover is enough for a calm,
   non-zoomed composition.
---------------------------------------------------------- */
/* Primary CTA (correction #32): was inheriting the global .btn/
   .btn-primary block-level pill (width:100%, font-size:19px,
   padding:16px 26px) meant for full-width form buttons — oversized
   here. Now a small inline pill, single line, pushed down (was
   margin-top:6px, now 306px, i.e. +300 per request) so it sits low in
   the frame instead of crowding the headline, which is untouched. */
.copy[data-copy-world="rsvp"] .scene-rsvp-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  max-width: none;
  font-size: clamp(15px, 3vw, 19px);
  line-height: 1;
  letter-spacing: 0.12em;
  padding: 14px 28px;
  white-space: nowrap;
  border-radius: 999px;
  margin-top: 306px;
  margin-bottom: 0;
  background: var(--ink-deep);
  color: var(--white);
}
/* Global .btn-primary:hover uses --lino (olive/green) — overridden to
   plain black here, per request ("no green hover"). */
.copy[data-copy-world="rsvp"] .scene-rsvp-primary:hover,
.copy[data-copy-world="rsvp"] .scene-rsvp-primary:focus-visible {
  background: #000;
  color: var(--white);
}
/* "Ver los detalles" (correction #31, spacing recalculated in #32): a
   genuinely small, underlined, de-emphasized text link — not the
   pill/badge feel of .scene-rsvp-primary above it. Was margin-top:500px,
   measured against the OLD (much higher) primary CTA position; now
   that CTA sits ~300px lower, 500px would have pushed this far past
   the frame. A normal fixed gap reads correctly instead. */
.copy[data-copy-world="rsvp"] .scene-cta--muted {
  margin-top: 28px;
  font-size: 14px;
  white-space: nowrap;
  color: var(--ink-deep); /* "brown" per request — this token already is one */
  border-bottom-color: currentColor; /* restore the underline .scene-cta--muted otherwise removes */
}
/* Base .scene-cta:hover uses --lino (an olive/green tone) — swapped to
   plain black for this link specifically, per request ("no green
   hover here"). Equal specificity to the base rule, but declared later
   in the cascade, so it wins without needing !important. */
.copy[data-copy-world="rsvp"] .scene-cta--muted:hover,
.copy[data-copy-world="rsvp"] .scene-cta--muted:focus-visible {
  color: #000;
}

/* RSVP -> FOOTER, correction #34 — replaces the duplicated-illustration
   bleed attempt (correction #33), which read as a repeated/cut second
   copy of the artwork. New approach: no second image anywhere.
   1) rsvp.png shown in FULL (object-fit:contain, never cropped),
      bottom-aligned in its world.
   2) A plain color scrim behind it fades from transparent (cielo's own
      blue watercolor shows through untouched at the top) to the exact
      footer brown at the bottom, so the frame's own lower edge already
      IS footer-brown before the footer even begins — the illustration
      then simply sits on top of that color, once, nowhere else. */
.rsvp-brown-scrim {
  /* inset:0 (was -4vh, matching the generic .layer overscan) —
     correction #35. With the overscan, this box's own bottom edge sat
     4vh BELOW the actual visible viewport edge, so the gradient's own
     100%-opaque stop landed there instead — at the real visible
     bottom, the color was only ~92% opaque, reading as an
     off-brown, not the exact solid ink-deep the footer uses. inset:0
     makes "100%" mean the real bottom. The last 8% is held flat at
     full opacity (no interpolation left to round short of solid) so
     the visible edge is unambiguously 100% var(--ink-deep), not still
     mid-fade. */
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(37, 26, 21, 0) 0%, rgba(37, 26, 21, 0) 55%, var(--ink-deep) 92%, var(--ink-deep) 100%);
}
/* Belt-and-suspenders against sub-pixel rounding between a
   position:sticky element un-pinning and the next normal-flow element
   — #home-journey's own height is a vh value (--journey-h), which can
   round to a fractional px at some viewport heights, leaving a
   sub-pixel gap. Scoped to THIS page's structure (footer directly
   follows #home-journey only here — styles.css's shared .footer rule,
   reused on other pages with different preceding elements, is left
   alone) rather than a global fix. Both sides are var(--ink-deep) now
   anyway (see #home-journey and .rsvp-brown-scrim above), so this is
   purely defensive — nothing but a solid color could ever show
   through, even if the 1px pull ever became visible itself. */
#home-journey + .footer { margin-top: -1px; }

[data-world="rsvp"] [data-layer="rsvp"] {
  /* inset:0 (not the generic .layer's inset:-4vh overscan) — with
     object-fit:contain + object-position:bottom, "bottom" needs to
     mean the ACTUAL visible viewport edge; the standard -4vh overscan
     box would push the image's own bottom 4vh below the fold, cropping
     exactly the edge this change is meant to keep fully visible. */
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: bottom center;
}

/* ----------------------------------------------------------
   RESPONSIVE — desktop gets more room per beat; mobile is the
   authored default above.
---------------------------------------------------------- */
@media (min-width: 900px) {
  /* 3515 -> 3860 (correction #29): same ratio to the new mobile
     2750/3020 base as before (3515/2750 ≈ 1.278), just carried
     forward against the new total instead of recomputed from scratch. */
  :root { --journey-h: 3860vh; }
}
@media (max-width: 720px) {
  .scene-headline { max-width: 13ch; }
  .home-story-progress {
    bottom: calc(clamp(20px, 4vh, 48px) - 25px);
    bottom: calc(clamp(20px, 4vh, 48px) - 25px + env(safe-area-inset-bottom));
  }

  /* MOBILE PATCH 01b: whole countdown group (Faltan + numbers + sub)
     lifted on mobile only — "MIN" was competing with the volcano/arch
     artwork below it and needed clearer sky around it. Tuned down from
     the initially-suggested 150px (which pushed "Faltan" off the top,
     under the nav) to 60px — verified in-browser to clear both the nav
     above and the arch below. Desktop keeps its existing -40vh/-75px/
     45px positions untouched. */
  .copy--countdown .scene-eyebrow,
  .copy--countdown .scene-countdown-value {
    transform: translateY(calc(-40vh - 75px - 60px));
  }
  .copy--countdown .scene-countdown-sub {
    transform: translateY(calc(45px - 60px));
  }

  /* Force "para encontrarnos / en Antigua" onto two lines on mobile —
     browser-decided wrapping read awkwardly at this width. Desktop
     keeps the single-line/natural-wrap reading via the inline default
     above. */
  .scene-countdown-sub-break { display: block; }

  /* Scene-specific position while Arco's countdown copy is active (see
     home-scenes.js updateStoryProgressTone) — pushed further down than
     the default mobile position so "CONTINÚA" never sits on top of
     "para encontrarnos en Antigua" above it, now that copy has its own
     two-line height plus the -150px lift. */
  .home-story-progress.is-arco-countdown {
    bottom: calc(clamp(20px, 4vh, 48px) - 45px);
    bottom: calc(clamp(20px, 4vh, 48px) - 45px + env(safe-area-inset-bottom));
  }
}

/* ----------------------------------------------------------
   REDUCED MOTION — stable, in-flow, readable; no pinning, no
   parallax, no scale. home-scenes.js renders all worlds as plain
   stacked sections instead of building the master timeline.
---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  #home-journey { height: auto; }
  #home-stage { position: static; height: auto; }
  .world { position: relative; opacity: 1; pointer-events: auto; min-height: 70vh; padding: 60px 0; }
  .layer { position: absolute; will-change: auto; }
  .copy { position: relative; top: auto; bottom: auto; transform: none; padding: 24px var(--margin-h); }
  /* Convento's three independently-absolute-positioned pieces (see
     the CONVENTO SANTA CLARA layout patch above) need their own
     reset here too — otherwise they'd stay position:absolute inside
     the now position:relative .convento-copy and stack on top of
     each other instead of reading as a normal stacked block. */
  .convento-title, .convento-meta, .convento-cta {
    position: static;
    left: auto; top: auto; bottom: auto;
    transform: none;
    width: auto;
    margin: 0 auto;
  }
  .story-photo-object { position: relative; left: auto; top: auto; transform: rotate(-2deg); margin: 24px auto; }
  .floating-objects { display: none; } /* spans transitions that don't exist in the static fallback */
  .explore-frame { padding: 40px var(--margin-h); }
}
.reduced-motion #home-journey { height: auto; }
.reduced-motion #home-stage { position: static; height: auto; }
.reduced-motion .world { position: relative; opacity: 1; pointer-events: auto; min-height: 70vh; padding: 60px 0; }
.reduced-motion .story-photo-object { position: relative; left: auto; top: auto; transform: rotate(-2deg); margin: 24px auto; }
.reduced-motion .floating-objects { display: none; }

/* ----------------------------------------------------------
   DEBUG MODE — set DEBUG_HOME_SCENES = true in home-scenes.js.
---------------------------------------------------------- */
[data-debug-scenes="true"] .world { outline: 1px dashed rgba(200, 40, 20, 0.55); outline-offset: -1px; }
.scene-debug-hud {
  position: fixed; left: 12px; bottom: 12px; z-index: 400;
  font: 11px/1.5 monospace;
  color: var(--white);
  background: rgba(20, 18, 12, 0.78);
  padding: 8px 12px;
  border-radius: 4px;
  pointer-events: none;
  white-space: pre;
}
