/* =========================================================================
   FOLDS — header, and the stage that carries fold 1 into fold 2
   ========================================================================= */

/* -------------------------------------------------------------------------
   HEADER — pills on a white rail
   ------------------------------------------------------------------------- */
.hdr {
  position: fixed; inset: 0 0 auto 0; z-index: 120;
  padding: clamp(0.9rem, 1.6vw, 1.4rem) 0;
  transition: background-color var(--t-2) var(--ease),
              box-shadow var(--t-2) var(--ease);
}
.hdr.is-stuck {
  background: rgba(var(--white-rgb), 0.82);
  backdrop-filter: saturate(1.5) blur(14px);
  box-shadow: 0 1px 0 var(--rule-quiet);
}
.hdr .shell { display: flex; align-items: center; gap: var(--sp-3); }

.brand { display: flex; align-items: center; gap: 0.65rem; }
.brand img { width: 34px; height: 34px; }
.brand b { font-size: var(--fs-md); font-weight: 700; letter-spacing: var(--track-tight); line-height: 1.1; }
.brand i {
  display: block; font-style: normal;
  font-size: var(--fs-nano); font-weight: 600; letter-spacing: var(--track-micro);
  text-transform: uppercase; color: var(--ink-quiet);
}

.pillnav {
  margin-inline: auto;
  display: flex; align-items: center; gap: 2px;
  padding: 4px;
  border-radius: var(--r-pill);
  background: var(--n-05);
  box-shadow: inset 0 0 0 var(--hair) var(--rule-quiet);
}
.pillnav a {
  padding: 0.55rem 1.05rem;
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--ink-mid);
  transition: background-color var(--t-1) var(--ease), color var(--t-1) var(--ease);
}
.pillnav a:hover { background: var(--white); color: var(--ink); }
/* header.php's wp_nav_menu() call wraps each link in <li> once a real menu
   is assigned in Appearance > Menus (the fallback function it replaces
   outputs plain <a> tags with no <li> at all, which is why this never
   showed up before a menu existed). display:contents drops the <li>'s own
   box — including the browser's default list-item bullet — so its <a>
   becomes the flex item directly, exactly like the fallback's bare links. */
.pillnav li { display: contents; }

.hdr-right { display: flex; align-items: center; gap: var(--sp-3); }
.hdr-tel { font-size: var(--fs-sm); font-weight: 600; }
/* the header CTA is just .btn .btn-primary now — see base.css */

/* =========================================================================
   THE STAGE
   Fold 1 and fold 2 are one pinned scene. The specimen never re-mounts: the
   tooth met in the hero is literally the tooth that develops a cavity on the
   way down and then gets repaired.
   ========================================================================= */
/* The pin is 100svh and the scene runs for 7.2 screens, so GSAP's spacer is
   8.2 screens tall — and the last screen of it is an empty pin scrolling out
   after the specimen has already faded. That was a full blank screen between
   the two folds. Pulling the next section up over that dead tail closes it,
   and fold 3 arrives just as the specimen finishes leaving. */
.stage {
  position: relative;
  /* One source of truth for the specimen's geometry. The band below is
     positioned FROM these rather than from the bottom of the screen, so the
     gap under the tooth is a constant instead of whatever happens to be left
     over — it measured 44px on one screen and -7px on another. */
  --tooth-rise: -44px;   /* matches RISE in site.js */
  /* the space between line 2's own rendered bottom and .hero-foot — not
     "under the tooth" any more now that line 2 sits below it; see .hero-foot */
  --tooth-gap: 20px;
  --tooth-h: min(62vh, 545px, calc(100svh - 460px));
  /* The vertical anchor everything in the hero hangs off — the specimen, both
     halves of the headline, both floating cards, .hero-foot. Plain 50svh grows
     without limit on a tall monitor: at 2560x1440 it pushed the whole
     composition 228px below the header, versus 13px at a common 900px-tall
     laptop screen, and the header itself does not grow to match. Capped so
     the anchor stops moving once the header-to-content gap is already
     comfortable — measured directly against the header's own (roughly fixed)
     height, not derived from it. */
  --stage-center: min(50svh, 540px);
  /* Line 1 sits at a fixed distance above the specimen's centre; line 2 hangs
     --word-gap below line 1's OWN bottom edge, not symmetrically around the
     centre — moving line 2 is a --word-gap edit, not a --line1-offset one,
     and the two are deliberately independent so one can move without
     dragging the other along with it.

     --word-gap is small (40px, not the ~140-220px tried earlier) on purpose:
     the tooth asset has a baked-in contact shadow low on its roots (see
     .hero-word-split below) that reads as damage once real letters sit
     behind it, so line 2 is kept close under line 1 and high against the
     tooth's solid crown/upper-root, well clear of that shadow. Line 1 keeps
     its own deliberate bite into "Smiles" — that part of the tooth has no
     shadow problem and was never in question. */
  /* 104, up from 80: this lifts BOTH lines (line 2's own formula subtracts it
     too), which is what opens the room under the headline without touching
     the captions. The ceiling is the header, not the tooth — at 1440x820, the
     tightest common case, the old value left 77px of clearance and this
     leaves 53. */
  --line1-offset: 112px;
  /* 24, down from 40: line 2 rides an extra 16px closer to line 1, so the two
     lines read as one headline rather than two stacked ones, and every pixel
     it gains is a pixel added below it. */
  --word-gap: 24px;
  /* The captions do NOT follow the two lines.

     They used to hang off line 2's own formula, so tightening --word-gap or
     lifting --line1-offset dragged them up by exactly as much as the headline
     gained and the 21px gap under line 2 never actually opened. This is their
     own anchor, and 40px is the (--line1-offset − --word-gap) the old formula
     resolved to — so the cards sit precisely where they always did while the
     headline moves off them. Raise it to lift the captions, lower it to drop
     them; neither touches the headline. */
  --caption-drop: 40px;
  /* The horizontal gap "Take"/"Shape." sit either side of, AND the same gap
     the two captions below them align to — one number, shared, so the words
     and their captions can never drift apart from each other. Sized off the
     tooth's own rendered width (--tooth-h times its 680:1209 aspect), not a
     flat px number, so it tracks the tooth at every viewport. 0.8x leaves
     the tooth's outer edges overlapping the innermost letters ("e" of Take,
     "S" of Shape) by a deliberate sliver. */
  --flank-gap: calc(var(--tooth-h) * 680 / 1209 * 0.8);
}
/* Fold 3 exists now, so the pull-up is back. It only applies to the pinned
   build: stacked, there is no dead tail and pulling the next section up would
   bury the procedure.

   82, not 70 and not 100. The spacer runs one viewport past the pin's end, so
   whatever this does not claw back is dead scroll: at 70 the reader crossed
   270px of empty white between fold 2 finishing and fold 3's panel reaching
   the top. 100 closes that gap exactly — and is too far, because fold 3's
   eyebrow then arrives while fold 2's "Polished & Checked" is still legible
   and the two collide, which is the failure the fade length was tuned around
   in the first place. 82 halves the dead band to ~160px and still lands fold
   3's panel edge below the last of fold 2. Measured, not guessed: the
   collision appears between 90 and 100.

   .is-pinned is set by site.js at the moment the pin is created, because the
   pull is measured against the spacer GSAP inserts and is nonsense without
   it. The media query alone still matched with scripting off, where no spacer
   exists, and fold 3 landed 738px up on top of the hero. */
@media (min-width: 1025px) and (prefers-reduced-motion: no-preference) {
  .stage.is-pinned { margin-bottom: -82svh; }
}
.stage.is-static { margin-bottom: 0; }
/* GSAP injects .pin-spacer as a full-height block around the pin. It spans the
   whole scene, so where fold 3 is pulled up underneath it, it swallowed the
   first register row's hover and links. Nothing but the pin itself ever needs
   to take events, so the spacer is opted out and the pin opted back in. */
.stage .pin-spacer { pointer-events: none; }

.stage-pin {
  height: 100svh;
  position: relative;
  z-index: 2;              /* stays over fold 3 while the scene is still pinned */
  pointer-events: auto;
  overflow: hidden;
  isolation: isolate;
  /* no background: the body is already white, and an opaque pin would hide
     fold 3 where the two now overlap */
}

/* --- the shared specimen ------------------------------------------------ */
.spec {
  position: absolute;
  left: 50%; top: var(--stage-center);
  margin-left: -0.5px;
  z-index: 3;
  display: grid; place-items: center;
  will-change: transform;
  /* No blend mode: the frames are matted and carry a real alpha channel, so
     the tooth is genuinely opaque and simply sits in front of the word. This
     is what the multiply trick could never do — under multiply, dark type
     behind a light subject reads straight through it. */
}
/* v9 puts two cards in the bottom band, and the specimen's roots landed on
   top of them — 620px against a card top of 619px at 1440x820. The third term
   is the fix: reserve the band's height instead of stepping at one arbitrary
   breakpoint, so the tooth is only ever as tall as the space actually left
   above the cards. 420 is measured against the real band: the cards lost
   their icon discs and dropped to 153, so the reserve came down with them and
   the specimen got the space back. */
.spec img, .spec canvas { height: var(--tooth-h); width: auto; }
/* the compacted band measures ~135 without the icon discs, so the reserve and
   the caps both relax. The card overrides that go with this live below the
   .hcard rules — put here they lost to them, since a media query adds no
   specificity. */
@media (min-width: 1025px) and (max-height: 900px) {
  .stage {
    --tooth-h: min(56vh, 470px, calc(100svh - 420px));
    /* Same reasoning as --tooth-h above, applied to the two headline lines:
       a shorter viewport leaves less room between the header and the
       specimen's own top edge for line 1's portion above the bite (and,
       symmetrically, between the specimen and .hero-foot for line 2's). */
    --fs-hero: clamp(2.2rem, 10.3vw, 7.95rem);
  }
}
.spec canvas { position: absolute; opacity: 0; }
.stage.live .spec canvas { opacity: 1; }
.stage.live .spec img { opacity: 0; }

.spec-halo {
  position: absolute; left: 50%; top: var(--stage-center);
  width: min(70vh, 600px); aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle at 50% 46%,
    rgba(var(--sage-rgb), 0.30), rgba(var(--sage-rgb), 0.07) 58%, transparent 72%);
  z-index: 2;
  will-change: transform, opacity;
  pointer-events: none;
}

/* --- fold 1 layer -------------------------------------------------------
   No z-index here (used to be 1) — a positioned element WITH a z-index
   creates a stacking context, which traps every descendant's own z-index
   inside it. That is fine for .hero-word-line--top (still meant to run
   behind the specimen) but was silently overriding --bottom's own z-index
   below too: "Shape" needs to float clear of the tooth the same way the two
   floating cards do, and as long as this element created a context there was
   no z-index high enough on --bottom to make that happen — the whole
   hero-layer subtree painted as one unit, behind .spec, regardless. Removing
   it lets --top and --bottom set genuinely different stacking without
   restructuring the DOM (both still have to live inside the one <h1> for the
   accessible name to include the whole phrase). */
.hero-layer { position: absolute; inset: 0; }

/* The two lines sit behind the specimen at the edges where they meet it, and
   are tonal rather than black for the same reason the old single line was:
   the frames carry no alpha and composite with multiply, so a light subject
   over dark type would disappear. Decorative — the accessible heading is the
   h1 itself, id="hero-h". */
.hero-word { position: static; margin: 0; }
/* Phones only — see the .hero-lede rule in the narrow build below. The wide
   hero has the specimen and the two cards to say what this is; adding a
   standfirst there would be a third thing competing with the headline. */
.hero-lede { display: none; }

.hero-word-line {
  position: absolute; left: 0; right: 0;
  margin: 0;
  text-align: center;
  font-size: var(--fs-hero);
  font-weight: 800;
  letter-spacing: var(--track-hero);
  line-height: 0.9;
  color: var(--ink);
  white-space: nowrap;
  will-change: transform, opacity;
}
/* Line 1 hangs --line1-offset above the specimen's centre; line 2 hangs
   --word-gap below line 1's OWN bottom edge — see the comment on
   --line1-offset above for why they are independent rather than a
   symmetric pair.

   Both use `top`, never `bottom`, and that is load-bearing, not a style
   choice: .stage-pin is 100svh while the scene is pinned, but grows to `auto`
   (height enough for the whole procedure, laid out in normal flow) the
   moment .is-static applies — reduced motion, or JS never having run at all.
   `bottom: X` measures up from the CONTAINING BLOCK's bottom edge, so the
   same X lands in a wildly different place once that block goes from ~900px
   to several thousand — this element ended up rendered a full 1700px down
   the page, off the bottom of the visible hero entirely. `top: X` measures
   down from the top edge instead, which never moves. */
.hero-word-line--top {
  top: calc(var(--stage-center) + var(--tooth-rise) - var(--line1-offset) - 0.9 * var(--fs-hero));
  z-index: 1; /* behind .spec (3) — the deliberate "little part comes on text" look */
}
.hero-word-line--bottom {
  /* line 1's bottom edge (stage-center + tooth-rise - line1-offset) + the gap */
  top: calc(var(--stage-center) + var(--tooth-rise) - var(--line1-offset) + var(--word-gap));
  /* Back behind .spec (3), matching line 1 — briefly moved to z-index:6 (in
     front of the tooth) to keep "Shape" legible after the tight --word-gap
     hid its "Sh" entirely, now moved back on request: both lines take the
     same occluded, "tooth cuts through the type" treatment. */
  z-index: 1;
}

/* "Take" and "Shape." flank the tooth instead of running as one centred
   phrase through the middle of it. A two-column GRID, not flex with
   justify-content:center on a single run — that distinction matters here,
   not just as a implementation detail. "Shape." is visibly wider than "Take"
   (six letters and a period against four), so centering the pair as ONE
   flex group put the gap's own midpoint off the tooth's centre by half that
   width difference: "Take" cleared the tooth with 52px to spare while the
   tooth ate 105px into "Shape." A two-column grid gives each word its OWN
   equal-width half — "Take" end-aligned in the left one, "Shape." start-
   aligned in the right — so the gap between them sits at exactly 50% of the
   line, matching the tooth's own centre (also 50%), regardless of how the
   two words' widths compare.

   The gap is --flank-gap, the same one the two captions below align to
   (see .hero-caption) — one shared number, so a word and its caption can
   never drift apart from each other. */
.hero-word-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--flank-gap);
  align-items: baseline;
}
.hero-word-take {
  justify-self: end;
  font-weight: 300;
  color: var(--sage-deep);
}
.hero-word-shape { justify-self: start; }
/* .hero-word-shape needs no rule of its own — it inherits color: var(--ink)
   from .hero-word-line, same as "Shape." did as plain trailing text before. */

/* -------------------------------------------------------------------------
   CAPTIONS — the rating and the location, sitting plainly under "Take" and
   "Shape." Normal placement, on request: no rotation, no idle bob, no
   scatter — the opposite of how these two used to behave (floating above
   "Where Great Smiles", tilted, bobbing). Both still sit above .spec
   (z-index 3), because unlike the word, a trust signal has to stay legible
   rather than lose a letter to the specimen.

   Each is a positioned ZONE (.hero-caption--rating / --loc, one per half of
   the line) containing a naturally-sized CARD (.hero-caption-card) pushed to
   the zone's inner edge — the same two-piece structure .hero-word-split
   uses for "Take"/"Shape.", and for the same reason: a caption centred in
   its own half would not line up with a word that is end/start-aligned
   rather than centred in its half. */
.hero-caption {
  position: absolute;
  /* Anchored to the specimen, NOT to line 2 — see --caption-drop for why.
     The line-box term and the 1.3rem stay so the cards still scale with the
     headline's own size at every viewport. */
  top: calc(
    var(--stage-center) + var(--tooth-rise) - var(--caption-drop)
    + 0.9 * var(--fs-hero) + 1.3rem
  );
  z-index: 5;
  display: flex;
  will-change: transform, opacity;
}
.hero-caption--rating {
  left: 0;
  right: calc(50% + var(--flank-gap) / 2);
  justify-content: flex-end; /* card's inner edge meets "Take"'s inner edge */
}
.hero-caption--loc {
  left: calc(50% + var(--flank-gap) / 2);
  right: 0;
  justify-content: flex-start; /* card's inner edge meets "Shape."'s inner edge */
}

.hero-caption-card {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  white-space: nowrap;
}
.hero-caption-card--rating {
  padding: 0.7rem 1.05rem;
  background: var(--white);
  border-radius: var(--r-md);
  box-shadow: 0 4px 10px rgba(var(--deep-rgb), 0.08), 0 22px 46px -18px rgba(var(--deep-rgb), 0.38);
}
.hero-caption-card--loc {
  padding: 0.5rem 1.15rem;
  background: var(--white);
  border: 1.5px solid var(--sage-deep);
  border-radius: var(--r-pill);
  box-shadow: 0 14px 30px -14px rgba(var(--deep-rgb), 0.28);
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--leaf);
}
.hero-caption-stars { display: inline-flex; gap: 2px; color: var(--star); flex: none; }
.hero-caption-stars svg { width: 14px; height: 14px; fill: currentColor; }
.hero-caption-t { font-size: var(--fs-sm); font-weight: 500; color: var(--ink); }
.hero-caption-t b { font-weight: 700; }

.hero-foot {
  /* hung off whichever sits lower: the specimen's own bottom edge, or line
     2's rendered bottom (its own top — see .hero-word-line--bottom — plus
     its line-box height). Either one can be the taller of the two depending
     on --tooth-h and --fs-hero at a given viewport, so this needs the real
     max — anchoring to just one of them left .hero-foot overlapping
     whichever it ignored. */
  position: absolute; left: 0; right: 0;
  top: calc(max(
    var(--stage-center) + var(--tooth-rise) + var(--tooth-h) / 2,
    var(--stage-center) + var(--tooth-rise) - var(--line1-offset) + var(--word-gap) + 0.9 * var(--fs-hero)
  ) + var(--tooth-gap));
  max-width: none;
  /* A centred stack under the specimen: the two cards, then the two actions
     on one line. The three-column band is gone with the copy. */
  display: grid; justify-items: center;
  gap: clamp(0.9rem, 1.6vw, 1.35rem);
  z-index: 4;
}
/* both actions on one line, centred under the cards */
.hero-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.7rem; }

/* -------------------------------------------------------------------------
   HERO CARDS — CDCP and Invisalign, carried over from v7

   v7 drew these as glassmorphism panels. That is on the banned list and would
   not survive on a white ground anyway, so only the content came across: flat
   white card, hairline edge, sage disc for the icon, and the same arrow link
   the rest of the site uses.
   ------------------------------------------------------------------------- */
/* wider than before — the band is no longer sharing the row with two other
   columns, so the cards get the space and the photographs get bigger */
.hero-cards {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.9rem, 1.5vw, 1.3rem);
  width: min(100%, 940px);
}
.hcard {
  /* photo beside the copy, as on the old site */
  display: grid; grid-template-columns: 104px minmax(0, 1fr);
  gap: 1.2rem; align-items: center;
  will-change: transform;
  padding: 1.15rem 1.3rem 1.15rem 1.15rem;
  background: var(--white);
  border-radius: var(--r-sm);
  box-shadow: inset 0 0 0 var(--hair) var(--rule-quiet),
              0 26px 50px -40px rgba(var(--deep-rgb), 0.5);
  transition: box-shadow var(--t-2) var(--ease), transform var(--t-2) var(--ease);
}
/* a circle, cropped by object-position rather than by cutting new files */
/* The sources are 1024-square and the hole is square, so object-fit had
   nothing to crop — it just shrank the whole room into a thumbnail. The zoom
   and focal point are set per image, inline. */
.hcard-pic {
  width: 104px; height: 104px; border-radius: 50%;
  overflow: hidden; flex: none;
  background: var(--n-10);
  box-shadow: 0 0 0 3px var(--white), 0 0 0 4px var(--rule-quiet);
}
.hcard-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hcard-t {
  font-size: var(--fs-md); font-weight: 700; line-height: 1.35;
  letter-spacing: var(--track-tight); color: var(--ink);
}
/* the one word the old site set in its accent colour */
.hcard-t em { font-style: normal; color: var(--accent-text); }
.hcard-s {
  margin-top: 0.38rem;
  font-size: var(--fs-xs); line-height: 1.55; color: var(--ink-mid);
}
.hcard-a {
  display: inline-flex; align-items: center; gap: 0.42rem;
  margin-top: 0.6rem;
  font-size: var(--fs-xs); font-weight: 600; color: var(--accent-text);
  transition: gap var(--t-1) var(--ease);
}
/* an SVG arrow, not a text one — the glyph "→" sits on a different baseline
   in every fallback face and was landing a pixel low next to the label */
.hcard-a svg { width: 13px; height: 13px; flex: none; }
.hcard:hover {
  box-shadow: inset 0 0 0 var(--hair) var(--rule),
              0 30px 56px -38px rgba(var(--deep-rgb), 0.6);
}
@media (prefers-reduced-motion: no-preference) {
  .hcard:hover { transform: translateY(-3px); }
}
.hcard:hover .hcard-a { gap: 0.7rem; }

/* Compact cards on a laptop-height screen — trimmed proportionally, not
   crushed, so the icons stay legible and the band still reads as roomy.
   Must come after the .hcard block above: same specificity, later wins. */
@media (min-width: 1025px) and (max-height: 900px) {
  .hcard { padding: 0.95rem 1.05rem; grid-template-columns: 78px minmax(0, 1fr); gap: 0.95rem; }
  .hcard-pic { width: 78px; height: 78px; }
  .hcard-t { font-size: var(--fs-sm); }
  .hcard-s { font-size: var(--fs-xs); }
}

.hero-meta {
  grid-column: 3; justify-self: end; text-align: right;
  display: grid; gap: 3px;
}
.hero-meta b { font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums; }
.hero-meta span {
  font-size: var(--fs-nano); letter-spacing: var(--track-micro);
  text-transform: uppercase; color: var(--ink-quiet);
}

/* --- fold 2 layer ------------------------------------------------------- */
.proc-layer {
  position: absolute; inset: 0; z-index: 4;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 30vw) minmax(0, 1fr);
  align-items: center;
  gap: 0 clamp(20px, 3vw, 52px);
  padding: 6rem var(--gutter) 4rem;
  /* boxed: the procedure stays inside the page measure so the specimen reads
     as sitting in a container, against fold 3 which runs full bleed */
  max-width: var(--page);
  margin-inline: auto;
  opacity: 0;
  pointer-events: none;
}

.proc-index { grid-column: 1; justify-self: end; width: min(100%, 320px); }
.proc-steps { position: relative; }
.proc-steps::before {
  content: '';
  position: absolute; right: 20px; top: 12px; bottom: 12px;
  width: var(--hair);
  background: linear-gradient(to bottom, transparent, var(--rule) 14%, var(--rule) 86%, transparent);
}
.pstep { position: relative; min-height: 4.9rem; padding-right: 46px; text-align: right; }
.pstep-n {
  font-size: var(--fs-micro); letter-spacing: var(--track-micro);
  color: var(--ink-quiet); font-variant-numeric: tabular-nums;
  transition: color var(--t-2) var(--ease);
}
.pstep-t {
  font-size: var(--fs-lg); font-weight: 600; line-height: 1.25; margin-top: 5px;
  letter-spacing: var(--track-tight);
  color: var(--ink-quiet);
  transition: color var(--t-2) var(--ease);
}
.pstep.done .pstep-t { color: var(--ink-mid); }
.pstep.now .pstep-n { color: var(--accent-text); }
.pstep.now .pstep-t { color: var(--ink); }

.pstep::after {
  content: '';
  position: absolute; right: 15px; top: 1.3rem;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--white);
  box-shadow: inset 0 0 0 var(--hair) var(--n-40);
  transition: transform var(--t-2) var(--ease),
              background-color var(--t-2) var(--ease),
              box-shadow var(--t-2) var(--ease);
}
.pstep.done::after { background: var(--n-40); }
.pstep.now::after {
  background: var(--graphic);
  box-shadow: inset 0 0 0 var(--hair) var(--sage-deep), 0 0 0 7px rgba(var(--sage-rgb), 0.24);
  transform: scale(1.24);
}
.pstep::before {
  content: '';
  position: absolute; left: calc(100% - 14px); top: calc(1.3rem + 5px);
  height: var(--hair); width: clamp(20px, 2.6vw, 44px);
  background: linear-gradient(to right, var(--sage-deep), rgba(var(--sage-deep-rgb), 0));
  transform: scaleX(0); transform-origin: left center; opacity: 0;
  transition: transform var(--t-3) var(--ease), opacity var(--t-2) var(--ease);
}
.pstep.now::before { transform: scaleX(1); opacity: 1; }

.proc-detail { grid-column: 3; display: grid; justify-self: start; width: min(100%, 350px); }
.pcard {
  grid-area: 1 / 1;
  position: relative;
  padding: 24px 26px 20px;
  background: var(--white);
  border: var(--hair) solid var(--rule-quiet);
  border-radius: var(--r-sm);
  box-shadow: 0 34px 70px -50px rgba(var(--deep-rgb), 0.55);
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity var(--t-2) var(--ease), transform var(--t-3) var(--ease);
}
.pcard.now { opacity: 1; transform: none; }
.pcard::before {
  content: '';
  position: absolute; left: 0; top: 0; height: 2px; width: 0;
  background: linear-gradient(to right, var(--graphic), rgba(var(--sage-rgb), 0));
  transition: width var(--t-3) var(--ease);
}
.pcard.now::before { width: 70px; }
.pcard-n {
  display: flex; align-items: baseline; gap: 7px;
  font-size: var(--fs-micro); letter-spacing: var(--track-micro);
  text-transform: uppercase; color: var(--ink-quiet);
  font-variant-numeric: tabular-nums; margin-bottom: 9px;
}
.pcard-n b { font-size: var(--fs-xs); font-weight: 700; color: var(--accent-text); }
.pcard h3 { font-size: var(--fs-xl); font-weight: 700; letter-spacing: var(--track-tight); line-height: 1.18; margin-bottom: 9px; }
.pcard p { font-size: var(--fs-sm); line-height: 1.6; color: var(--ink-mid); }
.pcard dl { margin-top: 16px; padding-top: 4px; border-top: var(--hair) solid var(--rule-quiet); }
.pcard dl div {
  display: grid; grid-template-columns: 7rem minmax(0, 1fr); gap: 10px;
  padding: 6px 0; border-bottom: var(--hair) solid var(--rule-quiet);
}
.pcard dl div:last-child { border-bottom: 0; }
.pcard dt { font-size: var(--fs-nano); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-quiet); padding-top: 0.32em; }
.pcard dd { margin: 0; font-size: var(--fs-xs); font-weight: 500; color: var(--ink); font-variant-numeric: tabular-nums; }

/* =========================================================================
   NARROW — no pin, no scrub. A plain stack: hero, specimen, then the five
   steps each with their own copy.
   ========================================================================= */
@media (max-width: 1024px) {
  .pillnav, .hdr-tel { display: none; }

  .stage-pin { height: auto; overflow: visible; }
  .spec {
    position: relative; left: auto; top: auto;
    transform: none !important; margin: 0 auto var(--sp-4);
  }
  .spec img, .spec canvas { height: min(42vh, 320px); }
  .spec-halo { display: none; }

  /* --- the stacked hero is a real column now ----------------------------
     It used to be a pile of absolutely-positioned pieces held together by a
     15.6rem padding reservation on .hero-layer, with the two badges parked
     on top of that reserved space. It read as loose parts rather than a
     section, and every one of those numbers had to be re-measured by hand
     whenever a font size moved.

     .stage-pin becomes the column and orders its contents outright.
     display:contents on the two wrappers dissolves them so the headline,
     the lede, both badges, the actions and the cards are all siblings of
     that column — which is what makes a single `order` sequence possible
     across elements that live at three different depths in the markup. */
  .stage-pin {
    display: flex; flex-direction: column;
    /* clears the fixed header and nothing more. It is one number now rather
       than the old 15.6rem, which had to cover the header AND the two badges
       parked on top of the reserved space; the badges are ordered flow
       content here, so this only has the header to clear — 72px on a phone,
       79px up at the 1024 end, plus breathing room. */
    padding-top: clamp(6rem, 14vw, 8rem);
  }
  .hero-layer { position: static; padding: 0; display: contents; }
  .hero-foot { position: static; display: contents; }

  .hero-word            { order: 1; }
  .hero-lede            { order: 2; }
  .hero-caption--rating { order: 3; }
  .hero-caption--loc    { order: 4; }
  .hero-actions         { order: 5; }
  .hero-cards           { order: 6; }
  /* 7 and 8 only apply to the un-enhanced stack — once the phone scrub runs,
     the specimen is moved inside .proc-layer and .proc-scrub becomes the item
     that carries the whole procedure. The order still reads the same either
     way: cards, then the tooth, then the steps. */
  .spec                 { order: 7; }
  .proc-layer           { order: 8; }
  .proc-scrub           { order: 8; }

  /* The phone-only standfirst. Sits between the headline and the trust
     badges, which is the one place it reads as an answer to the headline
     rather than as a caption on the badges. */
  .hero-lede {
    display: block;   /* released from the desktop `display: none` */
    padding-inline: var(--gutter);
    margin-bottom: var(--sp-3);
    max-width: 46ch;
    font-size: var(--fs-body); line-height: 1.62;
    color: var(--ink-mid);
  }

  /* Both word lines and both captions render as plain, calm, stacked content
     on a phone. The specimen doesn't overlap anything here (it is
     separately in-flow, position: relative, further down), so there is
     nothing for the lines to bite into. */
  .hero-word-line,
  .hero-word-line--top,
  .hero-word-line--bottom {
    position: static;
    font-size: var(--fs-hero-sm);
    /* --ink, not --n-20. This is the page's h1 and the label for the whole
       stage, and --n-20 is a rule colour: 1.29:1 on white. On a phone the
       headline read as "tooth." with the first half a ghost, while the same
       element one pixel wider is nearly black. Same element, same colour. */
    white-space: normal; color: var(--ink);
    transform: none !important; opacity: 1 !important;
    /* it is not inside .shell, so it needs the gutter itself — and a
       line-height of 0.9 put its descenders straight into the sub-line */
    padding-inline: var(--gutter);
    text-align: left;
  }
  .hero-word-line--top { margin-bottom: 0.15em; }
  .hero-word-line--bottom { margin-bottom: var(--sp-3); }

  /* The flanking split is a wide-viewport trick — the specimen sits separately
     in flow here, not behind the text, so there is no tooth to flank and a
     forced gap would just leave a hole between the two words. display:block
     drops the grid entirely (grid-template-columns/justify-self are no-ops
     once this isn't a grid), so "Take"/"Shape." fall back to plain inline
     spans reading left to right, same as line 1. */
  .hero-word-split { display: block; }
  .hero-word-take { justify-self: auto; }
  .hero-word-shape { justify-self: auto; margin-left: 0.28em; }

  /* Still absolute, not static: both captions live in the DOM as siblings of
     .hero-layer (moved there for their desktop z-index — see the comment by
     the markup), so on this stacked, DOM-order-is-visual-order layout, static
     would drop them after the cards and actions instead of above the
     headline. .stage-pin stays position:relative at every width, so
     anchoring here against the top padding .hero-layer reserves for exactly
     this works without needing a flex re-order of the whole stage.

     left/right/bottom are all reset here — the desktop rule gives each
     caption an asymmetric HALF of the line (e.g. `left:0; right:calc(50% +
     …)`) so it can align to its word's inner edge; on a single stacked
     column there is no "half" to align to, so both go back to spanning the
     full width. */
  /* Static, not absolute. They were positioned off .hero-layer's reserved
     padding purely because DOM order would otherwise have dropped them below
     the cards; the column above orders them explicitly instead, so they can
     just be flow content — no top offsets to re-measure. */
  .hero-caption {
    position: static; inset: auto;
    padding-inline: var(--gutter);
    justify-content: flex-start;
    transform: none !important; opacity: 1 !important;
  }
  .hero-caption--rating { margin-bottom: 0.5rem; }
  .hero-caption--loc { margin-bottom: var(--sp-3); }

  .hero-cards { width: 100%; padding-inline: var(--gutter); margin-top: var(--sp-4); }

  /* Both CTAs on one line, per the brief. They only fit side by side once the
     label stops wrapping and the pill's own padding comes in, so the two are
     done together — nowrap alone would just overflow the gutter. */
  .hero-actions {
    justify-content: flex-start;
    flex-wrap: nowrap;
    padding-inline: var(--gutter);
    gap: 0.5rem;
  }
  .hero-actions .btn {
    white-space: nowrap;
    font-size: var(--fs-xs);
    padding: 0.66rem 0.5rem 0.66rem 0.9rem;
    transform: none !important;
  }
  .hero-actions .btn i { width: 24px; height: 24px; }
  .hero-actions .btn i svg { width: 12px; height: 12px; }
  /* the cards no longer travel; on the stacked build there is nothing to
     make way for */
  .hcard { transform: none !important; }

  .proc-layer {
    position: static; opacity: 1; pointer-events: auto;
    grid-template-columns: minmax(0, 1fr);
    padding: 0 var(--gutter) var(--fold);
  }
  .proc-index, .proc-detail { grid-column: 1; justify-self: stretch; width: auto; }
  .proc-steps::before { display: none; }
  .pstep {
    min-height: 0; padding: 0 0 var(--sp-3) 20px; text-align: left;
    border-left: 2px solid var(--rule-quiet);
  }
  .pstep.now { border-left-color: var(--graphic); }
  .pstep::after, .pstep::before { display: none; }
  .proc-detail { display: block; margin-top: var(--sp-4); }
  .pcard {
    opacity: 1; transform: none; box-shadow: none;
    border: 0; border-top: var(--hair) solid var(--rule-quiet);
    border-radius: 0; padding: var(--sp-3) 0 var(--sp-4);
  }
  .pcard::before { display: none; }

  /* =======================================================================
     THE PHONE SCRUB  (.is-scrub — added by site.js only once the stage has
     been rebuilt and the frames are loading, so everything above stays the
     fallback for reduced motion, no JS, or a failed frame set)

     One sticky screen: the five steps as a horizontal rail across the top
     with the live one centred, the specimen under it, and that step's card
     beneath. A runway below supplies the scroll distance the rail, the frame
     sequence and the card all read from.

     Every selector here is .stage.is-static.is-scrub, not .stage.is-scrub.
     .is-scrub is only ever added on top of .is-static, and the
     `.stage.is-static .proc-layer { position: static }` / `.pcard { opacity:1 }`
     block further down this file is the same two-class weight and later in
     source — at .stage.is-scrub it won, the sticky silently degraded to
     static and all five cards stayed lit. The extra class settles it on
     specificity so source order stops mattering.
     ======================================================================= */
  .stage.is-static.is-scrub .proc-scrub { position: relative; }
  /* 5 steps over ~3 screens — roughly half a screen of scroll per step, which
     is slow enough to read the card before the next one takes over. */
  .stage.is-static.is-scrub .proc-runway { height: 300svh; }

  /* No side gutter on the stage itself — the specimen and the rail both want
     the whole width, and inset by --gutter the tooth read as a small picture
     in a wide margin rather than as the subject. Only the card takes an
     inset, and a tighter one than the page gutter, because it is the one
     thing here that is a line of text needing a measure. */
  .stage.is-static.is-scrub .proc-layer {
    position: sticky; top: 0;
    height: 100svh;
    display: flex; flex-direction: column; gap: 0.7rem;
    /* the header is fixed and overlays this */
    padding: calc(73px + 0.6rem) 0 var(--sp-3);
  }

  /* .proc-layer is a flex column too now, so the order values set on .spec
     for the OUTER column still apply to it in here — and order:7 against the
     other two children's default 0 dropped the specimen below the card. Each
     child of this stage states its own place. */
  .stage.is-static.is-scrub .proc-index  { order: 1; }
  .stage.is-static.is-scrub .spec        { order: 2; }
  .stage.is-static.is-scrub .proc-detail { order: 3; }

  /* --- the rail --------------------------------------------------------- */
  /* width:100% and min-width:0, not `auto`. The rail's five slots come to
     200% of it by design, and on `auto` the element sized to that content
     instead of to the column — 459px inside a 390px screen, which put a real
     horizontal scrollbar on the page. It has to be exactly the container's
     width for overflow:hidden to have the right box to clip against. */
  .stage.is-static.is-scrub .proc-index {
    flex: none; overflow: hidden;
    width: 100%; min-width: 0; max-width: 100%;
    box-sizing: border-box;
  }
  .stage.is-static.is-scrub .proc-steps { min-width: 0; }
  /* Each step is exactly the content-box width (70% of the rail once the 15%
     padding either side is taken), so ONE step is centred and its neighbours
     sit half-visible at each edge. Stepping is therefore a flat -70% of the
     rail per index — no per-item measuring, and it holds at any width. */
  /* 25% either side, so a slot is half the rail: the live step sits centred
     and its neighbours land exactly on the two edges, half in frame. At the
     15%/70% first cut a slot was wide enough that the neighbouring labels
     fell outside the rail entirely and the "faded either side" read was lost
     — the rail just looked like one word. */
  .stage.is-static.is-scrub .proc-steps {
    display: flex; padding-inline: 30%;
    transform: translateX(calc(var(--step, 0) * -40%));
    transition: transform var(--t-3) var(--ease);
  }
  /* Fades the ends rather than cutting them, so the rail reads as running on
     past the frame. Only the outer tenth: each neighbour's label is centred
     in its own slot and those centres land ~40% out from the middle, so a
     deeper fade erased the very text it was meant to be softening. */
  .stage.is-static.is-scrub .proc-index {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
  }
  .stage.is-static.is-scrub .pstep {
    flex: 0 0 100%;
    min-height: 0; padding: 0; border: 0; text-align: center;
    opacity: 0.25;
    transition: opacity var(--t-2) var(--ease);
  }
  .stage.is-static.is-scrub .pstep.now { opacity: 1; }
  .stage.is-static.is-scrub .pstep-n {
    font-size: var(--fs-nano); letter-spacing: var(--track-micro);
    color: var(--ink-quiet); margin-bottom: 0.15rem;
  }
  .stage.is-static.is-scrub .pstep.now .pstep-n { color: var(--accent-text); }
  /* nowrap in a 40%-wide slot, so the longest label ("Polished & Checked")
     sets the ceiling for all five */
  .stage.is-static.is-scrub .pstep-t {
    font-size: var(--fs-sm); font-weight: 600;
    letter-spacing: var(--track-tight); white-space: nowrap;
    color: var(--ink);
  }
  /* the live step is the screen's heading, so it carries a size the four
     ghosts either side do not */
  .stage.is-static.is-scrub .pstep.now .pstep-t { font-size: var(--fs-lg); }

  /* --- the specimen ----------------------------------------------------- */
  .stage.is-static.is-scrub .spec { flex: 1 1 auto; min-height: 0; margin: 0; }
  .stage.is-static.is-scrub .spec img,
  .stage.is-static.is-scrub .spec canvas {
    height: 100%; width: 100%; max-height: 100%;
    object-fit: contain;
  }

  /* --- the card --------------------------------------------------------- */
  /* The active card is in flow so the box takes its height; the other four
     are stacked on top of it at zero opacity, which is what lets them cross
     over rather than jump. */
  .stage.is-static.is-scrub .proc-detail {
    position: relative; flex: none; margin-top: 0;
    padding-inline: 1.15rem;
    max-height: 44svh; overflow: hidden;
  }
  .stage.is-static.is-scrub .pcard {
    position: absolute; inset: 0 1.15rem auto 1.15rem;
    opacity: 0; pointer-events: none;
    margin: 0; padding: 0; border: 0;
    transition: opacity var(--t-2) var(--ease);
  }
  .stage.is-static.is-scrub .pcard.now { position: relative; inset: auto; opacity: 1; pointer-events: auto; }

  /* --- type ------------------------------------------------------------
     The card is the only sustained read on this screen and it was set two
     steps down from the page's body size to make room. Sizes here are
     picked against that: the heading drops so the paragraph can come up,
     which is the right way round — the step is already named twice above,
     in the rail and in the "03 — of 05" line. */
  .stage.is-static.is-scrub .pcard h3 {
    font-size: var(--fs-lg); line-height: 1.22; margin-bottom: 6px;
  }
  .stage.is-static.is-scrub .pcard p { font-size: var(--fs-sm); line-height: 1.62; }
  .stage.is-static.is-scrub .pcard dl { margin-top: 0.7rem; }
  .stage.is-static.is-scrub .pcard dl div { padding: 0.34rem 0; }
  .stage.is-static.is-scrub .pcard dd { font-size: var(--fs-sm); }
}

/* On a phone two card columns leave ~150px each and the copy shreds into
   six-line paragraphs. */
@media (max-width: 560px) {
  .hero-cards { grid-template-columns: minmax(0, 1fr); }
}

/* A short phone (a 568pt SE, or any handset with the browser chrome showing)
   leaves ~250px under the specimen and the step cards want ~330. The
   definition list is the part that can go: it is supplementary detail, and
   every one of its rows restates something the paragraph above has already
   said in a sentence. Clipping it silently — which is what max-height alone
   did — was the alternative. */
@media (max-width: 1024px) and (max-height: 680px) {
  .stage.is-static.is-scrub .pcard dl { display: none; }
  .stage.is-static.is-scrub .pcard p { font-size: var(--fs-xs); line-height: 1.55; }
}

/* Static: reduced motion on a wide screen. Without this the four inactive
   cards sit at opacity 0 and nothing will ever advance the step. */
.stage.is-static .stage-pin { height: auto; overflow: visible; }
.stage.is-static .hero-layer { position: static; padding-top: 7rem; display: block; }
.stage.is-static .hero-word-line { transform: none !important; opacity: 1 !important; }
.stage.is-static .hero-caption { transform: none !important; opacity: 1 !important; }

/* On a narrow viewport .is-static is always present too (wideQ fails
   regardless of motion preference), and .stage.is-static .hero-layer above
   — same specificity, declared first — would otherwise win over the
   @media (max-width: 1024px) rule and silently put the padding back to 7rem.
   This has to out-order it, not just out-specify it: same two-class
   specificity, later in the source.

   It now carries display too, for the same reason: the narrow build turns
   .stage-pin into an ordered flex column and needs this wrapper dissolved so
   the headline, lede, badges and actions become items of it. The plain
   `.hero-layer { display: contents }` in the media query above is a single
   class and loses to the is-static rule every time. */
@media (max-width: 1024px) {
  .stage.is-static .hero-layer { padding-top: 0; display: contents; }
}
.stage.is-static .spec {
  position: relative; left: auto; top: auto;
  transform: none !important; margin-inline: auto;
}
.stage.is-static .spec-halo { display: none; }
.stage.is-static .proc-layer {
  position: static; opacity: 1; pointer-events: auto;
  grid-template-columns: minmax(0, 1fr); padding-top: var(--fold);
}
.stage.is-static .proc-index,
.stage.is-static .proc-detail { grid-column: 1; justify-self: stretch; width: auto; }
.stage.is-static .proc-detail { display: block; }
.stage.is-static .pcard { opacity: 1; transform: none; }

/* =========================================================================
   FOLD 3 — WHY WE STAND APART

   Rebuilt and compacted. The nested white card is gone — everything sits on
   the green panel directly, which is where most of the old height went. The
   composition is a connected 1-2-3 flow, then text | image | text.
   ========================================================================= */
.apart {
  position: relative; z-index: 1;   /* sits under the pin while it is still pinned */
  background: var(--white);
  scroll-margin-top: 88px;
  padding-block: clamp(3rem, 6vw, 6rem);
}
/* Wider than the page rail, which is the point — this panel is meant to run
   broader than the boxed procedure above it. But "gutter only" meant that on
   a 1600 screen the panel's own type started at 115 while every other fold on
   the page started at 184, and the eye reads that as a mistake rather than as
   a device.

   Capping the shell at the page plus the panel's own padding puts the panel
   edge exactly one padding outside the rail, so the type inside it lands on
   the rail with the rest of the page and only the surface runs wide. Below
   ~1450 the cap is inactive and the panel is gutter-to-gutter as before. */
.apart { --apanel-pad: clamp(1.5rem, 3.2vw, 3.2rem); }
.apart > .shell { max-width: calc(var(--page) + var(--apanel-pad) * 2); }
.apart-panel {
  position: relative;
  /* two sage washes under 10% alpha — tonal depth, not a colour ramp */
  background:
    radial-gradient(120% 90% at 85% -10%, rgba(var(--sage-rgb), 0.10), transparent 55%),
    radial-gradient(90% 70% at 0% 110%, rgba(var(--sage-rgb), 0.06), transparent 60%),
    var(--surface-deep);
  border-radius: var(--r-md);
  padding: clamp(1.8rem, 3.2vw, 3.2rem) var(--apanel-pad);
  box-shadow: inset 0 0 0 1px var(--rule-invert),
              0 40px 80px -50px rgba(var(--deep-rgb), 0.8);
}

/* --- the 1-2-3 flow ------------------------------------------------------ */
/* --- top row: white feature card + quote card, v7's ~55/45 split -------- */
.apart-top {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  gap: clamp(1.2rem, 2.4vw, 2rem);
  align-items: stretch;
}

.apart-card {
  background: var(--white);
  border-radius: var(--r-md);
  padding: clamp(1.7rem, 3vw, 2.6rem);
  box-shadow: 0 30px 60px -40px rgba(var(--deep-rgb), 0.5);
}
.apart-card .apart-eyebrow { color: var(--ink-quiet); }
.apart-card .apart-h {
  color: var(--ink);
  margin-top: 0.6rem;
  margin-bottom: clamp(1.3rem, 2.2vw, 1.9rem);
}
.apart-card .apart-h em { color: var(--accent-text); }

.apart-check { display: grid; gap: clamp(1rem, 1.8vw, 1.4rem); }
.apart-check li { display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 0.85rem; align-items: start; }
.apart-tick {
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; margin-top: 0.1rem;
  background: var(--n-05); color: var(--sage-deep);
  box-shadow: inset 0 0 0 var(--hair) var(--rule-quiet);
}
.apart-tick svg { width: 13px; height: 13px; display: block; }
.apart-check h3 { font-size: var(--fs-md); font-weight: 700; letter-spacing: var(--track-tight); color: var(--ink); line-height: 1.3; }
.apart-check p { margin-top: 0.3rem; font-size: var(--fs-sm); line-height: 1.65; color: var(--ink-mid); }

.apart-quote {
  display: flex; flex-direction: column; justify-content: center;
  gap: clamp(1.1rem, 2vw, 1.6rem);
  background: var(--deep-soft);
  border-radius: var(--r-md);
  padding: clamp(1.7rem, 3vw, 2.4rem);
  box-shadow: inset 0 0 0 1px var(--rule-invert);
}
/* Absorbs whatever height the quote and button leave, rather than carrying a
   fixed aspect ratio. The column runs 407-669px wide across viewports while
   the spare height barely moves, so any single ratio would fill the gap at
   one width and overshoot the card at another. */
.apart-shot {
  position: relative;
  flex: 1 1 auto; min-height: 130px;
  margin: 0; border-radius: var(--r-md); overflow: hidden;
  background: var(--deep);
  box-shadow: inset 0 0 0 1px var(--rule-invert);
}
/* Zoomed and pushed right, deliberately. The practice only has two
   photographs, and this one also fills fold 9's Visit panel at close to its
   native framing — shown wide here too, the reader meets the same room shot
   twice in one scroll and it reads as a stock library running thin. Enlarged
   to 190% and offset, the frame lands on the chair and window instead of the
   whole room, so the two uses read as two pictures.

   Sized rather than transformed: GSAP owns the transform on this figure's
   parent for the entrance, and nesting a second transform here would fight
   the crop as the card slides in. */
.apart-shot img {
  position: absolute;
  top: -46%; left: -58%;
  width: 190%; height: 190%; max-width: none;
  object-fit: cover; object-position: 50% 50%; display: block;
}

.apart-quote p {
  font-size: var(--fs-md); line-height: 1.8; color: var(--on-deep-mid);
  padding-left: 1rem; border-left: 2px solid var(--sage);
}
.apart-quote .btn { align-self: flex-start; }

/* --- bottom row: video + vertical timeline, ~62/38 -------------------------
   38% sits inside the 30-40% asked for. */
.apart-bottom {
  display: grid;
  grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr);
  gap: clamp(1.2rem, 2.4vw, 2rem);
  margin-top: clamp(1.2rem, 2.4vw, 2rem);
  align-items: stretch;
}

.apart-video {
  /* centre, not stretch: the iframe carries its own 16:9 ratio, so stretching
     the figure only added dead background under the player. Where the step
     stack runs taller, centring splits the difference above and below rather
     than pooling it all at the bottom. */
  align-self: center;
  margin: 0; border-radius: var(--r-md); overflow: hidden;
  background: var(--deep-soft);
  box-shadow: inset 0 0 0 1px var(--rule-invert),
              0 34px 70px -34px rgba(var(--shade-rgb), 0.72);
}
/* border-radius does not clip an iframe's own content — only the parent's
   overflow:hidden does that; inherit keeps the corners in step regardless */
.apart-video iframe {
  width: 100%; height: auto; aspect-ratio: 16 / 9;
  display: block; border: 0; border-radius: inherit;
}

/* Each step is its own card now, so the outer element is only a column —
   a panel inside a panel inside a panel read as far too much boxing. */
.vstep-card { position: relative; display: block; }

/* One gap value drives both the space between steps and the height of the
   connector that fills it — they have to be the same number or the line
   floats short of the next capsule. */
.vstep-list {
  --vgap: clamp(0.7rem, 1.4vw, 1.05rem);
  display: grid;
  /* 1fr rows off a full-height list: the three cards split the column
     equally, so they are the same height whatever the copy wraps to, and
     the stack always lands exactly on the video's height beside it */
  grid-auto-rows: minmax(0, 1fr);
  gap: var(--vgap);
  height: 100%;
}

.vstep {
  position: relative;
  display: grid; align-content: center; justify-items: center; gap: 0.32rem;
  text-align: center;
  padding: clamp(0.75rem, 1.5vw, 1.05rem) clamp(0.9rem, 1.8vw, 1.35rem);
  background: var(--deep-soft);
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 1px var(--rule-invert);
}
/* the capsule, then the name beneath it */
.vstep-cap {
  display: inline-flex; align-items: center;
  padding: 0.42rem 1rem;
  border-radius: var(--r-pill);
  background: var(--sage); color: var(--deep);
  font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: var(--track-micro); text-transform: uppercase;
  box-shadow: 0 10px 22px -12px rgba(var(--sage-rgb), 0.55);
}
.vstep-name {
  font-size: var(--fs-md); font-weight: 700; letter-spacing: var(--track-tight);
  color: var(--on-deep); line-height: 1.35; max-width: 24ch;
}
.vstep-txt {
  font-size: var(--fs-xs); line-height: 1.45;
  color: var(--on-deep-mid); max-width: 34ch;
}

/* The connector fills the gap below each step. Absolute, so it occupies no
   layout space and cannot push the gap open wider than --vgap. */
.vstep-link {
  position: absolute; z-index: 1;
  top: 100%; left: 50%;
  width: 2px; height: var(--vgap);
  margin-left: -1px;
  background: var(--rule-invert);
  border-radius: var(--r-sm);
  overflow: hidden;
  transform-origin: top center;
}
.vstep-link::after {
  content: '';
  position: absolute; left: 0; right: 0; top: 0; height: 70%;
  border-radius: var(--r-sm);
  background: linear-gradient(180deg, transparent, var(--sage) 50%, transparent);
  transform: translateY(-140%);
}
@media (prefers-reduced-motion: no-preference) {
  .vstep-link::after { animation: vstep-run 2.6s cubic-bezier(0.45, 0, 0.55, 1) infinite; }
  /* half a cycle behind, so the pulse reads as one thing travelling the whole
     timeline rather than two firing in unison */
  .vstep:nth-child(2) .vstep-link::after { animation-delay: 1.3s; }
}
@keyframes vstep-run {
  from { transform: translateY(-140%); }
  to   { transform: translateY(280%); }
}

@media (max-width: 1024px) {
  .apart { scroll-margin-top: 112px; }
  .apart-top, .apart-bottom { grid-template-columns: minmax(0, 1fr); }
  /* nothing to absorb once the cards are their natural height */
  .apart-shot { flex: none; aspect-ratio: 16 / 9; }
  /* no video beside it to match, so the cards take their natural height */
  .vstep-list { height: auto; grid-auto-rows: auto; }
  .apart-bottom { margin-top: var(--sp-4); }
  /* the rail is a vertical device between two discs at a known gap; stacked
     under a narrower card the gap is still known, so it is kept rather than
     hidden — only the horizontal flow-rail depended on a row layout */

  /* Stacking the two columns hands each card the panel's full width, and the
     copy inside had nothing holding its measure: 115 characters a line at
     1024, 92 at 834. The cap only bites on tablets — a phone is already well
     under it — and a measure is what this token exists for. */
  .apart-check p, .apart-quote p { max-width: 66ch; }
}

/* =========================================================================
   FOLD 3 ON A PHONE — full bleed

   640, not 1024. The stacked build starts at 1024, but a full-width dark
   panel only works while the line length works: at 1024 the list paragraph
   measured 110 characters and at 834 it was 87, both far past the ~75 a
   reader tracks comfortably. Tablets keep the inset, rounded panel and the
   column measure that comes with it; phones, where full width buys real
   space, get the bleed.
   ========================================================================= */
@media (max-width: 640px) {
  /* --- fold 5 gets the same bleed ---------------------------------------
     The life-stages panel is the page's other dark surface and was carrying
     the same three-deep boxing: gutter, panel radius and padding, then each
     stripe's own radius. Edge to edge it reads as a band the page passes
     through rather than as a wide card, and the stripes gain the ~40px the
     gutter was holding. Its overflow is already hidden — the photos scale on
     hover — so the corners had nothing else to do.

     Scoped `.stages .stages-panel`, not `.stages-panel`: fold 5's own CSS is
     further down this file than this block, so at equal weight the base
     rule's border-radius won on source order and the corners stayed
     rounded. */
  .stages > .shell { padding-inline: 0; max-width: none; }
  .stages .stages-panel {
    border-radius: 0;
    padding: clamp(1.8rem, 6vw, 2.6rem) clamp(0.7rem, 2.6vw, 1rem) clamp(0.7rem, 2.6vw, 1rem);
  }
  /* the head is centred inside the panel, so it needs its own inset once the
     panel stops providing one */
  .stages .stages-head { padding-inline: 0.6rem; }
  /* --fs-lead is a standfirst size and set the stripe copy to 24 characters
     a line at 390 (19 at 320). At the page's body size the same column runs
     27 — still tight, but a readable measure rather than a column of
     three-word lines. */
  .stages .lband-copy p { font-size: var(--fs-body); line-height: 1.6; }

  /* This section was three boxes deep on a phone: page gutter, then the dark
     panel's radius and padding, then a white card's radius and padding. The
     copy started ~60px into a 390px screen and the panel read as a card
     rather than as the ground.

     The gutter and the radius both go, so the dark surface runs edge to edge
     and its own padding — reduced, since it is now the only inset before the
     cards — does the spacing. The white cards inside keep their radius: they
     are meant to read as cards ON this surface, and that is the one level of
     boxing worth keeping. */
  .apart > .shell { padding-inline: 0; max-width: none; }
  .apart-panel {
    border-radius: 0;
    padding: clamp(1.6rem, 5vw, 2.4rem) clamp(0.9rem, 3.4vw, 1.4rem);
    /* the inset hairline drew a line down the screen edge once square */
    box-shadow: 0 40px 80px -50px rgba(var(--deep-rgb), 0.8);
    /* The entrance in site.js starts the two top cards at x:-28 and x:+28,
       and GSAP applies that "from" state at load. With a gutter either side
       the offset had somewhere to go; full-bleed it put the quote card's
       right edge 14px past the viewport and gave the whole page a horizontal
       scrollbar. The panel is the natural place to contain its own children's
       entrance — .stages-panel already clips for the same reason. */
    overflow: hidden;
  }

  /* The list is the substance of this panel and it was set at --fs-sm, two
     steps under the page's body size, because on the wide build it shares a
     row with a second card. Stacked full-width there is no such constraint,
     so the copy comes back up to a reading size — 63 characters at 640px,
     33 at 390 — and the headings move with it. */
  .apart-check h3 { font-size: var(--fs-lg); }
  .apart-check p  { font-size: var(--fs-body); line-height: 1.6; }
  .apart-quote p  { font-size: var(--fs-body); line-height: 1.62; }
  .vstep h3       { font-size: var(--fs-lg); }
  .vstep p        { font-size: var(--fs-sm); line-height: 1.6; }
}

/* =========================================================================
   FOLD 4 — THE DENTISTS

   Alternating spreads, so it reads differently again from the register above
   it and the stage above that. Both portraits were shot against clashing
   backdrops — one vivid cobalt, one grey studio sweep — and were keyed onto a
   single palette neutral so the two can sit on the same page.
   ========================================================================= */
.docs { position: relative; z-index: 1; background: var(--white); scroll-margin-top: 88px; }
/* Title left, standfirst right, baselines aligned. Stacked, this header ran
   262px tall on its own — a third of the screen budget for a fold that has to
   hold two people. */
.docs-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
  gap: clamp(1.2rem, 3vw, 3.5rem);
  align-items: end;
  padding-bottom: clamp(1.6rem, 3vw, 2.6rem);
}
.docs-head .lead { max-width: 44ch; padding-bottom: 0.35rem; }

/* The two sit side by side rather than as full-width alternating rows. That
   alternation was the whole reason this section ran to 1919px — two rows of a
   0.58/1 grid at 4.6rem of vertical padding each — and the mirrored second row
   bought nothing that the pairing does not say more plainly. */
.docs-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.75rem);
}

/* Each dentist is now a card, and the card is laid out landscape — portrait in
   a fixed column on the left, everything else beside it. A portrait ABOVE the
   text would have been the obvious card, and would have put the two frames'
   full height back into the fold; side by side, the tallest thing in the row
   is the text, and the photo fills to match it. */
.doc {
  display: grid;
  grid-template-columns: clamp(120px, 13vw, 178px) minmax(0, 1fr);
  gap: clamp(1rem, 1.8vw, 1.6rem);
  /* stretch, not start: the portrait fills whatever height the copy beside it
     needs, so the two cards read as one band rather than two photos floating
     above their own whitespace */
  align-items: stretch;
  padding: clamp(1.1rem, 1.8vw, 1.5rem);
  background: var(--white);
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 var(--hair) var(--rule),
              0 26px 62px -46px rgba(var(--deep-rgb), 0.5);
  transition: box-shadow var(--t-2) var(--ease);
}
.doc:hover {
  box-shadow: inset 0 0 0 var(--hair) var(--rule),
              0 30px 66px -40px rgba(var(--deep-rgb), 0.55);
}
.doc-shot { margin: 0; position: relative; }
/* so the link can be pushed to the card's floor and both cards' links share a
   baseline however unevenly the two bios run */
.doc-body { display: flex; flex-direction: column; }

/* The frame is the clipping mask GSAP wipes open; the image scales down inside
   it, so the portrait settles rather than simply appearing.

   4:5 rather than the source's square. Both files are 900x900 with the sitter
   centred, so cover crops 10% off each side and nothing off the top or bottom
   — the crop cannot cut a head however the column resizes, which is why no
   focal point is declared. The declared ratio is also what stops the two rows
   drifting apart again if a future portrait arrives at another size.

   The inset hairline survives the wipe: GSAP leaves clip-path at inset(0%),
   which clips at the border box, and an inset shadow is drawn inside it. */
/* Height comes from the row, not from a ratio: the column is a fixed 120-178px
   and the card's height is set by the copy, so a declared 4/5 left the frame
   short of the card's bottom edge by ~80px. The floor keeps it portrait if a
   bio is ever trimmed to nothing. cover means no ratio can crop a head. */
.doc-frame {
  display: block; position: relative;
  height: 100%; min-height: 11rem;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--n-10);       /* the plate the portraits were keyed onto */
  box-shadow: inset 0 0 0 var(--hair) var(--rule);
}
.doc-shot img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
}
/* Dr. Lotfi was photographed further back than Dr. Shaker: same 900x900 plate,
   but her head measures about a quarter smaller in frame, which is what still
   made the matched pair look unmatched once the two frames were the same size.
   Her image box is grown past the frame instead — mostly downward, so the head
   stays put and the extra scale falls off the bottom. Done with the box rather
   than a transform because GSAP owns transform on this element for the wipe. */
.doc--lotfi .doc-shot img {
  inset: -3% auto auto -11%;
  /* sized, not inset-stretched: an absolutely positioned replaced element with
     auto width takes its intrinsic size, and the reset's max-width:100% then
     pulls it back inside the frame — which letterboxed her instead of zooming
     her. Hence the explicit 122% and the max-width release. */
  width: 122%; height: 122%; max-width: none;
}

/* A real element, not a pseudo — GSAP cannot animate ::after, and this rule is
   now drawn on entry rather than on hover. Same sage device as the register
   rows and the procedure cards.

   Inside the card it sits ON the frame's lower edge rather than 0.9rem below
   it: hanging under the portrait it fell into the gap between the photo and
   the card's padding and read as a stray mark rather than an underline. */
.doc-rule {
  position: absolute; left: 0; bottom: 0;
  height: 3px; width: 62%;
  border-radius: 0 var(--r-pill) var(--r-pill) 0;
  background: linear-gradient(to right, var(--graphic), rgba(var(--sage-rgb), 0));
  transform-origin: left center;
}

/* the number carries the same sage tick as .eyebrow, so the two label devices
   on the page are one device */
.doc-n {
  display: inline-flex; align-items: center; gap: 0.7rem;
  font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: var(--track-micro); color: var(--ink-quiet);
}
.doc-n::after { content: ''; width: 28px; height: 2px; background: var(--graphic); flex: none; }
/* d2 down to d3's range: at the old size two names side by side in half-width
   columns wrapped to two lines each and set the row's height by themselves. */
.doc-name {
  margin-top: 0.5rem;
  font-size: var(--fs-d1);
  font-weight: 700; letter-spacing: var(--track-display); line-height: 1.12;
}
.doc-cred {
  margin-top: 0.3rem;
  font-size: var(--fs-sm); font-weight: 600;
  color: var(--accent-text);
}
.doc-bio {
  margin-top: 0.7rem;
  font-size: var(--fs-sm); line-height: 1.68;
  color: var(--ink-mid);
}
/* Was a three-row definition list — 8rem of label column, a rule under every
   row, ~150px tall. The labels were doing no work the values did not already
   carry ("Degree: BDS, Baghdad University"), so the values became chips and the
   labels went. Same three facts, a third of the height. */
.doc-facts {
  margin-top: 0.85rem;
  display: flex; flex-wrap: wrap; gap: 0.32rem;
}
.doc-facts li {
  padding: 0.24rem 0.6rem;
  font-size: var(--fs-nano); font-weight: 600;
  color: var(--ink-mid);
  background: var(--surface-quiet);
  border-radius: var(--r-pill);
  box-shadow: inset 0 0 0 var(--hair) var(--rule-quiet);
}

.doc-link {
  display: inline-flex; align-items: center; gap: 0.55rem;
  margin-top: var(--sp-3);
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink);
  box-shadow: 0 1px 0 var(--rule);
  transition: color var(--t-1) var(--ease), box-shadow var(--t-1) var(--ease);
}
.doc-link i { font-style: normal; transition: transform var(--t-1) var(--ease); }
.doc-link:hover { color: var(--accent-text); box-shadow: 0 1px 0 var(--accent-text); }
.doc-link:hover i { transform: translateX(4px); }
/* scoped, because .doc-link is also fold 6's "All treatments" and that one
   sits in a row of --fs-micro labels rather than under a bio */
.doc-body .doc-link {
  margin-top: auto; padding-top: 0.9rem;
  align-self: start;   /* the underline stops at the text, not the column */
  font-size: var(--fs-sm);
}

/* Two cards side by side stop fitting well before the grid does: below ~1080
   each column is too narrow for a name plus a portrait beside it. One column
   of two landscape cards is still far shorter than the old alternating rows. */
@media (max-width: 1080px) {
  .docs-grid { grid-template-columns: minmax(0, 1fr); }
  .doc { max-width: 34rem; }
}

@media (max-width: 1024px) {
  /* the header wraps to two lines on a phone, so an anchor jump needs more
     clearance or the eyebrow lands underneath it */
  .reg, .docs { scroll-margin-top: 112px; }
  .docs-head { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .docs-head .lead { max-width: none; padding-bottom: 0; }
}

/* Only here does the portrait go above the text: under ~520px a 120px column
   beside the copy leaves the bio setting at about 20 characters a line. */
/* Under ~520 the portrait goes above the copy and takes the card's full
   width — at the old 220px cap it sat in a 350px card with a third of the
   row left empty beside it, which read as an avatar rather than as the
   photograph of the person the card is about.

   Full width means the card's own edges, not its padding box: the padding
   moves off .doc and onto .doc-body so the image can meet the card's sides,
   and .doc clips so the top two corners follow its radius. */
@media (max-width: 520px) {
  .doc {
    grid-template-columns: minmax(0, 1fr);
    gap: 0; padding: 0; overflow: hidden;
  }
  .doc-shot { max-width: none; }
  /* 1/1, not the 4/5 used elsewhere: the sources are 900x900, so a square
     frame at full width crops nothing at all — and 4/5 across a 350px card
     is a 437px-tall photograph before the name is even reached. */
  .doc-frame { height: auto; aspect-ratio: 1 / 1; border-radius: 0; }
  .doc-body { padding: clamp(1.1rem, 4.5vw, 1.5rem); }

  /* Type matched to the phone treatment used on fold 3: the bio is the
     card's substance and was set at --fs-sm because on the wide build it
     shares a row with a 178px portrait. Stacked full-width that constraint
     is gone, so it comes up to a reading size and the supporting lines
     move with it. */
  .doc-name  { font-size: var(--fs-d2); }
  .doc-cred  { font-size: var(--fs-md); margin-top: 0.35rem; }
  .doc-bio   { font-size: var(--fs-body); line-height: 1.6; margin-top: 0.8rem; }
  .doc-facts li { font-size: var(--fs-xs); padding: 0.3rem 0.7rem; }
  .doc-body .doc-link { font-size: var(--fs-md); }
}

/* =========================================================================
   FOLD 5 — CARE FOR EVERY STAGE

   One dark rounded panel on white — v7's container — holding three 240px
   stripes. Each photo has one side the subject does not occupy; the wash goes
   there and the copy sits on the wash — left, right, left — so type never
   lands on a face. Measured before choosing: the boy leaves the left clear,
   the woman leaves the right clear (that side is pure backdrop), and the
   couple's frame is even enough to take either.

   The stripes used to run full bleed against the viewport. Boxed, they get a
   declared width instead of the screen's, so the photo crops to the same
   aspect at every size — and the fold stops reading as three black bars laid
   across a white page.
   ========================================================================= */
.stages {
  position: relative; z-index: 1;
  background: var(--white);
  scroll-margin-top: 88px;
  --spanel-pad: clamp(1rem, 2.4vw, 2.4rem);
}
/* Same device as .apart-panel above: cap the shell at the page rail plus the
   panel's own padding, so the panel edge sits exactly one padding outside the
   rail and the type inside it still lands on the rail with the rest of the
   page. Below ~1450 the cap is inactive and the panel is gutter to gutter. */
.stages > .shell { max-width: calc(var(--page) + var(--spanel-pad) * 2); }

.stages-panel {
  position: relative;
  /* the same two sub-10% sage washes the other dark box uses, mirrored — its
     light comes from the top right, this one's from the top left, which is
     where the header sits */
  background:
    radial-gradient(120% 90% at 22% -12%, rgba(var(--sage-rgb), 0.10), transparent 58%),
    radial-gradient(90% 70% at 100% 108%, rgba(var(--sage-rgb), 0.05), transparent 60%),
    var(--surface-deep);
  border-radius: var(--r-md);
  padding: clamp(2.4rem, 4.4vw, 4rem) var(--spanel-pad) var(--spanel-pad);
  box-shadow: inset 0 0 0 1px var(--rule-invert),
              0 40px 80px -50px rgba(var(--deep-rgb), 0.8);
  /* the stripes carry their own radius, but this catches the photo panels'
     1.07 hover scale at the panel corners */
  overflow: hidden;
}

/* Centred inside the box, unlike every other head on the page. The panel is a
   self-contained object with its own edges, so the copy answers to those
   rather than to the page rail. */
.stages-head {
  max-width: 62ch; margin-inline: auto; text-align: center;
  padding-bottom: clamp(2rem, 4vw, 3.2rem);
}
.stages-head .stages-h { color: var(--on-deep); }
/* sage is 8.03:1 on --deep — on this surface it is finally legal under type */
.stages-head .stages-h em { color: var(--sage); }
.stages-head .lead { margin-top: var(--sp-3); margin-inline: auto; color: var(--on-deep-mid); }

/* Three stripes, not one block: the gap is what makes them read as three. */
.stages-band { display: grid; gap: clamp(0.5rem, 1vw, 0.85rem); }

.lband {
  position: relative;
  /* Declared here rather than on the two children that need them, because the
     copy has to know exactly how wide the photo is to keep clear of it. */
  --media-w: clamp(300px, 46%, 680px);
  --lb-pad: clamp(1.25rem, 2.6vw, 2.6rem);
  /* 240, back down from 360. The band is a strip, not a hero — at 360 it took
     a third of a screen each and three of them read as three heroes. */
  height: 240px;
  overflow: hidden;
  background: var(--deep);
  isolation: isolate;
  border-radius: var(--r-md);
  /* Sage at 16%, not --rule-invert: the grey outline read as a box drawn
     around the stripe, this one reads as the brand green catching the light.
     border-box sizing means the 1px comes out of the declared height, and the
     photo panel positions against the padding box, so it sits inside the line
     rather than under it. */
  border: var(--hair) solid rgba(var(--sage-rgb), 0.16);
}

/* THE PHOTO GETS A BOX.

   It used to be `position:absolute; inset:0` — the photograph was the band,
   cropped by whatever the viewport happened to be. That is why the subjects
   came out cut through the hairline: at 1440x360 the crop is 4:1, at 1280x240
   it is 5.3:1, and the same file loses a different slice of head at every
   width. A panel with a declared width crops to a known aspect instead, so a
   photo that reads correctly once reads correctly everywhere.

   Anything dropped in here is cropped to roughly 2.7:1 at desktop. Supply
   images with the subject clear of the top and bottom edges. */
.lband-media {
  position: absolute;
  top: 0; bottom: 0; right: 0;
  width: var(--media-w);
  display: block;
  /* The photo is scaled 1.02 (and 1.07 on hover) so it has somewhere to move.
     Without this it spills past the panel, and the spill lands outside the
     wash that ::after paints — which showed as a hard 6px stripe of raw
     photograph down the seam: white on kids, pink on adults, green on
     seniors. The band's own overflow:hidden never caught it, because the
     spill is inside the band and only outside the panel. */
  overflow: hidden;
}
.lband--right .lband-media { right: auto; left: 0; }

.lband-img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* per band, so the subject stays inside the panel */
  object-position: var(--fx, 50%) var(--fy, 50%);
  display: block;
  transform: scale(1.02);
  transition: transform var(--t-3) var(--ease);
}

/* The seam. The photo dissolves into the copy panel rather than butting
   against it, which is what keeps the band reading as one strip instead of
   two boxes side by side — and it is the same wash the section had before,
   only now it belongs to the picture rather than to the whole band. */
.lband-media::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(to left,
    rgba(var(--shade-rgb), 0) 34%, rgba(var(--shade-rgb), 0.4) 62%,
    rgba(var(--shade-rgb), 0.85) 86%, var(--deep) 100%);
}
.lband--right .lband-media::after {
  background: linear-gradient(to right,
    rgba(var(--shade-rgb), 0) 34%, rgba(var(--shade-rgb), 0.4) 62%,
    rgba(var(--shade-rgb), 0.85) 86%, var(--deep) 100%);
}

/* Where the subject sits in each file, expressed against the panel rather
   than against the band. Kids is hard right, the woman is hard left, and the
   couple sits centre-left so its panel pulls slightly back from the edge. */
.lband--kids    { --fx: 100%; }
.lband--adults  { --fx: 0%; }
.lband--seniors { --fx: 42%; }

/* The copy sits on the stripe's own inset now that the stripe has edges. It
   used to carry .shell's geometry — max-width plus gutter — because the band
   ran full bleed and the type had to be pulled back onto the page rail by
   hand. Inside the panel the stripe is already on the rail, so the same
   measurement applied twice would inset the copy twice. */
.lband-body {
  position: relative; z-index: 2;
  height: 100%;
  width: 100%;
  padding-inline: var(--lb-pad);
  display: flex; align-items: center;
}
/* The photo's own width is reserved on its side of the body, so the copy can
   never reach it. Bounding the copy by max-width alone does not hold: at 900
   the 40ch cap was wider than the space left beside the photo and "Adults" ran
   onto the woman's hair. One padding of clear air between type and picture. */
.lband--left  .lband-body { padding-right: calc(var(--media-w) + var(--lb-pad)); }
.lband--right .lband-body { padding-left:  calc(var(--media-w) + var(--lb-pad)); }
/* the copy width is now the inner block's job, since the body spans the rail */
.lband-copy {
  max-width: 44ch;
  /* the whole block moves as one on hover, so the three lines stay locked to
     each other instead of drifting apart */
  transition: transform var(--t-2) var(--ease);
}
.lband--right .lband-body { justify-content: flex-end; }

/* The title carries the stripe now that the icon disc is gone — one step under
   .d2 rather than a card title, because with nothing beside it the word is the
   only thing anchoring the left edge. */
.lband h3 {
  font-size: var(--fs-d2); font-weight: 700;
  letter-spacing: var(--track-display); line-height: var(--lh-tight);
  color: var(--on-deep);
  transition: color var(--t-2) var(--ease);
}
.lband p {
  margin-top: 0.5rem;
  font-size: var(--fs-lead); line-height: 1.55; color: var(--on-deep-mid);
  max-width: 44ch;
}
.lband-link {
  display: inline-flex; align-items: center; gap: 0.5rem;
  margin-top: 0.85rem;
  font-size: var(--fs-md); font-weight: 600; color: var(--sage);
  transition: gap var(--t-1) var(--ease);
}
.lband-link svg { width: 16px; height: 16px; flex: none; }

@media (prefers-reduced-motion: no-preference) {
  .lband:hover .lband-img { transform: scale(1.07); }
  /* the copy drifts a few pixels away from the photo — mirrored, so both
     variants read as the stripe opening rather than as two different moves */
  .lband--left:hover  .lband-copy { transform: translateX(-6px); }
  .lband--right:hover .lband-copy { transform: translateX(6px); }
}
/* sage is 8.03:1 on --deep, so the title is allowed to land on it */
.lband:hover h3 { color: var(--sage); }
.lband:hover .lband-link { gap: 0.85rem; }

@media (max-width: 1024px) {
  .stages { scroll-margin-top: 112px; }
  /* 220 was sized for the old small type. The title is --fs-d2 now and the
     description a lead, which is ~190px of copy at this width — the strip
     needs the room or the two ends touch the edges. */
  .lband { height: 260px; }
  /* The photo panel narrows with the stripe rather than eating it. The floor
     drops from 300 because the stripe is now the panel's width, not the
     screen's — at 760 the old floor left the copy under 330px. */
  .lband { --media-w: clamp(240px, 42%, 520px); }
  .lband-copy { max-width: 40ch; }
}
@media (max-width: 700px) {
  /* No room for two panels side by side, so the split turns vertical rather
     than collapsing. The alternative — photo across the whole band with the
     copy washed over it — was measured and fails: at 390px the paragraph
     reaches the weak end of the wash and lands on the kids photo's white
     ground at 3.24:1. Stacked, the copy is back on solid --deep at 7.67:1 and
     the photograph is fully visible instead of 20% visible under a heavy
     wash. The panel keeps roughly the desktop crop, so a photo that works on
     one works on the other. */
  .lband { height: auto; display: block; }
  .lband-media, .lband--right .lband-media {
    position: relative; inset: auto;
    width: 100%; height: 150px;
  }
  /* the seam runs along the bottom edge now, not the side */
  .lband-media::after,
  .lband--right .lband-media::after {
    background: linear-gradient(to top,
      var(--deep) 0%, rgba(var(--shade-rgb), 0.72) 15%, rgba(var(--shade-rgb), 0) 48%);
  }
  /* the photo is above the copy now, so neither side is reserved */
  .lband-body,
  .lband--left .lband-body,
  .lband--right .lband-body {
    height: auto; justify-content: flex-start;
    padding-inline: var(--lb-pad);
    padding-block: var(--sp-3) var(--sp-4);
  }
  .lband-copy { max-width: none; }
}

/* =========================================================================
   FOLD 6 — TREATMENTS

   Four columns divided by hairlines rather than four boxes: the fold above
   is a photographic band and the one below is a card rail, so this one earns
   its difference by being the flattest thing on the page. The sage rule on
   the top edge is the only thing that moves.
   ========================================================================= */
.tx { position: relative; z-index: 1; scroll-margin-top: 88px; }
.tx-head { max-width: 64ch; padding-bottom: clamp(2.4rem, 4.5vw, 3.6rem); }
.tx-head .lead { margin-top: var(--sp-3); }

.tx-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: var(--hair) solid var(--rule);
}
.tx-card {
  position: relative;
  display: flex; flex-direction: column;
  padding: clamp(1.5rem, 2.2vw, 2.1rem) clamp(1rem, 1.6vw, 1.6rem);
  background: var(--white);
  border-right: var(--hair) solid var(--rule-quiet);
  transition: background-color var(--t-2) var(--ease),
              border-color var(--t-2) var(--ease),
              box-shadow var(--t-2) var(--ease);
}
.tx-card:last-child { border-right: 0; }
/* sits on the shared top rule, so hovering a column claims that segment of it */
.tx-card::before {
  content: '';
  position: absolute; left: 0; top: -1px;
  width: 100%; height: 3px;
  background: var(--graphic);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--t-2) var(--ease);
}
/* White at rest, deep green under the pointer — the column inverts rather
   than just brightening, which is a far clearer "this one" than the old
   quiet-grey-to-white step.

   Raised by shadow, not by a translate: the sage bar above sits ON the grid's
   shared top rule, so lifting the card would peel that bar off the line it is
   meant to be claiming. z-index puts the shadow over the neighbouring columns
   rather than under them, and drops the hairline that would otherwise cut
   across the dark fill. */
.tx-card:hover {
  z-index: 1;
  background: var(--deep);
  border-right-color: transparent;
  box-shadow: 0 26px 54px -34px rgba(var(--deep-rgb), 0.55);
}
.tx-card:hover::before { transform: scaleX(1); }

/* Every piece of type re-tunes for the dark fill. These are not stylistic
   swaps — on --deep the resting colours measure between 1.4:1 and 1.9:1,
   i.e. unreadable — so each one moves to the token built for that surface:
     title  --on-deep      white     16.36:1
     body   --on-deep-mid  #A9B4B0    7.66:1
     tag    --on-deep-key  sage       8.03:1
   The tag stays green, as asked; it just becomes the light green, because
   --leaf is the green that works on white and sage is the one that works on
   deep. There is no green that does both. */
.tx-card:hover .tx-n   { color: var(--on-deep-mid); }
.tx-card:hover .tx-tag { color: var(--on-deep-key); }
.tx-card:hover .tx-t   { color: var(--on-deep); }
.tx-card:hover .tx-d   { color: var(--on-deep-mid); }

/* --- how the four arrive ------------------------------------------------
   The grid used to carry one .r for all four columns, so they faded up as a
   single slab — the least interesting way four things can enter, and the
   reason this fold read as a block dropping in rather than a row building.

   Each card reveals itself now. They all trip together (reveal() takes the
   section as the unit, deliberately), and the cascade is pure
   transition-delay, continuing from where .r-4 left the grid: 0.64s, then a
   card every 110ms. Small enough that the row still reads as one gesture,
   long enough that the eye follows it left to right.

   .r's own base delay would otherwise win, so these have to be at least as
   specific — hence .reveal-on on the front of each. */
@media (prefers-reduced-motion: no-preference) {
  /* a shorter rise than the page default: these are wide, short cards in a
     row, and 28px on all four at once reads as the whole grid sliding.

     :not(.in) is load-bearing, not tidiness. base.css resets the arrived
     state with `.reveal-on .r.in { transform: none }` — three classes, same
     as this rule — and folds.css is the later stylesheet, so an unqualified
     selector here wins the tie and pins every card 20px low forever. */
  .reveal-on .tx-card.r:not(.in) { transform: translate3d(0, 20px, 0); }
  .reveal-on .tx-card.r.in:nth-child(1) { transition-delay: 0.64s; }
  .reveal-on .tx-card.r.in:nth-child(2) { transition-delay: 0.75s; }
  .reveal-on .tx-card.r.in:nth-child(3) { transition-delay: 0.86s; }
  .reveal-on .tx-card.r.in:nth-child(4) { transition-delay: 0.97s; }

  /* The nine pills run on the same idea at a quicker pace — 45ms apart, so
     the row ripples rather than counting itself out. Starts after the label
     (.r-5, 0.78s) has landed. */
  .reveal-on .tx-chips li.r:not(.in) { transform: translate3d(0, 12px, 0); }
  .reveal-on .tx-chips li.r.in:nth-child(1) { transition-delay: 0.86s; }
  .reveal-on .tx-chips li.r.in:nth-child(2) { transition-delay: 0.905s; }
  .reveal-on .tx-chips li.r.in:nth-child(3) { transition-delay: 0.95s; }
  .reveal-on .tx-chips li.r.in:nth-child(4) { transition-delay: 0.995s; }
  .reveal-on .tx-chips li.r.in:nth-child(5) { transition-delay: 1.04s; }
  .reveal-on .tx-chips li.r.in:nth-child(6) { transition-delay: 1.085s; }
  .reveal-on .tx-chips li.r.in:nth-child(7) { transition-delay: 1.13s; }
  .reveal-on .tx-chips li.r.in:nth-child(8) { transition-delay: 1.175s; }
  .reveal-on .tx-chips li.r.in:nth-child(9) { transition-delay: 1.22s; }
  /* the button lands last, on its own beat, as the full stop */
  .reveal-on .tx-more-cta.r.in { transition-delay: 1.34s; }
}

/* All five carry a colour transition on the same curve as the card's own
   fill, so the column inverts as one move rather than the background
   changing and the type catching up after it. */
.tx-n {
  font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: var(--track-micro); color: var(--ink-quiet);
  transition: color var(--t-2) var(--ease);
}
.tx-tag {
  margin-top: 1.15rem;
  font-size: var(--fs-micro); font-weight: 600;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--accent-text);
  transition: color var(--t-2) var(--ease);
}
.tx-t {
  margin-top: 0.45rem;
  font-size: var(--fs-card-t);
  font-weight: 700; letter-spacing: var(--track-display); line-height: 1.14;
  transition: color var(--t-2) var(--ease);
}
.tx-d {
  margin-top: 0.7rem;
  font-size: var(--fs-sm); line-height: 1.72; color: var(--ink-mid);
  transition: color var(--t-2) var(--ease);
}
.tx-link {
  margin-top: auto; padding-top: var(--sp-4);
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: var(--fs-xs); font-weight: 600; color: var(--ink);
  transition: color var(--t-2) var(--ease);
}
.tx-link svg { width: 13px; height: 13px; flex: none; transition: transform var(--t-1) var(--ease); }
/* sage, not --leaf: this is now sitting on --deep (8.03:1 against 1.9:1) */
.tx-card:hover .tx-link { color: var(--on-deep-key); }
.tx-card:hover .tx-link svg { transform: translateX(4px); }

/* The remaining nine treatments, named rather than summarised — fold 3 claims
   twelve under one roof and this is where that becomes checkable. */
/* The label used to sit in a column of its own beside the chips, which cost
   the list ~150px of the rail. Nine pills measure about 1210 and the rail is
   1360, so that column was the whole reason the ninth broke to a line by
   itself — and it left the label stranded next to a ragged block with a gap
   under it. Label and link take a line above instead, and the chips get the
   full rail: one clean row at desktop, an even wrap below it. */
/* Centred, and read top to bottom: label, the nine names, then one button
   that closes the fold. The label and link used to share a justified line
   above a left-ragged block of pills, which gave the eye three different
   left edges to track and ended on a ragged right edge with nothing to
   resolve it. Centring the pills makes the block symmetrical, and moving
   the link to the end turns a stray text link into the fold's full stop. */
.tx-more {
  margin-top: clamp(2rem, 3.4vw, 2.8rem);
  padding-top: clamp(1.3rem, 2.2vw, 1.9rem);
  /* the grid above opens with a rule and never closed; this is that rule */
  border-top: var(--hair) solid var(--rule);
  display: grid;
  gap: clamp(1rem, 1.8vw, 1.4rem);
  justify-items: center;
  text-align: center;
}
.tx-more-lab {
  font-size: var(--fs-micro); letter-spacing: var(--track-micro);
  text-transform: uppercase; color: var(--ink-quiet);
}
/* No max-width, and the padding below is measured rather than chosen: the
   nine pills come to ~1207 against a 1232 rail, so the desktop gets one
   clean row and every narrower width wraps centred and even. At the roomier
   padding this block was 1240 wide — eight pixels over — and broke to a
   lopsided 7 + 2 that read as an accident rather than a shape. */
.tx-chips {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 0.45rem;
}
.tx-chips li {
  padding: 0.38rem 0.86rem;
  font-size: var(--fs-xs); font-weight: 500; color: var(--ink-mid);
  background: var(--white); border-radius: var(--r-pill);
  box-shadow: inset 0 0 0 var(--hair) var(--rule);
  transition: color var(--t-1) var(--ease), box-shadow var(--t-1) var(--ease),
              transform var(--t-1) var(--ease);
}
/* These are names, not links — the hover is only an acknowledgement that the
   pointer is on one, which is why it moves a pixel and does not colour up
   like something clickable. */
.tx-chips li:hover {
  color: var(--ink);
  box-shadow: inset 0 0 0 var(--hair) var(--sage-deep);
}
@media (prefers-reduced-motion: no-preference) {
  .tx-chips li:hover { transform: translateY(-1px); }
}
.tx-more-cta { margin-top: clamp(0.3rem, 0.8vw, 0.7rem); }

/* Between the one-row width and the phone widths the rail is just wide
   enough to fit eight pills and strand the ninth on a line of its own.
   Capping the block well under the rail there turns that into a deliberate
   5 + 4. Below 768 the rail is narrow enough to wrap evenly by itself. */
@media (min-width: 768px) and (max-width: 1365px) {
  .tx-chips { max-width: 46rem; }
}

@media (max-width: 1024px) {
  .tx { scroll-margin-top: 112px; }
  .tx-head { max-width: none; }
  .tx-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tx-card { border-bottom: var(--hair) solid var(--rule-quiet); }
  .tx-card:nth-child(2n), .tx-card:last-child { border-right: 0; }
}
@media (max-width: 640px) {
  .tx-grid { grid-template-columns: minmax(0, 1fr); }
  .tx-card, .tx-card:nth-child(2n) { border-right: 0; }
}

/* =========================================================================
   FOLD 7 — BEGIN YOUR SMILE JOURNEY  (v7's composition, v9's tokens)

   The photograph is the whole band and the copy sits ON it, bottom-left,
   rather than beside it in a washed column. That means the scrim has to be
   bottom-weighted (v7's shape) instead of left-weighted (v9's earlier one):
   the type needs contrast exactly where it sits, and the top of the frame is
   left clear so the photograph reads as a photograph.
   ========================================================================= */
.cta {
  position: relative; z-index: 1;
  background: var(--white);
  scroll-margin-top: 88px;
}
/* The band itself is the "dark version" panel — same device as .apart-panel
   and .stages-panel: gutter on the left/right from .shell, var(--r-md) on
   the panel so the photo's corners round off instead of running to the
   viewport edge. */
.cta-panel {
  position: relative;
  isolation: isolate; overflow: hidden;
  background: var(--deep);
  border-radius: var(--r-md);
  min-height: clamp(420px, 46vw, 560px);   /* v7's 560px ceiling */
  display: grid; align-items: end;
}
/* The frame clips; the image inside it is deliberately taller than the frame.
   That over-height is v7's crop (it used inset: -18% 0 on the picture layer)
   and it is what pulls the two figures in close — at a plain inset:0 the same
   source reads as a wide room shot with the window and the overhead lamp
   dominating, and the subjects too small to carry the band. Kept as an
   over-height image rather than an over-sized .cta-media so the scrim, which
   lives in here too, still measures against the real section box. */
.cta-media { position: absolute; inset: 0; z-index: -1; overflow: hidden; }
.cta-media img {
  position: absolute; left: 0; top: -18%;
  width: 100%; height: 136%;
  object-fit: cover; object-position: 50% 42%;
}
/* Bottom-weighted, per v7 — transparent through the top half so the room is
   visible, ramping to near-solid under the bottom bar where the type lands.
   The faint top stop keeps the join with the fold above from reading as a
   hard seam. */
.cta-wash {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg,
      rgba(var(--shade-rgb), 0)    0%,
      rgba(var(--shade-rgb), 0.08) 45%,
      rgba(var(--shade-rgb), 0.5)  78%,
      rgba(var(--shade-rgb), 0.88) 100%),
    linear-gradient(180deg, rgba(var(--shade-rgb), 0.32) 0%, rgba(var(--shade-rgb), 0) 18%);
}

/* --- floating annotation callout ----------------------------------------- */
/* Sits over the photograph's clear upper half, left of the subject. Percent
   placement (not a fixed offset) so it tracks the crop as the band reflows. */
.cta-callout {
  position: absolute; top: 34%; left: 46%;
  z-index: 1;
  animation: cta-callout-float 5s ease-in-out infinite;
}
@keyframes cta-callout-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-10px); }
}
.cta-callout-card {
  display: inline-flex; align-items: center; gap: 0.6rem;
  padding: 0.75rem 1rem;
  border-radius: var(--r-md);
  /* Opaque, not v7's glassmorphic panel: backdrop-filter is on the banned
     list here, and over a busy photograph a translucent card loses the
     contrast this text needs anyway. */
  background: var(--white);
  color: var(--ink);
  font-size: var(--fs-xs); font-weight: 700; line-height: 1.3;
  letter-spacing: var(--track-tight);
  white-space: nowrap;
  box-shadow: 0 20px 40px rgba(var(--shade-rgb), 0.34);
}
.cta-callout-card svg { width: 20px; height: 20px; flex: none; color: var(--leaf); }

/* --- bottom bar: headline + proof on the left, CTA on the right ---------- */
.cta-inner {
  padding: clamp(2rem, 4vw, 2.5rem) clamp(1.5rem, 3.2vw, 3.2rem);
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--sp-4) var(--sp-5); flex-wrap: wrap;
}
.cta-copy { max-width: 44ch; }
/* No margin-top: the eyebrow that used to sit above this is gone, so the
   headline is the first thing in the column now. */
.cta-h {
  color: var(--on-deep);
  text-shadow: 0 4px 20px rgba(var(--shade-rgb), 0.45);
}

.cta-proof { margin-top: var(--sp-3); display: flex; align-items: center; gap: 0.9rem; }
.cta-faces { display: flex; flex: none; }
.cta-faces img {
  width: 38px; height: 38px; border-radius: 50%;   /* v7's 38px */
  object-fit: cover; object-position: 50% 20%;
  background: var(--n-10);
  box-shadow: 0 0 0 2px var(--white), 0 4px 10px rgba(var(--shade-rgb), 0.3);
}
.cta-faces img + img { margin-left: -12px; }
.cta-stars { display: flex; gap: 2px; color: var(--star); }
.cta-stars svg { width: 13px; height: 13px; fill: currentColor; }
.cta-proof-t {
  margin-top: 0.28rem;
  font-size: var(--fs-sm); font-weight: 500; color: var(--on-deep-mid);
  text-shadow: 0 2px 8px rgba(var(--shade-rgb), 0.5);
}
.cta-btn { flex: none; }

@media (max-width: 1024px) {
  /* The callout is placed against the wide crop; once the band narrows it
     collides with both the headline and the subject, so it goes. Its claim
     already appears verbatim in fold 4. */
  .cta-callout { display: none; }
}

@media (max-width: 720px) {
  .cta-panel { min-height: clamp(360px, 64vw, 440px); }
  .cta-media img { object-position: 74% 34%; }
  /* Stacked, the bar spans the band, so the scrim carries further up. */
  .cta-wash {
    background: linear-gradient(180deg,
      rgba(var(--shade-rgb), 0.12) 0%, rgba(var(--shade-rgb), 0.55) 52%, rgba(var(--shade-rgb), 0.92) 100%);
  }
  .cta-inner { align-items: flex-start; }
  .cta-copy { max-width: none; }
}

/* =========================================================================
   FOLD 8 — WHAT PATIENTS SAY

   A scroll-snap rail, not a scripted carousel: five real reviews stay
   readable and reachable with the keyboard whether or not site.js runs. The
   arrows are an enhancement on top, and appear only when it does.
   ========================================================================= */
.rev { position: relative; z-index: 1; background: var(--white); scroll-margin-top: 88px; }
.rev-head {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(2rem, 4vw, 4rem); align-items: center;
  padding-bottom: clamp(2.2rem, 4vw, 3.2rem);
}
.rev-head .lead { margin-top: var(--sp-3); }

.rev-cluster { position: relative; width: 268px; height: 208px; flex: none; }
.rev-circle {
  position: absolute; display: block;
  border-radius: 50%; overflow: hidden;
  background: var(--n-10);
  box-shadow: 0 20px 44px -24px rgba(var(--deep-rgb), 0.55);
}
/* Both of these are decorative vignettes beside a header about people, and
   both source files are already on the page at full size — the consultation
   shot fills fold 7's band, the operatory fills fold 9. At native framing the
   circles are simply those two pictures a third time. Zoomed hard, the large
   one holds faces and the small one holds the chair, which is what a vignette
   should have been showing anyway. */
.rev-circle img {
  position: absolute;
  width: 100%; height: 100%; max-width: none; object-fit: cover;
}
.rev-circle--lg img { width: 215%; height: 215%; top: -42%; left: -66%; }
.rev-circle--sm img { width: 265%; height: 265%; top: -58%; left: -78%; }
.rev-circle--lg { width: 176px; height: 176px; right: 0; top: 0; }
.rev-circle--sm {
  width: 108px; height: 108px; left: 0; bottom: 0;
  box-shadow: 0 0 0 7px var(--white), 0 20px 44px -24px rgba(var(--deep-rgb), 0.55);
}

.rev-rail {
  display: flex; gap: var(--sp-3);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  /* A hovered card's shadow is painted outside the card's own box, and
     setting overflow-x makes overflow-y compute to auto rather than stay
     visible — so the rail was slicing that shadow off at its padding edge
     and the card read as cut away from the page.

     The room is made with padding and then taken back off the layout with a
     matching negative margin, so the rail occupies exactly the space it did
     before (4px above, --sp-2 below) while giving the shadow somewhere to
     land. The inline bleed is 16px, comfortably inside --gutter's 20px floor,
     so widening the rail can never push the page into a sideways scroll. */
  padding: 30px 16px 40px;
  margin: -26px -16px -24px;
  /* the arrows are the affordance; a native bar under five cards is noise */
  scrollbar-width: none;
}
.rev-rail::-webkit-scrollbar { height: 0; }
.rev-card {
  flex: 0 0 clamp(262px, 31%, 392px);
  scroll-snap-align: start;
  display: flex; flex-direction: column;
  padding: clamp(1.3rem, 2vw, 1.7rem);
  background: var(--surface-quiet);
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 var(--hair) var(--rule-quiet);
  transition: box-shadow var(--t-2) var(--ease), transform var(--t-2) var(--ease);
}
.rev-card:hover {
  box-shadow: inset 0 0 0 var(--hair) var(--rule), 0 20px 44px -30px rgba(var(--deep-rgb), 0.5);
}

.rev-top { display: flex; align-items: center; justify-content: space-between; }
.rev-g { width: 20px; height: 20px; }
.rev-stars { display: flex; gap: 2px; color: var(--star); }
.rev-stars svg { width: 13px; height: 13px; fill: currentColor; }

.rev-txt {
  margin-top: var(--sp-3);
  font-size: var(--fs-sm); line-height: 1.78; color: var(--ink-mid);
}
.rev-who {
  margin-top: auto; padding-top: var(--sp-3);
  border-top: var(--hair) solid var(--rule);
  display: flex; flex-direction: column; gap: 0.18rem;
}
.rev-name { font-size: var(--fs-md); font-weight: 600; color: var(--ink); }
.rev-where {
  font-size: var(--fs-micro); letter-spacing: var(--track-micro);
  text-transform: uppercase; color: var(--ink-quiet);
}

.rev-nav { margin-top: var(--sp-3); display: flex; align-items: center; gap: 0.6rem; }
.rev-btn {
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--white); color: var(--ink);
  box-shadow: inset 0 0 0 var(--hair) var(--rule);
  transition: background-color var(--t-1) var(--ease), color var(--t-1) var(--ease),
              box-shadow var(--t-1) var(--ease), opacity var(--t-1) var(--ease);
}
.rev-btn svg { width: 16px; height: 16px; }
.rev-btn:hover { background: var(--deep); color: var(--ink-invert); box-shadow: none; }
.rev-btn[disabled] { opacity: 0.32; pointer-events: none; }
.rev-src {
  margin-left: auto;
  font-size: var(--fs-micro); letter-spacing: var(--track-micro);
  text-transform: uppercase; color: var(--ink-quiet);
  box-shadow: 0 1px 0 var(--rule-quiet);
  transition: color var(--t-1) var(--ease), box-shadow var(--t-1) var(--ease);
}
.rev-src:hover { color: var(--ink); box-shadow: 0 1px 0 var(--rule); }

@media (max-width: 1024px) {
  .rev { scroll-margin-top: 112px; }
  .rev-head { grid-template-columns: minmax(0, 1fr); }
  .rev-cluster { display: none; }   /* decorative, and the header is taller here */
  /* 80, not 84: at 84 the second card started exactly at the shell's right
     edge, so the rail read as a single card with nothing after it. */
  .rev-card { flex-basis: min(80vw, 400px); }
}

/* =========================================================================
   FOLD 9 — VISIT US

   Photograph and card, equal height. The hours table is the live element:
   site.js marks today and states whether the practice is open, read in
   America/Toronto rather than on the reader's clock.
   ========================================================================= */
.visit { position: relative; z-index: 1; scroll-margin-top: 88px; }
/* The headline breaks explicitly into two lines, and "Visit our welcoming"
   is wider than a 64ch rail at the display size — so the cap moves off the
   header and onto the paragraph, which is the only part that needs a
   reading measure. */
.visit-head { padding-bottom: clamp(2.2rem, 4vw, 3.2rem); }
.visit-head .lead { max-width: 64ch; margin-top: var(--sp-3); }

.visit-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.86fr);
  gap: clamp(1.5rem, 2.8vw, 2.8rem);
  align-items: stretch;
}
.visit-shot {
  margin: 0; border-radius: var(--r-md); overflow: hidden;
  background: var(--n-10); min-height: 340px;
}
.visit-shot img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 58%; }

.visit-card {
  display: flex; flex-direction: column;
  padding: clamp(1.5rem, 2.4vw, 2.2rem);
  background: var(--white); border-radius: var(--r-md);
  box-shadow: inset 0 0 0 var(--hair) var(--rule),
              0 26px 62px -42px rgba(var(--deep-rgb), 0.5);
}
/* Both blocks carry their own bottom rule — same device v7 used (each info
   group draws the line below it), which puts one divider after Location and
   a second after the hours table, ahead of the action row. */
.visit-block {
  padding-bottom: var(--sp-4); margin-bottom: var(--sp-4);
  border-bottom: var(--hair) solid var(--rule-quiet);
}
.visit-block h3 {
  font-size: var(--fs-micro); font-weight: 600;
  letter-spacing: var(--track-micro); text-transform: uppercase;
  color: var(--ink-quiet);
}
.visit-tag { margin-top: 0.4rem; font-size: var(--fs-xs); font-weight: 700; color: var(--accent-text); }
.visit-addr {
  margin-top: 0.6rem; display: flex; align-items: flex-start; gap: 0.55rem;
  font-size: var(--fs-body); font-weight: 500; line-height: 1.5;
}
.visit-pin { width: 15px; height: 15px; flex: none; margin-top: 0.25rem; color: var(--graphic); }

.visit-status {
  display: inline-flex; align-items: center; gap: 0.5rem;
  margin-top: 0.7rem; padding: 0.34rem 0.82rem;
  font-size: var(--fs-xs); font-weight: 600; border-radius: var(--r-pill);
}
.visit-status::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; flex: none;
}
.visit-status.is-open { color: var(--leaf);  background: var(--tint-open); }  /* 5.71:1 */
.visit-status.is-shut { color: var(--n-80);  background: var(--n-10); }  /* 9.24:1 */

/* --- the hours table: a bordered box of its own, nested in the card ------ */
.visit-hours-table {
  margin-top: 0.85rem;
  border: var(--hair) solid var(--rule);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.visit-hours-head {
  display: flex; justify-content: space-between;
  padding: 0.5rem 0.85rem;
  font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-quiet); background: var(--n-05);
}
.visit-hours li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: 0.55rem 0.85rem;
  border-bottom: var(--hair) solid var(--rule-quiet);
  font-size: var(--fs-sm); color: var(--ink-mid);
  font-variant-numeric: tabular-nums;
}
.visit-hours li:last-child { border-bottom: none; }
.visit-hours li:nth-child(even) { background: var(--n-05); }
.visit-hours li span:first-child { font-weight: 500; color: var(--ink); }
.visit-hours li.shut span:last-child { color: var(--ink-quiet); font-weight: 500; }
/* today wins over the zebra stripe: same specificity, later in source */
.visit-hours li.today {
  background: var(--tint-open);
}
.visit-hours li.today span:first-child { color: var(--leaf); font-weight: 700; }
.visit-hours li.today span:first-child::after {
  content: 'Today'; margin-left: 0.5rem;
  font-size: var(--fs-nano); font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--white); background: var(--leaf);
  border-radius: var(--r-pill); padding: 0.14rem 0.5rem;
  vertical-align: middle;
}

.visit-act { padding-top: var(--sp-1); display: flex; gap: var(--sp-3); flex-wrap: wrap; }
/* These two stretch to share the row, unlike every other button on the page,
   which sizes to its own label. .btn centres its contents — right for a
   content-width pill, wrong once the pill is wider than what is in it: the
   label and the disc ended up floating together in the middle with dead
   space either side. .btn's padding is deliberately asymmetric (1.35rem
   leading, 0.8rem trailing) so the disc sits ON the trailing edge, and
   space-between is what restores that reading at any width. */
.visit-act .btn { flex: 1 1 auto; justify-content: space-between; }

/* --- dashed callout: new-patient note + phone, v7's banner --------------- */
.visit-banner {
  margin-top: var(--sp-3);
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 0.6rem 1rem;
  padding: 0.85rem 1.1rem;
  background: var(--tint-open);
  border: 1px dashed rgba(var(--sage-deep-rgb), 0.4);
  border-radius: var(--r-md);
}
.visit-note { font-size: var(--fs-xs); font-weight: 600; color: var(--ink); }
.visit-tel {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: var(--fs-xs); font-weight: 700; color: var(--accent-text);
  transition: color var(--t-1) var(--ease);
}
.visit-tel svg { width: 14px; height: 14px; flex: none; }
.visit-tel:hover { color: var(--leaf); box-shadow: 0 1px 0 var(--leaf); }

@media (max-width: 1024px) {
  .visit { scroll-margin-top: 112px; }
  .visit-head .lead { max-width: none; }
  .visit-grid { grid-template-columns: minmax(0, 1fr); }
  .visit-shot { min-height: 260px; max-height: 380px; }
}

/* =========================================================================
   FOOTER

   The ratios invert here: sage finally carries text at 8.03:1 on deep, and
   it labels the columns. Everything else is the two opaque invert tiers —
   no translucent whites, whose contrast would depend on what sat behind.
   ========================================================================= */
/* v7's footer is a panel, not a band: it sits inset from both edges on a white
   ground with the page's corner radius, the same object .apart-panel,
   .stages-panel and .cta-panel already are here. So the dark fill moves off
   the <footer> and onto .ftr-panel, and the footer element becomes the white
   ground the panel sits on — including the strip below it, which is what
   stops the radius reading as a notch cut out of a full-bleed band. */
.ftr {
  position: relative; z-index: 1;
  background: var(--white);
  padding-bottom: clamp(1.25rem, 2.4vw, 2rem);
  --fpanel-pad: clamp(1.5rem, 3.2vw, 3.2rem);
}
/* Same device as the other two dark panels: cap the shell at the page rail
   plus the panel's own padding, so the panel edge sits exactly one padding
   outside the rail and the type inside it still lands on the rail with the
   rest of the page. */
.ftr > .shell { max-width: calc(var(--page) + var(--fpanel-pad) * 2); }

.ftr-panel {
  position: relative;
  overflow: hidden;
  color: var(--on-deep-mid);
  /* the same two sub-10% sage washes the other dark boxes carry, lit from the
     top left like the life-stages panel */
  background:
    radial-gradient(120% 90% at 18% -10%, rgba(var(--sage-rgb), 0.10), transparent 58%),
    radial-gradient(90% 70% at 100% 108%, rgba(var(--sage-rgb), 0.05), transparent 60%),
    var(--surface-deep);
  border-radius: var(--r-md);
  padding: clamp(2.4rem, 4.4vw, 3.8rem) var(--fpanel-pad) clamp(1.4rem, 2.4vw, 2rem);
  box-shadow: inset 0 0 0 1px var(--rule-invert),
              0 40px 80px -50px rgba(var(--deep-rgb), 0.8);
}
.ftr-top {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(0, 0.85fr) minmax(0, 0.95fr) minmax(0, 1.1fr);
  gap: clamp(1.8rem, 3vw, 3.2rem);
}

.ftr-mark {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-size: var(--fs-lg); font-weight: 700; letter-spacing: var(--track-tight);
  color: var(--on-deep);
}
.ftr-mark img { width: 30px; height: 30px; }
.ftr-about {
  margin-top: var(--sp-3);
  font-size: var(--fs-sm); line-height: 1.75; max-width: 38ch;
}
.ftr-addr { margin-top: var(--sp-2); font-size: var(--fs-sm); color: var(--on-deep); }

.ftr-col h2 {
  font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: var(--track-micro); text-transform: uppercase;
  color: var(--on-deep-key);
}
.ftr-list {
  margin-top: var(--sp-3);
  display: flex; flex-direction: column; gap: 0.6rem;
  font-size: var(--fs-sm);
}
.ftr-list a { transition: color var(--t-1) var(--ease); }
.ftr-list a:hover { color: var(--on-deep); }
.ftr-quiet { color: var(--on-deep-mid); font-variant-numeric: tabular-nums; }

.ftr-cta p { margin-top: var(--sp-3); font-size: var(--fs-sm); line-height: 1.7; max-width: 32ch; }
.ftr-cta .btn { margin-top: var(--sp-3); }

.ftr-rule {
  margin-top: clamp(2.4rem, 4vw, 3.4rem);
  border: 0; border-top: var(--hair) solid var(--rule-invert);
}
.ftr-bottom {
  margin-top: var(--sp-3);
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--sp-2) var(--sp-4); flex-wrap: wrap;
  font-size: var(--fs-micro);
}
.ftr-legal { display: flex; flex-wrap: wrap; gap: 0.6rem var(--sp-3); }
/* The scroll-to-top disc is fixed in this corner, and at the very foot of the
   page that is exactly where the legal links sit. Reserve its width here
   rather than hiding the control, which is still useful from down here. */
.ftr-bottom { padding-right: 120px; }
.ftr-legal a { transition: color var(--t-1) var(--ease); }
.ftr-legal a:hover { color: var(--on-deep); }

@media (max-width: 1024px) {
  .ftr-bottom { padding-right: 96px; }
  .ftr-top { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ftr-brand { grid-column: 1 / -1; }
  .ftr-about { max-width: none; }
}
@media (max-width: 560px) {
  .ftr-top { grid-template-columns: minmax(0, 1fr); }
  /* stacked, the disc sits under the bar rather than beside it */
  .ftr-bottom { justify-content: flex-start; padding-right: 0; padding-bottom: 74px; }
}

/* =========================================================================
   SCROLL TO TOP — the specimen, again

   It arrives as the tooth finishes fading off the stage, so the same object
   the page opened with ends up parked in the corner. White plate, hairline
   edge, the label set around the rim rather than under it.
   ========================================================================= */
.totop {
  position: fixed;
  right: clamp(1rem, 2.2vw, 2.1rem);
  bottom: clamp(1rem, 2.2vw, 2.1rem);
  z-index: 110;
  width: 92px; height: 92px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--white);
  box-shadow: 0 20px 44px -20px rgba(var(--deep-rgb), 0.45),
              inset 0 0 0 var(--hair) var(--rule-quiet);
  opacity: 0;
  transform: scale(0.55) translateY(16px);
  pointer-events: none;
  transition: opacity var(--t-2) var(--ease),
              transform var(--t-3) var(--ease),
              box-shadow var(--t-2) var(--ease);
}
.totop.on { opacity: 1; transform: none; pointer-events: auto; }
.totop:hover {
  box-shadow: 0 26px 54px -20px rgba(var(--deep-rgb), 0.55),
              inset 0 0 0 var(--hair) var(--rule);
}

.totop > * { grid-area: 1 / 1; }
.totop img { width: 25px; height: auto; }

.totop-ring { width: 100%; height: 100%; overflow: visible; }
.totop-ring text {
  font-family: var(--font);
  font-size: 8.6px;
  font-weight: 600;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  fill: var(--ink-quiet);            /* 5.21:1 on white */
}
@media (prefers-reduced-motion: no-preference) {
  .totop.on .totop-ring {
    animation: totop-spin 22s linear infinite;
    transform-origin: 50% 50%;
  }
  .totop:hover .totop-ring { animation-duration: 9s; }
}
@keyframes totop-spin { to { transform: rotate(360deg); } }

/* No arrow under the tooth: it landed on the roots and the ring already says
   which way this goes. */

@media (max-width: 1024px) {
  .totop { width: 74px; height: 74px; }
  .totop img { width: 21px; }
  .totop-ring text { font-size: 9.6px; letter-spacing: var(--track-label); }
}

/* =========================================================================
   PHONES — a pass over what the desktop layout leaves behind at 640px and
   under. Everything here is inside a max-width query, so nothing above it
   changes: this file's desktop rules are untouched by all of it.

   Four things were actually wrong on a phone, each measured rather than
   eyeballed. They are grouped by cause, not by selector.
   ========================================================================= */
@media (max-width: 640px) {

  /* --- 1. the header ate 11% of the screen ------------------------------
     "Book appointment" wrapped to two lines inside the pill, which took the
     header from ~62px to 97px on every phone — a tenth of the viewport,
     permanently, because the header is sticky. The label is short enough to
     fit on one line at every width once it is allowed to stay on one. */
  .hdr .btn {
    white-space: nowrap;
    padding: 0.6rem 0.55rem 0.6rem 1rem;
    font-size: var(--fs-xs);
  }
  .hdr .btn i { width: 24px; height: 24px; }
  .hdr .btn i svg { width: 12px; height: 12px; }

  /* --- 2. type below the legibility floor -------------------------------
     --fs-nano resolves to 9.3px, which is a caption size on a desktop card
     and simply too small held at arm's length. It is only ever used for
     in-card meta, so lifting it on phones cannot disturb a heading. */
  :root { --fs-nano: 0.72rem; }   /* 11.5px, from 9.3px */

  /* --- 3. touch targets under 44px --------------------------------------
     Every one of these is an inline text link that rendered 18-25px tall —
     fine for a cursor, too small for a thumb. min-height rather than padding
     so the text stays where it is and only the hit area grows around it. */
  .hcard-a, .lband-link, .doc-link, .tx-link, .rev-src, .visit-tel, .visit-map {
    min-height: 44px;
  }
  /* The footer is the densest tap area on the page — two stacked columns of
     nav links at 17px and a legal row at 13px. 40px with the list's own gap
     pulled in almost to nothing: each target more than doubles while the
     column as a whole grows by far less than the targets do. */
  .ftr-list { gap: 0.1rem; }
  .ftr-list a, .ftr-legal a, .ftr-mark {
    min-height: 40px;
    display: inline-flex; align-items: center;
  }
  /* the wordmark is a link home, and drops to a single 34px line once the
     sub-line is hidden below 380 */
  .brand { min-height: 44px; align-items: center; }

  /* --- 5. and the length ------------------------------------------------
     The fold rhythm is a desktop measure: --fold bottoms out at 5rem, so
     every section carried 160px of vertical padding on a 390px-wide screen.
     Tightened here, which returns roughly the height the touch targets above
     just claimed — the page ends up shorter than it started, with none of
     the cramping. */
  :root { --fold: 3.4rem; --fold-wide: 4.4rem; }

  /* --- 4. the scroll-to-top disc sat on the copy -------------------------
     At 74px on a 390px screen the fixed disc covered live paragraph text
     while the reader was in the middle of it. Smaller, and tucked further
     into the corner, it clears the measure at every width down to 320. */
  .totop { width: 54px; height: 54px; right: 0.75rem; bottom: 0.75rem; }
  .totop img { width: 16px; }
  .totop-ring { display: none; }   /* the ring's text is unreadable at 54px */
}

/* Under ~380 the brand's second line ("Erin Mills · Mississauga") wraps and
   pushes the header taller again. The wordmark alone still identifies the
   practice, and the location is in the footer, the Visit fold and the page
   title. */
@media (max-width: 380px) {
  .brand b i { display: none; }

  /* Below ~360 the two hero CTAs and their discs come to ~316px against 280px
     of usable width, so the phone number ran off the screen. The discs are
     the only decorative part of the pair — dropping them buys 64px, which is
     enough to keep both on the one line the layout asks for rather than
     wrapping the primary action onto its own row. */
  .hero-actions .btn i { display: none; }
  .hero-actions .btn {
    padding: 0.62rem 0.85rem;
    font-size: var(--fs-nano);
  }
}

/* =========================================================================
   FOLD 6 ON A PHONE — type only

   The treatments grid is four narrow columns on a wide screen, and its copy
   was sized for that column: --fs-sm for the description, --fs-xs for the
   link. Stacked, each card is the full width of the page and the same copy
   was running 33 characters a line at 390 in a box with room for far more —
   set small for a constraint that is no longer there.

   Same sizes fold 3, 4 and 5 now use on a phone, so the four sections read
   as one page rather than four decisions. Declared at the end of the file
   on purpose: fold 6's own rules are ~1000 lines above and would otherwise
   win on source order at equal weight.
   ========================================================================= */
@media (max-width: 640px) {
  /* the card title has to stay clear of a 16px body, and --fs-card-t was
     tuned against a 308px column */
  .tx-card .tx-t { font-size: var(--fs-xl); line-height: 1.2; }
  .tx-card .tx-d { font-size: var(--fs-body); line-height: 1.6; }
  /* 15px, matching .doc-link — these two are the same control in two folds */
  .tx-card .tx-link { font-size: var(--fs-md); }
  /* the nine names are read, not tapped; at --fs-xs they were the smallest
     type on the fold bar the uppercase eyebrows */
  .tx-chips li { font-size: var(--fs-sm); padding: 0.44rem 0.9rem; }
}

/* =========================================================================
   FOLDS 8 AND 9 ON A PHONE — type only

   Same pass as folds 3-6: copy that was sized against a narrow desktop
   column comes up to a reading size once the phone gives it the full width.
   Declared at the end of the file for the same reason as fold 6's block —
   both sections' own rules are far above and win on source order at equal
   weight.
   ========================================================================= */
@media (max-width: 640px) {

  /* --- fold 8, the reviews rail ----------------------------------------
     The quote is the whole card and was the smallest body text on the page.
     Raising it alone would have cost measure, so the card widens with it:
     86vw still leaves the next card peeking at the edge — which is what
     tells the reader the rail scrolls — while giving the quote back the
     characters the larger size takes. 28ch before, 26ch after, at a size
     that is actually comfortable to read. */
  .rev-rail .rev-card { flex-basis: min(86vw, 400px); }
  .rev-card .rev-txt { font-size: var(--fs-body); line-height: 1.7; }
  .rev-card .rev-name { font-size: var(--fs-lg); }

  /* --- fold 9, the visit card ------------------------------------------
     The hours table is the one thing on this page a reader is likely to be
     checking on a phone, in a hurry, and it was set two steps under the
     address directly above it. The phone number is both a link and a tap
     target and was smaller still. */
  .visit-hours li { font-size: var(--fs-md); }
  /* The hours never wrap. At the larger size the "today" row could not hold
     the day, the TODAY pill and the times on one line, and "9:00am – 6:00pm"
     broke across two — which made one row of a seven-row table taller than
     the rest and read as a rendering fault. The pill tightens to pay for it. */
  .visit-hours li span:last-child { white-space: nowrap; }
  .visit-hours li.today span:first-child::after {
    margin-left: 0.35rem; padding: 0.1rem 0.4rem; letter-spacing: 0.06em;
  }
  .visit-hours-head { font-size: var(--fs-nano); }
  .visit-tag { font-size: var(--fs-sm); }
  .visit-status { font-size: var(--fs-sm); }
  .visit-note { font-size: var(--fs-sm); }
  .visit-banner .visit-tel { font-size: var(--fs-md); }
}

/* =========================================================================
   SECTION HEADINGS ON A PHONE — one size, the hero's

   The hero sets its own size on a phone (--fs-hero-sm, 30.4px at 390) and it
   is the size that works: the h1 sits on two comfortable lines. Every section
   head below it was still running the desktop display scale — d4 at 44.8px,
   d3 at 33.6 — so "Smiles supported at every age" broke over three lines and
   read louder than the page's own headline.

   Both steps collapse onto the hero's size here. The d3/d4 distinction is a
   wide-screen device — it separates a head inside a panel from a head on the
   page rail, and neither distinction survives a single stacked column.
   ========================================================================= */
@media (max-width: 640px) {
  .display.d3,
  .display.d4 { font-size: var(--fs-hero-sm); }
}

/* =========================================================================
   FOLD 9's TWO CTAs ON A PHONE — one line

   They were set to flex: 1 1 auto so each takes its natural width and grows;
   with wrap allowed that meant each one claimed a full row on anything under
   ~640. flex-basis 0 makes them share the row equally instead, and the label
   and pill padding come in far enough that "Book appointment" and "Get
   directions" both fit beside each other at 320.
   ========================================================================= */
@media (max-width: 640px) {
  .visit-act { flex-wrap: nowrap; gap: 0.5rem; }
  /* 1 1 auto, not 1 1 0: a zero basis splits the row into two equal halves,
     and "Book appointment" is the longer label — at 320 it was handed the
     same 112px as "Get directions" and clipped. Sizing from content and
     sharing only the leftover keeps both labels whole. */
  .visit-act .btn {
    flex: 1 1 auto; min-width: 0;
    white-space: nowrap;
    font-size: var(--fs-xs);
    /* measured, not guessed: at 0.85rem/0.6rem "Get directions" came to 137px
       against 134px of share at exactly 390, and clipped by three pixels */
    padding: 0.66rem 0.5rem 0.66rem 0.75rem;
    gap: 0.4rem;
  }
  .visit-act .btn i { width: 24px; height: 24px; }
  .visit-act .btn i svg { width: 12px; height: 12px; }
}

/* Under ~380 the pair plus their two discs comes to more than the card's
   inner width. The discs go — the same trade the hero's two CTAs make at
   this width — and the labels centre once there is nothing to sit opposite. */
@media (max-width: 380px) {
  .visit-act .btn i { display: none; }
  .visit-act .btn {
    justify-content: center;
    padding: 0.62rem 0.7rem;
    font-size: var(--fs-nano);
  }
}

/* =========================================================================
   FOLD 4's VERTICAL RHYTHM

   .docs is the only white fold with a dark panel on BOTH sides — .apart-panel
   above, .stages-panel below — and no background of its own. Every other fold
   either carries a tint (.fold--quiet) or butts onto a full-bleed band, so its
   padding is visually closed off by an edge. Here there is no edge: the
   section's 144px runs straight into the neighbour's, and the reader sees
   240px of white above the heading and 288px below the cards around a section
   whose content is only ~490px tall.

   Matched to .apart's own padding-block, which is the section directly above
   it, so the two now sit on the same rhythm rather than stacking two
   different ones. Desktop only — the phone build already trims --fold.
   ========================================================================= */
@media (min-width: 641px) {
  /* 48px, and .stages' top trimmed to 96 to match. Those two numbers are
     what make BOTH gaps 144 — the same white run the page shows everywhere
     else a panel edge meets the next section (e.g. .stages-panel's bottom to
     the treatments heading). Halving .docs alone was not enough: the gap
     below it is .docs' padding PLUS .stages', so the second half of it was
     never mine to fix from this rule. */
  .docs.fold { padding-block: clamp(2rem, 3vw, 3rem); }
  .stages.fold { padding-top: clamp(3rem, 6vw, 6rem); }
}
