/* ==========================================================================
   MAYFIELD V2 — COMPONENTS
   Each block is tagged with the Figma node it came from.
   Mobile-first; the desktop overrides live at the end of each block.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Nav + menu — one growing green box    [figma 214:2280 / 214:2440 / Loom]
   The bar IS the menu. Its height runs from --nav-h to the full viewport on
   --menu-p, and every other moving part is derived from that same value, so
   nothing can outrun anything else.
   -------------------------------------------------------------------------- */
.nav {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 60;
  height: calc(var(--nav-h) + (100svh - var(--nav-h)) * var(--menu-p));
  overflow: hidden;
  background: var(--c-accent);
  color: var(--c-ink);
  /* Held out of the way until the hero has been scrolled past; motion.js adds
     .is-live. Without JS it simply shows, which is the safe default. */
  transform: translateY(-100%);
  opacity: 0;
  transition: transform 0.5s var(--ease-menu), opacity 0.5s var(--ease);
}
.nav.is-live { transform: none; opacity: 1; }
.no-js .nav { transform: none; opacity: 1; }

/* The bar's own row stays pinned to the top as the box grows. */
.nav__bar {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  /* Above the links and the wordmark. Both of those span the whole box, so
     without this the links container sat on top of the toggle and swallowed
     every click on CLOSE. */
  z-index: 3;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--s-3);
  color: var(--c-ink);
}
/* Closed, the bar is black on the acid box. Open, it sits over the dark video
   panels, so the whole row — temp, wordmark, Close — turns white. */
.menu-open .nav__bar,
.menu-open .nav__mark { color: var(--c-on-media); }
.nav__temp { display: flex; align-items: center; gap: var(--s-2); font-size: var(--t-ui); text-transform: uppercase; }
.nav__temp img { width: 20px; height: 20px; }

.nav__toggle { cursor: pointer; }
/* The label swaps on the same state, so there is only ever one control. */
.nav__toggle-close { display: none; }
.menu-open .nav__toggle-open { display: none; }
.menu-open .nav__toggle-close { display: inline; }

/* The small wordmark, centred IN the bar. [figma 465:8227] The earlier design
   grew a big wordmark out of the bar as the menu opened; the new menu is four
   video panels instead, so the mark simply stays put at bar size and the panels
   do the opening. It follows the bar's colour — black on the acid bar, white
   once the panels are behind it. */
.nav__mark {
  position: absolute;
  z-index: 3;
  inset-block-start: 0;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  height: var(--nav-h);
  width: 92px;
  display: flex;
  align-items: center;
  color: currentColor;
}
.nav__mark svg { display: block; width: 100%; height: auto; }

/* --------------------------------------------------------------------------
   The menu — four full-height video panels    [figma 465:7814 / 496:17251]
   HOME · PARK · WORK · VISIT. Each is a link over looping footage with the
   section word centred and an acid bar that fills on hover/at the current page.
   Stacked on mobile, four columns up. Revealed on the same .menu-open the box
   grows on, a beat after the box starts opening so the panels arrive into a
   space that is already there rather than being squashed.
   -------------------------------------------------------------------------- */
.nav__menu {
  position: absolute;
  inset: 0;
  z-index: 1;
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.3s var(--ease), visibility 0s linear 0.3s;
}
.menu-open .nav__menu {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.45s var(--ease) 0.2s, visibility 0s;
}
.nav__menu > li { flex: 1 1 0; min-height: 0; display: flex; }

.mpanel {
  position: relative;
  flex: 1 1 auto;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  isolation: isolate;
  color: var(--c-on-media);
  text-align: center;
}
.mpanel__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* A wash so the word reads on any frame of any panel's footage. */
.mpanel::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgb(0 0 0 / 0.28);
  transition: background 0.3s var(--ease);
}
.mpanel:hover::before,
.mpanel:focus-visible::before { background: rgb(0 0 0 / 0.12); }

.mpanel__label {
  font-weight: var(--w-xbold);
  font-size: var(--t-title);
  line-height: var(--lh-flat);
  letter-spacing: var(--ls-tight);
  text-transform: uppercase;
}

/* [figma 465:5137] the acid underline: present on the current page, and drawn
   in on hover/focus for the others. */
.mpanel::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  height: 8px;
  background: var(--c-accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.32s var(--ease);
}
.mpanel:hover::after,
.mpanel:focus-visible::after,
.mpanel[aria-current="page"]::after { transform: scaleX(1); }

@media (min-width: 1024px) {
  .nav__mark { width: 104px; }
  /* Four columns across the full viewport. */
  .nav__menu { flex-direction: row; }
}

@media (prefers-reduced-motion: reduce) {
  .mpanel::before, .mpanel::after { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  :root { transition: none; }
  .nav { transition: none; }
}

/* --------------------------------------------------------------------------
   2. Initial nav — the landing bar                       [figma 214:2433]
   48px tall, but only its top 12px carry the acid green; the rest is
   transparent so the hero video runs underneath. SCROLL / MENU sit in white
   ON the video, which is why they are in the lower 36px, not the green.
   -------------------------------------------------------------------------- */
.initial-nav {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 40;
  height: var(--nav-init-h);
  color: var(--c-on-media);
}

.initial-nav::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: var(--nav-band);
  background: var(--c-accent);
}

.initial-nav__row {
  position: absolute;
  inset-block-start: var(--nav-band);
  inset-inline: 0;
  height: calc(var(--nav-init-h) - var(--nav-band));
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--s-3);
}

/* --------------------------------------------------------------------------
   3. Hero                                          [figma 261:8642 / 281:15868]
   Full-bleed video from y=12 (under the green strip). The MAYFIELD wordmark
   is a composed SVG — in Figma it is eight separate vector letterforms, so
   there is no font to match and it stays razor sharp at any width.
   -------------------------------------------------------------------------- */
/* The runway is the scroll distance the transition is spent over; the hero
   sticks inside it so the video stays put while the page scrolls past. */
.runway { position: relative; z-index: 20; height: 200svh; }

.hero {
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
  height: 100svh;
  min-height: 560px;
  padding-block-start: var(--nav-band);
  /* Paper, not ink: as the scroll-jacked video shrinks into the centre window
     the hero fills the viewport, so its background is what shows AROUND the
     shrinking panel. That reveal should be the page's paper (white by day, navy
     at night — same as <body>), not a black plate. */
  background: var(--c-paper);
  isolation: isolate;
}

.hero__media {
  position: absolute;
  inset-block-start: var(--nav-band);
  inset-inline-start: 0;
  width: 100%;
  height: calc(100% - var(--nav-band));
  z-index: 0;
  overflow: hidden;
}
.hero__media video,
.hero__media img { width: 100%; height: 100%; object-fit: cover; }

.hero__inner {
  position: relative;
  z-index: 2;
  height: 100%;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--s-9);
  padding-inline: var(--s-3);
}

/* [figma 373:26289] 755 of the 1280 frame — 59vw, capped so it never crowds
   the edges on a wide display. */
.hero__mark { width: min(59vw, 940px); color: var(--c-on-media); transform-origin: 50% 50%; }
.hero__mark svg { width: 100%; height: auto; }

/* [figma 261:8645] the time/weather row sits UNDER the wordmark, spanning the
   same measure as the video's inner column — not the full page width. */
.hero__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: min(100%, 772px);
  color: var(--c-on-media);
  font-size: var(--t-ui);
  text-transform: uppercase;
}
.hero__meta .temp { display: flex; align-items: center; gap: var(--s-2); }
.hero__meta img { width: 20px; height: 20px; }

/* --------------------------------------------------------------------------
   4. The window                       [figma 261:8672 / 281:15889, Loom 02:05]
   The signature interaction. A FIXED, centred video panel that the collage
   scrolls over — "a window into the park". It is not sticky: sticky would
   need a scrolling ancestor to stick within, and this has to hold dead centre
   of the viewport across the entire page.

   It sits ABOVE every content row — the collage plates, the white benefit
   panels, the Instagram bed and the footer all pass beneath it — and below
   only the nav, so the bar and the menu still open over it. It is
   pointer-events: none, so nothing underneath becomes unclickable.
   -------------------------------------------------------------------------- */
.window {
  position: fixed;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  /* Above the content rows (20), below the nav (60). */
  z-index: 50;
  width: var(--win-w);
  height: var(--win-h);
  overflow: hidden;
  background: var(--c-ink);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.window.is-live { opacity: 1; }

.window video,
.window img { width: 100%; height: 100%; object-fit: cover; }

/* The word that crosses the window as each section passes. */
.window__word {
  position: absolute;
  inset-block-start: 50%;
  inset-inline: 0;
  translate: 0 -50%;
  overflow: hidden;
  height: 50px;
  display: flex;
  align-items: center;
  color: var(--c-on-media);
  font-size: var(--t-panel);
}

/* [figma 214:2748] three copies, gap 20/30px, translated -50% for a seamless
   loop. The track is duplicated in the markup so the reset is invisible. */
.window__track {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  padding-inline-end: var(--s-6);
  animation: window-scroll var(--dur-marquee) linear infinite;
  white-space: nowrap;
}
@keyframes window-scroll { to { transform: translateX(-50%); } }

/* With JS on, motion.js §2 drives the track as a scroll-jacked transform, so
   the constant animation above is the no-JS fallback only. */
.js .window__track { animation: none; }

/* The intro state — the word the hero's wordmark became. It holds still and
   centred; only the section words travel. */
.window__word.is-intro .window__track {
  animation: none;
  justify-content: center;
  width: 100%;
  padding-inline-end: 0;
}
.window__word.is-intro .window__track span:not(:first-child) { display: none; }

@media (min-width: 1024px) {
  .window__word { height: 60px; }
  .window__track { gap: var(--s-10); padding-inline-end: var(--s-10); }
}

/* --------------------------------------------------------------------------
   5. Collage                       [figma 261:8669-8671 / 281:15893, Loom 02:05]
   Full-bleed imagery that the window cuts through. Each plate is its own
   viewport-height band so the window always has something behind it.
   -------------------------------------------------------------------------- */
.plate {
  position: relative;
  z-index: 10;          /* under everything; the collage is the backdrop */
  height: 100svh;
  min-height: 480px;
  overflow: hidden;
}
.plate img { width: 100%; height: 100%; object-fit: cover; }

/* --------------------------------------------------------------------------
   6. Benefit                                 [figma 214:2630 / 281:15831]
   Mobile stacks headline over copy+button across a 667px band. Desktop puts
   them side by side with the headline hugging the top and the copy the
   bottom — the gap between the columns is the design's 420px.
   -------------------------------------------------------------------------- */
.benefit {
  position: relative;
  z-index: 20;          /* over the plates, under the window */
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--s-9);
  padding: var(--s-11) var(--gutter);
  background: var(--c-paper);
}

.benefit__body { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-6); }

@media (min-width: 1024px) {
  /* The Figma component is 480px tall, but it does NOT sit flush against the
     plates above and below it: img1 ends at 1640 and the benefit starts at
     1840, and the benefit ends at 2320 with img2 starting at 2520. That is
     200px of white on each side, and it is most of what gives this section
     its air. Building the 480px row alone made the copy feel cramped against
     the imagery. */
  .benefit {
    flex-direction: row;
    align-items: stretch;
    gap: clamp(60px, 33vw, 420px);
    padding-block: 200px;
    min-height: calc(480px + 400px);
  }
  .benefit .t-title { flex: 1 0 0; min-width: 0; }
  .benefit__body { flex: 1 0 0; min-width: 0; justify-content: flex-end; }
}

/* --------------------------------------------------------------------------
   7. Button                                              [figma 214:2637]
   A 2px black stroke, fully rounded, never filled. Inverts on hover.
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-4) var(--s-8) 13px;
  border: var(--border-btn) solid currentColor;
  border-radius: var(--radius-btn);
  font-size: var(--t-caps);
  line-height: var(--lh-flat);
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
/* Explicit pair, never derived from currentColor — deriving a hover
   background from the element's own colour is how a label goes invisible. */
.btn:hover,
.btn:focus-visible { background: var(--c-ink); color: var(--c-accent); }

.btn--block { display: flex; width: 100%; }

/* Brochure / download button — the design-system variant. [figma 465:7873]
   A rectangle (4px), not a pill: 2px black stroke on the Republic yellow, the
   label at left and a divided 52px download cell flush to the right edge. The
   divider + arrow live in download.svg, painted as a mask so they invert with
   the label on hover. */
.btn--brochure {
  gap: var(--s-8);
  padding: 0 0 0 20px;
  min-height: 52px;
  border-radius: 4px;
  background: var(--c-republic);
  color: var(--c-ink);
  overflow: hidden;
}
/* No colour change on hover — only the arrow animates. This override is needed
   because the generic .btn:hover inverts. */
.btn--brochure:hover,
.btn--brochure:focus-visible { background: var(--c-republic); color: var(--c-ink); }

/* The download cell: a static divider on the left, and a clipped column holding
   two identical arrows. On hover the column slides up one cell so the arrow
   shoots up and out while its duplicate rises into place. */
.btn__dl {
  align-self: stretch;
  flex: 0 0 auto;
  width: 52px;
  margin-inline-start: auto;
  border-inline-start: 2px solid currentColor;
  overflow: hidden;
  position: relative;
}
.btn__dl-track {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 200%;                    /* two arrow cells tall */
  display: grid;
  grid-template-rows: 1fr 1fr;     /* each row = one cell height */
  transition: transform 0.42s cubic-bezier(0.5, 0, 0.2, 1);
}
.btn--brochure:hover .btn__dl-track,
.btn--brochure:focus-visible .btn__dl-track { transform: translateY(-50%); }
.btn__arrow {
  background-color: currentColor;
  -webkit-mask: url("../assets/republic/download-arrow.svg") center / 54px 52px no-repeat;
          mask: url("../assets/republic/download-arrow.svg") center / 54px 52px no-repeat;
}
@media (prefers-reduced-motion: reduce) { .btn__dl-track { transition: none; } }

/* --------------------------------------------------------------------------
   8. Instagram                                    [figma 230:3831 / 281:15356]
   Acid bed. Mobile scrolls the tiles horizontally at a fixed 160px; desktop
   fits six equal tiles across the full width.
   -------------------------------------------------------------------------- */
.insta {
  position: relative;
  /* Above the window (50), with the footer. These two are the page's closing
     blocks — the scroll narrative through the three sections is over by the
     time you reach them, so they take the screen back rather than having the
     panel float across the grid and the sign-off. */
  z-index: 55;
  display: flex;
  flex-direction: column;
  gap: var(--s-9);
  padding-block: var(--s-9);
  background: var(--c-accent);
  color: var(--c-ink);
}

.insta__text { display: flex; flex-direction: column; gap: var(--s-6); padding-inline: var(--gutter); }
.insta__head { display: flex; flex-direction: column; gap: var(--s-4); text-transform: uppercase; }
.insta__lens { font-weight: var(--w-xbold); font-size: var(--t-lens); line-height: var(--lh-flat); letter-spacing: var(--ls-title); }

.insta__grid {
  display: flex;
  gap: var(--s-1);
  padding-inline: var(--gutter);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* The scrollport ignores padding-inline when snapping, so the last tile
     would sit flush to the edge without this. */
  scroll-padding-inline: var(--gutter);
  scrollbar-width: none;
}
.insta__grid::-webkit-scrollbar { display: none; }

.insta__grid img {
  flex: 0 0 160px;
  width: 160px;
  height: 199px;
  object-fit: cover;
  scroll-snap-align: start;
}

.insta__cta { padding-inline: var(--gutter); }

@media (min-width: 1024px) {
  .insta { padding: var(--s-9); gap: var(--s-9); }
  .insta__text {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    padding-inline: 0;
  }
  .insta__head { flex: 1 0 0; min-width: 0; }
  .insta__blurb { width: 354px; font-size: var(--t-caps); }
  .insta__grid { padding-inline: 0; overflow: visible; }
  .insta__grid img { flex: 1 1 0; width: auto; height: 248px; min-width: 0; }
  .insta__cta { padding-inline: 0; }
}

/* --------------------------------------------------------------------------
   9. Footer                                      [figma 261:8674 / 281:15918]
   Mobile rotates the ©Mayfield 2026 slab to run up the right-hand edge with
   the link columns beside it; desktop lays it flat across the top.
   -------------------------------------------------------------------------- */
/* Above the window (50), with the Instagram bed. The video passes over the
   collage plates and the benefit copy; these last two blocks close the page
   and the panel slides away behind them. */
.foot { position: relative; z-index: 55; background: var(--c-paper); overflow: hidden; }

.foot__top { position: relative; display: flex; gap: var(--s-6); padding: var(--s-9) var(--s-5) var(--s-11); }

.foot__links {
  display: flex;
  flex-direction: column;
  gap: var(--s-9);
  flex: 1 1 auto;
}
.foot__col { display: flex; flex-direction: column; gap: var(--s-6); }

/* [figma 281:15784] Two lines, each rotated a quarter turn and reading
   bottom-up, sitting side by side with the big one outermost. Authored in a
   row so the rotation does not have to reorder them. */
.foot__stamp {
  flex: 0 0 auto;
  display: flex;
  flex-direction: row;
  /* Both lines start from the same bottom edge, so the © and the strapline's
     first letter sit on one line and the pair reads as a single block. */
  align-items: flex-end;
  gap: var(--s-2);
  font-weight: var(--w-xbold);
  line-height: var(--lh-crush);
  letter-spacing: var(--ls-title);
  text-transform: uppercase;
}
.foot__stamp span {
  writing-mode: vertical-rl;
  /* `mixed` is the default and it rotates Latin letters while leaving
     non-letters upright — so MAYFIELD 2026 turned but the © stayed standing,
     sitting off-axis at the foot of the line. `sideways` turns everything
     together, which is what makes it read as one set line. */
  text-orientation: sideways;
  rotate: 180deg;
  white-space: nowrap;
}
.foot__stamp-a { font-size: var(--t-stamp); }
.foot__stamp-b { font-size: calc(var(--t-stamp) * 0.46); }
/* Rotated on mobile, so it reads as one line up the side. */
.foot__stamp-b br { display: none; }

/* The acid bar: partner logos over the legal line on mobile, side by side up. */
.foot__base {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-7);
  padding: var(--s-5) var(--s-3);
  background: var(--c-accent);
}
.foot__logos { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 14px var(--s-6); }
.foot__logos img { height: auto; }
.foot__legal { display: flex; justify-content: space-between; gap: var(--s-6); width: 100%; }

/* Mobile only. The links read first and the stamp sits to their right; DOM
   order keeps the stamp first for a screen reader. Scoped to this breakpoint
   because .foot__top is a COLUMN up, where an order swap would drop the stamp
   underneath the links instead of beside them. */
@media (max-width: 1023px) {
  .foot__stamp { order: 2; }
}
.foot__legal a { text-decoration: underline; }

@media (min-width: 1024px) {
  .foot__top { padding: 64px var(--s-8) var(--s-11); flex-direction: column; gap: var(--s-11); }
  /* [figma 261:8674] flat across the top up here, the strapline set beside it.

     Figma's 100px only fits its own 1280 artboard: the pair measures ~1190px,
     so anywhere narrower the strapline ran past the edge and was silently
     clipped by the footer's overflow. Fluid between 1024 and 1280, with the
     strapline held at Figma's 0.3 ratio to it. */
  .foot__stamp { align-items: baseline; gap: var(--s-6); flex-wrap: wrap; }
  .foot__stamp span { writing-mode: horizontal-tb; rotate: none; }
  /* [figma 261:8674] two lines, and the break is explicit rather than left to
     whatever the measure happens to allow — relying on wrapping meant it only
     broke once it had already run past the edge. */
  .foot__stamp-b {
    font-size: calc(var(--t-stamp) * 0.3);
    white-space: normal;
  }
  .foot__stamp-b br { display: inline; }
  .foot__links { flex-direction: row; gap: var(--s-6); }
  .foot__col { flex: 1 0 0; min-width: 0; }
  .foot__base { flex-direction: row-reverse; justify-content: space-between; padding: var(--s-5) var(--s-8) var(--s-5) var(--s-6); }
  .foot__legal { width: auto; flex-direction: column; gap: 0; }
}

/* --------------------------------------------------------------------------
   11. Hero -> window, on the scroll timeline
   "as the user scrolls from this video background we scale this down... the
   user will be in control of this track."

   Driven by `animation-timeline: scroll()` rather than a scroll listener.
   Three reasons: it runs off the main thread so the shrink cannot stutter
   behind other work; it is genuinely position-driven, so scrubbing backwards
   is exact rather than reconstructed; and there is no listener to leak.
   motion.js carries an equivalent JS path for browsers without it.
   -------------------------------------------------------------------------- */
@supports (animation-timeline: scroll()) {
  .js .hero__media,
  .js .hero__mark,
  .js .hero,
  .js .window,
  .js .window__word,
  .js .nav {
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: scroll(root block);
  }

  /* The shrink occupies the first viewport of the runway. */
  .js .hero__media {
    animation-name: hero-shrink;
    animation-range: 0 100svh;
  }
  @keyframes hero-shrink {
    from {
      width: 100%;
      height: calc(100% - var(--nav-band));
      left: 0;
      top: var(--nav-band);
    }
    to {
      width: var(--win-w);
      height: var(--win-h);
      left: calc(50% - (var(--win-w) / 2));
      top: calc(50% - (var(--win-h) / 2));
    }
  }

  /* 0.26 is the panel wordmark measured against the hero wordmark in Figma
     (44/... on mobile, 60/... up) — the word travels down with the video. */
  .js .hero__mark { animation-name: mark-shrink; animation-range: 0 100svh; }
  @keyframes mark-shrink { from { scale: 1; } to { scale: 0.26; } }

  /* The handover. The animated hero (video + its SVG wordmark) fades out as the
     fixed window video fades in, over the last 8% of the runway. */
  .js .hero   { animation-name: hero-out;   animation-range: 92svh 100svh; }
  .js .window { animation-name: window-in;  animation-range: 92svh 100svh; }

  /* The window's INTRO word waits until the hero has fully gone, then resolves
     in on its own. The hero's SVG wordmark and this t-slab word are different
     renderings of MAYFIELD, so crossfading them over each other ghosted into a
     doubled, offset wordmark. Instead the hero mark carries the word down
     through the shrink, leaves at 100svh, and this takes over just after — one
     wordmark on screen at a time. (Section words are exempt: once JS drops
     `.is-intro` this rule stops matching and the scroll-jacked marquee owns it.) */
  .js .window__word.is-intro { animation-name: introword-in; animation-range: 100svh 103svh; }
  @keyframes introword-in { from { opacity: 0; } to { opacity: 1; } }

  /* The bar gets a much longer runway than the handover itself — 55svh rather
     than the last 8% — and an ease-out curve so it decelerates into place.
     Over 8svh it was effectively instant: scroll-linked means a flick of the
     wheel covered the whole range in one frame, which is what made it snap.
     It also fades rather than only sliding, so a solid block never arrives
     with a hard edge. */
  .js .nav {
    animation-name: nav-in;
    animation-range: 45svh 100svh;
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  }

  @keyframes hero-out   { from { opacity: 1; } to { opacity: 0; } }
  @keyframes window-in  { from { opacity: 0; } to { opacity: 1; } }
  @keyframes nav-in {
    from { transform: translateY(-100%); opacity: 0; }
    to   { transform: translateY(0);     opacity: 1; }
  }
}

@media (prefers-reduced-motion: reduce) {
  /* No scrub. The window is simply present and the hero simply leaves. */
  .js .hero__media, .js .hero__mark, .js .hero, .js .window, .js .nav {
    animation: none;
  }
  .window { opacity: 1; }
  .nav { transform: none; }
}

/* --------------------------------------------------------------------------
   12. Link hover — the directional rule
   Carried over from V1 in behaviour, but hand-built: V2 has no GSAP, and this
   is ~40 lines of CSS against ~78KB of library for the same result.

   The rule enters from whichever side the pointer crossed and leaves by the
   opposite one, so it reads as passing THROUGH the word rather than rewinding.
   That is the whole effect — the type itself does not move.
   -------------------------------------------------------------------------- */

/* `width: fit-content` is as load-bearing as `position: relative`. These links
   are block-level inside the menu and footer columns, so their box is the full
   column width while the word is much shorter — and the rule, being
   left:0/right:0, would stretch the whole column and fire when you hovered the
   empty space beside a short word. */
.js-underline .u-link {
  position: relative;
  width: fit-content;
  text-decoration: none;
}

.u {
  position: absolute;
  inset-inline: 0;
  inset-block-end: -0.18em;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: var(--u-from, left) center;
  transition: transform 0.34s var(--ease);
  pointer-events: none;
}
.u-link.is-on .u { transform: scaleX(1); }

.u-link .lbl { display: inline-block; }

@media (prefers-reduced-motion: reduce) {
  .u { transition: none; }
}


/* Where a condition has no glyph drawn for it, the readout says the word
   instead of borrowing another condition's icon. Same size and case as the
   temperature it sits beside. */
.wx-word { text-transform: uppercase; white-space: nowrap; }

/* The weather glyph is painted as a MASK, not loaded as an <img>. An external
   SVG in an <img> cannot inherit currentColor — the same trap the wordmark
   fell into — and these have to be black on the accent in the nav and white
   over video in the hero. As a mask, one asset serves both and follows the
   text colour it sits beside. */
.wx-icon {
  display: block;
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask: var(--wx, url("../assets/icons/weather-cloudy.svg")) center / contain no-repeat;
  mask: var(--wx, url("../assets/icons/weather-cloudy.svg")) center / contain no-repeat;
}

/* --------------------------------------------------------------------------
   The weather badge waits for its reading
   The markup used to ship 19° and a default cloud, which then corrected
   itself the moment the API answered — a visible flicker, and for a beat the
   page stated a temperature nobody had checked. Both readouts are held at
   zero until the call resolves, then the time and the weather arrive
   together.

   Gated on .js so that without JavaScript nothing is hidden that will never
   be revealed.
   -------------------------------------------------------------------------- */
.js .hero__meta,
.js .nav__temp {
  opacity: 0;
  transition: opacity 0.45s var(--ease);
}
.js.wx-ready .hero__meta,
.js.wx-ready .nav__temp { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .js .hero__meta, .js .nav__temp { transition: none; }
}

/* --------------------------------------------------------------------------
   13. The power of Mayfield
   An acid bed carrying curated press quotes under a live Google rating.
   Everything is centred; the quote deck cross-fades in place so the panel
   never changes height as you page through it.
   -------------------------------------------------------------------------- */
.power {
  position: relative;
  /* With the Instagram bed and the footer — the video window passes behind
     the closing blocks, not across them. */
  z-index: 55;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-9);
  padding: var(--s-11) var(--gutter);
  background: var(--c-accent);
  color: var(--c-ink);
  text-align: center;
}

.power__rating {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  /* Held back until the score is known, like the weather badge — a placeholder
     rating would be a claim about the park nobody had checked. */
  opacity: 0;
  transition: opacity 0.45s var(--ease);
}
.js .power.is-rated .power__rating,
.no-js .power__rating { opacity: 1; }

.power__google { font-size: 26px; font-weight: var(--w-medium); letter-spacing: -0.02em; }
.power__score  { font-size: 20px; font-weight: var(--w-medium); }
/* The span carries the size, not the SVG. base.css sets `svg { max-width:
   100% }`, and 100% of a parent whose own width depends on the SVG resolves
   to zero — the stars painted but the box measured 0 wide. */
.power__stars { flex: 0 0 auto; width: 108px; height: 20px; }
.power__stars svg { width: 100%; height: 100%; max-width: none; display: block; }

.power__title { max-width: 18ch; }

/* The deck is a grid with every quote in the same cell, so the tallest sets
   the height once and paging cannot make the panel jump. */
.power__deck { display: grid; width: min(100%, 940px); }
.power__quote {
  grid-area: 1 / 1;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-7);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.35s var(--ease), visibility 0s linear 0.35s;
}
.power__quote.is-on { opacity: 1; visibility: visible; transition: opacity 0.35s var(--ease), visibility 0s; }

.power__quote blockquote { margin: 0; font-size: var(--t-body); line-height: var(--lh-body); }
.power__quote figcaption { font-weight: var(--w-xbold); font-size: var(--t-body); }

.power__nav { display: flex; align-items: center; gap: var(--s-6); }
.power__nav button { cursor: pointer; }
.power__count { min-width: 4ch; }

@media (min-width: 1024px) {
  .power { gap: var(--s-10); padding-block: 120px; }
  .power__title { font-size: 76px; max-width: none; }
  .power__quote blockquote { font-size: 28px; }
  .power__quote figcaption { font-size: 22px; }
  .power__google { font-size: 34px; }
  .power__score { font-size: 26px; }
  .power__stars { width: 130px; height: 24px; }
}

@media (prefers-reduced-motion: reduce) {
  .power__rating, .power__quote { transition: none; }
}

/* --------------------------------------------------------------------------
   14. Interior pages — the bar is present from the top   [figma 465:8206]
   The homepage nav arrives on the hero -> window handover; every other page
   has no hero to scroll past, so the 36px bar is simply there from the first
   frame. `nav--static` opts the bar out of the scroll-timeline animation the
   homepage drives it with, and `.page` drops the first band below the fixed
   bar so nothing hides beneath it. The menu still grows from this bar exactly
   as it does on the homepage — only the arrival is different.
   -------------------------------------------------------------------------- */
.nav--static { transform: none; opacity: 1; }
/* Beats the @supports `animation-name: nav-in` on `.js .nav` (equal
   specificity, later in source) so the bar is not animated from off-screen. */
.js .nav--static { animation: none; }

.page {
  position: relative;
  padding-block-start: var(--nav-h);
  background: var(--c-paper);
}

/* --------------------------------------------------------------------------
   15. Page head — the acid title band                    [figma 465:8192]
   A full-bleed accent band opening an interior page: a Title over a line of
   body copy, both centred. Inherits the weather/night colour like everything
   else, so it is orange on a sunny day and navy at night without a second rule.
   -------------------------------------------------------------------------- */
.page-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-6);
  padding: var(--s-10) var(--gutter) var(--s-11);
  background: var(--c-accent);
  color: var(--c-ink);
  text-align: center;
}
.page-head__sub { width: min(100%, 600px); }

@media (min-width: 1024px) {
  /* [figma] 80 over the title, 120 under the copy, 40 between. */
  .page-head { gap: var(--s-9); padding: var(--s-11) var(--gutter) 120px; }
}

/* --------------------------------------------------------------------------
   16. Form + field                             [figma 465:8182 / 255:4767]
   A field is a grey rounded box holding a label over its control. The control
   is a real <input>/<select>/<textarea> — the design is progressively
   enhanced, so the form posts and validates natively with scripting off, and
   the JS below only upgrades the submit to stay on the page.

   Dropdowns carry a white chevron well on the right; text fields do not. The
   whole box is a <label>, so a tap anywhere lands on its control.
   -------------------------------------------------------------------------- */
.contact-form {
  display: flex;
  justify-content: center;
  padding: var(--s-11) var(--gutter);
  background: var(--c-paper);
}

.form { width: min(100%, 600px); display: flex; flex-direction: column; gap: var(--s-9); }
.form__intro { text-align: center; }
/* [figma 465:8185] the block of fields tightens to 8px between boxes, inside
   the 40px rhythm of the form as a whole. 8 is off the spacing scale by
   design — this is the one place the fields read as a single stacked group. */
.form__fields { display: flex; flex-direction: column; gap: 8px; }

.field {
  display: flex;
  gap: var(--s-4);
  align-items: stretch;
  padding: var(--s-6);
  background: var(--c-field);
  border-radius: var(--radius-field);
}
.field__main { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: var(--s-4); }
.field__label { color: var(--c-ink); }

/* Transparent control that inherits the box — no border or fill of its own, so
   the grey box IS the input. Native appearance is stripped from the select
   because the chevron well stands in for its arrow. */
.field__control {
  width: 100%;
  font: inherit;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-ink);
  background: none;
  border: 0;
  padding: 0;
  appearance: none;
  border-radius: 0;
}
.field__control:focus { outline: none; }
.field:focus-within { outline: 2px solid var(--c-ink); outline-offset: 2px; }
/* "Type here" is the placeholder, greyed against the field's own text. */
.field__control::placeholder { color: var(--c-ink); opacity: 0.45; }
select.field__control { cursor: pointer; }

.field--area { align-items: flex-start; }
/* [figma 465:8190] the message box is 270 tall including its 20px padding. */
.field--area .field__control { min-height: 230px; resize: vertical; }

/* [figma 255:4790] a 52px white well with the chevron, full field height. */
.field__arrow {
  flex: 0 0 auto;
  align-self: stretch;
  display: grid;
  place-items: center;
  width: 52px;
  background: var(--c-paper);
  border-radius: var(--radius-arrow);
  pointer-events: none;
}
.field__arrow::before {
  content: "";
  width: 20px;
  height: 10px;
  /* Painted as a mask so it follows --c-ink into night mode, the same trick
     the weather glyph uses — the exported chevron is a black stroke otherwise. */
  background: var(--c-ink);
  -webkit-mask: url("../assets/icons/chevron-down.svg") center / contain no-repeat;
          mask: url("../assets/icons/chevron-down.svg") center / contain no-repeat;
}

/* The enquiry addresses under the form. [figma 465:8196] */
.enquiries {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-9);
  padding: var(--s-10) var(--gutter) var(--s-11);
  background: var(--c-paper);
  color: var(--c-ink);
  text-align: center;
}
.enquiry { display: flex; flex-direction: column; gap: var(--s-4); }
/* [figma Title 32] the design sets these in Bold 700; the project ships only
   Medium and ExtraBold (tokens.css), and every headline is ExtraBold, so these
   follow that rule rather than shipping a third weight for two lines. */
.enquiry__type {
  font-weight: var(--w-xbold);
  font-size: 26px;
  line-height: var(--lh-flat);
  letter-spacing: -0.03em;
  text-transform: uppercase;
}

/* The submit's own feedback line, revealed by the JS enhancement. Without JS
   the form posts natively and the page returns the same message server-side. */
.form__status { text-align: center; }
.form__status[hidden] { display: none; }
.form__status--err { color: inherit; font-weight: var(--w-xbold); }

@media (min-width: 1024px) {
  .enquiry__type { font-size: 32px; }
}

/* --------------------------------------------------------------------------
   17. Flag loop — the little waving marker             [figma 534:21157]
   A decorative animated flag that rides the left margin down the page. Sticky
   with a zero-height box, so it holds near the top as you scroll without ever
   pushing a band sideways. Desktop only: at 375px there is no margin to spare,
   and the mobile frame drops it too.
   -------------------------------------------------------------------------- */
.flag { display: none; }

@media (min-width: 1024px) {
  .flag {
    display: block;   /* the media query set sticky/height but never re-enabled
                         display, so the base `display:none` kept the flag hidden
                         at every width — it had never actually shown. */
    position: sticky;
    inset-block-start: calc(var(--nav-h) + var(--s-5));
    z-index: 15;
    height: 0;
    pointer-events: none;
  }
  .flag img {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: var(--s-3);
    width: 120px;
    height: 120px;
    object-fit: contain;
  }
}

/* --------------------------------------------------------------------------
   18. Interior hero — a video header band              [figma 465:8175]
   Spaces/Park/Republic open on a full-bleed video header with the initial-nav
   over it and a centred title, rather than the homepage's shrink-to-window
   hero. The persistent bar arrives as this scrolls away — driven by the same
   scroll-timeline nav-in the homepage uses, with a JS fallback in motion.js.
   -------------------------------------------------------------------------- */
.phero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 72svh;
  display: grid;
  align-content: center;
  justify-items: center;
  padding: calc(var(--nav-init-h) + var(--s-9)) var(--gutter) var(--s-11);
  background: var(--c-ink);
  color: var(--c-on-media);
  text-align: center;
}
.phero__media { position: absolute; inset: 0; z-index: -2; }
.phero__media video,
.phero__media img { width: 100%; height: 100%; object-fit: cover; }
/* The footage carries real detail; a wash keeps the white title legible over
   whatever frame is showing. */
.phero::after { content: ""; position: absolute; inset: 0; z-index: -1; background: rgb(0 0 0 / 0.32); }

.phero__inner { display: flex; flex-direction: column; align-items: center; gap: var(--s-6); width: min(100%, 880px); }
.phero__sub { text-transform: uppercase; }

@media (min-width: 1024px) {
  .phero { min-height: 808px; }
}

/* --------------------------------------------------------------------------
   19. Intro — a centred statement                      [figma 465:8157]
   A Title over a short paragraph, both centred, opening a page's body. The
   title holds at 40 rather than riding the 58 desktop Title, per the frame.
   -------------------------------------------------------------------------- */
.intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-8);
  padding: var(--s-11) var(--gutter);
  background: var(--c-paper);
  color: var(--c-ink);
  text-align: center;
}
.intro__title {
  font-weight: var(--w-xbold);
  font-size: 28px;
  line-height: var(--lh-flat);
  letter-spacing: var(--ls-title);
  text-transform: uppercase;
  max-width: 22ch;
}
.intro__body { display: flex; flex-direction: column; gap: var(--s-5); width: min(100%, 60ch); }

@media (min-width: 1024px) {
  .intro { gap: var(--s-9); padding: 120px var(--gutter); }
  .intro__title { font-size: 40px; }
}

/* --------------------------------------------------------------------------
   20. Destinations — the grid of spaces        [figma 465:8163 / 274:11766]
   Each card is a 440px image with the venue's white logo centred on it, then a
   blurb and a pair of actions: an outlined pill and a square arrow button.
   One column on mobile, three across on desktop.
   -------------------------------------------------------------------------- */
.dests {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-9);
  padding: 0 var(--gutter) var(--s-11);
  background: var(--c-paper);
  color: var(--c-ink);
}

.dest { display: flex; flex-direction: column; }

.dest__img {
  position: relative;
  height: 440px;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--c-ink);
}
.dest__photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* A light wash so the white logo reads on a pale or busy photo. */
.dest__img::after { content: ""; position: absolute; inset: 0; background: rgb(0 0 0 / 0.15); }
.dest__logo { position: relative; z-index: 1; display: grid; place-items: center; width: 200px; height: 200px; }
.dest__logo img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }

.dest__body { display: flex; flex-direction: column; gap: var(--s-6); padding-block-start: var(--s-6); }
.dest__blurb { font-size: var(--t-caps); line-height: var(--lh-body); }
.dest__actions { display: flex; gap: var(--s-3); align-items: stretch; }

/* The square arrow button. Mirrors .btn's invert-on-hover, but holds a 1:1 box
   with the exported arrow painted as a mask so it follows the theme colour. */
.dest__icon {
  flex: 0 0 auto;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  padding-inline: var(--s-4);
  border: var(--border-btn) solid currentColor;
  border-radius: var(--radius-btn);
  color: var(--c-ink);
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.dest__icon::before {
  content: "";
  width: 24px;
  height: 24px;
  background: currentColor;
  -webkit-mask: url("../assets/icons/arrow.svg") center / contain no-repeat;
          mask: url("../assets/icons/arrow.svg") center / contain no-repeat;
}
.dest__icon:hover,
.dest__icon:focus-visible { background: var(--c-ink); color: var(--c-accent); }

@media (min-width: 1024px) {
  .dests { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 60px var(--s-9); }
}

/* --------------------------------------------------------------------------
   21. Park hero weather strip + the ratings-on-white variant  [figma 465:7964]
   The Park header carries a five-day forecast strip near its foot, and the
   "power of Mayfield" ratings block reappears here on white rather than the
   acid bed — so `.power--plain` just repaints it; everything else is §13.
   TODO: the Park titles are Stratos Bold in Figma; V2 ships only BDO, so they
   fall back to ExtraBold until a Stratos woff2 is self-hosted.
   -------------------------------------------------------------------------- */
.power--plain { background: var(--c-paper); }

/* The Park hero title runs larger than a section Title — nearer the Figma 80. */
.phero--park h1 { font-size: clamp(44px, 8vw, 80px); }

.wxstrip {
  position: absolute;
  inset-inline: 0;
  inset-block-end: var(--s-9);
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-6) var(--s-8);
  margin: 0;
  padding-inline: var(--gutter);
  list-style: none;
  color: var(--c-on-media);
}
.wxday { display: flex; align-items: center; gap: var(--s-2); font-size: var(--t-ui); text-transform: uppercase; }
.wxday__icon {
  width: 20px; height: 20px; flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask: var(--wx) center / contain no-repeat;
          mask: var(--wx) center / contain no-repeat;
}

/* --------------------------------------------------------------------------
   22. Gallery — a receding depth stack           [figma 496:18061 / 499:18094]
   The active image sits front and centre; the next ones recede behind it,
   smaller and darker, with Prev/Next paging through. With no JS the first
   image simply shows — the `:first-child` fallback below is overridden the
   moment the script assigns every slide a `data-pos`.
   -------------------------------------------------------------------------- */
/* overflow:hidden clips the dropped card at the section's own edges so it can
   never reappear over the section below; the gradient well fades it out before
   the clip, so the cut is never visible. Top padding leaves room for the
   receding cards to peek without being clipped. */
.gallery { position: relative; overflow: hidden; padding: 120px var(--gutter) var(--s-11); background: var(--c-paper); color: var(--c-ink); }

/* The stage is a 3D scene. The cards recede INTO it on the Z axis — perspective
   shrinks them, so there is no `scale()`; that is what gives the real depth the
   flat version lacked. Transposed from paulkalkbrenner.net's `dropping-stack`
   (perspective ~100em on the list, preserve-3d, each card stepped up in Y and
   back in Z with a descending z-index) — rebuilt here in vanilla CSS + JS, no
   GSAP. `--rise`/`--recede` are the per-card step; JS sets each card's `--d`
   (0 = front), z-index, dim and opacity. */
.gallery__stage {
  position: relative;
  height: 360px;
  perspective: 1600px;
  perspective-origin: 50% 42%;
  --rise: 104px;
  --recede: 440px;
}
.gallery__slide {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  margin: 0;
  width: 240px;
  height: 320px;
  transform-style: preserve-3d;
  --d: 0;
  transform:
    translate(-50%, -50%)
    translateY(calc(var(--d) * var(--rise) * -1))
    translateZ(calc(var(--d) * var(--recede) * -1));
  transition: transform 0.6s var(--ease), opacity 0.6s var(--ease);
  opacity: 0;
  z-index: 0;
  will-change: transform, opacity;
}
.gallery__slide img { width: 100%; height: 100%; object-fit: cover; }
/* Cards darken as they drop back, so the stack reads even over busy imagery. */
.gallery__slide::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / var(--dim, 0));
  transition: background 0.6s var(--ease);
}
/* No-JS fallback: the first image, front and centre. */
.gallery__slide:first-child { opacity: 1; z-index: 3; }
/* Once JS takes over it adds this to every slide; it then sets --d/z-index/
   opacity per card, so the fallback front card no longer forces itself on. */
.gallery.is-live .gallery__slide:first-child { opacity: 0; }
.gallery.is-live .gallery__slide { opacity: 0; }
.gallery.is-live .gallery__slide.is-shown { opacity: 1; }
/* The front card is the grab handle for the drag. */
.gallery.is-live .gallery__slide.is-front { cursor: grab; }
.gallery.is-live.is-dragging .gallery__slide.is-front { cursor: grabbing; }

/* NEXT drops the front card straight down and off, fading as it falls (its
   reset to the back happens under snap() in motion.js with the transition
   suppressed, so it is never seen). Ease-in so it accelerates like a fall; PREV
   is the mirror, eased in from above by the JS. */
.gallery.is-live .gallery__slide.is-dropping {
  transform: translate(-50%, -50%) translateY(230%);
  opacity: 0;
  z-index: 2000;
  transition: transform 0.6s cubic-bezier(0.45, 0.05, 0.85, 0.4), opacity 0.44s ease 0.14s;
}

/* The card falls into a soft "well" at the foot of the gallery: a gradient that
   fades the card out to the page as it drops (paper-on-paper, so it is invisible
   until a card passes through). With the card also blurring, it reads as
   dropping into a faded, out-of-focus void rather than over the section below.
   The upper half is fully transparent so it never touches the front card. */
.gallery::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  height: 42%;
  background: linear-gradient(to bottom, transparent 0%, transparent 58%, var(--c-paper) 96%);
  pointer-events: none;
  z-index: 2100;
}

/* Nav flanks the stack, VERTICALLY CENTRED on the gallery: the count far left,
   Prev/Next far right (Figma 496:18061). The row lets pointer events through so
   it never blocks dragging the card in the middle; only the controls catch them. */
.gallery__nav {
  position: absolute;
  inset-block-start: 50%;
  inset-inline: var(--gutter);
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: var(--s-6);
  font-size: var(--t-caps);
  text-transform: uppercase;
  z-index: 5;
  pointer-events: none;
}
.gallery__nav > * { pointer-events: auto; }
.gallery__nav button { cursor: pointer; }
.gallery__count { order: -1; margin-inline-end: auto; min-width: 4ch; }

@media (min-width: 1024px) {
  .gallery__stage { height: 520px; --rise: 120px; --recede: 560px; }
  .gallery__slide { width: 360px; height: 480px; }
}

@media (prefers-reduced-motion: reduce) {
  .gallery__slide, .gallery__slide::after { transition: none; }
}

/* --------------------------------------------------------------------------
   23. Press — coverage cards                     [figma 517:20584 / 517:20880]
   A square publication image over its name and the headline, with a Read
   article pill pinned to the foot. One column on mobile, three across up.
   -------------------------------------------------------------------------- */
.press { display: grid; grid-template-columns: 1fr; gap: var(--s-9); padding: 0 var(--gutter) var(--s-9); background: var(--c-paper); color: var(--c-ink); }
.press__card { display: flex; flex-direction: column; gap: var(--s-4); }
.press__img { aspect-ratio: 1; overflow: hidden; }
.press__img img { width: 100%; height: 100%; object-fit: cover; }
.press__pub { text-align: center; font-size: var(--t-caps-l); text-transform: uppercase; }
.press__body { display: flex; flex: 1 0 auto; flex-direction: column; justify-content: space-between; gap: var(--s-6); }
.press__title { font-weight: var(--w-xbold); font-size: 26px; line-height: var(--lh-flat); letter-spacing: -0.03em; text-transform: uppercase; }
/* The full-width CTA spans every column beneath the cards. */
.press__cta { grid-column: 1 / -1; padding-block-start: var(--s-6); }

@media (min-width: 1024px) {
  .press { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 60px var(--s-9); }
}

/* --------------------------------------------------------------------------
   24. Park info + FAQ                             [figma 465:7935 / 261:10184]
   An address block over a map, then a native <details> accordion — no JS, the
   browser owns the open/close. TODO: Stratos Bold titles fall back to xbold;
   the FAQ answer copy is placeholder pending real text.
   -------------------------------------------------------------------------- */
.parkinfo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-9);
  padding: var(--s-11) var(--gutter);
  background: var(--c-paper);
  color: var(--c-ink);
  text-align: center;
}
.parkinfo__head { display: flex; flex-direction: column; align-items: center; gap: var(--s-6); }
.parkinfo__title { font-weight: var(--w-xbold); font-size: clamp(44px, 9vw, 80px); line-height: var(--lh-crush); letter-spacing: -0.02em; text-transform: uppercase; }
.parkinfo__map { width: min(100%, 816px); height: auto; border-radius: var(--radius-field); }

.faq { width: min(100%, 640px); display: flex; flex-direction: column; gap: 60px; padding-block-start: var(--s-9); text-align: left; }
/* Each row: question (always shown) -> collapsible answer panel -> the toggle,
   pinned at the BOTTOM of the row, under the answer. */
.faq__item { display: flex; flex-direction: column; }
.faq__q { margin: 0; font-weight: var(--w-xbold); font-size: 26px; line-height: var(--lh-crush); letter-spacing: -0.02em; text-transform: uppercase; }

/* The panel animates its height via the grid 0fr -> 1fr trick (the inner clips),
   so open AND close are one smooth physical transition. Open by default so the
   answers are readable with no JS; the .js branch collapses them until opened. */
.faq__panel { display: grid; grid-template-rows: 1fr; transition: grid-template-rows 0.45s var(--ease); }
.faq__panel-inner { overflow: hidden; min-height: 0; }
.js .faq__item:not(.is-open) .faq__panel { grid-template-rows: 0fr; }
.faq__a { margin-block: var(--s-6) 0; font-size: var(--t-body); max-width: 60ch; transition: opacity 0.4s var(--ease); }
.js .faq__item:not(.is-open) .faq__a { opacity: 0; }

/* The toggle sits at the bottom of the row. Its + is two bars so it can morph to
   a − (the vertical bar retracts) when open. No-JS: no toggle, answers just show. */
.faq__toggle {
  order: 3;
  align-self: flex-start;
  margin-block-start: var(--s-6);   /* space between the title/answer and the toggle */
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  cursor: pointer;
  font-size: var(--t-caps);
  text-transform: uppercase;
}
.no-js .faq__toggle { display: none; }
.faq__plus { position: relative; width: 11px; height: 11px; flex: 0 0 auto; transition: transform 0.3s var(--ease); }
.faq__plus::before, .faq__plus::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  background-color: currentColor;
}
.faq__plus::before { width: 100%; height: 1.5px; }   /* horizontal bar (stays — the −) */
.faq__plus::after  { width: 1.5px; height: 100%; transition: scale 0.3s var(--ease), opacity 0.3s var(--ease); }  /* vertical bar */

/* Hover (closed only): the + spins a smooth quarter-turn. */
.faq__item:not(.is-open) .faq__toggle:hover .faq__plus,
.faq__item:not(.is-open) .faq__toggle:focus-visible .faq__plus { transform: rotate(90deg); }

/* Open: vertical bar retracts to leave a −, and the label swaps to Hide answer. */
.faq__item.is-open .faq__plus::after { scale: 0 1; opacity: 0; }
.faq__label--hide { display: none; }
.faq__item.is-open .faq__label--read { display: none; }
.faq__item.is-open .faq__label--hide { display: inline; }

@media (prefers-reduced-motion: reduce) {
  .faq__panel { transition: none; }
  .faq__plus, .faq__plus::after, .faq__a { transition: none; }
}
.faq__legal { font-size: var(--t-legal); line-height: var(--lh-body); opacity: 0.5; }

@media (min-width: 1024px) {
  .faq__q { font-size: 36px; }
}

/* ==========================================================================
   REPUBLIC  [figma 465:7870]
   The Republic sub-brand: its own acid yellow (--c-republic) and a Garamond
   serif display (--f-serif, roman + italic). Both are noted TODOs — the yellow
   is applied here, the serif falls back to a platform serif until a woff2 lands.
   ========================================================================== */

/* -- 25. Republic hero — bg image, centred logo + portrait   [figma 465:7922] */
.rhero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* Height comes from the shared .phero (72svh mobile, fixed 808px desktop);
     a second svh min-height here ballooned the header on tall viewports. */
  display: grid;
  place-items: center;
  padding: calc(var(--nav-init-h) + var(--s-10)) var(--gutter) var(--s-11);
  background: var(--c-ink);
  color: var(--c-on-media);
}
.rhero__bg { position: absolute; inset: 0; z-index: -2; width: 100%; height: 100%; object-fit: cover; }
.rhero::after { content: ""; position: absolute; inset: 0; z-index: -1; background: rgb(0 0 0 / 0.28); }
/* Recoloured to the Republic acid yellow via a mask (the SVG is shared with the
   Spaces cards, so its own fill is left alone). */
.rhero__logo {
  position: absolute;
  inset-block-start: calc(var(--nav-init-h) + var(--s-5));
  inset-inline-start: 50%;
  transform: translateX(-50%);
  width: 138px; height: 47px;
  z-index: 1;
  background-color: var(--c-republic);
  -webkit-mask: url("../assets/logos/republic.svg") center / contain no-repeat;
          mask: url("../assets/logos/republic.svg") center / contain no-repeat;
}
.rhero__portrait { width: min(74vw, 420px); aspect-ratio: 517 / 591; }
.rhero__portrait img { width: 100%; height: 100%; object-fit: cover; }

/* -- 26. Republic display serif + centred bands           [figma Title 80] */
.r-title {
  font-family: var(--f-serif);
  font-weight: 400;
  font-size: clamp(44px, 9vw, 80px);
  line-height: 1;
  letter-spacing: -0.06em;
  text-transform: uppercase;
}
.r-title em { font-style: italic; }

/* A centred content band — used for the intro, the paragraphs between sections,
   the brochure CTA and the application lead-in. */
.rband {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-9);              /* [figma] 40px between lead, button, logo */
  padding: var(--s-11) var(--gutter);
  background: var(--c-paper);
  color: var(--c-ink);
  text-align: center;
}
@media (min-width: 1024px) { .rband { padding-block: 140px; } }   /* the intro band's generous vertical space */
.rband__logo { width: 138px; height: auto; }
/* [figma Hero 48] Garamond 48px, lh 1.0, tracking -4%. */
.rband__lead { max-width: 30ch; font-family: var(--f-serif); font-weight: 400; font-size: clamp(30px, 4vw, 48px); line-height: 1; letter-spacing: -0.04em; }
.rband__text { max-width: 60ch; }

/* -- 27. Location list                                    [figma 465:7886] */
.rlocations { background: var(--c-paper); color: var(--c-ink); }
.rloc {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-6);
  padding: var(--s-8) var(--s-8) var(--s-7);   /* [figma] rows inset 30px, not the 60px gutter */
  border-block-start: 2px solid currentColor;
  transition: background 0.2s var(--ease);
}
.rlocations > .rloc:last-child { border-block-end: 2px solid currentColor; }
.rloc:hover { background: var(--c-republic); }
.rloc__name { font-family: var(--f-serif); font-weight: 400; font-size: clamp(28px, 5vw, 42px); line-height: 1; letter-spacing: -0.08em; text-transform: uppercase; }
.rloc__time { font-size: var(--t-caps-l); white-space: nowrap; }   /* [figma Body 18] */

/* -- 28. Benefit stats — a row of figures                 [figma 505:19992] */
.rstats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-9) var(--s-7);
  padding: var(--s-11) var(--gutter);
  background: var(--c-paper);
  color: var(--c-ink);
  text-align: center;
}
.rstat { display: flex; flex-direction: column; gap: var(--s-3); align-items: center; }
.rstat__num { font-family: var(--f-serif); font-weight: 400; font-size: clamp(40px, 7vw, 64px); line-height: 1; letter-spacing: -0.04em; }
.rstat__label { font-size: var(--t-caps); text-transform: uppercase; }
@media (min-width: 1024px) { .rstats { grid-template-columns: repeat(4, 1fr); } }

/* -- 29. Benefit panel — a yellow split card              [figma 214:2088] */
.bpanels { display: flex; flex-direction: column; gap: var(--s-9); padding: var(--s-10) var(--gutter); background: var(--c-paper); }
.bpanel {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 6px solid var(--c-paper);
  border-radius: 40px;
  background: var(--c-republic);
  color: var(--c-ink);
}
.bpanel__img { aspect-ratio: 4 / 3; }
.bpanel__img img { width: 100%; height: 100%; object-fit: cover; }
.bpanel__text { display: flex; flex-direction: column; justify-content: space-between; gap: var(--s-9); padding: var(--s-9); }   /* [figma] 40px column padding */
.bpanel__title { font-family: var(--f-serif); font-weight: 400; font-size: clamp(44px, 9vw, 80px); line-height: 1; letter-spacing: -0.08em; text-transform: uppercase; }  /* [figma Title 80] -8% */
.bpanel__title em { font-style: italic; }
.bpanel__tags { display: flex; flex-wrap: wrap; gap: var(--s-7); font-size: var(--t-caps); text-transform: uppercase; margin-block-end: var(--s-6); }   /* [figma] tags gap 24, desc gap 20 */
@media (min-width: 1024px) {
  .bpanel { flex-direction: row; align-items: stretch; border-width: 8px; border-radius: 50px; }
  .bpanel > * { flex: 1 1 0; min-width: 0; }
  .bpanel__img { aspect-ratio: auto; }
}

/* -- 30. Floors — image + schedule table                  [figma 465:7910] */
.floors { display: flex; flex-direction: column; background: var(--c-paper); color: var(--c-ink); }
/* The render is positioned absolutely inside a definite-height box so that
   swapping between renders of different aspect ratios (building-1 is a tall
   562x1200 portrait; the rest are ~square) can never change the box height and
   shove the page on hover. The box takes its height from its aspect-ratio
   (mobile) or from the flex row = table height (desktop); the img just covers. */
.floors__img { aspect-ratio: 3 / 2; position: relative; overflow: hidden; }
.floors__img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.floors__table { padding: var(--s-9) var(--gutter); }
.floors__head, .floor {
  display: grid;
  grid-template-columns: 4.5em 1fr auto;
  gap: var(--s-4);
  align-items: center;
}
.floors__head { padding-block-end: var(--s-4); font-size: var(--t-caps); text-transform: uppercase; }
.floor { padding-block: var(--s-4); border-block-start: 1px solid rgb(0 0 0 / 0.25); font-size: var(--t-body); }
.floors__list > .floor:last-child { border-block-end: 1px solid rgb(0 0 0 / 0.25); }
.floor__cap { display: inline-flex; align-items: center; gap: var(--s-2); }
.floor__cap::before {
  content: ""; width: 20px; height: 20px; flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask: url("../assets/icons/person.svg") center / contain no-repeat;
          mask: url("../assets/icons/person.svg") center / contain no-repeat;
}
[data-mode="night"] .floor, [data-mode="night"] .floors__list > .floor:last-child { border-color: rgb(255 255 255 / 0.3); }
@media (min-width: 1024px) {
  .floors { flex-direction: row; }
  .floors__img { flex: 1 1 0; aspect-ratio: auto; }
  .floors__table { flex: 1 1 0; align-self: center; }
  .floors__head, .floor { grid-template-columns: 170px 1fr 100px; }   /* [figma] Floor 170 / Size 1fr / Capacity 100 */
}

/* --------------------------------------------------------------------------
   31. The Republic — display type, real content & the two hover interactions
   From the walkthrough: the location list reveals a green-duotone map per row,
   and hovering a floor swaps the building render on the left. Titles are set in
   the Garamond fallback (--f-serif) until a woff2 is self-hosted.
   TODO: self-host Garamond Premier Pro (roman + italic subhead).
   -------------------------------------------------------------------------- */

/* The inline logo inside the intro sentence: "The [Republic] is a place…". */
.rband__inline { display: inline-block; height: 0.6em; width: auto; margin-inline: 0.14em; transform: translateY(0.04em); }

/* Republic title block — Garamond, uppercase, three lines each aligned
   differently, with one italic accent word. [figma republic title block] */
.rtitle-sec { padding: var(--s-11) var(--gutter); background: var(--c-paper); color: var(--c-ink); }
.rtitle {
  font-family: var(--f-serif);
  font-weight: 400;
  font-size: clamp(46px, 11vw, 100px);
  line-height: 1;              /* [figma Title 100] lh 1.0 */
  letter-spacing: -0.08em;     /* [figma] Garamond titles track -8% */
  text-transform: uppercase;
  display: flex;
  flex-direction: column;
}
.rtitle { align-items: flex-start; }
.rtitle em { font-style: italic; }
/* [figma 465:7900] Each line cascades further to the right — a staircase, not a
   right/centre zigzag. Indents approximate the Figma left offsets (~11/34/44%). */
.rtitle > span { width: fit-content; }
.rtitle > span:nth-child(1) { margin-inline-start: 11%; }
.rtitle > span:nth-child(2) { margin-inline-start: 34%; }
.rtitle > span:nth-child(3) { margin-inline-start: 45%; }

/* The acid-yellow title laid over the feature photo. */
.rtitle-sec--image {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  align-content: center;
  min-height: 60svh;
  color: var(--c-republic);
}
.rtitle-sec__bg { position: absolute; inset: 0; z-index: -1; width: 100%; height: 100%; object-fit: cover; }
.rtitle-sec--image::after { content: ""; position: absolute; inset: 0; z-index: -1; background: rgb(0 0 0 / 0.12); }
@media (min-width: 1024px) { .rtitle-sec--image { min-height: 640px; } }

/* [figma 465:7892] The between-section paragraph is a LEFT-aligned 600px block
   inset from the left, NOT centred — padding t100 / b150 / x100 on desktop. */
.rpara { padding: var(--s-9) var(--gutter) var(--s-10); background: var(--c-paper); color: var(--c-ink); }
.rpara p { width: min(100%, 600px); text-align: left; font-size: var(--t-body); line-height: var(--lh-body); }
@media (min-width: 1024px) { .rpara { padding: 100px 100px 150px; } }

/* -- Location list — hover reveals a per-row green-duotone map -------------
   The map is a photo multiplied down over the Republic yellow, so it reads as
   the green duotone the design uses. Desktop only: it is a hover affordance and
   at 375px there is no room for a centred plate over the row text. */
.rlocations { position: relative; isolation: isolate; }
.rloc { position: static; }
.rloc:hover { background: transparent; }   /* the reveal is the image, not a row wash */
.rloc__map { display: none; }
@media (min-width: 1024px) {
  /* The map now FOLLOWS the cursor with a subtle swing (motion.js §10 drives the
     transform): it lerps toward the pointer and rotates from its own horizontal
     velocity, so it trails and swings like it is hanging from the cursor.
     transform-origin at the top edge makes it a pendulum. */
  .rloc__map {
    display: block;
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    width: 240px;
    height: 320px;
    background: var(--c-republic);
    isolation: isolate;
    transform-origin: 50% 50%;   /* rotate around the cursor so it stays centred */
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.35s var(--ease);   /* JS owns the transform */
    z-index: 3;
    will-change: transform;
  }
  .rloc__map img { width: 100%; height: 100%; object-fit: cover; mix-blend-mode: multiply; }
  .rloc__map.is-on { opacity: 1; }
}

/* -- Floors — the left render cross-fades as motion.js swaps layers on hover -- */
.floors__img img { transition: opacity 0.4s ease; }
/* The yellow appears immediately on hover but DECAYS out over 300ms — sweeping
   the rows leaves a trailing gradient of fading-yellow rows. The direction split
   is the transition-on-the-target-state trick: the base rule governs the return
   to white (slow), the :hover rule governs the arrival (instant). */
.floor { transition: background 300ms ease-out; }
.floor:hover { background: var(--c-republic); transition-duration: 0ms; }

@media (prefers-reduced-motion: reduce) {
  .rloc__map { transition: none; }
}

/* --------------------------------------------------------------------------
   32. The Republic — yellow bands, the benefits row & the brochure card
   Corrections from the client walkthrough: the intro and the "MCR's first
   office" title sit on the Republic yellow (not white); the four benefits are
   captioned photos (not figures); the brochure CTA is a rounded yellow card.
   -------------------------------------------------------------------------- */
.rband--brand { background: var(--c-republic); color: var(--c-ink); }
.rtitle-sec--brand { background: var(--c-republic); color: var(--c-ink); }

/* Four captioned photos. [figma 505:20004] */
.rbenefits {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-9) var(--s-7);
  padding: var(--s-10) var(--gutter) var(--s-11);
  background: var(--c-paper);
  color: var(--c-ink);
}
.rbenefit { display: flex; flex-direction: column; gap: var(--s-5); align-items: center; text-align: center; }
.rbenefit img { width: 100%; aspect-ratio: 1; object-fit: cover; }
/* [figma 55:3789] captions are Halyard Text Regular 15/1.4 in near-black green
   (halyard-text is in the same Adobe kit already loaded on this page). */
.rbenefit p { font-family: "halyard-text", var(--f-sans, sans-serif); font-weight: 400; font-size: var(--t-caps); line-height: 1.4; color: #061202; max-width: 24ch; }
@media (min-width: 1024px) { .rbenefits { grid-template-columns: repeat(4, 1fr); gap: var(--s-7) 48px; } }   /* [figma] 24px row / 48px col */

/* Brochure CTA — a rounded yellow card rather than a full-bleed band. [465:7915] */
.rcta { padding: var(--s-10) var(--gutter); background: var(--c-paper); }
.rcta__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-7);
  padding: var(--s-10) var(--gutter);
  background: var(--c-republic);
  color: var(--c-ink);
  border-radius: 40px;
  text-align: center;
}
/* [figma 465:7917] the CTA lead is Garamond 48, not body copy. */
.rcta__card .rband__text {
  max-width: 46ch;
  font-family: var(--f-serif);
  font-weight: 400;
  font-size: clamp(28px, 4vw, 48px);
  line-height: 1;
  letter-spacing: -0.04em;
}
@media (min-width: 1024px) { .rcta__card { border-radius: 80px; padding: var(--s-11); } }
