/* =============================================================================
   North Simcoe Physiotherapy - concept preview
   Blueprint recreation: templates/blueprints/clinical/physio-ivyrehab

   TYPE PAIRING AND WHY
   --------------------
   Display / headlines / pull-quotes : NEWSREADER (humanist serif, true italic)
     The blueprint's whole tonal trick is an italic humanist serif on every
     headline - it is what stops a healthcare page reading as a hospital
     portal. Newsreader was chosen over the usual suspects because (a) it has a
     genuinely drawn italic rather than a slanted roman, which the mission /
     friction / testimonial eyebrows lean on heavily, (b) its optical-size axis
     keeps a long clinical headline legible when it drops to ~30px on a 390px
     phone, and (c) its slightly condensed lowercase lets "Expert
     physiotherapy, right here in Oshawa" hold two lines on a phone without
     hyphenation. Deliberately NOT Fraunces (operator taste rule) and not a
     transitional serif like Playfair, whose thin hairlines shatter at body
     sizes over photography.
   Body / UI / labels : INSTRUMENT SANS (neutral humanist grotesque)
     A physio page is dense with detail a patient actually has to read -
     insurance wording, WSIB, opening hours, a booking form. Instrument Sans is
     neutral enough to disappear behind the serif but has enough warmth in the
     round letterforms that it does not read as a government form. Its tight
     default tracking suits the small caps-ish utility labels the blueprint
     uses.

   PALETTE - sampled from the prospect's own live site + logo, nothing invented
   -------
     #4b827b  teal, primary          : their real footer + phone bar + logo word
     #86b2a3  light teal             : the teal inside their own logotype
     #824a52  wine                   : sampled off the phone plate in their live
                                       header - this is the ONE hot band the
                                       blueprint spends on a single section.
                                       Their palette has no orange, so the
                                       blueprint's orange-to-pink band is mapped
                                       to their real wine instead of importing a
                                       hue they never use.
     #dbe6e5  pale teal tint         : the testimonial field (a tint of primary)
     #1f2523  ink                    : utility bar + the dark first-visit band
   ============================================================================= */

:root {
  --teal: #4b827b;
  --teal-deep: #35635d;
  --teal-darker: #274b46;
  --teal-light: #86b2a3;
  --tint: #dbe6e5;
  --tint-soft: #eef4f3;
  --wine: #824a52;
  --wine-light: #a2646b;
  --wine-deep: #5e343a;
  --ink: #1f2523;
  --ink-soft: #3d4744;
  --grey: #6c7573;
  --grey-line: #dfe4e3;
  --paper: #ffffff;
  --paper-warm: #fbfcfc;
  --gold: #B0700A;

  --serif: "Newsreader", Georgia, "Times New Roman", serif;
  --sans: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --wrap: 1240px;
  --gutter: clamp(1.25rem, 4vw, 3.5rem);
  --radius: 14px;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

/* Anchor jumps used to land with the target section's heading tucked UNDER the
   sticky nav. Every element that can be an anchor target reserves the nav's
   height plus breathing room above itself.

   --nav-h is MEASURED at runtime by main.js, because the nav's real height is
   driven by the brand logo's aspect ratio (here 101px at 1440, not the 78px
   min-height the .wrap declares) and so differs per template and per viewport.
   The values below are the measured fallback for a no-JS render. */
:root { --nav-h: 102px; }
@media (max-width: 960px) { :root { --nav-h: 86px; } }
[id] { scroll-margin-top: calc(var(--nav-h) + 1.25rem); }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: clamp(1rem, 0.97rem + 0.16vw, 1.0625rem);
  line-height: 1.65;
  color: var(--ink-soft);
  background: var(--paper);
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }

/* The [hidden] attribute must beat any component's own display rule. Without
   this, .hero-form's `display: flex` silently outranked the UA's
   `[hidden] { display: none }` and the form stayed on screen underneath its own
   confirmation panel. */
[hidden] { display: none !important; }

h1, h2, h3, h4 {
  font-family: var(--serif);
  color: var(--ink);
  font-weight: 400;
  line-height: 1.12;
  margin: 0;
  letter-spacing: -0.01em;
}

p { margin: 0 0 1.05em; }
p:last-child { margin-bottom: 0; }

a { color: inherit; }

.wrap { max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gutter); }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: #fff; padding: 0.7rem 1.1rem; border-radius: 0 0 8px 0;
  font-size: 0.9rem;
}
.skip-link:focus { left: 0; }

:where(a, button, input, select, textarea):focus-visible {
  outline: 3px solid var(--wine-light);
  outline-offset: 2px;
}

/* ---------- shared bits -------------------------------------------------- */

.eyebrow {
  font-family: var(--sans);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin: 0 0 1.1rem;
}

.serif-italic { font-style: italic; }

.pill {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1;
  padding: 0.95rem 1.6rem;
  border-radius: 999px;
  border: 1.5px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease, transform .18s ease;
  white-space: nowrap;
}
.pill:hover { transform: translateY(-1px); }

.pill-teal { background: var(--teal); color: #fff; }
.pill-teal:hover { background: var(--teal-deep); }

.pill-wine { background: var(--wine); color: #fff; }
.pill-wine:hover { background: var(--wine-deep); }

.pill-white { background: #fff; color: var(--ink); }
.pill-white:hover { background: var(--tint); }

.pill-ghost-dark { background: transparent; color: var(--ink); border-color: rgba(31,37,35,.28); }
.pill-ghost-dark:hover { background: var(--ink); color: #fff; border-color: var(--ink); }

.pill-ghost-light { background: transparent; color: #fff; border-color: rgba(255,255,255,.55); }
.pill-ghost-light:hover { background: #fff; color: var(--ink); border-color: #fff; }

/* Section rhythm. Operator directive 2026-08-29: the vertical gap BETWEEN
   sections comes down 40%, so every value here is the old rhythm x0.6
   (3.5/7/6.5 -> 2.1/4.2/3.9). A section pair therefore reads 201.6px -> 121.0px
   at 1440 and 112px -> 67.2px at 390. */
.section { padding-block: clamp(2.1rem, 4.2vw, 3.9rem); }

/* ---------- 1. utility bar ----------------------------------------------- */

.utility {
  background: var(--ink);
  color: rgba(255,255,255,.82);
  font-size: 0.8125rem;
  letter-spacing: 0.01em;
}
.utility .wrap {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1.6rem;
  min-height: 42px; padding-block: 0.5rem;
}
.utility a { text-decoration: none; }
.utility a:hover { color: #fff; text-decoration: underline; }
/* Utility items carry their meaning-icons (operator 2026-08-29 03:00: "add icons
   for all") - same shared sprite, inline beside the text, teal-light so they
   read as marks rather than competing with the copy. */
.utility .wrap > span { display: inline-flex; align-items: center; gap: 0.45em; }
.utility .ico { stroke: var(--teal-light); flex: none; }
.utility-sep { margin-left: auto; }
.utility-strong { color: #fff; font-weight: 600; }

@media (max-width: 720px) {
  .utility { font-size: 0.75rem; }
  .utility .wrap { justify-content: center; gap: 0.35rem 1rem; }
  .utility-sep { margin-left: 0; }
  .utility-hide-sm { display: none; }
}

/* ---------- 2. nav -------------------------------------------------------
   Blueprint has a solid brand-green nav with a knocked-out wordmark. This
   prospect's real logotype is two-tone (grey + teal) around a beige disc, so a
   white knockout collapses the disc into a blob. Their real site also runs a
   white header. The nav therefore stays white and carries the true logo, and
   the brand green is spent on the mission band, the testimonial tint and the
   footer instead.
   ------------------------------------------------------------------------- */

.nav {
  position: sticky; top: 0; z-index: 60;
  background: rgba(255,255,255,.96);
  backdrop-filter: saturate(140%) blur(8px);
  border-bottom: 1px solid var(--grey-line);
}
.nav .wrap {
  display: flex; align-items: center; gap: 1.25rem;
  min-height: 78px; padding-block: 0.75rem;
}
.nav-logo { display: block; flex: 0 0 auto; }
.nav-logo img { width: clamp(148px, 17vw, 200px); height: auto; }

.nav-links {
  display: flex; align-items: center; gap: clamp(1rem, 2vw, 1.9rem);
  margin-left: auto;
  list-style: none; padding: 0; margin-block: 0;
}
.nav-links a {
  text-decoration: none;
  /* A sixth link ("Our team", added 2026-08-29 02:45) took the row past its
     width at 1440 and every two-word label broke onto a second line — "Getting /
     started", "Who we / are", "Our / team" — which read as a broken nav rather
     than a full one. Labels never wrap; the flex gap absorbs the pressure
     instead, and its clamp already floors at 1rem. */
  white-space: nowrap;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ink-soft);
  padding-block: 0.4rem;
  border-bottom: 2px solid transparent;
}
.nav-links a:hover { color: var(--teal-deep); border-bottom-color: var(--teal); }

.nav-cta { flex: 0 0 auto; }

/* Nav conversion controls. The primary CTA is filled in the build's accent so
   it wins the row; the call control sits beside it as a bordered secondary and
   carries the accent in its icon. The primary's icon rides currentColor (white
   on the filled accent) because an accent-on-accent icon would be invisible. */
.nav-actions { display: flex; align-items: center; gap: 0.6rem; flex: 0 0 auto; }
.nav-actions .pill { padding: 0.8rem 1.25rem; font-size: 0.875rem; }
/* One icon register for the whole build: line icons, currentColor by default,
   overridden to the accent wherever the control is a secondary option. */
.ico {
  flex: 0 0 auto;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
/* The sprite that every .ico references (2026-08-29 02:46). Out of flow and out
   of the a11y tree; it must not be `display: none`, which in some engines stops
   the symbols resolving for <use>. */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
/* The two direct-contact lines align on the BASELINE of their text, which would
   drop an icon below it; centre just the icon against the line it labels. */
.book-call .ico { align-self: center; }
.nav-call .ico { stroke: var(--teal); transition: stroke .18s ease; }
.nav-call:hover .ico { stroke: #fff; }

.nav-toggle {
  display: none;
  margin-left: auto;
  background: none; border: 1px solid var(--grey-line); border-radius: 10px;
  width: 44px; height: 44px; cursor: pointer;
  align-items: center; justify-content: center;
  color: var(--ink);
}
.nav-toggle span, .nav-toggle span::before, .nav-toggle span::after {
  content: ""; display: block; width: 20px; height: 2px; background: currentColor; border-radius: 2px;
}
.nav-toggle span { position: relative; }
.nav-toggle span::before { position: absolute; top: -6px; }
.nav-toggle span::after { position: absolute; top: 6px; }

@media (max-width: 960px) {
  .nav .wrap { min-height: 66px; }
  .nav-toggle { display: inline-flex; }
  .nav-links {
    position: absolute; inset: 100% 0 auto 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: #fff; border-bottom: 1px solid var(--grey-line);
    padding: 0.5rem var(--gutter) 1.1rem;
    margin-left: 0;
    display: none;
  }
  .nav[data-open="true"] .nav-links { display: flex; }
  .nav-links a { padding-block: 0.85rem; border-bottom: 1px solid var(--grey-line); }
  .nav-links li:last-child a { border-bottom: 0; }
  /* Both conversion controls survive on a phone, collapsed to icon-only round
     buttons; the labels stay in the accessibility tree. */
  .nav-toggle { margin-left: 0; order: 9; }
  .nav-actions { margin-left: auto; order: 8; gap: 0.4rem; }
  .nav-actions .pill { padding: 0; width: 44px; height: 44px; }
  .nav-label {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; border: 0;
  }
}
@media (max-width: 600px) { .nav .wrap { gap: 0.65rem; } }

/* ---------- 3. hero -------------------------------------------------------
   Blueprint: a three-panel band of real clinic photography, edge to edge, with
   a centred white serif H1 over it and ONE conversion control. The blueprint's
   control is a multi-location zip finder; scaled to a single Oshawa clinic that
   becomes a two-field callback request plus the real street address.
   ------------------------------------------------------------------------- */

.hero { position: relative; isolation: isolate; background: var(--ink); }

.hero-panels {
  position: absolute; inset: 0; z-index: -2;
  display: grid; grid-template-columns: 1fr 1fr 1fr;
}
.hero-panels img { width: 100%; height: 100%; object-fit: cover; }
.hero-panels .panel-b { object-position: 50% 35%; }

.hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(180deg, rgba(20,26,25,.62) 0%, rgba(20,26,25,.48) 42%, rgba(20,26,25,.80) 100%),
    linear-gradient(90deg, rgba(31,37,35,.30) 0%, rgba(31,37,35,0) 30%, rgba(31,37,35,0) 70%, rgba(31,37,35,.30) 100%);
}

.hero-inner {
  padding-block: clamp(3.25rem, 8vw, 6.5rem);
  min-height: clamp(520px, 74vh, 720px);
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  text-align: center;
}

.hero h1 {
  color: #fff;
  font-size: clamp(1.95rem, 1.05rem + 3.7vw, 4rem);
  max-width: 22ch;
  text-wrap: balance;
  text-shadow: 0 2px 24px rgba(0,0,0,.42);
  margin-bottom: clamp(1.4rem, 3vw, 2.1rem);
}

/* The bar is now a two-step wizard (operator 2026-08-29 02:34). The plate, the
   radius and the shadow are unchanged; what changed is that the plate holds one
   of two panels. The WIDTH grows with the step because step 2 carries five
   controls instead of three: 620px is right for name/phone/button and would have
   forced the appointment fields onto a second row, so step 2 opens the plate to
   880px, which fits service + issue + when + submit on one bar-height row at 1440
   (measured: 864px of inner width against ~700px of content). It transitions, so
   the plate visibly grows into the second step rather than jumping. */
.hero-form {
  background: rgba(255,255,255,.96);
  padding: 0.5rem;
  border-radius: 999px;
  width: min(620px, 100%);
  box-shadow: 0 18px 44px rgba(0,0,0,.28);
  transition: width .22s ease;
}
.hero-form[data-step="2"] { width: min(880px, 100%); }
/* The panels, not the form, are the flex rows now — the form holds exactly one
   visible panel at a time, and [hidden] takes the other out of the layout. */
.hero-step {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  width: 100%;
}
.hero-form input,
.hero-form select {
  flex: 1 1 150px; min-width: 0;
  border: 0; background: transparent;
  font: inherit; font-size: 0.9375rem;
  color: var(--ink);
  padding: 0.75rem 1.1rem;
  border-radius: 999px;
}
/* The two selects sit on the same white plate as the inputs, so they have to lose
   the platform chrome to match them — otherwise step 2 reads as a different
   control family from step 1. The caret is drawn back in as a background mark,
   with room reserved for it in the right padding. */
.hero-form select {
  appearance: none; -webkit-appearance: none;
  padding-right: 2.1rem;
  text-overflow: ellipsis;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' fill='none' stroke='%2335635d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  cursor: pointer;
}
.hero-form input::placeholder { color: #8b9491; }
/* The selects' own "placeholder" is a real empty-valued first option, so it needs
   to be styled to match rather than inherit the chosen-value ink. */
.hero-form select:has(option[value=""]:checked) { color: #8b9491; }
.hero-form input:focus, .hero-form select:focus { outline: 2px solid var(--teal); outline-offset: -1px; }
.hero-form .pill { flex: 0 0 auto; }
.hero-next { text-decoration: none; }

/* Wizard chrome under the bar: where you are, and the way back. White on
   photography, so it carries the same shadow the other hero support lines do. */
.hero-stepper {
  display: flex; align-items: center; gap: 0.85rem;
  /* The caption sits directly UNDER the primary pill (operator 2026-08-29 02:58),
     not centred under the whole bar.
     `justify-content: flex-end` was the first attempt and it was WRONG at the
     widths in between (operator 03:05, "alignment bad"): flex-end assumes the
     pill is at the bar's right, which is only true while the bar's row fits on
     one line. Where the row wraps, or below 860 where the bar becomes a column,
     the pill sits at the LEFT and the caption floated off to the right on its
     own — a fixed justify cannot follow a control whose position it does not
     measure.
     So the row starts at flex-start and its left edge is driven by
     `--stepper-offset`, which main.js measures from the ACTIVE panel's pill on
     load, on every step change and on resize. It is a plain px value with a 0
     fallback, so before the script runs (and if it never does — the stepper is
     [hidden] until JS reveals it) the row is simply left-aligned, never
     mispositioned. */
  justify-content: flex-start;
  padding-left: var(--stepper-offset, 0px);
  width: min(880px, 100%);
  margin: 0.85rem 0 0;
  font-family: var(--sans); font-size: 0.8125rem;
  color: rgba(255,255,255,.92);
  text-shadow: 0 1px 10px rgba(0,0,0,.55);
}
.hero-back {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font: inherit; font-weight: 600; color: #fff;
  background: rgba(255,255,255,.14);
  border: 1px solid rgba(255,255,255,.42);
  border-radius: 999px;
  padding: 0.3rem 0.8rem 0.3rem 0.6rem;
  cursor: pointer;
}
.hero-back svg { fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.hero-back:hover { background: rgba(255,255,255,.26); }

/* The hero's preview-honest confirmation takes the form's place in the layout,
   on the same white plate so it stays fully legible over the photography. */
.hero-done {
  width: min(620px, 100%); margin: 0;
  background: rgba(255,255,255,.96);
  border-radius: 20px;
  padding: 1.05rem 1.35rem;
  color: var(--ink);
  font-size: 0.9375rem; line-height: 1.6; text-align: left;
  box-shadow: 0 18px 44px rgba(0,0,0,.28);
}
.hero-done a { color: var(--teal-deep); font-weight: 600; white-space: nowrap; }

.hero-objection {
  margin: 1.15rem 0 0;
  color: #fff;
  font-size: clamp(0.95rem, 0.9rem + 0.2vw, 1.0625rem);
  text-shadow: 0 1px 12px rgba(0,0,0,.5);
}

.hero-where {
  margin: 1.5rem 0 0;
  color: rgba(255,255,255,.86);
  font-size: 0.875rem;
  letter-spacing: 0.02em;
  text-shadow: 0 1px 10px rgba(0,0,0,.5);
}
.hero-where a { color: #fff; font-weight: 600; text-decoration: none; white-space: nowrap; border-bottom: 1px solid rgba(255,255,255,.5); }
.hero-where a:hover { border-bottom-color: #fff; }

@media (max-width: 860px) {
  /* Blueprint's mobile hero collapses the three panels into one strip. */
  .hero-panels { grid-template-columns: 1fr; }
  .hero-panels .panel-a, .hero-panels .panel-c { display: none; }
  .hero-panels .panel-b { display: block; object-position: 50% 50%; }
  .hero-inner { min-height: 78vh; justify-content: flex-end; padding-bottom: 4rem; }
  .hero h1 {
    max-width: 100%;
    text-wrap: normal;
    font-size: clamp(1.6rem, 7.1vw, 2.4rem);
  }
  /* The wizard stacks here: the plate is a rounded card and both steps become a
     column of full-width controls. The step-2 width bump is pointless at this
     size, so both steps share the one width. */
  .hero-form { border-radius: 18px; padding: 0.75rem; }
  .hero-form[data-step="2"] { width: min(620px, 100%); }
  .hero-step { flex-direction: column; align-items: stretch; gap: 0.6rem; }
  .hero-form input,
  .hero-form select {
    flex: 0 0 auto; width: 100%;
    border-radius: 11px;
    /* background-COLOR, not the `background` shorthand: the shorthand would reset
       background-image and silently delete the select's drawn caret. */
    background-color: #fff;
    box-shadow: inset 0 0 0 1px var(--grey-line);
    padding: 0.85rem 1rem;
  }
  .hero-form select { padding-right: 2.1rem; }
  .hero-form .pill { width: 100%; }
  .hero-stepper { width: min(620px, 100%); }
  .hero-where { margin-top: 1.1rem; }
}

/* ---------- shared: gradient-band grain -----------------------------------
   Both full-bleed gradient bands (mission, friction) carry a fine SVG grain
   over the colour ramp so the surface reads as a printed paper tone instead
   of a flat CSS gradient, and so the wide ramp stops banding on 8-bit
   displays. Felt, not seen: 10% opacity on a 120px tile, plus a 1-in-7px
   diagonal hairline at 2.8% white set on each band's own background stack.
   Inline data-URI, no external asset.
   ------------------------------------------------------------------------- */

.mission, .friction { position: relative; isolation: isolate; }
.mission::before, .friction::before {
  content: ""; position: absolute; inset: 0;
  pointer-events: none; z-index: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 120px 120px;
  opacity: .10;
}
.mission > .wrap, .friction > .wrap { position: relative; z-index: 1; }

/* ---------- 4. mission band ---------------------------------------------- */

.mission {
  background-image:
    repeating-linear-gradient(135deg, rgba(255,255,255,.028) 0 1px, rgba(255,255,255,0) 1px 7px),
    linear-gradient(115deg, var(--teal-darker) 0%, var(--teal) 55%, var(--teal-light) 130%);
  color: #fff;
}
.mission .wrap {
  display: grid; gap: clamp(1.6rem, 4vw, 3.5rem);
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  align-items: center;
}
.mission h2 {
  color: #fff;
  font-style: italic;
  font-size: clamp(1.75rem, 1.1rem + 2.4vw, 3rem);
  margin-bottom: 1.1rem;
  text-wrap: balance;
}
.mission p { color: rgba(255,255,255,.9); max-width: 60ch; }

/* The CTA column stretches to the full band height so the pill drops to the
   bottom (roughly level with the end of the paragraph) instead of floating
   mid-height, and the decorative gesture figure takes the space above it. */
.mission-cta {
  align-self: stretch;
  display: flex; flex-direction: column;
  align-items: flex-start; justify-content: space-between;
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
.mission-cta .pill { flex: 0 0 auto; }

.mission-figure {
  display: block; flex: 0 0 auto;
  width: clamp(140px, 12vw, 168px); height: auto;
  margin-inline-start: clamp(0rem, 1.2vw, 1rem);
  overflow: visible;
}
.mission-figure .fig-line {
  fill: none; stroke: #fff; stroke-width: 2.4;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .92;
}
.mission-figure .fig-ground {
  fill: none; stroke: #fff; stroke-width: 1.6; stroke-linecap: round;
  opacity: .34;
}
.mission-figure .fig-accent {
  fill: none; stroke: var(--wine-light); stroke-width: 2.8; stroke-linecap: round;
}
.mission-figure .fig-accent-dot { fill: var(--wine-light); stroke: none; }

@media (max-width: 860px) {
  .mission .wrap { grid-template-columns: 1fr; }
  /* Stacked: the pill leads on the left, the figure shrinks in beside it so
     the band does not grow a tall empty column on a phone. */
  .mission-cta {
    align-self: auto;
    flex-direction: row-reverse; flex-wrap: wrap;
    align-items: flex-end; justify-content: space-between;
    gap: 1rem;
  }
  .mission-figure {
    width: clamp(84px, 23vw, 116px);
    margin-inline: 0 0.5rem;
  }
}

/* ---------- 5. services (two labelled groups, photo tiles) ---------------- */

.services { background: var(--paper); }

.split-intro {
  display: grid; gap: clamp(1rem, 3vw, 3rem);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}
/* Vertical divider between the heading and copy columns (operator 2026-08-29
   03:02). Accent-derived hairline per the fleet rule (never neutral grey);
   drawn on the copy column so the single-column stack below 800px loses it
   automatically with the grid change. */
.split-intro > p {
  border-left: 1px solid rgba(39, 75, 70, 0.22);
  padding-left: clamp(1rem, 2.5vw, 2.2rem);
}
@media (max-width: 800px) { .split-intro > p { border-left: 0; padding-left: 0; } }
.split-intro h2 {
  color: var(--teal-deep);
  font-style: italic;
  font-size: clamp(1.75rem, 1.1rem + 2.4vw, 2.9rem);
  text-wrap: balance;
}
.split-intro p { color: var(--grey); max-width: 52ch; }

@media (max-width: 800px) { .split-intro { grid-template-columns: 1fr; } }

.group-label {
  font-family: var(--sans);
  font-size: 0.75rem; font-weight: 700;
  letter-spacing: 0.2em; text-transform: uppercase;
  margin: 0 0 1.4rem;
}
.group-label.teal { color: var(--teal-deep); }
.group-label.wine { color: var(--wine); }

.tile-row {
  display: grid; gap: clamp(1.25rem, 2.6vw, 2rem);
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-bottom: clamp(2.5rem, 5vw, 3.75rem);
}
.tile-row:last-of-type { margin-bottom: clamp(2rem, 4vw, 3rem); }

.tile { display: block; text-decoration: none; color: inherit; }
.tile-media {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: 10px;
  background: var(--tint);
}
.tile-media img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform .5s cubic-bezier(.2,.7,.3,1);
}
.tile:hover .tile-media img { transform: scale(1.045); }
.tile h3 {
  font-size: clamp(1.125rem, 1rem + 0.45vw, 1.4rem);
  border-top: 1px solid var(--grey-line);
  padding-top: 0.9rem;
  margin-top: 0.9rem;
}
.tile p { color: var(--grey); font-size: 0.9375rem; margin-top: 0.45rem; }

@media (max-width: 900px) { .tile-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .tile-row { grid-template-columns: 1fr; } }

.services-foot { text-align: center; }

/* ---------- 6. friction band (the ONE hot band) --------------------------- */

/* COLLAPSED TO THE FLAT TREATMENT, 2026-08-30. This band used to carry
   band-treatment-table.jpg under the wine. Two measurements killed it:

     1. It was not a new photograph. band-treatment-table.jpg is a re-encode of
        img/hero-a.jpg, already on this page at the top: 64x64 greyscale
        NCC 1.0000, mae 0.06 (scripts/check-image-collisions.mjs). Different
        bytes, same picture, which is why every byte-hash check passed it.
        blueprint-gate's image-reuse axis now fails a build for this.
     2. It was not reaching anyone anyway. audit-band-photos scored it 0.76
        against a floor of 2.0 (p75 0.72, p90 0.79 over 51 of 176 tiles clear of
        overlaid content): the most deeply drowned band in this build.

   Re-sourcing was the alternative and was rejected on (2), not on supply: five
   unplaced Google Place photos and one unplaced stock band image sit in this
   run's asset pools. A fresh photo would land under the same deliberately deep
   scrim and score the same, buying a clean identity check and no visible
   picture. The scrim depth is the intent here (the band must read as WINE), so
   the honest move is to stop paying for a photograph nobody sees.

   The tint stack is unchanged and now paints over the flat --wine token, which
   the band already fell back to whenever the image failed to load. Measured
   cost of the removal at 1440 over the band's own 1440x555 box: mean
   rgb(135.92, 91.25, 96.84) before, rgb(135.21, 89.37, 95.89) after, a largest
   channel shift of 1.88 of 255 (the band sits a hair less pink). Per-channel
   stddev went UP, 33.96/44.87/43.47 to 34.05/45.30/43.62, so the photo was
   very slightly flattening the wine gradient rather than adding movement to it.
   The texture and the gradient, not the photo, carry this band's grain. */
.friction {
  background-color: var(--wine);
  background-image:
    repeating-linear-gradient(135deg, rgba(255,255,255,.028) 0 1px, rgba(255,255,255,0) 1px 7px),
    linear-gradient(118deg, rgba(94,52,58,.95) 0%, rgba(130,74,82,.92) 50%, rgba(140,82,90,.93) 100%),
    linear-gradient(rgba(46,24,28,.44), rgba(46,24,28,.44));
  background-size: auto, auto, auto;
  background-position: center, center, center;
  background-repeat: repeat, no-repeat, no-repeat;
  color: #fff;
}
/* Lifted from .75/.88: the far (light) end of the wine gradient was already the
   band's weak point for white text before any photo went behind it, and the
   alpha-faded text was the part that suffered. */
.friction .eyebrow { color: rgba(255,255,255,.92); }
.friction h2 {
  color: #fff; font-style: italic;
  font-size: clamp(1.75rem, 1.1rem + 2.4vw, 3rem);
  margin-bottom: clamp(2.25rem, 5vw, 3.5rem);
  max-width: 20ch;
  text-wrap: balance;
}
.friction-grid {
  display: grid; gap: clamp(1.75rem, 3.2vw, 2.5rem);
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.friction-item { display: flex; flex-direction: column; align-items: center; text-align: center; }
.friction-item img.f-icon { width: 40px; height: 40px; margin-bottom: 1.1rem; }
.friction-item h3 {
  color: #fff; font-weight: 500;
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.3rem);
  margin-bottom: 0.5rem;
}
.friction-item p { color: rgba(255,255,255,.95); font-size: 0.9375rem; margin-bottom: 1.25rem; }
.friction-item .pill { margin-top: auto; }

@media (max-width: 980px) { .friction-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) {
  .friction-grid { grid-template-columns: 1fr; }
  /* Stacked full-bleed centring reads loose, so cap each item and centre the
     block rather than centring text across the whole viewport width. */
  .friction-item { max-width: 30ch; margin-inline: auto; }
}

/* ---------- 7. who-we-are mosaic ------------------------------------------
   Blueprint's careers mosaic alternates real staff portraits against solid
   colour tiles. This clinic publishes NO staff photographs (site-photos.json
   found one image on their entire site and it is stock, not a person), and
   dressing stock models up as their therapists would be a fabrication, so the
   portrait cells are dropped and the mosaic runs one unlabelled treatment
   photograph against three colour tiles carrying their real, published copy.
   ------------------------------------------------------------------------- */

.about { background: var(--paper); }
/* Squared up: the copy column stretches to the mosaic's full height and its
   pills are pushed to the bottom, so the two columns end on the same line
   instead of the left one stopping short and leaving the section ragged. */
.about .wrap {
  display: grid; gap: clamp(1.75rem, 4vw, 3.25rem);
  grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
  align-items: stretch;
}
.about-copy { display: flex; flex-direction: column; }
.about h2 {
  font-style: italic;
  /* Cap trimmed 2.9rem -> 2.55rem with the ink sweep (2026-08-29 02:27). This is
     the ONE heading on the page the sweep cost a line: upright 600 is ~15% wider
     per point than the italic 400 it replaced, and in this two-column band that
     tipped "A family-founded clinic in north Oshawa." from two lines to three at
     1440, which `text-wrap: balance` then split into ~13-character stubs. The
     other six headings hold their line counts unchanged at 1440, 901 and 390 —
     measured, not assumed — so only this one needed the cap moved. */
  font-size: clamp(1.75rem, 1.1rem + 2.4vw, 2.55rem);
  margin-bottom: 1.1rem;
  text-wrap: balance;
}
.about .eyebrow { color: var(--wine); }
.about-copy p { color: var(--grey); }
/* margin-top:auto drops the pills to the bottom of the stretched column so they
   land level with the bottom edge of the mosaic squares. */
.about-actions {
  display: flex; flex-wrap: wrap; gap: 0.75rem;
  margin-top: auto; padding-top: 1.75rem;
}

.why-label {
  font-family: var(--sans);
  font-size: 0.6875rem; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--wine);
  margin: 1.75rem 0 0.85rem;
}
.why-us {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 0.6rem;
}
.why-us li {
  position: relative; padding-left: 1.15rem;
  font-size: 0.9375rem; font-weight: 500; line-height: 1.45;
  color: var(--ink);
}
/* Same marker register as the team-tile bullets: a small rotated square in the
   section's accent, never a default disc. */
.why-us li::before {
  content: ""; position: absolute; left: 0; top: 0.5em;
  width: 6px; height: 6px; border-radius: 1px;
  background: var(--wine);
  transform: rotate(45deg);
}

.mosaic {
  display: grid; gap: 12px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
/* Text starts at a FIXED offset from the tile top, identical in every tile.
   These were bottom-aligned, so a long bio pushed its own name up and no two
   tiles started at the same height. Top alignment plus a reserved row for the
   name and two reserved rows for the role means a role that wraps (Ased Ali's
   "Co-founder, Registered Physiotherapist") no longer shoves its bullets below
   the neighbouring tile's. */
.mosaic-cell {
  border-radius: 10px; overflow: hidden;
  min-height: 210px;
  padding: 1.5rem;
  display: flex; flex-direction: column; justify-content: flex-start;
}
.mosaic-photo { padding: 0; }
.mosaic-photo img { width: 100%; height: 100%; min-height: 210px; object-fit: cover; }
.mosaic-teal { background: var(--teal); color: #fff; }
.mosaic-wine { background: var(--wine); color: #fff; }
.mosaic-ink  { background: var(--ink); color: #fff; }
/* min-height reserves one line for the name and TWO for the role, in each
   element's own em, so every tile hands its bullets the same starting y. */
.mosaic-cell h3 {
  color: #fff; font-style: italic;
  font-size: 1.1875rem; line-height: 1.2;
  min-height: 1.2em;
  margin-bottom: 0.5rem;
}
.mosaic-cell .role {
  font-family: var(--sans); font-style: normal;
  font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  color: rgba(255,255,255,.78);
  line-height: 1.45; min-height: 2.9em;
  display: block; margin-bottom: 0.75rem;
}
.mosaic-cell p { font-size: 0.875rem; line-height: 1.55; color: rgba(255,255,255,.9); }

/* Bullets in the refined register: a small rotated square in a white tint,
   never a default disc. */
.mosaic-bullets {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 0.55rem;
}
.mosaic-bullets li {
  position: relative; padding-left: 0.95rem;
  font-size: 0.875rem; line-height: 1.5;
  color: rgba(255,255,255,.92);
}
.mosaic-bullets li::before {
  content: ""; position: absolute; left: 0; top: 0.55em;
  width: 5px; height: 5px; border-radius: 1px;
  background: rgba(255,255,255,.58);
  transform: rotate(45deg);
}

@media (max-width: 900px) {
  .about .wrap { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
  .mosaic { grid-template-columns: 1fr; }
}

/* ---------- 8. testimonials ---------------------------------------------- */

/* Operator 2026-08-29 02:19: "move whole page up 50% FROM TESTIMONAILS" — the
   eyebrow/heading/star stack started too far down the band. Two offsets stacked
   above it: this section's own top padding and the hero block's padding-top. Both
   are halved, so the stack rises by ~58px at 1440 and the whole page below it
   moves up with it. Only the TOP is cut; the band's bottom padding is untouched. */
.reviews { background: var(--tint); position: relative; overflow: hidden; }
/* SECOND pass, operator 2026-08-29 02:42: "move testimonials and anything below
   up 75px" — on top of the 50% cut above. MEASURED at 1440 the eyebrow sat 58px
   below the band's top edge, so 75px cannot come out of this band alone: there
   are only 58px there and the last of them is the strip of clear photograph the
   eyebrow sits on.
   So the band's own top padding goes to zero and the remaining ~45px is taken
   from the whitespace ABOVE it — the preceding band's empty trailing padding,
   eaten by a negative margin. (That band is .about; it was briefly a standalone
   team band, reverted 03:04. The mechanism does not care which, it eats bottom
   padding.)
   That is what "and anything below" means in layout terms: the boundary closes
   and the whole rest of the page rises with it. Nothing with ink in it is
   overlapped — a section's bottom padding is empty by definition.
   The vw ramp is what keeps this honest at 390, where 75px does not exist to give:
   the pull-up is -44.6px at 1440 (total -74.9 with the padding, i.e. the 75 asked
   for) and -12.1px on a phone, for -28.9px total. The phone end of the ramp is
   -8px rather than the -24px first tried: MEASURED, -24px leaves only 10px of air
   under the band above at 390 where -8px leaves 22px, and the two are identical at
   1440 and 901, so the softer floor costs nothing.
   Verified after the 03:04 revert restored the mosaic above this band — no
   clipping, no overlap. MEASURED gap between the mosaic's bottom tiles and this
   band's photograph: 16px at 1440, 10px at 901, 22px at 390. The desktop figures
   are a deliberately tight seam and are the price of the full 75px asked for at
   that width; it is a tile edge above a photograph, not a collision. If it ever
   reads as cramped, this margin is the number to move, and moving it moves the
   testimonials header back down by the same amount. */
.reviews {
  padding-top: 0;
  margin-top: clamp(-45px, -3.1vw, -8px);
}
.reviews .eyebrow { color: var(--teal-deep); }
/* Operator 2026-08-29 02:19: "REDUCE GAPS ABOVE AND BELOW HERE WHAT REAL PATIENTS
   SAY". Below the sub-line is this margin (was clamp(2.25rem, 5vw, 3.5rem)); above
   it are .reviews-hero's padding-bottom and the p's own margin-top, both cut with
   it, so the sub-line sits close to the stack it belongs to. */
.reviews-head { text-align: center; margin-bottom: clamp(1.4rem, 3vw, 2.1rem); }
.reviews-head h2 {
  font-style: italic;
  font-size: clamp(1.6rem, 1.05rem + 2.1vw, 2.75rem);
  max-width: 22ch; margin-inline: auto;
  text-wrap: balance;
}
/* `> p`, not a descendant selector: the eyebrow inside .reviews-hero is also a
   <p> in this subtree and was picking up this rule's margin (and, after the copy
   grew, its measure too). The child combinator scopes it to the sub-line. */
.reviews-head > p {
  /* NOT --grey. Measured on the rendered band: #6c7573 on this section's #dbe6e5
     is 3.72:1 — under the 4.5 AA floor for normal text, and it was already under
     it before the copy grew; expanding this line from five words to a sentence
     (operator 02:21) is exactly the wrong moment to leave that standing. #565f5d
     measures 5.16:1 on the same tint, with real headroom rather than the 4.50:1
     that #5f6866 lands on. (--grey being this thin page-wide is a separate job.) */
  color: #565f5d;
  margin-top: 0.45rem;
  max-width: 58ch; margin-inline: auto;
  text-wrap: balance;
}

/* Photo header for the band (operator, 2026-08-29: "top hero image for this
   section that fades out right below stars for pop").

   The photograph runs the FULL band width from the section's top edge and is
   masked to nothing a little below the rating pill, so the eyebrow, heading and
   stars sit on imagery and every quote card below sits on clean tint. The mask
   dissolves the layer itself, which means the tail is the band's own --tint
   showing through rather than a painted approximation of it.

   Two layers, both carrying the identical mask so they die together:
     img            z-index -2  the photograph
     .reviews-hero::before  -1  the legibility wash + the build's diagonal texture
   Text is in normal flow above both. The <img> is a real element and eagerly
   loaded on purpose: a lazy band image stays undecoded while images-load still
   reports fine, and being a `> img` child of the text's ancestor is what puts
   this text inside the gate's overlay-legibility sweep at all.

   The wash is a white veil, strongest at the top where the small teal eyebrow
   sits and easing off toward the star pill (which carries its own white chip).
   Measured over the rendered page rather than assumed — see the readings
   recorded with this change. */
.reviews-hero {
  position: relative; z-index: 2;
  /* Tracks .reviews's own padding-top, which is now ZERO (see the 02:42 pull-up
     above) — the photo bleeds up by exactly the section's top padding so it starts
     at the band edge and no further, and with no top padding there is nothing left
     to bleed through. */
  --reviews-bleed: 0px;
  /* Halved from clamp(3rem, 6vw, 3.5rem) (operator 02:19). The floor still keeps a
     strip of uncovered photograph above the eyebrow at 390, which is what stops
     the mobile band reading as a white sheet — there is just less of it now. */
  padding-top: clamp(1.5rem, 3vw, 1.75rem);
  /* Cut with the sub-line gaps (operator 02:19): this is the space ABOVE
     "Here's what real patients say." Was clamp(2rem, 4vw, 3.4rem). */
  padding-bottom: clamp(1.3rem, 2.6vw, 2.1rem);
  /* Operator 2026-08-29 02:19: "MAKE GRADIENT FADE OUT MY AGGRESIVE FOR IAMGE."
     The old curve held full opacity to 68% and then took the whole remaining
     third of the box to die, so the photograph spent most of its height as a
     half-visible ghost — the long soft wash the operator is describing. This one
     holds full opacity FURTHER down (78%, which is past the rating pill) and then
     drops hard. The hold point is MEASURED against the rating pill rather than
     guessed: the pill's bottom edge sits at 84.2% of this box at 1440 and 88.1% at
     390, so the layer is at full opacity for the whole of it and the entire fade
     happens in the padding underneath — 84% to 100%, which is ~34px at 1440
     against the old curve's ~68px, i.e. twice as hard. Net: the photo reads as a
     photograph everywhere the words are, and is gone by the first quote card with
     no ghost tail under it. */
  --reviews-fade: linear-gradient(to bottom,
    #000 0%, #000 84%,
    rgba(0,0,0,.45) 90%,
    rgba(0,0,0,.08) 96%,
    rgba(0,0,0,0) 100%);
}
/* Deeper than the band's shared --teal-deep, and the reason is measured. With the
   harder fade and the thinner wash the operator asked for, this 12px caps line is
   the ONE piece of text on the band whose worst backdrop pixel fell under the
   gate's 3.0 floor (2.83:1 at 390, 3.35:1 at 1440). The fix could have been more
   veil, but more veil is exactly what was just removed on purpose. #274b46 is
   1.42x darker in luminance terms than #35635d, which lifts the same pixels to
   4.02:1 and 4.76:1 without putting a single point of white back over the
   photograph. Same doctrine as the heading sweep: answer thin text with ink. */
.reviews-hero > .eyebrow { margin-top: 0; color: #274b46; }
.reviews-hero-photo,
.reviews-hero::before {
  position: absolute;
  left: 50%; transform: translateX(-50%);
  width: 100vw; max-width: none;
  top: calc(-1 * var(--reviews-bleed));
  height: calc(100% + var(--reviews-bleed));
  pointer-events: none;
  -webkit-mask-image: var(--reviews-fade);
          mask-image: var(--reviews-fade);
}
/* 52% and not the top of the frame: at 1440 the band shows only about a third of
   the photo's height, and a crop through the two faces cut them at the eyes.
   This slice lands on the hands, the shoulder and the bright room instead — no
   half-faces, and the atmospheric-generic read the placeholder doctrine wants
   from a stock photo standing on a clinic's own page. (At 390 the band is nearly
   the whole frame, so the position barely moves anything there.) */
.reviews-hero-photo { z-index: -2; object-fit: cover; object-position: center 52%; }
/* The wash is a HALO, not a sheet. A flat veil heavy enough to carry the small
   teal eyebrow (which needs a ~210 backdrop for AA, where the ink heading needs
   only ~137) washed the whole photograph to near-white at 390, which is the
   opposite of the "pop" this was asked for. So the veil is thin at the edges,
   where nothing sits on top and the room can actually read, and concentrates
   into an ellipse behind the centre column where every word of this band lives.
   Alphas set by measuring the rendered page, not by modelling it. */
/* Rebalanced 2026-08-29 02:19 with the harder fade: the operator wants LESS veil
   over the image, and the flat bottom layer was the part veiling everything —
   including the parts of the frame no text sits on. So the flat layer drops from
   .26/.24/.22 to .11/.10/.09 (the image gets most of that back), and the radial
   HALO tightens and deepens instead: .82 at the centre where the eyebrow and
   heading are, and out of the way by 74% of the radius. Same total protection
   where the words are, much less everywhere else. Alphas measured on the rendered
   page against the readings recorded with this change, not modelled. */
.reviews-hero::before {
  content: ''; z-index: -1;
  background-image:
    repeating-linear-gradient(135deg, rgba(31,37,35,.02) 0 1px, rgba(31,37,35,0) 1px 7px),
    radial-gradient(108% 86% at 50% 42%,
      rgba(255,255,255,.90) 0%,
      rgba(255,255,255,.62) 46%,
      rgba(255,255,255,0) 82%),
    linear-gradient(to bottom,
      rgba(255,255,255,.14) 0%,
      rgba(255,255,255,.12) 55%,
      rgba(224,234,233,.10) 100%);
}
/* At 390 the band's aspect is close to the photograph's own, so `cover` shows
   almost the entire frame — and this frame is high-key: windows, white clothing,
   pale walls. The mobile band therefore read as a blank sheet no matter how thin
   the wash got, because there was nothing dark in view to survive it. Widening
   the image box past the viewport zooms `cover` into the same hands-on-shoulder
   moment the desktop crop lands on, which is where this photograph's contrast
   actually lives. .reviews clips the overhang; the wash layer stays at 100vw. */
@media (max-width: 700px) {
  .reviews-hero-photo { width: 175vw; object-position: 52% 56%; }
}

/* No mask support (older Safari/Firefox builds): the photo would otherwise end
   in a hard horizontal seam across the band. Drop it and keep the flat tint —
   the band then reads exactly as it did before this change. */
@supports not ((-webkit-mask-image: linear-gradient(#000, transparent)) or (mask-image: linear-gradient(#000, transparent))) {
  .reviews-hero-photo, .reviews-hero::before { display: none; }
}

.review-stack {
  position: relative; z-index: 2;
  max-width: 760px; margin-inline: auto;
  /* The star rows used to sit BETWEEN the cards and carry this rhythm in their
     own padding. Now that each rating lives inside its card, the stack owns the
     spacing itself. */
  display: flex; flex-direction: column; align-items: stretch;
  gap: clamp(1.1rem, 2.4vw, 1.9rem);
}
.quote-card {
  position: relative; z-index: 2;
  background: #fff;
  border-radius: 12px;
  padding: clamp(1.6rem, 3.5vw, 2.6rem);
  box-shadow: 0 12px 34px rgba(39,75,70,.09);
  text-align: center;
}
/* Readability pass (operator, 2026-08-29): the quote was serif in --wine, which
   at 4.9:1 on white is legal but tiring over a 60-word patient story. Patients
   actually READ these, so the body sans in --ink takes over (15.4:1), with a
   looser line-height. The serif keeps the headlines; it no longer has to carry
   long-form reading. */
.quote-card blockquote {
  margin: 0;
  font-family: var(--sans);
  font-size: clamp(1rem, 0.95rem + 0.4vw, 1.1875rem);
  line-height: 1.62;
  color: var(--ink);
  max-width: 56ch; margin-inline: auto;
}
.quote-card cite {
  display: block; margin-top: 0;
  font-family: var(--sans); font-style: normal;
  font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--grey);
}
/* When the review was written, under the name (operator, 2026-08-29). Real
   RFC3339 publishedAt timestamps out of place.json, matched to the page by
   author name; a review with no timestamp match carries no date at all. Month
   and year only — the day adds nothing and invites a "how old is this" read of
   a precise date. Dropped out of the name's uppercase register on purpose so
   the name still leads the attribution.

   REVISED 2026-08-29 02:20, operator: "BY 50% TOO. DATES SHOULD BE 12/12/1984."
   Two changes. FORMAT: numeric dd/mm/yyyy, the day/month/year order this clinic's
   Canadian readers use, taken from each review's real `publishedAt` in place.json
   at full day precision (17/02/2026, 13/12/2021, 02/04/2026, 12/03/2026) — the
   month-and-year rendering above was a truncation of data the file already had.
   SIZE: the operator asked for half. A literal 0.34rem is not legible type, so
   this goes to the smallest size the page can defend — 0.625rem/10px, the same
   step as the © Google chip — which is a ~10% cut in cap height and reads as the
   footnote they are asking for. Colour goes from --grey (4.79:1) to --ink-soft at
   this size so the smaller type does not also get fainter: #3d4744 on the card's
   #fff computes to 9.6:1, well past AA for small text. Tabular figures so four dates in a
   column line up on the slashes. */
.quote-date {
  display: block; margin-top: 0.35rem;
  text-transform: none; letter-spacing: 0.02em; font-weight: 500;
  font-variant-numeric: tabular-nums;
  font-size: 0.625rem; color: var(--ink-soft);
}
/* Rating stars. Were 0.95rem teal text glyphs, which read faint on the tint;
   now filled SVG at a deep review-gold, sized to actually carry. Measured
   contrast for --gold: 3.15:1 on the tint band, 4.06:1 on the white chip —
   both clear the 3:1 WCAG non-text-contrast bar for a meaningful graphic. */
.star-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.stars { display: block; height: 22px; width: auto; fill: var(--gold); }
.stars-lg { height: 27px; }

/* The rating row that CLOSES each card (operator 2026-08-29: copy, stars, name,
   date). Smaller than the head strip, and now sitting between the quote and the
   attribution it belongs to, so the card leads with the words and signs off with
   the rating and the reviewer. */
.card-stars {
  display: flex; justify-content: center;
  margin-top: clamp(1.15rem, 2.2vw, 1.55rem);
  margin-bottom: clamp(0.7rem, 1.3vw, 0.95rem);
}
.card-stars .stars { height: 17px; }

/* Source attribution: stars + the Google mark on a white chip, the standard
   review-widget register. No count, no badge, no claim beyond the source. */
.rating-strip {
  display: inline-flex; align-items: center; gap: 0.85rem;
  margin-top: 1.15rem;
  padding: 0.6rem 1.15rem;
  background: #fff;
  border-radius: 999px;
  box-shadow: 0 6px 20px rgba(39,75,70,.10);
}
.rating-sep { width: 1px; height: 20px; background: var(--grey-line); }
.g-mark { display: block; width: 20px; height: 20px; }

/* Photo collage bleeding around the quote stack, per the blueprint: the images
   tuck BEHIND the card corners and run off the page edge. Positioned against
   .review-stack (the 760px column) so the overlap is the same at every width. */
.reviews-bleed {
  position: absolute; z-index: 1;
  width: 300px; height: 210px;
  border-radius: 10px; overflow: hidden;
  /* The inset hairline keeps a pale photo from dissolving into the tint band
     — without it a blown-out frame reads as a missing image. */
  box-shadow: 0 14px 36px rgba(39,75,70,.18), inset 0 0 0 1px rgba(39,75,70,.12);
}
.reviews-bleed img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* quote-a.jpg is backlit: its top 40% is 31-54% near-white, so at the default
   full-height cover crop the top of the frame washed out to nothing. Scale
   into the lower-centre where the therapist and patient actually are. */
.bleed-left img { transform: scale(1.55); transform-origin: 46% 82%; }
.bleed-left  { left: -170px; top: 22%; }
.bleed-right { right: -170px; top: 58%; }
/* The third tile, beside the LAST quote card (operator 2026-08-29 02:21). Sized
   from its own source rather than from the other two: the photograph is portrait
   (640x800 shipped), so the tile is portrait, which also keeps the collage from
   reading as a row of matching rectangles. Anchored to the BOTTOM of the stack so
   it stays level with the last card however tall the cards run — the other two are
   anchored by percentage from the top, which would have drifted. Left side, far
   enough below .bleed-left (22%) not to crowd it. */
.bleed-low {
  width: 220px; height: 290px;
  left: -150px; bottom: -6px; top: auto;
}
/* Upper-middle of the frame: the wall sign and the console, not the tiled floor. */
.bleed-low img { object-position: center 38%; }

@media (max-width: 1080px) {
  .reviews-bleed { width: 240px; height: 175px; }
  .bleed-left  { left: -130px; }
  .bleed-right { right: -130px; }
  .bleed-low   { width: 170px; height: 225px; left: -105px; }
}
@media (max-width: 900px) {
  .reviews-bleed { display: none; }
}

.reviews-foot { text-align: center; margin-top: clamp(2rem, 4vw, 3rem); position: relative; z-index: 2; }

/* ---------- 9. first visit (dark band) ------------------------------------ */

/* COLLAPSED TO THE FLAT TREATMENT, 2026-08-30, same call and same two
   measurements as the friction band above:

     1. band-shoulder-assess.jpg was a re-encode of img/svc-physio.jpg, already
        on this page in the services grid: 64x64 greyscale NCC 0.9992, mae 1.21.
        A visitor scrolling this page met that one photograph twice.
     2. audit-band-photos scored it 1.41 against a floor of 2.0 (p75 0.99,
        p90 2.01 over 76 of 220 tiles clear of overlaid content), so the
        assessment it showed was never legible through the ink.

   Re-sourcing was rejected for the same reason: the scrim, not the choice of
   picture, is what the reader is looking at, and this band was deliberately
   tuned that way. The operator's 2026-08-29 note further down this section
   already settled the direction, "the fix is STRUCTURE, not a brighter
   picture"; the raised step surfaces are what make this band read.

   The tint stack is unchanged and now paints over the flat --ink token.
   Measured cost of the removal at 1440 over the band's own 1440x648 box: mean
   rgb(46.80, 54.36, 51.50) before, rgb(41.55, 50.01, 47.68) after, a largest
   channel shift of 5.25 of 255, so the band reads marginally deeper because the
   drowned photo was slightly brighter than --ink. Per-channel stddev went UP,
   33.90/34.17/34.30 to 34.79/34.98/34.99: the photo was damping the band's own
   gradient, not lending it any. */
.firstvisit {
  background-color: var(--ink);
  background-image:
    repeating-linear-gradient(135deg, rgba(255,255,255,.022) 0 1px, rgba(255,255,255,0) 1px 7px),
    linear-gradient(112deg, rgba(31,37,35,.95) 0%, rgba(31,37,35,.92) 55%, rgba(33,52,48,.92) 100%),
    linear-gradient(rgba(16,22,20,.42), rgba(16,22,20,.42));
  background-size: auto, auto, auto;
  background-position: center, center, center;
  background-repeat: repeat, no-repeat, no-repeat;
  color: rgba(255,255,255,.86);
}
.firstvisit .eyebrow { color: var(--teal-light); }
.firstvisit h2 {
  color: #fff; font-style: italic;
  font-size: clamp(1.75rem, 1.1rem + 2.4vw, 2.9rem);
  max-width: 20ch; margin-bottom: 1rem;
  text-wrap: balance;
}
.firstvisit-lede { max-width: 56ch; margin-bottom: clamp(1.9rem, 4.2vw, 2.75rem); color: rgba(255,255,255,.92); }

/* Operator 2026-08-29: "make 1 2 3 section pop more". The band already carries a
   photo and a texture, so the fix is STRUCTURE, not a brighter picture. Three
   changes, all in the build's own register:
     - each step becomes a raised surface (a faint white veil + a soft lift)
       instead of three columns hanging off a hairline, so the trio reads as
       three objects on the band rather than three paragraphs;
     - the hairline becomes a 3px teal-light accent rule at the top of each
       surface, which is what actually carries colour into the band;
     - the numeral grows and gets its own baseline rule, so 1-2-3 is legible as
       a sequence at a glance from across the page.
   The veil is deliberately thin (6%): over --ink it lands around #2b302e, which
   keeps every white and near-white value in the card far above the floor. */
.steps {
  display: grid; gap: clamp(1rem, 2.2vw, 1.6rem);
  grid-template-columns: repeat(3, minmax(0, 1fr));
  list-style: none; padding: 0; margin: 0 0 clamp(1.6rem, 3vw, 2.3rem);
}
.steps li {
  position: relative;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.10);
  border-top: 3px solid var(--teal-light);
  border-radius: 12px;
  padding: clamp(1.15rem, 2.2vw, 1.6rem) clamp(1.15rem, 2.2vw, 1.5rem) clamp(1.3rem, 2.4vw, 1.7rem);
  box-shadow: 0 14px 30px rgba(9,14,13,.28);
}
/* Operator 2026-08-29, second pass on this band: the numeral and the step title
   both read THIN in white/teal on ink, and the hairline sat between the numeral
   and the title, which pushed the title down into the body copy instead of
   letting it lead. Three changes:
     - the numeral is upright and 700, not italic 400. Italic at a light weight
       is the thinnest thing this typeface does, which is the wrong instrument
       for a 1-2-3 that has to be read from across the page. Same accent colour,
       same size — only the cut changed. (SUPERSEDED at 02:29: the numeral now
       takes the title's own size and weight via `font: inherit`, so it is no
       longer separately specified. The accent colour survives.)
     - the title steps up to 600 and a size, and takes over the hairline as its
       OWN bottom rule, so the stack is numeral, title, rule, copy. The title is
       now the element the rule underlines.
     - Newsreader is requested at 600/700 as well as 400/500 (see the font link).
       Without those cuts the browser synthesises a smeared faux-bold, which is
       exactly the weak-looking result this is meant to fix.
   Serif rather than switching to the sans: the sibling dark band (.friction-item
   h3) is serif 500, so the sans would have made these the only card titles on
   the page out of family. Weight, not typeface, was what was missing. */
/* REVISED 2026-08-29 02:29, operator: "MAKE NUMBER AND HEADING SAME SIZE ...
   ADD . FOR NUMBERS." The numeral used to be a 3.4rem display block sitting above
   the title; it is now a span INSIDE the h3, so the two are one heading line —
   "1. Book your assessment" — and the rule underlines the whole unit.
   `font: inherit` rather than restating the values: it takes the h3's family,
   size, weight, style and line-height by definition, so the two can never drift
   apart again when the h3's ramp is retuned. It also RESETS the shorthand's other
   parts, which is why letter-spacing (not part of `font`) is inherited on the
   next line. Only the colour differs, which is the operator's brief: number in
   the band accent, title in ink-on-dark white.
   The gap is an em so it scales with the heading rather than being a fixed px
   that opens up as the clamp grows. */
.step-num {
  font: inherit;
  letter-spacing: inherit;
  color: var(--teal-light);
  margin-right: 0.38em;
}
.steps h3 {
  color: #fff; font-weight: 600;
  /* Sized so the longest title ("3. A Program Built Around You") holds ONE line
     in its column at every width (operator 2026-08-29 02:55); balance removed -
     it was forcing an even 2-line split even when one line fit. */
  font-size: clamp(1.1rem, 0.98rem + 0.42vw, 1.28rem);
  line-height: 1.18; letter-spacing: -0.005em;
  padding-bottom: 0.75rem; margin-bottom: 0.9rem;
  border-bottom: 1px solid rgba(134,178,163,.35);
}
.steps p { font-size: 0.9375rem; color: rgba(255,255,255,.84); margin-bottom: 0; }

/* Support copy beside the band's primary (operator 2026-08-29 02:41). The line
   sits LEFT of the pill and reads into it, so the row is one sentence-plus-button
   rather than a floating button. `flex-wrap` plus a max-width on the copy keeps
   them on one row at 1440 and lets the copy take its own line as the band
   narrows; at 390 the column rule below stacks it above the pill outright. */
.firstvisit-cta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.9rem 1.5rem;
}
.firstvisit-cta-copy {
  margin: 0; max-width: 42ch;
  font-size: 0.9375rem; line-height: 1.5;
  color: rgba(255,255,255,.92);
}

@media (max-width: 800px) { .steps { grid-template-columns: 1fr; } }
@media (max-width: 560px) {
  .firstvisit-cta { flex-direction: column; align-items: stretch; gap: 1rem; }
  .firstvisit-cta .pill { justify-content: center; }
}

/* ---------- 10. booking form ---------------------------------------------- */

/* Booking band, light-band variant of the same recipe used on .friction and
   .firstvisit. Those are dark bands, so they darken the photo before tinting;
   this band is white with INK text, where the risk runs the other way — a dark
   photo pixel under dark type — so the photo is LIGHTENED first, then tinted
   with the band's own paper/teal.

   PHOTO SWAPPED 2026-08-29: this band used to carry a med-spa STOCK room. It
   now carries the clinic's OWN treatment room, from their Google Business
   photos (place-photos/google-place-03.jpg, contributed by Desirée Massey —
   Google requires that attribution to be displayed, and it is, in the footer
   colophon). Being their real room changes the brief: the point is no longer
   "atmospheric-generic", it is "this is the room you will actually lie down
   in", so the wash is eased from .66/.955 to .46/.85 and the room reads.

   That is affordable because the new photograph is DARK (mean 105,92,81) where
   the stock one was bright, so the same lightening recipe lands much lower on
   the scale — but not as far as arithmetic predicted, so these two alphas were
   set by MEASURING the rendered band, not by modelling it. The band's weakest
   text is .book-aside p at #5f6866: at .40/.82 it measured 4.12:1 against its
   crop mean (~4.55:1 against the backdrop once the text's own dark pixels are
   discounted) — over the gate's 3.0 pixel floor but with almost nothing left
   against the 4.5 AA floor, so the wash was taken back up to .46/.85. The
   readings recorded with this change are the ones that count.
   background-color keeps the band white if the image fails. */
.book {
  background-color: var(--paper);
  background-image:
    repeating-linear-gradient(135deg, rgba(31,37,35,.016) 0 1px, rgba(31,37,35,0) 1px 7px),
    linear-gradient(118deg, rgba(255,255,255,.85) 0%, rgba(238,244,243,.83) 100%),
    linear-gradient(rgba(255,255,255,.46), rgba(255,255,255,.46)),
    url("../img/place-treatment-room.jpg");
  background-size: auto, auto, auto, cover;
  background-position: center, center, center, center 58%;
  background-repeat: repeat, no-repeat, no-repeat, no-repeat;
}
.book .wrap {
  display: grid; gap: clamp(1.75rem, 4vw, 3.5rem);
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  align-items: start;
}
/* Exterior shot above the booking heading. Squared corners are the point — this
   is the one deliberately hard-edged image on a page of soft cards, so it reads
   as a document of the place rather than another decorative tile. Capped at
   460px: the Street View source is 640px square, and stretched across the band
   it would visibly soften. It sits in the LEFT column only, so it cannot push
   the form down — it fills dead space that column already had.

   THE .photo-credit CHIP IS LOAD-BEARING, not decoration. The caption that
   carried the required © Google notice was removed (operator, 2026-08-29). The
   preferred replacement was to re-crop so Google's own baked watermark stayed in
   frame; that was built and measured, and it does not work — the watermark runs
   along the very bottom of the tile, under the road, so the shortest crop that
   holds it is 44%-100% of the frame, of which ~48% is asphalt. The tight plaza
   crop is back, and the notice is a chip in the corner of the image instead, in
   the watermark's own register rather than as page furniture. Removing the chip
   without putting the notice somewhere else breaks the licence terms. */
/* Operator 2026-08-29 02:26: "IMAGE SHOULD FILL FULL VIEW". The 460px cap was an
   inset chosen for looks, and it was leaving the figure narrower than its own
   column. The real ceiling is the SOURCE: Street View returned a 640x640 still,
   so any display box up to 640 on the long edge is a downscale and stays sharp.
   The cap is now that number and nothing else, which means the figure is the full
   column width everywhere the two-column layout runs (the aside column tops out
   at 462px), and stops short only on a tablet where the single column is wider
   than the pixels exist to fill. */
.book-photo {
  position: relative;
  margin: 0 0 clamp(1.4rem, 2.6vw, 2rem);
  max-width: 640px;
}
/* Operator 2026-08-29 02:16: the photo was a short letterbox strip and the aside
   column ended ~250px above the form card, so the two columns did not square off
   (ledger #62). Rather than pick a taller number and hope, the photo ABSORBS the
   difference: in the two-column layout the grid stretches, the aside becomes a
   flex column, and the figure is the one growing item. The columns then bottom-
   align exactly at every width instead of at the one width a fixed height was
   tuned for.
   The max-height is the crispness ceiling, not a taste one: the source is a
   640x640 Street View still and the figure is capped at 460px wide, so `cover`
   scales the source by 460/640 = 0.72 — a downscale, therefore sharp — for any
   height up to 460px. Past that the scale factor is set by HEIGHT and the image
   starts being enlarged. Taller aspect, never wider. */
.book-photo img {
  display: block; width: 100%;
  height: clamp(148px, 12vw, 190px);
  /* 47% lands the window on sky-to-sidewalk: the plaza, its parking and the
     grass verge, with the road foreground cropped away. */
  object-fit: cover; object-position: center 47%;
  border-radius: 0;
  box-shadow: 0 12px 28px rgba(39,75,70,.18);
}
/* Placed AFTER the base rule on purpose: it overrides the fixed height, and both
   selectors carry the same specificity, so source order is what decides. */
@media (min-width: 901px) {
  .book .wrap { align-items: stretch; }
  .book-aside { display: flex; flex-direction: column; }
  /* `flex-basis: 0`, not `auto`: with `auto` the figure's base size is the image's
     own intrinsic box (456x456 here), so it did not grow into the leftover — it
     STARTED at 456 and pushed the aside 110px PAST the card, which is the same
     misalignment the other way round. From 0 the figure's height is purely the
     space the card leaves, floored by min-height and capped by max-height.
     The image is then taken out of flow so `height: 100%` has a definite box to
     resolve against; the © Google chip is already absolute inside this figure, so
     it rides the crop instead of detaching below it.
     max-height is the same 640px source ceiling as the width: past it, `cover`
     starts scaling the still UP. In practice the figure settles around 345px at
     1440 because that is exactly what the form card leaves. */
  .book-photo {
    flex: 1 1 0;
    min-height: clamp(148px, 12vw, 190px);
    max-height: 640px;
  }
  .book-photo img { position: absolute; inset: 0; height: 100%; }
}
.photo-credit {
  position: absolute; right: 0; bottom: 0;
  font-family: var(--sans); font-size: 0.625rem; letter-spacing: 0.02em;
  line-height: 1;
  color: #fff;
  background: rgba(16,22,20,.58);
  padding: 0.3rem 0.45rem;
}
.book .eyebrow { color: var(--teal-deep); }
.book h2 {
  font-style: italic;
  font-size: clamp(1.75rem, 1.1rem + 2.4vw, 2.9rem);
  margin-bottom: 1rem;
  text-wrap: balance;
}
/* Operator 2026-08-29 02:18: "let us get you moving font doesn't pop. hard to
   read." It was Newsreader ITALIC 400 — the lightest, thinnest thing this face
   offers — at display size, which is why it read as a whisper. Three changes,
   in order of how much ink each buys:

   1. UPRIGHT, not italic. This is not only a style call. The Google Fonts link
      loads Newsreader italic at 400 and 500 ONLY; the upright axis carries
      400/500/600/700. MEASURED at 100px: italic 600 and italic 700 both render
      at exactly the italic-500 advance, i.e. asking for a bold italic here gets
      a 500. Italic could never have been made heavy. Upright can.
   2. WEIGHT 600 for the line, 700 for the accent word, so "moving" is the
      heaviest thing in the heading as well as the only coloured one.
   3. Size held rather than "reclaimed", because the constraint is horizontal and
      the eyebrow leaving the aside gave VERTICAL room, not width. Upright 600 is
      12.44 em-widths against italic 400's 10.77 at this tracking — 15% wider per
      point of size — so the same column buys fewer points. Net at 1440:
      37.6px/400/italic -> 35.2px/600/upright, a 6% size cut against a 200-unit
      weight rise and the switch off the thin italic, which is a large net gain
      in ink. The alternative was widening the aside column, which moves the form
      and costs the actions row its single line; that trade was rejected.

   The accent stays --wine, not the band's --teal-deep: the phone and email below
   are already teal-deep, so a third teal element would not read as an accent. */
.book h2.book-h2 {
  font-style: normal;
  font-weight: 600;
  /* MEASURED, not modelled. The binding width is the aside column just above the
     two-column breakpoint: 337px at 901. Against the rendered advance the ramp
     below leaves ~4-5% headroom at EVERY step of the two-column range (901, 920,
     960, 1024, 1100, 1280) and ~4% at the 1440 cap, where the column stops
     growing at 455.6px. Do not raise the cap past 2.2rem without re-measuring
     1440 — the wrap's max-width, not the viewport, sets the ceiling there. */
  font-size: clamp(1.55rem, 0.075rem + 2.75vw, 2.2rem);
  letter-spacing: -0.03em;
}
/* The accent word is the only 700 on the page's serif and the only wine one.
   Upright like its line: an italic here would be a 500 (see above) and would sit
   LIGHTER than the words around it, which is the opposite of an accent. */
.book-h2 em { font-style: normal; font-weight: 700; color: var(--wine); }
@media (max-width: 900px) {
  /* Below the two-column layout the column is the full page width minus the
     wrap padding, so the size tracks the viewport directly instead of the
     column's fr share. Tuned at 390 (350px column) and checked at 360. */
  .book h2.book-h2 { font-size: clamp(1.5rem, 0.1rem + 6.5vw, 2.2rem); }
}
/* A shade darker than --grey. The shared --grey is only 4.79:1 on pure white,
   so it had almost no headroom to spend; over the band's photo tint it fell to
   4.32:1. This clears 4.5 with margin instead of relying on the backdrop being
   perfectly white. (--grey being that thin page-wide is worth a separate look.) */
.book-aside p { color: #5f6866; }
.book-contacts {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 0.6rem; margin-top: 1.4rem;
}
/* The phone and email lines are ONE designed unit: same face, size, weight and
   colour for both values, the same .small label register on both, baseline
   alignment within each line and one shared gap between them. They previously
   differed (1.5rem vs 1.125rem), which is what read as unformatted. 1.25rem is
   the size at which the 26-character address still fits a 390px column, so the
   two can match without the email forcing a break. */
/* Operator 2026-08-29: "phone number and email text don't match rest of site."
   Both values were in the DISPLAY serif, which on this page is the voice of
   headlines, not of data — a phone number set in it reads as a pull quote. They
   are now the body sans at 600, matching every other piece of read-it-and-act-on-
   it text on the page, at one shared size so the pair reads as one unit.
   `font-variant-numeric: tabular-nums` on the phone so the digits sit on an even
   rhythm rather than the proportional figures the sans defaults to. */
.book-call {
  display: inline-flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
  font-family: var(--sans); font-size: 1.0625rem; font-weight: 600;
  letter-spacing: -0.005em;
  font-variant-numeric: tabular-nums;
  line-height: 1.35;
  color: var(--teal-deep); text-decoration: none;
}
.book-email { overflow-wrap: anywhere; max-width: 100%; }
.book-call:hover { text-decoration: underline; }
/* "IN A HURRY?" carries this section's accent (the same --teal-deep as its
   eyebrow) rather than the neutral grey, so the label reads as the prompt it is.
   6.97:1 on the white band, up from 4.80:1 in grey. */
.book-call .small {
  font-family: var(--sans); font-size: 0.75rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--teal-deep);
}

.form-card {
  background: var(--tint-soft);
  border: 1px solid var(--grey-line);
  border-radius: var(--radius);
  padding: clamp(1.35rem, 3vw, 2.25rem);
}
/* Operator 2026-08-29 02:18: the card's sans h3 title and its instruction
   paragraph are removed, and the aside's eyebrow moved in here. So the card is
   labelled in the eyebrow register — the same .eyebrow the rest of the page uses
   to name a band — and then goes straight into the fields, with no rule and no
   prose between the label and "Your name". Colour is the band's own accent
   (--teal-deep, 6.97:1 on this card's --tint-soft), matching what the eyebrow
   was in the aside so the move is a relocation, not a restyle. */
/* Operator 2026-08-29 02:26: "THE BOOK AN APOINTMENT FONT SHOULD BE BIGGER" — at
   the shared 0.75rem it read as a caption on the card rather than as the card's
   title. It steps up to 1.1875rem, which is a proper title size, and stays in the
   tracked-caps register the page uses for labels.
   The tracking comes DOWN as the size goes up, which is the point of "keep the
   tracking proportional so it doesn't gap out": 0.18em is tuned for 12px type
   (2.2px of air), and carried straight to 19px it would open to 3.4px and read as
   s p a c e d  o u t. 0.1em puts it back at 1.9px — the same optical air as the
   small eyebrows elsewhere. Weight 700 rather than the eyebrow's 600, because
   caps at this size on a tinted card need the extra stem to hold. */
.form-eyebrow {
  color: var(--teal-deep);
  font-size: 1.1875rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  margin-bottom: 1.35rem;
}

.field-grid { display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.field { display: flex; flex-direction: column; gap: 0.4rem; }
.field.full { grid-column: 1 / -1; }
.field label {
  font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.04em; color: var(--ink);
}
.field input, .field select, .field textarea {
  font: inherit; font-size: 0.9375rem;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--grey-line);
  border-radius: 9px;
  padding: 0.75rem 0.9rem;
  width: 100%;
}
.field textarea { min-height: 120px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: 2px solid var(--teal); outline-offset: -1px; border-color: var(--teal);
}
/* Three peers in descending weight: filled primary, outlined call, text email.
   All three wrap rather than overflow on a 390px phone. */
/* Primary alone on the left, the two secondary options grouped hard right, so
   the booking action is the one the eye lands on. "Send email" used to be a
   bare text link (.form-opt, now removed) — it is the same outline pill as
   "Call the clinic" so the row reads as one family: filled + two outlines. */
/* align-items was `stretch` because the two option pills carried an icon (17px)
   and the primary carried only text (15px line box), so centring left the primary
   2px shorter than its neighbours. As of 2026-08-29 02:16 the primary carries the
   SAME 17px icon, so all three line boxes are identical and the `min-height: 49px`
   floor below matches them outright — and the row now has a fourth child, the "or"
   divider, which under `stretch` would have been pulled to the full 49px row height
   and sat with its baseline off. So this is `center`: the pills are equal by their
   own floor, and the word centres against them.
   The column gap is a MINIMUM — space-between opens the real separation to
   whatever is left over — and it was 1.5rem so the three controls still fit on
   one row inside the card at 1440 (they wrapped at 2.5rem, which silently
   destroyed the left/right split this rule exists to create). It is now 0.5rem,
   MEASURED: the row grew by the submit's new icon (+25px) and by the "or" divider
   (+13px plus a second gap), which at 1.5rem put the total at 556px inside a
   544px card and wrapped. At 0.5rem here, 0.6rem between the two options and
   0.9rem of inline padding on them, the row needs 532px of that 544px — one row
   at 1440 with 12px spare, and space-between still opens the real separation, so
   the smaller minimum costs nothing visually. (Below ~1280 the card is narrower
   than the row needs and it wraps to two rows; that was already true before this
   change, and the stacked form reads correctly.) */
.form-actions {
  margin-top: 1.35rem;
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between;
  gap: 0.75rem 0.5rem;
}
/* Operator 2026-08-29 02:16: the classic "primary — or — alternatives" divider.
   Deliberately the quietest thing in the row: sans, lowercase, small, muted, so
   it separates without competing with either side. #5f6866 is the same body-muted
   used elsewhere on this band (4.5:1+ on the card tint), not a lighter grey. */
.form-or {
  font-family: var(--sans); font-size: 0.8125rem; font-weight: 500;
  color: #5f6866; line-height: 1;
  flex: 0 0 auto;
}
.form-opts { display: flex; flex-wrap: wrap; align-items: stretch; gap: 0.6rem; }
.form-alt { padding-inline: 0.9rem; }
/* 49px is the natural height of a pill carrying a 17px icon. This is now the ONLY
   thing equalising the three (align-items is `center`, see above), which is why it
   is stated outright rather than left to the layout — and it was already doing the
   job at 390, where the row becomes a COLUMN and stretch equalises width, not
   height. */
.form-actions .pill { min-height: 49px; }
.form-alt .ico { stroke: var(--teal); transition: stroke .18s ease; }
.form-alt:hover .ico { stroke: #fff; }

/* On a phone the primary takes the full width on its own row and the two
   options sit in a clearly separated row beneath it, with the "or" divider
   centred between the two rows — the same relationship the wide row states
   horizontally. The stacked gap drops to 0.7rem because the divider now sits in
   the middle of it: at 1.15rem on both sides the word floated in a void. */
@media (max-width: 560px) {
  .form-actions { flex-direction: column; align-items: stretch; gap: 0.7rem; }
  .form-actions > .pill { width: 100%; }
  .form-or { text-align: center; }
  .form-opts { justify-content: center; gap: 0.6rem; }
  .form-opts .pill { flex: 1 1 auto; padding-inline: 0.9rem; }

  /* The 26-character address wraps below its label on a phone while the shorter
     phone number stays inline, so the two lines stopped matching. Stacking BOTH
     keeps the pair one consistent unit at this width. */
  .book-call { flex-direction: column; align-items: flex-start; gap: 0.15rem; }
}

/* Success state. Sized and padded like the form it replaces so the column does
   not jump when they swap. */
.form-done { text-align: center; }
.form-done-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: 50%;
  background: var(--teal); margin-bottom: 1.1rem;
}
.form-done-mark svg { fill: none; stroke: #fff; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.form-done h3 {
  font-family: var(--serif); font-style: italic; font-weight: 500;
  font-size: clamp(1.35rem, 1.1rem + 1vw, 1.75rem);
  color: var(--ink); margin: 0 0 0.7rem;
}
.form-done p { color: var(--ink-soft); font-size: 0.9375rem; line-height: 1.6; margin: 0 auto; max-width: 46ch; }
.form-done-alt {
  margin-top: 1.1rem !important; padding-top: 1.1rem;
  border-top: 1px solid var(--grey-line);
  font-weight: 600;
}
.form-done-alt a { color: var(--teal-deep); overflow-wrap: anywhere; }

@media (max-width: 900px) { .book .wrap { grid-template-columns: 1fr; } }
@media (max-width: 560px) { .field-grid { grid-template-columns: 1fr; } }

/* ---------- 11. footer ----------------------------------------------------- */

.footer { background: var(--teal); color: rgba(255,255,255,.88); padding-block: clamp(3rem, 6vw, 4.5rem) 0; }
/* THE HOURS COLUMN IS SIZED BY ITS CONTENT, NOT BY A GUESSED FRACTION (ported
   from the clinical master, commit 26b7330c, batch 7 #2a). This demo carried the
   identical defective rules and was measured spilling too: at 1440 the hours
   column's own content ended 16.1px OUTSIDE its 201.8px track, in the gutter in
   front of FIND US (31.9px of clear space left of the 48px gutter). Nothing saw
   it: the page never scrolls horizontally, so blueprint-gate's `overflow` reads
   0, and the text stays on its own tinted ground so text-contrast and page-void
   read clean too. A 0.8fr track resolves to ~202px at 1440 while "Wednesday" +
   gap + "8:30am to 8pm" needs ~218px — the DEFAULT week overran, not some rare
   value. `auto` is the fix rather than a bigger fraction: the track takes exactly
   the width the week needs and gives the rest back, so no future set of day names
   or time strings can be the one that does not fit, and there is no magic number
   to re-tune. The three fr siblings keep minmax(0, …) so they yield the space
   instead of pushing the page wide. The wrap fallback in .hours-time below covers
   the case where even auto cannot be honoured (a very narrow parent). */
.footer-grid {
  display: grid; gap: clamp(1.75rem, 4vw, 3rem);
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.8fr) minmax(0, auto) minmax(0, 1.2fr);
  align-items: start;
}
.footer h3 {
  color: #fff; font-family: var(--sans);
  font-size: 0.75rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase;
  margin-bottom: 1rem;
}
.footer a { color: #fff; text-decoration: none; }
.footer a:hover { text-decoration: underline; }
.footer ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.55rem; font-size: 0.9375rem; }
.footer-logo-plate {
  display: inline-block; background: #fff; border-radius: 10px;
  padding: 0.85rem 1.1rem; margin-bottom: 1.25rem;
}
.footer-logo-plate img { width: 180px; height: auto; }
.footer address { font-style: normal; font-size: 0.9375rem; line-height: 1.7; }
.footer-addr { margin: 0 0 0.75rem; }
/* Phone / email / fax: one icon line each, all sharing a left edge. The icon is
   a fixed-width flex item so the labels align with each other too. */
.contact-line {
  display: flex; align-items: center; gap: 0.6rem;
  margin: 0 0 0.45rem;
  color: #fff; text-decoration: none;
}
.contact-line:last-child { margin-bottom: 0; }
.contact-line .ico { stroke: var(--teal-light); }
.footer a.contact-line:hover span { text-decoration: underline; text-underline-offset: 3px; }
/* Today's row is marked at load by main.js. The dot's gutter is reserved on
   EVERY row and the dot is simply transparent until it is today's, so marking a
   row costs zero layout shift — and with JS off the list renders exactly as it
   always did, just uniformly indented. */
/* One grid for the whole week, with the rows as display:contents, so "day" and
   "time" are two REAL columns: every time starts at the same x and is left
   justified. They were flex rows with space-between, which pushed each time to
   its own row's right edge and left the column ragged. */
/* max-width CAPS what the `auto` footer track can ask for. Without it a single
   very wordy hours line would be free to take the whole row and squeeze FIND US
   toward zero, which is the overrun's mirror image rather than its fix. Above
   the cap the value wraps inside its own column (see .hours-time). 20rem holds
   every real week measured on this fleet with room to spare. */
.hours-grid {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: 1rem; row-gap: 0.5rem;
  font-size: 0.9375rem;
  max-width: 20rem;
}
.hours-row { display: contents; }
.hours-day { position: relative; padding-left: 0.95rem; }
/* The time column takes the remaining width (minmax(0, 1fr) above) instead of
   max-content, and may WRAP rather than spill. `nowrap` was the second half of
   the overrun: with it, a value the track could not hold had nowhere to go but
   outside the column. A clock range still never breaks in practice — the auto
   track on .footer-grid gives the week the width it asks for — so this is the
   graceful floor, not the normal render. `overflow-wrap: anywhere` is the last
   resort for a single unbroken token, matching .form-done-alt's idiom above. */
.hours-time { text-align: left; min-width: 0; overflow-wrap: anywhere; }
/* Today's marker: a triangle POINTING RIGHT at the day name, sitting 2px lower
   than the old dot. Colour is set by main.js from the visitor's real local time
   against this row's own data-open/data-close, so it states whether the clinic
   is open RIGHT NOW, not merely which day it is.

   Both tones are light because the --teal band sits mid-luminance (0.189), so
   darkening cannot reach 3:1 while lightening can; each is >=3:1 against the
   band and they are far apart in hue. */
.hours-day::before {
  content: ""; position: absolute; left: 0; top: calc(0.5em + 2px);
  /* content-box explicitly: the global border-box rule makes `width: 0` resolve
     against the borders that FORM the triangle, so the box stops collapsing and
     the shape is no longer a clean triangle. */
  box-sizing: content-box;
  width: 0; height: 0; border-radius: 0;
  border-top: 4.5px solid transparent;
  border-bottom: 4.5px solid transparent;
  border-left: 7.5px solid transparent;
  background: none;
}
.hours-time { color: rgba(255,255,255,.78); }
.hours-row[data-today="true"] { font-weight: 700; color: #fff; }
.hours-row[data-today="true"] .hours-time { color: #fff; }
.hours-row[data-today="true"][data-state="open"] .hours-day::before { border-left-color: #9bf0a8; }
/* #ffc6b6 measured 2.93:1 on the band — under the 3:1 non-text bar — so this is
   a shade lighter at 3.12:1. On a mid-luminance teal only light tints can reach
   3:1, which is why "closed" is a warm coral rather than a saturated red. */
.hours-row[data-today="true"][data-state="closed"] .hours-day::before { border-left-color: #ffcfc0; }
.footer-map {
  width: 100%; height: 210px; border: 0; border-radius: 10px; display: block;
  background: var(--teal-deep);
}
/* Get directions is a white button in the shared .pill geometry, not a text
   link, and carries the Google Maps pin in Google's own red as the one spot of
   colour down here — the same source-attribution role the G plays on reviews. */
/* No wrapper element: the button centres itself, so the only thing the contrast
   gate can measure here is the button, which carries its own white fill.
   `.footer a { color:#fff }` is (0,1,1) and outranks `.pill-white`'s (0,1,0),
   so the label rendered white-on-white and vanished. Stated at (0,2,0) here. */
.footer .btn-directions {
  color: var(--ink);
  padding: 0.8rem 1.25rem;
  display: flex; width: max-content;
  margin: 0.9rem auto 0;
}
.footer .btn-directions:hover { color: var(--ink); background: var(--tint); }
.ico-pin { flex: 0 0 auto; stroke: none; }
.footer-directions a { text-decoration: underline; text-underline-offset: 3px; }
.footer-bottom {
  margin-top: clamp(2.25rem, 5vw, 3.25rem);
  border-top: 1px solid rgba(255,255,255,.24);
  padding-block: 1.4rem;
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; align-items: center;
  font-size: 0.8125rem; color: rgba(255,255,255,.78);
}
.footer-bottom .spacer { margin-left: auto; }
/* Google place-photo attribution. Same size as the rest of the colophon so it
   is a real credit rather than fine print hidden below the legible floor. */
.footer-credit a { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

@media (max-width: 980px) { .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .footer-grid { grid-template-columns: 1fr; } }

/* ---------- preview badge -------------------------------------------------- */

.preview-badge {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 14px; z-index: 120;
  display: flex; align-items: stretch; gap: 1px;
  background: rgba(31,37,35,.94);
  border-radius: 999px; overflow: hidden;
  box-shadow: 0 10px 30px rgba(0,0,0,.28);
  font-family: var(--sans); font-size: 0.75rem;
  max-width: calc(100vw - 24px);
}
.preview-badge a { text-decoration: none; padding: 0.6rem 1rem; white-space: nowrap; }
.preview-badge-info { color: rgba(255,255,255,.85); }
.preview-badge-cta { background: var(--teal-light); color: #14201d; font-weight: 700; }
.preview-badge-cta:hover { background: #fff; }

@media (max-width: 540px) {
  .preview-badge { font-size: 0.6875rem; }
  .preview-badge a { padding: 0.5rem 0.7rem; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* ---------- display-heading ink sweep (TASTE_LEDGER #44/#53) ---------------- */

/* Operator 2026-08-29, THREE separate flags in nine minutes — "let us get you
   moving font doesn't pop. hard to read" (booking h2), "NEEDS TO BE THICKER"
   (Rated 4.9 on Google.), "HARD TOR EAD" (Three steps, and none of them are
   paperwork.). Three reports of ONE defect, so this is a class fix rather than
   three patches: every display heading on the page was Newsreader at weight 400,
   and six of the seven were also ITALIC — the thinnest thing this typeface can
   produce, set at 28-64px, three of them reversed white on a photographed dark
   band. That is the whole complaint.

   Why upright and not "bold italic": MEASURED at 100px on this page, Newsreader
   italic renders identically at 500, 600 and 700 — the stylesheet link loads the
   italic axis at 400 and 500 ONLY, so every heavier italic request silently
   resolves to a 500. Italic on this page therefore CANNOT hold 600 of ink at any
   size. The upright axis carries 400/500/600/700 for real. So the doctrine is
   satisfiable only upright, and the italic voice moves to the places that are not
   load-bearing display type.

   -0.02em because a 600 at display size sets loose at the base -0.01em.

   One deliberate exception, stated rather than omitted: .form-done h3 (the
   booking confirmation panel) keeps italic 500. It is a state message, not a
   display heading, it tops out at 28px, it sits on white, and 500 is the heaviest
   italic that exists here — so it is already at its own ceiling.

   The per-heading rules above are untouched on purpose: they own each heading's
   size, colour and measure, and this owns the one property they were all wrong
   about. Same specificity (0,1,1), so this block must stay LAST in the file. */
.hero h1,
.mission h2,
.split-intro h2,
.friction h2,
.about h2,
.reviews-head h2,
.firstvisit h2,
.book h2 {
  font-style: normal;
  font-weight: 600;
  letter-spacing: -0.02em;
}
