/* ============================================================
   STYLE.CSS — BridgePoint Living
   Structure + components. Reads tokens.css only.
   ============================================================ */

/* ---------- Fonts (self-hosted, no external requests) ---------- */
@font-face{font-family:"Cinzel";font-style:normal;font-weight:500 700;font-display:swap;
  src:url("../assets/fonts/cinzel-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2122,U+2212,U+FEFF,U+FFFD;}
@font-face{font-family:"Cinzel";font-style:normal;font-weight:500 700;font-display:swap;
  src:url("../assets/fonts/cinzel-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+1E00-1E9F,U+2020,U+20A0-20AB,U+2113,U+A720-A7FF;}
@font-face{font-family:"Karla";font-style:normal;font-weight:200 800;font-display:swap;
  src:url("../assets/fonts/karla-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2122,U+2212,U+FEFF,U+FFFD;}
@font-face{font-family:"Karla";font-style:normal;font-weight:200 800;font-display:swap;
  src:url("../assets/fonts/karla-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+1E00-1E9F,U+2020,U+20A0-20AB,U+2113,U+A720-A7FF;}
@font-face{font-family:"Karla";font-style:italic;font-weight:200 800;font-display:swap;
  src:url("../assets/fonts/karla-italic-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2122,U+2212,U+FEFF,U+FFFD;}
/* Great Vibes — the signature face, and the ONLY place it is used. Latin subset
   only: it sets two words ("Paula Harrison"), so the ext/cyrillic/greek/vietnamese
   subsets Google serves would be dead weight. Self-hosted like the other two
   faces — this site makes zero external font requests. */
@font-face{font-family:"Great Vibes";font-style:normal;font-weight:400;font-display:swap;
  src:url("../assets/fonts/greatvibes-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2122,U+2212,U+FEFF,U+FFFD;}

/* ---------- Reset ---------- */
*,*::before,*::after{box-sizing:border-box}
/* scroll-padding-top is what stops the fixed nav from covering whatever an
   in-page link just jumped to. Without it the browser scrolls the target to
   y=0 and the nav sits on top of the first 73px — measured, the "Who we serve"
   heading landed 9px behind the bar. Set on the scroll container so it applies
   to every anchor at once (nav links, the two hero CTAs, the footer list, the
   skip link) rather than needing scroll-margin on each target.

   **It is `--nav-h` MINUS 3px, not plus breathing room.** It shipped as
   `+ .5rem`, which parked every target 81px down while the nav only covers 73 —
   leaving a 7-8px strip of the *previous* section visible under the bar. That
   was audited and it was not cosmetic: six of eight anchors showed it, and every
   one lands on a colour change. Four are ink-above-cream (`#promise`,
   `#founder`, `#questions`, and `#included` via the seam art), which reads as a
   dark bar wedged under the nav on arrival.

   Minus 3px instead of exactly `--nav-h` because the nav gains a pixel at wide
   viewports (measured 73px at 390 and 1050, 74px at 1280); tucking the target's
   top edge behind the bar absorbs that so no hairline survives at any width.
   Nothing is lost to the overlap — every section carries `--section-y` padding
   (64-120px), so only blank ground hides up there and headings stay well clear. */
/* `scroll-behavior:smooth` was removed, and it was a real bug, not a taste call.
   This page is tall: the nav's "Join the waitlist" jump is **6,643px and took
   1,550ms** to animate at 390px wide (measured). Any touch on the screen
   cancels an in-progress smooth scroll, so on a phone — where a thumb is
   already near the glass — the scroll aborted partway and left the visitor
   stranded wherever it stopped. Because the bridge seam sits directly above
   the waitlist, "wherever it stopped" was usually the bridge, which is exactly
   how it was reported: the same button landing in two different places.
   Instant jumps cannot be interrupted, so the CTA now always arrives.
   The 1.5s glide was never one of the page's three authored motions anyway
   (hero reveal, two parallax seams) — nothing in the design story is lost.
   **Do not reintroduce it on a page this tall.** */
html{-webkit-text-size-adjust:100%;
  scroll-padding-top:calc(var(--nav-h) - 3px)}
body{margin:0;background:var(--surface);color:var(--ink-2);
  font-family:var(--font-body);font-size:var(--step-0);font-weight:var(--fw-body);
  line-height:var(--leading-body);-webkit-font-smoothing:antialiased;overflow-x:hidden}
[hidden]{display:none !important}
img,video,svg{display:block;max-width:100%}
img,video{height:auto}
h1,h2,h3{margin:0;font-family:var(--font-display);font-weight:var(--fw-display);
  color:var(--ink);line-height:var(--leading-tight);letter-spacing:var(--tracking-tight);
  text-wrap:balance}
p{margin:0}
ul{margin:0;padding:0;list-style:none}
a{color:inherit}
button,input,select{font:inherit;color:inherit}
/* The dark rectangle that flashes on tap is the mobile browser's own default
   highlight, not anything this site draws — it fires the instant a touch lands,
   before any of our own feedback. Killing it does not leave taps unacknowledged:
   the .btn press-scale and each control's own open/close motion already answer
   the touch. Buttons/links only, so form fields keep their native affordance. */
a,button,summary,[role="button"]{-webkit-tap-highlight-color:transparent}

:where(a,button,input,select,summary):focus-visible{outline:none;box-shadow:var(--focus);border-radius:var(--radius-sm)}
.hero :where(a,button):focus-visible,
.included :where(a,button):focus-visible,
.waitlist :where(a,button,input,select):focus-visible,
.foot :where(a,button):focus-visible{box-shadow:var(--focus-ink)}

.skip{position:absolute;left:-9999px;top:0;z-index:200;background:var(--gold);
  color:var(--ink);padding:.75rem 1.25rem;font-weight:var(--fw-bold)}
.skip:focus{left:.5rem;top:.5rem}

.wrap{width:min(100% - (var(--gutter) * 2),var(--container));margin-inline:auto}

/* ---------- Type helpers ---------- */
.h2{font-size:var(--step-3)}
.h2--on-ink{color:var(--on-ink)}
.gold-ink{color:var(--gold-deep)}
.lede{font-size:var(--step-1);line-height:var(--leading-snug);color:var(--ink-2);max-width:34ch}

/* ---------- Drawn icons (one stroke system) ---------- */
.ic{width:1.15em;height:1.15em;flex:none;fill:none;stroke:currentColor;
  stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.ic--lg{width:2rem;height:2rem;stroke-width:1.4;color:var(--gold-lit)}

/* ---------- Buttons ---------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;
  padding:.95rem 1.7rem;border:1px solid transparent;border-radius:var(--radius-sm);
  font-weight:var(--fw-bold);font-size:var(--step--1);letter-spacing:.08em;
  text-transform:uppercase;text-decoration:none;cursor:pointer;
  transition:background var(--dur-ui) var(--ease),color var(--dur-ui) var(--ease),
             border-color var(--dur-ui) var(--ease),transform var(--dur-ui) var(--ease)}
.btn--gold{background:var(--gold);color:var(--ink);border-color:var(--gold)}
.btn--ghost{background:transparent;color:var(--on-ink);border-color:rgba(224,183,92,.5)}
.btn--sm{padding:.6rem 1.05rem}
.btn--block{width:100%;padding-block:1.1rem}

/* Hover is gated to real pointers. A tap on a touchscreen fires a hover that
   then *sticks*, so without this the CTA stays lifted and lit after the finger
   is long gone — which reads as a stuck button, not a hovered one. */
@media (hover:hover) and (pointer:fine){
  .btn--gold:hover{background:var(--gold-lit);border-color:var(--gold-lit);
    transform:translateY(-1px)}

  /* ---- Ghost button: the gold fills in, left to right ----
     The outlined button becomes the solid one under the cursor — it lands on
     exactly `--gold` with `--ink` text, which is `.btn--gold` verbatim, so the
     hover state is a button this site already has rather than a fourth colour
     pairing invented for it. That also means it reads the same on cream and on
     ink, which matters because the three ghost buttons live on both grounds.

     `scaleX` on a pseudo-element, never `width` — width would animate layout on
     every frame of every hover (the detector flags that pattern, and rightly).
     The fill sits at `z-index:-1` under an `isolation:isolate` button, which is
     what keeps it behind the label without needing a wrapper span around the
     text. `overflow:hidden` is what clips it to the 4px corner radius; the
     focus ring is a box-shadow on the button itself, so it is not clipped by
     this and still shows in full.

     `transform-origin` stays left in both directions on purpose. Flipping it to
     the right on release makes the fill *exit* rightward, which looks better in
     the ideal case but visibly jumps if you pull the cursor away mid-sweep,
     because the origin switches while the transform is still moving. Filling
     right and draining back left has no such artifact. */
  .btn--ghost{position:relative;isolation:isolate;overflow:hidden}
  .btn--ghost::before{content:"";position:absolute;inset:0;z-index:-1;
    background:var(--gold);transform:scaleX(0);transform-origin:left center;
    transition:transform var(--dur-fill) var(--ease)}
  .btn--ghost:hover::before{transform:scaleX(1)}
  /* The label flips a beat after the gold starts moving, so the colour change
     reads as the fill arriving underneath it rather than as a separate event
     firing at the same moment. */
  .btn--ghost{transition:background var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease) .09s,
              border-color var(--dur-ui) var(--ease),
              transform var(--dur-ui) var(--ease)}
  .btn--ghost:hover{color:var(--ink);border-color:var(--gold)}
}

/* Press feedback. Touch has no hover to fall back on, so without this a tap on
   "Add me to the waitlist" over a slow connection gives no acknowledgment at
   all until the response lands. Must stay *after* the hover block above:
   `.btn--gold:hover` and `.btn:active` are both specificity (0,2,0), so source
   order is what lets the press win over the hover lift on desktop. */
.btn:active{transform:scale(.97)}

/* ============================================================
   NAV
   ============================================================ */
.nav{position:fixed;inset:0 0 auto 0;z-index:100;
  transition:background var(--dur) var(--ease),box-shadow var(--dur) var(--ease)}
.nav__inner{width:min(100% - (var(--gutter) * 2),var(--container));margin-inline:auto;
  display:flex;align-items:center;justify-content:space-between;gap:1rem;padding-block:.9rem}
/* Solid, not translucent — a 93%-opacity + blur version of this let scrolled
   content bleed through legibly right at the nav's bottom edge (worst right
   under the offer accordion, where muted italic text sits close to the seam).
   Blur only hides what's behind it when there's still something showing
   through; at full opacity there's nothing left to blur, so it's dropped too. */
.nav.is-stuck{background:var(--ink);box-shadow:0 1px 0 var(--line-ink)}

.nav__brand{display:flex;align-items:center;gap:.7rem;text-decoration:none;color:var(--on-ink)}
.nav__mark{width:44px;height:auto}
.nav__word{font-family:var(--font-display);font-weight:600;font-size:.98rem;
  letter-spacing:.09em;text-transform:uppercase;line-height:1;white-space:nowrap}
.nav__word em{font-style:normal;color:var(--gold-lit)}

/* `flex:none` is load-bearing. As a default flex item this row had
   `flex-shrink:1`, so when the bar got tight flexbox shrank the row instead of
   leaving it alone — and because the labels can wrap, "Our promise" quietly
   became two lines rather than the nav ever looking full. The result was a
   two-line nav that no breakpoint could fix, because shrinking always
   "succeeded". Refusing to shrink is what makes the collapse breakpoint below
   the single thing that decides between one-line nav and hamburger. */
.nav__links{display:flex;align-items:center;gap:1.65rem;flex:none}
/* :not(.btn) matters. The nav's CTA is an <a class="btn"> living inside this
   list, and a bare `.nav__links a` (0,1,1) outranks `.btn` (0,1,0) — so the
   whole button transition was being replaced by this colour-only one, and the
   CTA's background, border and lift were snapping with no transition at all.
   The plain links keep their colour fade; the button keeps being a button. */
.nav__links a:not(.btn){color:var(--on-ink-soft);text-decoration:none;
  font-size:var(--step--1);letter-spacing:.04em;transition:color var(--dur-ui) var(--ease)}
.nav__links a:not(.btn):hover,.nav__links a:not(.btn).is-active{color:var(--gold-lit)}
.nav__links .btn{color:var(--ink)}
.nav__links .btn:hover{color:var(--ink)}

.nav__toggle{display:none;width:44px;height:44px;background:none;border:0;
  cursor:pointer;padding:11px 9px;flex-direction:column;justify-content:space-between}
.nav__toggle span{display:block;height:2px;background:var(--on-ink);border-radius:2px;
  transition:transform .35s var(--ease),opacity .2s var(--ease)}
/* 10px, not 9px — and it's measured, not eyeballed. The toggle is 44px with
   11px block padding, so the content box is 22px; three 2px bars with
   space-between sit at centres 1 / 11 / 21. Converging on the middle one is
   therefore exactly ±10px. At 9px the two arms crossed 2px apart, so the X
   never closed to a point and the gap read as wobble through the rotation.
   If the toggle's size or padding changes, re-derive this. */
.nav.is-open .nav__toggle span:nth-child(1){transform:translateY(10px) rotate(45deg)}
.nav.is-open .nav__toggle span:nth-child(2){opacity:0}
.nav.is-open .nav__toggle span:nth-child(3){transform:translateY(-10px) rotate(-45deg)}

/* ============================================================
   HERO — the dark crossing
   ============================================================ */
.hero{position:relative;background:var(--ink);color:var(--on-ink);
  padding-block:clamp(7rem,14vh,10rem) clamp(4.5rem,10vw,8rem);
  text-align:center;isolation:isolate;overflow:hidden}
.hero__glow{position:absolute;left:50%;top:22%;translate:-50% -50%;
  width:min(120vw,1000px);aspect-ratio:1;z-index:-1;
  background:radial-gradient(circle,rgba(179,129,47,.26),rgba(179,129,47,0) 62%)}

.hero__inner{width:min(100% - (var(--gutter) * 2),var(--container-narrow));margin-inline:auto;
  display:flex;flex-direction:column;align-items:center}

/* The reveal video carries its own wordmark at the bottom of the frame.
   The window shows only the animated mark; the wordmark below is real type. */
.hero__mark{position:relative;width:min(82%,420px);aspect-ratio:16/3.78;overflow:hidden}
.hero__still,.hero__video{position:absolute;left:0;top:-30.9%;width:100%;
  aspect-ratio:16/9;object-fit:fill}
.hero__still{transition:opacity .4s var(--ease)}
/* The reveal ships on a black matte; screen drops the black against the ink. */
.hero__video{opacity:0;mix-blend-mode:screen;transition:opacity .5s var(--ease)}
.hero__mark.is-animating .hero__video{opacity:1}
.hero__mark.is-animating .hero__still{opacity:0}

.hero__word{margin-top:1.35rem;font-family:var(--font-display);font-weight:600;
  font-size:clamp(1.15rem,3.6vw,1.7rem);letter-spacing:.2em;text-transform:uppercase;
  color:var(--on-ink);line-height:1}
.hero__word span{color:var(--gold-lit)}

.hero__h1{margin-top:2.1rem;font-size:var(--step-4);color:var(--on-ink)}

.hero__sub{margin-top:1.4rem;max-width:47ch;color:var(--on-ink-soft);font-size:var(--step-1);
  line-height:1.55}

.hero__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:.9rem;margin-top:2.2rem}
.hero__note{margin-top:1.5rem;font-size:var(--step--1);color:var(--on-ink-mute);letter-spacing:.02em}

/* ============================================================
   SEAMS — Paula's own artwork as the horizon where a light
   section gives way to an ink one. The art carries a welded
   ink bar along its bottom edge, so the join stays sealed no
   matter how far the parallax layer drifts.
   ============================================================ */
.seam{--drift:64px;position:relative;overflow:hidden;line-height:0}
.seam__art{position:absolute;left:0;bottom:calc(var(--drift) * -1);
  width:100%;height:auto;will-change:transform}

/* Same overflow trick as the bridge: the band is shorter than the art, so the
   rooftops break the section edge instead of being sliced off by it. */
.seam--houses{background:var(--surface-2);aspect-ratio:1535 / 500;
  overflow-x:clip;overflow-y:visible}

/* The bridge is taller than its band on purpose. Its sky is transparent, so
   letting the art overflow upward puts the tower and cables out in the open
   cream above rather than guillotining the span at the section edge. The
   section before it carries the extra bottom padding that clears the crown. */
/* `clip` on one axis only: the art has to spill upward into the light section,
   but the zoomed mobile version is wider than the screen and must not push the
   page sideways. `hidden` cannot do this — pairing it with `visible` forces the
   other axis to `auto` — but `clip` leaves the free axis genuinely visible. */
.seam--bridge{background:var(--surface);aspect-ratio:1536 / 460;
  overflow-x:clip;overflow-y:visible}

/* ============================================================
   PROMISE — first light
   ============================================================ */
.promise{background:var(--surface);padding-block:var(--section-y)}
/* Centred, and the only section on the page that is. Every other section runs
   an asymmetric intro-left/content-right split, so centring gives the promise
   its own role instead of being a fourth variation on the same layout — and it
   sits square over the symmetric Values triad below it. It also removes the
   dead space rather than balancing it: left-pinned, this 720px block left
   440px empty against the 1160px container. Both children need their boxes
   centred, not just their text — `text-align` centres words inside a box, and
   a max-width box with default margins still sits hard left. Below 760px the
   section was already centred by the mobile rules, so this makes the two
   agree rather than diverge. */
.promise__lead{max-width:var(--container-narrow);margin-inline:auto;text-align:center}
.promise__lead .lede{margin-top:1.4rem;max-width:52ch;margin-inline:auto}

.values{margin-top:clamp(3rem,6vw,4.5rem);display:grid;gap:0;
  grid-template-columns:repeat(3,1fr);border-top:1px solid var(--line)}
.values li{padding:2.1rem 2rem 0 0}
.values li + li{border-left:1px solid var(--line);padding-left:2rem}
.values__word{display:block;font-family:var(--font-display);font-weight:600;
  font-size:var(--step-2);color:var(--gold-deep);letter-spacing:.02em;line-height:1}
.values li p{margin-top:.85rem;color:var(--ink-2);max-width:30ch}

/* ============================================================
   WHO WE SERVE
   ============================================================ */
/* Bottom padding leaves the sky the rooftops climb into — see .seam--houses. */
.serve{background:var(--surface-2);padding-block:var(--section-y);
  padding-bottom:calc(var(--section-y) + 10vw);
  border-top:1px solid var(--line)}
.serve__grid{display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1fr);
  gap:clamp(2.5rem,6vw,5rem);align-items:start}
.serve__intro p{margin-top:1.3rem;max-width:40ch}
.serve__aside{color:var(--ink-3);font-size:var(--step--1);padding-top:1.3rem;
  border-top:1px solid var(--line)}

.serve__list{display:grid;grid-template-columns:repeat(2,1fr);gap:.1rem 2rem}
.serve__list li{display:flex;align-items:center;gap:.8rem;padding-block:1.05rem;
  min-height:3.9rem;border-bottom:1px solid var(--line);color:var(--ink);
  font-size:var(--step-0)}
/* --rose, not --rose-deep. --rose-deep was chosen for its margin as *text* on
   cream (4.8:1) — this is a small graphical icon, where WCAG's bar is 3:1, not
   4.5:1. --rose measures 3.21:1 against --surface: the brightest token left in
   the palette that still clears the graphical minimum. --rose-lit (the next
   step up) measures 1.86:1 here — it's built for ink grounds and fails
   outright on cream. Don't reach past --rose for more brightness. */
.serve__list .ic{color:var(--rose);stroke-width:2.1}

/* ============================================================
   INCLUDED — back to ink
   ============================================================ */
.included{background:var(--ink);color:var(--on-ink-soft);padding-block:var(--section-y);
  position:relative}

/* Intro left, offerings right — the same asymmetric split .serve__grid,
   .founder__grid and .faq__grid already use. This section was the only one
   running a centred full-width stack, which is part of why it read as louder
   and looser than everything around it. */
.included__grid{display:grid;grid-template-columns:minmax(0,.72fr) minmax(0,1fr);
  gap:clamp(2.5rem,6vw,5rem);align-items:start}

/* The intro column is ~365px shorter than the six offerings beside it, which
   left the section's bottom-left corner visibly empty. Sticky spends that
   difference instead of padding it: the heading and the payment answer stay
   with you the whole way down the list. `align-self:start` from the grid above
   is what gives it room to travel — stretched to the row height it would have
   nothing to move within. Released below 920px, where the grid is one column
   and a pinned intro would just sit on top of the list. */
@media (min-width:921px){
  .included__intro{position:sticky;top:calc(74px + 2.5rem)}
}
.included__lede{margin-top:1rem;color:var(--on-ink-mute);max-width:34ch}

/* The offerings are a single-open accordion, not a grid of cards or a flat
   list. Six identical icon-heading-text blocks gave every item equal weight,
   so nothing led — this gives one item the floor at a time. Hairline rules
   carry the separation, matching the FAQ's own list treatment; there is no
   wrapping box or border-radius, which is what keeps it out of card territory. */
/* Gold, not the usual hairline — brightened on request so the rows read as
   more inviting to tap, not just structurally separated. Same gold as the
   payment block's rule above, so it stays the section's one consistent accent
   rather than a second, different gold. */
.included__list{border-top:1px solid var(--gold)}
.included__row{border-bottom:1px solid var(--gold)}

.included__q{width:100%;display:grid;grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;column-gap:1.15rem;padding-block:clamp(1.15rem,2.2vw,1.5rem);
  padding-inline:0;background:none;border:0;cursor:pointer;text-align:left;
  color:inherit;transition:color .3s var(--ease)}
.included__q:hover{color:var(--gold-lit)}
.included__q .ic--lg{grid-row:1 / span 2}
.included__q-text{display:flex;flex-direction:column;gap:.3rem}
.included__q-title{font-family:var(--font-display);font-weight:600;
  font-size:var(--step-1);color:var(--on-ink);letter-spacing:.01em}
.included__q-lead{font-size:var(--step--1);color:var(--on-ink)}

/* Closed rows still show icon + title + lead — only the expanded detail and
   highlights hide behind the click, so skimming never costs a visitor a
   benefit outright. Same drawn chevron as .faq__q::after, gold-lit for ink. */
/* One `transform`, not separate `rotate` + `translate`. Those are two
   independent transitions on two properties, and they were competing for
   frames with the panel's grid-template-rows animation — which is a *layout*
   animation on the main thread. Collapsing to a single composited transform
   and promoting it with will-change lets the chevron run on the compositor,
   so a busy main thread can no longer make it stutter.
   Order is load-bearing: the individual-property model applies translate
   before rotate, so `translate(...) rotate(...)` is the equivalent. Reversed,
   the chevron drifts ~2.1px diagonally. */
.included__q-toggle{flex:none;width:14px;height:14px;will-change:transform;
  border-right:1.6px solid var(--gold-lit);border-bottom:1.6px solid var(--gold-lit);
  transform:translate(0,-3px) rotate(45deg);
  transition:transform .4s var(--ease)}
.included__row.is-open .included__q-toggle{transform:translate(0,3px) rotate(225deg)}

/* grid-template-rows 0fr→1fr, not max-height — the FAQ's max-height transition
   is a known layout-thrash antipattern (flagged by the impeccable detector);
   this avoids it entirely and needs no scrollHeight read in JS. */
/* The ladder close. Two properties on two elements, offset by delay, so the
   open and close run in opposite orders:
     closing — the panel's bottom padding collapses first (.16s), so the
       hairline travels up to sit against the highlights line, and only then
       does the row height close (.45s, delayed .16s).
     opening — the row expands immediately, then the padding settles in
       underneath the text (.28s, delayed .2s).
   The delay lives on the *closed* rule, because that is the state being
   transitioned toward when a row closes. Reduced-motion is already covered by
   the blanket transition-duration override at the bottom of this file. */
.included__a{display:grid;grid-template-rows:0fr;
  transition:grid-template-rows .45s var(--ease) .16s}
/* Indent lives on the wrapper, not on `p:first-child` — that only ever
   aligned the detail paragraph. The highlights line is the *second* child, so
   it fell outside that rule entirely and sat flush at the row's left edge
   instead of under the title. Indenting the shared wrapper means every child
   lines up automatically, including whatever gets added next. */
.included__a > div{min-height:0;overflow:hidden;
  padding-left:calc(2rem + 1.15rem);padding-bottom:0;
  transition:padding-bottom .16s var(--ease)}
.included__row.is-open .included__a{grid-template-rows:1fr;transition-delay:0s}
/* Space between the last line of copy and the row's hairline, so the text
   isn't sitting on the rule. This is also the thing that animates in the
   ladder above — it is deliberately not a static padding. Only duration and
   delay are overridden here, so the `transition` shorthand stays declared
   once above rather than twice. */
.included__row.is-open .included__a > div{padding-bottom:1.35rem;
  transition-duration:.28s;transition-delay:.2s}
/* No max-width by default — the column is wide at desktop/tablet, and a 48ch
   cap left most of it empty next to a two- or three-line answer. Mobile gets
   its own narrower measure below, since a full-width line there is exactly
   the width doctrine already avoids everywhere else on the page. */
.included__a p{color:var(--on-ink);font-size:var(--step--1);
  line-height:1.62;padding-top:.15rem}
/* p.included__highlights, not .included__highlights alone — it needs to match
   .included__a p's specificity (class + element) or that rule's on-ink color
   wins regardless of source order, since both are single-class selectors
   otherwise and .included__a p carries an extra element selector. */
p.included__highlights{margin-top:1.15rem;font-style:italic;color:var(--gold-lit)}

/* Payment options — the intro column's lower half. Kept as flowing text, not a
   card or a price table: Paula presents one covered rate, never a base cost
   with line items added on. The gold rule above it is the only accent in the
   section, which is what makes it read as the answer rather than a footnote. */
.included__pay{margin-top:clamp(2.25rem,4vw,3rem);padding-top:1.6rem;
  border-top:2px solid var(--gold)}
.included__pay-title{font-size:var(--step-1);color:var(--gold-lit);letter-spacing:.01em}
.included__pay-lede{margin-top:.8rem;color:var(--on-ink-mute);font-size:var(--step--1)}
.included__pay-opts{margin-top:.9rem;display:grid;gap:.5rem;color:var(--on-ink)}
.included__pay-opts li{display:flex;align-items:baseline;gap:.65rem}
/* Drawn, not a bullet glyph — the page has no character bullets anywhere. */
.included__pay-opts li::before{content:"";flex:none;width:.4rem;height:.4rem;
  border:1px solid var(--gold-lit);transform:rotate(45deg);translate:0 -.15rem}
/* 1.35rem, not the .55rem this carried when the price line sat between it and
   the list — with that line gone the note now follows the list directly, and
   .55rem read as cramped rather than close. */
.included__pay-note{margin-top:1.35rem;color:var(--on-ink-mute);font-size:.82rem;
  line-height:1.55}

/* ============================================================
   FOUNDER
   ============================================================ */
/* The extra bottom padding is the sky the bridge rises into — the seam's art
   overflows ~15vw above its own band, and this is what keeps the crown clear
   of Paula's portrait. Keep the two numbers in step if either one moves. */
.founder{background:var(--surface);padding-block:var(--section-y);
  padding-bottom:calc(var(--section-y) + 16vw)}
.founder__grid{display:grid;grid-template-columns:minmax(0,.72fr) minmax(0,1fr);
  gap:clamp(2.5rem,6vw,5rem);align-items:center}

.founder__figure{margin:0;position:relative;background:
  radial-gradient(ellipse at 50% 78%,var(--rose-soft),transparent 68%)}
.founder__figure img{width:100%;filter:drop-shadow(0 26px 40px rgba(20,17,13,.22))}

.founder__quote{margin:1.75rem 0 0;padding-left:1.6rem;border-left:1px solid var(--gold);
  display:grid;gap:1.1rem;font-size:var(--step-1);line-height:1.5;color:var(--ink-2)}
.founder__sig{margin-top:1.75rem;font-family:var(--font-display);font-weight:600;
  color:var(--ink);letter-spacing:.05em;text-transform:uppercase;font-size:var(--step--1)}
.founder__sig span{display:block;margin-top:.3rem;font-family:var(--font-body);
  font-weight:400;text-transform:none;letter-spacing:0;color:var(--ink-3)}

/* The signature. Real text in Great Vibes, not an image or a traced path, so it
   stays selectable and swappable — a real traced signature can replace this one
   element later without touching anything else.

   Foil, not flat gold and not glitter: a three-stop gradient across the site's
   own gold tokens (deep → lit → soft → lit → deep) clipped to the glyphs, which
   is what reads as stamped metal. No sparkle, no noise texture — that would be
   the "glittery" this brief explicitly ruled out. This is the one deliberate
   gradient on a site that is otherwise flat-colour by doctrine; it is earned by
   the brief, and is not licence for gradients elsewhere.

   The writing motion is a mask sweeping left to right, so the script appears to
   be drawn in the direction it would actually be written. Masking rather than
   the checkmarks' stroke-dashoffset because this is live text, not an SVG path
   — same intent, the technique that fits the medium. */
/* Scoped `.founder__sig .founder__sign`, not `.founder__sign` alone: the sibling
   rule `.founder__sig span` is (0,1,1) and would otherwise win, dropping this
   back to Karla in body colour. Two classes beats one class plus an element. */
.founder__sig .founder__sign{display:block;margin-bottom:.15rem;
  /* Measured, not guessed: at 2.2rem the glyph run is ~206px inside a 288px
     column on a 320px phone, so it sits on one line with real headroom. (Do
     not size this off the span's own box — it is display:block, so its width
     is the column's, not the text's, and reading that number will make the
     signature look far tighter than it is.) */
  font-family:"Great Vibes",cursive;font-size:clamp(2.2rem,6.4vw,3.4rem);
  font-weight:400;text-transform:none;letter-spacing:.01em;line-height:1.15;
  /* Foil built for a CREAM ground, which means the shimmer lives in the DARK
     golds. Measured against --surface: gold-soft is 1.07:1 and gold-lit 1.59:1
     — both effectively the same luminance as the page, which is what blew the
     middle of the name out to nothing. Peak is --gold (2.89:1); the troughs are
     --gold-deep (4.44:1). Still reads as struck metal, but never washes out.
     If this ever moves onto an ink section, the ramp has to invert. */
  background:linear-gradient(100deg,
    var(--gold-deep) 0%, var(--gold-strong) 18%, var(--gold) 40%,
    var(--gold) 52%, var(--gold-strong) 74%, var(--gold-deep) 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  -webkit-mask-image:linear-gradient(90deg,#000 0 0);mask-image:linear-gradient(90deg,#000 0 0);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  /* Fully drawn by default. The hidden start state lives under .js below, so
     with JS off the signature simply renders complete instead of being masked
     to nothing forever — same fail-safe direction as every other reveal here. */
  -webkit-mask-size:100% 100%;mask-size:100% 100%}
.js .founder__sig .founder__sign{-webkit-mask-size:0% 100%;mask-size:0% 100%}

/* Written in once the founder block scrolls into view, riding the reveal system
   already on this section rather than adding a second trigger. 1.4s is slower
   than the site's UI motion on purpose — handwriting that snaps reads as a wipe,
   not as signing. */
/* Triggered by the SIGNATURE's own visibility, not the founder block's.
   `.founder__body` is tall and its reveal fires as soon as its top edge enters
   the viewport — but the signature sits near its bottom, so the writing ran and
   finished off-screen and nobody ever saw it. `.founder__sig` carries its own
   `reveal` class now, so the observer watches the signature itself.
   2.6s, not 1.4s: this is the one moment on the page meant to be watched rather
   than glanced at, and a hand does not move at UI speed. */
.js .founder__sig.is-in .founder__sign{animation:sign-in 2.6s var(--ease) .45s both}

@keyframes sign-in{
  from{-webkit-mask-size:0% 100%;mask-size:0% 100%}
  to{-webkit-mask-size:100% 100%;mask-size:100% 100%}
}

/* Visible name for assistive tech — the styled one is aria-hidden because a
   screen reader announcing a decorative script span adds nothing, but the name
   itself must still be readable. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

.founder__tags{display:flex;flex-wrap:wrap;gap:.55rem;margin-top:2rem}

/* Mission/Vision accordion inside the founder column. Reuses `.faq__list` /
   `.faq__item` wholesale — those classes are what `js/main.js` binds to, so this
   needs no new JS and inherits the same independent-toggle behaviour (correct
   here: these two are unrelated, unlike the offer list where opening one should
   close the others). Only the outer spacing is new. */
.founder__about{margin-top:2.25rem}

/* Sized up to Cinzel display, not body text. At the FAQ's default size these
   read as another paragraph and get skimmed past — the whole point is that
   someone actually stops and opens them. */
.founder__about .faq__q{font-family:var(--font-display);font-weight:600;
  font-size:var(--step-1);letter-spacing:.02em;color:var(--ink);
  padding-block:1.15rem;container-type:inline-size}
.founder__about .faq__item.is-open .faq__q{color:var(--gold-deep)}

/* The label slides to centre while open and back when closed.
   `calc(50cqw - 50%)` is the whole trick: 50cqw is half the BUTTON's width
   (container query unit), 50% is half the LABEL's own width — subtracting them
   lands the label dead centre with no JS measurement. Pure transform, so it
   composites and never touches layout; if a browser lacks container units the
   calc is simply invalid and the label stays left, which is the current design
   rather than a broken one. */
.founder__about .faq__q > span{display:inline-block;
  transition:transform .45s var(--ease)}
.founder__about .faq__item.is-open .faq__q > span{transform:translateX(calc(50cqw - 50%))}
.founder__tags li{font-size:var(--step--1);letter-spacing:.09em;text-transform:uppercase;
  color:var(--rose-deep);padding:.45rem .9rem;border:1px solid var(--line);border-radius:var(--radius-pill)}

/* ============================================================
   WAITLIST
   ============================================================ */
.waitlist{background:var(--ink);color:var(--on-ink-soft);padding-block:var(--section-y)}
.waitlist__grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.95fr);
  gap:clamp(2.5rem,6vw,5rem);align-items:start}
.waitlist__pitch p{margin-top:1.3rem;max-width:42ch}

/* Her flyer's own closing line. Brightened rather than enlarged — it should
   land as the last thing read before the form, without competing with the h2. */
.waitlist__step{color:var(--on-ink)}
.waitlist__assure{margin-top:2rem;display:grid;gap:.85rem}
.waitlist__assure li{display:flex;align-items:center;gap:.75rem;color:var(--on-ink)}
.waitlist__assure .ic{color:var(--gold-lit);stroke-width:2.1}

.waitlist__call{margin-top:2.25rem;padding-top:1.5rem;border-top:1px solid var(--line-ink);
  color:var(--on-ink-mute);font-size:var(--step--1)}
.waitlist__call a{display:inline-block;white-space:nowrap;color:var(--gold-lit);
  text-decoration:none;font-weight:var(--fw-bold);
  border-bottom:1px solid rgba(224,183,92,.4);padding-bottom:1px}
.waitlist__call a:hover{border-bottom-color:var(--gold-lit)}

.wform{background:var(--surface);color:var(--ink-2);padding:clamp(1.6rem,3.5vw,2.4rem);
  border-radius:var(--radius);box-shadow:var(--shadow-lg);display:grid;gap:1.15rem}
.wform__hp{position:absolute;left:-9999px}

.field{display:grid;gap:.4rem}
.field label{font-size:var(--step--1);font-weight:var(--fw-bold);color:var(--ink);
  letter-spacing:.03em}
.field__opt{font-weight:400;color:var(--ink-3)}
/* aria-hidden in the markup: the `required` attribute already makes screen
   readers announce the field, so voicing the asterisk too would double it. */
.field__req{color:var(--rose-deep);margin-left:.2em;font-weight:var(--fw-body)}
.field input,.field select{width:100%;padding:.85rem .95rem;background:var(--surface-2);
  border:1px solid var(--line);border-radius:var(--radius-sm);color:var(--ink);
  transition:border-color .3s var(--ease),background .3s var(--ease)}
.field input:hover,.field select:hover{border-color:var(--gold)}
.field input:focus,.field select:focus{border-color:var(--gold-deep);background:#fff}
.field input::placeholder{color:var(--ink-3)}
.field select{appearance:none;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B6055' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right .8rem center;background-size:1.1rem;
  padding-right:2.6rem}
.field__help{font-size:var(--step--1);color:var(--ink-3);line-height:1.5}
.field input:user-invalid,.field select:user-invalid{border-color:var(--rose-deep)}

.wform .btn{margin-top:.35rem}
.wform__fine{font-size:var(--step--1);color:var(--ink-3);text-align:center;line-height:1.5}

/* The confirmation rises in rather than teleporting. This is the one moment on
   the page that earns it: someone has just handed over their name and phone
   and pressed submit, and it happens exactly once per visitor.

   Fail-safe by construction — the visible state is the DEFAULT, and JS applies
   the transparent "from" state for one frame before releasing it (see
   js/main.js). The inverse (hidden by default, revealed by adding a class)
   would leave the confirmation permanently invisible if that class ever failed
   to land, which on this form would mean a visitor believing their signup
   vanished. Never invert this. */
.wform__done{background:var(--surface);color:var(--ink-2);padding:clamp(1.8rem,4vw,2.6rem);
  border-radius:var(--radius);box-shadow:var(--shadow-lg);border-top:3px solid var(--gold);
  transition:opacity .5s var(--ease),transform .5s var(--ease)}
.wform__done.is-entering{opacity:0;transform:translateY(12px);transition:none}
.wform__done h3{font-size:var(--step-2);color:var(--gold-deep)}
.wform__done p{margin-top:.9rem}
.wform__done a{color:var(--gold-deep);font-weight:var(--fw-bold)}

/* ============================================================
   FAQ
   ============================================================ */
.faq{background:var(--surface);padding-block:var(--section-y)}
/* Two columns for the same reason .serve__grid has them: a 760px accordion
   inside an 1140px container left a third of the row empty, and stretching the
   rows instead would strand every chevron a screen away from its label. The
   heading takes the space, and below 920px this collapses to heading-then-list,
   which is exactly the single-column stack it already had. */
.faq__grid{display:grid;grid-template-columns:minmax(0,.72fr) minmax(0,1fr);
  gap:clamp(2.5rem,6vw,5rem);align-items:start}
.faq .h2{max-width:18ch}
.faq__aside{margin-top:1.3rem;max-width:30ch;color:var(--ink-3);font-size:var(--step--1);
  line-height:1.6}
.faq__aside a{color:var(--gold-deep);text-decoration:none;font-weight:var(--fw-bold);
  border-bottom:1px solid rgba(179,129,47,.4);padding-bottom:1px;white-space:nowrap}
.faq__aside a:hover{border-bottom-color:var(--gold-deep)}
/* The grid column governs the width now; the top margin only applies once the
   columns stack and the list sits under the heading rather than beside it. */
.faq__list{border-top:1px solid var(--line)}
.faq__item{border-bottom:1px solid var(--line)}
.faq__q{width:100%;display:flex;align-items:center;justify-content:space-between;gap:1.5rem;
  padding:1.4rem 0;background:none;border:0;cursor:pointer;text-align:left;
  font-family:var(--font-body);font-size:var(--step-1);font-weight:var(--fw-medium);
  color:var(--ink);line-height:1.35;transition:color .3s var(--ease)}
/* Same single-transform + compositing treatment as the offer chevron above —
   see that comment for why, including the translate-before-rotate ordering. */
.faq__q::after{content:"";flex:none;width:14px;height:14px;will-change:transform;
  border-right:1.6px solid var(--gold-deep);border-bottom:1.6px solid var(--gold-deep);
  transform:translate(0,-3px) rotate(45deg);
  transition:transform .4s var(--ease)}
.faq__q:hover{color:var(--gold-deep)}
.faq__item.is-open .faq__q::after{transform:translate(0,3px) rotate(225deg)}
/* Same grid-template-rows technique as the offer accordion, for the same
   reasons: max-height is a layout-thrash pattern, and it needs a hardcoded
   ceiling that silently clips the panel the moment an answer grows past it.
   This has no ceiling and needs no scrollHeight read in JS. The FAQ stays
   multi-open (each item toggles independently) — that difference from the
   single-open offer list is deliberate, not an oversight. */
.faq__a{display:grid;grid-template-rows:0fr;
  transition:grid-template-rows .45s var(--ease)}
/* The bottom spacing lives on the paragraph, NOT on this wrapper. As padding
   on the wrapper it sits outside the collapsed content box, so a closed panel
   measured 24px instead of 0 and every item carried a phantom gap. Inside the
   clipped area it disappears with the rest. */
.faq__a > div{min-height:0;overflow:hidden}
.faq__item.is-open .faq__a{grid-template-rows:1fr}
.faq__a p{max-width:62ch;color:var(--ink-2);padding-bottom:1.5rem}

/* ============================================================
   FOOTER
   ============================================================ */
.foot{background:var(--ink);color:var(--on-ink-mute);padding-top:var(--section-y)}
.foot__grid{display:grid;grid-template-columns:minmax(0,1.25fr) repeat(2,minmax(0,1fr));
  gap:clamp(2rem,5vw,4rem)}
.foot__brand img{width:clamp(150px,17vw,210px)}
.foot__line{margin-top:1.2rem;color:var(--on-ink-soft);font-size:var(--step--1);max-width:26ch}
.foot__col h3{font-size:var(--step--1);letter-spacing:var(--tracking-caps);
  text-transform:uppercase;color:var(--gold-lit);margin-bottom:1.1rem}
.foot__col p{margin-bottom:.35rem}
.foot__col a{display:inline-block;padding-block:.5rem;color:var(--on-ink-soft);
  text-decoration:none;transition:color .3s var(--ease)}
.foot__col a:hover{color:var(--gold-lit)}
/* The state mark sits beside the service line rather than above it, so the
   footer gains a graphic without gaining a row of height. Gold fill, no stroke
   — it is a silhouette, matching how her own flyer uses it. */
.foot__area{margin-top:1.2rem;font-size:var(--step--1);line-height:1.6;
  display:flex;align-items:center;gap:.8rem}
.foot__state{flex:none;width:auto;height:2.9rem;fill:var(--gold);stroke:none}

.foot__base{margin-top:clamp(3rem,6vw,4.5rem);padding-block:1.6rem;
  border-top:1px solid var(--line-ink);display:flex;flex-wrap:wrap;gap:.5rem 1.5rem;
  justify-content:space-between;font-size:var(--step--1)}

/* ============================================================
   REVEAL
   ============================================================ */
.js .reveal{opacity:0;transform:translateY(22px);
  transition:opacity .85s var(--ease),transform .85s var(--ease)}
.js .reveal.is-in{opacity:1;transform:none}

/* Grouped items arrive in sequence rather than as one flat block. The list
   itself stops animating so the two passes don't compound; the children carry
   the whole motion, and the delay rides on the list's own reveal so nothing
   waits on a timer the visitor has already scrolled past. */
.js .values.reveal,
.js .serve__list.reveal,
.js .included__list.reveal{opacity:1;transform:none;transition:none}
.js .values li,
.js .serve__list li,
.js .included__list li{opacity:0}
.js .values.is-in li,
.js .serve__list.is-in li,
.js .included__list.is-in li{animation:seq .8s var(--ease) both}
.js .values li:nth-child(2),
.js .serve__list li:nth-child(2),
.js .included__list li:nth-child(2){animation-delay:.07s}
.js .values li:nth-child(3),
.js .serve__list li:nth-child(3),
.js .included__list li:nth-child(3){animation-delay:.14s}
.js .serve__list li:nth-child(4),
.js .included__list li:nth-child(4){animation-delay:.21s}
.js .serve__list li:nth-child(5),
.js .included__list li:nth-child(5){animation-delay:.28s}
.js .serve__list li:nth-child(6),
.js .included__list li:nth-child(6){animation-delay:.35s}
.js .serve__list li:nth-child(7){animation-delay:.42s}
.js .serve__list li:nth-child(8){animation-delay:.49s}

@keyframes seq{
  from{opacity:0;transform:translateY(14px)}
  to{opacity:1;transform:none}
}

/* The checkmarks draw themselves in, each one after its own row has settled.
   Not decoration: a check being *drawn* reads as confirmation, and this list's
   whole job is telling a visitor they belong on it. Sequential, so it lands as
   one affirmation after another rather than eight things twitching at once.

   23 is the path's real length, not a guess: `m4 12.5 5 5L20 6.5` is two
   segments, sqrt(50) + sqrt(242) = 7.071 + 15.556 = 22.627, rounded up so no
   stray dot shows at full offset. If this icon is ever swapped, recompute it.
   Plain dasharray/dashoffset in the path's own units — deliberately not the
   pathLength="1" trick, which Safari only supports from v16 and would need a
   fallback. Scoped to .js like every other reveal here, so with JS off the
   checks simply render fully drawn and static. */
.js .serve__list .ic{stroke-dasharray:23;stroke-dashoffset:23}
.js .serve__list.is-in .ic{animation:draw-check .5s var(--ease) both}
/* Offset .15s past each row's own delay, reusing the same ladder above rather
   than inventing a second one: the row arrives, then its check ticks in. */
.js .serve__list.is-in li:nth-child(1) .ic{animation-delay:.15s}
.js .serve__list.is-in li:nth-child(2) .ic{animation-delay:.22s}
.js .serve__list.is-in li:nth-child(3) .ic{animation-delay:.29s}
.js .serve__list.is-in li:nth-child(4) .ic{animation-delay:.36s}
.js .serve__list.is-in li:nth-child(5) .ic{animation-delay:.43s}
.js .serve__list.is-in li:nth-child(6) .ic{animation-delay:.50s}
.js .serve__list.is-in li:nth-child(7) .ic{animation-delay:.57s}
.js .serve__list.is-in li:nth-child(8) .ic{animation-delay:.64s}

@keyframes draw-check{
  from{stroke-dashoffset:23}
  to{stroke-dashoffset:0}
}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width:920px){
  .serve__grid,.founder__grid,.waitlist__grid,.faq__grid{grid-template-columns:1fr}
  /* Stacked, the list needs its own breathing room back. */
  .faq__grid{gap:0}
  .faq__list{margin-top:clamp(2.25rem,4.5vw,3.25rem)}
  .founder__figure{max-width:380px;margin-inline:auto}
  /* Stacked, the offer list needs the same breathing room the FAQ gets. */
  .included__grid{grid-template-columns:1fr;gap:0}
  .included__list{margin-top:clamp(2.25rem,4.5vw,3.25rem)}
  .foot__grid{grid-template-columns:1fr 1fr}
  .foot__brand{grid-column:1 / -1}
}

/* The full link set plus the CTA needs real room; below this the nav collapses
   rather than letting every label wrap onto three lines.

   This number is DERIVED, not chosen — **re-measure it whenever a nav link is
   added or removed, or a label is reworded.** Method: force every label to
   `white-space:nowrap`, then measure brand width + a 24px minimum gap + the
   summed link row (including the CTA and the flex column-gap). Add the two
   40px gutters to get the viewport the bar needs.

   History, because it shows how much this moves: five links + CTA needed a
   ~968px container / **1048px viewport** (measured at 1280, which slightly
   overstates it since `--step--1` grows with the viewport). A sixth link
   ("Products", added when this site briefly had its own store page) pushed
   that to 1004px / 1084px, and the breakpoint went to 1100 to match. That page
   was retired in favour of Paula's own site, the link came out, and this came
   back down — left at 1100 it would have hidden a perfectly good desktop nav
   on every 1050–1100px screen for no reason.

   Current: six links + CTA need a 1087px container / **1167px viewport**. The
   "Blueprint Hub" link alone accounts for ~119px of that — it took the figure
   from 1048 back up to 1167, which is the honest price of a seventh item and
   the reason to think twice before adding an eighth.

   1180 clears the measured 1167 with margin. Verified empirically at 1181px:
   nothing wraps, `.nav__inner` does not overflow. Re-verify the same way if you
   change this. */
@media (max-width:1180px){
  .nav__toggle{display:flex}
  .nav{background:var(--ink)}
  /* Put the three bars on their own compositor layers. Their fade and rotate
     are cheap, but they were being frozen mid-animation by the panel's first
     paint happening on the same thread — the middle bar visibly hung at full
     opacity for around a second before snapping away, while closing (nothing
     new to paint) stayed smooth. Composited, the bars keep animating even
     when the main thread is busy. Three 2px elements, so the layer cost is
     negligible, and this only applies where the toggle actually exists. */
  .nav__toggle span{will-change:transform,opacity}
  /* Fades and slides rather than snapping from display:none. The snap forced
     a full layout + paint of the panel — six rows, their borders, and a 40px
     blur shadow — in the very frame the X starts rotating, and that paint
     spike is what made the toggle animation stutter. Staying in the layer
     tree and animating opacity/transform keeps it on the compositor, so the
     two animations stop competing.

     `visibility` is what still removes it properly when closed: opacity alone
     would leave the links tabbable and announced by screen readers. Its
     transition is delayed by the panel's own duration on close so it hides
     only after the fade finishes, and zeroed on open so it shows instantly.
     The panel is absolutely positioned, so none of this affects page flow. */
  .nav__links{position:absolute;inset:100% 0 auto 0;flex-direction:column;
    align-items:stretch;gap:0;padding:.5rem var(--gutter) 1.5rem;
    background:var(--ink);border-top:1px solid var(--line-ink);
    /* 40px of blur across the full viewport width was the expensive part of
       this panel's first paint, and blur cost scales with the blurred area.
       Halved. On an ink panel over an ink page the shadow was doing very
       little visual work anyway — it reads as a soft edge either way. Keep
       transition-time blur small; this element animates. */
    box-shadow:0 12px 20px rgba(0,0,0,.4);
    opacity:0;visibility:hidden;transform:translateY(-8px);
    transition:opacity .22s var(--ease),transform .28s var(--ease),
               visibility 0s linear .28s}
  .nav.is-open .nav__links{opacity:1;visibility:visible;transform:none;
    transition:opacity .22s var(--ease),transform .28s var(--ease),
               visibility 0s linear 0s}
  /* Centred to match the rest of the panel's axis, not for symmetry's own
     sake: the mark above it is centred (see the header note below) and the
     CTA below it centres its own label, so left-aligned links were the only
     thing off-axis in an otherwise centred column. Safe here where it isn't
     elsewhere — five one-or-two-word labels in a short overlay, not the
     who-we-serve checklist or the form. The rows stay full-width, so only the
     text moves; the tap target is unchanged. */
  .nav__links a{padding-block:.95rem;border-bottom:1px solid var(--line-ink);
    font-size:var(--step-0);text-align:center}
  .nav__links .btn{margin-top:1rem;border-bottom:0}
}

/* On phones the bar carries the mark alone, centred, with the menu on the
   right. The wordmark goes: it repeats the mark's own lettering and cost more
   width than it earned. The ::before is a phantom the width of the toggle, so
   the mark centres on the bar itself rather than on the space left over beside
   the button. The link keeps its aria-label, so the name is still announced. */
@media (max-width:760px){
  .nav__inner::before{content:"";flex:none;width:44px}
  .nav__brand{margin-inline:auto}
  .nav__word{display:none}
  .nav__mark{width:52px}
}

@media (max-width:760px){
  .values{grid-template-columns:1fr}
  .values li{padding:1.75rem 0;border-bottom:1px solid var(--line)}
  .values li + li{border-left:0;padding-left:0}
  .values li:last-child{border-bottom:0;padding-bottom:0}

  .serve__list{grid-template-columns:1fr;gap:0}
  .founder__quote{padding-left:1.15rem}
}

@media (max-width:760px){
  .seam{--drift:34px}

  /* The bridge is drawn in hairlines — squeezed to phone width the cables
     disappear and the seam reads as an empty band. Zoom the art past the
     viewport and anchor it left so the tower and the near span stay legible;
     the far end of the deck runs off-screen, which is how you'd see it
     standing under it anyway. */
  .seam--bridge{aspect-ratio:auto;height:38vw}
  .seam--bridge .seam__art{width:170%;max-width:none}
  .founder{padding-bottom:calc(var(--section-y) + 40vw)}

  /* The houses lose their shape at phone width for the same reason. Anchor
     this one right — the homes sit at that end of the scene, so the hill
     runs off the left edge instead of the rooftops running off the right. */
  .seam--houses{aspect-ratio:auto;height:30vw}
  .seam--houses .seam__art{width:150%;max-width:none;left:auto;right:0}
  .serve{padding-bottom:calc(var(--section-y) + 34vw)}
}

/* ------------------------------------------------------------
   Mobile: copy centres by default. Once the columns collapse to
   one, a single centred axis holds the page together far better
   than everything hugging the left edge.

   Three blocks opt out, and they are all lists or long-form
   reading where a centred ragged edge actively hurts: the
   who-we-serve checklist, Paula's quote, and the waitlist
   assurances. Anything with a max-width also needs its auto
   margins back, or it centres its text inside a box that is
   still pinned left.
   ------------------------------------------------------------ */
@media (max-width:760px){
  .promise,
  .serve,
  .included,
  .founder,
  .waitlist,
  .foot{text-align:center}

  /* The FAQ centres its heading only — see the accordion note below. */
  .faq .h2,
  .faq__aside{text-align:center}

  /* Constrained blocks have to recentre their BOX, not just their text.
     `text-align` centres the words inside the box; a max-width box with
     default margins still sits hard against the left edge, so the narrower
     the block the further off-centre it reads (the 30ch value copy was the
     worst of it). Scoped to the sections rather than listed selector by
     selector — a per-element list is how the first pass missed four of
     them, and it would miss the next one somebody adds. */
  .promise :is(p, .lede),
  .included :is(p, .lede),
  .serve__intro :is(p, .lede),
  .waitlist__pitch p,
  .faq :is(.h2, .faq__aside),
  .foot :is(p, .foot__line){margin-inline:auto}

  /* Centred on mobile, on request — icon, title, lead, and chevron stack in
     one column instead of running left-aligned across a narrow row. Generous
     padding-inline so rows read as roomy, not flush against the edges. */
  .included__q{grid-template-columns:1fr;justify-items:center;row-gap:.5rem;
    column-gap:0;padding:1.75rem 1.25rem;text-align:center}
  .included__q .ic--lg{grid-row:auto;width:1.9rem;height:1.9rem}
  /* Stacked, the chevron sat tight under the lead line (.15rem) with the
     button's full 1.75rem below it — crowded above, roomy below. One static
     value evens that out in both states; deliberately NOT animated between
     open and closed, since transitioning margin is the layout-thrash pattern
     the detector flags, and a shorthand `transition` here would also silently
     wipe out the chevron's own rotate/translate animation from the base rule. */
  .included__q-toggle{margin-top:1.2rem}
  .included__a{padding-inline:1.25rem}
  .included__a > div{padding-left:0}
  .included__a p{text-align:center}

  .included__pay-opts{justify-items:center}
  .founder__tags{justify-content:center}
  .foot__brand img{margin-inline:auto}
  /* .foot__area is a flex row, so the section's text-align:center does not
     centre it — flex needs its own axis instruction. */
  .foot__area{justify-content:center;text-align:left}
  .foot__base{gap:.75rem}

  /* The accordion stays left, heading aside. Centring it is not stable: the
     label carries its own `text-align:left`, so a centred flex row only looks
     centred while the question fits one line — the moment one wraps it fills
     the width and snaps left, and the section reads half-and-half. Alignment
     that depends on question length breaks the next time the copy is edited.
     Left also keeps the chevron pinned in one predictable place to tap. */

  /* The form opts out of the section's centring entirely. It is a thing to
     operate, not to read: labels sit on the same left axis as the text the
     visitor types under them, which is what makes a stack of fields scannable.
     Centred labels over left-aligned inputs is the mismatch you feel here. */
  .wform{text-align:left}

  /* --- the four opt-outs ------------------------------------- */
  .serve__list li,
  .waitlist__assure li{text-align:left;justify-content:flex-start}
  .founder__quote{text-align:left}
  /* The offer accordion centres on mobile, unlike the FAQ (still the fourth
     opt-out at every other width — see the accordion's own rules above for
     why left-aligned is still correct at ≥761px). The constrained detail
     paragraph needs its auto-margin back so its box centres, not just its
     text. */
  .included__a p{max-width:38ch;margin-inline:auto}
}

@media (max-width:520px){
  .hero{padding-top:clamp(6rem,20vh,8rem)}
  .hero__mark{width:92%}
  .hero__actions{flex-direction:column;align-items:stretch;width:100%}
  .hero__actions .btn{width:100%}
  .foot__grid{grid-template-columns:1fr}
  .foot__base{flex-direction:column}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important}
  .js .reveal{opacity:1;transform:none}
  .js .values li,
  .js .serve__list li,
  .js .included__list li{opacity:1;animation:none}
  /* Keep the end state, drop the drawing — without this the blanket
     animation-duration override above would leave every check frozen at full
     dashoffset, i.e. invisible. */
  .js .serve__list .ic{stroke-dashoffset:0;animation:none}
  /* Signature lands fully written, no sweep — same rule as the checkmarks:
     keep the end state, drop the motion. Without this the blanket duration
     override would freeze it mid-sweep, i.e. half a name on screen. */
  .js .founder__sig .founder__sign{-webkit-mask-size:100% 100%;mask-size:100% 100%;animation:none}
  /* Label stays put; the accordion still opens, just without the slide. */
  .founder__about .faq__item.is-open .faq__q > span{transform:none}
  /* The blanket rule above kills transition-duration but not delay, which
     would leave the closed nav panel invisible-but-tabbable for 280ms. */
  .nav__links{transition-delay:0s !important}
  .hero__video{display:none} /* the still carries the mark */
  /* Park the horizons at the sealed end of their travel. */
  .seam__art{transform:translate3d(0,calc(var(--drift) * -1),0) !important}
}


/* ---------- Cross-band ----------
   One band, on index.html only, and the single pointer to the product line.
   It sends a would-be *provider* off to Paula's separate brand (The Shared
   Living Blueprint Hub) on its own domain. It sits last on the page on purpose:
   the products sell to the opposite audience from the housing this site is for,
   so anywhere higher it would put a contradictory ask in front of someone who
   came here needing a home.

   White, not the lifted-paper --surface-2 it shipped with first: at #FAF5EE
   against the #F3EADD above it, the band read as a stray piece of that section
   rather than its own thing. It follows a cream section and precedes the ink
   footer, so ink here would fuse with the footer and read as chrome rather than
   an offer. White is the one ground neither neighbour uses.

   (An .ink variant existed while a second page carried this component in the
   opposite direction. That page was retired in favour of Paula's own site, and
   the variant went with it rather than sitting here unused.) */
.crossband{background:var(--surface-w);color:var(--ink-2);
  padding-block:clamp(3rem,6vw,4.5rem);border-top:1px solid var(--line)}
/* Centred, unlike the rest of the page's asymmetric sections. This is a closing
   invitation rather than content to read alongside something else, and it is
   the last thing before the footer — centring gives it the same "square on the
   page" role the Promise lead has at the top, so the page opens and closes the
   same way. */
.crossband__inner{max-width:var(--container-narrow);margin-inline:auto;text-align:center}
.crossband__h{color:var(--ink);max-width:20ch;margin-inline:auto}
/* The Hub's name, set in gold as its own line — it is the proper noun the
   heading is asking about, and giving it a line makes the destination legible
   before anyone reaches the button. --gold-deep because gold is never running
   text on a light ground (2.9:1); this measures 5.30:1 on white. */
.crossband__lead{margin-top:.9rem;font-family:var(--font-display);
  font-weight:var(--fw-display);font-size:var(--step-1);color:var(--gold-deep);
  letter-spacing:.01em}
.crossband__body{margin:1.35rem auto 0;max-width:52ch;color:var(--ink-2)}
.crossband .btn{margin-top:2rem}
/* Deliberately quiet and last: it exists to keep the two businesses distinct,
   not to sell. --ink-3 measures 5.14:1 on cream and higher on white. */
.crossband__note{margin:2rem auto 0;max-width:60ch;font-size:.82rem;
  line-height:1.6;color:var(--ink-3)}
/* .btn--ghost is authored for ink (light label, gold-tinted border); on this
   white ground that would be --on-ink text on paper, i.e. invisible. */
.crossband .btn--ghost{color:var(--gold-deep);border-color:rgba(143,99,24,.45)}
/* This rest-state rule is (0,2,0) and ties with the generic `.btn--ghost:hover`,
   so source order alone decided it — and at one point the gold filled in on
   hover while the label stayed gold-on-gold and vanished. Restated explicitly
   rather than left to ordering. **It must stay inside the hover query**:
   `:hover` still matches on a touch tap, and out here it would strip the
   label's colour on a phone and drop it to `.btn--ghost`'s cream on white. */
@media (hover:hover) and (pointer:fine){
  .crossband .btn--ghost:hover{color:var(--ink);border-color:var(--gold)}
}
/* On touch there is no hover, so the gold fill above never fires and this
   button would sit as an outline forever — the one state it was never designed
   to rest in. Where hover does not exist it ships solid instead: the same
   `--gold` fill with `--ink` text (5.47:1) the fill animates *to*, so the two
   input models end at the same place rather than one getting a lesser button.

   Scoped to `hover:none` rather than a width breakpoint because that is the
   actual condition — a narrow desktop window still has a cursor. And scoped to
   the cross-band, NOT `.btn--ghost` generally: the hero's ghost sits beside the
   gold "Join the waitlist", where the outline is what marks it secondary.
   Filling that one would flatten the hero's hierarchy. This button is alone in
   its section, so it has no hierarchy to lose. */
@media (hover:none){
  .crossband .btn--ghost{background:var(--gold);color:var(--ink);
    border-color:var(--gold)}
}


/* The band is centred at every width now, so there is no row to stack — only
   the button needs to become a full-width tap target. */
@media (max-width:760px){
  .crossband .btn{width:100%;max-width:22rem}
}
