/* ==========================================================================
   Rushmi to my vows — CORE SYSTEM
   Melbourne marriage celebrant · four pages, one sheet.

   Owned by the core-system agent. Page agents never edit this file; they load
   it FIRST and then their own /assets/pages/<page>.css on top.

   The look: warm ground, deep warm brown ink, hairline rules, a full-bleed
   12-column grid and a violent type cliff. No boxes, no shadows, no rounded
   cards floating on tinted panels. Structure comes from the ledger rows —
   small label left, copy right, the leftover columns deliberately empty.

   The motion vocabulary is five mechanics and no more:
     M1  slot line reveals        every display / statement heading
     M2  glyph ticker strips      two rows drifting in opposite directions
     M3  sticky notched cards     the three ceremony packages
     M4  marquee shelves          place names, no two rows in phase
     M5  the vows fill ★        the closing word fills with the day

   Two safety nets, because a blank hero costs enquiries:
     1. No-JS renders everything visible — CSS only hides behind html.js.
     2. prefers-reduced-motion turns every effect into its static end state.

   Contract for page agents: notes/core-contract.md
   ========================================================================== */


/* ==========================================================================
   1. FONTS — self-hosted, latin, zero third-party requests
   Inter Tight is the workhorse. Instrument Serif is allowed in exactly two
   places sitewide: oversized section numerals, and the closing brand line
   "to my vows". Anywhere else is a spec violation.
   Aleo and Fraunces are gone — the files remain on disk, nothing wires them.
   Great Vibes is the brand script from the business card: the wordmark
   "Rushmi" in the header, the footer and the ticker strips — nowhere else.
   ========================================================================== */

@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/assets/fonts/inter-tight.woff2) format('woff2');
}

@font-face {
  font-family: 'Instrument Serif';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/instrument-serif.woff2) format('woff2');
}

@font-face {
  font-family: 'Great Vibes';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/great-vibes.woff2) format('woff2');
}


/* ==========================================================================
   2. TOKENS
   ========================================================================== */

:root {
  /* ---- palette. Warm, committed. Every ratio measured against --ground. --- */
  --ground:      #E1D7CE;  /* page                                           */
  --ground-2:    #D9CEC3;  /* alternating full-bleed band                    */
  --ground-3:    #EDE6DF;  /* raised card / input fill                       */
  --ink:         #241C16;  /* display + headings                    11.0:1   */
  --ink-2:       #5E4C3E;  /* body                                   5.7:1   */
  --ink-3:       #675548;  /* small labels, meta                     5.0:1   */
  --accent:      #843F20;  /* text-safe clay      5.5:1 ground / 5.0:1 band  */
  --accent-soft: #A85731;  /* DECORATIVE ONLY — never carries text           */
  --rule:        rgba(36, 28, 22, 0.20);
  --rule-strong: rgba(36, 28, 22, 0.38);
  /* The edge of an interactive control is NOT a hairline. WCAG 2.1 SC 1.4.11
     wants 3:1 against BOTH neighbours, and a form field has two: its own
     --ground-3 fill inside and --ground outside. --rule measures 1.47:1 and
     --rule-strong 2.0:1 — neither is legal on a field. This one measures
     4.02:1 on the fill and 3.45:1 on the ground and still reads as a quiet
     line rather than a box. Use it for control boundaries, never for rules. */
  --rule-field:  rgba(36, 28, 22, 0.58);

  /* footer is the one inverted slab on the site — the colophon, not a band */
  --foot-ink:    #B7ADA5;  /* body on --ink                          7.7:1   */
  --foot-fine:   #A79C93;  /* fine print on --ink                    6.3:1   */

  /* ---- brand (measured off the business card, 2026-07-29) --------------- */
  --navy:  #0F3353;  /* carries text: 9.1:1 on ground, 8.4:1 on band        */
  --gold:  #D1B279;  /* the rings — DECORATIVE ONLY, 1.4:1 on ground        */
  --pink:  #FCAAB7;  /* DECORATIVE ONLY — one nod, the closing selection    */
  --off:   #F8F4EF;  /* card off-white — the footer wordmark on the slab    */
  --script: 'Great Vibes', 'Snell Roundhand', cursive;

  /* ---- type ------------------------------------------------------------ */
  --sans:  'Inter Tight', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --serif: 'Instrument Serif', ui-serif, Georgia, 'Times New Roman', serif;

  /* The cliff, corrected 2026-07-29. The original ban on everything between
     1.0625rem and 1.75rem put 15px body directly against a 124px headline —
     an 8x jump with nothing between it, so every paragraph on the site read
     as fine print. The cliff survives where it earns its keep (display vs
     statement); the reading registers were lifted so prose reads as prose.
        16px body -> 20px lede -> 48px statement -> 124px display. */
  --t-giant:   clamp(5rem, 17vw, 15rem);      /* Instrument Serif, lh .82   */
  --t-display: clamp(2.9rem, 8.6vw, 8.5rem);  /* Inter Tight 500, lh .92    */
  --t-state:   clamp(1.75rem, 3.4vw, 3rem);   /* Inter Tight 300, lh 1.16   */
  --t-lead:    clamp(1.125rem, 1.35vw, 1.25rem); /* Inter Tight 400, lh 1.55 */
  --t-body:    1rem;                          /* Inter Tight 400, lh 1.65   */
  --t-label:   0.6875rem;                     /* Inter Tight 500, uppercase */

  /* Measure. Nothing that wraps may run past this — the footer fine print was
     a single 165-character line 1377px wide before this existed. */
  --measure:      68ch;
  --measure-lede: 46ch;

  /* Instrument Serif section numerals (the ledger's 01–0n). Pulled back from
     6.4vw/6.5rem: at 92px it dictated a 159px row for a single line of text. */
  --t-numeral: clamp(2rem, 3.8vw, 3.5rem);

  /* ---- grid ------------------------------------------------------------ */
  --gutter: 0.75rem;
  --pad:    clamp(1rem, 2.2vw, 2rem);
  /* --gutter is a COLUMN measure. Using it on both axes put 12px between,
     say, a portrait and the paragraph under it every time the grid stacked,
     which is why every page sheet had to re-declare row-gap. Rows get their
     own default; a page still overrides it with plain `row-gap`. */
  --grid-row: clamp(1.25rem, 2.6vw, 2rem);

  /* ---- vertical rhythm ------------------------------------------------- */
  --space-page:  clamp(5rem, 11vw, 11rem);
  --space-block: clamp(2.5rem, 5vw, 5rem);
  --space-tight: clamp(1rem, 2vw, 1.75rem);

  /* ---- chrome ---------------------------------------------------------- */
  /* 52px = .mini-nav's real height (3.25rem), and the bar now takes its
     height FROM this token so the two can't drift apart again. It was 68 here
     and 112 in the mobile block — both left over from the retired two-row
     .site-head, which no page has used since the redesign. The stale 112 was
     inflating the hero's empty top and overshooting every anchor landing by
     60px. Fixed, not measured: a JS-set value would shift layout after paint. */
  --nav-h: 52px;

  /* ---- easing ---------------------------------------------------------- */
  --ease:      cubic-bezier(.22, 1, .36, 1);   /* quint-out, the house curve */
  --ease-soft: cubic-bezier(.33, 0, .18, 1);

}


/* ==========================================================================
   3. RESET
   ========================================================================== */

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

html {
  /* the ground lives HERE as well as on body, deliberately: with html bare,
     body's background propagates to the canvas, and the canvas sits outside
     every blending group — mix-blend-mode children (the home mini bar) would
     have nothing to invert against over bare ground. With html painted,
     body paints its own box and blending sees it. */
  background: var(--ground);
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 1.5rem);
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink-2);
  font-family: var(--sans);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* clip, not hidden: `hidden` on the body creates a scroll container and
     breaks position:sticky. `clip` propagates to the viewport. */
  overflow-x: clip;
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

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

input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; }

a { color: var(--accent); }

::selection { background: var(--accent); color: var(--ground); }

:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: var(--ground);
  padding: .8rem 1.2rem;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  text-decoration: none;
}
.skip:focus { left: 0; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}


/* ==========================================================================
   4. LAYOUT — full-bleed 12 columns, no max-width container anywhere
   12 → 6 at 900px → 1 at 600px.
   ========================================================================== */

.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: var(--gutter);
  row-gap: var(--grid-row);
  padding-inline: var(--pad);
}

/* Generic escape hatch. --cs = start column, --cw = span.
   <div class="col" style="--cs:6;--cw:7"> */
.grid > .col { grid-column: var(--cs, auto) / span var(--cw, 12); }

/* Named slots — the vocabulary page agents should reach for first. */
.grid > .col-full   { grid-column: 1 / -1;      }
.grid > .col-n      { grid-column: 1 / span 2;  }  /* numeral                */
.grid > .col-label  { grid-column: 3 / span 3;  }  /* ledger label           */
.grid > .col-body   { grid-column: 6 / -1;      }  /* ledger copy            */
.grid > .col-lede   { grid-column: 1 / span 7;  }  /* page ledes, intros     */
.grid > .col-wide   { grid-column: 1 / span 10; }  /* the one h1 per page    */
.grid > .col-half-l { grid-column: 1 / span 6;  }
.grid > .col-half-r { grid-column: 7 / span 6;  }
.grid > .col-side   { grid-column: 1 / span 4;  }  /* index / portrait rail  */
.grid > .col-main   { grid-column: 6 / -1;      }
.grid > .col-inset  { grid-column: 3 / -1;      }

/* padding-only full-bleed wrapper for things that aren't grids */
.bleed { padding-inline: var(--pad); }

/* sections */
.section { padding-block: var(--space-page); }
.section--band { background: var(--ground-2); }
.section--tight { padding-block: var(--space-block); }

/* --- ONE JOIN, ONE --space-page -----------------------------------------
   Two adjacent sections each brought their own --space-page to the same
   join, so every major seam on the site measured 2 x --space-page — 316px
   at 1440 — and the page read as a run of marooned islands. Every page
   agent was patching this differently (flush-top here, a non-section
   spacer there), which is exactly how four pages stop matching. It is
   decided here, once:

     plain -> plain   the second section gives its top padding up.  158px
     anything -> band the band's padding is the air INSIDE the tint, not
                      the gap above it, so it carries a symmetric
                      --space-block of its own at both edges regardless of
                      what sits next to it, and the neighbour still brings
                      one full --space-page to the seam.            230px

   The band stays symmetric on purpose: a tint whose top air and bottom air
   disagree looks like a mistake, and it would disagree the moment a page
   put a non-section between two sections. */
.section--band { padding-block: var(--space-block); }

/* 1. two plain sections: the second gives its top padding up */
.section + .section { padding-block-start: 0; }
/* 2. …unless the first already gave its BOTTOM padding up, or the first is
      a band whose padding stopped at the tint edge — either way the seam
      would otherwise collapse to nothing */
.section--flush-bottom + .section,
.section--band + .section { padding-block-start: var(--space-page); }
/* 3. a band as the second element always keeps its own symmetric air */
.section + .section--band,
.section--flush-bottom + .section--band,
.section--band + .section--band { padding-block-start: var(--space-block); }

/* the explicit modifiers always win over the adjacency rules above */
.section.section--flush-top { padding-block-start: 0; }
.section.section--flush-bottom { padding-block-end: 0; }
.section--flush-top { padding-block-start: 0; }
.section--flush-bottom { padding-block-end: 0; }

/* flow spacing helpers */
.flow > * + * { margin-block-start: var(--flow, var(--space-tight)); }
.block + .block { margin-block-start: var(--space-block); }


/* ==========================================================================
   5. HAIRLINES — the only section separator on the site.
   They draw in on scroll: scaleX 0 → 1 from the left, .9s ease-out, driven by
   --rule-progress. Static with no JS and under reduced motion.
   JS hook: main.js observes `.rule, .ledger, .ledger-set` and adds .is-ruled.
   ========================================================================== */

.rule,
.ledger { position: relative; }

.rule { height: 1px; border: 0; }

.rule::before,
.ledger::before {
  content: "";
  position: absolute;
  left: var(--pad); right: var(--pad); top: 0;
  height: 1px;
  background: var(--rule);
  transform: scaleX(var(--rule-progress, 1));
  transform-origin: 0 50%;
  transition: transform .9s var(--ease);
}
.rule--bleed::before { left: 0; right: 0; }
.rule--strong::before,
.ledger--strong::before { background: var(--rule-strong); }
.rule--none::before,
.ledger--none::before { display: none; }

.js .rule,
.js .ledger { --rule-progress: 0; }
.js .rule.is-ruled,
.js .ledger.is-ruled { --rule-progress: 1; }


/* ==========================================================================
   6. TYPE COMPONENTS
   ========================================================================== */

h1 {
  font-family: var(--sans);
  font-size: var(--t-display);
  font-weight: 500;
  line-height: .92;
  letter-spacing: -.035em;
  color: var(--ink);
  text-wrap: balance;
}

h2 {
  font-family: var(--sans);
  font-size: var(--t-state);
  font-weight: 300;
  line-height: 1.16;
  letter-spacing: -.02em;
  color: var(--ink);
  text-wrap: balance;
}

h3, h4, h5, h6 {
  font-family: var(--sans);
  font-size: var(--t-label);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink);
}

/* explicit register overrides, for when the tag can't carry the register */
.h-display { font-size: var(--t-display); font-weight: 500; line-height: .92;  letter-spacing: -.035em; color: var(--ink); text-transform: none; }
.h-state   { font-size: var(--t-state);   font-weight: 300; line-height: 1.16; letter-spacing: -.02em;  color: var(--ink); text-transform: none; }
.h-label   { font-size: var(--t-label);   font-weight: 500; line-height: 1.4;  letter-spacing: .13em;   text-transform: uppercase; }

/* eyebrow — label register, clay */
.eyebrow {
  font-size: var(--t-label);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--accent);
}
.eyebrow--quiet { color: var(--ink-3); }

/* statement register — the display-scale "body" that carries a whole idea */
.statement {
  font-size: var(--t-state);
  font-weight: 300;
  line-height: 1.16;
  letter-spacing: -.02em;
  color: var(--ink);
  max-width: 22ch;
  text-wrap: balance;
}
.statement--wide { max-width: 30ch; }

/* Page ledes and section intros only. The measure is in ch, so raising
   --t-lead widens the block in step with the type instead of leaving it a
   432px sliver stranded under a 1145px headline. */
.lede {
  font-size: var(--t-lead);
  line-height: 1.55;
  color: var(--ink-2);
  max-width: var(--measure-lede);
}

/* body prose */
.prose { font-size: var(--t-body); line-height: 1.65; color: var(--ink-2); max-width: var(--measure); }
.prose > p + p { margin-top: 1.1em; }
.prose a { color: var(--accent); text-underline-offset: .22em; text-decoration-thickness: 1px; }

/* small print */
.fine { font-size: var(--t-label); letter-spacing: .04em; text-transform: none; color: var(--ink-3); line-height: 1.7; }

/* the giant Instrument Serif section numeral */
.numeral {
  font-family: var(--serif);
  font-weight: 400;
  font-size: var(--t-numeral);
  line-height: .82;
  letter-spacing: -.04em;
  color: var(--ink);
  font-variant-numeric: lining-nums tabular-nums;
}
.numeral--quiet { color: var(--rule-strong); }


/* ==========================================================================
   7. HEADER — sticky, shared by all four pages
   ========================================================================== */

/* sticky, not fixed, and it matters: a fixed blended layer sits outside
   the scroller's layer tree and Chromium drops its backdrop at deep scroll
   offsets (the bar painted NOTHING at the footer). Sticky rides inside the
   scrolling contents, so the blend always has something to invert. The
   negative margin gives its slot back to the page. */
.mini-nav {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: clamp(1.3rem, 3vw, 2.4rem);
  /* .25rem, not .7rem: the links carry a 44px minimum below and 44 + 8 is
     exactly the 3.25rem bar, so the touch target grew without the bar
     getting taller. */
  padding: .25rem var(--pad);
  height: var(--nav-h);
  margin-bottom: calc(var(--nav-h) * -1);
  box-sizing: border-box;
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  /* SOLID IS THE DEFAULT, and deliberately so: with no JS, or if bar() ever
     throws, the bar keeps a real background and the words stay readable. The
     bare look is the enhancement, not the fallback. There is no flash on load
     either way — this is --ground sitting on --ground until something scrolls
     underneath it. */
  background: var(--ground);
  transition: background-color .3s var(--ease), box-shadow .3s var(--ease),
              transform .35s var(--ease);
}
/* --- the three states, driven by bar() in main.js -----------------------
   The words are ink brown, always. They only ever sit on the page's own
   ground: bare at the top of a page (where that ground IS the backdrop), and
   carrying that ground with them once there is anything else behind. Nothing
   guesses a colour to sit on any more. */
.mini-nav--bare {                 /* at the top of the page */
  background: transparent;
  box-shadow: none;
}
.mini-nav--solid {                /* scrolled: same ground, plus its edge */
  background: var(--ground);
  box-shadow: 0 1px 0 var(--rule);
}
.mini-nav--away { transform: translateY(-100%); }
@media (prefers-reduced-motion: reduce) {
  /* bar() never sets --away under this setting; belt and braces so a stale
     class can't leave the header off-screen. */
  .mini-nav { transition: none; }
  .mini-nav--away { transform: none; }
}

.mini-nav__logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  min-width: 44px;
  min-height: 44px;
  margin-inline-start: -.35rem;   /* keep the rings optically on the margin */
}
.mini-nav__logo img { display: block; height: 1.9rem; width: auto; }
.mini-nav__links {
  display: flex;
  align-items: center;
  gap: clamp(1.1rem, 2.4vw, 2rem);
}
.mini-nav__links a {
  position: relative;
  /* inline-flex + a 44px floor: the label still sits on the bar's centre
     line, but the tappable box now meets the 44px guideline instead of
     being the 18px the glyphs happen to occupy. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--ink);
  text-decoration: none;
  transition: color .25s var(--ease);
}
.mini-nav__links a::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  /* measured off the text's centre line, not the padded box, so growing the
     tap target to 44px did not drag the hairline down with it */
  top: calc(50% + .72em);
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .45s var(--ease), background-color .25s var(--ease);
}
.mini-nav__links a:hover::after,
.mini-nav__links a:focus-visible::after,
.mini-nav__links a[aria-current="page"]::after { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .mini-nav__links a::after { transition: none; }
}

/* ==========================================================================
   8. BUTTONS — squared, label register, no pills
   ========================================================================== */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  min-height: 44px;
  padding: .95rem 1.7rem;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  text-align: center;
  background: var(--ink); color: var(--ground);
  border: 1px solid var(--ink);
  border-radius: 2px;
  text-decoration: none; cursor: pointer;
  transition: background-color .35s var(--ease), color .35s var(--ease),
              border-color .35s var(--ease), transform .35s var(--ease);
}
/* --- the anjalee flood (notes/anjalee-button-recon.md) -------------------
   A wave parked below the button's clip rises and floods it from the bottom
   on hover, rounded crest while moving, settling flat. The label flips as
   the wave passes; the ink border stays as the outline. ::before at z -1
   paints above the element's own background but below its text, which is
   the whole trick. ~.4s quint-out, the observed decel. */
.btn { position: relative; overflow: hidden; z-index: 0; }
.btn::before {
  content: "";
  position: absolute; z-index: -1;
  left: -6%; right: -6%; bottom: -10%;
  height: 160%;
  background: var(--off);
  border-radius: 46% 46% 0 0 / 34% 34% 0 0;
  transform: translateY(101%);
  transition: transform .4s var(--ease), border-radius .4s var(--ease);
}
.btn:hover::before,
.btn:focus-visible::before { transform: translateY(9%); border-radius: 4% 4% 0 0 / 3% 3% 0 0; }
.btn:hover,
.btn:focus-visible { color: var(--navy); border-color: var(--ink); transform: scaleX(.9); }
/* the reference's squeeze, at his 10 percent: the box narrows on hover but
   the label counter-scales so the letters stay true. Transform, not width,
   so flex neighbours never move. */
.btn__t { display: inline-block; transition: transform .35s var(--ease); }
.btn:hover .btn__t,
.btn:focus-visible .btn__t { transform: scaleX(1.1112); }
.btn:active { transform: translateY(1px) scaleX(.9); }

/* The border IS the button — nothing else marks its bounds — so it answers to
   SC 1.4.11 (3:1), not to the decorative hairline scale. --rule-strong measured
   2.21:1 against the ground; --rule-field measures 3.70:1. */
.btn--ghost {
  background: transparent; color: var(--ink);
  border-color: var(--rule-field);
}
/* the ghost mirrors the flood: its wave rises in ink, the label goes light */
.btn--ghost::before { background: var(--ink); }
.btn--ghost:hover,
.btn--ghost:focus-visible { color: var(--off); border-color: var(--ink); background: transparent; }

.btn--full { width: 100%; }
/* 40px measured 42.5px in the wild — under the 44px floor. The padding is
   unchanged; only the floor moved. */
.btn--sm { min-height: 44px; padding: .7rem 1.15rem; }

/* a text link that behaves like a button without becoming one */
.link {
  display: inline-flex; align-items: baseline; gap: .5rem;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--rule-field);
  padding-bottom: .25rem;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}
.link:hover { color: var(--navy); border-color: var(--navy); }
.link::after { content: "\2192"; transition: transform .35s var(--ease); }
.link:hover::after { transform: translateX(4px); }


/* ==========================================================================
   9. LEDGER ROWS — the load-bearing layout move.
   Small label left, copy right, the leftover columns DELIBERATELY EMPTY.
   Never centre a ledger row. Never let the copy span the full 12.
      .ledger__n     cols 1–2    optional Instrument Serif numeral
      .ledger__label cols 3–5    --t-label, --ink-3
      .ledger__body  cols 6–12   the copy
   ========================================================================== */

/* Rows were 159px tall with the content in the top 55px: the numeral was
   driving the height and everything else was pinned to the top of it, so each
   row read as unfinished rather than airy. Centred against a smaller numeral,
   with the padding pulled in. */
.ledger {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gutter);
  padding-inline: var(--pad);
  padding-block: clamp(1.15rem, 2.2vw, 1.75rem);
  align-items: center;
}

.ledger__n {
  grid-column: 1 / span 2;
  font-family: var(--serif);
  font-weight: 400;
  font-size: var(--t-numeral);
  line-height: .82;
  letter-spacing: -.04em;
  color: var(--navy);
  font-variant-numeric: lining-nums tabular-nums;
}

.ledger__label {
  grid-column: 3 / span 3;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--navy);
  line-height: 1.4;
}

.ledger__body { grid-column: 6 / -1; }
.ledger__body > * + * { margin-top: .75rem; }

/* the copy's own headline inside a ledger row */
.ledger__title {
  font-size: var(--t-lead);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -.01em;
  color: var(--ink);
  text-transform: none;
}
.ledger__copy { font-size: var(--t-body); line-height: 1.65; color: var(--ink-2); max-width: 54ch; }

/* the whole row as one link (home's ceremony teasers) */
a.ledger { text-decoration: none; color: inherit; transition: background-color .4s var(--ease); }
a.ledger:hover { background: rgba(36, 28, 22, .04); }
a.ledger .ledger__title { transition: color .3s var(--ease); }
a.ledger:hover .ledger__title { color: var(--navy); }
/* The arrow takes column 12, so inside a linked row the body has to stop at
   11. It used to run 6/-1, which overlaps the arrow's 12/-1: auto-placement
   could not fit them on the same line and dropped the arrow onto a second
   grid row — 36px of dead height on every teaser and the arrow rendered
   UNDER the copy instead of at the end of it. */
a.ledger .ledger__body { grid-column: 6 / span 6; }
.ledger__arrow {
  grid-column: 12 / -1;
  justify-self: end;
  color: var(--accent);
  font-size: var(--t-lead);
  line-height: 1.4;
  opacity: 0; transform: translateX(-6px);
  transition: opacity .35s var(--ease), transform .35s var(--ease);
}
a.ledger:hover .ledger__arrow,
a.ledger:focus-visible .ledger__arrow { opacity: 1; transform: none; }

/* a run of rows closes with one final hairline */
.ledger-set { position: relative; padding-bottom: 1px; }
.ledger-set::after {
  content: "";
  position: absolute; left: var(--pad); right: var(--pad); bottom: 0;
  height: 1px; background: var(--rule);
  transform: scaleX(var(--rule-progress, 1)); transform-origin: 0 50%;
  transition: transform .9s var(--ease);
}
.js .ledger-set { --rule-progress: 0; }
.js .ledger-set.is-ruled { --rule-progress: 1; }


/* ==========================================================================
   10. LISTS
   ========================================================================== */

/* pillar list — the five reasons */
.pillars { display: grid; gap: .9rem; max-width: 58ch; }
.pillars li {
  position: relative;
  padding-left: 1.6rem;
  font-size: var(--t-body);
  line-height: 1.65;
  color: var(--ink-2);
}
.pillars li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 6px; height: 6px; border-radius: 50%;
  border: 1px solid var(--accent-soft);
}

/* index list — the uppercase table of contents beside a section */
.index { display: grid; gap: .55rem; }
.index li {
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
  line-height: 1.5;
}
.index--ruled li { border-top: 1px solid var(--rule); padding-top: .55rem; }

/* direct-contact list */
.direct { display: grid; gap: 1.1rem; }
.direct li { display: grid; gap: .2rem; }
.direct span {
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
}
/* 44px of touch area with the type untouched. The hairline is a text
   decoration rather than a border-bottom on purpose: a border would sit at
   the bottom of the taller box and leave the line stranded under the link
   instead of welded to it. */
.direct a {
  display: inline-flex; align-items: center;
  min-height: 44px;
  font-size: var(--t-lead);
  color: var(--ink);
  justify-self: start;
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-decoration-thickness: 1px;
  text-underline-offset: .3em;
  transition: text-decoration-color .3s var(--ease), color .3s var(--ease);
}
.direct a:hover { color: var(--accent); text-decoration-color: var(--accent); }


/* ==========================================================================
   11. FIGURES / PORTRAITS / PHOTO-SHUFFLE
   Preserved from the current build — the double-buffered crossfade must not
   regress. The Ken Burns zoom is gone: window parallax owns the drift now.
   ========================================================================== */

.figure { margin: 0; }
.figure img { width: 100%; }
.figure__cap {
  margin-top: .75rem;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
}

.portrait { position: relative; width: 100%; overflow: hidden; }
.portrait img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }

/* full-bleed photographic band (home hero photo) */
.band-photo { position: relative; width: 100%; overflow: hidden; }
.band-photo img {
  width: 100%;
  aspect-ratio: 16 / 7;
  object-fit: cover;
  /* 34% is the resting crop and the no-JS/reduced-motion value. With JS the
     window-parallax module drives --wp-pos instead, drifting the crop window
     through the height `cover` is already discarding. See .wp below. */
  object-position: 50% var(--wp-pos, 34%);
}

/* --- photo-shuffle -------------------------------------------------------
   JS wraps the base <img> in .frame and appends an absolutely placed .over.
   Both layers must be identically sized or the crossfade jumps. */
.shuffle { position: relative; width: 100%; overflow: hidden; }
.shuffle img { width: 100%; object-fit: cover; }
.shuffle .frame { position: relative; display: block; overflow: hidden; }
.shuffle .over {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .9s ease;
}
.portrait.shuffle .over { aspect-ratio: 4 / 5; }
/* --- window parallax (window-parallax skill) ----------------------------
   Every framed still: the photo is pre-scaled so a 15% band hangs outside
   its frame, and scroll drifts it inside — like walking past a window.
   Travel is DERIVED from live geometry (JS), so an edge can never show.
   JS writes --wp-par on the FRAME so the shuffle's two crossfade layers
   inherit exactly the same drift — this REPLACES the old Ken Burns zoom.
   Unset (no JS / reduced motion) it falls back to the plain 1.15 crop. */
.wp { overflow: hidden; }
.wp img {
  /* 1.12: the drift got its drama back (~80px at the screen edges) but now
     eases to a whisper through the middle third — strong while a photo
     enters or leaves, nearly still while you look at it. The extra hidden
     band over 1.08 is what the bigger edge travel spends. */
  --wp-scale: 1.12;
  transform: translateY(var(--wp-par, 0px)) scale(var(--wp-scale));
}
@media (min-width: 721px) and (prefers-reduced-motion: no-preference) {
  .wp img { will-change: transform; }
}


/* ==========================================================================
   12. THE FIVE STEPS + DRAWING STEM (Contact's signature)
   Preserved. .step__n knocks the stem out behind each number, so the number's
   background must match whatever the section sits on — set --stem-knock.
   ========================================================================== */

.steps {
  --stem-knock: var(--ground);
  position: relative;
  max-width: 52ch;
  padding-left: clamp(3.2rem, 7vw, 4.6rem);
}
.steps::before {
  content: "";
  position: absolute;
  left: clamp(1.25rem, 2.9vw, 1.85rem);
  top: .5rem; bottom: .5rem; width: 1px;
  background: linear-gradient(var(--accent-soft), rgba(168, 87, 49, .15));
  transform-origin: 50% 0;
  transition: transform 1.4s var(--ease);
}
/* The collapsed state hides behind html.js like everything else: .stem-in is
   added by JS, so with JS off the stem was scaleY(0) forever and the one
   hairline that carries this section never appeared at all. */
.js .steps::before { transform: scaleY(0); }
.js .steps.stem-in::before { transform: scaleY(1); }

.section--band .steps { --stem-knock: var(--ground-2); }

.step { position: relative; padding-bottom: 2.6rem; }
.step:last-child { padding-bottom: 0; }
.step__n {
  position: absolute;
  left: calc(clamp(3.2rem, 7vw, 4.6rem) * -1);
  top: 0;
  width: clamp(2.5rem, 5.8vw, 3.7rem);
  font-family: var(--sans);
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em;
  color: var(--accent);
  background: var(--stem-knock);
  padding: .35rem 0 .4rem;
}
.step h3 { margin-bottom: .5rem; }
.step p { font-size: var(--t-body); line-height: 1.65; color: var(--ink-2); max-width: 46ch; }


/* ==========================================================================
   13. FORMS — load-bearing. Every control keeps a real label, its
   autocomplete and its placeholder. Inputs sit at 1rem so iOS does not zoom
   the page on focus; that is the one deliberate break from --t-body.
   ========================================================================== */

.form { max-width: 46rem; }
.f-row { margin-bottom: 1.1rem; }
.f-row--2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; }
.f { display: block; }

.f__l {
  display: block;
  margin-bottom: .5rem;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
}
.f__l b { color: var(--accent); font-weight: 500; }   /* the required marker */

.f__i {
  width: 100%;
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background-color: var(--ground-3);
  /* NOT --rule. A field edge is a non-text UI boundary and WCAG 2.1 SC
     1.4.11 wants 3:1 against both of its neighbours. --rule measured
     1.47:1 and --rule-strong 2.0:1; --rule-field measures 4.02:1 against
     the field's own fill and 3.45:1 against the ground outside it. */
  border: 1px solid var(--rule-field);
  border-radius: 2px;
  padding: .8rem .95rem;
  transition: border-color .3s var(--ease), background-color .3s var(--ease);
}
.f__i::placeholder { color: var(--ink-3); opacity: 1; }
/* hover has to be at least as strong as rest, or the field goes quieter the
   moment the pointer arrives */
.f__i:hover { border-color: var(--ink-3); }
/* background-COLOR, not the shorthand. The shorthand also resets
   background-image, which is where select.f__i keeps its chevron — so the
   arrow vanished the instant the control took focus, at every width. */
.f__i:focus { border-color: var(--accent); background-color: #F6F1EC; }
.f__i:user-invalid { border-color: #8C2F1B; }
.f__t { resize: vertical; min-height: 7rem; }

select.f__i {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8'><path d='M1 1l5 5 5-5' fill='none' stroke='%23843F20' stroke-width='1.5'/></svg>");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  padding-right: 2.6rem;
}

.form .btn { margin-top: .8rem; }
.form__note {
  margin-top: 1rem;
  font-size: var(--t-label);
  letter-spacing: .04em; text-transform: none;
  line-height: 1.7;
  color: var(--ink-3);
}
.form__note.is-ok { color: var(--accent); }
.form__note a { color: var(--accent); }

/* Samadhi & Shiran beside their own words. align-self: start, or the grid
   stretches the figure to the quote row's height (538px vs the 4:5 img's
   419px at 1440) and the window parallax exposes the photo's bottom edge
   inside the frame — the frame must be exactly the img's size. */
.quote__photo { margin: 0; align-self: start; }
.quote__photo img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; display: block; }

.sign-off {
  font-size: var(--t-state);
  font-weight: 300;
  line-height: 1.16;
  letter-spacing: -.02em;
  color: var(--ink);
  max-width: 16ch;
}


/* ==========================================================================
   14. ACCORDION — <details>
   ========================================================================== */

.qa { border-bottom: 1px solid var(--rule); }
.qa:first-child { border-top: 1px solid var(--rule); }
.qa summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: baseline; justify-content: space-between; gap: 1.5rem;
  padding: 1.35rem 0;
  font-size: var(--t-body);
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink);
  transition: color .3s var(--ease);
}
.qa summary::-webkit-details-marker { display: none; }
.qa summary::marker { content: ""; }
.qa summary:hover { color: var(--accent); }
.qa summary::after {
  content: ""; flex: none;
  align-self: center;
  width: 9px; height: 9px;
  border-right: 1px solid var(--accent);
  border-bottom: 1px solid var(--accent);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .35s var(--ease);
}
.qa[open] summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.qa__a { padding: 0 0 1.5rem; max-width: 62ch; }
.qa__a p { font-size: var(--t-body); line-height: 1.65; color: var(--ink-2); }
.qa__a > p + p { margin-top: 1.1em; }


/* ==========================================================================
   15. THE ONE REAL QUOTE
   She has exactly one. It is set as a single confident quote rather than
   padded out with placeholders. The attribution stays verbatim.
   ========================================================================== */

/* The measure belongs on the line that carries the type. `ch` resolves
   against the element's OWN font-size, and .quote is --t-body (15px), so a
   34ch cap here landed at ~292px while the blockquote inside it was set at
   --t-state (48px) — her one testimonial rendered as a twenty-line ribbon
   three words wide. The cap moves down to the paragraph, where 34ch is
   34 characters of the face you can actually see. */
.quote { max-width: none; }
.quote blockquote { margin: 0; }
.quote blockquote p {
  max-width: 34ch;
  font-size: var(--t-state);
  font-weight: 300;
  line-height: 1.16;
  letter-spacing: -.02em;
  color: var(--ink);
}
.quote blockquote p::before { content: "\201C"; }
.quote blockquote p::after  { content: "\201D"; }
.quote__who {
  margin-top: 1.6rem;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
}
.quote__note {
  margin-top: 1.6rem;
  font-size: var(--t-body); line-height: 1.65;
  color: var(--ink-2);
  max-width: 52ch;
}


/* ==========================================================================
   16. M1 — SLOT LINE REVEALS
   Every display / statement heading. Each line rides in an overflow:hidden
   mask; the inner starts at translateY(100%) and reveals over 1s quint-out
   with a .14s stagger per line. --i is set by main.js.
   ========================================================================== */

.line {
  display: block;
  overflow: hidden;
  /* the mask would otherwise crop descenders and accents */
  padding: .06em 0 .16em;
  margin: -.06em 0 -.16em;
}
.line__in { display: block; }

.js [data-lines] .line__in { transform: translateY(100%); }
.js [data-lines].is-in .line__in {
  transform: none;
  transition: transform 1s var(--ease);
  transition-delay: calc(var(--i, 0) * .14s);
}


/* ==========================================================================
   17. M2 — GLYPH TICKER STRIPS
   Two thin full-bleed rows of the brand ring + "to my vows", drifting forever
   in OPPOSITE directions. 25s linear infinite, pure CSS, aria-hidden.
   Paused via animation-play-state until on screen.
   ========================================================================== */

.ticker {
  --tick-dur: 72s;   /* was 25s — calm, not busy */
  overflow: hidden;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding-block: clamp(.5rem, 1.2vw, .9rem);
  user-select: none;
}
.ticker__row { overflow: hidden; }
.ticker__row + .ticker__row { margin-top: .35rem; }

.ticker__track {
  display: flex;
  width: max-content;
  animation: ticker-run var(--tick-dur) linear infinite;
  animation-play-state: paused;
}
.ticker__row--back { --tick-dur: 80s; }   /* never in phase with the front row */
.ticker__row--back .ticker__track { animation-direction: reverse; }
.js .ticker.is-playing .ticker__track { animation-play-state: running; }
/* with no JS the strips simply run — nothing is hidden */
html:not(.js) .ticker__track { animation-play-state: running; }

.ticker__group { display: flex; align-items: center; flex: none; }

.ticker__unit {
  display: inline-flex; align-items: center; gap: .55rem;
  padding-inline: .9rem;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
/* the rings logo IS the mark — the only gold on the page arrives with it */
.ticker__rings { display: block; flex: none; height: 1.15em; width: auto; }
/* --- hover link flip (nav + footer nav, all pages) ----------------------
   The hovered word slides up and out while its twin rises into the vacated
   slot (sakazuki mechanic 10). JS builds the .lf spans; the clone is
   aria-hidden. Namespaced .lf — home.css already owns .flip for the
   ceremony teaser rows. Touch never fires it; focus does. */
.lf { position: relative; display: inline-block; overflow: hidden; vertical-align: top; }
/* the layers must be block: transforms do not apply to non-replaced inline
   elements, and an inline span silently ignores the flip */
.lf__a, .lf__b { display: block; }
.lf__b { position: absolute; left: 0; top: 100%; width: 100%; }
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .lf__a, .lf__b { transition: transform .38s var(--ease); }
  a:hover .lf__a, a:focus-visible .lf__a,
  a:hover .lf__b, a:focus-visible .lf__b { transform: translateY(-100%); }
}
@media (prefers-reduced-motion: reduce) {
  .lf__b { display: none !important; }
  a:hover .lf__a { text-decoration: underline; text-underline-offset: .22em; }
}

/* --- soft page fade between the four pages ------------------------------
   In from .01 (never 0: an opacity-0 body would push LCP out by the fade's
   length), out via .is-leaving before JS follows the link. Reduced motion
   never arms either side. */
/* fill 'backwards', not 'both': once the fade lands, the animation stops
   applying, body releases its stacking context, and fixed blend-mode
   children composite against the page instead of an isolated layer */
html.js body { animation: page-in .3s ease backwards; }
html.js body.is-leaving { opacity: 0; transition: opacity .24s ease; animation: none; }
@keyframes page-in { from { opacity: .01; } }
@media (prefers-reduced-motion: reduce) {
  html.js body { animation: none !important; }
}


@keyframes ticker-run {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}


/* ==========================================================================
   18. M3 — STICKY NOTCHED CARDS (the three ceremony packages)
   position:sticky, a clip-path notch cut into the top edge, dealing over one
   another as you scroll. Stacking is pure CSS sticky — no JS.
   Degrades to a plain stacked list at <=900px; sticky stacking on a phone
   traps the reader.
   NOTE: the spec's top:1.25rem is measured from BELOW the sticky header,
   otherwise the cards slide under it.
   ========================================================================== */

.stack { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); }

/* --stack-tail — the travel the last card needs.
   Chromium constrains a sticky grid item to the grid container's CONTENT
   box, so with the stack ending flush at the bottom of the last card that
   card is pushed off again a moment before it ever reaches its own sticky
   top: the deal stops one card short and the third package never gets its
   turn. Padding on .stack does nothing (the constraint is measured against
   the content box); it needs a real extra row, which is what this is. The
   value IS the dwell on the finished tableau, so a page can retune it with
   --stack-tail without re-deriving the mechanism.
   Sticky is off at <=900px and under reduced motion, so there is no travel
   to make room for and the tail would just be a hole. */
.stack::after {
  content: "";
  display: block;
  height: var(--stack-tail, clamp(4rem, 42vh, 30rem));
}

.notch {
  --notch-w: clamp(72px, 12vw, 120px);
  --notch-d: 18px;
  --stack-top: calc(var(--nav-h) + 1.25rem);
  position: sticky;
  top: calc(var(--stack-top) + var(--stack-i, 0) * 1.4rem);
  background: var(--ground-3);
  /* Extra breathing room at the foot: the next card's edge sweeps up through
     this one as it deals, and with the CTA flush to the bottom that edge
     bisected the button and read as a broken element rather than a card being
     covered. The tail gives the crossing somewhere empty to happen. */
  padding: calc(var(--notch-d) + clamp(1.5rem, 2.6vw, 2.4rem)) clamp(1.4rem, 2.6vw, 2.4rem) clamp(2.5rem, 4.5vw, 3.75rem);
  clip-path: polygon(
    0 0,
    calc(50% - var(--notch-w) / 2) 0,
    calc(50% - var(--notch-w) / 2 + var(--notch-d)) var(--notch-d),
    calc(50% + var(--notch-w) / 2 - var(--notch-d)) var(--notch-d),
    calc(50% + var(--notch-w) / 2) 0,
    100% 0,
    100% 100%,
    0 100%
  );
}
.stack > .notch:nth-child(1) { --stack-i: 0; }
.stack > .notch:nth-child(2) { --stack-i: 1; }
.stack > .notch:nth-child(3) { --stack-i: 2; }
.stack > .notch:nth-child(4) { --stack-i: 3; }

.notch--flag { background: #F2EBE4; }

.notch__flag {
  display: inline-block;
  margin-bottom: 1rem;
  padding: .2rem .7rem;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--accent);
  border: 1px solid rgba(132, 63, 32, .45);
  border-radius: 2px;
}
.notch__name { font-size: var(--t-state); font-weight: 300; line-height: 1.16; letter-spacing: -.02em; color: var(--ink); text-transform: none; }
.notch__meta {
  margin-top: .5rem;
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
}
.notch__line { margin-top: 1.2rem; font-size: var(--t-body); line-height: 1.65; color: var(--ink-2); max-width: 54ch; }
.notch__list { margin-top: 1.4rem; display: grid; gap: .55rem; }
.notch__list li {
  position: relative;
  padding-left: 1.5rem;
  font-size: var(--t-body); line-height: 1.55; color: var(--ink-2);
}
.notch__list li::before {
  content: ""; position: absolute; left: 0; top: .58em;
  width: 6px; height: 6px; border-radius: 50%;
  border: 1px solid var(--accent-soft);
}
.notch .btn { margin-top: 1.8rem; }


/* --- THE PRICE ------------------------------------------------------------
   Shared by the three stacked cards and the vow-renewal ledger row, so the
   four starting points read as one price list rather than four decisions.

   Instrument Serif, because the house already sets figures in the serif
   (.ledger__n) and nothing else on the site sets numerals in the sans. Clay
   rather than ink so the figure reads as a separate fact from the name it sits
   beside — --accent is the one text-safe clay, measured 5.5:1 on --ground and
   higher again on the cards' lighter --ground-3.

   .priced is the row: name left, figure hard right, aligned on their shared
   baseline. It wraps rather than squeezes, so "Full wedding ceremony" can take
   three lines on a phone and drop its figure underneath instead of crushing
   it to one glyph per line. */
.priced {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .35rem clamp(1rem, 3vw, 2rem);
}
.price {
  flex: none;
  /* margin-left:auto rather than justify-content:space-between, so the figure
     stays on the right edge even when a long name ("Full wedding ceremony")
     wraps it onto its own line. With space-between it fell to the left there,
     and one card out of four disagreeing reads as a bug rather than a layout. */
  margin-left: auto;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.6rem, 2.6vw, 2.4rem);
  line-height: 1;
  letter-spacing: -.01em;
  color: var(--accent);
  /* tabular so $500 / $700 / $650 hold the same width down the stack, lining
     so the digits sit on the cap line beside the name rather than dipping */
  font-variant-numeric: lining-nums tabular-nums;
}


/* ==========================================================================
   19. M4 — MARQUEE SHELVES, NO TWO IN PHASE
   Shelves of real place names, alternating direction. Per-row --shelf-dur and
   a NEGATIVE --shelf-delay so no two shelves are ever in phase (main.js
   randomises any shelf that has not been given its own values).
   aria-hidden — the same place names exist as readable prose in the section.
   ========================================================================== */

.shelves { overflow: hidden; user-select: none; display: grid; gap: clamp(.15rem, .6vw, .5rem); }

.shelf { overflow: hidden; }
.shelf__track {
  display: flex;
  width: max-content;
  animation: shelf-run var(--shelf-dur, 34s) linear infinite;
  animation-delay: var(--shelf-delay, 0s);
  animation-play-state: paused;
}
.shelf--back .shelf__track { animation-direction: reverse; }
.js .shelves.is-playing .shelf__track { animation-play-state: running; }
html:not(.js) .shelf__track { animation-play-state: running; }

.shelf__group { display: flex; align-items: baseline; flex: none; }
.shelf__item {
  padding-inline: .5em;
  font-size: var(--t-state);
  font-weight: 300;
  line-height: 1.16;
  letter-spacing: -.02em;
  color: var(--ink);
  white-space: nowrap;
}
/* --rule-strong composited to ~#998F88 on the ground: 2.0:1, the worst
   contrast on the site. These shelves are aria-hidden decoration, but they
   are still --t-state words a sighted reader reads, so the quiet row is a
   real ink token. --ink-3 measures 5.13:1 and still sits a clear step back
   from the 11:1 of the loud rows. */
.shelf__item--quiet { color: var(--ink-3); }

@keyframes shelf-run {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}


/* ==========================================================================
   20. M5 — ★ MEDIA BEHIND TYPE
   A tall narrow photograph stands centred behind the giant closing line,
   like a strip of film propped behind the words: you see it above and below
   the line and in the open gaps between letters, and the thick strokes hide
   it. No mask, no clip-path — the panel simply paints first and the type
   paints over it.

   One scroll progress drives BOTH layers at different rates: main.js writes
   --p (-1..1, 0 at the middle of the band's travel) on .close__brand and
   never touches layout; this sheet moves the panel up by --film-drift and
   the words down by --line-drift. The drift between them is the entire
   effect — the layers are never animated separately.

   Scrubbed, not tweened: position is a pure function of scroll, so the
   drift plays identically in both directions and cannot stick. With no JS,
   and under reduced motion, --p stays 0 — panel centred, fully visible.
   ========================================================================== */

/* --- M5 · THE VOWS FILL (the signature) --------------------------------
   The giant closing line starts as quiet ink. Across the page's final
   stretch of scroll a dusk photograph — a couple dancing under string
   lights — surfaces INSIDE the letterforms: the word becomes a window onto
   the day. --fill runs 0..1, written by one rAF-throttled scroll listener
   (main.js closeFill); the ink veil thins from solid to 0.30 and the image
   drifts a few percent upward inside the strokes as it fills.
   The veil floor is 0.30, not 0: it is what keeps every letter a letter on
   the image's lightest region, and it is the state the client approved.
   Defaults are the FILLED state — no-JS shows it; html.js re-arms to ink
   and scrubs; reduced motion forces filled and the module never runs. */
.close__brand {
  --fill: 1;
  background-image:
    linear-gradient(rgba(36, 28, 22, calc(1 - var(--fill, 0) * .70)),
                    rgba(36, 28, 22, calc(1 - var(--fill, 0) * .70))),
    url(/assets/close-fill-1800.webp);
  background-size: cover, cover;
  background-position: center, center calc(46% - var(--fill, 0) * 8%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.js .close__brand { --fill: 0; }

/* Small screens: same correction as the hero lockup. `cover` keeps the crop
   identical at every width, so on a phone this is the same picture rendered
   a third the size, and it collapses into flat brown inside 80px letters.
   This band runs the full viewport width, so it needs a bigger number than
   the hero line does: measured, `cover` resolves to 404% here at 430px and
   up, against the hero's constant 227%. 520% clears that everywhere with
   room to spare, and the --fill drift keeps working because the position is
   still a percentage. */
@media (max-width: 900px) {
  .close__brand { background-size: cover, auto 520%; }
}

/* --- lede line-cascade ([data-animate-lines]) ---------------------------
   Each word sits in its own overflow-hidden slot; delays step per LINE, so
   the paragraph peels up line by line. JS builds the slots and buckets
   words into lines by their measured offsetTop; with no JS (or reduced
   motion) the paragraph is simply there. */
.lsw { display: inline-block; overflow: hidden; vertical-align: top; }
.lsw__in {
  display: inline-block;
  transform: translateY(114%);
  transition: transform .85s var(--ease);
  transition-delay: var(--d, 0s);
}
[data-animate-lines].is-lines-in .lsw__in { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .lsw__in { transform: none !important; transition: none !important; }
}

/* the card's pink, nodding in exactly once: select the giant line */
.close__brand::selection, .close__brand *::selection { background: var(--pink); color: var(--navy); }


/* ==========================================================================
   21. THE SHARED CLOSING BAND
   One component, reused verbatim by all four pages. Every page ends on the
   same offer and the same giant Instrument Serif line "to my vows".
   ========================================================================== */

.close {
  position: relative;
  padding-block: var(--space-page) clamp(2rem, 5vw, 4rem);
}
.close__brand {
  position: relative;
  font-family: var(--serif);
  font-weight: 400;
  font-size: min(var(--t-giant), 24vw);   /* the 24vw guard only bites below
                                             ~340px, where 5rem would clip */
  line-height: .82;
  letter-spacing: -.04em;
  /* color lives in the M5 fill block: the glyphs are transparent and the
     ink is painted by the veil layer BEHIND them, so the fill can surface.
     Declaring ink here would paint over the clipped image — it did. */
  white-space: nowrap;
  margin-top: clamp(2.5rem, 6vw, 5rem);
}
/* Lifted to the lede register: at body size this was a 347px sliver sitting
   under a 798px statement with 1061px of void beside it. */
.close__sub { font-size: var(--t-lead); line-height: 1.55; max-width: var(--measure-lede); }
.close__or { font-size: var(--t-body); color: var(--ink-2); }
.close__or a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--rule); }
.close__or a:hover { border-color: var(--accent); color: var(--accent); }


/* ==========================================================================
   22. FOOTER — the colophon. The one inverted slab on the site.
   ========================================================================== */

.foot {
  background: var(--ink);
  color: var(--foot-ink);
  padding-block: clamp(3rem, 7vw, 5rem);
}
.foot__in { display: grid; gap: 2rem; }
.foot__top {
  display: flex; flex-wrap: wrap; gap: 2rem;
  justify-content: space-between; align-items: flex-start;
}
/* the brand lockup: footer-owned now that the header carries no brand.
   (These styles were casualties of the header removal: without them the
   rings imaged at their 600px attribute and blew 390px pages 86px wide.) */
.brand {
  display: flex; align-items: baseline; gap: .45rem;
  text-decoration: none;
}
.brand__rings { display: block; flex: none; align-self: center; height: 1.9rem; width: auto; }
.brand__name {
  font-family: var(--script);
  font-size: 1.625rem; font-weight: 400;
  line-height: 1;
}
.brand__sub {
  font-size: var(--t-label); font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase;
}

.brand--foot { display: flex; align-items: baseline; gap: .45rem; margin-bottom: .5rem; }
.brand--foot .brand__name { color: var(--off); }
.brand--foot .brand__sub { color: #D8AE96; }
.foot__lede { font-size: var(--t-body); color: var(--foot-ink); }
/* the 44px boxes supply their own separation, so the grid gap goes */
.foot__nav { display: grid; gap: 0; }
.foot__nav a, .foot__links a {
  display: inline-flex; align-items: center;
  min-height: 44px;
  min-width: 44px;      /* "Home" is only 39px of type */
  justify-self: start;
  color: var(--ground);
  font-size: var(--t-body);
  /* text decoration, not border-bottom — see .direct a */
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: .3em;
  transition: text-decoration-color .3s var(--ease);
}
.foot__links a { text-decoration-color: rgba(225, 215, 206, .3); }
.foot__nav a:hover, .foot__links a:hover { text-decoration-color: var(--ground); }
.foot__links { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.foot__fine {
  font-size: var(--t-label);
  letter-spacing: .04em; text-transform: none;
  line-height: 1.7;
  color: var(--foot-fine);
  padding-top: 1.5rem;
  border-top: 1px solid rgba(225, 215, 206, .16);
  /* was one unbroken 165-character line 1377px wide at 11px */
  max-width: 88ch;
}
.foot :where(a):focus-visible { outline-color: var(--ground); }


/* ==========================================================================
   23. QUIET REVEAL — the default for anything that isn't a signature moment
   ========================================================================== */

.reveal { opacity: 1; }
.js .reveal { opacity: 0; transform: translateY(10px); }
.js .reveal.in {
  opacity: 1; transform: none;
  transition: opacity .8s var(--ease), transform .8s var(--ease);
}
.js .reveal--slow.in { transition-duration: 1.05s; }

/* --- PAGE-LOAD ENTRANCE (anjalee recon, notes/anjalee-load-recon.md) ----
   Words paint at rest, like a printed page. The hero photo band starts a
   step below its resting place and glides up immediately; half a beat later
   the header drops from above the viewport (JS adds .is-shown at +0.4s).
   The reference's overdamped spring (stiffness 200 / damping 40, settled
   ~0.8s) reads as the house quint-out at .8s — nothing bounces. */
.js .hero__bg { animation: band-rise .8s var(--ease) both; }
@keyframes band-rise { from { transform: translateY(min(160px, 18vh)); } }





/* ==========================================================================
   24. RESPONSIVE — 12 → 6 at 900px → 1 at 600px
   ========================================================================== */

@media (max-width: 900px) {
  /* The two-row .site-head that used to live here — brand + Enquire on top,
     the three pages under, --nav-h: 112px — was removed from all four pages
     in the redesign. Its rules stayed behind, styling nothing, while the 44px
     tap-target floor they carried was quietly lost: .mini-nav shipped to
     phones with 18px-tall links. The floor now lives on .mini-nav itself, in
     section 7, so it applies at every width. */

  .grid { grid-template-columns: repeat(6, 1fr); }
  .grid > .col { grid-column: var(--cs-md, 1) / span var(--cw-md, 6); }
  .grid > .col-full,
  .grid > .col-n,
  .grid > .col-label,
  .grid > .col-body,
  .grid > .col-lede,
  .grid > .col-wide,
  .grid > .col-half-l,
  .grid > .col-half-r,
  .grid > .col-side,
  .grid > .col-main,
  .grid > .col-inset { grid-column: 1 / -1; }

  /* ledger: label over body, both across the full six */
  .ledger { grid-template-columns: repeat(6, 1fr); row-gap: .5rem; }
  .ledger__n     { grid-column: 1 / -1; font-size: clamp(2.25rem, 9vw, 3.5rem); }
  .ledger__label { grid-column: 1 / -1; padding-top: 0; }
  .ledger__body,
  a.ledger .ledger__body { grid-column: 1 / -1; }
  .ledger__arrow { grid-column: 1 / -1; justify-self: start; opacity: 1; transform: none; }

  /* M3 must not trap the reader on a phone */
  .notch { position: static; }
  .stack::after { display: none; }

  .f-row--2 { grid-template-columns: 1fr; }
  .foot__top { flex-direction: column; gap: 1.8rem; }

  .band-photo img { aspect-ratio: 4 / 3; }
}

@media (max-width: 600px) {
  .grid { grid-template-columns: 1fr; }
  .grid > .col,
  .grid > [class*="col-"] { grid-column: 1 / -1; }

  .ledger { grid-template-columns: 1fr; }
  .ledger__n, .ledger__label, .ledger__body, .ledger__arrow,
  a.ledger .ledger__body { grid-column: 1 / -1; }

  .foot__links { gap: .8rem 1.4rem; }
  .mini-nav__links { gap: 1.05rem; }
  /* "to my vows" IS the brand name — hiding it left both lockups reading
     "Rushmi". Measured fitting on one row at 320px in the header and in the
     footer, so there is nothing to hide it for. */
}


/* ==========================================================================
   25. REDUCED MOTION — every effect becomes its static end state.
   Nothing loops, drifts, flies or fades. The mark is rendered already seated
   in the slot with the o already ducked, so the line still reads "v○ws".
   ========================================================================== */

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

  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
  }

  /* hairlines: drawn */
  .js .rule, .js .ledger, .js .ledger-set { --rule-progress: 1 !important; }

  /* quiet reveals + hero: settled */
  .js .reveal { opacity: 1 !important; transform: none !important; }


  /* M1: lines already up */
  .js [data-lines] .line__in { transform: none !important; }

  /* M2 + M4: strips stand still */
  .ticker__track, .shelf__track {
    animation: none !important;
    transform: none !important;
  }

  /* M3: no sticky deal, a plain stacked list */
  .notch { position: static !important; }
  .stack::after { display: none !important; }

  /* photo-shuffle: one still frame, no drift, no crossfade layer */

  .shuffle .over { display: none !important; }

  /* contact stem: already drawn */
  .steps::before { transform: scaleY(1) !important; }

  /* buttons: no wave, no squeeze, the colours simply swap */
  .btn::before { transition: none !important; }
  .btn:hover, .btn:focus-visible { transform: none !important; }
  .btn:hover .btn__t, .btn:focus-visible .btn__t { transform: none !important; }

  /* the vows fill: complete and still */
  .js .close__brand { --fill: 1 !important; }

  /* entrance: everything at rest on paint */
  .js .hero__bg { animation: none !important; }
  .mini-nav__links a::after { transition: none !important; }
  /* window parallax: the plain 1.15 crop, no drift */
  .wp img { transform: scale(var(--wp-scale, 1.12)) !important; }
}
