/* ============================================================
   Peninsula Aesthetic Medicine — site stylesheet (rewrite, v1.1 kit)
   Five tokens. Three type families. Shared by all pages.
   Replaces /styles.css when the full page set deploys.
   ============================================================ */

:root {
  --ivory:      #FAF9F6;  /* 70% — primary surface */
  --deep-slate: #2A333D;  /* 22% — ink, lines, reversed surfaces */
  --soft-gray:  #D8DCE0;  /* 6%  — dividers, disabled states */
  --ice-blue:   #B8CEDA;  /* 2%  — accent only, never for text */
  --muted:      #6B7784;  /* secondary text */

  /* For the treatment explainers: one token per chromophore those figures
     are actually about, plus a stage ground that reads as a media surface
     a half-step off the page. A figure earns a hue by having something to
     name with it -- every explainer draws what its laser cannot treat in
     the neutrals above, which is the whole reason the colored ones read. */
  --pigment:    #8C5A3C;  /* melanin */
  --vessel:     #A8474F;  /* oxyhemoglobin */
  --water:      #1A6B7A;  /* water -- what Er:YAG is tuned to */
  --heat:       #A8493C;  /* residual thermal damage, the quantity it trades against */
  --ink-red:    #B23A31;  /* red tattoo ink, one of two 532 nm clears */
  --ink-amber:  #C97F2A;  /* orange tattoo ink, the other */
  --stage:      #F2F0EA;  /* figure ground */

  --font-display: 'Cormorant Infant', 'Cormorant Garamond', Georgia, serif;
  --font-mono:    'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --font-body:    'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;

  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --page-max: 72rem;
  --measure: 38rem;

  --rule: 1px solid var(--soft-gray);
}

@media (prefers-reduced-motion: no-preference) {
  :root { scroll-behavior: smooth; }
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--ivory);
  color: var(--deep-slate);
}

body {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  border-top: 3px solid var(--ice-blue);
}

img, svg { display: block; max-width: 100%; height: auto; }

::selection { background: var(--ice-blue); color: var(--deep-slate); }

a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--deep-slate);
  outline-offset: 3px;
  border-radius: 1px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.skip-link:focus {
  left: var(--gutter);
  top: var(--gutter);
  width: auto;
  height: auto;
  padding: 0.5rem 0.75rem;
  background: var(--deep-slate);
  color: var(--ivory);
  z-index: 100;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
}

/* ============================================================
   Type
   ============================================================ */

.eyebrow,
.mono-label,
.mono-meta,
.mono-num {
  font-family: var(--font-mono);
  font-weight: 400;
  text-transform: uppercase;
}

.eyebrow {
  font-size: 0.6875rem;
  line-height: 1.6;
  letter-spacing: 0.16em;
  color: var(--muted);
  margin: 0 0 1.25rem;
}

.accent-dash {
  display: block;
  width: 28px;
  height: 2px;
  background: var(--ice-blue);
  margin: 0 0 1.375rem;
}

.display-1 {
  font-family: var(--font-display);
  font-weight: 300;
  letter-spacing: -0.01em;
  line-height: 1.05;
  font-size: clamp(2.75rem, 5.5vw, 4.25rem);
  margin: 0;
  text-wrap: balance;
  /* "Hyperpigmentation." is one unbreakable 18-character word; at 320px it
     pushed the page into horizontal scroll. Only affects words that cannot
     otherwise fit, so ordinary headings are untouched. */
  overflow-wrap: break-word;
}

.display-3 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: -0.005em;
  line-height: 1.12;
  font-size: clamp(1.75rem, 3.5vw, 2.75rem);
  margin: 0 0 1.5rem;
  text-wrap: balance;
}

.h2-serif {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.15;
  font-size: clamp(1.625rem, 2.6vw, 2.125rem);
  margin: 0;
}

.h3 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.25;
  font-size: 1.375rem;
  margin: 0 0 0.5rem;
}

.body-1 {
  font-size: 1.0625rem;
  line-height: 1.65;
  margin: 0 0 1rem;
  max-width: var(--measure);
}

.body-2 {
  font-size: 0.9375rem;
  line-height: 1.65;
  margin: 0;
  color: var(--muted);
  max-width: var(--measure);
}

.mono-label {
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  color: var(--muted);
}

.mono-meta {
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  color: var(--muted);
  margin: 0;
  line-height: 1.9;
}

.mono-num {
  font-size: 0.8125rem;
  letter-spacing: 0.1em;
  color: var(--muted);
}

.text-link {
  color: var(--deep-slate);
  text-decoration: none;
  border-bottom: 1px solid var(--soft-gray);
  transition: border-color 160ms ease;
}
.text-link:hover { border-bottom-color: var(--deep-slate); }

.rule {
  border: none;
  border-top: var(--rule);
  margin: 0;
}

/* ============================================================
   Header / nav
   ============================================================ */

.site-header {
  border-bottom: var(--rule);
}

.site-header__inner {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 1.125rem var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.lockup {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: var(--deep-slate);
  flex-shrink: 0;
}
.lockup svg { height: 56px; width: auto; }

.site-nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.site-nav a,
.nav-drop__btn {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: color 160ms ease;
}
.site-nav a:hover,
.nav-drop__btn:hover { color: var(--deep-slate); }

.site-nav a[aria-current="page"] { color: var(--deep-slate); }

.nav-drop { position: relative; }

.nav-drop__btn::after {
  content: " ▾";
  font-size: 0.625rem;
}

.nav-drop__menu {
  position: absolute;
  top: calc(100% + 0.875rem);
  left: -1.25rem;
  background: var(--ivory);
  border: var(--rule);
  padding: 0.75rem 0;
  min-width: 14rem;
  display: none;
  z-index: 50;
}
.nav-drop__menu[data-open="true"] { display: block; }

.nav-drop__menu a {
  display: block;
  padding: 0.5rem 1.25rem;
}
.nav-drop__menu a:hover { background: rgba(216, 220, 224, 0.25); }

.nav-drop__sep {
  border: none;
  border-top: var(--rule);
  margin: 0.5rem 0;
}

/* Two-column dropdown panel, used by Treatments and by The Science — each runs
   past twenty links, and one column of that runs off the bottom of a laptop
   viewport. Collapses to a single column inside the mobile nav (see the
   max-width: 1080px block below). */
.nav-drop__menu--wide {
  min-width: 34rem;
  grid-template-columns: 1fr 1fr;
  column-gap: 0.5rem;
  padding-bottom: 1rem;
}
.nav-drop__menu--wide[data-open="true"] { display: grid; }

/* Group label inside a dropdown panel. Not a link and not focusable, so it is
   skipped by Tab; the panels stay a flat list of links for keyboard users. */
.nav-drop__head {
  margin: 0.875rem 0 0.125rem;
  padding: 0 1.25rem;
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.nav-drop__col > .nav-drop__head:first-child { margin-top: 0; }

.btn-primary {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  background: var(--deep-slate);
  color: var(--ivory);
  border: 1px solid var(--deep-slate);
  padding: 0.8125rem 1.375rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background 180ms ease, color 180ms ease;
}
.btn-primary:hover { background: var(--ivory); color: var(--deep-slate); }

/* Outline counterpart to .btn-primary, for pages where two calls to action sit
   side by side and only one may carry the filled weight. Kept as a design-system
   primitive: no page uses it as of 2026-08-18, when the header's second button
   (the founding-list link) was removed and Book Now became the sole CTA. */
.btn-secondary {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  background: none;
  color: var(--deep-slate);
  border: 1px solid var(--soft-gray);
  padding: 0.8125rem 1.375rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background 180ms ease, border-color 180ms ease, color 180ms ease;
}
.btn-secondary:hover {
  background: rgba(216, 220, 224, 0.25);
  border-color: var(--deep-slate);
}

/* `.site-nav a` (0,1,1) outranks `.btn-primary` (0,1,0), so it flattens its
   box: background, border and padding all reset. Re-apply them here at (0,2,0)
   so the header button reads as a button. */
.site-nav .btn-primary {
  padding: 0.8125rem 1.375rem;
  font-weight: 500;
  background: var(--deep-slate);
  border: 1px solid var(--deep-slate);
  color: var(--ivory);
}
.site-nav .btn-primary:hover { background: var(--ivory); color: var(--deep-slate); }

/* Standing $450 promotion in the header. Outlined, not filled, so Book Now stays
   the single filled CTA and the two don't compete. Same specificity workaround as
   the rule above: `.site-nav a` is (0,1,1) and would otherwise strip the border
   and padding off a plain class selector. */
.site-nav .nav-offer {
  padding: 0.6875rem 1rem;
  border: 1px solid var(--soft-gray);
  color: var(--deep-slate);
  white-space: nowrap;
}
.site-nav .nav-offer:hover {
  border-color: var(--deep-slate);
  background: rgba(216, 220, 224, 0.25);
}
.site-nav .nav-offer[aria-current="page"] { border-color: var(--deep-slate); }

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--soft-gray);
  padding: 0.625rem 0.875rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--deep-slate);
  cursor: pointer;
}

/* The row is seven items wide (three dropdowns + two links + the $450 offer +
   the CTA); below this width the labels start wrapping, so hand over to the
   toggle instead. What We Don't Offer moved into The Science panel when the
   offer link was added, so the row is no wider than it was at this breakpoint. */
@media (max-width: 1080px) {
  .nav-toggle { display: inline-block; }
  .site-nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--ivory);
    border-bottom: var(--rule);
    flex-direction: column;
    align-items: flex-start;
    padding: 1.25rem var(--gutter) 1.75rem;
    gap: 1.125rem;
    z-index: 60;
  }
  .site-nav[data-open="true"] { display: flex; }
  /* The Treatments panel is eighteen links long; opened inside the mobile nav it
     would push Book Now past the bottom of a phone screen. Cap the panel at the
     viewport and let it scroll internally so the CTA stays reachable. */
  .site-nav {
    max-height: calc(100vh - 6rem);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .site-header { position: relative; }
  .nav-drop__menu {
    position: static;
    border: none;
    border-left: var(--rule);
    padding: 0.25rem 0 0.25rem 0.25rem;
    margin-top: 0.625rem;
    min-width: 0;
  }
  .nav-drop__menu--wide { min-width: 0; padding-bottom: 0.25rem; }
  .nav-drop__menu--wide[data-open="true"] { display: block; }
  .nav-drop__col + .nav-drop__col > .nav-drop__head:first-child { margin-top: 0.875rem; }
}

/* ============================================================
   Page scaffolding
   ============================================================ */

main {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.page-head {
  padding: clamp(2.5rem, 7vw, 5rem) 0 clamp(2rem, 5vw, 3.5rem);
}

.page-head__lede {
  font-size: clamp(1rem, 1.4vw, 1.125rem);
  line-height: 1.65;
  color: var(--muted);
  max-width: var(--measure);
  margin: 1.5rem 0 0;
}
.page-head__lede + .body-1 { margin-top: 1.25rem; }

.breadcrumb {
  padding: 1.25rem 0 0;
}
.breadcrumb ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.breadcrumb li:not(:last-child)::after { content: "·"; margin-left: 0.5rem; }
.breadcrumb a { color: inherit; text-decoration: none; }
.breadcrumb a:hover { color: var(--deep-slate); }
.breadcrumb [aria-current="page"] { color: var(--deep-slate); }

/* ============================================================
   CTA band
   ============================================================ */

.cta-band {
  background: var(--ice-blue);
  padding: clamp(2.5rem, 6vw, 4rem) clamp(1.5rem, 4vw, 3rem);
  margin: clamp(2.5rem, 6vw, 4rem) 0;
}

.cta-band .eyebrow { color: var(--deep-slate); opacity: 0.75; }

.cta-band__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.625rem, 3vw, 2.375rem);
  line-height: 1.15;
  margin: 0 0 1rem;
  max-width: 44rem;
}

.cta-band__body {
  font-size: 1rem;
  line-height: 1.65;
  margin: 0 0 1.75rem;
  max-width: var(--measure);
}

/* ============================================================
   Home
   ============================================================ */

.hero {
  padding: clamp(3rem, 9vw, 6.5rem) 0 clamp(2.5rem, 6vw, 4rem);
  max-width: 56rem;
}

.hero-lede {
  font-size: clamp(1.0625rem, 1.6vw, 1.25rem);
  line-height: 1.55;
  color: var(--muted);
  max-width: 38rem;
  margin: 1.75rem 0 2.25rem;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem 1.75rem;
  margin: 0;
}
.hero-actions__secondary {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  border-bottom: none;
}
.hero-actions__secondary:hover { border-bottom: none; color: var(--deep-slate); }

.home-approach,
.home-visit,
.home-honest,
.home-lead {
  padding: clamp(3rem, 6vw, 5rem) 0;
  max-width: 50rem;
}

/* Patient reviews. The heading and the "Read our reviews on Google" link are
   the whole section as served; everything under .home-reviews__panel is drawn
   by /assets/reviews.js from /api/reviews and is absent whenever that endpoint
   is unconfigured, unreachable, or has nothing to show. These rules therefore
   have to look deliberate when they apply to nothing at all — which is why the
   panel carries no margin of its own and no reserved height. (This block
   previously styled a cross-origin Pabau widget, removed 2026-08-29.) */
.home-reviews { padding: clamp(3rem, 6vw, 5rem) 0; }
.home-reviews .display-3 { margin-bottom: 2.5rem; }
.home-reviews__link { margin: -1.5rem 0 2.5rem; }

/* No `display` on the panel itself: a display value here would beat the UA's
   [hidden] rule and the empty container would take up space before the script
   has anything to put in it. Layout is the children's job. */
.home-reviews__panel[hidden] { display: none; }

/* Read aloud, never seen. Pairs with the aria-hidden star glyphs, which are
   decoration and would otherwise be announced character by character. */
.reviews-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* "★★★★★ 4.9 from 37 reviews on Google" — a measurement, set like one. */
.reviews-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.625rem;
  margin: 0 0 1.75rem;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.reviews-summary__stars {
  letter-spacing: 0.12em;
  color: var(--deep-slate);
  /* The glyphs come from whichever fallback face has them; the mono display
     face is not guaranteed to. Keeping them off the mono stack avoids a
     ragged mix of substituted and native characters. */
  font-family: var(--font-body);
  font-size: 0.875rem;
}
.reviews-summary__value {
  color: var(--deep-slate);
  font-size: 0.9375rem;
}
.reviews-summary__detail { letter-spacing: 0.04em; }

/* Same rules-not-boxes treatment as .cat-grid, so the reviews read as part of
   the page rather than as a widget dropped into it. */
.reviews-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  border-top: var(--rule);
}

.review {
  padding: 1.75rem 0;
  border-bottom: var(--rule);
  /* Long unbroken strings — a pasted URL in a review body, a very long display
     name — are the one thing here that can force a horizontal scrollbar at
     320px. This bounds the grid track regardless of content. */
  min-width: 0;
}

.review__head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.875rem;
}

/* The author's initial, drawn from text this page already has. Standing in for
   a profile photo on purpose — see the note at the top of assets/reviews.js. */
.review__initial {
  flex: none;
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 50%;
  background: var(--ice-blue);
  color: var(--deep-slate);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 2.125rem;
  text-align: center;
  letter-spacing: 0;
}

.review__who { min-width: 0; }

.review__author {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.review__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 0.5rem;
  margin: 0.125rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  color: var(--muted);
}
.review__stars {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  letter-spacing: 0.1em;
  color: var(--deep-slate);
}

.review__quote {
  margin: 0;
  quotes: none;
}

/* Capped by line, not by character count, so the cut lands on a whole line and
   the browser draws its own ellipsis. /api/reviews also clips the string, but
   that cap is about payload size; this one is about the layout. Anyone who
   wants the rest follows the author link to Google. */
.review__text {
  margin: 0;
  font-size: 1rem;
  line-height: 1.65;
  color: var(--deep-slate);
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 7;
  line-clamp: 7;
  overflow: hidden;
}

.reviews-note {
  margin: 1.5rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  line-height: 1.8;
  letter-spacing: 0.08em;
  color: var(--muted);
  max-width: var(--measure);
}

@media (min-width: 720px) {
  .reviews-grid { grid-template-columns: 1fr 1fr; gap: 0 3rem; }
}


.home-treatments { padding: clamp(3rem, 6vw, 5rem) 0; }
.home-treatments__head { margin-bottom: 2.5rem; }
.home-treatments__all { margin: 2rem 0 0; }

.cat-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  border-top: var(--rule);
}
.cat-card {
  padding: 1.75rem 0;
  border-bottom: var(--rule);
}
.cat-card .h3 { margin-bottom: 0.5rem; }
.cat-card__link {
  text-decoration: none;
  color: var(--deep-slate);
  border-bottom: 1px solid transparent;
  transition: border-color 160ms ease;
}
.cat-card__link:hover { border-bottom-color: var(--ice-blue); }

@media (min-width: 720px) {
  .cat-grid { grid-template-columns: 1fr 1fr; gap: 0 3rem; }
}

/* ============================================================
   Enquiry form (founding-patients → Pabau Create Lead API)
   ============================================================ */

.form {
  margin-top: 2.5rem;
  max-width: 36rem;
  display: grid;
  gap: 1.5rem;
}

.form-row { display: grid; gap: 0.5rem; }

/* Two fields side by side on anything wider than a phone. */
.form-pair { display: grid; gap: 1.5rem; }
@media (min-width: 560px) {
  .form-pair { grid-template-columns: 1fr 1fr; gap: 1.5rem 2rem; }
}

.form-row input,
.form-row textarea {
  appearance: none;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(42, 51, 61, 0.3);
  border-radius: 0;
  padding: 0.625rem 0;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--deep-slate);
  transition: border-color 160ms ease;
  width: 100%;
}
.form-row textarea { resize: vertical; min-height: 5.5rem; }
.form-row input::placeholder,
.form-row textarea::placeholder { color: var(--muted); opacity: 0.7; }
.form-row input:hover,
.form-row textarea:hover { border-bottom-color: var(--muted); }
.form-row input:focus-visible,
.form-row textarea:focus-visible {
  outline: none;
  border-bottom-color: var(--deep-slate);
  border-bottom-width: 2px;
  padding-bottom: calc(0.625rem - 1px);
}
.form-row input[aria-invalid="true"],
.form-row textarea[aria-invalid="true"] {
  border-bottom-color: var(--deep-slate);
  border-bottom-width: 2px;
  padding-bottom: calc(0.625rem - 1px);
}

.optional { text-transform: none; letter-spacing: 0; font-size: 0.75rem; color: var(--muted); }

/* A2P 10DLC SMS consent control (contact page).

   Deliberately NOT wrapped in `.form-row`: the `.form-row input` rule above sets
   `appearance: none`, strips the border and forces `width: 100%`, which renders a
   checkbox as an invisible full-width underlined bar. Keeping the consent control
   outside `.form-row` sidesteps that without editing a shared rule that other
   forms (and other pages) depend on. If you ever move this markup into a
   `.form-row`, scope that rule to `input:not([type="checkbox"])` first.

   The label wraps the box so the whole disclosure is a click target; the two
   links inside it still navigate rather than toggle, per the HTML spec's
   interactive-content exception for label activation. */
.form-consent {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  cursor: pointer;
}

.form-consent__box {
  flex: 0 0 auto;
  width: 1rem;
  height: 1rem;
  margin: 0.1875rem 0 0;  /* optically centres on the first line of the disclosure */
  accent-color: var(--deep-slate);
  cursor: pointer;
}

/* ~70 words of legal copy: it has to sit as a wrapping block beside the box,
   not as a vertically centred single line. Colour is inherited ink, not muted —
   a required disclosure has to stay conspicuous. */
.form-consent__text { font-size: 0.875rem; line-height: 1.6; }

.honeypot { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }

.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem 1.5rem;
  margin-top: 0.5rem;
}

.btn-arrow { transition: transform 180ms ease; }
.btn-primary:hover .btn-arrow { transform: translateX(3px); }
.btn-primary[aria-busy="true"] { opacity: 0.7; cursor: wait; }
.btn-primary[disabled] { cursor: not-allowed; opacity: 0.5; }

.form-footnote {
  font-size: 0.8125rem;
  color: var(--muted);
  margin: 0;
  max-width: 24rem;
  line-height: 1.5;
}

.form-status {
  font-size: 0.9375rem;
  line-height: 1.5;
  margin: 0;
  min-height: 1.5em;
}
.form-status[data-state="error"] { color: var(--deep-slate); }
.form-status[data-state="error"]::before {
  content: "";
  display: inline-block;
  width: 10px;
  height: 1px;
  background: var(--deep-slate);
  vertical-align: middle;
  margin-right: 0.5rem;
  transform: translateY(-2px);
}

.form[data-state="success"] .form-row,
.form[data-state="success"] .form-pair,
.form[data-state="success"] .form-actions { display: none; }
.form[data-state="success"] .form-status {
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1.3;
  color: var(--deep-slate);
  max-width: 32rem;
}

.offer-list {
  list-style: none;
  margin: clamp(2.5rem, 5vw, 4rem) 0 0;
  padding: 0;
  border-top: var(--rule);
  max-width: 42rem;
}
.offer-item {
  display: flex;
  gap: 1.25rem;
  padding: 1.5rem 0;
  border-bottom: var(--rule);
}
.offer-item .mono-num { flex-shrink: 0; line-height: 1.5; }
.offer-item .h3 { margin-bottom: 0.375rem; }


/* Prose (contact, privacy, simple text pages) */
.prose { max-width: 42rem; padding-bottom: clamp(2.5rem, 5vw, 4rem); }
.prose .h2-serif { margin: clamp(2rem, 4vw, 3rem) 0 0.75rem; }
.prose .h2-serif:first-child { margin-top: 0; }
.prose p { margin: 0 0 1rem; }
.prose ul { margin: 0 0 1.25rem; padding-left: 1.25rem; }
.prose li { margin-bottom: 0.5rem; line-height: 1.6; }
.prose .wdo-block:last-child { margin-bottom: 0; }

/* ============================================================
   Treatments grid (hub + category pages)
   ============================================================ */

.treatments-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2.5rem, 5vw, 3.5rem) 4rem;
  border-top: var(--rule);
  padding-top: clamp(2rem, 4vw, 3rem);
}
@media (min-width: 760px) {
  .treatments-grid { grid-template-columns: 1fr 1fr; }
}

.group__label {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.625rem;
  text-decoration: none;
}
a.group__label:hover { color: var(--deep-slate); }

.group__rule {
  height: 1px;
  background: var(--soft-gray);
  margin-bottom: 1.5rem;
}

.service {
  padding-bottom: 1.25rem;
  margin-bottom: 1.25rem;
  border-bottom: var(--rule);
}
.service:last-child { border-bottom: none; padding-bottom: 0; margin-bottom: 0; }

.service__name {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 1.4375rem;
  font-weight: 400;
  line-height: 1.15;
  margin-bottom: 0.375rem;
  color: var(--deep-slate);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 160ms ease;
}
a.service__name:hover { border-bottom-color: var(--ice-blue); }

.service__desc {
  font-size: 0.875rem;
  line-height: 1.65;
  color: var(--muted);
  margin: 0;
}

.treatments-note {
  margin-top: clamp(3rem, 5vw, 4rem);
  padding-top: 1.5rem;
  border-top: var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}
.treatments-note .mono-label { flex-shrink: 0; }
.treatments-note p {
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--muted);
  margin: 0;
  max-width: 48rem;
}

/* ============================================================
   Treatment detail pages
   ============================================================ */

.bullets { margin: 0.75rem 0 0; padding-left: 1.1rem; max-width: var(--measure); }
.bullets li { font-size: 0.9375rem; line-height: 1.6; color: var(--muted); margin-bottom: 0.4rem; }

.svc-summary {
  display: inline-block;
  margin-top: 0.75rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--deep-slate);
  text-decoration: none;
  border-bottom: 1px solid var(--ice-blue);
  padding-bottom: 2px;
}
.svc-summary:hover { border-bottom-color: var(--deep-slate); }

.faq {
  border-top: var(--rule);
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding-top: clamp(2rem, 4vw, 3rem);
}
.faq__item {
  padding: 1.25rem 0;
  border-bottom: var(--rule);
  max-width: var(--measure);
}
.faq__q {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.25rem;
  line-height: 1.25;
  margin: 0 0 0.5rem;
  color: var(--deep-slate);
}
.faq__a { margin: 0; font-size: 0.9375rem; line-height: 1.65; color: var(--muted); max-width: var(--measure); }

.cross-links {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem 2.5rem;
  margin-top: clamp(2rem, 4vw, 3rem);
  padding-top: clamp(1.5rem, 3vw, 2rem);
  border-top: var(--rule);
}
.cross-links a {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
}
.cross-links a:hover { color: var(--deep-slate); }

/* ============================================================
   Philosophy pages (comparison table, pull-quote, disclosure)
   ============================================================ */

.cmp-table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.75rem 0;
  font-size: 0.9375rem;
  max-width: 44rem;
}
.cmp-table th,
.cmp-table td {
  text-align: left;
  padding: 0.75rem 1rem 0.75rem 0;
  border-bottom: var(--rule);
  vertical-align: top;
}
.cmp-table thead th {
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.cmp-table tbody th {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.0625rem;
  color: var(--deep-slate);
}
.cmp-table td.price { font-family: var(--font-mono); white-space: nowrap; }
.cmp-table__caption { margin-bottom: 0.5rem; }

.pullquote {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 3vw, 2.25rem);
  line-height: 1.2;
  color: var(--deep-slate);
  border-left: 2px solid var(--ice-blue);
  padding-left: 1.5rem;
  margin: 2rem 0;
  max-width: 40rem;
}

.disclosure-note {
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--muted);
  border-top: var(--rule);
  margin-top: clamp(2rem, 4vw, 3rem);
  padding-top: 1.5rem;
  max-width: var(--measure);
}

.disclaimer-banner {
  margin: 1.25rem 0 0;
  padding: 0.875rem 1.125rem;
  border: 1px solid var(--soft-gray);
  border-left: 2px solid var(--ice-blue);
  background: rgba(184, 206, 218, 0.10);
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--muted);
  max-width: var(--measure);
}
.disclaimer-banner strong,
.disclaimer-banner a { color: var(--deep-slate); }

.disclaimer-block {
  border-top: var(--rule);
  margin-top: clamp(2rem, 4vw, 3rem);
  padding-top: 1.5rem;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--muted);
  max-width: var(--measure);
}
.disclaimer-block strong { color: var(--deep-slate); }

.trademark-note {
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--muted);
  margin: 1.25rem 0 0;
  max-width: var(--measure);
}

/* ============================================================
   "What we don't offer" page
   ============================================================ */

.wdo-disclosure {
  border-left: 2px solid var(--ice-blue);
  padding: 0.5rem 0 0.5rem 1.25rem;
  margin: 0 0 clamp(2rem, 4vw, 3rem);
  max-width: var(--measure);
}

.wdo-toc {
  margin: 0 0 clamp(2.5rem, 5vw, 4rem);
  padding: 1.5rem 0;
  border-top: var(--rule);
  border-bottom: var(--rule);
}
.wdo-toc .mono-label { display: block; margin: 0 0 1rem; }
.wdo-toc ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem 2rem;
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .wdo-toc ol { grid-template-columns: 1fr 1fr; }
}
.wdo-toc a {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  text-decoration: none;
  color: var(--deep-slate);
  font-size: 0.9375rem;
  padding: 0.25rem 0;
  border-bottom: 1px solid transparent;
}
.wdo-toc a:hover { border-bottom-color: var(--soft-gray); }
.wdo-toc .mono-num { flex-shrink: 0; }

.wdo-section {
  padding: clamp(2rem, 4vw, 3rem) 0;
  border-top: var(--rule);
  scroll-margin-top: 2rem;
}

.wdo-section--callout {
  border-left: 2px solid var(--ice-blue);
  padding-left: clamp(1.25rem, 3vw, 2rem);
}

/* First section under a disclosure block, where the section rule would double
   up with the block's own bottom border. Replaces an inline style="border-top:none"
   that the CSP's style-src blocks. */
.wdo-section--flush { border-top: none; }

.wdo-marker {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.wdo-marker .mono-num { flex-shrink: 0; }

.wdo-block { margin-bottom: 1.5rem; }
.wdo-block:last-of-type { margin-bottom: 0; }
.wdo-block .mono-label { display: block; margin-bottom: 0.5rem; }

.wdo-refs {
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--muted);
  margin: 1.5rem 0 0;
  max-width: var(--measure);
}

.wdo-refs--list {
  list-style: none;
  padding: 0;
}
.wdo-refs--list li { margin: 0 0 0.875rem; }
.wdo-refs--list li:last-child { margin-bottom: 0; }

.wdo-menu {
  padding: clamp(2rem, 4vw, 3rem) 0 0;
  border-top: var(--rule);
}
.wdo-menu .h3 { margin-bottom: 1rem; }

.wdo-updated {
  font-size: 0.75rem;
  color: var(--muted);
  margin: clamp(2rem, 4vw, 3rem) 0 0;
  font-style: italic;
}

/* ============================================================
   Footer
   ============================================================ */

.site-footer {
  border-top: var(--rule);
  margin-top: clamp(2rem, 5vw, 4rem);
}

.site-footer__inner {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: clamp(2.5rem, 5vw, 3.5rem) var(--gutter) 2rem;
  display: grid;
  gap: 2.5rem;
  grid-template-columns: 1fr;
}

@media (min-width: 760px) {
  .site-footer__inner { grid-template-columns: auto 1fr 1fr 1fr; gap: 3.5rem; }
}

.site-footer__mark { width: 108px; color: var(--deep-slate); }

.footer-col h2 {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 1rem;
}

.footer-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.footer-col li { margin-bottom: 0.625rem; }
.footer-col a {
  font-size: 0.875rem;
  color: var(--deep-slate);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 160ms ease;
}
.footer-col a:hover { border-bottom-color: var(--soft-gray); }

.footer-nap {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 2;
}

.site-footer__fine {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 1.25rem var(--gutter) 1.75rem;
  border-top: var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  font-size: 0.75rem;
  color: var(--muted);
}
.site-footer__fine a { color: inherit; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Confirmation for the no-JS form POST. /api/contact redirects to
   /contact?submitted=1#request-submitted; :target reveals the block without a
   line of JavaScript, so it works with scripting disabled. Hidden otherwise so
   the page reads normally on arrival. */
.form-confirmation {
  display: none;
  margin: 0 0 1.5rem;
  padding: 0.875rem 1.125rem;
  border-left: 2px solid var(--deep-slate);
  background: rgba(184, 206, 218, 0.18);
  font-size: 0.9375rem;
  line-height: 1.6;
  max-width: 36rem;
}
.form-confirmation:target { display: block; }

/* ============================================================
   Before-and-after gallery (/before-and-after)

   Every item is ONE manufacturer-supplied slide: the before and
   after frames sit side by side inside a single JPEG, with the
   Rohrer Aesthetics logo and any physician credit baked into the
   pixels. Nothing below crops, overlays, masks or letterboxes the
   image — those credits are the evidence that the people shown
   are not this practice's patients, so they have to stay legible.

   Click-to-enlarge is a plain <a href> to the JPEG itself. The
   site ships script-src 'self' and these pages carry no
   page-level JavaScript, so a lightbox would need script that
   isn't there; a link to the file is keyboard-operable, survives
   JS being off, and hands the reader the browser's own image
   viewer to pinch-zoom the fine detail (single lentigines, leg
   telangiectasia) that these slides turn on.
   ============================================================ */

/* Top-of-page disclosure. Deliberately the loudest block on the
   page: filled accent panel, display-size heading, body copy in
   full-strength ink rather than --muted. A disclosure a reader can
   skim past is not a disclosure. */
.ba-disclosure {
  background: var(--ice-blue);
  border-left: 3px solid var(--deep-slate);
  padding: clamp(1.5rem, 4vw, 2.25rem) clamp(1.25rem, 3vw, 2rem);
  margin: 0 0 clamp(2.5rem, 5vw, 3.5rem);
}
.ba-disclosure .mono-label {
  display: block;
  color: var(--deep-slate);
  margin: 0 0 0.75rem;
}
.ba-disclosure__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 3vw, 2.125rem);
  line-height: 1.15;
  color: var(--deep-slate);
  margin: 0 0 1rem;
}
.ba-disclosure p {
  font-size: 1rem;
  line-height: 1.65;
  color: var(--deep-slate);
  margin: 0 0 0.875rem;
  max-width: 44rem;
}
.ba-disclosure p:last-child { margin-bottom: 0; }
/* --soft-gray underlines vanish against the accent panel. */
.ba-disclosure .text-link { border-bottom-color: var(--deep-slate); }

/* One modality per group: heading, a sentence of context, the grid. */
.ba-group {
  border-top: var(--rule);
  padding-top: clamp(2rem, 4vw, 3rem);
  margin: 0 0 clamp(2.5rem, 5vw, 3.5rem);
  scroll-margin-top: 2rem;
}
.ba-group__head { margin: 0 0 clamp(1.5rem, 3vw, 2rem); }
.ba-group__head .h2-serif { margin-bottom: 0.75rem; }
.ba-group__intro {
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--muted);
  margin: 0;
  max-width: 44rem;
}

.ba-grid {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 4vw, 2.75rem);
  margin: 0;
  padding: 0;
}
@media (min-width: 760px) {
  .ba-grid { grid-template-columns: 1fr 1fr; }
}

.ba-item { margin: 0; }

/* The border does the work the slides don't: most have a white
   ground that would otherwise bleed into the ivory page. */
.ba-item__link {
  display: block;
  text-decoration: none;
  color: inherit;
  border: 1px solid var(--soft-gray);
  transition: border-color 160ms ease;
}
.ba-item__link:hover { border-color: var(--deep-slate); }
.ba-item__link img { width: 100%; }

.ba-item__zoom {
  display: block;
  border-top: 1px solid var(--soft-gray);
  padding: 0.5rem 0.75rem;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 160ms ease;
}
.ba-item__link:hover .ba-item__zoom { color: var(--deep-slate); }

.ba-item__caption {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--deep-slate);
  margin: 0.875rem 0 0;
}

/* Attribution rides with every image, always rendered, never
   behind a hover or a disclosure toggle. */
.ba-item__attrib {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  line-height: 1.7;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0.5rem 0 0;
}

/* ============================================================
   Paired-image variant of the gallery (.ba-pair)

   The Rohrer slides above are ONE file containing both frames,
   with the credits printed into the pixels. The Dermapen set is
   different: two separate 1080x720 JPEGs per case, and no logo
   or credit anywhere in the image data. Everything that tells a
   reader whose photographs these are therefore has to come from
   the markup, which is why .ba-pair keeps the shared
   .ba-item__caption and .ba-item__attrib below the pair rather
   than doubling them per frame — one case, one caption, one
   attribution line, always rendered.

   The two frames stay side by side at every width, down to
   320px. Reasons, in order:

   1. On /before-and-after these pairs sit under the Rohrer
      slides, which are single images and physically cannot
      stack. Stacking the Dermapen pairs at narrow widths would
      put two contradictory reading patterns on one page at one
      width — worse for comprehension than small photographs.
   2. At 320px each frame lands around 130px wide, which is the
      same per-photograph width a reader already gets from each
      half of a Rohrer slide at that width. No new legibility
      floor is introduced.
   3. A before/after comparison read by scrolling between two
      images is a materially weaker comparison than one read by
      eye movement. For photographs whose whole job is honest
      comparison, adjacency is worth more than pixel size.
   4. Every frame is a plain link to its own 1080x720 original,
      so the fine detail is one tap away. No script involved —
      the site ships script-src 'self' and these pages carry no
      page-level JavaScript.

   The Before / After label is real text inside each link, above
   its own image and inside its own border, so it survives
   screen readers, stylesheet failure and reading order. It is
   never generated with CSS content.
   ============================================================ */

.ba-pair__frames {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(0.375rem, 1.5vw, 0.75rem);
  align-items: stretch;
}

/* Equal-height frames: if one label bar wraps at a narrow width
   the other frame matches it, so the two images stay level. */
.ba-pair .ba-item__link {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.ba-pair .ba-item__zoom { margin-top: auto; }

/* The state label. Full ink rather than --muted: this is the
   label, not the metadata. It sits above its own image, inside
   the same border, so which photograph it names cannot be read
   two ways. */
.ba-pair__state {
  display: block;
  border-bottom: 1px solid var(--soft-gray);
  padding: 0.4375rem 0.625rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  line-height: 1.3;
  text-transform: uppercase;
  color: var(--deep-slate);
}

/* Shared caption and attribution: one per case, not per frame. */
.ba-pair > figcaption { margin: 0; }

/* Compact form of the top-of-page disclosure, for treatment
   pages that carry a few pairs rather than the whole gallery.
   Same panel, same weight of language, smaller type — it is a
   modifier on .ba-disclosure, not a second disclosure style. */
.ba-disclosure--compact {
  padding: clamp(1.125rem, 3vw, 1.5rem) clamp(1rem, 2.5vw, 1.375rem);
  margin: 0 0 clamp(1.75rem, 3vw, 2.25rem);
}
.ba-disclosure--compact .ba-disclosure__title {
  font-size: clamp(1.25rem, 2.2vw, 1.5rem);
  margin: 0 0 0.75rem;
}
.ba-disclosure--compact p {
  font-size: 0.9375rem;
  margin: 0 0 0.75rem;
}

/* Manufacturer video. Same border and same caption/attribution
   pair as the stills, because it is the same kind of material. */
.ba-video {
  margin: 0 0 clamp(2rem, 4vw, 2.75rem);
}
.ba-video video {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--soft-gray);
  background: var(--deep-slate);
}
.ba-video > figcaption { margin: 0; }

/* Wide tables. Every other cmp-table on this site is two columns and fits a
   phone; the skin-of-color modality table is four and has a min-content width
   of roughly 459px against 335px of content width at a 375px viewport. Scroll
   the table rather than shrinking the type or hyphenating clinical terms. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 0 0 1.25rem;
}
.table-scroll .cmp-table { margin-bottom: 0; }

/* Diagram figures. These SVGs carry 11-12 unit labels on a 900-960 unit canvas;
   scaled into a 375px viewport that lands near 5px, which is not readable. The
   package's own QA calls for scrolling them rather than shrinking them, so hold
   a legible floor and let the narrow case scroll horizontally. */
.fig-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.fig-scroll img { min-width: 640px; width: 100%; height: auto; display: block; }

/* ============================================================
   Current-offer callout (home page)

   The offer used to sit as a paragraph below the treatments grid, a long
   scroll from the top. It is promoted here into a panel that reads as part of
   the page rather than as an advertisement bolted to it: the same ivory
   ground, the same ice-blue accent edge as .disclaimer-banner and
   .wdo-section--callout, a display-serif line for the price and body copy at
   full ink for the conditions.

   The conditions are deliberately NOT set in --muted. The photo consent is
   what the $450 is conditional on; a condition typeset as fine print is a
   condition designed to be skimmed past.
   ============================================================ */

.offer-callout {
  border: var(--rule);
  border-left: 3px solid var(--ice-blue);
  background: rgba(184, 206, 218, 0.10);
  padding: clamp(1.5rem, 4vw, 2.25rem) clamp(1.25rem, 3vw, 2rem);
  margin: clamp(2.5rem, 6vw, 4rem) 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(1.5rem, 3vw, 2rem);
}

/* Two columns only once there is room for the price line and a ~34rem measure
   side by side. Below that the panel is one column, price first. */
@media (min-width: 860px) {
  .offer-callout {
    grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
    gap: clamp(2rem, 4vw, 3rem);
    align-items: start;
  }
}

.offer-callout .mono-label { display: block; margin: 0 0 0.75rem; }

.offer-callout__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 3vw, 2.125rem);
  line-height: 1.15;
  letter-spacing: -0.005em;
  color: var(--deep-slate);
  margin: 0 0 1.5rem;
  text-wrap: balance;
}

.offer-callout__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
  margin: 0;
}

.offer-callout__more {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* --soft-gray is nearly invisible on the tinted ground. */
  border-bottom-color: var(--muted);
}
.offer-callout__more:hover { border-bottom-color: var(--deep-slate); }

.offer-callout__terms p {
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--deep-slate);
  margin: 0 0 0.875rem;
  max-width: var(--measure);
}
.offer-callout__terms p:last-child { margin-bottom: 0; }

/* ============================================================
   Offer modal (home page)

   Anchored, not full-screen. Google treats a mobile page whose content is
   obscured by an interstitial as a demotion candidate, so the panel is capped
   in both dimensions and always leaves page content visible around it: at
   320px it is 296px wide inside the viewport padding, and it never exceeds
   76vh. Long copy scrolls inside the panel rather than growing it.

   Timing and dismissal live in /assets/offer-modal.js. There is no inline
   style anywhere in the markup — style-src carries no 'unsafe-inline'.
   ============================================================ */

.offer-modal {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: clamp(0.75rem, 3vw, 2rem);
}
/* `display: flex` above would otherwise defeat the hidden attribute. */
.offer-modal[hidden] { display: none; }

/* Light rather than opaque: enough to mark the page behind as inert, not
   enough to read as a content-blocking interstitial. */
.offer-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(42, 51, 61, 0.28);
}

.offer-modal__panel {
  position: relative;
  width: 100%;
  max-width: 25rem;
  max-height: min(76vh, 34rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--ivory);
  border: var(--rule);
  border-top: 3px solid var(--ice-blue);
  box-shadow: 0 12px 40px rgba(42, 51, 61, 0.20);
  padding: 1.375rem 1.25rem 1.5rem;
}
/* Focused programmatically on open; the outline would otherwise draw a box
   around the whole panel for a mouse user who never asked for it. */
.offer-modal__panel:focus { outline: none; }

@media (min-width: 720px) {
  .offer-modal { justify-content: flex-end; }
  .offer-modal__panel { padding: 1.75rem 1.75rem 2rem; }
}

/* Real text, not a bare glyph: the accessible name is the word itself, so it
   survives an icon font failing and reads correctly in a screen reader's
   controls list. */
.offer-modal__close {
  position: absolute;
  top: 0.625rem;
  right: 0.625rem;
  background: none;
  border: none;
  padding: 0.375rem 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: color 160ms ease;
}
.offer-modal__close:hover { color: var(--deep-slate); }

.offer-modal .mono-label { display: block; margin: 0 0 0.625rem; }

.offer-modal__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.375rem, 4vw, 1.625rem);
  line-height: 1.18;
  color: var(--deep-slate);
  margin: 0 0 1rem;
  /* Clears the Close control in the corner. */
  padding-right: 3.5rem;
}

.offer-modal__body p {
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--deep-slate);
  margin: 0 0 0.75rem;
}
.offer-modal__body p:last-child { margin-bottom: 0; }

.offer-modal__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.875rem 1.25rem;
  margin: 1.25rem 0 0;
}

.offer-modal__more {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* Motion only where it is welcome. The global prefers-reduced-motion block
   above already neutralises durations with !important; scoping the animation
   to no-preference means there is nothing to neutralise in the first place. */
@media (prefers-reduced-motion: no-preference) {
  .offer-modal__backdrop { animation: offer-modal-fade 200ms ease-out both; }
  .offer-modal__panel { animation: offer-modal-rise 220ms ease-out both; }
}

@keyframes offer-modal-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes offer-modal-rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* ============================================================
   Important Safety Information — collapsed regulatory block

   Allergan's JUVEDERM Collection ISI on /treatments/dermal-fillers.
   Set quiet by design: it is required text, not sales copy, so it
   sits under a native <details> that ships closed. Content inside
   <details> is still crawled, so collapsing costs nothing in index
   terms and keeps a 900-word legal block from burying the page.

   Token note: the source for these rules referenced --measure-wide,
   --ink and --accent, which this stylesheet does not define, and used
   --rule as if it were a colour. Remapped to the real tokens above:
   --page-max, --deep-slate, --ice-blue, and --rule as the complete
   border shorthand it actually is (`border-top: var(--rule)`), which
   is how .wdo-section and .faq already draw their top rules.
   ============================================================ */

.isi {
  max-width: var(--page-max);
  margin: 4rem auto 0;
  padding: 0 var(--gutter);
  border-top: var(--rule);
}

.isi__summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.6em;
  padding: 1.25rem 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 0.15s ease;
}

.isi__summary::-webkit-details-marker { display: none; }

/* chevron */
.isi__summary::before {
  content: "";
  width: 0.5em;
  height: 0.5em;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translate(-0.1em, -0.1em);
  transition: transform 0.2s ease;
  flex-shrink: 0;
}

.isi[open] .isi__summary::before {
  transform: rotate(-135deg) translate(-0.1em, -0.1em);
}

.isi__summary:hover { color: var(--deep-slate); }

.isi__summary:focus-visible {
  outline: 2px solid var(--ice-blue);
  outline-offset: 4px;
  border-radius: 2px;
}

.isi__body {
  padding-bottom: 3rem;
  color: var(--muted);
  font-size: 0.8125rem;
  line-height: 1.65;
  max-width: 68ch;
}

.isi__body > * + * { margin-top: 0.9em; }

.isi__body h2 {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--deep-slate);
  margin-top: 2.25em;
}

.isi__body h3 {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--deep-slate);
  margin-top: 1.75em;
}

.isi__body ul {
  margin-top: 0.75em;
  padding-left: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.6em;
}

@media (prefers-reduced-motion: reduce) {
  .isi__summary,
  .isi__summary::before { transition: none; }
}

/* The block above was written for a top-level .isi. On /treatments/dermal-fillers
   it sits inside <main>, which already applies --page-max and --gutter, so the
   gutter doubles and the ISI hangs a full gutter (20px at 320, 40px at 1280)
   inside .page-head, .cta-band and every other block on the page. Neutralise the
   padding only in that nesting; a top-level .isi keeps its own gutter. */
main > .isi { padding-left: 0; padding-right: 0; }

/* ============================================================
   Ad landing pages (/lp/*)
   ------------------------------------------------------------
   Deliberately minimal chrome. These pages carry no site nav: the nav links
   every treatment page, and several of those mention platelet-rich plasma,
   which Google names outright in its speculative-and-experimental-treatment
   policy. Google reviews the website rather than only the ad's URL, so these
   pages link only to pages carrying no regenerative-medicine content. Fewer
   exits also converts better, which is the ordinary reason landing pages look
   like this.
   ============================================================ */
.lp-header { border-bottom: var(--rule); }
.lp-header__inner {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 1.125rem var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.lp-wordmark {
  font-family: var(--font-display);
  font-size: 1.375rem;
  color: var(--deep-slate);
  letter-spacing: 0.01em;
}
.lp-header__actions { display: flex; align-items: center; gap: 1.25rem; }
.lp-tel {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  color: var(--deep-slate);
  text-decoration: none;
  white-space: nowrap;
}
.lp-tel:hover { text-decoration: underline; }

.lp-footer { border-top: var(--rule); margin-top: clamp(2.5rem, 6vw, 4.5rem); }
.lp-footer__inner {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 2rem var(--gutter) 1rem;
  display: flex;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}
.lp-footer__links { display: flex; flex-direction: column; gap: 0.5rem; }
.lp-footer__links a {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
}
.lp-footer__links a:hover { color: var(--deep-slate); }
.lp-footer__fine {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 var(--gutter) 2rem;
  font-size: 0.75rem;
  color: var(--muted);
}

/* ============================================================
   Manufacturer offer creative (/treatments/botox, /treatments/dermal-fillers)

   Allergan ships these consumer offers as 1080x1920 portrait social creatives.
   Left to the page measure that renders as a full-bleed billboard on a desktop
   screen and buries the pricing it sits under, so the figure is capped at a
   phone-width column and stays left-aligned with the body copy. The intrinsic
   width/height on the img still reserves the right box before it loads; the
   cap is on the figure, not on the attributes.

   The creative carries a QR code, which is useless to the majority of this
   page's traffic -- someone already holding the phone cannot scan a code
   printed on that same phone's screen. The creative is therefore wrapped in
   the URL the code encodes, and a plain text anchor to the same destination
   sits underneath it for keyboard and screen-reader users.
   ============================================================ */

.mfr-offer__figure { margin: 1.25rem 0 0; }

.mfr-offer__link {
  display: block;
  max-width: 18rem;
  border: var(--rule);
}
.mfr-offer__link img { display: block; width: 100%; height: auto; }

.mfr-offer__caption {
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--muted);
  max-width: var(--measure);
  margin: 0.75rem 0 1.25rem;
}

/* ============================================================
   Premises photographs (/, /contact)

   The only photographs on this site that are ours. Everything else in
   assets/ is manufacturer before-and-after imagery of other clinicians'
   patients, which carries its own disclosure styling; these do not need it
   and should not borrow it.

   They run to the text measure rather than full-bleed, because both sit
   inside running copy that is explaining how to find the building -- the
   photograph is evidence for the sentence above it, not a hero image.
   figure carries a browser default margin of 1em 40px, which would indent
   them out of alignment with the paragraphs, so it is reset explicitly.
   ============================================================ */

.premises-figure {
  margin: 1.25rem 0 1.5rem;
  max-width: var(--measure);
}
.premises-figure img {
  display: block;
  width: 100%;
  height: auto;
  border: var(--rule);
}

.premises-figure__caption {
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--muted);
  margin: 0.75rem 0 0;
}

/* ============================================================
   Printed menu destination (/menu)
   ------------------------------------------------------------
   A reading surface, not a marketing page. Someone is standing in the suite
   with a paper trifold in one hand and a phone in the other, and they want the
   price list in under two seconds. So: one column at every width, hairline
   rules, no cards, no shadows, no icons, nothing that has to load before the
   list is readable.

   The price column is what the eye scans, so it is set in the mono face and
   right-aligned, and the row is a dotted leader between the two. Qualifiers
   ("0.55 mL", "per unit") ride inline after the name in muted small text
   rather than taking a column of their own, because a third column is what
   breaks a price list at 390px.
   ============================================================ */

.menu { max-width: var(--measure); }

.menu-effective {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  line-height: 1.6;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0.75rem 0 0;
}

.menu-cat { margin: 0 0 clamp(2rem, 4vw, 2.75rem); }

.menu-cat__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 3vw, 1.875rem);
  line-height: 1.2;
  margin: 0 0 0.375rem;
  padding-bottom: 0.5rem;
  border-bottom: var(--rule);
}

.menu-cat__device,
.menu-cat__note {
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--muted);
}

.menu-cat__device { margin: 0.625rem 0 0; }
.menu-cat__note { margin: 0.875rem 0 0; }

.menu-rows { margin: 0.875rem 0 0; }

/* Grid rather than flex so the price column lines up down the whole category
   instead of landing wherever each name happens to end. */
.menu-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 0 0.75rem;
  padding: 0.5rem 0;
  border-bottom: 1px dotted var(--soft-gray);
}

.menu-row:last-child { border-bottom: 0; }

.menu-row__name {
  font-size: 0.9375rem;
  line-height: 1.5;
  min-width: 0;
}

.menu-row__qual {
  color: var(--muted);
  font-size: 0.8125rem;
}

.menu-row__price {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  letter-spacing: 0.04em;
  margin: 0;
  text-align: right;
  white-space: nowrap;
}

/* "Quoted" rather than a number, for work that cannot be priced off a list.
   Set apart from the real prices the way the print pieces do it -- muted and
   letterspaced -- so the eye does not read it as a figure. */
.menu-row__price--quoted {
  color: var(--muted);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.menu-fine {
  max-width: var(--measure);
  border-top: var(--rule);
  padding-top: 1.25rem;
}

.menu-fine__p {
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--muted);
  margin: 0 0 0.75rem;
}

.menu-fine__p:last-child { margin-bottom: 0; }

/* Sticky, not fixed: it sits at the bottom of the viewport while there is menu
   left to scroll and then comes to rest above the footer, so it never covers
   the last category or the disclaimers. */
.menu-book {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  justify-content: center;
  padding: 0.875rem var(--gutter);
  background: var(--ivory);
  border-top: var(--rule);
}

.menu-book .btn-primary { width: 100%; max-width: var(--measure); text-align: center; }

/* ============================================================
   Treatment explainer — the stepped figure player

   Five figures that narrate one mechanism, for the treatment pages
   where the physics is the thing worth explaining: what the device
   emits, what the tissue absorbs, and what is left afterwards. The
   stage, the rail and the chapter buttons are shared; only the
   per-figure animation classes carry a treatment prefix (ipl1-…ipl5-
   here, then ery-, vas-, qsw- as those pages land), so four
   explainers run off this one block and one small script.

   Two decisions here look like mistakes from the outside, so both
   are written down.

   1. The stacked figures are the DEFAULT, not the fallback.
      .expl__stage is a plain grid: five figures, five captions,
      legible with JavaScript off, to a crawler, and under
      prefers-reduced-motion. /assets/explainer.js adds .is-player,
      which folds the five into one stage and reveals the transport
      controls — which stay display:none until it does. Nothing is
      hidden from anyone until there is something able to play it.

   2. .is-running is stripped again at data-settle, a beat after the
      last animation in an act has finished. It reads as a no-op,
      and visually it is one: every animation in these figures ends
      on its element's base state. What it actually does is release
      the act's compositor layers. A finished animation keeps its
      layer alive, and the SVG text inside goes on being painted
      from the bitmap rasterised while it was moving — on a step
      that then sits on screen for ten seconds, the labels read
      visibly ghosted. Dropping the class re-rasterises them crisp.
      Per-act settle time is data-settle on the .act, in ms.

   There is no inline <style> and no style attribute anywhere in the
   markup this serves: style-src carries no 'unsafe-inline', so an
   inline declaration is dropped and the figure animates at the
   wrong moment or not at all. That is why the sixteen spectrum-bar
   delays far below are sixteen rules, and why the rail segments are
   given their length from data-dur by the script instead.
   ============================================================ */

.expl { margin-block: 0 3rem; }

.expl__lede {
  max-width: 38rem;
  margin: 0 0 1.75rem;
  font-size: 1.0625rem;
}

.expl__stage {
  display: grid;
  /* minmax(0, 1fr), not the implicit auto column: a grid item's min-width
     is auto, so each figure grew to the SVG's 700px floor and the whole
     page scrolled sideways on phones. Capping the track at the stage width
     puts the overflow back inside .act__frame, which scrolls on its own. */
  grid-template-columns: minmax(0, 1fr);
  gap: 2.5rem;
}

.act { margin: 0; }

/* Same argument as .fig-scroll: these SVGs carry 13-17 unit labels on a
   960 unit canvas, and scaled into a phone they land under 6px. Hold a
   legible floor and let the narrow case scroll sideways in its own box. */
.act__frame {
  background: var(--stage);
  border: 1px solid var(--soft-gray);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.act__frame svg { min-width: 700px; width: 100%; }

.act figcaption {
  padding-top: 1.1rem;
  max-width: 40rem;
}
.act__title {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 500;
  line-height: 1.2;
  margin: 0 0 0.4rem;
}
.act__body { margin: 0; font-size: 0.9375rem; color: var(--muted); }
.act__body strong { color: var(--deep-slate); font-weight: 500; }

/* --- player mode, added by /assets/explainer.js --------------------- */

.expl.is-player .expl__stage { gap: 0; }
.expl.is-player .act {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.45s ease, visibility 0s linear 0.45s;
}
.expl.is-player .act.is-current {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.45s ease, visibility 0s linear 0s;
}
/* Captions differ in length; without a floor the page jumps under the
   reader's cursor every time the step changes. */
.expl.is-player .act figcaption { min-height: 7.5rem; }

.expl__stagewrap { position: relative; }

.expl__bigplay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  aspect-ratio: 960 / 600;
  display: none;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  width: 100%;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--deep-slate);
}
.expl.is-player .expl__bigplay.is-on { display: flex; }
.expl__bigplay .disc {
  display: grid;
  place-items: center;
  width: 4.25rem;
  height: 4.25rem;
  border-radius: 50%;
  background: var(--deep-slate);
  transition: transform 0.2s ease;
}
.expl__bigplay:hover .disc { transform: scale(1.06); }
.expl__bigplay .disc::after {
  content: "";
  width: 0;
  height: 0;
  margin-left: 0.28rem;
  border-left: 1.05rem solid var(--ivory);
  border-top: 0.66rem solid transparent;
  border-bottom: 0.66rem solid transparent;
}

/* --- rail + controls ----------------------------------------------- */

.expl__rail,
.expl__controls { display: none; }
.expl.is-player .expl__rail { display: grid; }
.expl.is-player .expl__controls { display: flex; }

.expl__rail {
  grid-template-columns: repeat(5, 1fr);
  gap: 3px;
  margin-top: 1.25rem;
}
/* --d is set per segment from the act's data-dur by the script. */
.seg { height: 2px; background: var(--soft-gray); overflow: hidden; }
.seg i { display: block; height: 100%; width: 0; background: var(--deep-slate); }
.seg.is-done i { width: 100%; }
.seg.is-live i { animation: railfill var(--d) linear forwards; }

.expl__controls {
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
  margin-top: 1rem;
}

.ctl {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.55rem 1.1rem;
  border: 1px solid var(--deep-slate);
  background: var(--deep-slate);
  color: var(--ivory);
  cursor: pointer;
  min-width: 6.5rem;
}
.ctl:hover { background: #1f272f; }

.chapters { display: flex; flex-wrap: wrap; gap: 0.35rem 1.15rem; }
.chap {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  padding: 0.2rem 0;
  cursor: pointer;
}
.chap:hover { color: var(--deep-slate); }
.chap[aria-current="true"] {
  color: var(--deep-slate);
  border-bottom-color: var(--deep-slate);
}

.expl__transcript {
  margin-top: 2rem;
  border-top: var(--rule);
  padding-top: 1rem;
  max-width: 40rem;
}
.expl__transcript summary {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
}
.expl__transcript h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.125rem;
  margin: 1.4rem 0 0.25rem;
}
.expl__transcript p { margin: 0; font-size: 0.9375rem; color: var(--muted); }

.sr {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* ------------------------------------------------------------
   SVG type and the shared animation grammar

   Generic: every explainer draws from these. Treatment-specific
   keyframes go in that treatment's own block further down.
   ------------------------------------------------------------ */

.act svg text { text-rendering: geometricPrecision; }
.f-mono { font-family: var(--font-mono); font-size: 15px; }
.f-mono-s { font-family: var(--font-mono); font-size: 13px; }
.f-lab { font-family: var(--font-body); font-size: 17px; font-weight: 500; }
.ink { fill: var(--deep-slate); }
.dim { fill: var(--muted); }

/* transform-box so percentage and transform-origin values resolve against
   the viewBox rather than the element's own tight bounding box. */
.act svg [data-anim] { transform-box: view-box; }

@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
@keyframes drawline { from { stroke-dashoffset: var(--len); } to { stroke-dashoffset: 0; } }
@keyframes popin {
  0%   { opacity: 0; transform: scale(0.4); }
  62%  { opacity: 1; transform: scale(1.14); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes sweepx { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes riseband {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 0.8; transform: translateY(0); }
}
@keyframes riseup {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes beamdrop {
  from { opacity: 0; transform: translateY(-46px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes heatring {
  0%   { opacity: 0; transform: scale(0.3); }
  25%  { opacity: 0.95; }
  100% { opacity: 0; transform: scale(1.35); }
}
@keyframes bigflash {
  0%, 100% { opacity: 0; }
  4%       { opacity: 0.88; }
  18%      { opacity: 0; }
}
@keyframes railfill { from { width: 0; } to { width: 100%; } }

/* Pause freezes every running animation in the act and the rail rather
   than unwinding them, so resume picks up mid-gesture. */
.expl.is-held .act.is-running *,
.expl.is-held .seg.is-live i { animation-play-state: paused; }

/* The stacked default is already the reduced-motion answer; this only
   catches a preference set after the script has promoted the player. */
/* Reduced motion keeps the player and drops the playback. The stage still
   folds to one act, but nothing animates and nothing advances on its own --
   /assets/explainer.js adds .is-stepped and turns the transport into a single
   Next control beside the chapter list. The overlay goes, because there is no
   playback left to offer, and the rail becomes a position marker rather than
   a clock. */
.expl.is-stepped .expl__bigplay { display: none; }
.expl.is-stepped .seg i { animation: none; transition: none; }

@media (prefers-reduced-motion: reduce) {
  .act svg * { animation: none !important; }
  .expl.is-player .act { transition: none !important; }
}

/* ============================================================
   IPL photofacial explainer (/treatments/ipl-photofacial)

   Everything above is shared. Everything below belongs to this
   figure set alone: the five acts' classes, and the five keyframes
   only these figures use. The Er:YAG, vascular and Q-switched
   explainers add their own block beside this one, with their own
   prefix, and leave this one alone.
   ============================================================ */

/* ---------------- ACT 1 : the flash ---------------- */
.ipl1-glow { opacity: 0.16; }
.ipl1-cone { transform-origin: 234px 260px; }
.ipl1-ray  { stroke-width: 2.1; stroke-linecap: round; opacity: 0.78; }
.ipl1-band rect { opacity: 0.8; }

.act.is-running .ipl1-glow { animation: lampflash 3.4s 0.2s ease-out infinite; }
.act.is-running .ipl1-cone { animation: sweepx 1.1s 0.35s cubic-bezier(.2,.7,.3,1) backwards; }
.act.is-running .ipl1-ray {
  --len: 680;
  stroke-dasharray: 680;
  animation: drawline 0.85s cubic-bezier(.3,.7,.3,1) backwards;
}
.act.is-running .ipl1-r1 { animation-delay: 0.45s; }
.act.is-running .ipl1-r2 { animation-delay: 0.53s; }
.act.is-running .ipl1-r3 { animation-delay: 0.61s; }
.act.is-running .ipl1-r4 { animation-delay: 0.69s; }
.act.is-running .ipl1-r5 { animation-delay: 0.77s; }
.act.is-running .ipl1-r6 { animation-delay: 0.85s; }
.act.is-running .ipl1-r7 { animation-delay: 0.93s; }
.act.is-running .ipl1-band rect { animation: riseband 0.5s backwards; }

/* The spectrum bar fills left to right, one 36.25-unit slice every 80ms.
   These were style="animation-delay:…" in the source figure; style-src
   has no 'unsafe-inline', so they are rules. Order matches the markup. */
.act.is-running .ipl1-band rect:nth-child(1)  { animation-delay: 1.10s; }
.act.is-running .ipl1-band rect:nth-child(2)  { animation-delay: 1.18s; }
.act.is-running .ipl1-band rect:nth-child(3)  { animation-delay: 1.26s; }
.act.is-running .ipl1-band rect:nth-child(4)  { animation-delay: 1.34s; }
.act.is-running .ipl1-band rect:nth-child(5)  { animation-delay: 1.42s; }
.act.is-running .ipl1-band rect:nth-child(6)  { animation-delay: 1.50s; }
.act.is-running .ipl1-band rect:nth-child(7)  { animation-delay: 1.58s; }
.act.is-running .ipl1-band rect:nth-child(8)  { animation-delay: 1.66s; }
.act.is-running .ipl1-band rect:nth-child(9)  { animation-delay: 1.74s; }
.act.is-running .ipl1-band rect:nth-child(10) { animation-delay: 1.82s; }
.act.is-running .ipl1-band rect:nth-child(11) { animation-delay: 1.90s; }
.act.is-running .ipl1-band rect:nth-child(12) { animation-delay: 1.98s; }
.act.is-running .ipl1-band rect:nth-child(13) { animation-delay: 2.06s; }
.act.is-running .ipl1-band rect:nth-child(14) { animation-delay: 2.14s; }
.act.is-running .ipl1-band rect:nth-child(15) { animation-delay: 2.22s; }
.act.is-running .ipl1-band rect:nth-child(16) { animation-delay: 2.30s; }

.act.is-running .ipl1-scale { animation: fadein 0.6s 2.9s backwards; }
.act.is-running .ipl1-brace { animation: fadein 0.6s 3.3s backwards; }

/* ---------------- ACT 2 : the filter ---------------- */
.ipl2-plate { transform-origin: 303px 180px; }
.ipl2-ray { stroke-width: 2.1; stroke-linecap: round; opacity: 0.78; }
.ipl2-stop { transform-origin: 296px 252px; }

.act.is-running .ipl2-glow { animation: lampflash 3.4s 0.2s ease-out infinite; }
.act.is-running .ipl2-ray {
  --len: 670;
  stroke-dasharray: 670;
  animation: drawline 0.8s 0.3s cubic-bezier(.3,.7,.3,1) backwards;
}
.act.is-running .ipl2-ray-s { --len: 70; stroke-dasharray: 70; }
.act.is-running .ipl2-plate { animation: riseup 0.7s 1.15s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .ipl2-platelab { animation: fadein 0.5s 1.75s backwards; }
.act.is-running .ipl2-stop { animation: popin 0.4s 1.85s backwards; }
.act.is-running .ipl2-dead { animation: fadein 0.7s 2.1s reverse backwards; }
.act.is-running .ipl2-cutgroup { animation: fadein 0.5s 2.5s backwards; }
.act.is-running .ipl2-out { animation: fadein 0.6s 3.1s backwards; }

/* the cut-off is not one fixed number: it walks, then settles */
.act.is-running .ipl2-slide { animation: cutwalk 6s 3.6s ease-in-out both; }

/* ---------------- ACT 3 : the two curves ---------------- */
.ipl3-curve { fill: none; stroke-width: 2.6; stroke-linejoin: round; }
.ipl3-mel { stroke: var(--pigment); }
.ipl3-hb  { stroke: var(--vessel); }
.ipl3-band { transform-origin: 306px 150px; }

.act.is-running .ipl3-axes { animation: fadein 0.5s backwards; }
.act.is-running .ipl3-mel { --len: 830; stroke-dasharray: 830; animation: drawline 1.5s 0.4s ease-out backwards; }
.act.is-running .ipl3-hb  { --len: 1300; stroke-dasharray: 1300; animation: drawline 1.8s 2.1s ease-out backwards; }
.act.is-running .ipl3-legend { animation: fadein 0.6s 1.6s backwards; }
.act.is-running .ipl3-band { animation: sweepx 0.9s 4.4s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .ipl3-cutline { animation: fadein 0.5s 4.4s backwards; }
.act.is-running .ipl3-hbmark { animation: popin 0.5s 5.4s backwards; transform-origin: 316px 280px; }
.act.is-running .ipl3-irmark { animation: popin 0.5s 6.0s backwards; transform-origin: 713px 410px; }
.act.is-running .ipl3-melmark { animation: popin 0.5s 6.6s backwards; transform-origin: 306px 314px; }

/* ---------------- ACT 4 : selective photothermolysis ---------------- */
.ipl4-spot { fill: #3A2A20; }
.ipl4-vessel { stroke: #8D8F92; fill: none; stroke-linecap: round; }
.ipl4-v-main { stroke-width: 3.5; }
.ipl4-v-branch { stroke-width: 2; }
.ipl4-flash { opacity: 0; }
.ipl4-ring { fill: none; stroke-width: 1.6; stroke-dasharray: 3 5; opacity: 0; }
.ipl4-r-pig { stroke: var(--pigment); transform-origin: 297px 215px; }
.ipl4-r-ves { stroke: var(--vessel); transform-origin: 660px 330px; }

.act.is-running .ipl4-skin { animation: fadein 0.5s backwards; }
.act.is-running .ipl4-targets { animation: fadein 0.6s 0.5s backwards; }
.act.is-running .ipl4-beam { animation: beamdrop 0.8s 1.5s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .ipl4-flash { animation: bigflash 3.4s 2.2s ease-out; }
.act.is-running .ipl4-ring { animation: heatring 2.2s 2.5s ease-out; }
.act.is-running .ipl4-ring2 { animation-delay: 2.75s; }
.act.is-running .ipl4-ring3 { animation-delay: 3s; }
.act.is-running .ipl4-spot { animation: cook 4.4s 2.3s ease-out backwards; }
.act.is-running .ipl4-v-main { animation: collapsemain 4.4s 2.3s ease-out backwards; }
.act.is-running .ipl4-v-branch { animation: collapsebr 4.4s 2.3s ease-out backwards; }
.act.is-running .ipl4-through { animation: fadein 0.6s 4.2s backwards; }
.act.is-running .ipl4-timing { animation: fadein 0.7s 5.4s backwards; }
.act.is-running .ipl4-pulsebar { transform-origin: 560px 500px; animation: sweepx 0.5s 5.9s ease-out backwards; }
.act.is-running .ipl4-trtbar { transform-origin: 560px 522px; animation: sweepx 1.5s 5.9s linear backwards; }

/* ---------------- ACT 5 : what the skin does next ---------------- */
.ipl5-card { animation-fill-mode: backwards; }
.act.is-running .ipl5-s1 { animation: fadein 0.6s 0.2s backwards; }
.act.is-running .ipl5-s2 { animation: fadein 0.6s 1.6s backwards; }
.act.is-running .ipl5-s3 { animation: fadein 0.6s 3.0s backwards; }
.act.is-running .ipl5-s4 { animation: fadein 0.6s 4.4s backwards; }
.act.is-running .ipl5-ar1 { animation: fadein 0.4s 1.3s backwards; }
.act.is-running .ipl5-ar2 { animation: fadein 0.4s 2.7s backwards; }
.act.is-running .ipl5-ar3 { animation: fadein 0.4s 4.1s backwards; }
.act.is-running .ipl5-limits { animation: fadein 0.7s 6.1s backwards; }

/* The five keyframes only the IPL figures use: a xenon lamp's decay, the
   cut-off wandering across the spectrum, and pigment and vessel changing
   colour and calibre as they take the heat. */
@keyframes lampflash {
  0%, 100% { opacity: 0.16; }
  5%       { opacity: 1; }
  16%      { opacity: 0.3; }
  50%      { opacity: 0.22; }
}
@keyframes cutwalk {
  0%   { transform: translateX(0); }
  22%  { transform: translateX(72.5px); }
  48%  { transform: translateX(-36.25px); }
  74%  { transform: translateX(36.25px); }
  100% { transform: translateX(0); }
}
@keyframes cook {
  0%, 8%  { fill: #9E6A46; }
  46%     { fill: #6B4230; }
  100%    { fill: #3A2A20; }
}
@keyframes collapsemain {
  0%, 8%  { stroke: #A8474F; stroke-width: 9; }
  100%    { stroke: #8D8F92; stroke-width: 3.5; }
}
@keyframes collapsebr {
  0%, 8%  { stroke: #A8474F; stroke-width: 5; }
  100%    { stroke: #8D8F92; stroke-width: 2; }
}

/* ============================================================
   Er:YAG resurfacing explainer (/treatments/laser-resurfacing)

   Sibling to the IPL block above: the shell, the rail, the SVG type
   helpers and the twelve shared keyframes are already defined once
   further up this file. Everything here belongs to this figure set
   alone — the ery1-…ery5- classes, and the three keyframes only
   these figures use.

   Two tokens go in :root with the others, not here: --water and
   --heat. See ship-eryag.md; the rules below assume both exist.

   Same constraint as the IPL block: style-src carries no
   'unsafe-inline', so nothing this figure set needs may live in a
   style attribute. The four steam plumes each scale from their own
   centre, which was style="transform-origin:…" in the source
   figure; here it is four :nth-child rules.
   ============================================================ */

/* ---------------- ACT 1 : where 2,940 nm sits ---------------- */
.ery1-curve { fill: none; stroke: var(--water); stroke-width: 2.6; stroke-linejoin: round; stroke-linecap: round; }

.act.is-running .ery1-axes { animation: fadein 0.5s backwards; }
.act.is-running .ery1-curve { --len: 1100; stroke-dasharray: 1100; animation: drawline 1.8s 0.4s ease-out backwards; }
.act.is-running .ery1-drop { animation: fadein 0.5s 2.3s backwards; }
.act.is-running .ery1-dot1 { animation: popin 0.5s 2.4s backwards; transform-origin: 291.6px 170.6px; }
.act.is-running .ery1-lab1 { animation: fadein 0.6s 2.6s backwards; }
.act.is-running .ery1-dot2 { animation: popin 0.5s 3.6s backwards; transform-origin: 850.8px 224.4px; }
.act.is-running .ery1-lab2 { animation: fadein 0.6s 3.8s backwards; }
.act.is-running .ery1-note { animation: fadein 0.6s 4.6s backwards; }
.act.is-running .ery1-prop { animation: fadein 0.6s 5.6s backwards; }
.act.is-running .ery1-bar1 { transform-origin: 312px 510px; animation: sweepx 0.9s 6.2s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .ery1-bar2 { transform-origin: 312px 544px; animation: sweepx 0.9s 6.6s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .ery1-mult { animation: fadein 0.6s 7.8s backwards; }

/* ---------------- ACT 2 : spent at the surface ---------------- */
.ery2-steam { opacity: 0; }
.ery2-soak { fill: var(--water); }

/* The plumes scale outward from their own centres, so each carries its
   own origin. Order matches the markup, left to right. */
.ery2-steam:nth-child(1) { transform-origin: 205px 150px; }
.ery2-steam:nth-child(2) { transform-origin: 255px 150px; }
.ery2-steam:nth-child(3) { transform-origin: 305px 150px; }
.ery2-steam:nth-child(4) { transform-origin: 350px 150px; }

.act.is-running .ery2-block { animation: fadein 0.5s backwards; }
.act.is-running .ery2-rule { animation: fadein 0.5s 0.4s backwards; }
.act.is-running .ery2-beam { animation: beamdrop 0.8s 1.0s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .ery2-beamlab { animation: fadein 0.5s 1.2s backwards; }
.act.is-running .ery2-soak { animation: soak 1.4s 1.8s ease-out backwards; }
.act.is-running .ery2-soaklab { animation: fadein 0.6s 2.4s backwards; }
.act.is-running .ery2-steam { animation: plume 1.6s 2.6s ease-out; }
.act.is-running .ery2-s2 { animation-delay: 2.8s; }
.act.is-running .ery2-s3 { animation-delay: 3.0s; }
.act.is-running .ery2-s4 { animation-delay: 3.2s; }
.act.is-running .ery2-crater { transform-origin: 150px 160px; animation: sweepy 0.7s 2.8s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .ery2-pass { animation: fadein 0.6s 3.8s backwards; }
.act.is-running .ery2-epi { animation: fadein 0.6s 4.6s backwards; }
.act.is-running .ery2-data { animation: fadein 0.7s 5.6s backwards; }

/* ---------------- ACT 3 : the heat left behind ---------------- */
.ery3-crater { fill: var(--ivory); stroke: var(--deep-slate); stroke-width: 1.2; }
.ery3-rtd { fill: url(#hatch-ery3); stroke: var(--heat); stroke-width: 1; }

.act.is-running .ery3-blocks { animation: fadein 0.5s backwards; }
.act.is-running .ery3-scale { animation: fadein 0.5s 0.4s backwards; }
.act.is-running .ery3-craterE { transform-origin: 170px 170px; animation: sweepy 0.6s 1.2s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .ery3-rtdE { transform-origin: 170px 204px; animation: sweepy 1.0s 1.8s ease-out backwards; }
.act.is-running .ery3-labE { animation: fadein 0.6s 2.6s backwards; }
.act.is-running .ery3-craterC { transform-origin: 600px 170px; animation: sweepy 0.6s 3.4s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .ery3-rtdC { transform-origin: 600px 251px; animation: sweepy 1.6s 4.0s ease-out backwards; }
.act.is-running .ery3-labC { animation: fadein 0.6s 5.4s backwards; }
.act.is-running .ery3-table { animation: fadein 0.7s 6.4s backwards; }

/* ---------------- ACT 4 : depth is the decision ---------------- */
.ery4-cut { fill: var(--ivory); stroke: var(--deep-slate); stroke-width: 1.2; }

.act.is-running .ery4-block { animation: fadein 0.6s backwards; }
.act.is-running .ery4-p1 { transform-origin: 150px 150px; animation: sweepy 0.5s 1.0s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .ery4-l1 { animation: fadein 0.5s 1.4s backwards; }
.act.is-running .ery4-p2 { transform-origin: 150px 205px; animation: sweepy 0.5s 2.2s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .ery4-l2 { animation: fadein 0.5s 2.6s backwards; }
.act.is-running .ery4-p3 { transform-origin: 150px 260px; animation: sweepy 0.5s 3.4s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .ery4-l3 { animation: fadein 0.5s 3.8s backwards; }
.act.is-running .ery4-epi { animation: fadein 0.6s 4.6s backwards; }
.act.is-running .ery4-cardA { animation: fadein 0.6s 5.0s backwards; }
.act.is-running .ery4-cardB { animation: fadein 0.6s 6.0s backwards; }
.act.is-running .ery4-not { animation: fadein 0.6s 7.0s backwards; }

/* ---------------- ACT 5 : healing, described honestly ---------------- */
.act.is-running .ery5-head { animation: fadein 0.5s 0.1s backwards; }
.act.is-running .ery5-s1 { animation: fadein 0.6s 0.2s backwards; }
.act.is-running .ery5-s2 { animation: fadein 0.6s 1.6s backwards; }
.act.is-running .ery5-s3 { animation: fadein 0.6s 3.0s backwards; }
.act.is-running .ery5-s4 { animation: fadein 0.6s 4.4s backwards; }
.act.is-running .ery5-ar1 { animation: fadein 0.4s 1.3s backwards; }
.act.is-running .ery5-ar2 { animation: fadein 0.4s 2.7s backwards; }
.act.is-running .ery5-ar3 { animation: fadein 0.4s 4.1s backwards; }
.act.is-running .ery5-sun { animation: fadein 0.6s 5.4s backwards; }
.act.is-running .ery5-limits { animation: fadein 0.7s 6.4s backwards; }

/* The three keyframes only the Er:YAG figures use: a band growing down
   from its own top edge, water taking up the pulse and flashing off, and
   the vapour leaving. sweepy is sweepx's vertical twin and earns its own
   name rather than a rotate hack, because these bands grow downward from
   the skin surface and the surface must not move. */
@keyframes sweepy { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes soak {
  0%   { opacity: 0; }
  18%  { opacity: 1; }
  46%  { opacity: 0.4; }
  72%  { opacity: 1; }
  100% { opacity: 1; }
}
@keyframes plume {
  0%   { opacity: 0; transform: translateY(6px) scale(0.5); }
  22%  { opacity: 0.75; }
  100% { opacity: 0; transform: translateY(-46px) scale(1.6); }
}

/* ============================================================
   Vascular Nd:YAG explainer (/treatments/spider-veins)

   Appends to the shared explainer block above. Nothing generic is
   repeated here: this is the five acts' own classes and the four
   keyframes only these figures use. The two hues are --pigment and
   --vessel, already in :root, so no new token is added. As with the
   IPL block, every delay is a rule rather than a style attribute --
   style-src carries no 'unsafe-inline'.
   ============================================================ */

/* ---------------- ACT 1 : reaching the vessel ---------------- */
/* a beam column is a rect with a vertical alpha ramp; it grows downward,
   so transform-origin is its own top edge. No text inside any of them. */
.vas1-col { transform-origin: 50% 140px; }
.vas1-bar { transform-origin: 50% 420px; }

.act.is-running .vas1-skin   { animation: fadein 0.6s backwards; }
.act.is-running .vas1-heads  { animation: fadein 0.5s 0.5s backwards; }
.act.is-running .vas1-vessel { animation: fadein 0.6s 0.9s backwards; }
.act.is-running .vas1-c1 { animation: growdown 0.8s 1.6s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .vas1-l1 { animation: fadein 0.5s 2.3s backwards; }
.act.is-running .vas1-c2 { animation: growdown 0.9s 2.6s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .vas1-l2 { animation: fadein 0.5s 3.4s backwards; }
.act.is-running .vas1-c3 { animation: growdown 1.1s 3.7s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .vas1-hit {
  transform-origin: 430px 372px;
  animation: heatring 1.8s 4.5s ease-out;
}
.act.is-running .vas1-hit2 { animation-delay: 4.9s; }
.act.is-running .vas1-l3 { animation: fadein 0.5s 5.0s backwards; }
.act.is-running .vas1-chart { animation: fadein 0.6s 5.4s backwards; }
.act.is-running .vas1-bar { animation: growup 0.7s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .vas1-b1 { animation-delay: 5.7s; }
.act.is-running .vas1-b2 { animation-delay: 5.9s; }
.act.is-running .vas1-b3 { animation-delay: 6.1s; }
.act.is-running .vas1-note { animation: fadein 0.6s 6.6s backwards; }

/* ---------------- ACT 2 : a weak target, taken whole ---------------- */
.vas2-curve { fill: none; stroke-width: 2.6; stroke-linejoin: round; stroke-linecap: round; }
.vas2-oxy   { stroke: var(--vessel); }
.vas2-deoxy { stroke: var(--vessel); stroke-dasharray: 7 5; opacity: 0.75; }
.vas2-fbar  { transform-origin: 620px 50%; }

.act.is-running .vas2-axes  { animation: fadein 0.5s backwards; }
.act.is-running .vas2-oxy   { --len: 780; stroke-dasharray: 780; animation: drawline 1.3s 0.4s ease-out backwards; }
.act.is-running .vas2-deoxy { --len: 720; stroke-dasharray: 7 5; animation: fadein 1.3s 1.5s ease-out backwards; }
.act.is-running .vas2-legend { animation: fadein 0.6s 1.2s backwards; }
.act.is-running .vas2-peak  { animation: popin 0.5s 2.9s backwards; transform-origin: 142px 130px; }
.act.is-running .vas2-cut   { animation: fadein 0.5s 3.4s backwards; }

.act.is-running .vas2-cells { animation: fadein 0.6s 3.9s backwards; }
.act.is-running .vas2-beamA { --len: 78; stroke-dasharray: 78; animation: drawline 0.8s 4.4s ease-out backwards; }
.act.is-running .vas2-rim   { --len: 96; stroke-dasharray: 96; animation: drawline 0.6s 5.1s ease-out backwards; }
.act.is-running .vas2-labA  { animation: fadein 0.5s 5.5s backwards; }
.act.is-running .vas2-beamB { --len: 192; stroke-dasharray: 192; animation: drawline 0.9s 6.0s ease-out backwards; }
.act.is-running .vas2-warm  { animation: fadein 1.0s 6.6s backwards; }
.act.is-running .vas2-labB  { animation: fadein 0.5s 7.2s backwards; }

.act.is-running .vas2-lead  { --len: 252; stroke-dasharray: 252; animation: drawline 0.7s 7.8s ease-out backwards; }
.act.is-running .vas2-mag   { animation: fadein 0.5s 8.2s backwards; }
.act.is-running .vas2-m1    { animation: sweepx 0.6s 8.5s cubic-bezier(.2,.8,.3,1) backwards; transform-origin: 262px 457px; }
.act.is-running .vas2-m2    { animation: sweepx 0.6s 8.8s cubic-bezier(.2,.8,.3,1) backwards; transform-origin: 262px 497px; }
.act.is-running .vas2-mnote { animation: fadein 0.5s 9.2s backwards; }
.act.is-running .vas2-flu   { animation: fadein 0.5s 9.6s backwards; }
.act.is-running .vas2-f1    { animation: sweepx 0.6s 9.9s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .vas2-f2    { animation: sweepx 0.6s 10.2s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .vas2-fnum  { animation: fadein 0.5s 10.6s backwards; }
.act.is-running .vas2-fnote { animation: fadein 0.6s 11.0s backwards; }

/* ---------------- ACT 3 : pulse matched to caliber ---------------- */
.vas3-ring { fill: none; stroke: var(--vessel); stroke-width: 1.6; stroke-dasharray: 3 5; opacity: 0; }
.vas3-rs { transform-origin: 190px 180px; }
.vas3-rl { transform-origin: 190px 340px; }

.act.is-running .vas3-head { animation: fadein 0.5s backwards; }
.act.is-running .vas3-vs   { animation: fadein 0.5s 0.5s backwards; }
.act.is-running .vas3-rs   { animation: heatring 1.2s 1.0s ease-out; }
.act.is-running .vas3-rs2  { animation-delay: 1.25s; }
.act.is-running .vas3-rs3  { animation-delay: 1.5s; }
.act.is-running .vas3-ls   { animation: fadein 0.5s 2.0s backwards; }
.act.is-running .vas3-vl   { animation: fadein 0.5s 2.8s backwards; }
.act.is-running .vas3-rl   { animation: heatring 2.4s 3.3s ease-out; }
.act.is-running .vas3-rl2  { animation-delay: 3.75s; }
.act.is-running .vas3-rl3  { animation-delay: 4.2s; }
.act.is-running .vas3-ll   { animation: fadein 0.5s 5.6s backwards; }
.act.is-running .vas3-law  { animation: fadein 0.6s 6.2s backwards; }
.act.is-running .vas3-axes { animation: fadein 0.5s 6.8s backwards; }
.act.is-running .vas3-curve {
  --len: 480; stroke-dasharray: 480;
  animation: drawline 1.5s 7.2s ease-out backwards;
}
.act.is-running .vas3-mA { animation: popin 0.5s 8.8s backwards; transform-origin: 638px 428px; }
.act.is-running .vas3-mAt { animation: fadein 0.4s 8.9s backwards; }
.act.is-running .vas3-mB { animation: popin 0.5s 9.3s backwards; transform-origin: 774px 336px; }
.act.is-running .vas3-mBt { animation: fadein 0.4s 9.4s backwards; }
.act.is-running .vas3-band { animation: sweepx 0.7s 9.8s ease-out backwards; transform-origin: 570px 430px; }
.act.is-running .vas3-bandlab { animation: fadein 0.5s 10.1s backwards; }
.act.is-running .vas3-note { animation: fadein 0.6s 10.3s backwards; }

/* ---------------- ACT 4 : the vessel closes ---------------- */
.vas4-tube { fill: none; stroke-linecap: round; }

.act.is-running .vas4-s1 { animation: fadein 0.6s 0.2s backwards; }
.act.is-running .vas4-s2 { animation: fadein 0.6s 2.4s backwards; }
.act.is-running .vas4-s3 { animation: fadein 0.6s 4.2s backwards; }
.act.is-running .vas4-s4 { animation: fadein 0.6s 6.0s backwards; }
.act.is-running .vas4-ar1 { animation: fadein 0.4s 2.0s backwards; }
.act.is-running .vas4-ar2 { animation: fadein 0.4s 3.8s backwards; }
.act.is-running .vas4-ar3 { animation: fadein 0.4s 5.6s backwards; }

/* the first frame is the only one that moves: blood and wall heating */
.act.is-running .vas4-hot { animation: cookblood 2.0s 0.8s ease-out backwards; }
.act.is-running .vas4-ring {
  transform-origin: 175px 225px;
  animation: heatring 1.8s 1.0s ease-out;
}
.act.is-running .vas4-ring2 { animation-delay: 1.4s; }
.act.is-running .vas4-limits { animation: fadein 0.7s 7.4s backwards; }

/* ---------------- ACT 5 : where the laser stops ---------------- */
/* The treated vessels clear and stay cleared -- the page is explicit that
   treated vessels are gone for good. What the unfixed source does is mint
   NEW ones, which is a separate group that arrives late. Resting opacity
   is the end state, so dropping .is-running at settle changes nothing. */
.vas5-treated { opacity: 0.16; }

.act.is-running .vas5-skin    { animation: fadein 0.6s 0.2s backwards; }
.act.is-running .vas5-treated { animation: treatedarc 5s 0.8s ease-out backwards; }
.act.is-running .vas5-deep    { animation: fadein 0.6s 1.6s backwards; }
.act.is-running .vas5-valve   { animation: fadein 0.5s 2.4s backwards; }
.act.is-running .vas5-press   { animation: fadein 0.6s 3.0s backwards; }
.act.is-running .vas5-new     { animation: fadein 1.0s 5.2s backwards; }
.act.is-running .vas5-key     { animation: fadein 0.6s 6.0s backwards; }
.act.is-running .vas5-o1 { animation: fadein 0.5s 6.8s backwards; }
.act.is-running .vas5-o2 { animation: fadein 0.5s 7.4s backwards; }
.act.is-running .vas5-o3 { animation: fadein 0.5s 8.0s backwards; }
.act.is-running .vas5-limits { animation: fadein 0.7s 9.0s backwards; }

/* The four keyframes only the vascular figures use: a beam column growing
   down from the skin surface, a bar growing up from its baseline, blood
   darkening as it coagulates, and a treated vessel flaring once and then
   staying faded. growdown and growup are the same two frames under two
   names -- the direction lives in each class's transform-origin, and the
   pair is kept apart so the markup reads as what it means. */
@keyframes growdown { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes growup   { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes cookblood {
  0%, 6%  { stroke: #C97F85; }
  100%    { stroke: #A8474F; }
}
@keyframes treatedarc {
  0%   { opacity: 0; }
  12%  { opacity: 1; }
  58%  { opacity: 1; }
  80%  { opacity: 0.16; }
  100% { opacity: 0.16; }
}

/* ============================================================
   Q-switched Nd:YAG explainer (/treatments/tattoo-removal)

   The shared .expl shell, the type helpers and the sixteen
   common keyframes are above. Everything here belongs to this
   figure set alone: the five acts' qsw1-…qsw5- classes and the
   one keyframe only these figures use.

   Two tokens this block expects in :root beside --pigment and
   --vessel: --ink-red and --ink-amber, the two chromatic ink
   families 532 nm actually treats. Every ink the platform
   cannot treat is drawn in the site neutrals, which is how act
   4 makes its point without a caption.

   No style attribute anywhere in the markup this serves:
   style-src carries no 'unsafe-inline', so the per-element
   delays below are rules, and the rail segments take their
   length from data-dur via the script.
   ============================================================ */

/* ---------------- ACT 1 : size sets the clock ---------------- */
.qsw1-drop { stroke: #9AA3AB; stroke-width: 1; stroke-dasharray: 3 4; }
.qsw1-nsbar { transform-origin: 130px 504px; }
.qsw1-msbar { transform-origin: 704.8px 504px; }

.act.is-running .qsw1-hdr   { animation: fadein 0.5s 0.1s backwards; }
.act.is-running .qsw1-axis  { animation: fadein 0.5s 0.4s backwards; }
.act.is-running .qsw1-t1    { animation: fadein 0.6s 1.0s backwards; }
.act.is-running .qsw1-t2    { animation: fadein 0.6s 1.8s backwards; }
.act.is-running .qsw1-t3    { animation: fadein 0.6s 2.6s backwards; }
.act.is-running .qsw1-drop  { --len: 84; stroke-dasharray: 84; animation: drawline 0.5s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .qsw1-d1    { animation-delay: 1.5s; }
.act.is-running .qsw1-d2    { animation-delay: 2.3s; }
.act.is-running .qsw1-d3    { animation-delay: 3.1s; }
.act.is-running .qsw1-nsbar { animation: sweepx 0.5s 3.8s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .qsw1-nslab { animation: fadein 0.5s 4.2s backwards; }
.act.is-running .qsw1-msbar { animation: sweepx 0.8s 4.8s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .qsw1-mslab { animation: fadein 0.5s 5.5s backwards; }
.act.is-running .qsw1-gap   { animation: fadein 0.7s 6.1s backwards; }
.act.is-running .qsw1-note  { animation: fadein 0.6s 6.8s backwards; }

/* ---------------- ACT 2 : store it, then dump it ---------------- */
.qsw2-fill  { transform-origin: 152px 194px; }
.qsw2-shut  { opacity: 0; }
.qsw2-bar1  { transform-origin: 240px 378px; }
.qsw2-bar2  { transform-origin: 240px 446px; }

.act.is-running .qsw2-cav   { animation: fadein 0.6s 0.2s backwards; }
.act.is-running .qsw2-pump  { animation: fadein 0.5s 0.8s backwards; }
.act.is-running .qsw2-shut  { animation: qsw-shut 2.6s 0.9s linear backwards; }
@keyframes qsw-shut {
  0%   { opacity: 0; }
  12%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { opacity: 0; }
}
.act.is-running .qsw2-fill  { animation: sweepx 1.5s 1.2s cubic-bezier(.4,.6,.5,1) backwards; }
.act.is-running .qsw2-open  { animation: fadein 0.4s 3.2s backwards; }
.act.is-running .qsw2-out   { animation: fadein 0.5s 3.5s backwards; }
.act.is-running .qsw2-clock { animation: fadein 0.6s 4.0s backwards; }
.act.is-running .qsw2-div   { animation: fadein 0.4s 4.6s backwards; }
.act.is-running .qsw2-hdr   { animation: fadein 0.5s 4.8s backwards; }
.act.is-running .qsw2-row1  { animation: fadein 0.5s 5.3s backwards; }
.act.is-running .qsw2-bar1  { animation: sweepx 0.6s 5.5s ease-out backwards; }
.act.is-running .qsw2-val1  { animation: fadein 0.4s 6.1s backwards; }
.act.is-running .qsw2-row2  { animation: fadein 0.5s 6.7s backwards; }
.act.is-running .qsw2-bar2  { animation: sweepx 1.0s 6.9s ease-out backwards; }
.act.is-running .qsw2-val2  { animation: fadein 0.4s 7.9s backwards; }
.act.is-running .qsw2-scale { animation: fadein 0.5s 8.4s backwards; }

/* ---------------- ACT 3 : it shatters ---------------- */
.qsw3-ring { fill: none; stroke: #6B7784; stroke-width: 1.8; stroke-dasharray: 5 6; opacity: 0; transform-origin: 490px 280px; }

.act.is-running .qsw3-hdr   { animation: fadein 0.5s 0.1s backwards; }
.act.is-running .qsw3-p1    { animation: fadein 0.6s 0.5s backwards; }
.act.is-running .qsw3-beam  { --len: 84; stroke-dasharray: 84; animation: drawline 0.5s 1.1s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .qsw3-ar1   { animation: fadein 0.4s 1.7s backwards; }
.act.is-running .qsw3-p2    { animation: fadein 0.6s 2.1s backwards; }
.act.is-running .qsw3-ring  { animation: heatring 2.0s 2.5s ease-out; }
.act.is-running .qsw3-ring2 { animation-delay: 2.8s; }
.act.is-running .qsw3-ring3 { animation-delay: 3.1s; }
.act.is-running .qsw3-ar2   { animation: fadein 0.4s 4.4s backwards; }
.act.is-running .qsw3-p3    { animation: fadein 0.6s 4.8s backwards; }
.act.is-running .qsw3-frost { animation: fadein 0.7s 5.8s backwards; }

/* ---------------- ACT 4 : two wavelengths, and a gap ---------------- */
.qsw4-curve { fill: none; stroke: #6B7784; stroke-width: 2.4; stroke-linejoin: round; }
.qsw4-vline { stroke: #2A333D; stroke-width: 1.6; }
.qsw4-m532  { transform-origin: 352.6px 263.4px; }
.qsw4-m1064 { transform-origin: 846.6px 325.3px; }
.qsw4-band  { transform-origin: 352.6px 482px; }
/* The two ink colours the platform can treat, as classes rather than
   fill attributes, so the tokens are the single source of truth --
   the same arrangement as .ipl3-mel / .ipl3-hb and --pigment. */
.qsw4-sw-red   { fill: var(--ink-red); }
.qsw4-sw-amber { fill: var(--ink-amber); }

.act.is-running .qsw4-axes    { animation: fadein 0.5s backwards; }
.act.is-running .qsw4-curve   { --len: 700; stroke-dasharray: 700; animation: drawline 1.4s 0.4s ease-out backwards; }
.act.is-running .qsw4-mellab  { animation: fadein 0.5s 1.6s backwards; }
.act.is-running .qsw4-vline   { --len: 170; stroke-dasharray: 170; animation: drawline 0.5s cubic-bezier(.3,.7,.3,1) backwards; }
.act.is-running .qsw4-v532    { animation-delay: 2.2s; }
.act.is-running .qsw4-v1064   { animation-delay: 3.3s; }
.act.is-running .qsw4-lab532  { animation: fadein 0.5s 2.5s backwards; }
.act.is-running .qsw4-m532    { animation: popin 0.45s 2.9s backwards; }
.act.is-running .qsw4-lab1064 { animation: fadein 0.5s 3.6s backwards; }
.act.is-running .qsw4-m1064   { animation: popin 0.45s 4.0s backwards; }
.act.is-running .qsw4-treat   { animation: fadein 0.7s 4.6s backwards; }
.act.is-running .qsw4-band    { animation: sweepx 0.9s 5.6s cubic-bezier(.2,.8,.3,1) backwards; }
.act.is-running .qsw4-bandlab { animation: fadein 0.5s 6.4s backwards; }
.act.is-running .qsw4-miss    { animation: fadein 0.6s 7.0s backwards; }
.act.is-running .qsw4-yellow  { animation: fadein 0.6s 7.8s backwards; }
.act.is-running .qsw4-white   { animation: fadein 0.6s 8.6s backwards; }

/* ---------------- ACT 5 : the slow part is your body ---------------- */
.act.is-running .qsw5-hdr    { animation: fadein 0.5s 0.1s backwards; }
.act.is-running .qsw5-s1     { animation: fadein 0.6s 0.4s backwards; }
.act.is-running .qsw5-ar1    { animation: fadein 0.4s 1.1s backwards; }
.act.is-running .qsw5-s2     { animation: fadein 0.6s 1.5s backwards; }
.act.is-running .qsw5-ar2    { animation: fadein 0.4s 2.2s backwards; }
.act.is-running .qsw5-s3     { animation: fadein 0.6s 2.6s backwards; }
.act.is-running .qsw5-ar3    { animation: fadein 0.4s 3.3s backwards; }
.act.is-running .qsw5-s4     { animation: fadein 0.6s 3.7s backwards; }
.act.is-running .qsw5-tl     { animation: fadein 0.5s 4.6s backwards; }
.act.is-running .qsw5-dot    { animation: fadein 0.35s backwards; }
.act.is-running .qsw5-n0     { animation-delay: 5.00s; }
.act.is-running .qsw5-n1     { animation-delay: 5.12s; }
.act.is-running .qsw5-n2     { animation-delay: 5.24s; }
.act.is-running .qsw5-n3     { animation-delay: 5.36s; }
.act.is-running .qsw5-n4     { animation-delay: 5.48s; }
.act.is-running .qsw5-n5     { animation-delay: 5.60s; }
.act.is-running .qsw5-n6     { animation-delay: 5.72s; }
.act.is-running .qsw5-n7     { animation-delay: 5.84s; }
.act.is-running .qsw5-tllab  { animation: fadein 0.5s 6.2s backwards; }
.act.is-running .qsw5-brace  { animation: fadein 0.5s 6.7s backwards; }
.act.is-running .qsw5-limits { animation: fadein 0.7s 7.2s backwards; }

/* ============================================================
   Call to Book

   The practice's phone line is a 24/7 receptionist that registers the
   client and books straight into Pabau, so a call is a booking path, not
   a contact channel. These rules give it booking-button weight in four
   places: the sticky mobile bar, the header, every .cta-band, and a
   reassurance line under the homepage's remaining Book buttons.

   Every call CTA is a plain <a href="tel:..."> -- /assets/analytics.js
   already reports each tap as generate_lead / phone_link from a delegated
   listener, and tells the placements apart by the anchor's id. Do not add
   click handlers or tracking code for these links; it would double-count.

   44px (2.75rem) minimum height on every new target. Text is --deep-slate
   or --ivory throughout: --muted on ivory is 4.4:1, under AA for body text.
   ============================================================ */

/* ---- Header ----------------------------------------------------------
   Outlined in --deep-slate rather than .btn-secondary's --soft-gray, so it
   reads as co-equal with Book Now rather than subordinate to it. The full
   label shows from 768px; below that the sticky bar carries it. */
.lp-header__call,
.site-header__call {
  min-height: 2.75rem;
  justify-content: center;
  border-color: var(--deep-slate);
  white-space: nowrap;
}
.lp-header__call-short,
.site-header__call-short { display: none; }

@media (max-width: 767.98px) {
  .lp-header__call-full,
  .site-header__call-full { display: none; }
  .lp-header__call-short,
  .site-header__call-short { display: inline; }
  .lp-header__call,
  .site-header__call { padding-left: 0.875rem; padding-right: 0.875rem; }
}

/* On site-header pages the button sits between the lockup and the Menu
   toggle, pushed right to sit beside it. */
.site-header__call { margin-left: auto; }

/* Lockup + Call + Menu is 304px wide at the default gap; a 320px screen has
   280px inside the gutters. */
@media (max-width: 359.98px) {
  .site-header__inner { gap: 0.75rem; }
}

/* Above 1080px the full nav row is showing and is already at its width
   limit, so the call CTA moves onto its own slim line above the row,
   right-aligned, as text rather than a boxed button. The ::before is a
   zero-height, full-width flex item that forces the wrap: call (order -2),
   then the break (order -1), then the lockup and nav on the row below. */
@media (min-width: 1080.02px) {
  .site-header__inner { flex-wrap: wrap; row-gap: 0; }
  .site-header__inner::before {
    content: "";
    order: -1;
    flex: 0 0 100%;
  }
  .site-header__call {
    order: -2;
    min-height: 2.75rem;
    margin: -0.625rem 0 -0.5rem auto;
    padding: 0;
    border: none;
    background: none;
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    color: var(--deep-slate);
  }
  .site-header__call:hover {
    background: none;
    text-decoration: underline;
    text-underline-offset: 0.25em;
  }
}

/* ---- CTA band ----------------------------------------------------------
   Two buttons of equal size. .btn-secondary's --soft-gray border vanishes
   against --ice-blue (1.3:1), so it takes the --deep-slate border here. */
.cta-band__actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
  max-width: 30rem;
}
@media (min-width: 480px) {
  .cta-band__actions { grid-template-columns: 1fr 1fr; }
}
.cta-band__actions > a {
  min-height: 2.75rem;
  justify-content: center;
  text-align: center;
}
.cta-band .btn-secondary { border-color: var(--deep-slate); }
.cta-band .btn-secondary:hover { background: var(--ivory); }

/* Tier 1 of "How booking works" (PAM spec 2026-09-27): one sentence under
   the band's Book Online. It replaced the band's "Answered 24/7" line, which
   said the same thing a second time. Deep slate, not --muted: --muted on
   ivory is 4.4:1, under AA for body text. */
.cta-note {
  margin: 1rem 0 0;
  max-width: 30rem;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--deep-slate);
}
.cta-note a {
  display: inline-block;
  padding: 0.75rem 0;
  margin: -0.75rem 0;
  font-weight: 600;
  color: inherit;
  white-space: nowrap;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* ---- Reassurance line ------------------------------------------------ */
.cta-reassure {
  margin: 0.875rem 0 0;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--deep-slate);
}
/* Padding with an equal negative margin grows the tap target to 44px
   without moving the line. */
.cta-reassure a {
  display: inline-block;
  padding: 0.75rem 0;
  margin: -0.75rem 0;
  font-weight: 600;
  color: inherit;
  white-space: nowrap;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* ---- Mobile call bar ---------------------------------------------------
   Shown below 768px only, as an ordinary block after the footer. It was
   fixed to the bottom of the viewport until the Pabau chat widget arrived:
   the widget's launcher owns the bottom-right corner, and a pinned bar
   under it would put Book Online beneath the chat bubble, so the bar was
   un-stuck by the practice owner's decision. The class and ids keep their
   "sticky" names so GA4's link_id (cta-call-sticky, cta-book-sticky) stays
   continuous across the change.
   The header and .cta-band still carry Call to Book on every page. */
.sticky-cta { display: none; }

@media (max-width: 767.98px) {
  .sticky-cta {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
    padding: 0.75rem var(--gutter) calc(0.75rem + env(safe-area-inset-bottom));
    background: var(--ivory);
    border-top: var(--rule);
  }

  /* /menu and /price-list have their own sticky Book bar. It stays hidden on
     phones: the chat launcher would sit on top of it, and the bar above
     carries the same href (utm tags included). */
  .menu-book { display: none; }
}

.sticky-cta__call,
.sticky-cta__book {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: 0 0.75rem;
  border: 1px solid var(--deep-slate);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
}
.sticky-cta__call { background: var(--deep-slate); color: var(--ivory); }
.sticky-cta__book { background: var(--ivory); color: var(--deep-slate); }

@media print {
  .sticky-cta { display: none; }
}

/* ============================================================
   Two ways to book (Tier 2, PAM spec 2026-09-27)

   Sits directly above the .cta-band on /lp/*, /treatments/* and /contact.
   Pabau's own "you're only booked once you see the confirmation page"
   message renders only on its category step, which every deep link skips,
   so the site says it before the visitor leaves. Reassurance, not a
   warning: the same rule-and-type treatment as the rest of the page, no
   colour, no box. The markup carries no classes on its headings (it is the
   spec's verbatim block), so they are styled from here to match .h2-serif
   and .h3.
   ============================================================ */
.booking-ways {
  border-top: var(--rule);
  padding-top: clamp(2rem, 4vw, 2.75rem);
  margin-top: clamp(2.5rem, 6vw, 4rem);
}
.booking-ways h2 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.15;
  font-size: clamp(1.625rem, 2.6vw, 2.125rem);
  margin: 0 0 1.5rem;
}
.booking-ways__item h3 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.25;
  font-size: 1.375rem;
  margin: 0 0 0.5rem;
}
.booking-ways__item p {
  font-size: 1rem;
  line-height: 1.65;
  margin: 0;
  max-width: var(--measure);
}
.booking-ways__item + .booking-ways__item { margin-top: 1.5rem; }
@media (min-width: 720px) {
  .booking-ways { display: grid; grid-template-columns: 1fr 1fr; column-gap: clamp(2rem, 5vw, 4rem); }
  .booking-ways h2,
  .booking-ways__note { grid-column: 1 / -1; }
  .booking-ways__item + .booking-ways__item { margin-top: 0; }
}
.booking-ways__item a {
  display: inline-block;
  padding: 0.625rem 0;
  margin: -0.625rem 0;
  font-weight: 600;
  color: var(--deep-slate);
  white-space: nowrap;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.booking-ways__note {
  margin: 1.5rem 0 0;
  max-width: var(--measure);
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--deep-slate);
}

/* "Directions" under the footer address. --deep-slate rather than the
   address's --muted, which is 4.4:1 on ivory; padded to a 44px target
   without moving the line. */
.footer-map {
  display: inline-block;
  padding: 0.6875rem 0;
  margin: -0.6875rem 0;
  color: var(--deep-slate);
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

/* A DOI link is one unbreakable string; on a 320px phone the longest
   (doi:10.1001/archderm.1988.01670060015008) ran past the viewport and
   scrolled the whole page sideways. Let reference links break anywhere. */
.wdo-refs a { overflow-wrap: anywhere; }

/* Pabau lead-capture form on the home page. The styling lives here and not in
   a style attribute on the <iframe>, because style-src carries no
   'unsafe-inline': an attribute would be stripped and the frame would fall
   back to the browser default 300x150 with a border. Pabau's own snippet
   supplies exactly that attribute, so it has to be translated, not pasted.

   The height is fixed rather than fitted to the content. We cannot measure a
   cross-origin document, and a frame that resizes after load moves the page
   under whoever is reading it -- the fault 6f03f8b traced to the last Pabau
   embed on this page. A declared height reserves the space before the frame
   loads, so a lazy frame costs no layout shift. If Pabau's form grows past it
   the frame scrolls internally, which is survivable; the page jumping is not.

   920px, not the 500px Pabau's snippet suggests. Measured against the live form
   at 375px and 1009px: seven fields (name, email, phone, a free-text box, a
   treatment selector, the email opt-in and Submit) need ~900px, and at 500px
   the Submit button sits below an internal scrollbar where nobody will find it.
   One value covers every width because Pabau renders the form as a fixed-width
   column that does not reflow -- so there is no breakpoint to write.

   Re-measure this if the form's fields change. Nothing here can detect that:
   the document is cross-origin, so a form that outgrows this box fails
   silently, and the only symptom is a scrollbar inside the frame. */
.lead-embed {
  display: block;
  width: 100%;
  height: 920px;
  border: 0;
}

/* Parks the Pabau chat launcher while the lead form is on screen; the class is
   toggled by assets/chat-clearance.js. See that file for why.

   TWO selectors, because Pabau renders the widget as two unrelated things:

     [data-pabau-engage]        the launcher. A 0x0 absolutely positioned div at
                                the end of <body> with an OPEN SHADOW ROOT; the
                                pill and its greeting are a <button> and a <div>
                                inside that root, positioned from the shadow's
                                own <style>. This is the part that sits on the
                                form.
     iframe[title="Pabau chat"] the conversation panel, a separate body-level
                                iframe.

   The first version of this rule had only the iframe, which is 0x0 until the
   panel is opened -- so every measurement said visibility:hidden and the pill
   stayed on screen regardless. Nothing in the DOM at body level gave it away:
   the launcher is invisible to querySelectorAll('iframe') and to a sweep for
   high z-index elements, because the host is 0x0 and everything with a size is
   inside the shadow root.

   visibility, not display: visibility is an inherited property and crosses the
   shadow boundary, so hiding the host hides the button inside it (verified in
   production -- the shadow <button> computes visibility:hidden). display:none
   also works but collapses the host, and the panel iframe beside it can reload
   when it comes back.

   !important is load bearing. Pabau writes an enormous inline style on that
   host, visibility among it, and an author !important declaration is the only
   thing that outranks an inline one -- which is what lets this work without
   editing pabau-chat.js, whose injected style strings are pinned by sha256 in
   _headers.

   If Pabau drops the data attribute or renames the title, the rule stops
   matching and the launcher stays put: today's behaviour, no breakage. */
.chat-stand-down [data-pabau-engage],
.chat-stand-down iframe[title="Pabau chat"] {
  visibility: hidden !important;
  pointer-events: none !important;
}
