/* Journey with Nari — Redesign v2
   Aman.com editorial structure + violet/gold/ivory sound-healing palette */

/* ---------- Self-hosted type ----------
   These were served from Google Fonts, which cost 154 KB and two extra origins
   (fonts.googleapis.com for the CSS, then fonts.gstatic.com for the files —
   a request chain, since the font URLs aren't known until the CSS lands).
   They're now local: 93 KB, no third-party DNS or TLS, no chain.

   Still variable fonts, with the weight axis trimmed to 400-700 — the CSS asks
   for 400/500/600, and the extra headroom is there because Cormorant sets a
   lighter colour on the page than the Fraunces it replaced. Cormorant Garamond
   has no optical-size axis, so unlike Fraunces there is nothing to track
   font-size. Regenerate with tools/build-fonts.py. */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/cormorant.woff2') format('woff2');
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/cormorant-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('../fonts/inter.woff2') format('woff2');
}

:root {
  --ivory: #F7F2EA;
  --violet: #5B3E7D;
  --lilac: #C9AEE0;
  --gold: #C9A24B;
  --plum: #3A2650;
  --amber: #E8B98A;
  --ink: #2B2233;
  --ink-on-dark: #F7F2EA;

  /* Two golds. --gold is the decorative one: hairlines, card borders, bullet
     dots, anything where contrast doesn't apply. --gold-ink is for gold used as
     TEXT — the brand gold measured only 2.15:1 on the ivory sections and 1.31:1
     on the lavender bands, far under the 4.5:1 that small uppercase labels need.
     Darkening it along its own hue reaches 4.68:1 on light backgrounds while
     still reading as gold rather than brown. Gold is never legible on the
     saturated lavender at any usable darkness, so text there uses violet. */
  --gold-ink: #836931;

  /* Failure states for form messages. The light one is used on ivory and on
     the newsletter card; the dark-surface one is for the footer, where #A32B2B
     drops to unreadable against the violet. */
  --error: #A32B2B;
  --error-on-dark: #F0AEAE;
  --font-display: "Cormorant Garamond", Georgia, serif;
  --font-body: "Inter", sans-serif;

  /* Fluid so the rhythm scales continuously from a 320px phone to a 1920px
     desktop instead of snapping at one breakpoint.

     The top of the scale was retuned down on 15 Aug 2026: lg was 96px and the
     band padding 160px at desktop, which put 96px between a section heading and
     its own content and 320px between one band's content and the next. Phones
     were never the problem — the floors moved far less than the maximums, so
     the scale is now flatter across the range rather than uniformly smaller. */
  --space-xs: 12px;
  --space-sm: clamp(18px, 2.7vw, 24px);
  --space-md: clamp(30px, 5.3vw, 48px);
  --space-lg: clamp(40px, 4.7vw, 64px);
  --space-xl: clamp(76px, 11.1vw, 160px);

  /* Band padding is its own token, deliberately shorter than --space-xl,
     because two adjacent bands each contribute it — the gap a visitor actually
     sees between sections is double this number. --space-xl is kept for the
     hero's bottom padding, where the space is single-sided and sets the hero
     composition, so it must not follow the band rhythm down. */
  --space-section: clamp(44px, 4.5vw, 64px);

  /* Corners were ad hoc — 3, 4, 6, 8, 12 and 14px were all in use across card
     surfaces, which is the kind of small inconsistency that reads as unfinished
     even when nothing looks obviously wrong. Three deliberate steps: sm for
     small media, inputs and chips; md for every card and tile; lg for the two
     large panels (the footer and the contact card). Pills stay 999px and
     circles 50% — those are shapes, not points on this scale. */
  --radius-sm: 4px;
  --radius-md: 10px;
  --radius-lg: 14px;

  --max-width: 1240px;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  /* Dreamy pastel sky — cotton-candy clouds, replaces flat ivory on section backgrounds */
  --sky-bg:
    radial-gradient(ellipse 55% 40% at 14% 16%, rgba(255,255,255,0.65) 0%, rgba(255,255,255,0) 62%),
    radial-gradient(ellipse 46% 34% at 82% 10%, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 60%),
    radial-gradient(ellipse 50% 36% at 62% 94%, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 65%),
    radial-gradient(ellipse 40% 30% at 6% 90%, rgba(255,255,255,0.45) 0%, rgba(255,255,255,0) 60%),
    radial-gradient(ellipse 36% 28% at 45% 40%, rgba(255,255,255,0.35) 0%, rgba(255,255,255,0) 65%),
    linear-gradient(120deg, #FBE1E6 0%, #ECE1F7 26%, #FCEEE0 52%, #E2ECF9 78%, #F7EFE5 100%);

  /* Amethyst-cloud backdrop. The artwork sits on one fixed layer behind the
     whole page (body::before); light sections lay a translucent ivory veil
     over it so it reads as atmosphere and body text keeps its contrast.
     Veil at 0.80 leaves the darkest part of the image at ~9.9:1 for --ink. */
  --crystal: url('../img/crystal-bg.webp');
  --sky-veil: rgba(247, 242, 234, 0.80);
  --sky-veil-soft: rgba(247, 242, 234, 0.62);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

html { background: #EFE6F7; }

body {
  margin: 0;
  background: transparent;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* height:auto preserves the intrinsic ratio now that width/height attributes are
   on the markup (added to stop layout shift). Class rules needing height:100%
   out-specify this single-element selector, so cover-crops still work. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  background: var(--crystal) center / cover no-repeat;
  z-index: -1;
  pointer-events: none;
}

img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--violet);
  color: var(--ivory);
  padding: 12px 20px;
  z-index: 1000;
}
.skip-link:focus { left: 0; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 500;
  color: var(--violet);
  line-height: 1.15;
  margin: 0 0 var(--space-sm);
  letter-spacing: -0.01em;
}

/* Base heading scale. These used to fall through to the browser defaults
   (h2 25.5px, h3 19.9px, h4 17px), which was invisible until the display face
   changed: Cormorant's much smaller x-height made every section heading that
   had no component rule of its own read a quarter too small. Declaring the
   scale explicitly fixes that and means a future face swap only needs these
   numbers retuned, not a hunt through thirty component rules. Component rules
   are more specific and still win where they exist. */
h2 { font-size: clamp(26px, 3vw, 32px); }
h3 { font-size: 25px; }
h4 { font-size: 21px; }

.section--plum h1, .section--plum h2, .section--plum h3, .section--plum h4 {
  color: var(--violet);
}
.section--lavender h1, .section--lavender h2, .section--lavender h3, .section--lavender h4 {
  color: var(--violet);
}

em, .italic-accent {
  font-style: italic;
  font-weight: 400;
  color: var(--gold-ink);
}

.eyebrow {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gold-ink);
  margin: 0 0 var(--space-xs);
}

.section--plum .eyebrow { color: var(--violet); }
.section--lavender .eyebrow { color: var(--violet); }

p { margin: 0 0 1em; }

.section__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--space-md);
  position: relative;
}

.section {
  position: relative;
  padding: var(--space-section) 0;
  overflow: hidden;
}

.section--ivory { background: var(--sky-veil); }
.section--plum { background: linear-gradient(160deg, rgba(201,174,224,0.86) 0%, rgba(223,206,242,0.86) 100%); color: var(--ink); }
.section--plum p { color: var(--ink); }
.section--lavender { background: linear-gradient(160deg, rgba(201,174,224,0.86) 0%, rgba(223,206,242,0.86) 100%); color: var(--ink); }
.section--lavender p { color: var(--ink); }

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

/* ---------- Cloud / orb motif ---------- */
.cloud {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  pointer-events: none;
  z-index: 0;
  animation: drift 26s ease-in-out infinite alternate;
}
.cloud--aura {
  background: radial-gradient(circle at 30% 30%, var(--lilac) 0%, var(--amber) 55%, transparent 75%);
  opacity: 0.55;
}
.cloud--violet {
  background: radial-gradient(circle at 40% 40%, var(--lilac) 0%, transparent 70%);
  opacity: 0.5;
}
.cloud--gold {
  background: radial-gradient(circle at 50% 50%, var(--amber) 0%, transparent 70%);
  opacity: 0.4;
}

@keyframes drift {
  from { transform: translate(0, 0) scale(1); }
  to   { transform: translate(-3%, 4%) scale(1.05); }
}

/* A drifting cloud is a 70px blur being re-composited every frame, forever, and
   the homepage stacks six of them. Below 900px the drift is dropped and the
   blobs are rasterised once — the motion was a 3% translate, so what's lost is
   near-invisible, and phones stop paying for it. */
@media (max-width: 900px) {
  .cloud { animation: none; }
}

/* Nothing on this site animated conditionally before, so a visitor who asks
   their OS for reduced motion still got drifting clouds, a Ken Burns hero and
   smooth-scroll. Honour the preference. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .cloud, .hero__bg img { animation: none !important; }
  /* Keep the hero's framing — the scale is baked into the static state. */
  .hero__bg img { transform: scale(1.06); }
  [data-reveal] { opacity: 1 !important; transform: none !important; }
}

.hero-gradient {
  background: linear-gradient(160deg, #C9AEE0 0%, #E9D9F2 35%, #E8B98A 75%, #F7F2EA 100%);
}

hr.divider-gold {
  border: none;
  border-top: 1px solid rgba(201, 162, 75, 0.4);
  max-width: var(--max-width);
  /* Sits at the very end of a band, so its top margin stacks on top of the
     section's own bottom padding. At --space-lg that was 96px + 104px of empty
     background above the next band — the rule read as floating in a void
     rather than closing the section. */
  margin: var(--space-md) auto 0;
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 15px 30px;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: all 0.3s var(--ease);
  white-space: nowrap;
}
.btn-gold {
  background: transparent;
  border-color: var(--plum);
  color: var(--plum);
}
.btn-gold:hover { background: var(--plum); color: var(--ivory); }
.hero__content--onphoto .btn-gold { color: var(--amber); border-color: var(--amber); }
.hero__content--onphoto .btn-gold:hover { background: var(--amber); color: var(--plum); }
.section--plum .btn-gold { color: var(--amber); border-color: var(--amber); }
.section--plum .btn-gold:hover { background: var(--amber); color: var(--plum); }
.section--lavender .btn-gold { color: var(--violet); border-color: var(--violet); }
.section--lavender .btn-gold:hover { background: var(--violet); color: var(--ivory); }

.btn-violet {
  background: var(--violet);
  border-color: var(--violet);
  color: var(--ivory);
}
.btn-violet:hover { background: #4a3269; }

/* The gold rule is drawn by ::after rather than border-bottom so the box can be
   a full 44px tall for a thumb while the rule still sits tight under the words.
   line-height is pinned so the offset below is deterministic: half a line
   (0.6em) down from the centre, plus the 3px gap the border-bottom used to
   leave. Both scale with font-size, so a restyled instance stays correct.
   Instances that need a different rule colour set --tl-rule rather than
   border-color. */
.text-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 13px;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  --tl-rule: var(--gold);
  transition: opacity 0.25s;
}
.text-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: calc(50% + 0.6em + 3px);
  height: 1px;
  background: var(--tl-rule);
}
.text-link:hover { opacity: 0.65; }

/* ---------- Read more ----------
   Progressive enhancement: mark a BLOCK `data-readmore` and main.js clamps the
   whole block and inserts one toggle — not one per paragraph, which is what the
   first version did and which left two buttons stacked inside a single section.

   Where the block sits beside a photograph, main.js sets --rm-max to that
   photograph's rendered height, so the column of text ends level with the image
   instead of running past it. Where there is no image it falls back to a line
   count. Both arrive as --rm-max in pixels.

   The fade is a mask rather than a gradient overlay, because these blocks sit on
   four different section backgrounds and a mask needs to know none of them.
   With JS off nothing is clamped and the full text is in the markup. */
/* The clamp goes on an inner wrapper main.js creates, never on the marked block
   itself. The marked blocks are direct grid children of .meet-nari, so appending
   the toggle as a sibling made it a third grid item and pushed the photograph
   onto its own row — the text and image ended up stacked at full width instead
   of side by side. Wrapping the content keeps the grid at exactly two children. */
.rm__inner.is-clamped {
  max-height: var(--rm-max);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 2.4em), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 2.4em), transparent 100%);
}
/* Borrows .text-link wholesale — including its 44px min-height and the
   ::after gold rule — so this control matches every other inline action on the
   site. Only the button's own user-agent chrome needs resetting. */
.rm-toggle {
  background: none;
  border: 0;
  padding: 0;
  margin-top: -4px;
  font-family: var(--font-body);
  color: var(--violet);
  cursor: pointer;
}
.section--lavender .rm-toggle,
.section--plum .rm-toggle { color: var(--violet); }

/* ---------- Nav ---------- */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  padding: 14px 0;
  transition: all 0.4s var(--ease);
  /* Same concentrated lilac veil as the footer, always on — not just once
     scrolled — so the header reads as one consistent brand element with it.
     Held near-opaque because at 0.55 the ink nav text dropped to ~2:1 against
     the bright patches of the hero photograph behind it and became unreadable.
     Solid lilac gives ink 7.7:1 regardless of what the page scrolls past. */
  background: linear-gradient(160deg, rgba(201,174,224,0.94) 0%, rgba(223,206,242,0.94) 100%);
  backdrop-filter: blur(12px);
  box-shadow: 0 1px 0 rgba(43,34,51,0.06);
}
.nav__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--space-md);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.nav__logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  line-height: 1;
}
.nav__logo-mark {
  width: 38px;
  height: 38px;
  object-fit: contain;
  display: block;
}
.nav__logo-text {
  font-family: var(--font-display);
  font-size: 13.8px;
  letter-spacing: 0.03em;
  white-space: nowrap;
  color: var(--violet);
  transition: color 0.4s;
}

.nav__links {
  display: flex;
  gap: 30px;
}
.nav__item { position: relative; }
.nav__link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink);
  transition: color 0.4s, opacity 0.25s;
}
.nav__link:hover { opacity: 0.7; }
.nav__caret { width: 9px; height: 6px; transition: transform 0.25s var(--ease); }

/* Dropdown panels. Opened on hover and on keyboard focus-within, so tabbing
   through the sub-links keeps the panel up without any JS. The bridging
   padding-top on the panel means the pointer can cross the gap from the
   parent link without the panel closing underneath it. */
.nav__panel {
  position: absolute;
  top: 100%;
  left: -18px;
  min-width: 240px;
  padding-top: 14px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.25s var(--ease), transform 0.25s var(--ease), visibility 0.25s;
  z-index: 10;
}
.nav__panel a {
  display: block;
  padding: 11px 18px;
  font-size: 13px;
  letter-spacing: 0.02em;
  color: var(--ink);
  background: var(--ivory);
  border-left: 1px solid rgba(201,162,75,0.35);
  border-right: 1px solid rgba(201,162,75,0.35);
  transition: background 0.2s, color 0.2s, padding-left 0.2s;
}
.nav__panel a:first-child {
  border-top: 1px solid rgba(201,162,75,0.35);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.nav__panel a:last-child {
  border-bottom: 1px solid rgba(201,162,75,0.35);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  box-shadow: 0 14px 34px rgba(58,38,80,0.14);
}
.nav__panel a:hover { background: var(--lilac-pale, #F1E9FA); color: var(--violet); padding-left: 23px; }

.nav__item--has-menu:hover .nav__panel,
.nav__item--has-menu:focus-within .nav__panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.nav__item--has-menu:hover .nav__caret,
.nav__item--has-menu:focus-within .nav__caret { transform: rotate(180deg); }

.nav__cta.btn-gold { color: var(--plum); border-color: var(--plum); }
.nav__cta.btn-gold:hover { background: var(--plum); color: var(--ivory); }

.nav.is-scrolled { padding: 16px 0; }
/* backdrop-filter on an ancestor becomes the containing block for any
   position:fixed descendant — including the full-screen .nav__links
   overlay below. With the menu open that made "inset:0" resolve against
   this ~80px header instead of the viewport once the page was scrolled,
   so the overlay only covered a sliver instead of the full screen. Drop
   the header's own blur while the menu is open (toggled in main.js,
   not :has() — the header's computed backdrop-filter didn't reliably
   update off a :has() match in testing) — it's hidden underneath the
   overlay anyway, so nothing is lost. */
.nav.menu-open { backdrop-filter: none; }

.nav__toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px;
  /* Three 1.5px bars made a 34x27 hit area — the primary navigation control on
     every phone, well under the 44px minimum. The bars are unchanged and stay
     centred; only the touch target grows, and the negative margin keeps it
     optically aligned with the edge of the bar. */
  min-width: 44px;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  margin-right: -5px;
}
.nav__toggle span {
  width: 22px; height: 1.5px;
  background: var(--violet);
  transition: background 0.4s;
}

.desktop-only { display: inline-flex; }

/* ---------- Hero ---------- */
.hero {
  position: relative;
  min-height: 100vh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
.hero__bg { position: absolute; inset: 0; z-index: 0; }
/* Both layers are absolute so the soft copy sits exactly on top of the sharp
   one; identical object-fit, transform and animation keep them in lockstep as
   the Ken Burns pan runs. */
.hero__bg img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1.06);
  animation: kenburns 24s ease-in-out infinite alternate;
}
@keyframes kenburns {
  from { transform: scale(1.06) translate(0,0); }
  to   { transform: scale(1.14) translate(-1%,-1%); }
}
/* ---------- Hero legibility ----------
   The headline sits over a photograph with candles and a warm lit wall in it.
   Averaged across the text area contrast looked fine (7-14:1), but against the
   brightest pixels the eyebrow fell to 1.0:1 and the h1 to 1.4:1 -- letters
   disappearing into highlights.

   Blur alone would not have fixed that: blurring a bright area leaves it just
   as bright. What the blur does is flatten the *variation*, so no single
   highlight can swallow a letter. The darkening below does the rest of the
   work, and the two together are what lift the worst case. */
/* The blurred copy, faded in from roughly two-thirds up so the photograph stays
   sharp where it is doing the storytelling and softens only behind the type.
   The mask is the gradient: fully opaque at the bottom, gone by 66%. */
/* Legibility is carried by the letters, not by anything laid over the picture —
   the photograph stays whole so her hands on the bowl read clearly.
   Two shadows per element: a tight, fairly opaque one that draws a dark edge
   right at the letterform (this is what actually separates type from a busy
   background), and a wider, softer halo that lifts the area immediately around
   it. Small type gets a tighter halo — a wide blur on 12px text just smudges. */
.hero__content--onphoto {
  /* Three parts, and the order matters more than the total darkness:
     --type-hug  a very tight, near-opaque shadow with no offset. It wraps each
                 glyph on all sides and is what actually separates a letter from
                 a busy photograph — the eye reads the edge, not the average.
     --type-edge a small downward offset, so it still reads as a drop shadow.
     --type-halo a wide, soft falloff that lifts the immediate surroundings. */
  --type-hug:  0 0 2px rgba(24,16,30,0.95);
  --type-edge: 0 1px 3px rgba(24,16,30,0.9);
  --type-halo: 0 0 16px rgba(24,16,30,0.7);
}
.hero__content--onphoto .eyebrow {
  /* Smallest type on the hero, and amber rather than ivory, so it leans hardest
     on the hug. A wide blur at 12px would only smudge it. */
  text-shadow: var(--type-hug), var(--type-edge), 0 0 10px rgba(24,16,30,0.9);
}
.hero__content--onphoto h1 {
  /* Large display type can carry a broader halo without looking muddy. */
  text-shadow: 0 0 3px rgba(24,16,30,0.8), 0 2px 5px rgba(24,16,30,0.7),
               0 6px 30px rgba(24,16,30,0.55);
}
.hero__content--onphoto .hero__sub,
.hero__content--onphoto .text-link,
.hero__content--onphoto .btn {
  text-shadow: var(--type-hug), var(--type-edge), var(--type-halo);
}

.hero__scrim {
  position: absolute; inset: 0;
  /* Deeper and reaching further up than before -- this is what actually moves
     the worst-case contrast; the blur makes the darkening look intentional
     rather than like a grey wash over the photograph. */
  /* Back to a light wash. The frosted panel carries the legibility now, so this
     only needs to give the nav something to sit on and add a little depth —
     the photograph above the panel stays as shot. */
  background: linear-gradient(0deg,
      rgba(43,34,51,0.30) 0%,
      rgba(43,34,51,0.12) 45%,
      rgba(91,62,125,0.25) 100%);
  z-index: 2;
}
.hero__content {
  position: relative;
  z-index: 3;   /* above .hero__scrim (2) */
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--space-md) var(--space-xl);
  width: 100%;
  color: var(--ivory);
}
.hero__content .eyebrow { color: var(--amber); }
.hero h1 {
  color: var(--ivory);
  font-size: clamp(55px, 8.75vw, 115px);
  margin-bottom: var(--space-sm);
}
.hero__sub {
  max-width: 44ch;
  font-size: 18px;
  color: rgba(247,242,234,0.88);
}
.hero__cta-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}
.hero__cta-row .text-link { color: var(--ivory); --tl-rule: var(--amber); }

/* ---------- Meet Nari ---------- */
/* The media column was 0.85fr, which at 1440px rendered a portrait photograph
   496px wide and 744px tall — taller than the text beside it and heavier than
   anything else on the page. Narrowing the column shrinks every photo
   proportionally, which is the part that needs no crop. */
.meet-nari {
  display: grid;
  grid-template-columns: 0.62fr 1fr;
  gap: var(--space-lg);
  align-items: center;
}
/* The layout alternates media-first and text-first down the About page. Grid
   tracks are positional, so the same 0.62fr that narrows the photo on a
   media-first row hands it the WIDE column on a text-first one — which is
   exactly why two photos measured 667px against their neighbours' 413px. Flip
   the track order when the media sits second, so the photo takes the narrow
   column either way and no HTML has to change. */
.meet-nari:has(> .meet-nari__media:last-child) { grid-template-columns: 1fr 0.62fr; }
.meet-nari__media { position: relative; border-radius: var(--radius-sm); overflow: hidden; }
/* Narrowing the column is not enough for the near-vertical sources — a 2:3
   portrait is still 570px tall in a 380px column. A max-height catches only
   those outliers; anything landscape or square is under the cap and keeps its
   natural proportions untouched, which is the whole point of the documented
   rule that this class must NOT carry a shared fixed aspect-ratio. */
.meet-nari__media img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: auto;
  max-height: clamp(300px, 34vw, 460px);
  object-fit: cover;
  object-position: center 22%;
  display: block;
}
.meet-nari__media--crop { aspect-ratio: 3/4; }
.meet-nari__media--crop img { height: 100%; object-fit: cover; object-position: center 25%; }
.meet-nari h2, .meet-nari h3 { font-size: clamp(30px, 3.25vw, 40px); }

/* ---------- Offerings ---------- */
.offerings-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  position: relative;
  z-index: 1;
}
.offering-card {
  display: block;
  text-decoration: none;
  color: inherit;
  padding: var(--space-md) var(--space-sm);
  border-left: 1px solid rgba(201,162,75,0.3);
  position: relative;
  cursor: pointer;
  transition: transform 0.35s var(--ease), background 0.35s var(--ease);
}
.offering-card:first-child { border-left: none; }
.offering-card:hover, .offering-card:focus-visible {
  transform: translateY(-6px);
  background: rgba(255,255,255,0.4);
}
.offering-card__icon {
  color: var(--violet);
  margin-bottom: var(--space-sm);
  transition: color 0.35s var(--ease), transform 0.35s var(--ease);
}
.offering-card:hover .offering-card__icon {
  color: var(--gold-ink);
  transform: scale(1.12) rotate(-4deg);
}
.offering-card__icon svg { width: 26px; height: 26px; }
.offering-card h3 {
  color: var(--violet);
  font-size: 23.8px;
  margin-bottom: 12px;
  transition: color 0.3s var(--ease);
}
/* These cards are transparent and sit directly on the lavender band, and this
   rule comes after `.section--lavender p` at equal specificity — so it was
   quietly reinstating the 0.75 alpha the contrast pass removed. */
.offering-card p { font-size: 14.5px; color: var(--ink); margin-bottom: var(--space-xs); }
.offering-card__cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold-ink);
  position: relative;
  padding-bottom: 3px;
  transition: gap 0.35s var(--ease);
}
.offering-card__cta::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.45s var(--ease);
}
.offering-card:hover .offering-card__cta { gap: 10px; }
.offering-card:hover .offering-card__cta::after { transform: scaleX(1); }

/* ---------- Retreats ---------- */
.retreats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
  position: relative;
  z-index: 1;
}
.retreat-card {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--ivory);
  border: 1px solid rgba(91,62,125,0.14);
  display: flex;
  flex-direction: column;
  transition: box-shadow 0.3s var(--ease), transform 0.3s var(--ease);
}
.retreat-card:hover { transform: translateY(-3px); box-shadow: 0 12px 28px rgba(58,38,80,0.14); }
.retreat-card__media {
  aspect-ratio: 4/3;
  background: linear-gradient(160deg, #C9AEE0 0%, #E4D6F5 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.retreat-card__media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform 0.5s var(--ease);
}
.retreat-card:hover .retreat-card__media img { transform: scale(1.04); }
.retreat-card__body {
  padding: var(--space-sm);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  flex: 1;
}
.retreat-card__date {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-ink);
}
.retreat-card__title {
  font-family: var(--font-display);
  color: var(--violet);
  font-size: 25px;
  line-height: 1.3;
  margin: 0 0 4px;
}

/* The .trusted-* monogram grid lived here (78px circles, lilac glow ring). The
   2026 brief removed the Trusted By / notable-people section ("no need for
   Notable people etc"), so the markup went and this styled nothing. There is no
   version control on this project yet, so if the section returns it will need
   rebuilding rather than reverting. */

/* ---------- Testimonials ---------- */
.quote-slider {
  max-width: 700px;
  margin: var(--space-md) auto 0;
  text-align: center;
  position: relative;
  z-index: 1;
}
.quote-mark {
  font-family: var(--font-display);
  font-size: 75px;
  color: var(--gold-ink);
  line-height: 1;
  margin-bottom: 8px;
}
.section--lavender .quote-mark { color: var(--violet); }
.section--lavender .quote-attr { color: var(--violet); }
.quote-attr {
  margin-top: var(--space-sm);
  font-size: 13.4px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--amber);
}

/* ---------- Closing invitation ---------- */
.closing {
  text-align: center;
  padding: var(--space-section) 0;
  position: relative;
  /* Every other cloud parent (.section, .hero) already clips. This one didn't,
     so its 500px decorative cloud pushed the document to 688px wide on a 375px
     phone — the whole page scrolled sideways. */
  overflow: hidden;
}
.closing__inner { position: relative; z-index: 1; }
.closing h2 { font-size: clamp(40px, 5.62vw, 67.5px); }
.closing p { max-width: 50ch; margin-left: auto; margin-right: auto; }
.closing__ctas {
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-md);
  flex-wrap: wrap;
}
.closing .btn-gold { color: var(--violet); border-color: var(--violet); }
.closing .btn-gold:hover { background: var(--violet); color: var(--ivory); }

/* ---------- Footer ---------- */
/* Solid plum, and inset from the page edges rather than bleeding to them, so the
   footer reads as a deliberate block pulled in at the end of the page. It used to
   be a translucent lilac veil over the fixed crystal artwork (body::before); that
   let the backdrop show through and the footer never resolved into its own
   surface. Same plum family as .contact-card__info, so the two dark panels on a
   page belong to each other. */
.footer {
  background:
    radial-gradient(110% 70% at 12% 0%,
      rgba(201, 174, 224, 0.28) 0%,
      rgba(201, 174, 224, 0) 58%),
    linear-gradient(165deg, #432C5C 0%, #33214A 55%, #3E2853 100%);
  color: var(--ivory);
  margin: 0 clamp(12px, 2vw, 28px) clamp(12px, 2vw, 28px);
  border: 1px solid rgba(201, 162, 75, 0.30);
  border-radius: var(--radius-lg);
  padding: var(--space-lg) 0 var(--space-sm);
  overflow: hidden;
}
.footer__grid {
  display: grid;
  grid-template-columns: 1.3fr 0.9fr 1.1fr 0.9fr;
  gap: var(--space-md);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid rgba(201,162,75,0.30);
}
.footer__news-copy {
  font-size: 13px;
  line-height: 1.6;
  opacity: 0.8;
  margin: 0 0 14px;
}
.footer__news-form { display: flex; flex-direction: column; gap: 10px; }
.footer__news-form input {
  width: 100%;
  padding: 11px 14px;
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--ink);
  background: rgba(255,255,255,0.72);
  border: 1px solid rgba(201,162,75,0.45);
  border-radius: var(--radius-sm);
}
.footer__news-form input:focus {
  outline: none;
  border-color: var(--violet);
  background: #fff;
}
.footer__news-form .btn { justify-content: center; padding: 11px 20px; font-size: 11px; }

/* Form status message.
   Written by the submit handler in main.js: .is-pending while the request is in
   flight, .is-error when the endpoint rejected it, neither once it succeeded.
   This used to be an inline style carrying a single violet message that
   appeared whether or not anything had actually been sent. */
/* 404 page. Its own block rather than inline styles on the markup — the CSP
   only tolerates inline styles because 184 of them predate it, and adding more
   moves away from being able to drop 'unsafe-inline' later. */
.notfound {
  text-align: center;
  max-width: 44rem;
  margin-inline: auto;
  padding-block: clamp(4rem, 12vw, 9rem);
}
.notfound__intro {
  font-size: clamp(17px, 1.6vw, 19px);
  margin-bottom: var(--space-sm);
}
.notfound__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  margin-bottom: 2.5rem;
}
.notfound__elsewhere { font-size: 15px; }

/* Honeypot. Positioned off-screen rather than display:none or hidden, because
   the naive bots this catches skip fields the browser would not submit. A real
   visitor never reaches it: no label, out of the tab order, hidden from
   assistive tech. Anything that arrives with it filled in is discarded by the
   Worker. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.form-note {
  margin-top: 16px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--violet);
}
.form-note.is-pending { color: rgba(43, 34, 51, 0.66); font-style: italic; }
.form-note.is-error { color: var(--error); }

/* The footer sits on dark violet, where the colours above are unreadable. */
.footer .form-note { color: var(--ivory); }
.footer .form-note.is-pending { color: rgba(247, 242, 234, 0.72); }
.footer .form-note.is-error { color: var(--error-on-dark); }
.footer__logo {
  display: flex;
  flex-direction: column;
  /* centre the mark over the wordmark, matching the nav lockup; fit-content keeps
     the whole block left-aligned in the footer column rather than centring it. */
  align-items: center;
  width: fit-content;
  gap: 6px;
  margin-bottom: 14px;
}
.footer__logo-mark { width: 56px; height: 56px; object-fit: contain; display: block; }
.footer__logo-text {
  font-family: var(--font-display);
  font-size: 23.8px;
  color: var(--ivory);
}
.footer__tagline { font-size: 14px; opacity: 0.75; max-width: 32ch; }
.footer h4 {
  font-size: 13.4px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: var(--space-xs);
}
.footer__col a, .footer__col span {
  display: block;
  font-size: 14px;
  padding: 5px 0;
  opacity: 0.85;
}
.footer__col a:hover { color: var(--amber); }
.footer__social { display: flex; gap: 14px; margin-top: var(--space-sm); }
.footer__social a { color: var(--ivory); opacity: 0.7; transition: opacity 0.25s; }
.footer__social a:hover { opacity: 1; }
/* Each platform's own brand color, not the site's monochrome violet. */
.footer__social a[aria-label="Instagram"] { color: #E4405F; }
.footer__social a[aria-label="Facebook"] { color: #1877F2; }
.footer__social a[aria-label="YouTube"] { color: #FF0000; }
.footer__social a[aria-label="WhatsApp"] { color: #25D366; }
.footer__bottom {
  text-align: center;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.5;
  padding-top: var(--space-sm);
}

/* ---------- Reveal on scroll ---------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
[data-reveal].is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ---------- Contact ---------- */
.form-group { margin-bottom: var(--space-sm); }
.form-group label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--violet);
  margin-bottom: 8px;
}
.form-group input, .form-group textarea {
  width: 100%;
  border: none;
  border-bottom: 1px solid rgba(91,62,125,0.3);
  background: transparent;
  padding: 10px 2px;
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--ink);
}
.form-group input:focus, .form-group textarea:focus {
  outline: none;
  border-color: var(--gold);
}
.contact-info__item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid rgba(91,62,125,0.12);
  font-size: 15px;
}
.contact-info__icon { color: var(--gold-ink); flex-shrink: 0; }
.contact-social { display: flex; gap: 14px; margin-top: var(--space-sm); }
.contact-social a { color: var(--violet); opacity: 0.75; }
.contact-social a:hover { opacity: 1; }

/* ---------- Contact card ----------
   One floating card holding both halves of getting in touch: the form on a mild
   glossy plum, the details on the deep plum beside it. The two panels meet with
   no gutter and the card clips them, so the colour change itself is the divider.

   The gloss is a specular sweep — a white diagonal that is strong in the top
   corner and gone by halfway — over a plum-tinted body, plus a one-pixel inset
   highlight along the top edge. Kept mild deliberately: the page behind it is
   already a pastel sky, and a hard shine would fight it. */
.contact-card {
  display: grid;
  grid-template-columns: 1.12fr 0.88fr;
  align-items: stretch;
  position: relative;
  z-index: 1;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid rgba(201, 162, 75, 0.38);
  box-shadow:
    0 30px 64px -24px rgba(58, 38, 80, 0.45),
    0 2px 8px rgba(58, 38, 80, 0.10);
}

.contact-card__form {
  padding: clamp(26px, 3.2vw, 52px);
  background:
    linear-gradient(135deg,
      rgba(255, 255, 255, 0.62) 0%,
      rgba(255, 255, 255, 0.18) 30%,
      rgba(255, 255, 255, 0) 58%),
    linear-gradient(160deg, #E9DEF1 0%, #F3ECF7 52%, #ECE1F2 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75);
}

.contact-card__info {
  padding: clamp(26px, 3.2vw, 52px);
  background:
    radial-gradient(120% 78% at 14% 0%,
      rgba(201, 174, 224, 0.32) 0%,
      rgba(201, 174, 224, 0) 55%),
    linear-gradient(165deg, #432C5C 0%, #33214A 55%, #3E2853 100%);
  color: var(--ivory);
}

.contact-card__title {
  font-family: var(--font-display);
  font-size: clamp(24px, 2.1vw, 30px);
  font-weight: 500;
  line-height: 1.2;
  margin: 0 0 var(--space-sm);
}
.contact-card__form .contact-card__title { color: var(--violet); }
.contact-card__info .contact-card__title { color: var(--ivory); }

/* The blurb under the info heading. Typed as a class rather than a bare
   element so no section-level `... p { color }` rule can reclaim it — that trap
   has cost this project five debugging sessions already. */
.contact-card__info p.contact-card__blurb {
  color: rgba(247, 242, 234, 0.86);
  font-size: 15px;
  margin: 0 0 var(--space-md);
  max-width: 34ch;
}

/* Deep-plum repaint of the shared contact-info list. Each selector carries one
   more class than the light-background original so it wins on specificity, not
   on source order. */
/* Centred, not flex-start: the tel: and mailto: links carry 44px tap-target
   padding while the plain spans do not, so aligning to the top left the icons of
   the padded rows floating above their own text. */
.contact-card__info .contact-info__item {
  align-items: center;
  border-bottom-color: rgba(247, 242, 234, 0.16);
  color: var(--ivory);
  padding: 14px 0;
}
.contact-card__info .contact-info__item:last-of-type { border-bottom: none; }
.contact-card__info .contact-info__icon { color: var(--amber); }
.contact-card__info .contact-info__item a { color: var(--ivory); border-bottom-color: var(--amber); }
.contact-card__info .contact-social { gap: 16px; margin-top: var(--space-md); }
.contact-card__info .contact-social a { color: var(--ivory); opacity: 0.8; }
.contact-card__info .contact-social a:hover { opacity: 1; color: var(--amber); }

/* Two fields to a row, the reference's pairing. Column gap only — the rows'
   vertical rhythm still comes from .form-group's own margin-bottom. */
.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 var(--space-sm);
}

/* The underline needs a touch more weight on the tinted panel than it carried
   on the near-white section background. */
.contact-card__form .form-group input,
.contact-card__form .form-group textarea {
  border-bottom-color: rgba(91, 62, 125, 0.38);
}
.contact-card__form .form-group input::placeholder,
.contact-card__form .form-group textarea::placeholder {
  color: rgba(43, 34, 51, 0.42);
}
.contact-card__form .form-group input:focus,
.contact-card__form .form-group textarea:focus {
  border-bottom-color: var(--gold-ink);
}
.contact-card__submit { margin-top: var(--space-sm); }

/* The booking form sets its own reading measure for a full-width section; inside
   the card the panel is already the measure. */
.contact-card__form .bform { max-width: none; margin: 0; }

/* Media queries live below the rules they override — a @media block placed
   earlier in this file loses silently, since media adds no specificity. */
@media (max-width: 900px) {
  .contact-card { grid-template-columns: 1fr; }
}
@media (max-width: 620px) {
  .form-row { grid-template-columns: 1fr; gap: 0; }
}

/* ---------- Event detail ---------- */
.event-detail-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-lg);
  align-items: start;
}
.event-poster {
  border-radius: var(--radius-md);
  overflow: hidden;
  background: linear-gradient(160deg, #C9AEE0 0%, #E4D6F5 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-md);
  min-height: 320px;
}
.event-poster img { max-width: 100%; max-height: 460px; object-fit: contain; border-radius: var(--radius-sm); }
.event-detail-meta { display: flex; gap: var(--space-sm); flex-wrap: wrap; margin-bottom: var(--space-md); }
.event-detail-meta span {
  font-size: 13px;
  font-weight: 600;
  color: var(--violet);
  background: rgba(201,174,224,0.18);
  padding: 8px 14px;
  border-radius: 999px;
}

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
  /* --space-xl / --space-lg used to be stepped down here; the fluid clamps in
     :root now cover it continuously. */
  /* The :has() track-flip further up is (0,2,0) and this collapse is (0,1,0),
     so a bare `.meet-nari` here loses and text-first rows stayed two-column on a
     phone — a 128px-wide photograph beside a squeezed column of text. The
     selector has to be repeated to match. */
  .meet-nari,
  .meet-nari:has(> .meet-nari__media:last-child) { grid-template-columns: 1fr; gap: var(--space-md); }
  .offerings-grid { grid-template-columns: repeat(2, 1fr); }
  .offering-card:nth-child(3) { border-left: none; }
  .retreats-grid { grid-template-columns: 1fr; }
  .event-detail-grid { grid-template-columns: 1fr; }
  .footer__grid { grid-template-columns: 1fr; gap: var(--space-sm); }
  /* The mobile nav used to be declared here as well. It now lives in the
     max-width:1000px block at the foot of this file — that block supersedes
     everything this one said about the nav, so keeping both was dead weight. */
}

/* Four footer columns need more room than three did; drop to two before the
   single-column breakpoint rather than letting the newsletter input crush. */
@media (min-width: 901px) and (max-width: 1080px) {
  .footer__grid { grid-template-columns: 1fr 1fr; row-gap: var(--space-md); }
}

@media (max-width: 560px) {
  .section { padding: var(--space-lg) 0; }
  .section__inner { padding: 0 20px; }
  .hero__content { padding: 0 20px var(--space-lg); }

  /* Every other grid collapses to one column by this width; this one stopped at
     two and kept going, which left ~90px of usable text per card on a 320px
     phone once the card padding was taken out. */
  .offerings-grid { grid-template-columns: 1fr; }
  .offering-card { border-left: none; }
}

/* =====================================================
   Inner-page components (About, Offerings, Retreats, Art, Contact)
   ===================================================== */

.section--lilac { background: linear-gradient(180deg, rgba(241,233,250,0.80) 0%, rgba(247,242,234,0.80) 100%); }

.btn-row {
  display: flex;
  gap: var(--space-sm);
  justify-content: center;
  flex-wrap: wrap;
  margin-top: var(--space-md);
}

/* ---------- Short inner-page hero ---------- */
.hero--short {
  min-height: 52vh;
  align-items: center;
  padding-top: 100px;
}
.hero--short .hero__content { padding-bottom: 0; text-align: center; margin: 0 auto; }
.hero--short .hero__sub { margin: 0 auto; }
.hero--short.hero--plain {
  /* Lightest veil on the site — the inner-page hero is where the amethyst
     artwork is meant to be seen, and the type here is large enough to carry it. */
  background: linear-gradient(160deg, rgba(201,174,224,0.62) 0%, rgba(228,214,245,0.62) 100%);
}
.hero--short.hero--plain .hero__content { color: var(--ink); }
.hero--short.hero--plain .hero__content h1 { color: var(--violet); }
.hero--short.hero--plain .hero__content .eyebrow { color: var(--violet); }
.hero--short.hero--plain .hero__sub { color: rgba(43,34,51,0.88); }
.hero--short.hero--plain::before {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(160deg, rgba(255,255,255,0.4) 0%, transparent 60%);
}

/* ---------- Pull quote (About / Offerings closing lines) ---------- */
.pull-quote {
  max-width: 720px;
  margin: 0 auto;
  text-align: center;
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(19px, 2.2vw, 25px);
  color: var(--violet);
  line-height: 1.55;
}
.section--plum .pull-quote { color: var(--violet); }
.section--lavender p.pull-quote { color: var(--violet); }

/* ---------- Service rows (alternating image/text) ---------- */
.service-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-lg);
  align-items: center;
  margin-bottom: var(--space-lg);
}
.service-row:last-child { margin-bottom: 0; }
.service-row--reverse .service-row__media { order: 2; }
.service-row__image { border-radius: var(--radius-sm); width: 100%; aspect-ratio: 4/3; max-height: clamp(280px, 30vw, 400px); object-fit: cover; }
.tag {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold-ink);
  border: 1px solid rgba(201,162,75,0.4);
  border-radius: 999px;
  padding: 5px 14px;
  margin-bottom: var(--space-xs);
}

/* ---------- Card grids: services / principles / media / programs ---------- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
  position: relative;
  z-index: 1;
}
.card-grid--4 { grid-template-columns: repeat(4, 1fr); }
.card-grid--2 { grid-template-columns: repeat(2, 1fr); max-width: 800px; margin: 0 auto; }

.info-card {
  display: block;
  background: rgba(255,255,255,0.6);
  border: 1px solid rgba(91,62,125,0.12);
  border-radius: var(--radius-md);
  padding: var(--space-md) var(--space-sm);
  text-decoration: none;
  color: inherit;
  transition: transform 0.35s var(--ease), background 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.info-card:hover, .info-card:focus-visible {
  transform: translateY(-5px);
  background: rgba(255,255,255,0.85);
  box-shadow: 0 14px 30px rgba(58,38,80,0.12);
}
.info-card h3 { font-size: 22.5px; margin-bottom: 10px; transition: color 0.3s var(--ease); }
.info-card:hover h4 { color: var(--gold-ink); }
.info-card p { font-size: 14.5px; margin-bottom: 0; }

/* ---------- Upcoming events carousel (Retreats page) ----------
   One focused card at a time, scroll-snapped to center; neighbours sit
   dimmed and half-visible at the edges. Native horizontal scroll drives
   it (works for touch swipe for free); JS only toggles .is-active on
   whichever card is nearest centre and drives the arrow buttons. */

/* ---------- Recently completed events (small uniform tiles) ----------
   Fixed size regardless of count, matching visualidentity.studio's client
   strip — the grid grows as more events lapse into the past, it never
   stretches the handful there are today to fill the row. */
.past-events-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; }
.past-event-tile {
  position: relative;
  display: block;
  width: 150px; height: 150px;
  border-radius: var(--radius-md);
  overflow: hidden;
}
.past-event-tile img {
  width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(0.4);
  transition: filter 0.35s, transform 0.35s;
}
.past-event-tile:hover img { filter: grayscale(0); transform: scale(1.05); }
.past-event-tile::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(58,38,80,0.82) 0%, rgba(58,38,80,0.05) 55%, transparent 75%);
}
.past-event-tile__title, .past-event-tile__date {
  position: absolute; left: 12px; right: 12px;
  color: var(--ivory);
}
.past-event-tile__title { bottom: 26px; font-size: 14px; font-weight: 600; line-height: 1.25; }
.past-event-tile__date { bottom: 10px; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.85; }

/* ---------- Testimonial cards ---------- */
.testimonial-card {
  background: rgba(255,255,255,0.45);
  border: 1px solid rgba(91,62,125,0.14);
  border-radius: var(--radius-md);
  padding: var(--space-md);
}
.testimonial-card p.testimonial-card__quote {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 18px;
  color: var(--violet);
  margin-bottom: var(--space-sm);
}
.testimonial-card p.testimonial-card__name {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--violet);
  margin: 0;
}

/* ---------- Form select / date parity ---------- */
.form-group select, .form-group input[type="date"] {
  width: 100%;
  border: none;
  border-bottom: 1px solid rgba(91,62,125,0.3);
  background: transparent;
  padding: 10px 2px;
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--ink);
  border-radius: 0;
  appearance: none;
}
.form-group select:focus, .form-group input[type="date"]:focus {
  outline: none;
  border-color: var(--gold);
}

/* ---------- Newsletter ---------- */
.newsletter { max-width: 640px; margin: 0 auto; text-align: center; }
.newsletter__form {
  display: flex;
  gap: 12px;
  margin-top: var(--space-md);
  max-width: 460px;
  margin-left: auto;
  margin-right: auto;
}
.newsletter__form input {
  flex: 1;
  border: 1px solid rgba(91,62,125,0.3);
  border-radius: 999px;
  padding: 13px 20px;
  font-family: var(--font-body);
  font-size: 14px;
  background: rgba(255,255,255,0.6);
}
.newsletter__form input:focus { outline: none; border-color: var(--gold); }
.newsletter__check {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: var(--space-sm);
  font-size: 13px;
  opacity: 0.75;
}

@media (max-width: 900px) {
  .service-row, .service-row--reverse {
    grid-template-columns: 1fr;
    gap: var(--space-sm);
  }
  .service-row--reverse .service-row__media { order: 0; }
  .card-grid, .card-grid--4, .card-grid--2 { grid-template-columns: 1fr 1fr; }
  .card-grid--2 { max-width: none; }
  .newsletter__form { flex-direction: column; }
}
@media (max-width: 560px) {
  .card-grid, .card-grid--4 { grid-template-columns: 1fr; }
}

/* ============================================================
   Gallery — photographs recovered from the original site
   ============================================================ */
.gallery-grid {
  columns: 3;
  column-gap: var(--space-sm);
}
.gallery-figure {
  break-inside: avoid;
  margin: 0 0 var(--space-sm);
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid rgba(91,62,125,0.12);
  background: rgba(255,255,255,0.5);
}
.gallery-figure img {
  display: block;
  width: 100%;
  height: auto;
  transition: transform 0.8s var(--ease);
}
.gallery-figure:hover img { transform: scale(1.04); }
.gallery-figure figcaption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: var(--space-sm) var(--space-xs) var(--space-xs);
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-on-dark);
  background: linear-gradient(to top, rgba(58,38,80,0.88) 0%, rgba(58,38,80,0.55) 55%, rgba(58,38,80,0) 100%);
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.gallery-figure:hover figcaption,
.gallery-figure:focus-within figcaption { opacity: 1; }
@media (max-width: 900px) { .gallery-grid { columns: 2; } }
@media (max-width: 560px) { .gallery-grid { columns: 1; } }

/* Always show captions where hover doesn't exist */
@media (hover: none) {
  .gallery-figure figcaption { opacity: 1; }
}

/* ============================================================
   Journal — the original site's blog archive
   ============================================================ */
.journal-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
}
.journal-card {
  display: flex;
  flex-direction: column;
  background: rgba(255,255,255,0.6);
  border: 1px solid rgba(91,62,125,0.12);
  border-radius: var(--radius-md);
  overflow: hidden;
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.journal-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 14px 38px rgba(91,62,125,0.14);
}
.journal-card__media {
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: linear-gradient(135deg, var(--lilac), var(--amber));
}
.journal-card__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.8s var(--ease);
}
.journal-card:hover .journal-card__media img { transform: scale(1.05); }
.journal-card__body { padding: var(--space-sm); display: flex; flex-direction: column; gap: 10px; }
.journal-card__date {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold-ink);
}
.journal-card__body h3 {
  font-size: 25px;
  line-height: 1.3;
  margin: 0;
}
.journal-card__body p { font-size: 15px; margin: 0; }
@media (max-width: 900px) { .journal-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .journal-grid { grid-template-columns: 1fr; } }

/* Year divider inside the journal */
.journal-year {
  font-family: var(--font-display);
  font-size: 14.6px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--violet);
  margin: var(--space-md) 0 var(--space-sm);
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(91,62,125,0.16);
}
.journal-year:first-child { margin-top: 0; }

/* Nav crowding between the mobile breakpoint and desktop is handled by the
   901-1180px block at the end of this file (see "2026 brief" section). */

/* ============================================================
   2026 brief — new components
   ============================================================ */

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --- Newsletter pop-up ------------------------------------- */
.np-overlay {
  position: fixed; inset: 0; z-index: 900;
  display: flex; align-items: center; justify-content: center;
  padding: var(--space-sm);
  background: rgba(58,38,80,0.55);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.np-overlay[hidden] { display: none; }
.np-overlay.is-open { opacity: 1; }
.np-card {
  position: relative;
  width: 100%; max-width: 480px;
  background: var(--sky-bg);
  border: 1px solid rgba(201,162,75,0.4);
  border-radius: var(--radius-md);
  padding: var(--space-md) var(--space-sm) var(--space-sm);
  text-align: center;
  box-shadow: 0 30px 70px rgba(58,38,80,0.35);
  transform: translateY(16px);
  transition: transform 0.5s var(--ease);
}
.np-overlay.is-open .np-card { transform: none; }
.np-card h3 { font-size: clamp(30px, 4.25vw, 37.5px); margin: 4px 0 12px; }
.np-body { font-size: 15px; margin: 0 0 var(--space-sm); }
.np-close {
  position: absolute; top: 10px; right: 14px;
  background: none; border: 0; cursor: pointer;
  font-size: 26px; line-height: 1; color: var(--violet);
  padding: 4px 8px;
}
.np-form { display: flex; gap: 10px; flex-wrap: wrap; }
.np-form input[type="email"] {
  flex: 1 1 200px; min-width: 0;
  padding: 14px 16px;
  border: 1px solid rgba(91,62,125,0.3);
  border-radius: 999px;
  background: rgba(255,255,255,0.7);
  font-family: var(--font-body); font-size: 15px;
  color: var(--ink);
}
.np-check {
  display: flex; align-items: flex-start; gap: 9px;
  margin-top: 14px;
  font-size: 13.5px; line-height: 1.5;
  text-align: left;
  color: rgba(43,34,51,0.8);
}
.np-check input { margin-top: 3px; flex: none; }

/* --- Three pillars (Corporate / Healing Justice / Bespoke) --- */
.pillar-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
}
.pillar-card {
  display: flex; flex-direction: column;
  background: rgba(255,255,255,0.45);
  border: 1px solid rgba(201,162,75,0.28);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.pillar-card__media { aspect-ratio: 4 / 3; overflow: hidden; }
.pillar-card__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease);
}
.pillar-card:hover .pillar-card__media img { transform: scale(1.05); }
.pillar-card__body {
  padding: var(--space-sm);
  display: flex; flex-direction: column; flex: 1;
}
.pillar-card__body h3 { font-size: 27.5px; margin: 0 0 10px; }
.pillar-card__body p { font-size: 15px; margin: 0 0 var(--space-sm); }
.pillar-card__body .btn { margin-top: auto; align-self: flex-start; }
@media (max-width: 900px) { .pillar-grid { grid-template-columns: 1fr; } }

/* --- Chocolate box: menu of services, reveal on hover/focus -- */
.choc-box {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 2px;
  background: rgba(201,162,75,0.28);
  border: 1px solid rgba(201,162,75,0.28);
}
.choc-item {
  position: relative;
  background: var(--ivory);
  padding: var(--space-sm) 20px;
  min-height: 132px;
  display: flex; align-items: center; justify-content: center;
  text-align: center;
  cursor: pointer;
  border: 0;
  width: 100%;
  font: inherit;
  color: var(--violet);
  transition: background 0.4s var(--ease);
}
.choc-item__name {
  font-family: var(--font-display);
  font-size: 22.5px; line-height: 1.3;
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.choc-item__desc {
  position: absolute; inset: 0;
  display: flex; align-items: center;
  padding: 18px;
  font-size: 14px; line-height: 1.55;
  color: var(--ivory);
  background: var(--violet);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
  pointer-events: none;
}
.choc-item:hover .choc-item__name,
.choc-item:focus-visible .choc-item__name,
.choc-item.is-open .choc-item__name { opacity: 0; transform: translateY(-6px); }
.choc-item:hover .choc-item__desc,
.choc-item:focus-visible .choc-item__desc,
.choc-item.is-open .choc-item__desc { opacity: 1; }

/* --- Standalone quote band --------------------------------- */
/* 900px, not the usual 720px measure: the longest sentence needs 853px at the
   40px cap, and this quote is set one sentence per line — a narrower band would
   wrap the third line and undo that. Only used on index.html. */
.quote-band { text-align: center; max-width: 900px; margin: 0 auto; }
.quote-band blockquote {
  margin: 0;
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(26px, 4vw, 40px);
  line-height: 1.35;
  color: var(--violet);
}
/* One sentence per line. Blocks rather than <br> so a sentence that is still
   too long for a narrow screen wraps naturally within its own line. */
.quote-band blockquote span { display: block; }
.quote-band cite {
  display: block;
  margin-top: var(--space-sm);
  font-family: var(--font-body);
  font-style: normal;
  font-size: 14px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gold-ink);
}
.section--plum .quote-band blockquote { color: var(--violet); }
.section--plum .quote-band cite { color: var(--gold-ink); }

/* --- Creative hub platform tiles --------------------------- */
.hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-sm);
}
.hub-card {
  display: flex; flex-direction: column;
  padding: var(--space-sm);
  background: rgba(255,255,255,0.45);
  border: 1px solid rgba(201,162,75,0.28);
  border-radius: var(--radius-md);
  transition: transform 0.35s var(--ease), background 0.35s var(--ease);
}
.hub-card:hover { transform: translateY(-5px); background: rgba(255,255,255,0.68); }
.hub-card h3 { font-size: 25px; margin: 0 0 8px; }
.hub-card p { font-size: 15px; margin: 0 0 var(--space-sm); }
.hub-card .text-link { margin-top: auto; }
.hub-card__pending {
  margin-top: auto;
  font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--gold-ink);
}

/* --- Multi-step booking form ------------------------------- */
.bform { max-width: 720px; margin: 0 auto; }
.bform__progress {
  display: flex; gap: 6px;
  margin-bottom: var(--space-md);
}
.bform__progress span {
  flex: 1; height: 2px;
  background: rgba(91,62,125,0.18);
  transition: background 0.4s var(--ease);
}
.bform__progress span.is-done { background: var(--gold); }
.bform__step { display: none; }
.bform__step.is-active { display: block; }
.bform__step h2 { font-size: clamp(30px, 4.25vw, 40px); margin: 0 0 var(--space-sm); }
.bform__nav {
  display: flex; justify-content: space-between; gap: var(--space-xs);
  margin-top: var(--space-md);
}
.bform__nav .btn-back {
  background: none; border: 0; cursor: pointer;
  font-family: var(--font-body); font-size: 12.5px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--violet);
}
.bform__nav .btn-back[hidden] { visibility: hidden; display: block; }
.check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 10px;
}
.check-pill {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 13px 16px;
  border: 1px solid rgba(91,62,125,0.22);
  border-radius: var(--radius-sm);
  background: rgba(255,255,255,0.45);
  font-size: 14.5px; line-height: 1.45;
  cursor: pointer;
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease);
}
.check-pill:hover { border-color: var(--gold); background: rgba(255,255,255,0.7); }
.check-pill input { margin-top: 3px; flex: none; }
.check-pill__group {
  grid-column: 1 / -1;
  margin: var(--space-xs) 0 2px;
  font-family: var(--font-body);
  font-size: 11.5px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--gold-ink);
}
.check-pill__group:first-child { margin-top: 0; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-xs); }
@media (max-width: 620px) { .form-row { grid-template-columns: 1fr; } }
.form-consent {
  display: flex; align-items: flex-start; gap: 10px;
  margin-top: var(--space-sm);
  padding: 16px;
  border-left: 2px solid var(--gold);
  background: rgba(255,255,255,0.4);
  font-size: 13.5px; line-height: 1.55;
}
.form-consent input { margin-top: 3px; flex: none; }

/* Nav is now 5 items + CTA (was 7). Keep the mid-band tightening —
   the labels are longer than before, so the squeeze still applies. */
@media (min-width: 1001px) and (max-width: 1180px) {
  .nav__links { gap: 18px; font-size: 13px; }
  .nav__inner { padding: 0 var(--space-sm); }
  .nav__cta { padding-left: 15px; padding-right: 15px; }
}

/* Credential stack under the Meet Nari heading */
.meet-nari__creds {
  font-family: var(--font-display);
  font-size: 17px;
  font-style: italic;
  line-height: 1.5;
  color: var(--gold-ink);
  margin: -6px 0 var(--space-sm);
}

/* The brief's page names are much longer labels than the old nav had
   ("Healing & Wellness" vs "Offerings"), so the desktop nav now runs out of
   room around 1000px rather than 900px. Hand over to the hamburger earlier;
   everything else keeps its 900px breakpoint. */
@media (max-width: 1000px) {
  .nav__links {
    position: fixed;
    inset: 0;
    /* background-color as a solid fallback, separate from the gradient:
       backdrop-filter can lag a frame behind the transform transition on
       some engines (WebKit especially), which briefly drops the gradient
       and leaves the panel fully see-through over the hero photo. The
       solid color paints immediately so the panel is never transparent,
       even if the blur/gradient layer takes a moment to composite. */
    background-color: #C9AEE0;
    background-image: linear-gradient(160deg, rgba(201,174,224,0.82) 0%, rgba(228,214,245,0.82) 100%);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    /* Promotes the panel to its own compositor layer ahead of time, so the
       backdrop-filter is ready before the transform transition starts
       rather than racing it. */
    will-change: transform, opacity;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-md);
    transform: translateY(-100%);
    opacity: 0;
    transition: transform 0.4s var(--ease), opacity 0.4s var(--ease);
    z-index: 99;
  }
  .nav__links.is-open { transform: translateY(0); opacity: 1; overflow-y: auto; padding: 92px 24px 40px; justify-content: flex-start; }
  .nav__links .nav__link { color: var(--violet); font-size: 20px; }
  .nav__item { text-align: center; }
  /* No hover on a phone, so the panels are simply always open — the whole
     menu becomes one scrollable list rather than a set of flyouts. */
  .nav__caret { display: none; }
  .nav__panel {
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    min-width: 0;
    padding-top: 8px;
  }
  .nav__panel a {
    background: none;
    border: 0;
    box-shadow: none;
    border-radius: 0;
    padding: 7px 0;
    font-size: 14px;
    color: var(--plum);
    opacity: 0.85;
  }
  /* The desktop panel's first/last-child rules carry a border, radius and drop
     shadow, and out-specify a plain `.nav__panel a` override — so they have to
     be unset by name or they paint stray plates behind the mobile list. */
  .nav__panel a:first-child,
  .nav__panel a:last-child {
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  .nav__panel a:hover { background: none; padding-left: 0; }
  .nav__toggle { display: flex; position: relative; z-index: 101; }
  .desktop-only { display: none; }
}

/* fieldset carries browser-default border/padding/margin — the step is a
   plain block, the <h3> inside it does the labelling. */
.bform__step { border: 0; padding: 0; margin: 0; min-width: 0; }

/* ---------- About: daily sadhana ---------- */
.practice-list {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: 720px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0 var(--space-md);
}
.practice-list li {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 13px 0;
  font-size: 16px;
  border-bottom: 1px solid rgba(201,162,75,0.3);
}
.practice-list__dur {
  flex: 0 0 92px;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 18.8px;
  color: var(--gold-ink);
}
.practice-note {
  max-width: 56ch;
  margin: var(--space-md) auto 0;
  text-align: center;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 22.5px;
  line-height: 1.6;
  color: var(--violet);
}
@media (max-width: 700px) { .practice-list { grid-template-columns: 1fr; } }

/* ---------- About: media features grid ---------- */
.press-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
}
.press-card {
  display: flex;
  flex-direction: column;
  background: var(--ivory);
  border: 1px solid rgba(201,162,75,0.35);
  border-radius: var(--radius-md);
  padding: var(--space-sm);
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
a.press-card:hover { transform: translateY(-4px); box-shadow: 0 18px 40px rgba(58,38,80,0.16); }

/* These cards used to lead with a 16:9 still from each segment, which made five
   small mentions read as heavy as the page's real photography. The mark of the
   outlet is enough to identify it, so each card now carries one 48px badge and
   lets the type do the work.

   The badges come from two different kinds of source — real outlet logos where
   the rights holder publishes one (KTN, Capital FM) and platform icons where
   they do not — so the plate is what makes them a set: same box, same border,
   same radius, whatever sits inside it. */
.press-card__icon {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  border: 1px solid rgba(201,162,75,0.30);
  border-radius: var(--radius-sm);
  margin-bottom: 14px;
  overflow: hidden;
}
.press-card__icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 5px;
}
.press-card__icon svg { display: block; }
.press-card__body { display: flex; flex-direction: column; flex: 1; }
.press-card__body h3 { font-size: 20px; margin: 4px 0 6px; }
.press-card__body p { font-size: 13.5px; line-height: 1.6; margin-bottom: 12px; }
.press-card__cta {
  margin-top: auto;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--violet);
}
.press-card__links { margin-top: auto; display: flex; flex-direction: column; gap: 8px; }
.press-card__links a {
  font-size: 13px;
  color: var(--violet);
  border-bottom: 1px solid rgba(201,162,75,0.5);
  align-self: flex-start;
  transition: border-color 0.25s;
}
.press-card__links a:hover { border-bottom-color: var(--violet); }
@media (max-width: 1100px) { .press-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px)  { .press-grid { grid-template-columns: 1fr; } }

/* ---------- Inner-page photo hero ----------
   The wide, green, "deep exhale" band the 2026 brief asks for on Healing &
   Wellness and Events & Retreats. Type sits on a darkened photograph rather
   than the amethyst artwork, so it borrows the homepage's on-photo treatment.
   Safe now that the header is a near-opaque lilac bar — nav contrast no longer
   depends on what the hero puts behind it. */
.hero--photo {
  position: relative;
  overflow: hidden;
  min-height: 62vh;
  align-items: center;
  padding-top: 100px;
  background: var(--plum);
}
.hero--photo .hero__bg { position: absolute; inset: 0; z-index: 0; }
.hero--photo .hero__bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transform: scale(1.04);
  animation: kenburns 28s ease-in-out infinite alternate;
}
.hero--photo .hero__scrim {
  background: linear-gradient(0deg,
      rgba(30,42,30,0.62) 0%,
      rgba(30,42,30,0.38) 50%,
      rgba(43,34,51,0.52) 100%);
}
.hero--photo .hero__content {
  text-align: center;
  margin: 0 auto;
  padding-bottom: var(--space-lg);
  color: var(--ivory);
}
.hero--photo .hero__content h1 { color: var(--ivory); text-shadow: 0 1px 3px rgba(20,14,26,0.55), 0 10px 34px rgba(20,14,26,0.4); }
.hero--photo .hero__content .eyebrow { color: var(--amber); text-shadow: 0 1px 3px rgba(20,14,26,0.6); }
.hero--photo .hero__sub { color: rgba(247,242,234,0.94); margin: 0 auto; text-shadow: 0 1px 3px rgba(20,14,26,0.6); }
.hero--photo .btn-gold { color: var(--amber); border-color: var(--amber); }
.hero--photo .btn-gold:hover { background: var(--amber); color: var(--plum); }
@media (prefers-reduced-motion: reduce) { .hero--photo .hero__bg img { animation: none; } }

/* Jump links under the hero intro */
.jump-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: var(--space-md);
}

/* Pillar cards with the title above the photo (2026 brief) */
.pillar-card__head {
  padding: var(--space-sm) var(--space-sm) 0;
  /* Keeps the three photos starting on the same line when one card's eyebrow
     wraps to a second line and the others don't. */
  min-height: 104px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.pillar-card__head .eyebrow { margin-bottom: 4px; }
.pillar-card__head h2 { margin: 0 0 var(--space-sm); }
@media (max-width: 900px) { .pillar-card__head { min-height: 0; } }

/* "What is sound healing" hand-off card */
.explainer {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: var(--space-md);
  align-items: center;
  max-width: 980px;
  margin: 0 auto;
  background: var(--ivory);
  border: 1px solid rgba(201,162,75,0.35);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.explainer__body { padding: var(--space-md); }
.explainer__media { height: 100%; min-height: 260px; max-height: clamp(300px, 34vw, 460px); }
.explainer__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 800px) {
  .explainer { grid-template-columns: 1fr; }
  .explainer__media { order: -1; min-height: 200px; }
}

/* ---------- Solid CTA on photo heroes ----------
   The outlined gold button all but vanished against a photograph — the brief
   flagged both Events & Retreats hero buttons as invisible. One solid, one
   outlined keeps the pair legible and still ranks them. */
.btn-solid-amber {
  background: var(--amber);
  color: var(--plum);
  border-color: var(--amber);
}
.btn-solid-amber:hover { background: var(--ivory); border-color: var(--ivory); color: var(--plum); }

/* ---------- Bespoke experiences tick list ---------- */
.tick-list { list-style: none; margin: 0; padding: 0; }
.tick-list li {
  position: relative;
  padding: 7px 0 7px 26px;
  font-size: 15px;
  line-height: 1.6;
}
.tick-list li::before {
  content: "";
  position: absolute;
  left: 4px; top: 15px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--gold);
}

/* ---------- Upcoming events: agenda layout ----------
   Replaces the horizontal carousel the brief called hard to read. A dated row
   scans top-to-bottom, so date, title, place and price are all readable
   without moving anything. */
.agenda { list-style: none; margin: 0 auto; padding: 0; max-width: 900px; }
.agenda__row {
  display: grid;
  grid-template-columns: 74px 160px 1fr auto;
  gap: var(--space-sm);
  align-items: center;
  padding: 18px 0;
  border-bottom: 1px solid rgba(201,162,75,0.3);
}
.agenda__row:first-child { border-top: 1px solid rgba(201,162,75,0.3); }
.agenda__date {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.05;
  color: var(--violet);
}
.agenda__mon { font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--gold-ink); }
.agenda__day { font-family: var(--font-display); font-size: 42.5px; }
.agenda__yr { font-size: 11px; letter-spacing: 0.08em; opacity: 0.7; }
.agenda__media { border-radius: var(--radius-sm); overflow: hidden; aspect-ratio: 16 / 10; }
.agenda__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.agenda__body h3 { font-size: 26.2px; margin: 0 0 5px; }
.agenda__meta { font-size: 14px; color: rgba(43,34,51,0.72); margin: 0; }
.agenda__price { font-size: 13px; color: var(--violet); margin: 5px 0 0; }
.agenda__cta .btn { white-space: nowrap; }

@media (max-width: 820px) {
  .agenda__row {
    grid-template-columns: 64px 1fr;
    grid-template-areas: "date body" "media media" "cta cta";
    row-gap: 12px;
  }
  .agenda__date { grid-area: date; align-items: flex-start; }
  .agenda__body { grid-area: body; }
  .agenda__media { grid-area: media; aspect-ratio: 16 / 7; }
  .agenda__cta { grid-area: cta; }
  .agenda__cta .btn { width: 100%; justify-content: center; }
}

/* ---------- Auto-sliding review strip ----------
   The brief keeps only a few event/retreat reviews here and wants them moving
   on their own. The track holds the set twice so the loop has no visible seam;
   the duplicate half is aria-hidden so screen readers hear each quote once. */
.marquee {
  overflow: hidden;
  padding: 4px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex;
  gap: var(--space-sm);
  width: max-content;
  animation: marquee 46s linear infinite;
}
.marquee:hover .marquee__track { animation-play-state: paused; }
.marquee__item {
  flex: 0 0 auto;
  width: min(400px, 82vw);
  background: rgba(255,255,255,0.5);
  border: 1px solid rgba(201,162,75,0.3);
  border-radius: var(--radius-sm);
  padding: var(--space-sm);
}
.marquee__item .testimonial-card__quote { margin-bottom: 12px; }
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; flex-wrap: wrap; width: 100%; justify-content: center; }
  .marquee { -webkit-mask-image: none; mask-image: none; }
  .marquee__item[aria-hidden="true"] { display: none; }
}

/* Event-detail meta icons — replaced the calendar/pin emoji the brief called
   childish with line icons that inherit the surrounding text colour. */
.meta-ico {
  width: 15px; height: 15px;
  vertical-align: -2px;
  margin-right: 2px;
  color: var(--gold-ink);
}

/* Hub grid column counts — the creative-hub sections vary in how many cards
   they hold, so the count is set per section rather than baked into .hub-grid. */
.hub-grid--3 { grid-template-columns: repeat(3, 1fr); }
.hub-grid--2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 900px) { .hub-grid--3, .hub-grid--2 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .hub-grid--3, .hub-grid--2 { grid-template-columns: 1fr; } }

/* The Music & Meditation hero carries a quotation rather than a description. */
.hero__sub--quote {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 21px;
  line-height: 1.55;
  max-width: 30ch;
}

/* ---------- Touch target sizing ----------
   Measured at 390px wide, several controls sat well under the 44px that both
   Google and Apple ask for — social icons at 18x29, the consent checkbox at
   13x13, standalone text links at 20px, footer links at 34px. Each grows here
   by gaining transparent padding around type and icons that already had
   whitespace on every side, so the icons and letterforms are unchanged; only
   the area that responds to a thumb is bigger. Applied at every width, since a
   larger target helps a mouse too. */

/* Social icon rows — the SVG stays 18/20px, the anchor becomes a 44px square.
   The row's own gap shrinks to compensate so it grows a little, not a lot. */
.footer__social,
.contact-social { gap: 2px; margin-top: 10px; margin-left: -11px; }
.footer__social a,
.contact-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
}

/* Footer link columns */
.footer__col a,
.footer__col span {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 0;
}

/* Footer newsletter form */
.footer__news-form input { min-height: 44px; }
.footer__news-form .btn { min-height: 44px; padding-top: 0; padding-bottom: 0; }


/* Multi-link press card and the contact detail list */
.press-card__links a,
.contact-info__item a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* Checkbox / radio rows: the <label> is the target, so it carries the height,
   and the box itself grows from 13px to a thumb-friendly 18px. */
.form-consent,
.np-check,
.check-pill,
.newsletter__check {
  min-height: 44px;
  align-items: center;
}
.form-consent input[type="checkbox"],
.np-check input[type="checkbox"],
.newsletter__check input[type="checkbox"],
.check-pill input {
  width: 18px;
  height: 18px;
  flex: none;
}

/* Text fields and textareas: 40px is short for a thumb, and an under-sized
   field is also what triggers iOS to zoom on focus. */
.form-group input,
.form-group textarea,
.form-group select { min-height: 44px; }

/* Mobile menu rows */
@media (max-width: 1000px) {
  .nav__links .nav__link { min-height: 44px; align-items: center; justify-content: center; }
  .nav__panel a { min-height: 44px; display: flex; align-items: center; justify-content: center; }
}

/* iOS Safari zooms the whole page when a focused text field is under 16px, which
   on a booking form means the visitor is fighting the viewport at exactly the
   moment they are trying to type. 16px is the documented floor that prevents it. */
.form-group input,
.form-group textarea,
.form-group select,
.form-group input[type="date"],
.footer__news-form input,
.np-form input,
.np-form input[type="email"],
.newsletter__form input {
  font-size: 16px;
}

/* ---------- Numerals in the display face ----------
   Cormorant Garamond sets oldstyle (text) figures by default — there is no
   `onum` feature in the font because oldstyle is the default and `lnum` is the
   switch. Oldstyle reads beautifully inside prose, but in the events date block
   the 0s and 4s drop below the baseline and the column stops looking like data.
   Lining + tabular figures there; lining in headings too, where numerals sit
   next to capitals ("Homecoming Retreat #3", "Beta 12-35 Hz"). Pull quotes and
   body copy keep the default oldstyle. */
.agenda__day,
.agenda__yr {
  font-variant-numeric: lining-nums tabular-nums;
}
h1, h2, h3, h4 {
  font-variant-numeric: lining-nums;
}

/* ---------- Contrast corrections ----------
   Measured from rendered pixels, not estimated: the lavender bands sample at
   rgb(205,183,229), the footer at rgb(186,164,218), the light sections at
   rgb(247,242,234). Ratios quoted below are against those. */

/* On a darkened photograph the relationship inverts — there the light brand
   gold is the legible one and --gold-ink would sink into the image. These
   heroes also carry the text-shadow stack, so they were never the problem. */
.hero__content--onphoto em,
.hero__content--onphoto .eyebrow,
.hero--photo em { color: var(--gold); }

/* Gold is not legible on the saturated lavender at any darkness that still
   reads as gold (1.31:1 at brand gold, 2.85:1 even at --gold-ink), so gold text
   on those bands becomes violet — 4.73:1. */
.section--lavender .practice-list__dur,
.section--plum .practice-list__dur,
.section--lavender .quote-band cite,
.section--plum .quote-band cite,
.section--lavender .hub-card__pending,
.section--plum .hub-card__pending { color: var(--violet); }

/* The footer used to sit on the site's most saturated lilac, where gold headings
   measured 1.08:1 — the worst figure anywhere — and --plum was the fix. It is now
   a solid plum panel, so the whole relationship inverts: --plum headings would be
   invisible and the ink links unreadable. Amber and ivory are the on-plum pair
   used everywhere else on this site (the photo heroes, .contact-card__info).
   Opacities stay at full or near it — they were lowered to buy contrast on the
   light band and cost it here. */
.footer h4 { color: var(--amber); }
.footer__col a, .footer__col span { opacity: 0.92; color: var(--ivory); }
.footer__tagline { opacity: 0.88; color: var(--ivory); }
.footer__news-copy { opacity: 0.88; color: var(--ivory); }
.footer__bottom { opacity: 0.72; color: var(--ivory); }
/* The newsletter field is a light plate on the dark panel — lift it clear of the
   translucency it carried when the footer behind it was already light. */
.footer__news-form input { background: rgba(255, 255, 255, 0.94); }
/* .btn-gold is a plum outline on transparent, which disappears entirely on the
   plum panel. Same amber inversion .section--plum already applies to it. */
.footer .btn-gold { color: var(--amber); border-color: var(--amber); }
.footer .btn-gold:hover { background: var(--amber); color: var(--plum); }

/* ============================================================
   MOBILE NAVIGATION — built to the section 15 proposal.

   Replaces a 1,208px lilac sheet that scrolled 364px inside an 844px
   viewport, showed all 21 links expanded at once, hid the brand, never
   turned the hamburger into a close control, and set the primary
   conversion action to display:none.

   This block sits at the END of the stylesheet on purpose. The earlier
   mobile block already styles .nav__panel a and .nav__link at the same
   weight, so these have to win on source order — counting selectors first
   is the lesson from the six specificity bugs on this project.
   ============================================================ */

/* the disclosure button is injected by main.js for the mobile sheet only */
.nav__sub { display: none; }

@media (max-width: 1000px) {

  /* --- the sheet: the footer's plum, warmed toward lilac ---
     Same construction as .footer — a lilac wash over a plum gradient — but the
     gradient is lifted toward violet and the wash strengthened, so it reads
     lilac rather than near-black plum. The ceiling here is contrast, not
     taste: ivory sits on this, and the brief already had to raise the header
     veil to 94% once because nav text fell to ~2:1. Measured after changing. */
  .nav__links {
    background-color: #46305E;
    background-image:
      radial-gradient(120% 78% at 12% 0%,
        rgba(201, 174, 224, 0.34) 0%,
        rgba(201, 174, 224, 0) 62%),
      linear-gradient(165deg, #513872 0%, #3C2859 55%, #4A3164 100%);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    align-items: stretch;
    gap: 0;
  }
  /* foot padding reserves room for the pinned CTA so the last row can't sit under it */
  .nav__links.is-open { padding: 84px 0 104px; justify-content: flex-start; }

  /* The closed sheet was swallowing clicks meant for the header.
     .nav__links is inset:0, but its containing block is .nav — which carries a
     backdrop-filter, so "the whole viewport" actually resolves to the header's
     own 82px box until .nav.menu-open drops that filter. Closed, the sheet is
     therefore 82px tall, its five 57px rows overflow it, and opacity:0 does not
     stop hit-testing — so an invisible "Music & Meditation" row lay across the
     logo (row at y -12..45, logo at 14..68) and every tap on the brand opened
     art.html instead of the homepage.
     visibility is delayed by the transition duration so the close animation
     still plays out, and only then does the sheet leave the hit-test and the
     accessibility tree. */
  .nav__links {
    pointer-events: none;
    visibility: hidden;
    transition:
      transform 0.4s var(--ease),
      opacity 0.4s var(--ease),
      visibility 0s linear 0.4s;
  }
  .nav__links.is-open {
    pointer-events: auto;
    visibility: visible;
    transition:
      transform 0.4s var(--ease),
      opacity 0.4s var(--ease),
      visibility 0s linear 0s;
  }

  /* --- brand stays top-left and legible while the sheet is up --- */
  .nav.menu-open .nav__logo { position: relative; z-index: 101; }
  .nav.menu-open .nav__logo-text { color: var(--ivory); }

  /* --- hamburger becomes an explicit amber close --- */
  .nav__toggle span { transition: background 0.4s, transform 0.3s var(--ease), opacity 0.2s; }
  .nav.menu-open .nav__toggle span { background: var(--amber); }
  .nav.menu-open .nav__toggle span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
  .nav.menu-open .nav__toggle span:nth-child(2) { opacity: 0; }
  .nav.menu-open .nav__toggle span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

  /* --- five left-aligned rows at 56px --- */
  .nav__links .nav__item {
    position: relative;
    text-align: left;
    border-bottom: 1px solid rgba(247, 242, 234, 0.10);
  }
  .nav__links .nav__link {
    justify-content: flex-start;
    min-height: 56px;
    padding: 0 24px;
    color: var(--ivory);
    font-size: 17px;
    opacity: 1;
  }
  /* keep the label clear of the disclosure button's 64px target */
  .nav__links .nav__item--has-menu .nav__link { padding-right: 68px; }
  .nav__links .nav__link:hover { opacity: 1; }

  /* --- disclosure button: its own control, so the row's link still navigates --- */
  .nav__sub {
    position: absolute; top: 0; right: 0;
    width: 64px; height: 56px;
    display: flex; align-items: center; justify-content: center;
    padding: 0; border: 0; background: none; cursor: pointer;
    color: var(--amber);
  }
  .nav__sub svg { width: 12px; height: 8px; transition: transform 0.3s var(--ease); }
  .nav__item.is-expanded .nav__sub svg { transform: rotate(180deg); }

  /* --- children: collapsed, and indented behind a gold rail --- */
  .nav__links .nav__panel {
    max-height: 0;
    overflow: hidden;
    padding: 0;
    transition: max-height 0.32s var(--ease);
  }
  .nav__links .nav__panel a {
    justify-content: flex-start;
    min-height: 48px;
    margin: 0 24px;
    padding: 0 0 0 18px;
    border-left: 2px solid rgba(201, 162, 75, 0.55);
    color: rgba(247, 242, 234, 0.80);
    font-size: 14.5px;
    opacity: 1;
  }

  /* --- the CTA, which was display:none on mobile until now ---
     .btn-gold is an outline button built for the light lilac header:
     transparent background, plum border, plum text. Dropped straight onto the
     plum sheet it renders plum-on-plum and disappears — correctly positioned
     and on top, just invisible. The codebase already solves this elsewhere by
     flipping to amber on plum grounds (.section--plum .btn-gold), so this
     follows that pairing, filled rather than outlined because it is the only
     conversion action on the screen. */
  .nav.menu-open .nav__cta {
    display: inline-flex;
    position: fixed;
    left: 24px; right: 24px;
    bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    z-index: 100;
    justify-content: center;
    background: var(--amber);
    border-color: var(--amber);
    color: var(--plum);
  }
  .nav.menu-open .nav__cta:hover,
  .nav.menu-open .nav__cta:active {
    background: var(--ivory);
    border-color: var(--ivory);
    color: var(--plum);
  }
}

/* ============================================================
   CARD SURFACE — matched to the contact form, in lilac.

   The cards had drifted into four different treatments: .journal-card and
   .testimonial-card on translucent white with a violet hairline, .retreat-card
   and .press-card on flat ivory, .hub-card on white with a gold one. This
   gives them all the surface the contact form already uses — the same glassy
   lilac gradient, gold hairline and inset top highlight — so the card language
   is one thing across the site.

   Geometry is left alone on purpose: each component keeps its own radius,
   padding and hover transform. Only the surface changes.

   At the end of the stylesheet so it wins on source order over the per-card
   rules above, per the specificity lesson recorded on this project.
   ============================================================ */
.journal-card,
.retreat-card,
.testimonial-card,
.hub-card,
.press-card {
  background:
    linear-gradient(135deg,
      rgba(255, 255, 255, 0.62) 0%,
      rgba(255, 255, 255, 0.18) 30%,
      rgba(255, 255, 255, 0) 58%),
    linear-gradient(160deg, #E9DEF1 0%, #F3ECF7 52%, #ECE1F2 100%);
  border: 1px solid rgba(201, 162, 75, 0.38);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75);
}

/* Hover keeps the inset highlight and adds the contact card's lift, rather
   than replacing the shorthand and losing the top edge. */
.journal-card:hover,
.retreat-card:hover,
a.press-card:hover,
.hub-card:hover {
  background:
    linear-gradient(135deg,
      rgba(255, 255, 255, 0.72) 0%,
      rgba(255, 255, 255, 0.24) 30%,
      rgba(255, 255, 255, 0) 58%),
    linear-gradient(160deg, #E9DEF1 0%, #F3ECF7 52%, #ECE1F2 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    0 22px 48px -20px rgba(58, 38, 80, 0.42),
    0 2px 8px rgba(58, 38, 80, 0.08);
}

/* --- "My Work" service cards, boxed like the rest ---------------------------
   These four were columns rather than cards: transparent, sitting straight on
   the lavender band, separated only by a single gold hairline on the left
   edge. They now carry the same surface, border and lift as every other card.

   Two things this had to account for:
   - .offerings-grid had no gap, because the hairline was the separation. Boxed
     panes with no gap would touch, so the grid needs one.
   - .offering-card:first-child { border-left: none } is (0,2,0) and beats a
     bare .offering-card at (0,1,0) whatever the source order, so the first
     card's left edge has to be restored at matching specificity or it renders
     with three sides. Counting selectors first, per this project's history. */
.offerings-grid { gap: var(--space-sm); }
.offering-card {
  border: 1px solid rgba(201, 162, 75, 0.38);
  border-radius: var(--radius-md);
  background:
    linear-gradient(135deg,
      rgba(255, 255, 255, 0.62) 0%,
      rgba(255, 255, 255, 0.18) 30%,
      rgba(255, 255, 255, 0) 58%),
    linear-gradient(160deg, #E9DEF1 0%, #F3ECF7 52%, #ECE1F2 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75);
}
.offering-card:first-child { border-left: 1px solid rgba(201, 162, 75, 0.38); }
.offering-card:hover,
.offering-card:focus-visible {
  background:
    linear-gradient(135deg,
      rgba(255, 255, 255, 0.72) 0%,
      rgba(255, 255, 255, 0.24) 30%,
      rgba(255, 255, 255, 0) 58%),
    linear-gradient(160deg, #E9DEF1 0%, #F3ECF7 52%, #ECE1F2 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    0 22px 48px -20px rgba(58, 38, 80, 0.42),
    0 2px 8px rgba(58, 38, 80, 0.08);
}

/* --- Staggered reveal inside grids -----------------------------------------
   A grid was one reveal target, so a row of cards faded up as a single flat
   rectangle. The animation moves onto the children instead, cascading, so they
   arrive in sequence. main.js adds .reveal-stagger to grids that are already
   reveal targets, so nothing new is ever hidden by this.

   The delay is capped from the sixth child on: journal.html has 69 cards in one
   grid, and an uncapped cascade would take five seconds to finish. */
[data-reveal].reveal-stagger { opacity: 1; transform: none; }
.reveal-stagger > * {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.reveal-stagger.is-visible > * { opacity: 1; transform: none; }
.reveal-stagger.is-visible > *:nth-child(1) { transition-delay: 0s; }
.reveal-stagger.is-visible > *:nth-child(2) { transition-delay: 0.07s; }
.reveal-stagger.is-visible > *:nth-child(3) { transition-delay: 0.14s; }
.reveal-stagger.is-visible > *:nth-child(4) { transition-delay: 0.21s; }
.reveal-stagger.is-visible > *:nth-child(5) { transition-delay: 0.28s; }
.reveal-stagger.is-visible > *:nth-child(n+6) { transition-delay: 0.35s; }

/* The existing reduced-motion guard only unsets [data-reveal] itself, which no
   longer animates on a stagger grid — the children do, so they need it too. */
@media (prefers-reduced-motion: reduce) {
  .reveal-stagger > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}
