/* ==========================================================================
   LVLUP Counseling — site styles
   Built to match full-site.png (1504px Sketch canvas).
   Brand values live in tokens.css. Do not hard-code a hex in this file.
   ========================================================================== */

*,*::before,*::after{box-sizing:border-box}
/* scroll-behavior is deliberately NOT set here. On the root it applies to every
   scroll of the document, including the ones the browser performs itself —
   history scroll restoration above all. Closing the SimplePractice modal hands
   the position back through a restore, and with smooth on <html> that restore
   ANIMATED: the page snapped to the top and then raced back down to where you
   were. Anchor smoothing now lives in site.js, scoped to clicks on our own
   links, so browser-initiated scrolls are instant again.
   scroll-padding-top stays: scrollIntoView honours it, which is what keeps a
   section clear of the fixed header. */
html{-webkit-text-size-adjust:100%;scroll-padding-top:var(--header-h)}
body{
  margin:0;background:var(--white);color:var(--plum);
  font-family:var(--body);font-size:18px;line-height:1.4;
  -webkit-font-smoothing:antialiased;
}
img{max-width:100%;display:block}
a{color:inherit}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--gutter)}

h1,h2,h3{font-family:var(--display);font-weight:800;letter-spacing:-.025em;line-height:1.05;margin:0}

.skip{position:absolute;left:-9999px;background:var(--white);color:var(--plum);padding:.8rem 1.2rem;z-index:200}
/* Visible to screen readers only. Used to announce that a link opens a new tab,
   which is otherwise a silent context change. clip-path rather than the old
   clip:rect() hack, and white-space:nowrap so the text cannot wrap into view. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;
}
.skip:focus{left:0;top:0}
:focus-visible{outline:3px solid var(--terra);outline-offset:3px;border-radius:3px}

/* ---------- eyebrow ------------------------------------------------------ */
.eyebrow{
  font-family:var(--display);font-weight:700;font-size:13px;
  letter-spacing:.08em;text-transform:uppercase;margin:0 0 18px;
  display:flex;align-items:center;gap:9px;
}
.eyebrow::before{content:"+";font-weight:700}
.eyebrow--center{justify-content:center}

/* ---------- buttons ------------------------------------------------------ */
/* Hover has two parts: the face colour rises from the bottom edge, and the
   arrow bounces out and back.

   The wipe is a ::before at z-index:-1. That specific value is what puts it
   ABOVE the button's own background but BENEATH the label: a negative-z child
   paints after the element's background and before its inline content. The
   label is a bare text node inside the <a> and cannot be given a z-index of
   its own, so this is what avoids a markup change across all four buttons.
   `isolation:isolate` stops that -1 escaping the button and sliding behind an
   ancestor's background; `overflow:hidden` makes it respect the corner radius.

   scaleY from a bottom origin rather than a translate or a height: a
   transform-only animation, composited, never touching layout. */
.btn{
  position:relative;isolation:isolate;overflow:hidden;
  display:inline-flex;align-items:center;gap:14px;
  font-family:var(--display);font-weight:700;font-size:15px;
  text-decoration:none;border-radius:var(--radius-sm);
  padding:5px 5px 5px 16px;
  transition:color var(--dur-wipe) var(--ease),
             box-shadow var(--dur-hover) var(--ease);
}
.btn::before{
  content:"";position:absolute;inset:0;z-index:-1;
  background:var(--btn-hover-face, transparent);
  transform:scaleY(0);transform-origin:50% 100%;
  transition:transform var(--dur-wipe) var(--ease-wipe);
}
.btn:hover::before,
.btn:focus-visible::before{transform:scaleY(1)}
/* NO transform on hover, and this is not a style preference — a lift here can
   only ever apply to some of the buttons, so it guarantees an inconsistency.

   Measured before removing it: the header button lifted -2px, while the hero
   and contact buttons did not move at all. Two separate higher-priority
   declarations were already claiming `transform` on those two:
     - the hero CTA runs `heroIn` with `animation-fill-mode:forwards`, and a
       filling animation outranks any normal declaration in the cascade;
     - the contact CTA carries `.js [data-reveal].is-in{transform:none}`, which
       at 0,3,0 outranks `.btn:hover` at 0,2,0.
   So the lift only ever worked on the one button that is neither revealed nor
   inside the hero. If a lift is ever wanted, it has to move to a wrapper
   element that owns its own transform — putting it back here will silently
   reproduce exactly this bug. The wipe and the arrow carry the hover. */
.btn:hover{box-shadow:0 8px 20px rgba(29,22,56,.20)}
.btn:active{box-shadow:none;transition-duration:.07s}

/* Draws back to the left, then travels right to rest — so the motion the eye
   follows runs the way the arrow points. Deliberately no overshoot past rest:
   an overshoot would end on a leftward correction, which is the thing this
   shape exists to avoid.

   The two halves get their own easing. The retreat is quick and takes 30% of
   the run; the rightward return takes the remaining 70% on a long-tail curve,
   which is what makes the forward travel the dominant movement rather than
   just a return to baseline. */
@keyframes btn-arrow{
  0%  {transform:translateX(0);    animation-timing-function:cubic-bezier(.4,0,.7,1)}
  30% {transform:translateX(-6px); animation-timing-function:var(--ease-out)}
  100%{transform:translateX(0)}
}
.btn:hover .btn__icon svg,
.btn:focus-visible .btn__icon svg{animation:btn-arrow var(--dur-arrow) var(--ease)}

/* The chip holds its resting colours through the hover — Gregory's call. The
   wipe and the arrow carry it; recolouring the chip as well made three things
   move at once. */
/* The chip sets the button's height: chip + 5px of padding top and bottom.
   26px chip -> 36px button, down from 40px. Trimmed here rather than off the
   padding, which keeps the 5px frame and so the original proportion between
   chip and button — dropping the padding to 3px instead would hit the same
   36px but leave the chip almost touching the edges. */
.btn__icon{
  display:grid;place-items:center;width:26px;height:26px;
  border-radius:5px;flex:none;
}
.btn__icon svg{width:15px;height:15px}

/* Hover faces are chosen so the LABEL COLOUR PASSES IN BOTH STATES, which is
   what rules out the obvious move of wiping to terracotta: white on terra is
   2.86, purple on terra is 2.15. Measured, not estimated:
       white       on purple  6.14  ->  white       on plum   17.16
       purple      on white   6.14  ->  purple-deep on blush   5.27
   The white button's label darkens to purple-deep as its face warms, because
   plain purple on blush is 4.18 and this label is 15px/700 — not large text,
   so the 3.0 large-text floor does not apply to it.

   The chips keep their resting fills over the hover face: white chip on plum
   and purple chip on blush both stay plainly legible. */
.btn--purple{background:var(--purple);color:var(--white);--btn-hover-face:var(--plum)}
.btn--purple .btn__icon{background:var(--white);color:var(--purple)}

.btn--white{background:var(--white);color:var(--purple);--btn-hover-face:var(--blush)}
.btn--white:hover,
.btn--white:focus-visible{color:var(--purple-deep)}
.btn--white .btn__icon{background:var(--purple);color:var(--white)}

/* The contact-form buttons also carry .spwidget-button, which is only a hook
   for the SimplePractice script to find them — it is given no styles here on
   purpose. Their embed ships a stylesheet for that class (blue border, 14px,
   6px padding) that we do not paste into the pages, so .btn below is the only
   thing painting these buttons. Do not "defend" against that stylesheet with
   a .spwidget-button rule: a.spwidget-button is (0,1,1) and outranks .btn at
   (0,1,0), so such a rule overrides the real styles rather than a phantom. */

/* ---------- header ------------------------------------------------------- */
/* Fixed, not absolute, so the CTA stays reachable. Over the hero the bar is
   transparent (the scrim already carries the contrast); once the header clears
   the hero, site.js adds .is-stuck and a plum bar fades in behind it, because
   blush and white marks measure 1.6 and 1.0 against the cream and white
   sections below. The bar is a ::before rather than a background on the header
   so it can cross-fade without the padding transition fighting it. */
.site-header{
  position:fixed;inset:0 0 auto 0;z-index:100;padding-top:28px;padding-bottom:0;
  transition:padding var(--dur-hover) var(--ease);
}
.site-header::before{
  content:"";position:absolute;inset:0;z-index:-1;
  background:var(--plum);opacity:0;
  box-shadow:var(--header-shadow);
  transition:opacity var(--dur-hover) var(--ease);
}
.site-header.is-stuck{padding-top:14px;padding-bottom:14px}
.site-header.is-stuck::before{opacity:1}
/* On the plum bar the translucent nav pill reads as a smudge, so drop it. */
.site-header.is-stuck .nav,
.site-header.is-stuck .nav__toggle{
  transform:translateY(calc(var(--logo-w) * var(--logo-scale) * -0.0474));
}
.site-header.is-stuck .nav{background:transparent;backdrop-filter:none}
.site-header .wrap{display:flex;align-items:center;justify-content:space-between;gap:32px}
.site-header__logo{display:block;flex:none}
/* The logo condenses with the bar. Animating WIDTH rather than transform:scale
   is deliberate: scale would leave the layout box at its full height, so the bar
   could not actually get shorter. The cost of a width animation — reflow on each
   frame — is nil here because .site-header is position:fixed, so its internal
   reflow never touches the document below it. height:auto follows the width via
   the img's intrinsic 605x185 ratio.

   The left edge stays put for free: the logo is the first, flex:none child of
   .wrap, so its left edge sits on the wrap's padding edge — the content grid —
   regardless of its width. Nothing needs to anchor it. */
.site-header__logo img{
  width:var(--logo-w);height:auto;
  transition:width var(--dur-hover) var(--ease);
}
.site-header.is-stuck .site-header__logo img{
  width:calc(var(--logo-w) * var(--logo-scale));
}

/* The nav is lifted so it centres on the LVLUP LETTERING, not on the logo's
   bounding box. Those are not the same line: the "Counseling" script hangs
   below the letterforms, which drags the box centre down and leaves the nav
   reading as too low.

   Measured from logo-header.svg — viewBox 304 293 605 185, lettering spanning
   y 293.8..419.8, so its midline is 356.8 against a box midline of 385.5. That
   is 28.7 of 185 units, or 0.1551 of the box height. The box height is
   width x 185/605, so the lift as a fraction of the logo's WIDTH is
   0.30579 x 0.1551 = 0.0474.

   Deriving it from --logo-w rather than hard-coding a pixel value means it
   follows the logo automatically: it rescales at the 760px breakpoint where
   --logo-w drops to 190, and again when the header condenses and the logo goes
   to --logo-scale. Transitioned so it moves with the condense rather than
   snapping halfway through it. */
/* Both the nav and the Menu button take the lift — .nav is display:none below
   1100px and .nav__toggle replaces it, so whichever one is on screen has to sit
   on the same line as the lettering. */
.nav,
.nav__toggle{
  transform:translateY(calc(var(--logo-w) * -0.0474));
  transition:transform var(--dur-hover) var(--ease),
             background-color var(--dur-hover) var(--ease);
}
.nav{
  display:flex;align-items:center;gap:6px;
  background:rgba(255,255,255,.18);border-radius:var(--radius-sm);
  padding:4px;backdrop-filter:blur(2px);
}
.nav__link{
  font-family:var(--display);font-weight:400;font-size:15px;
  color:var(--white);text-decoration:none;padding:9px 22px;border-radius:5px;
  transition:background-color .2s var(--ease);
}
.nav__link:hover{background:rgba(255,255,255,.16)}
.site-header.is-stuck .nav__link:hover{background:rgba(255,255,255,.12)}
/* Hamburger. Three absolutely-positioned bars rather than an SVG swap, so the
   closed and open states are the SAME elements moving — the centre bar fades
   while the outer two travel to the middle and cross. Swapping one icon for
   another cannot be animated. */
.nav__toggle{
  display:none;color:var(--white);
  padding:13px 10px;        /* 18px of bars + 26px of padding = a 44px target,
                               which is the floor for a comfortable tap */
}
.nav__bars{display:block;position:relative;width:26px;height:18px}
.nav__bars i{
  position:absolute;left:0;right:0;height:2px;border-radius:2px;
  background:currentColor;
  transition:transform var(--dur-hover) var(--ease),
             opacity var(--dur-hover) var(--ease);
}
.nav__bars i:nth-child(1){top:0}
.nav__bars i:nth-child(2){top:8px}
.nav__bars i:nth-child(3){top:16px}
.site-header.nav-open .nav__bars i:nth-child(1){transform:translateY(8px) rotate(45deg)}
.site-header.nav-open .nav__bars i:nth-child(2){opacity:0}
.site-header.nav-open .nav__bars i:nth-child(3){transform:translateY(-8px) rotate(-45deg)}

/* ---------- hero --------------------------------------------------------- */
/* Hero fills the viewport when the window's proportions allow it.
   Above the tablet breakpoint a max-height keeps it from becoming a tall,
   narrow box on portrait-ish desktop windows — the cap is ~1.02x the viewport
   width, matching the reference site's behaviour. Below that breakpoint the cap
   is lifted (a phone is always portrait) and the hero grows with its content
   instead of clipping it. svh rather than vh so a mobile URL bar doesn't push
   the content out of view. */
.hero{
  position:relative;overflow:hidden;display:flex;align-items:flex-end;
  height:100vh;            /* fallback for browsers without svh */
  height:100svh;
  min-height:600px;
}
@media (min-width:810px){
  .hero{max-height:102vw}
}
@media (max-width:809px){
  .hero{height:auto;min-height:100svh}
}
.hero picture{position:absolute;inset:0}
.hero__bg{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  /* Per-variant, supplied by the hero picker in index.html as --hero-pos.
     Her face does not sit in the same place in every photo, and on a phone only
     ~23% of the image width is visible, so centring on the frame mis-frames her.
     The fallback below is variant A's value. Re-solve for any new photo —
     tools/hero_pipeline.py prints a starting point, then check it at 390px. */
  object-position:var(--hero-pos, 50% 0);
}
/* Legibility scrim. The photo is bright sky + light fabric exactly where the
   headline sits, so the text needs its own contrast floor rather than relying
   on the image. Plum-tinted rather than black to stay on-brand. */
.hero::before{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    radial-gradient(150% 145% at 0% 100%, rgba(29,22,56,.92) 0%, rgba(29,22,56,.62) 40%, rgba(29,22,56,.18) 72%, rgba(29,22,56,0) 92%),
    linear-gradient(to top, rgba(29,22,56,.44) 0%, rgba(29,22,56,.12) 38%, rgba(29,22,56,0) 62%);
}
/* Per-variant scrim top-up. The base scrim above is tuned for variant A; a
   different photo can be brighter behind the headline and come up short. Rather
   than a second full gradient per variant, each variant carries a --hero-boost
   value (set alongside --hero-pos by the picker in index.html) and this layer
   applies it. 0 = no extra darkening, which is the default.
   Always re-measure with: python3 tools/verify_hero.py --variant <id> */
.hero::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(to top,
    rgba(29,22,56,var(--hero-boost,0)) 0%,
    rgba(29,22,56,calc(var(--hero-boost,0) * .82)) 58%,
    rgba(29,22,56,0) 92%);
}

.hero .wrap{position:relative;z-index:2;width:100%;padding-bottom:90px}
.hero h1{font-size:60px;line-height:74px;color:var(--blush);max-width:500px}
.hero h1 em{
  font-family:var(--body);font-style:italic;font-weight:400;
  color:var(--white);letter-spacing:-.005em;
}
.hero__sub{
  font-size:23px;line-height:34px;color:var(--white);
  max-width:412px;margin:34px 0 30px;
}

/* ---------- generic section --------------------------------------------- */
.section{padding-block:96px;scroll-margin-top:24px}
.section--cream{background:var(--cream)}
.section--white{background:var(--white);position:relative;overflow:hidden}
.who{padding-bottom:118px}
.about{padding-bottom:106px}
.rates{padding-bottom:148px}
.section--plum{background:var(--plum);color:var(--blush)}
.section--purple{background:var(--purple);color:var(--white)}

.section--cream .eyebrow{color:var(--plum)}
.section--white .eyebrow{color:var(--purple)}
/* Terracotta clears 5.99 on plum but only 2.15 on purple, and an eyebrow is
   13px/700 — not large text, so the floor is 4.50, not 3.0. The purple section
   takes cream instead (5.32): the warmest option in the palette that passes,
   and still distinct from the white body copy around it. */
.section--plum .eyebrow{color:var(--terra)}
.section--purple .eyebrow{color:var(--cream)}

/* ---------- about -------------------------------------------------------- */
.about__grid{display:grid;grid-template-columns:1fr 450px;gap:77px;align-items:start;padding-right:116px}
.about h2{font-size:50px;line-height:58px;color:var(--purple);max-width:590px}
/* The back half of Kelly's headline, dropped out of the display face into the
   Source Serif italic the hero h1 already uses as its counter-voice. Halves the
   amount of 50px purple while keeping her sentence intact — the leading em dash
   makes it read as a continuation, not a subhead. */
.about__deck{
  margin:18px 0 0;
  font-family:var(--body);font-style:italic;font-weight:400;
  font-size:27px;line-height:36px;letter-spacing:-.005em;
  color:var(--plum);max-width:520px;
}
.about__body{margin:34px 0 0;max-width:560px}
.about__body p{margin:0 0 24px;font-size:17px;line-height:25px}

.creds{
  background:var(--cream-lt);border-radius:var(--radius);
  padding:24px 30px;margin-top:20px;max-width:450px;
}
.creds li{
  display:flex;align-items:center;gap:12px;list-style:none;
  font-family:var(--display);font-weight:600;font-size:17px;color:var(--terra-ink);
}
.creds svg{width:19px;height:19px;flex:none}
/* The row gap has to live on the <ul>. It used to sit on .creds as
   `display:grid;gap:13px`, but .creds has exactly one child — the <ul> — so it
   never applied to anything and the rows were rendering flush, separated only
   by their 23.8px line box. Measured gap before this change: 0px. */
.creds ul{margin:0;padding:0;display:grid;gap:26px}

.about__figure{position:relative;margin:0}
.about__figure img{width:450px;height:600px;object-fit:cover;border-radius:var(--radius)}
/* ---------- about: the expanding bio panel -------------------------------
   ONE surface, not two. The panel sits at its full open size the whole time
   and is CLIPPED back to the toggle when closed. clip-path rather than an
   animated width/height for two reasons: an inset() clip animates without
   laying anything out, and clipping from the bottom-left corner IS the
   "expands up and to the right" motion — no transform-origin work, and the
   copy inside never reflows while the panel grows, so no text reflow flicker.

   The closed size depends on the rendered width of her name, so site.js
   measures the toggle and writes --bio-w / --bio-h. The fallbacks below are
   the measured values at the desktop size, so the first paint is right even
   if the script has not run yet.

   Scoped to .js on purpose: with scripting off the clip never applies, the
   panel renders open, and the bio stays readable rather than being clipped
   out of view with no way to reveal it. */
.bio{
  position:absolute;left:22px;right:22px;bottom:22px;
  max-height:calc(100% - 44px);
  display:flex;flex-direction:column;
  background:var(--cream);border-radius:var(--radius);
  pointer-events:none;
}
.js .bio{
  clip-path:inset(calc(100% - var(--bio-h,46px)) calc(100% - var(--bio-w,206px)) 0 0 round 9px);
  transition:clip-path var(--dur-bio) var(--ease-wipe);
}
.js .bio[data-open="true"]{clip-path:inset(0 0 0 0 round var(--radius))}

.bio__body{
  min-height:0;overflow-y:auto;
  padding:30px 30px 4px;
  font-family:var(--body);font-size:17px;line-height:26px;color:var(--plum);
  opacity:0;transform:translateY(8px);
  transition:opacity var(--dur-hover) var(--ease),
             transform var(--dur-hover) var(--ease);
}
.bio__body p{margin:0}
/* The panel opens first and the copy follows it in — hence the delay on the
   way OPEN only. Closing drops the copy immediately, so it is gone before the
   panel has shrunk past it rather than being visibly guillotined. */
.bio[data-open="true"] .bio__body{
  opacity:1;transform:none;pointer-events:auto;
  transition-delay:calc(var(--dur-bio) * .55);
}

.bio__toggle{
  align-self:flex-start;pointer-events:auto;
  display:flex;align-items:center;gap:12px;
  padding:6px 18px 6px 6px;border-radius:9px;
  font-family:var(--display);font-weight:600;font-size:16px;color:var(--plum);
  text-align:left;
  transition:color var(--dur-hover) var(--ease);
}
/* Affordance — the name is the only cue that this badge is a control. Purple on
   cream measures 5.32 (AA), so the hover state is legible rather than just
   decorative. :focus-visible gets it too: a keyboard user should see the same
   hint as a mouse user, not only the outline ring. */
.bio__toggle:hover,
.bio__toggle:focus-visible{color:var(--purple)}
.bio__mark{
  display:grid;place-items:center;width:34px;height:34px;flex:none;
  background:var(--purple);border-radius:7px;color:var(--white);
  transition:background-color var(--dur-hover) var(--ease);
}
.bio__mark svg{width:17px;height:17px}
.bio__mark .minus{display:none}
.bio[data-open="true"] .bio__mark{background:var(--terra)}
.bio[data-open="true"] .bio__mark .plus{display:none}
.bio[data-open="true"] .bio__mark .minus{display:block}

/* ---------- who I help --------------------------------------------------- */
.watermark{
  position:absolute;top:14px;left:58%;width:658px;height:auto;
  pointer-events:none;user-select:none;z-index:0;
}
.who .wrap{position:relative;z-index:1}
.who__head{text-align:center;margin-bottom:85px}
.who h2{font-size:49px;line-height:1.1;color:var(--plum)}
.who__sub{
  font-size:17px;line-height:25px;margin:36px auto 0;max-width:740px;color:var(--plum);
}
.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:30px}
.card{
  border:1px solid var(--terra);border-radius:var(--radius);
  padding:15px 26px 46px;background:var(--white);
}
.card h3{font-size:20px;font-weight:600;line-height:1.25;letter-spacing:-.01em;color:var(--plum)}
.card p{margin:28px 0 0;font-size:17px;line-height:25px;color:var(--plum)}

/* ---------- band: the rotating quote ------------------------------------
   Slides sit side by side in a flex track and the track translates by whole
   multiples of its own width. transform only — the slides never relayout, so
   the type inside never re-wraps mid-transition, and the whole move is
   composited. --band-i is the active index, written by site.js.

   --ease-wipe rather than --ease-out, same reason as the button wipe: this is
   motion the eye is meant to follow across, not something arriving. */
.band{position:relative;height:607px;overflow:hidden}
.band__track{
  display:flex;height:100%;
  transform:translate3d(calc(var(--band-i,0) * -100%),0,0);
  transition:transform var(--dur-band) var(--ease-wipe);
}
.band__slide{
  position:relative;flex:0 0 100%;height:100%;
  display:flex;align-items:flex-end;
}
/* Each photo carries its own crop inline as --band-pos, so adding a slide is a
   markup change and nothing else. */
.band__bg{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:var(--band-pos, 50% 50%);
}
/* Per-slide legibility scrim, the band's version of the hero's --hero-boost.
   Default 0, so slides that do not need it are untouched and nothing changes
   for the two that shipped before this existed. A slide that wants darkening
   carries its own value inline on the <article>, next to the --band-pos its
   image carries — same "adding a slide is a markup change" principle.

   Plum-tinted rather than black, matching the hero scrim: black greys a photo
   out, plum sits under the palette and reads as depth instead of dirt.

   Bottom-weighted and biased into the bottom-left corner, because the quote
   sets from the bottom of the content column and the top of the frame carries
   no type at all. --band-scrim lives on the slide, NOT the img: custom
   properties inherit downward, so a value set on the image would never reach
   this pseudo-element. */
.band__slide::before{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    linear-gradient(to top,
      rgba(29,22,56,var(--band-scrim,0)) 0%,
      rgba(29,22,56,calc(var(--band-scrim,0) * .55)) 42%,
      rgba(29,22,56,0) 78%),
    radial-gradient(120% 110% at 0% 100%,
      rgba(29,22,56,calc(var(--band-scrim,0) * .5)) 0%,
      rgba(29,22,56,0) 70%);
}
.band .wrap{position:relative;z-index:2;width:100%;padding-bottom:73px}

/* Dots sit on the content column, not the viewport edge — same derived inset
   the FAQ panel uses, so they line up with the copy above them. */
.band__dots{
  position:absolute;z-index:3;
  right:calc(50vw - min(670px, 50vw) + var(--gutter));
  /* Below the copy, not level with it. .band .wrap has 73px of bottom padding,
     so sitting the dots inside that band puts them clear of the last line
     whatever length it runs to — at 78px they sat level with slide 2's last
     line and would collide outright with a longer quote. */
  bottom:30px;display:flex;gap:11px;
}
.band__dot{
  width:10px;height:10px;padding:0;border-radius:50%;
  background:rgba(255,255,255,.42);
  transition:background-color var(--dur-hover) var(--ease),
             transform var(--dur-hover) var(--ease);
}
.band__dot:hover{background:rgba(255,255,255,.8)}
.band__dot[aria-current="true"]{background:var(--white);transform:scale(1.3)}
.band p{
  margin:0;font-family:var(--display);font-weight:800;letter-spacing:-.025em;
  font-size:49px;line-height:58px;color:var(--white);max-width:1260px;
}

/* ---------- rates & faqs ------------------------------------------------- */
.rates h2{font-size:49px;line-height:1.1;color:var(--blush)}
.rates__lede{
  margin:26px 0 0;font-size:17px;line-height:25px;color:var(--white);max-width:950px;
}
/* Two fee items now, not three — each takes half the content column. */
.rates__cards{display:grid;grid-template-columns:repeat(2,1fr);gap:30px;margin-top:22px}

/* Group label above the fee cards. Deliberately NOT h2 scale: it labels a group
   inside the section, so it sits at card-title size in terracotta (5.99 on plum,
   the same pairing the eyebrow uses). */
.rates__label{
  margin:62px 0 0;font-family:var(--display);font-weight:600;font-size:22px;
  line-height:1.2;letter-spacing:-.01em;color:var(--terra);
}

/* Insurance runs the full content width because it carries the bullets; the
   three shorter blocks below it share a 3-up row. Prose, not cards — no border,
   so uneven column bottoms read as deliberate rather than broken. */
.rates__insurance{margin-top:62px}
.rates__detail{display:grid;grid-template-columns:repeat(3,1fr);gap:30px;margin-top:58px}
.rates__detail h3,
.rates__insurance h3{
  margin:0 0 14px;font-family:var(--display);font-weight:600;font-size:22px;
  line-height:1.2;letter-spacing:-.01em;color:var(--terra);
}
.rates__detail p,
.rates__insurance p{margin:0;font-size:17px;line-height:25px;color:var(--white)}
/* The block spans the full 1340, but the paragraph does not: at full width this
   copy would run ~190 characters a line. 950 is the measure .rates__lede
   already uses in this section. */
.rates__insurance p{max-width:950px}
/* Bullets go 3-up so the list uses the full content width rather than stacking
   in a narrow strip. Custom marker: a list-style disc cannot be coloured
   independently of the text. */
.rates__insurance ul{
  margin:26px 0 0;padding:0;list-style:none;
  display:grid;grid-template-columns:repeat(3,1fr);gap:30px;
}
.rates__insurance li{
  position:relative;padding-left:21px;
  font-size:17px;line-height:25px;color:var(--white);
}
.rates__insurance li::before{
  content:"";position:absolute;left:0;top:10px;
  width:6px;height:6px;border-radius:50%;background:var(--terra);
}
.rates__detail a{color:var(--blush);text-decoration:underline;text-underline-offset:3px}
.rates__detail a:hover{color:var(--white)}

/* Rate cards mirror the Who-I-Help cards (terracotta hairline, same 12px radius,
   same 400/30 grid) but keep the rates typography: 24px title, 30px serif price.
   Equal height with the price pinned to the bottom, so prices line up across the
   row however long the descriptions run. */
.rate-card{
  display:flex;flex-direction:column;
  border:1px solid var(--terra);border-radius:var(--radius);
  padding:17px 26px 14px;
}
.rate-card h3{font-size:24px;font-weight:600;line-height:1.25;letter-spacing:-.01em;color:var(--white)}
/* 336px suited a third-width card. At half width the card is ~655px, and copy
   set to the full inner width would run ~85 characters — too long to track
   comfortably. 480px holds it near 65, which is where it should sit. */
.rate-card p{margin:31px 0 0;font-size:17px;line-height:25px;color:var(--white);max-width:480px}
.rate-card__price{
  margin-top:auto;padding-top:15px;text-align:right;
  font-family:var(--body);font-size:30px;line-height:1.2;color:var(--terra);
}

/* ---------- faq: full-bleed image beside a terracotta panel ---------- */
.faq-split{display:grid;grid-template-columns:1fr 1fr}
.faq-split__media{position:relative;margin:0;overflow:hidden;min-height:420px}
.faq-split__media img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 50%;
}
.faq-split__panel{
  background:var(--terra);
  /* Right padding tracks the page grid exactly, so the FAQ cards end on the same
     line as the rates cards above and the footer below. .wrap is max-width 1340
     centred with a --gutter inset, so its content edge sits at
     50vw + min(670px, 50vw) - gutter. This is the distance back from the
     viewport edge. Derived, not eyeballed — do not replace with a fixed value. */
  padding:100px calc(50vw - min(670px, 50vw) + var(--gutter)) 111px 58px;
}
/* Nothing light survives on terracotta: cream measures 2.48, white 2.86, blush
   1.95. Plum at 5.99 is the only passing option, and it is already the panel's
   heading colour, so it adds nothing new to the block. */
.faq-split__panel .eyebrow{color:var(--plum)}
.faq-split__panel h2{font-size:49px;line-height:58px;color:var(--plum);max-width:540px}

.faq{display:grid;gap:20px;align-content:start;margin-top:50px}
.faq__item{background:var(--cream);border-radius:10px;overflow:hidden}
.faq__q{
  width:100%;transition:background-color var(--dur-hover) var(--ease);display:flex;align-items:center;justify-content:space-between;gap:20px;
  text-align:left;padding:13px 16px 13px 26px;
  font-family:var(--display);font-weight:600;font-size:17px;color:var(--plum);
}
.faq__qt{flex:1 1 auto}
.faq__mark{
  display:grid;place-items:center;width:30px;height:30px;flex:none;
  border-radius:7px;background:var(--terra);color:var(--cream);
  transition:background-color var(--dur-hover) var(--ease),
             color var(--dur-hover) var(--ease);
}
.faq__mark svg{width:15px;height:15px}
.faq__q:hover .faq__mark{background:var(--plum);color:var(--cream)}
.faq__mark .minus{display:none}
.faq__item.is-open .faq__mark{background:var(--white);color:var(--plum)}
.faq__item.is-open .faq__mark .plus{display:none}
.faq__item.is-open .faq__mark .minus{display:block}

/* Height animates via grid-template-rows 0fr -> 1fr; overflow:hidden on the
   child is what lets the row actually reach zero. The bottom padding has to
   animate alongside it and live on the container: padding sits outside the
   grid row, so a closed panel with 26px on the child measures 26px tall, not
   zero. Horizontal padding is constant and stays put. */
.faq__a{
  display:grid;grid-template-rows:0fr;
  padding:0 45px 0 26px;
  font-size:17px;line-height:25px;color:var(--plum);
  transition:grid-template-rows var(--dur-panel) var(--ease),
             padding-bottom var(--dur-panel) var(--ease);
}
/* The collapsing row must contain exactly ONE child. Two sibling <p> elements
   put the second into an implicit auto row, which never collapses, and the
   panel then refuses to close — so the paragraphs live inside .faq__aw and it
   is the single grid item. */
.faq__a > .faq__aw{min-height:0;overflow:hidden}
.faq__aw p{margin:0}
.faq__aw p + p{margin-top:19px}
.faq__item.is-open .faq__a{grid-template-rows:1fr;padding-bottom:26px}

/* ---------- contact ------------------------------------------------------ */
.contact{text-align:center;padding-block:57px 101px}
.contact h2{font-size:49px;line-height:58px;color:var(--white)}
.contact h2 u{text-decoration-thickness:3px;text-underline-offset:5px}
.contact__sub{
  font-size:17px;line-height:25px;margin:6px auto 0;max-width:640px;color:var(--white);
}
.contact .btn{margin-top:32px}

/* ---------- contact details: map beside the address block ---------- */
.contact__details{
  display:grid;grid-template-columns:1fr 353px;gap:78px;
  align-items:center;text-align:left;margin-top:75px;
}
.contact__map{margin:0;border-radius:var(--radius);overflow:hidden;line-height:0}
/* The map is a link to Google Maps. It needs an affordance — nothing else about
   a full-bleed image says "clickable" — so it lifts slightly under the pointer.
   The scale is clipped by the figure's overflow:hidden, so the corners stay put.
   focus-visible gets the same cue, not just the outline ring. */
.contact__map a{
  display:block;
  transition:transform var(--dur-hover) var(--ease);
}
.contact__map a:hover,
.contact__map a:focus-visible{transform:scale(1.025)}
.contact__map a:focus-visible{outline-offset:-3px}
.contact__map img{width:100%;height:auto}

/* Archivo here, not the body serif — the comp sets the address in the UI face. */
.contact__info h3{
  margin:0;font-family:var(--display);font-weight:600;font-size:22px;
  line-height:1.2;letter-spacing:-.01em;color:var(--white);
}
.contact__info p{
  margin:20px 0 0;font-family:var(--display);font-weight:400;
  font-size:18px;line-height:21px;color:var(--white);
}
.contact__info p + h3{margin-top:39px}
.contact__info a{
  color:var(--white);text-decoration:underline;text-underline-offset:3px;
  transition:text-underline-offset var(--dur-hover) var(--ease);
}
.contact__info a:hover{text-underline-offset:6px}

/* ---------- footer ------------------------------------------------------- */
/* Plum now, not purple — the contact block above owns the purple, and the
   footer reads as a quiet close rather than a second CTA. */
.site-footer{background:var(--plum);color:var(--white);padding-block:60px 0}

.footer__card{
  background:var(--plum-card);border-radius:var(--radius);
  display:grid;grid-template-columns:auto auto 1fr;align-items:center;
  gap:47px;padding:30px;min-height:119px;
}
.footer__logo{width:176px;height:auto}
.footer__tagline{
  margin:0;font-family:var(--body);font-size:17px;line-height:25px;
  color:var(--white);max-width:190px;
}
.footer__nav{display:flex;justify-content:flex-end;gap:clamp(24px,3.4vw,57px)}
.footer__nav a{
  font-family:var(--display);font-weight:400;font-size:17px;
  color:var(--white);text-decoration:none;
}
.footer__nav a{transition:opacity var(--dur-hover) var(--ease)}
.footer__nav a:hover{text-decoration:underline;text-underline-offset:4px;opacity:.72}

.footer__legal{
  margin-top:17px;padding:0 0 58px;
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  font-family:var(--display);font-weight:500;font-size:14px;color:var(--white);
}
.footer__legal p{margin:0}
.footer__legal nav{display:flex;gap:48px}
.footer__legal a{color:var(--white);text-decoration:none}
.footer__legal a{transition:opacity var(--dur-hover) var(--ease)}
.footer__legal a:hover{text-decoration:underline;opacity:.72}

/* ==========================================================================
   Responsive — holding pattern only.
   The comp is desktop; this keeps narrow viewports usable but is NOT the
   finished responsive pass.
   ========================================================================== */
@media (max-width:1100px){
  .rates__cards{grid-template-columns:1fr;gap:24px;max-width:none}
  .rates__detail{grid-template-columns:1fr;gap:38px}
  .rates__insurance ul{grid-template-columns:1fr;gap:12px}
  .rate-card__price{padding-top:22px}
  /* ---- mobile nav drawer ----
     The links are no longer display:none. A drawer has to animate and a display
     switch cannot, so visibility:hidden carries the closed state instead — and
     it still does the job display:none was doing, keeping a closed drawer out
     of the tab order with no inert attribute to add and remove per breakpoint.

     Deliberately NO body scroll lock: this is a panel under the bar, not a
     full-screen takeover, so locking buys nothing and would collide with the
     smooth anchor scroll that fires on the same click. */
  .nav{
    /* .wrap is unpositioned, so this anchors to .site-header and tracks its
       height — the drawer stays glued to the bar through the is-stuck
       condense without a magic number to keep in sync. */
    position:absolute;top:100%;left:0;right:0;
    display:flex;flex-direction:column;align-items:stretch;gap:2px;
    padding:12px var(--gutter) 22px;
    background:var(--plum);border-radius:0;backdrop-filter:none;
    box-shadow:var(--header-shadow);
    opacity:0;visibility:hidden;
    transition:opacity var(--dur-hover) var(--ease),
               transform var(--dur-hover) var(--ease),
               visibility 0s linear var(--dur-hover);
  }
  /* Both selectors, and they carry the background too, because the desktop rule
     .site-header.is-stuck .nav{background:transparent} is (0,3,0) and beats a
     plain .nav at (0,1,0). That rule exists so the pill behind the desktop
     links disappears once the solid bar fades in — correct there, fatal here:
     a hero-less page (terms, privacy) gets .is-stuck immediately, so the drawer
     opened fully transparent while index.html looked fine. Matching the
     specificity and winning on source order is what fixes it. The lift is in
     the same block for the same reason. */
  .site-header .nav,
  .site-header.is-stuck .nav{
    background:var(--plum);backdrop-filter:none;
    transform:translateY(-10px);
  }
  .site-header.nav-open .nav,
  .site-header.is-stuck.nav-open .nav{
    transform:none;opacity:1;visibility:visible;transition-delay:0s;
  }
  .nav__link{padding:13px 12px;font-size:17px;border-radius:6px}
  .nav__link:hover{background:rgba(255,255,255,.12)}
  .nav .btn{margin-top:12px;align-self:flex-start}
  /* Open over a transparent header would show hero through the gap above. */
  .site-header.nav-open::before{opacity:1}
  .nav__toggle{display:block}
  .about__grid{grid-template-columns:1fr;gap:48px}
  .about__figure img{width:100%;height:auto;aspect-ratio:3/4}
  
  .cards{grid-template-columns:1fr}
  .footer__card{grid-template-columns:auto 1fr;gap:28px}
  .footer__nav{grid-column:1/-1;justify-content:flex-start;flex-wrap:wrap}
  .watermark{display:none}
}
/* Under ~600px the hero crops to little but face and sky, so the headline has
   no dark ground left to sit on. Measured 2.70 against a 3.0 floor at 390px
   with the desktop scrim; this is the boost that clears it. */
@media (max-width:599px){
  .hero::before{
    background:
      radial-gradient(155% 150% at 0% 100%, rgba(29,22,56,.94) 0%, rgba(29,22,56,.68) 40%, rgba(29,22,56,.26) 70%, rgba(29,22,56,0) 92%),
      linear-gradient(to top, rgba(29,22,56,.54) 0%, rgba(29,22,56,.20) 42%, rgba(29,22,56,0) 70%);
  }
}

/* the split stacks below the tablet breakpoint. NOTE: this has to live after
   the base .faq-split rules — same specificity, so source order decides. */
@media (max-width:809px){
  .faq-split{grid-template-columns:1fr}
  .faq-split__media{min-height:300px;max-height:44vh}
  .faq-split__panel{padding:64px var(--gutter) 72px}
}

@media (max-width:760px){
  :root{--logo-w:190px}
  .site-header .wrap{gap:12px}
  .about__grid{padding-right:0}
  .section{padding-block:64px}
  .hero .wrap{padding-top:140px}
  .hero h1{font-size:40px;line-height:48px}
  .hero__sub{font-size:19px;line-height:30px}
  .about h2,.rates h2,.who h2{font-size:34px;line-height:42px}
  .about__deck{font-size:21px;line-height:29px}
  /* ---- band at phone width ----
     The band stops being a fixed 607px strip here and sizes to its content, and
     three things follow from that.

     1. height:100% on the slides has to go. Against an auto-height band it
        resolves per slide instead of filling one, which defeats the flex
        stretch — each slide ended up exactly as tall as its own quote (301 /
        339 / 377px for 6 / 7 / 8 lines) rather than all matching the tallest.
     2. With no slack in the box, align-items:flex-end had nothing to work
        with: 0-1px above the text and the full 73px of .wrap padding below.
        Once the slides stretch, centring the quote is what balances it.
     3. The dots move. They sit at bottom:30px, which lands inside the band's
        own padding — on the page background rather than the photo, white on
        cream, invisible. They are also the pause control for an auto-advancing
        carousel (WCAG 2.2.2), so losing them on touch is not cosmetic.
        --band-inset ties their offset to the padding so the two cannot drift. */
  .band{--band-inset:56px;height:auto;padding-block:var(--band-inset)}
  .band__track{height:auto;align-items:stretch}
  .band__slide{height:auto;align-items:center}
  .band .wrap{padding-block:54px}
  .band__dots{bottom:calc(var(--band-inset) + 16px)}
  .band p{font-size:27px;line-height:38px}
  .contact h2{font-size:32px;line-height:42px}
  .footer__card{grid-template-columns:1fr;gap:20px;padding:24px}
  .about__figure img{width:100%}
  .contact__details{grid-template-columns:1fr;gap:36px;margin-top:48px}
  .footer__legal{flex-direction:column;align-items:flex-start;gap:14px}
  
}

/* ==========================================================================
   LEGAL PAGES  (terms.html, privacy.html)
   Plain reading pages that reuse the site header and footer. No hero, so
   site.js puts the header in its solid state from the first paint — see the
   note there. Top padding clears the stuck header (81px) with air to spare.
   ========================================================================== */
.legal{
  background:var(--cream-lt);
  padding-block:calc(var(--header-h) + 34px) 108px;
}
/* Narrower than .wrap on purpose — a legal page is read, not scanned, and a
   1260px measure at 17px is punishing. Declared after .wrap so it wins on
   source order at equal specificity. */
.legal__wrap{max-width:860px}

.legal h1{font-size:49px;line-height:1.08;color:var(--plum);margin:0 0 12px}
.legal__meta{
  margin:0 0 38px;font-family:var(--display);font-weight:500;font-size:14px;
  letter-spacing:.02em;color:var(--purple-deep);
}
.legal__lede{
  font-size:20px;line-height:31px;color:var(--plum);
  margin:0 0 8px;padding-bottom:30px;border-bottom:1px solid var(--blush);
}
.legal h2{
  font-family:var(--display);font-weight:700;font-size:22px;line-height:1.25;
  letter-spacing:-.012em;color:var(--plum);margin:44px 0 13px;
}
.legal p{margin:0 0 16px;font-size:17px;line-height:27px;color:var(--plum)}
.legal strong{font-weight:600}
.legal a{color:var(--purple-deep);text-underline-offset:3px;
  transition:text-underline-offset var(--dur-hover) var(--ease)}
.legal a:hover{text-underline-offset:6px}

/* Pulled out of the flow of the argument: the things a reader must not skim
   past. Terracotta as a rule, never as type — 2.65 on this background. */
.legal__callout{
  background:var(--cream);border-left:3px solid var(--terra);
  border-radius:var(--radius-sm);padding:22px 26px;margin:0 0 20px;
}
.legal__callout p:last-child{margin-bottom:0}
.legal__callout--urgent{border-left-color:var(--purple)}

.legal__seealso{
  margin-top:46px;padding-top:26px;border-top:1px solid var(--blush);
  font-family:var(--display);font-weight:500;font-size:15px;
}

@media (max-width:760px){
  .legal{padding-block:calc(var(--header-h) - 18px) 72px}
  .legal h1{font-size:34px;line-height:42px}
  .legal__lede{font-size:18px;line-height:28px}
  .legal h2{font-size:20px;margin-top:36px}
}

/* ==========================================================================
   MOTION
   Intensity is set by the tokens at the bottom of tokens.css, not here.
   Two systems: a CSS-only hero entrance that runs on load, and scroll
   reveals driven by IntersectionObserver in site.js.
   ========================================================================== */

/* ---------- scroll reveals ------------------------------------------------
   Scoped to .js, which the inline <head> script sets before first paint.
   Without that scope a visitor with JS disabled would get a page of
   permanently invisible sections. --reveal-delay is written per element by
   site.js; --reveal-dir is -1 for content entering from above. */
.js [data-reveal]{
  opacity:0;
  transform:translate3d(0, calc(var(--rise) * var(--reveal-dir, 1)), 0) scale(var(--reveal-scale));
  transition:opacity var(--dur-reveal) var(--ease-out),
             transform var(--dur-reveal) var(--ease-out);
  transition-delay:var(--reveal-delay, 0ms);
  will-change:opacity, transform;
}
.js [data-reveal].is-in{
  opacity:1;transform:none;will-change:auto;
}

/* ---------- hero entrance -------------------------------------------------
   The image is untouched — it is the LCP element and fading it would cost
   real numbers. Only the three text/CTA blocks move. Pure CSS so it does not
   wait on site.js at the bottom of the document. */
@keyframes heroIn{ to{ opacity:1; transform:none } }
.hero .wrap > *{
  opacity:0;transform:translate3d(0,26px,0);
  animation:heroIn var(--dur-hero) var(--ease-out) forwards;
}
.hero h1  {animation-delay:calc(var(--hero-step) * 1)}
.hero__sub{animation-delay:calc(var(--hero-step) * 2)}
.hero .btn{animation-delay:calc(var(--hero-step) * 3)}

/* ==========================================================================
   REDUCED MOTION
   Killing animation and transition alone would strand every revealed element
   at opacity:0, so the second rule forces the end state. Keep these together.
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none!important;transition:none!important}
  .js [data-reveal],
  .hero .wrap > *{opacity:1!important;transform:none!important}
}
