
@charset "utf-8";
/* ==========================================================================
   UBM Scans — design system
   Governed by "UBM Brand Guidelines" (client-supplied). Approved palette only.
   Palette ROLES respected: Peach = primary action, Blush = soft accent
   surface, Cream = background, Slate = BODY TEXT ONLY (never a surface).
   Peach carries neither White (1.88:1) nor normal Slate (2.96:1), so labels on
   a Peach fill use --on-peach, the deep tone of Slate that reaches 4.52:1.
   ========================================================================== */
@import url("../fonts/fonts.css");

/* ---------- tokens ---------- */
:root {
  /* Approved brand palette — do not add colours outside this set. */
  --peach:  #EDB08A;
  --blush:  #FCE7D9;
  --cream:  #FFF6ED;
  --taupe:  #A79B90;
  --slate:  #5E6A71;
  --white:  #FFFFFF;

  /* Derived tones of approved colours — used for hover states, rules and
     shadows only. These are shades of Slate, not additions to the palette. */
  /* Slate is the palette's BODY TEXT colour — never a surface. These are the
     deep tones of it used as LABELS on a Peach fill, where normal Slate (2.96:1)
     and White (1.88:1) both fail. */
  --on-peach:       #454E54; /* label on Peach       — 4.52:1 */
  --on-peach-hover: #3A4146; /* label on Peach-deep  — 4.57:1 */
  --peach-deep:     #E89A6A; /* primary hover fill, same hue as Peach */
  --line:        rgba(94, 106, 113, .16);
  --line-soft:   rgba(94, 106, 113, .09);
  --shadow-sm:   0 1px 2px rgba(94, 106, 113, .06), 0 2px 8px rgba(94, 106, 113, .05);
  --shadow-md:   0 4px 12px rgba(94, 106, 113, .08), 0 12px 32px rgba(94, 106, 113, .07);
  --shadow-lg:   0 8px 24px rgba(94, 106, 113, .10), 0 24px 64px rgba(94, 106, 113, .09);

  --font-display: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --font-body:    "Montserrat", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  /* Type scale. Tracking is size-specific: display tightens, small text opens. */
  --fs-display: clamp(2.55rem, 1.5rem + 3.6vw, 4.25rem);
  --fs-h1:      clamp(2.2rem, 1.45rem + 2.7vw, 3.5rem);
  --fs-h2:      clamp(1.85rem, 1.35rem + 1.8vw, 2.7rem);
  --fs-h3:      clamp(1.3rem, 1.1rem + .7vw, 1.65rem);
  --fs-h4:      clamp(1.1rem, 1.02rem + .32vw, 1.28rem);
  --fs-lead:    clamp(1.02rem, .97rem + .26vw, 1.19rem);
  --fs-body:    1rem;
  --fs-sm:      .9375rem;
  --fs-xs:      .8125rem;

  --lh-display: 1.05;
  --lh-heading: 1.16;
  --lh-body:    1.72;

  --tr-display: -.022em;
  --tr-h2:      -.014em;
  --tr-h3:      -.006em;
  --tr-body:    0;
  --tr-sm:      .006em;
  --tr-eyebrow: .17em;

  /* Spacing */
  --sp-1: .25rem;  --sp-2: .5rem;   --sp-3: .75rem;  --sp-4: 1rem;
  --sp-5: 1.5rem;  --sp-6: 2rem;    --sp-7: 2.75rem; --sp-8: 3.5rem;
  --sp-9: 4.5rem;  --sp-10: 6rem;   --sp-11: 7.5rem;
  /* Ceiling pulled from 6.5rem: at 1440 that put 104px of padding at both ends
     of every section, so a section boundary opened a ~145px band of nothing and
     any section with short content read as mostly empty. */
  --section-y: clamp(3rem, 1.8rem + 4vw, 4.75rem);

  --radius-sm: 8px;
  --radius:    14px;
  --radius-lg: 22px;
  --radius-pill: 999px;

  --container: 1440px;
  --measure: 68ch;

  /* Motion — Phase 1.5 budget. Named curves only, never bare keywords. */
  --ease-out:    cubic-bezier(.23, 1, .32, 1);
  --ease-in-out: cubic-bezier(.77, 0, .175, 1);
  --ease-drawer: cubic-bezier(.32, .72, 0, 1);
  --dur-press:   160ms;
  --dur-fast:    180ms;
  --dur-mid:     240ms;
  --dur-slow:    320ms;

  --header-h: 78px;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--cream);
  color: var(--slate);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-body);
  font-weight: 400;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* `hidden` on the body propagates to the viewport and makes the document a
     clipped scroll box. That is one of the documented triggers for Chromium
     leaving stale tiles behind a `position: sticky` header on the way back up
     the page. `clip` stops the same sideways overflow without creating a
     scroll container at all; `hidden` stays below it as the fallback for
     browsers that do not know `clip`. */
  overflow-x: hidden;
}
@supports (overflow-x: clip) { body { overflow-x: clip; } }

img, picture, svg, video { max-width: 100%; display: block; height: auto; }
/* <picture> is an inline wrapper by default, so a child img's height:100% has
   nothing to resolve against and the photo fails to fill its aspect-ratio box.
   Make the wrapper a full-size block wherever we crop to a fixed ratio. */
.hero-figure picture,
.cat__media picture,
.card__media picture,
.gallery-grid picture { display: block; width: 100%; height: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; }

h1, h2, h3, h4, h5 {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--slate);
  margin: 0 0 var(--sp-4);
  line-height: var(--lh-heading);
}
h1 { font-size: var(--fs-h1); letter-spacing: var(--tr-display); line-height: var(--lh-display); }
h2 { font-size: var(--fs-h2); letter-spacing: var(--tr-h2); }
h3 { font-size: var(--fs-h3); letter-spacing: var(--tr-h3); }
h4 { font-size: var(--fs-h4); letter-spacing: var(--tr-h3); font-weight: 600; }
p  { margin: 0 0 var(--sp-4); max-width: var(--measure); }
/* UA default is `figure { margin: 1em 40px }` — that 40px inset stops the hero
   image aligning with the container edge and the header above it. */
figure, blockquote { margin: 0; }
ul, ol { margin: 0 0 var(--sp-4); padding-left: 1.15rem; }
li { margin-bottom: var(--sp-2); }
strong { font-weight: 600; }

:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--slate);
  outline-offset: 3px;
  border-radius: 4px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 50%; translate: -50% -140%;
  background: var(--peach); color: var(--on-peach);
  padding: .75rem 1.4rem; border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  z-index: 200; font-size: var(--fs-sm); font-weight: 600; text-decoration: none;
  transition: translate var(--dur-fast) var(--ease-out);
}
.skip-link:focus { translate: -50% 0; }

/* ---------- layout ---------- */
/* Brief: max-width 1440, no side padding on desktop. A gutter is retained
   between 1025–1480px so text never touches the viewport edge; above 1480px
   the viewport itself supplies the whitespace and the gutter drops to zero. */
.container {
  width: min(var(--container), 100%);
  margin-inline: auto;
  padding-inline: clamp(1.15rem, .6rem + 1.6vw, 2rem);
}
@media (min-width: 1481px) { .container { padding-inline: 0; } }

.section { padding-block: var(--section-y); }
.section--tint { background: var(--white); }
.section--blush { background: var(--blush); }

/* Two plain sections in a row stack BOTH their vertical paddings, so the gap
   between them lands at roughly double the intended rhythm. Collapse it —
   but only between plain sections: a tinted, blush or dark band is a genuine
   new surface and keeps its full breathing room. */
.section + .section { padding-top: calc(var(--section-y) * .4); }
.section--tint + .section,
.section--blush + .section,
.cta-band + .section,
.section + .section--tint,
.section + .section--blush,
.section + .cta-band { padding-top: var(--section-y); }
/* ...but a tinted section is itself a `.section`, so the rule above also fired on
   tint-followed-by-tint, where the background does NOT change. That stacked a full
   top padding onto the previous section's full bottom padding — roughly 13rem of
   blank cream with no edge in it to justify the gap. Seven pages had one (all six
   medical-imaging sub-pages, plus Heartbeat Bears). Same background = same rhythm
   as any other pair. Specificity ties with the rule above, so source order wins. */
.section--tint + .section--tint,
.section--blush + .section--blush { padding-top: calc(var(--section-y) * .4); }

.stack > * + * { margin-top: var(--sp-4); }
.grid { display: grid; gap: clamp(1.25rem, .8rem + 1.4vw, 2.25rem); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 15.5rem), 1fr)); }
/* Fixed-column grids strand an incomplete last row against the left edge — five
   tiles in a four-column track give "4 + 1 marooned". Flex with a wrap keeps the
   final row centred at every width, which is how .pkg-grid already solves this. */
.grid--center { display: flex; flex-wrap: wrap; justify-content: center; }
.grid--center > * { flex: 1 1 15rem; max-width: 20rem; }
/* Columns of unequal length: without this the shorter one stretches and its last
   card's padding balloons to match the taller column. */
.grid--start { align-items: start; }

/* ---------- shared bits ---------- */
.eyebrow {
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-eyebrow);
  text-transform: uppercase;
  font-weight: 600;
  color: var(--slate);
  margin: 0 0 var(--sp-3);
  display: flex; align-items: center; gap: .7rem;
}
.eyebrow::after {
  content: ""; flex: 0 0 2.6rem; height: 1px; background: var(--peach);
}
.eyebrow--center { justify-content: center; }
.eyebrow--center::before {
  content: ""; flex: 0 0 2.6rem; height: 1px; background: var(--peach); order: -1;
}

.lead { font-size: var(--fs-lead); line-height: 1.62; }
/* The heading takes the row. This box used to be capped at 46rem, so a heading
   like "How to Prepare for Your General Ultrasound Imaging" wrapped onto two
   lines inside a 736px box centred in a 1440px row — narrow, and wrapped for no
   reason. Only the supporting paragraph needs a measure, so the cap moved there. */
.section-head { max-width: none; margin-bottom: var(--sp-8); }
/* Brief: no narrow columns on desktop — the 1440 container is there to be used.
   No cap at all: the intro runs the full container width, and .container's own
   padding is what keeps it off the viewport edge. */
.section-head p { max-width: none; }
.section-head--center { margin-inline: auto; text-align: center; }
.section-head--center p { margin-inline: auto; }

.divider-heart {
  display: flex; align-items: center; justify-content: center;
  gap: .85rem; margin: var(--sp-6) auto; max-width: 16rem;
}
.divider-heart::before, .divider-heart::after {
  content: ""; flex: 1; height: 1px; background: var(--peach); opacity: .65;
}
.divider-heart svg { width: 19px; height: 19px; color: var(--peach); flex: none; }

/* ---------- buttons ---------- */
/* Primary: Slate fill + white text = 5.56:1. Peach cannot carry text at any
   approved pairing (max 2.96:1), so it appears here only as an outline/accent. */
.btn {
  --btn-bg: var(--peach);
  --btn-fg: var(--on-peach);
  --btn-bd: var(--peach);
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  min-height: 48px;
  padding: .82rem 1.7rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 2px solid var(--btn-bd);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: .045em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  text-align: center;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              transform var(--dur-press) var(--ease-out);
}
.btn svg { width: 17px; height: 17px; flex: none; }
.btn:active { transform: scale(.97); }

@media (hover: hover) and (pointer: fine) {
  /* Primary: deepen the Peach fill, darkening the label in step so contrast
     holds (4.57:1). Both are tones of approved colours, not new brand colours. */
  .btn:hover {
    background: var(--peach-deep); border-color: var(--peach-deep);
    color: var(--on-peach-hover); box-shadow: var(--shadow-md);
  }
}

.btn--secondary {
  --btn-bg: transparent;
  --btn-fg: var(--slate);
  --btn-bd: var(--peach);
}
@media (hover: hover) and (pointer: fine) {
  /* Secondary carries the Peach border at rest, so it fills with PEACH on hover
     — the border colour flooding the shape, which is what the outline promises.
     ONE behaviour on every surface (cream, white, blush, inside cards), so two
     identical-looking buttons never behave differently.
     The label must darken: Peach cannot carry white (1.88:1) or normal Slate
     (2.96:1). --slate-deepest is the deepest tone of Slate and reaches 4.52:1. */
  .btn--secondary:hover {
    background: var(--peach);
    border-color: var(--peach);
    color: var(--on-peach);
  }
}

/* (.btn--onslate removed — it existed only for the old dark CTA band. The band
   is Blush now, so every button on the site uses .btn or .btn--secondary and
   there is exactly one hover behaviour per variant.) */

.btn--wide { width: 100%; }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; }
.btn-row--center { justify-content: center; margin-top: var(--sp-6); }

/* Text link with a peach underline that grows on hover. */
.link-arrow {
  display: inline-flex; align-items: center; gap: .45rem;
  font-weight: 600; font-size: var(--fs-sm); color: var(--slate);
  text-decoration: none; padding-bottom: 2px;
  background-image: linear-gradient(var(--peach), var(--peach));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 2px;
  transition: background-size var(--dur-fast) var(--ease-out);
}
.link-arrow svg { width: 15px; height: 15px; transition: translate var(--dur-fast) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .link-arrow:hover svg { translate: 3px 0; }
}

/* ---------- header ---------- */
/* THE BAND OF STALE PIXELS UNDER THE HEADER — the whole history, so nobody
   reintroduces it.

   Symptom: scroll down, scroll back up, and a ~22px strip directly beneath the
   header keeps whatever last passed under it — a slab of blush from a tinted
   section, a line of body copy from the Medicare band, part of the footer.

   Three things were blamed before the real one was found:
     1. `backdrop-filter: blur()` on the header. Removed. Not the cause.
     2. The header sharing a paint layer with the document, "fixed" with
        `will-change: transform`. Not the cause.
     3. That hint being droppable, "fixed" with `transform: translateZ(0)`.
        Not the cause either.

   The actual cause was this element's own ::after. It was a scroll-edge mask
   positioned at `top: 100%`, so it painted 22px BELOW the header's box, and it
   was a cream-to-transparent gradient — semi-transparent. That pushed the
   header's composited layer 22px past its own opaque background, and Chromium
   blits scrolled content beneath a composited layer without reliably
   invalidating a semi-transparent overflow region. So the strip kept its old
   backing-store pixels. Promoting the layer harder only made the wrong bounds
   permanent, which is why each attempt above appeared to change nothing.

   The header now paints nothing outside its own box: the scroll cue is an
   INSET shadow, drawn inside an opaque background, and the layer bounds are
   exactly the header. Do not restore a ::after, a drop shadow, or a fading
   border here — anything that paints past the bottom edge brings the band
   back. `inset` also avoids the 1px layout shift a real border would cause,
   which matters because .mobile-nav is offset by var(--header-h).

   Keep `transform: translateZ(0)`: with the box now fully opaque, promotion is
   a genuine win rather than a way to freeze bad bounds. It is safe because the
   only fixed-position elements on the site, .mobile-nav and .nav-scrim, are
   siblings of <header>, not descendants. Do NOT add `contain: paint` — it
   clips descendants to the header box and would amputate the nav dropdown. */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--cream);
  transform: translateZ(0);
  backface-visibility: hidden;
  box-shadow: inset 0 -1px 0 transparent;
  transition: box-shadow var(--dur-mid) var(--ease-out);
}
.site-header[data-scrolled="true"] { box-shadow: inset 0 -1px 0 var(--line-soft); }

.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-5); min-height: var(--header-h);
}
.brand { display: inline-flex; align-items: center; flex: none; text-decoration: none; }
.brand img { width: auto; height: 46px; }
@media (max-width: 480px) { .brand img { height: 38px; } }

.nav { display: none; }
.nav-list { display: flex; align-items: center; gap: clamp(.4rem, -.4rem + 1.5vw, 1.5rem); list-style: none; margin: 0; padding: 0; }
.nav-list > li { margin: 0; position: relative; }
.nav-link {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .55rem .35rem;
  font-size: var(--fs-sm); font-weight: 500; text-decoration: none; color: var(--slate);
  background-image: linear-gradient(var(--peach), var(--peach));
  background-repeat: no-repeat; background-position: 0 100%; background-size: 0 2px;
  transition: background-size var(--dur-fast) var(--ease-out);
  white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) { .nav-link:hover { background-size: 100% 2px; } }
.nav-link[aria-current="page"], .nav-item--active > .nav-link { background-size: 100% 2px; font-weight: 600; }
.nav-link svg { width: 12px; height: 12px; transition: rotate var(--dur-fast) var(--ease-out); }
.nav-item--has-menu[data-open="true"] > .nav-link svg { rotate: 180deg; }

/* Dropdown grows from its trigger (top of the panel), never centre-origin. */
.nav-menu {
  position: absolute; top: calc(100% + 8px); left: 50%; translate: -50% 0;
  transform-origin: top center;
  min-width: 20rem; padding: .6rem;
  background: var(--white);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  list-style: none; margin: 0;
  opacity: 0; scale: .96; visibility: hidden;
  transition: opacity var(--dur-fast) var(--ease-out),
              scale var(--dur-fast) var(--ease-out),
              visibility 0s linear var(--dur-fast);
}
.nav-item--has-menu[data-open="true"] .nav-menu {
  opacity: 1; scale: 1; visibility: visible;
  transition: opacity var(--dur-fast) var(--ease-out),
              scale var(--dur-fast) var(--ease-out),
              visibility 0s;
}
.nav-menu li { margin: 0; }
.nav-menu a {
  display: block; padding: .62rem .85rem; border-radius: var(--radius-sm);
  font-size: var(--fs-sm); text-decoration: none; color: var(--slate);
  transition: background-color var(--dur-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) { .nav-menu a:hover { background: var(--blush); } }
.nav-menu a[aria-current="page"] { background: var(--blush); font-weight: 600; }
/* The group heading is itself the link to the hub page, so it needs the
   interactive affordances a plain label did not. */
.nav-menu__group > a {
  display: block;
  padding: .6rem .85rem .45rem;
  margin-top: .2rem;
  font-size: var(--fs-xs); letter-spacing: var(--tr-eyebrow); text-transform: uppercase;
  font-weight: 700; color: var(--slate);
  border-radius: var(--radius-sm);
}
.nav-menu__group:first-child > a { margin-top: 0; }
.nav-menu .nav-menu__sub { padding-left: 1.5rem; }

.header-cta { display: none; align-items: center; gap: var(--sp-3); flex: none; }
.header-phone {
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: var(--fs-sm); font-weight: 600; text-decoration: none; color: var(--slate); white-space: nowrap;
}
.header-phone svg { width: 16px; height: 16px; color: var(--peach); }

.nav-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; flex: none;
  background: transparent; border: 1px solid var(--line); border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.nav-toggle:active { transform: scale(.97); }
.nav-toggle svg { width: 22px; height: 22px; }
.nav-toggle .icon-close { display: none; }
.nav-toggle[aria-expanded="true"] .icon-open { display: none; }
.nav-toggle[aria-expanded="true"] .icon-close { display: block; }

@media (min-width: 1100px) {
  .nav { display: block; }
  .header-cta { display: flex; }
  .nav-toggle { display: none; }
}

/* Mobile drawer */
.mobile-nav {
  position: fixed; inset: var(--header-h) 0 0 auto;
  width: min(24rem, 100%);
  background: var(--white);
  border-left: 1px solid var(--line-soft);
  padding: var(--sp-5) var(--sp-5) var(--sp-9);
  overflow-y: auto; z-index: 99;
  translate: 100% 0;
  visibility: hidden;
  transition: translate var(--dur-mid) var(--ease-drawer), visibility 0s linear var(--dur-mid);
}
.mobile-nav[data-open="true"] {
  translate: 0 0; visibility: visible;
  transition: translate var(--dur-mid) var(--ease-drawer), visibility 0s;
}
.mobile-nav ul { list-style: none; margin: 0; padding: 0; }
.mobile-nav li { margin: 0; }
.mobile-nav li a{ border-bottom: 1px solid var(--line-soft); }
.mobile-nav a {
  display: block; padding: .85rem .25rem; text-decoration: none; color: var(--slate);
   font-size: 16px;
}
.mobile-nav a[aria-current="page"] { font-weight: 700; }
.mobile-nav .is-sub a { padding-left: 1.15rem; font-size: var(--fs-sm); }
.mobile-nav .is-sub-2 a { padding-left: 2.15rem; font-size: var(--fs-sm); }
.mobile-nav__cta { margin-top: var(--sp-6); display: grid; gap: var(--sp-3); }
.nav-scrim {
  position: fixed; inset: 0; z-index: 98;
  background: rgba(94, 106, 113, .34);
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur-mid) var(--ease-out), visibility 0s linear var(--dur-mid);
}
.nav-scrim[data-open="true"] { opacity: 1; visibility: visible; transition: opacity var(--dur-mid) var(--ease-out), visibility 0s; }
@media (min-width: 1100px) { .mobile-nav, .nav-scrim { display: none; } }

/* ---------- hero ---------- */
/* Image sits to the RIGHT of the heading on desktop and ABOVE THE FOLD on
   small screens (it is the first grid row on mobile). */
.hero { position: relative; overflow: hidden; background: var(--cream); box-shadow: 0px 10px 20px 0px var(--blush);}
.hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(58% 68% at 88% 8%, var(--blush) 0%, transparent 62%),
    radial-gradient(46% 56% at 4% 96%, var(--blush) 0%, transparent 60%);
  opacity: .85;
}
.hero > .container { position: relative; }
.hero-grid {
  display: grid;
  gap: clamp(1.5rem, .8rem + 2.4vw, 3.25rem);
  align-items: center;
  padding-block: clamp(1.75rem, 1rem + 3vw, 4.5rem);
}
.hero-media { order: -1; }
.hero-copy { max-width: 38rem; }
@media (min-width: 900px) {
  /* The dead gap before the image was caused by the copy wrapping far short of
     its own column (max-width 38rem inside a ~44rem track), NOT by the track
     split. So the copy is released to fill its column and the image keeps its
     original proportion — widening the image column instead just made the
     photo dominate the fold. */
  /* The image track is expressed as a share of the row, not a fraction, so the
     brief ("max 50% width, prefer 45–48%") is encoded literally and holds at
     every width. The copy takes whatever is left after the gap. */
  .hero-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 47%); }
  .hero-media { order: 0; }
  .hero-copy { max-width: none; }
  .hero .lead { max-width: none; }
}
.hero h1 { margin-bottom: var(--sp-4); }
/* NB: no max-width on .hero .lead here. It previously sat AFTER the desktop
   media query and, at equal specificity, later source order won — silently
   overriding the `max-width: none` above and leaving the copy wrapping ~230px
   short of its column. The hero column is already a readable measure. */
.hero .btn-row { margin-top: var(--sp-6); }

.hero-figure {
  position: relative; border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: var(--shadow-lg);
  aspect-ratio: 4 / 3;
}
.hero-figure img { width: 100%; height: 100%; object-fit: cover; }
@media (min-width: 900px) { .hero-figure { aspect-ratio: 5 / 4; } }
.hero-figure::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .38);
  border-radius: inherit;
}

.hero-badges { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5); margin-top: var(--sp-6); padding: 0; list-style: none; }
.hero-badges li {
  display: flex; align-items: center; gap: .5rem; margin: 0;
  font-size: var(--fs-sm); font-weight: 500;
}
.hero-badges svg { width: 18px; height: 18px; color: var(--peach); flex: none; }

/* ---------- hero load timeline (the page's single signature moment) ----------
   Pure CSS with animation-fill-mode: both, so it runs at first paint and never
   depends on JavaScript. The hero image is the LCP element on most pages — it
   must never be left clipped by a script that was slow, deferred or blocked. */
@keyframes heroRise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes heroReveal {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}
.hero-anim { animation: heroRise 620ms var(--ease-out) both; }
.hero-copy > .hero-anim:nth-child(1) { animation-delay: 0ms; }
.hero-copy > .hero-anim:nth-child(2) { animation-delay: 70ms; }
.hero-copy > .hero-anim:nth-child(3) { animation-delay: 140ms; }
.hero-copy > .hero-anim:nth-child(4) { animation-delay: 210ms; }
.hero-copy > .hero-anim:nth-child(5) { animation-delay: 280ms; }
.hero-figure { animation: heroReveal 760ms var(--ease-out) 120ms both; }

/* Opt-in per page via hero(media_motion=True): the photo keeps drifting once
   the entrance wipe has landed, rather than settling into a still. It rides on
   the <img>, never on .hero-figure — the figure is already running heroReveal,
   and a second `animation` on the same element replaces the first rather than
   adding to it, which would drop the clip-path wipe.

   The scale floor is 1.06 so the drift can never expose an edge: at that scale
   the image overflows its box by 3% a side, comfortably more than the 1.5% the
   translate ever asks for. 30s and a 6% range is deliberately near-subliminal —
   this sits behind a phone number people are reading, and anything faster reads
   as a page that has not finished loading. */
@keyframes heroDrift {
  from { transform: scale(1.06) translate3d(0, 0, 0); }
  to   { transform: scale(1.12) translate3d(-1.2%, -1.5%, 0); }
}
.hero-figure--drift img {
  transform-origin: 55% 45%;
  animation: heroDrift 30s var(--ease-in-out) 900ms infinite alternate both;
}

/* ---------- hero compare slider (opt-in via hero(image_b=…)) ----------
   Two photos in one frame split by a draggable rule — Medical Imaging left,
   3D & 4D right. The reveal is a clip-path inset on the TOP layer, so both
   photos are painted once and never repainted per frame; only the clip and the
   divider's `left` change, and both stay on the compositor.

   Notes for anyone editing this:
   - the UNDER layer is the second image and the OVER layer is the first, so
     `image` (which keeps fetchpriority="high") is what shows on the left.
   - the builder writes both the clip-path and the divider offset INLINE, so
     with JS off the two photos still sit side by side at the default split.
     Only .cmp-handle is gated behind .js — showing a control that cannot be
     operated is worse than showing none.
   - touch-action: pan-y is load-bearing on phones. Without it the horizontal
     drag swallows vertical scrolling and the hero traps the page.
   - the ::after inset ring lives on .hero-figure, above both layers, so the
     rounded corner stays crisp across the seam. */
.hero-figure--compare { cursor: ew-resize; touch-action: pan-y; }
.hero-figure--compare .hero-frame { position: absolute; inset: 0; }
.hero-figure--compare .hero-frame img { width: 100%; height: 100%; object-fit: cover; }
.hero-figure--compare .hero-frame--over { will-change: clip-path; }
/* Per-photo crop bias is written inline by the builder (hero(focus=…)), NOT
   pinned to a layer here. It was on .hero-frame--under, so when the two sides
   were swapped the bias stayed behind and applied to the wrong photo. */

.cmp-divider {
  position: absolute; top: 0; bottom: 0; z-index: 10;
  width: 2px; margin-left: -1px;
  background: var(--peach);
  will-change: left;
}
/* Peach is an accent only — it carries no text here, just the rule and the
   handle's border. The chevrons are Slate on Cream (5.56:1). */
.cmp-handle { display: none; }
.js .cmp-handle {
  display: grid; place-items: center;
  position: absolute; left: 50%; top: 50%;
  width: 46px; height: 46px; margin: -23px 0 0 -23px;
  padding: 0; border-radius: 50%;
  cursor: ew-resize;
  border: 2px solid var(--peach);
  color: var(--slate);
  background: var(--cream);
  box-shadow: var(--shadow-md), 0 0 0 6px rgba(237, 176, 138, .22);
  transition: box-shadow 200ms var(--ease-out);
}
.js .cmp-handle svg { width: 18px; height: 14px; }
.js .cmp-handle:hover { box-shadow: var(--shadow-md), 0 0 0 9px rgba(237, 176, 138, .26); }
.js .cmp-handle:focus-visible { outline: 3px solid var(--slate); outline-offset: 3px; }

.cmp-label {
  position: absolute; top: var(--sp-4); z-index: 8;
  padding: .35rem .8rem; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: 600;
  letter-spacing: var(--tr-sm);
  color: var(--slate);
  background: rgba(255, 246, 237, .88);
  border: 1px solid var(--line);
  transition: opacity 300ms var(--ease-out);
}
.cmp-label--a { left: var(--sp-4); }
.cmp-label--b { right: var(--sp-4); }

/* ---------- before-and-after comparison grid ----------
   For photographs whose whole point is the pairing inside them. Unlike the
   curved gallery, nothing is cropped: each image keeps its own aspect ratio via
   `height: auto`, so a side-by-side arrives intact. That means rows of unequal
   height by design — `align-items: start` keeps them top-aligned rather than
   stretching a short image to match a tall neighbour.

   The white backplate matters: two of these are collages on a white ground, and
   without it they float on the tinted section with a hard edge. */
.cmp-grid {
  display: grid;
  /* 340px floor rather than 260 so a full-width container lands on THREE
     columns, not four. These are side-by-side composites: at four across each
     half is barely 150px and the comparison stops being readable, and five
     items over four columns leaves a conspicuous hole in row two. */
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  gap: var(--sp-5);
  align-items: start;
  margin-top: var(--sp-6);
}
.cmp-item { margin: 0; }
.cmp-item img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  background: var(--white);
}
.cmp-item figcaption {
  margin-top: .55rem;
  text-align: center;
  font-size: var(--fs-xs);
  letter-spacing: var(--tr-sm);
  color: var(--slate);
}

/* ---------- auto-scrolling marquee row ----------
   Sized by HEIGHT with width:auto, so each photo keeps its own aspect ratio.
   A fixed w/h box with object-fit:cover would crop, and on a before-and-after
   composite cropping throws away half the point.

   The seam: the track holds the list twice and shifts by exactly one copy. That
   is NOT -50% — with a flex `gap` the midpoint sits half a gap out and the loop
   visibly stutters once per cycle, so the shift is -50% minus half a gap.

   Paused on hover and on keyboard focus, so anyone reading a caption or tabbing
   through can stop it moving. */
.mq {
  --mq-gap: var(--sp-5);
  overflow: hidden;
  margin-top: var(--sp-6);
  -webkit-mask: linear-gradient(90deg, transparent 0%, #000 7%, #000 93%, transparent 100%);
  mask: linear-gradient(90deg, transparent 0%, #000 7%, #000 93%, transparent 100%);
}
.mq-track {
  display: flex;
  gap: var(--mq-gap);
  width: max-content;
  animation: mqScroll 46s linear infinite;
}
.mq:hover .mq-track,
.mq:focus-within .mq-track { animation-play-state: paused; }
.mq-item { margin: 0; flex: none; }
.mq-item img {
  display: block;
  height: clamp(200px, 12rem + 8vw, 330px);
  width: auto;
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  background: var(--white);
}
@keyframes mqScroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-50% - (var(--mq-gap) / 2))); }
}
@media (prefers-reduced-motion: reduce) {
  /* No autoplay. The row becomes an ordinary swipeable strip, and the duplicate
     copy is removed so the same photographs are not shown twice for no reason. */
  .mq { overflow-x: auto; -webkit-mask: none; mask: none; }
  .mq-track { animation: none; }
  .mq-track .mq-item[aria-hidden="true"] { display: none; }
}

/* ---------- quiet below-fold reveal ---------- */
.reveal { opacity: 0; translate: 0 12px; }
.js .reveal.is-in {
  opacity: 1; translate: 0 0;
  transition: opacity 420ms var(--ease-out), translate 420ms var(--ease-out);
  transition-delay: var(--d, 0ms);
}
.no-js .reveal { opacity: 1; translate: 0 0; }

/* ---------- cards ---------- */
.card {
  display: flex; flex-direction: column;
  background: var(--white);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--dur-mid) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.card__media { aspect-ratio: 16 / 10; overflow: hidden; background: var(--blush); }
.card__media img { width: 100%; height: 100%; object-fit: cover; }
.card__body { padding: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-3); flex: 1; }
.card__body > * { margin: 0; }
.card__body h3 { font-size: var(--fs-h3); }
.card__body p { font-size: var(--fs-sm); }
.card__foot { margin-top: auto; padding-top: var(--sp-2); }
.card--link { text-decoration: none; color: inherit; }
.card--link:active { transform: scale(.99); }
@media (hover: hover) and (pointer: fine) { .card--link:hover { box-shadow: var(--shadow-md); } }

/* Service cards led by the brand's own sub-lockups. The logo is the visual
   anchor, so it gets a generous panel of its own rather than sitting as a small
   mark above the text. The two lockups have very different aspect ratios
   (Medical Imaging is square, 3D & 4D is wide), so a fixed panel height plus
   object-fit: contain makes them read at equal weight. */
.svc-card {
  display: flex; flex-direction: column;
  background: var(--white);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  text-decoration: none; color: inherit;
  transition: box-shadow var(--dur-mid) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.svc-card:active { transform: scale(.99); }
@media (hover: hover) and (pointer: fine) {
  .svc-card:hover { box-shadow: var(--shadow-md); }
  .svc-card:hover .svc-card__logo { background: var(--blush); }
}
/* The panel is a FIXED height so both cards' titles and body copy line up on
   the same baseline. The logo inside is sized with max-height, which lets the
   two lockups differ optically without ever changing the panel. */
.svc-card__logo {
  display: grid; place-items: center;
  height: clamp(196px, 148px + 6.4vw, 288px);
  background: var(--cream);
  padding: var(--sp-5);
  border-bottom: 1px solid var(--line-soft);
  transition: background-color var(--dur-mid) var(--ease-out);
}
.svc-card__logo img {
  width: auto;
  max-height: clamp(112px, 78px + 5vw, 168px);
  max-width: 100%;
  object-fit: contain;
}
/* Optical balance: the Medical Imaging lockup is a compact stacked mark while
   3D & 4D is a wide one. Matched heights make the square one read smaller, so
   it is allowed to grow — inside the same fixed panel, so alignment holds. */
.svc-card__logo--stacked img { max-height: clamp(134px, 94px + 5.9vw, 198px); }
.svc-card__body {
  padding: var(--sp-6) var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-3); flex: 1;
}
.svc-card__body > * { margin: 0; }
/* Body font, not the display serif. Cormorant sets numerals as OLD-STYLE
   figures, so "3D & 4D" dropped its 3 and its 4 below the baseline and read as
   a different size from the sans copy directly beneath it. Montserrat has
   lining figures, so the digits sit on the baseline and match. Same treatment
   .icon-card h3 already uses. Montserrat runs optically larger than Cormorant
   at an equal px, hence the step down to --fs-h4. */
.svc-card__body h3 {
  font-size: var(--fs-h4);
  font-family: var(--font-body);
  font-weight: 600;
  letter-spacing: normal;
}
.svc-card__body p { font-size: var(--fs-sm); }
.svc-card__foot { margin-top: auto; padding-top: var(--sp-2); }

.icon-card {
  background: var(--white); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: var(--sp-6) var(--sp-5);
  box-shadow: var(--shadow-sm);
}
.icon-card__icon {
  width: 52px; height: 52px; border-radius: 50%;
  display: grid; place-items: center; background: var(--blush); margin-bottom: var(--sp-4);
}
.icon-card__icon svg { width: 25px; height: 25px; color: var(--slate); }
.icon-card h3 { font-size: var(--fs-h4); font-family: var(--font-body); font-weight: 600; margin-bottom: var(--sp-2); }
.icon-card p { font-size: var(--fs-sm); margin: 0; }

/* Centred variant, for tiles whose body is a line or two. A row of short tiles
   left-aligned leaves a ragged right edge and an obvious gap under the shortest
   one; centring reads as deliberate. Deliberately NOT applied to tiles carrying
   a full paragraph — centred prose that long is harder to read. */
.icon-card--center { text-align: center; }
.icon-card--center .icon-card__icon { margin-inline: auto; }
.icon-card--center p { margin-inline: auto; }
/* The arrow on a centred link tile centres with everything else. */
.icon-card--center .icon-card__foot { display: flex; justify-content: center; }

/* Icon beside the text rather than above it. On a wide card a stacked icon
   leaves a band of empty card across the top and pushes the paragraph down;
   alongside, the two read as one unit and the card keeps its height. */
.icon-card--row { display: flex; gap: var(--sp-5); align-items: flex-start; text-align: left; }
/* flex-shrink defaults to 1, so the 52px circle was being squeezed narrower by
   the text beside it and rendered as an oval. It never shrinks. */
.icon-card--row .icon-card__icon { margin-bottom: 0; flex: none; }
.icon-card--row .icon-card__body > *:last-child { margin-bottom: 0; }

/* Clickable variant: the whole tile is the target, so it needs a hit-state and a
   visible affordance. Column layout pins the arrow to the bottom so a row of tiles
   with different title lengths still lines its arrows up. */
.icon-card--link {
  display: flex; flex-direction: column; text-decoration: none; color: inherit;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              transform var(--dur-press) var(--ease-out);
}
.icon-card--link h3 { text-decoration: none; }
.icon-card__foot { margin-top: auto; padding-top: var(--sp-4); }
.icon-card--link .link-arrow svg { width: 20px; height: 20px; color: var(--peach); }
.icon-card--link:active { transform: translateY(1px); }
@media (hover: hover) and (pointer: fine) {
  .icon-card--link:hover { border-color: var(--peach); box-shadow: var(--shadow-md); }
  .icon-card--link:hover .link-arrow svg { transform: translateX(3px); }
}
.icon-card--link .link-arrow svg { transition: transform var(--dur-fast) var(--ease-out); }
.icon-card--link:focus-visible { outline: 3px solid var(--slate); outline-offset: 3px; }

/* ---------- explainer (page intro + its "what is it" answers) ---------- */
/* The intro used to run as a 46rem column against the left edge with the other
   half of the row empty, and the answers below it were bare headings sitting on
   the page background — two abreast, so the moment one question wrapped to a
   second line its answer no longer started level with its neighbour's. Same fix
   as .faq-layout below: narrow intro column, wider answer column, so the width
   is used. Stacking the answers means neither their questions nor their lengths
   have to match, and the row works with one answer or several. */
.explainer { display: grid; gap: clamp(1.5rem, .9rem + 2vw, 3rem); }
@media (min-width: 900px) {
  .explainer { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); align-items: start; }
  /* The intro holds its place while the answers scroll past it. */
  .explainer__intro { position: sticky; top: calc(var(--header-h) + 1.5rem); }
}
.explainer__intro p:last-child { margin-bottom: 0; }
.explainer__qa { display: grid; gap: var(--sp-5); }

.qa-card {
  background: var(--white);
  border: 1px solid var(--line-soft);
  /* Same peach edge the form status band uses — enough to read as an answer
     panel without a second card style entering the system. */
  border-left: 3px solid var(--peach);
  border-radius: var(--radius);
  padding: var(--sp-6);
  box-shadow: var(--shadow-sm);
}
/* The question stays an <h2> — it is the heading a reader and a search engine
   want — but it is not a section title, so it is set at h3 scale. */
.qa-card > :is(h2, h3) { font-size: var(--fs-h3); letter-spacing: var(--tr-h3); margin-bottom: var(--sp-3); }
/* The card is already a measured column; the global 68ch cap would leave a
   band of empty card down its right-hand side. */
.qa-card p { margin: 0; max-width: none; }

/* ---------- category section (image + full inclusion list + CTA) ---------- */
.cat {
  display: grid; gap: clamp(1.5rem, .9rem + 2vw, 3rem);
  align-items: center;
  padding-block: clamp(2.25rem, 1.4rem + 2.6vw, 3.75rem);
  border-top: 1px solid var(--line-soft);
}
.cat:first-of-type { border-top: 0; }
@media (min-width: 880px) {
  .cat { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  /* Media is first in source order (image left). Alternate rows flip it to the
     right for rhythm — set explicitly rather than via :nth-child, which would
     be thrown off by the section heading sharing the same parent. */
  .cat--flip .cat__media { order: 2; }
}
.cat__media { border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-md); aspect-ratio: 4 / 3; }
.cat__media img { width: 100%; height: 100%; object-fit: cover; }
.cat__body > *:last-child { margin-bottom: 0; }
.cat__list { list-style: none; padding: 0; margin: 0 0 var(--sp-5); display: grid; gap: var(--sp-2); }
.cat__list li { display: flex; gap: .65rem; align-items: flex-start; margin: 0; font-size: var(--fs-sm); }
.cat__list svg { width: 17px; height: 17px; color: var(--peach); flex: none; margin-top: .34rem; }
/* Short one- or two-word rows (the MSK "areas we scan" list) would otherwise run
   as a single narrow strip down a wide container, leaving the right half blank.
   `columns` needs a block formatting context, so the grid display above is undone
   here and the row rhythm comes from margin rather than the grid gap. */
.cat__list--cols { display: block; columns: 2; column-gap: var(--sp-6); }
.cat__list--cols li { break-inside: avoid; margin-bottom: var(--sp-2); }
@media (max-width: 480px) { .cat__list--cols { columns: 1; } }
/* Sentence-length rows under a full-width centred heading run as one narrow
   ribbon down the middle of the section with the rest of the row blank. Two
   tracks use the width and still land each line inside a readable measure.
   `--cols` cannot do this job: `columns` would let a two-line row break across
   the gap, which is fine for single words and wrong for sentences. */
@media (min-width: 860px) {
  .cat__list--split { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--sp-7); }
}
/* Eight sentence-length rows under a centred lead read as naked text pushed out
   to both container edges, and in a two-track grid every row is as tall as its
   longer side, so a one-line row leaves a gap the next row does not. As tiles
   each row carries its own edge and they all share a height, so the leftover
   space sits inside the card as padding instead of falling between the rows. */
.cat__list--tiles {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: var(--sp-5);
}
.cat__list--tiles li {
  background: var(--white); border: 1px solid var(--line-soft);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
  padding: var(--sp-4) var(--sp-5);
}
/* A white tile on the white band would be an outline and a shadow only. */
.section--tint .cat__list--tiles li { background: var(--cream); border-color: transparent; }
.prep {
  background: var(--blush); border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-5); margin: 0 0 var(--sp-5);
}
.prep h4 { font-family: var(--font-body); font-size: var(--fs-sm); font-weight: 700; margin-bottom: var(--sp-2); }
.prep ul { margin: 0; padding-left: 1.1rem; }
.prep li, .prep p { font-size: var(--fs-sm); margin-bottom: var(--sp-1); }
.prep > *:last-child { margin-bottom: 0; }

/* ---------- accordion ---------- */
/* Heading in a narrow left column, questions in a wider right one. A single
   54rem accordion under a full-width heading left roughly a third of the row
   empty; this uses the width while keeping answer line-length readable. */
.faq-layout { display: grid; gap: clamp(1.5rem, .9rem + 2vw, 3rem); }
@media (min-width: 900px) {
  .faq-layout { grid-template-columns: minmax(0, .8fr) minmax(0, 1.5fr); align-items: start; }
  /* The heading tracks the questions as they are opened and closed. */
  .faq-layout__head { position: sticky; top: calc(var(--header-h) + 1.5rem); }
  .faq-layout .section-head { margin-bottom: 0; }
}
.faq-layout .section-head { margin-bottom: var(--sp-5); }

/* ---------- narrative (closing / "why us" passages) ---------- */
/* A centred display heading above a single narrow prose column left roughly a
   third of the row blank down each side, and the section-head's own bottom
   margin opened a deep gap between the heading and its first line — on a short
   passage the whole band read as empty. Heading beside the prose instead: the
   row is used and the gap closes. Same shape as .faq-layout above. */
.narrative { display: grid; gap: clamp(1.5rem, .9rem + 2vw, 3rem); }
@media (min-width: 900px) {
  .narrative { grid-template-columns: minmax(0, .85fr) minmax(0, 1.35fr); align-items: start; }
  .narrative .section-head { margin-bottom: 0; }
}
.narrative .section-head { margin-bottom: var(--sp-4); max-width: none; }
.narrative__body > *:last-child { margin-bottom: 0; }
.narrative__body p { max-width: none; }
.narrative__body .btn-row { margin-top: var(--sp-5); }

.accordion { border-top: 1px solid var(--line); max-width: none; }
.acc-item { border-bottom: 1px solid var(--line); }
.acc-trigger {
  width: 100%; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-4);
  padding: var(--sp-5) 0;
  background: none; border: 0; cursor: pointer; text-align: left;
  font-family: var(--font-body); font-size: 1.02rem; font-weight: 600; color: var(--slate);
  line-height: 1.5;
  transition: color var(--dur-fast) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.acc-trigger:active { transform: scale(.995); }
.acc-icon {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center; background: var(--blush); margin-top: 1px;
  transition: rotate var(--dur-mid) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
.acc-icon svg { width: 15px; height: 15px; color: var(--slate); }
/* Caret down when collapsed, caret up when expanded. */
.acc-trigger[aria-expanded="true"] .acc-icon { rotate: 180deg; }
/* grid-template-rows animates without touching layout height directly. */
.acc-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur-mid) var(--ease-out); }
.acc-trigger[aria-expanded="true"] + .acc-panel { grid-template-rows: 1fr; }
.acc-panel > div { overflow: hidden; }
.acc-panel p:first-child { padding-top: 0; }
/* The global `p { max-width: 68ch }` was capping answers ~250px short of their
   column. The accordion column is already a controlled width, so release it. */
.acc-panel p, .acc-panel ul, .acc-panel li { font-size: var(--fs-sm); max-width: none; }
.acc-panel > div > *:last-child { margin-bottom: var(--sp-5); }

/* ---------- reviews ---------- */
.reviews-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-3) var(--sp-5); margin-bottom: var(--sp-6); }
.rating-badge { display: inline-flex; align-items: center; gap: .6rem; font-weight: 600; }
.stars { display: inline-flex; gap: 2px; }
.stars svg { width: 18px; height: 18px; color: var(--peach); }
.rating-badge__num { font-family: var(--font-display); font-size: 1.9rem; font-weight: 700; line-height: 1; letter-spacing: -.01em; }

.rev-viewport { overflow: hidden; }
/* Exactly N whole cards per view — never a sliced card at the edge.
   1 up to 640px, 2 to 1024px, 3 above. */
.rev-track {
  --rev-gap: var(--sp-5);
  --rev-per-view: 1;
  display: grid; grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--rev-per-view) - 1) * var(--rev-gap)) / var(--rev-per-view));
  gap: var(--rev-gap);
  transition: translate var(--dur-slow) var(--ease-in-out);
}
@media (min-width: 640px)  { .rev-track { --rev-per-view: 2; } }
@media (min-width: 1024px) { .rev-track { --rev-per-view: 3; } }
.rev-card {
  background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--radius);
  padding: var(--sp-5); box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column; gap: var(--sp-3);
}
.rev-card blockquote { margin: 0; font-size: var(--fs-sm); line-height: 1.68; }
.rev-card__author { margin-top: auto; font-weight: 600; font-size: var(--fs-sm); }
.rev-card__meta { font-size: var(--fs-xs); color: var(--slate); opacity: .78; }
/* Arrows left, "Read all reviews on Google" pushed to the right of the same row. */
.rev-controls {
  display: flex; gap: var(--sp-3); margin-top: var(--sp-5);
  align-items: center; flex-wrap: wrap;
}
.rev-controls__link { margin-left: auto; }
@media (max-width: 560px) {
  .rev-controls__link { margin-left: 0; width: 100%; }
  .rev-controls__link .btn { width: 100%; }
}
.rev-btn {
  width: 46px; height: 46px; border-radius: 50%;
  border: 2px solid var(--peach); background: transparent; cursor: pointer;
  display: grid; place-items: center; color: var(--slate);
  transition: background-color var(--dur-fast) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.rev-btn:active { transform: scale(.94); }
.rev-btn[disabled] { opacity: .38; cursor: default; }
@media (hover: hover) and (pointer: fine) { .rev-btn:not([disabled]):hover { background: var(--blush); } }
.rev-btn svg { width: 18px; height: 18px; }

/* ---------- CTA band ----------
   Blush, not Slate. The footer is Slate, so a Slate CTA band directly above it
   merged into one undifferentiated dark mass that no hairline could rescue.
   Blush also contrasts with the white/cream section that precedes it on every
   page. Slate text on Blush is 4.65:1 — passes AA. */
.cta-band { background: var(--blush); }
/* Text colour is inherited from body (Slate), and the buttons use the standard
   site-wide hover — deliberately NO per-section override here, so a secondary
   button behaves identically whether it sits in this band, in a card, or in
   the reviews controls. */
.cta-band__inner { display: grid; gap: var(--sp-6); align-items: center; }
@media (min-width: 860px) { .cta-band__inner { grid-template-columns: 1.4fr auto; } }

/* ---------- forms ---------- */
.form-card {
  background: var(--white); border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg); padding: clamp(1.4rem, 1rem + 1.5vw, 2.5rem);
  box-shadow: var(--shadow-md);
}
.field { margin-bottom: var(--sp-4); }
.field label { display: block; font-size: var(--fs-sm); font-weight: 600; margin-bottom: var(--sp-2); }
.req { color: var(--slate); }
.field input, .field select, .field textarea {
  width: 100%; padding: .8rem 1rem;
  background: var(--cream);
  border: 1.5px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: var(--fs-body);
  transition: border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
.field textarea { min-height: 8.5rem; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--slate); background: var(--white); outline-offset: 1px; }
.field-row { display: grid; gap: 0 var(--sp-4); }
@media (min-width: 620px) { .field-row { grid-template-columns: 1fr 1fr; } }
.field__error { display: none; font-size: var(--fs-xs); font-weight: 600; margin-top: var(--sp-2); }
.field[data-invalid="true"] input,
.field[data-invalid="true"] select,
.field[data-invalid="true"] textarea { border-color: #A5342A; }
.field[data-invalid="true"] .field__error { display: block; color: #A5342A; }
/* Honeypot — must remain reachable to bots but hidden from people and AT. */
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form-note { font-size: var(--fs-xs); margin-top: var(--sp-4); opacity: .85; }
.form-status { border-radius: var(--radius-sm); padding: var(--sp-4); margin-bottom: var(--sp-4); font-size: var(--fs-sm); display: none; }
.form-status[data-state="ok"] { display: block; background: var(--blush); border-left: 3px solid var(--peach); }
.form-status[data-state="error"] { display: block; background: #FBEAE7; border-left: 3px solid #A5342A; color: #7A2720; }

/* ---------- tables / packages ---------- */
/* Five packages: a plain auto-fit grid leaves the fifth card orphaned hard-left
   on its own row. Flex + centre wraps to 3 + 2 (or 2 + 2 + 1) with the short
   row centred, which reads as deliberate rather than broken. */
.pkg-grid {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: clamp(1.25rem, .8rem + 1.4vw, 2.25rem);
}
.pkg {
  background: var(--white); border: 1px solid var(--line-soft); border-radius: var(--radius-lg);
  padding: var(--sp-6) var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-3);
  box-shadow: var(--shadow-sm);
  flex: 0 1 100%;
}
@media (min-width: 640px)  { .pkg { flex-basis: calc(50% - 1.2rem); } }
@media (min-width: 1040px) { .pkg { flex-basis: calc(33.333% - 1.5rem); max-width: 26rem; } }
.pkg--feature { border-color: var(--peach); box-shadow: var(--shadow-md); }
.pkg__name { font-size: var(--fs-h3); margin: 0; }
.pkg__price { font-family: var(--font-display); font-size: 2.6rem; font-weight: 700; line-height: 1; letter-spacing: -.02em; margin: 0; }
.pkg__price small { font-family: var(--font-body); font-size: var(--fs-sm); font-weight: 500; letter-spacing: 0; display: block; margin-top: var(--sp-2); }
.pkg__gest { font-size: var(--fs-sm); font-weight: 600; margin: 0; }
.pkg__alt { font-size: var(--fs-sm); margin: 0; padding: var(--sp-2) var(--sp-3); background: var(--blush); border-radius: var(--radius-sm); }
.pkg__list { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--sp-2); }
.pkg__list li { display: flex; gap: .6rem; align-items: flex-start; margin: 0; font-size: var(--fs-sm); }
.pkg__list svg { width: 16px; height: 16px; color: var(--peach); flex: none; margin-top: .32rem; }
.pkg__foot { margin-top: auto; padding-top: var(--sp-4); }
.pkg__note { font-size: var(--fs-xs); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }

/* ---------- gallery ---------- */
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr)); gap: var(--sp-4); }
.gallery-grid figure { margin: 0; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-sm); background: var(--blush); }
.gallery-grid img { width: 100%; height: 100%; aspect-ratio: 1; object-fit: cover; }

/* ---------- curved WebGL gallery ----------
   Progressive enhancement over .gallery-grid. Until (and unless) the module in
   /assets/js/ubm-gallery.js mounts, this container IS the flat grid — real
   <img> with alt text, crawlable, and all anyone sees with JS off or reduced
   motion on. Once it mounts, [data-mode="gl"] swaps the grid for the canvas.
   Full-bleed on purpose: the arc only reads across the whole viewport. */
.circ-gallery { position: relative; }
.circ-gallery > .gallery-grid { padding-inline: clamp(1.15rem, .6rem + 1.6vw, 2rem); }
.circ-gallery__hint { display: none; }

.circ-gallery[data-mode="gl"] {
  height: clamp(24rem, 16rem + 22vw, 38rem);
  overflow: hidden;
  cursor: grab;
  /* getComputedStyle on this element supplies the label font + colour to the
     canvas text textures, so these are load-bearing, not decoration. Slate, not
     taupe — taupe on cream is too faint to read under the images. */
  color: var(--slate);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 26px;
  touch-action: pan-y;            /* vertical scroll stays with the page */
}
.circ-gallery[data-mode="gl"][data-dragging] { cursor: grabbing; }
.circ-gallery[data-mode="gl"] > .gallery-grid { display: none; }
.circ-gallery[data-mode="gl"] canvas { display: block; width: 100%; height: 100%; }
/* The hint is a SIBLING, not a child — the container is overflow:hidden at a
   fixed height once the canvas is in it, so anything inside would be clipped. */
.circ-gallery[data-mode="gl"] + .circ-gallery__hint {
  /* max-width:none is load-bearing: the global `p { max-width: 68ch }` would
     otherwise centre the text inside a 605px box pinned to the left edge, not
     inside the section. */
  display: block; text-align: center; max-width: none; margin: var(--sp-5) 0 0;
  font-size: var(--fs-xs); letter-spacing: var(--tr-eyebrow);
  text-transform: uppercase; font-weight: 700; color: var(--taupe);
}

/* ---------- info blocks ---------- */
.info-list { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--sp-4); }
.info-list li { display: flex; gap: var(--sp-3); margin: 0; align-items: flex-start; }
.info-list svg { width: 20px; height: 20px; color: var(--peach); flex: none; margin-top: .28rem; }
.info-list a { text-decoration: none; font-weight: 600; }
.info-list a:hover { text-decoration: underline; }
.info-list__label { display: block; font-size: var(--fs-xs); letter-spacing: var(--tr-eyebrow); text-transform: uppercase; font-weight: 700; margin-bottom: 2px; }

.hours { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.hours th, .hours td { text-align: left; padding: .6rem 0; border-bottom: 1px solid var(--line-soft); }
.hours th { font-weight: 600; }
.hours td { text-align: right; }

/* ---------- breadcrumbs ---------- */
/* ===== TEMPORARY: breadcrumbs hidden at the client's request, 2026-08-03 =====
   HIDDEN, NOT REMOVED. The markup still renders and the BreadcrumbList JSON-LD
   in includes/head.php is untouched, so search engines keep the hierarchy.
   To bring the bar back: delete this one rule. Nothing else needs changing. */
.crumbs { display: none; }
/* =========================================================================== */

/* Its own White band between the Cream header and the Cream hero. Previously it
   sat on the same Cream at 13px and half opacity, so it read as part of the
   header rather than as page context. */
.crumbs {
  background: var(--white);
  border-bottom: 1px solid var(--line-soft);
  padding-block: var(--sp-3);
}
.crumbs ol {
  list-style: none; display: flex; flex-wrap: wrap;
  gap: .5rem; padding: 0; margin: 0;
  font-size: var(--fs-sm);
}
.crumbs li { margin: 0; display: flex; align-items: center; gap: .5rem; }
.crumbs li + li::before { content: "/"; color: var(--peach); font-weight: 600; }
.crumbs a {
  text-decoration: none;
  background-image: linear-gradient(var(--peach), var(--peach));
  background-repeat: no-repeat; background-position: 0 100%; background-size: 0 1px;
  transition: background-size var(--dur-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .crumbs a:hover { background-size: 100% 1px; }
}
.crumbs [aria-current="page"] { font-weight: 600; }

/* ---------- footer ---------- */
/* Cream, not Slate. Slate is the palette's BODY TEXT colour and is never a
   surface. Cream is the palette's Background; a Peach top rule anchors the
   footer and the Blush CTA band above keeps the boundary obvious. */
.site-footer {
  background: var(--cream); color: var(--slate);
  border-top: 2px solid var(--peach);
  padding-block: var(--sp-9) 0;
}
/* Blush CTA band above, Cream footer below — the tone change carries the
   separation. Padding stays tightened so the break is not a chasm.
   NB: .cta-band sits inside <main>, so it is not a sibling of the footer. */
main > .cta-band:last-child { padding-bottom: var(--sp-8); }
main:has(> .cta-band:last-child) + .site-footer { padding-top: var(--sp-8); }
.site-footer h2, .site-footer h3 { color: var(--slate); }
.site-footer a { color: var(--slate); text-decoration: none; }
.site-footer a:hover { text-decoration: underline; }
.footer-grid { display: grid; gap: 45px; /*var(--sp-7);*/ }
@media (min-width: 780px) { .footer-grid { grid-template-columns: 1.4fr 0.51fr 1fr 1fr 1fr; } }
/* Matches the header mark. 44px rendered the sub-lockup's strapline
   illegibly small — this is sized to read as a real logo. */
.footer-brand__logo {
  display: inline-block;
  margin-bottom: var(--sp-5);
  border-radius: var(--radius-sm);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.footer-brand__logo:active { transform: scale(.97); }
@media (hover: hover) and (pointer: fine) {
  .footer-brand__logo:hover { opacity: .82; }
}
.footer-brand img { height: clamp(56px, 48px + 1.2vw, 72px); width: auto; }
.footer-brand p { font-size: 14px; /*var(--fs-sm); max-width: 24rem;*/ }
.footer-col h3 { font-family: var(--font-body); font-size: var(--fs-sm); font-weight: 700; letter-spacing: var(--tr-eyebrow); text-transform: uppercase; margin-bottom: var(--sp-4); }
.footer-col ul { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--sp-2); }
.footer-col li { margin: 0; }
.footer-col a { font-size: 14px; /*var(--fs-sm);*/ }
.footer-contact { display: grid; gap: var(--sp-3); font-size: 14px; /*var(--fs-sm);*/ }
.footer-contact div { display: flex; gap: .65rem; align-items: flex-start; }
.footer-contact svg { width: 17px; height: 17px; color: var(--peach); flex: none; margin-top: .3rem; }
.footer-social { display: flex; gap: var(--sp-3); margin-top: var(--sp-4); }
.footer-social a {
  width: 42px; height: 42px; border-radius: 50%; display: grid; place-items: center;
  border: 1.5px solid var(--peach);
  transition: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.footer-social a:active { transform: scale(.94); }
@media (hover: hover) and (pointer: fine) { .footer-social a:hover { background: var(--blush); border-color: var(--peach); } }
.footer-social svg { width: 19px; height: 19px; }

.footer-bottom {
  margin-top: var(--sp-8);
  border-top: 1px solid var(--line);
  padding-block: var(--sp-5);
  display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6);
  align-items: center; justify-content: space-between;
  font-size: var(--fs-xs);
}
.footer-bottom p { margin: 0; max-width: none; }
.footer-bottom nav { display: flex; flex-wrap: wrap; gap: var(--sp-4); }
.footer-credit { color: var(--slate); }

/* ---------- utilities ---------- */
.text-center { text-align: center; }
.mt-0 { margin-top: 0; } .mb-0 { margin-bottom: 0; }
.max-42 { max-width: 42rem; } .max-46 { max-width: 46rem; }
.mx-auto { margin-inline: auto; }
/* These boxes ALREADY set the measure. The global `p { max-width: 68ch }` then
   capped the text ~200px short of the box it sits in, so a 46rem centred column
   rendered as a ~540px ribbon pinned to its left edge — narrower than intended
   and visibly off-centre under a centred heading. Inside an explicitly measured
   box the paragraph fills it. */
.max-42 p, .max-46 p { max-width: none; }
/* Prose that leads a section reads across the whole row rather than sitting in
   a half-width column beside something else. The global 68ch cap is released
   here on purpose — the point of the block is to use the width. */
.prose-wide p { max-width: none; }
.prose-wide > *:last-child { margin-bottom: 0; }
.facts-row { margin-top: var(--sp-7); }
/* Tiles, used when the covers list has the row to itself — see _facts_row().
   auto-fit packs as many tracks as the width allows, so nine single words fill
   1440px instead of sitting as two sparse columns with a canyon between them. */
.fact-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
}
.fact-grid--wide { grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); }
.fact-tile {
  display: flex; gap: .6rem; align-items: flex-start; margin: 0;
  background: var(--white); border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm); padding: var(--sp-4);
  font-size: var(--fs-sm); line-height: 1.55;
  box-shadow: var(--shadow-sm);
}
.fact-tile svg { width: 17px; height: 17px; color: var(--peach); flex: none; margin-top: .28rem; }
@media (prefers-contrast: more) { .fact-tile { border-color: var(--slate); } }

/* ---------- accessibility preferences ---------- */
@media (prefers-reduced-motion: reduce) {
  /* Movement drops; opacity and colour transitions stay. */
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  /* Movement drops, the fade stays, and nothing is left hidden. */
  .hero-anim { animation: none !important; opacity: 1 !important; transform: none !important; }
  .hero-figure { animation: none !important; clip-path: none !important; }
  /* The blanket rule above only shortens animations; an infinite drift would
     still run one 0.01ms cycle and leave the photo parked at the end frame. */
  .hero-figure--drift img { animation: none !important; transform: none !important; }
  /* The hero compare slider needs no override here and must not get one: it is
     not decorative motion, it is the control. Reduced motion means the divider
     stops springing and tracks the pointer 1:1 — that is handled in ubm.js,
     which reads the same media query. Disabling anything in CSS would only
     break a working control for the people relying on it. */
  .js .reveal.is-in { transition: opacity 240ms linear !important; translate: none !important; }
  .reveal { translate: none; }
  .rev-track { transition: none; }
  .btn:active, .card--link:active, .rev-btn:active, .nav-toggle:active, .acc-trigger:active { transform: none; }
}
/* (prefers-reduced-transparency needs no header override any more — the header
   is solid Cream for everyone since the backdrop-filter was removed.) */
@media (prefers-contrast: more) {
  :root { --line: rgba(94,106,113,.5); --line-soft: rgba(94,106,113,.34); }
  .card, .icon-card, .pkg, .form-card, .rev-card, .qa-card { border-color: var(--slate); }
}

@media print {
  .site-header, .mobile-nav, .nav-scrim, .rev-controls, .btn { display: none !important; }
  body { background: #fff; }
}
