/* ==========================================================================
   HOME — page sheet.
   Loads AFTER /assets/style.css and owns nothing the core already owns.
   Everything here is spacing and one flex row: layout, colour, type and all
   five motion mechanics come from the core.

   Owned by the home agent. Four pages, one sheet each; the core is shared.
   ========================================================================== */


/* ==========================================================================
   1. HERO — THE LOCKUP, location B (the closing band's mirror)
   Rings top-left, facts top-right, and the giant photo-filled serif line
   flush bottom-left: the same line, corner, face and fill the closing band
   ends on. The page opens and closes on "to my vows". The h1 is visually
   hidden; everything visible here is aria-hidden presentation.
   ========================================================================== */

.hero { padding-block-start: 0; }

/* On a phone the stage is CONTENT-SIZED. It used to run 94vh tall with the
   lockup pushed to the bottom by margin-top:auto, which on desktop is the
   composition — a tall column of air with the brand sitting in the corner —
   but on a phone put 41–54% of the first screen (274px on an SE, 505px on a
   Pro Max) into empty ground and pushed both buttons past the fold on every
   size. It got worse the bigger the phone got. Now the lockup sits just under
   the bar, the photograph lands inside the first screen, and the lede and
   buttons follow it instead of starting a screen and a half down.
   The full-height composition is restored at 901px, where it belongs. */
.hero__stage {
  display: flex;
  flex-direction: column;
  padding-inline: var(--pad);
  /* --nav-h FIRST, then the air: the bar hands its slot back to the page with
     a negative margin, so this block starts at y=0 and has to step over the
     header itself. A flat 6vh looked right at 844 and printed the facts line
     through the nav at 667. */
  padding-block-start: calc(var(--nav-h) + clamp(.75rem, 2.5vh, 2rem));
}
@media (min-width: 901px) {
  .hero__stage {
    /* svh, not vh: on a phone vh is the LARGE viewport, so the browser's own
       chrome was added on top of a height already measured to fill the screen.
       Harmless at this width, correct if the breakpoint ever moves. */
    min-height: calc(94vh - var(--nav-h));      /* fallback first */
    min-height: calc(94svh - var(--nav-h));
    padding-block-start: 0;
  }
}

/* --- the header is core-owned: one .mini-nav sitewide, styled in
   style.css section 7. Nothing about it belongs to this page.
   (Three superseded descriptions used to sit here — a second "branded bar"
   that took over on scroll, and a mix-blend-mode exclusion trick with "the
   collision JS retired". Neither was ever what shipped, and style.css said
   something different again. The bar is transparent at the top of the page,
   takes the ground under it once scrolled, and steps out of the way while
   you read downward.) */

/* the bottom row: the lockup holds the left corner, the facts hold the
   right, bottom-aligned — clear of the header's territory entirely */
.hero__bottom {
  margin-top: auto;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
}
.hero-facts {
  display: flex;
  gap: clamp(1.5rem, 3vw, 3rem);
  padding-block-end: clamp(1.6rem, 3.5vh, 2.6rem);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--navy);
  white-space: nowrap;
}
@media (max-width: 900px) {
  .hero__bottom { flex-direction: column; align-items: flex-start; gap: 1.4rem; }
  .hero-facts { order: -1; padding-block-end: 0; }
}
.hero-lock__rings {
  display: block;
  height: clamp(2.4rem, 4vw, 3.4rem);
  width: auto;
  margin: 0 0 clamp(.9rem, 2vh, 1.4rem) .5rem;
}

.hero-lock {
  padding-block-end: clamp(1rem, 2.5vh, 1.8rem);
}
/* the script leads and flows into the serif's upper edge: the pun reads
   "Rush me to my vows" as one gesture */
.hero-lock__script {
  margin: 0 0 -.34em .3em;
  position: relative;
  z-index: 1;
  font-family: var(--script);
  font-weight: 400;
  font-size: clamp(2.6rem, 5.6vw, 5.4rem);
  line-height: 1.1;
  color: var(--navy);
  transform: rotate(-3deg);
  transform-origin: 0 100%;
  width: fit-content;
}
/* the closing band's exact treatment, opening the page: same serif, same
   dusk photograph, same .30 veil, one shared image fetch for both ends */
.hero-lock__vows {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(3.4rem, 15vw, 14.6rem);
  line-height: .84;
  letter-spacing: -.04em;
  white-space: nowrap;
  background-image:
    linear-gradient(rgba(36, 28, 22, .30), rgba(36, 28, 22, .30)),
    url(/assets/close-fill-1800.webp);
  background-size: cover, cover;
  background-position: center, center 42%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Small screens: zoom the photograph inside the letters.
   `cover` shows the SAME crop at every width, so the composition is identical
   on a phone — it is just rendered 3.7x smaller, and at 58px of type the
   photograph averages out into a flat brown smudge. Enlarging the fill puts
   fewer, bigger features inside each letterform, which is what makes it read
   as a photograph on desktop.
   The vertical anchor stays at 42%, exactly as on desktop, so this is the
   same part of the photograph — only magnified. 450% is measured, not
   guessed: the box holds a constant 4.04 aspect, so `cover` resolves to 227%
   at every mobile width, and anything below that would let the ground show
   through the strokes. */
@media (max-width: 900px) {
  .hero-lock__vows { background-size: cover, auto 450%; }
}

/* entrance: the lockup owns the immediate slot; the band fades as it rises */
html.js .hero__bottom { animation: lock-rise .8s var(--ease) both; }
@keyframes lock-rise { from { transform: translateY(28px); } }

html.js .hero__bg { animation: band-rise-fade .8s var(--ease) both; }
@keyframes band-rise-fade {
  from { opacity: .01; transform: translateY(min(160px, 18vh)); }
}

@media (prefers-reduced-motion: reduce) {
  html.js .hero__bottom,
  html.js .hero__bg { animation: none !important; }
}

/* The photograph sits BELOW the headline, full bleed, its box already
   reserved by the core so nothing shifts when it decodes. */
.hero__bg { margin-block-start: clamp(2.25rem, 5.5vw, 4.5rem); }

/* Zoomed out 10 percent, on the SCALE rather than the box. The 16:7 letterbox
   is the original and stays; what shrinks is .wp's 1.12 pre-scale, down to
   1.008. Visible height of the photograph goes from 59 to 65 percent of the
   frame.
   The cost, because these are the same pixels twice: that 12 percent was the
   hidden band window-parallax drifts through. At 1.008 there is ~1 percent
   left, so the hero photo now sits still where it used to travel on scroll.
   No edge can show either way (JS derives travel from live geometry), and
   every other .wp photo on the site keeps its full drift. */
.hero__bg img { --wp-scale: 1.008; }

/* Phones and tablets get a real pre-scale back, because the crop trick above
   cannot work here: at 900px and under the core switches this box to 4:3
   against a 3:2 photograph, so `cover` crops SIDEWAYS and there is no
   vertical band left to drift through. 1.05 buys back just enough hidden
   band for the whisper — the drift measures ~14px on a 390px phone, well
   under the module's 22px mobile ceiling. Desktop keeps the 10 percent
   zoom-out at 1.008 and drifts through its crop instead. */
@media (max-width: 900px) {
  .hero__bg img { --wp-scale: 1.05; }
}

.hero__foot {
  padding-block-start: clamp(1.75rem, 4vw, 3rem);
  row-gap: clamp(1.25rem, 2.8vw, 2rem);
}

/* Stacked, the lede was a 517px block with 891px of dead space beside it and
   the buttons marooned on their own row underneath. Sharing the row gives the
   hero a real bottom edge: copy left, the two calls to action opposite it. */
@media (min-width: 901px) {
  .hero__foot > .lede { grid-column: 1 / span 6; }
  .hero__foot > .hero__cta {
    grid-column: 8 / -1;
    align-self: end;
    justify-content: flex-start;
  }
}

.hero__cta { display: flex; flex-wrap: wrap; gap: .75rem .9rem; }

/* below ~420px two side-by-side buttons squeeze the labels onto three lines */
@media (max-width: 420px) {
  .hero__cta { display: grid; gap: .7rem; }
}


/* ==========================================================================
   2. QUIET BEAT
   A statement and one plain sentence, then straight into the ticker — so the
   section closes on --space-block rather than the full --space-page, or the
   thin strip ends up marooned in 300px of nothing.
   ========================================================================== */


/* --- MANIFESTO FILL ------------------------------------------------------
   The sentence sits dim and lights char-by-char in reading order as the
   section scrolls through its band, each character hopping up as it
   brightens. Pure function of scroll, so it scrubs identically both ways.

   ONE DELIBERATE DEPARTURE FROM THE SHIPPED COMPONENT, and it is not
   optional: its dim state is opacity 0.2. Composited over this ground that
   measures 1.48:1 — a hard AA failure, and this is body-weight statement
   copy a reader is meant to be able to read before they ever scroll. The
   measured floor for 4.5:1 here is 0.65; --bf-unlit sits at 0.68 (~4.9:1)
   and the lit state is full --ink (11.8:1). Every intermediate value in the
   fade tail is therefore also above AA, because the ramp only interpolates
   between those two.

   Prefixed .bf* per the component's standing rule: new deployment, new
   prefix, so two fills could never collide on one page. */
.bfw { display: inline-block; white-space: nowrap; }

.beatfill.bf--js .bfc {
  display: inline-block;
  opacity: var(--bf-unlit, .68);
  /* a small hop, not a slide: the character rises into its line as it lights */
  transform: translate3d(0, var(--bf-popy, .16em), 0);
  transition: opacity var(--bf-opd, .3s) linear,
              transform var(--bf-popd, .34s) cubic-bezier(.34, 1, .64, 1);
}
.beatfill.bf--js .bfc.lit { opacity: 1; transform: none; }

/* Fade-tail chars take their opacity INSTANTLY — a timed opacity fade on the
   moving boundary inverts brightness under fast scroll (older chars mid-fade
   end up brighter than newer ones). The spatial ramp IS the fade; only the
   hop keeps a timed transition. */
.beatfill.bf--js .bfc.bfz,
.beatfill.bf--js.bf--ramp .bfc {
  transition: transform var(--bf-popd, .34s) cubic-bezier(.34, 1, .64, 1);
}

/* promote every char ONCE while the band is live rather than churning
   will-change per character mid-sweep */
.beatfill.bf--anim .bfc { will-change: transform, opacity; }

@media (prefers-reduced-motion: reduce) {
  /* the module bails before it ever adds .bf--js, so the sentence is simply
     fully lit — but belt and braces in case a char span survives a reload */
  .beatfill .bfc { opacity: 1 !important; transform: none !important; }
}

/* NOTE on the page's vertical rhythm: two adjacent .section blocks stack
   their padding, so every join lands at 2 x --space-page (316px at 1440) and
   the page reads as a series of marooned islands. The beat is flush-top and
   #words is flush-bottom so every major join is exactly ONE --space-page,
   which is what the spec's rhythm scale means. */


/* ==========================================================================
   3. WHY — portrait + index rail (cols 1–4), copy (cols 6–12)
   ========================================================================== */

.why__rail { --flow: clamp(1.25rem, 2.4vw, 1.75rem); align-content: start; }
.why__body { --flow: clamp(1.25rem, 2.6vw, 2rem); }

/* --- THE FLYING MARK'S LANE ----------------------------------------------
   MEASURED, and the reason #why can declare a stop at all. With the copy
   free to fill its column, its right edge lands at 0.95–0.98 of the viewport
   between 920px and 1050px (the 12-col grid squeezes col-main) and at 0.92
   at 600px (the one-column stack). Below 900px the copy is full-width and
   above it the copy is on the right of the grid, so the free band flips sides
   at the breakpoint: swept 24 viewports × every x/y/size the M5 grammar
   allows and there is NO single viewport anchor that clears running text on
   both sides of 900px. The stop is only placeable if a lane is reserved.

   So: the wrapping copy stops at 0.79 of the viewport, the ring parks from
   0.8225 outward. Measured live against the settled ring at 34 viewports —
   glyph overlap 0px² at every one, tightest daylight 20px at 600 and 31px
   through the 920–1024 crunch. It holds through the ease in from the beat's
   stop too: that ring's left edge is 0.8075, still outside the copy.

   Only the wrapping copy is pulled in. The h2 is one line inside an M1 mask
   and must never wrap, so it takes the lane back; its longest line measures
   0.776 of the viewport at every width, already inside the lane.
   Below 600px the mark doesn't fly, so the copy keeps its full measure. */
@media (min-width: 600px) {
  .why__body      { padding-inline-end: calc(21vw - var(--pad)); }
  .why__body > h2 { margin-inline-end: calc(var(--pad) - 21vw); }
}

/* at ≤900px the rail goes full width; a 4:5 portrait at full width is a
   billboard, so cap it and keep it left-aligned with everything else */
@media (max-width: 900px) {
  .why__rail .portrait { max-width: 22rem; }
}


/* ==========================================================================
   4. CEREMONIES — heading block, four teaser rows, one button
   .block + .block carries the vertical rhythm; only the heading's own two
   rows need their gap opening up.
   ========================================================================== */

.cer__head { row-gap: clamp(1rem, 2.2vw, 1.5rem); }

/* CORE GAP (reported): .ledger__arrow declares grid-column 12/-1, which
   overlaps .ledger__body's 6/-1. Auto-placement therefore cannot fit the
   arrow in row 1 and drops it onto a second grid row — 36px of dead height
   on every teaser row, and the arrow appears UNDER the copy instead of at
   the end of it. Pinning it to row 1 restores the intended row.
   Only above 900px: below that the core deliberately unstacks the arrow onto
   its own full-width row, and that is correct there. */
@media (min-width: 901px) {
  #ceremonies a.ledger .ledger__body { grid-column: 6 / span 6; }
}

/* --- HOVER LINK FLIP ------------------------------------------------------
   Hover a row and the words slide straight up and out of sight while an
   identical copy rises from underneath to take their place — a train-station
   flip board. The clone is aria-hidden so nothing is announced twice.

   .flip is display:block, not inline-block: an inline-block with
   overflow:hidden takes its margin edge as the baseline and would drop the
   whole line. The clone is absolutely placed at top:100%, so it contributes
   no height and the row does not grow. */
.flip { display: block; position: relative; overflow: hidden; }
.flip__a,
.flip__b { display: block; transition: transform .55s var(--ease); }
.flip__b { position: absolute; left: 0; top: 100%; width: 100%; }

/* Hover is guarded: a touch device has no hover, and a sticky :hover there
   would leave a row reading its own clone. Keyboard focus is NOT guarded —
   a tap does not raise :focus-visible, so it is safe everywhere, and the
   brief requires keyboard parity. */
@media (hover: hover) {
  a.ledger:hover .flip__a,
  a.ledger:hover .flip__b { transform: translateY(-100%); }
}
a.ledger:focus-visible .flip__a,
a.ledger:focus-visible .flip__b { transform: translateY(-100%); }

@media (prefers-reduced-motion: reduce) {
  /* no flip at all — the core's clay colour change carries the hover, and
     the clone is removed from the box entirely so it can never peek */
  .flip__a,
  .flip__b { transform: none !important; }
  .flip__b { display: none !important; }
  a.ledger:hover .ledger__title,
  a.ledger:focus-visible .ledger__title { text-decoration: underline; text-underline-offset: .22em; }
}


/* ==========================================================================
   5. KIND WORDS — eyebrow in the ledger's label columns, the quote in its body
   ========================================================================== */

.words { row-gap: clamp(1.25rem, 2.6vw, 2rem); }
.words .eyebrow { padding-top: .35em; }   /* optically level with the quote */

/* CORE GAP (reported): .quote is capped at 34ch, but `ch` resolves against
   .quote's OWN font-size (--t-body, 15px) while the blockquote inside it sets
   --t-state (48px). The cap therefore lands at ~292px and the one testimonial
   she has renders as a 20-line ribbon three words wide. Releasing the cap lets
   the quote fill its ledger body columns, which is ~30ch of the 48px face —
   the measure the 34ch was reaching for in the first place. */
.words .quote { max-width: none; }


/* ==========================================================================
   6. CLOSING BAND row rhythm
   The core styles every part of .close but leaves its grid on the 0.75rem
   column gutter, which is far too tight between a 3rem statement and the
   sentence under it. Reported as a core gap; set here so home reads right.
   ========================================================================== */

.close__grid { row-gap: clamp(1.25rem, 2.6vw, 2rem); }


/* ==========================================================================
   7. MARQUEE SHELVES spacing
   The About page's drifting shelves now run here in place of the old glyph
   ticker. The core owns every shelf style; this is only the vertical room
   the band needs between the credentials ledger and the WHY section, and it
   mirrors .a-shelves so both pages breathe the same.
   ========================================================================== */

.h-shelves { margin-block: var(--space-block); }
