/* =========================================================================
   The MLD Register - main.css
   ONE source-of-truth stylesheet. Design tokens first, then base,
   components, utilities, page-specific sections. All style edits go here.

   Identity: "The Living Ledger" (see DESIGN.md). Committed bottle-green on
   warm bone paper with aged-brass detail; Source Serif 4 display over
   Hanken Grotesk body; ledger-entry captions, index numbering and the
   scalloped verification seal as signature motifs. Mobile-first.
   ========================================================================= */

/* -------------------------------------------------------------------------
   1. Design tokens
   ------------------------------------------------------------------------- */
:root {
  /* Palette (OKLCH; neutrals tinted toward the brand hues, never #000/#fff) */
  --ink: oklch(0.24 0.02 165);
  --ink-soft: oklch(0.42 0.02 165);
  --ink-mute: oklch(0.50 0.016 165); /* >=4.5:1 on paper, paper-2 and green-wash */
  --paper: oklch(0.972 0.008 95);
  --paper-2: oklch(0.943 0.012 95);
  --surface: oklch(0.99 0.004 95);
  /* Filter-panel cards. Rick's specified value (25 Jul 2026), kept as the
     authored hex so it is exactly the colour he picked; it sits in the same
     warm hue-95 family as paper/surface, a shade lighter than paper-2 so the
     cards lift off the panel behind them. */
  --filter-card: #fcfaf1;
  --line: oklch(0.9 0.012 110);
  --line-strong: oklch(0.8 0.02 110);
  --green: oklch(0.36 0.055 165);
  --green-deep: oklch(0.27 0.045 168);
  --green-wash: oklch(0.93 0.02 160);
  /* Brass is context-split (Rick, 17 Aug 2026): #DFA33A on light surfaces,
     #E4B349 on the dark jade bands. Rules INSIDE dark containers
     (.section-green, .site-footer, .cta-band) use --brass-on-dark; brand
     theme.css files that retune --brass must retune --brass-on-dark too.
     --brass-deep stays a darkened text-safe tone (white-on-brass buttons
     need >=4.5:1, which the display brass cannot give). */
  --brass: #DFA33A;
  --brass-on-dark: #E4B349;
  --brass-deep: oklch(0.52 0.085 80);
  --brass-soft: #D3AE71;
  --verified: oklch(0.5 0.09 160);
  --verified-wash: oklch(0.94 0.025 160);
  --danger: oklch(0.5 0.13 25);
  --danger-wash: oklch(0.95 0.02 25);
  --focus: oklch(0.55 0.13 250);

  /* On-green (light text on the committed colour needs +line-height too) */
  --on-green: oklch(0.96 0.01 120);
  --on-green-soft: oklch(0.83 0.02 140);

  /* Typography */
  --font-serif: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --font-sans: 'Hanken Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI',
               Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* Rhythm */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: clamp(3.5rem, 8vw, 6rem);
  --radius: 4px;
  --radius-lg: 12px;
  --shadow-1: 0 1px 2px oklch(0.27 0.045 168 / 0.07), 0 1px 8px oklch(0.27 0.045 168 / 0.05);
  --shadow-2: 0 2px 8px oklch(0.27 0.045 168 / 0.09), 0 12px 32px oklch(0.27 0.045 168 / 0.08);
  /* 80rem = 1280px, the common modern page shell (Tailwind's 7xl; Bootstrap
     sits either side at 1140 and 1320). The old 74rem was undocumented and
     inside the normal range, but it was costing the one page that needs the
     room: almost all the extra goes to the finder's results column, because
     the map beside it is a fixed 22rem. Prose is unaffected either way -
     .prose, guides and forms cap themselves at 46-52rem and every paragraph
     at 70ch, so this widens listings and admin tables, not reading measure. */
  --container: 80rem;
  /* 60rem = 960px. The middle shell, for a long FORM that is neither a
     listing nor prose: the concierge profile preview, where 46rem squeezed
     paired fields (phone + second phone, fee label + minutes + price) into
     columns too narrow to read while 80rem left a single-column form
     stranded across 1280px. Rick, 16 Aug 2026, reviewing his own profile.
     Paragraphs still cap at 70ch, so this widens the CONTROLS, not the
     reading measure. */
  --container-medium: 60rem;
  --container-narrow: 46rem;
  --ease-out: cubic-bezier(0.165, 0.84, 0.44, 1); /* ease-out-quart */

  /* Component tones: every literal a component rule needs, hoisted so a
     per-brand theme.css can re-skin without touching component CSS.
     Values are the engine's Living Ledger defaults. */
  /* Dark-jade header (18 Aug 2026 rebuild). The band is the brand's own
     deep bottle green - the footer's colour, so the page sits inside one
     frame. Heights per the approved spec: 86px desktop resting, 66px
     scrolled; 64px/58px on a phone. --header-offset is what OTHER sticky
     elements (finder bar, sticky map, profile rail) add to their top so
     they park under the pinned compact header, and what scroll-padding
     uses so anchor jumps never land hidden behind it. */
  --header-bg: var(--green-deep);
  --header-ink: var(--on-green);
  --header-ink-soft: var(--on-green-soft);
  --header-accent: var(--brass-on-dark);
  --header-cta-bg: #DCA23A;
  --header-cta-ink: var(--green-deep);
  --header-h: 5.375rem;
  --header-h-scrolled: 4.125rem;
  --header-offset: var(--header-h-scrolled);
  --link-underline: oklch(0.62 0.09 85 / 0.75);
  --on-green-hairline: oklch(0.96 0.01 120 / 0.16);
  --on-green-hairline-strong: oklch(0.96 0.01 120 / 0.18);
  --on-green-mute: oklch(0.72 0.02 150);
  --btn-secondary-border: oklch(0.36 0.055 165 / 0.45);
  --off-white: oklch(0.99 0.004 95);
  --tone-danger-border: oklch(0.7 0.08 25);
  --tone-danger-ink: oklch(0.38 0.12 25);
  --tone-success-border: oklch(0.65 0.08 160);
  --tone-success-ink: oklch(0.35 0.08 160);
  --tone-verified-border: oklch(0.6 0.08 160);
  --tone-verified-ink: oklch(0.35 0.09 160);
  --tone-neutral-border: oklch(0.55 0.06 165);
  --tone-warn-ink: oklch(0.42 0.08 80);
  --notice-border: oklch(0.7 0.04 160);
  --search-keyline: #92afa4;
  --filter-ink: #727272;
  --filter-line: #a8a8a8;
  --cta-ring: oklch(0.62 0.09 85 / 0.35);
  --cta-ring-soft: oklch(0.62 0.09 85 / 0.2);
  --cta-outline: oklch(0.96 0.01 120 / 0.5);
  --cta-outline-wash: oklch(0.96 0.01 120 / 0.1);
  --radius-stamp: 3px;
  --flourish-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20360%20240'%3E%3Cg%20fill='none'%20stroke='%23F2EFE4'%20stroke-width='2.5'%20stroke-linecap='round'%3E%3Cpath%20d='M-10,120C60,70,140,170,220,120S350,70,380,110'/%3E%3Cpath%20d='M-10,150C60,100,140,200,220,150S350,100,380,140'/%3E%3Cpath%20d='M-10,180C60,130,140,230,220,180S350,130,380,170'/%3E%3C/g%3E%3C/svg%3E");
  --select-chevron: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23727272' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
}

/* -------------------------------------------------------------------------
   2. Font faces (self-hosted variable files, exactly two - no CDN)
   ------------------------------------------------------------------------- */
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/source-serif-4-var.woff2') format('woff2');
}

@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/hanken-grotesk-var.woff2') format('woff2');
}

/* -------------------------------------------------------------------------
   3. Reset and base
   ------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/* The header is sticky, so every anchor jump, :target and scrollIntoView
   must clear it or the landing content starts hidden under the band. */
html { scroll-padding-top: calc(var(--header-offset) + var(--space-4)); }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 1.125rem;
  line-height: 1.65;
  color: var(--ink);
  background: var(--paper);
  text-rendering: optimizeLegibility;
}

/* .shot-title and .shot-heading are headings by APPEARANCE ONLY, for the
   laptop product shot in section 13c. A shot is a picture of another page,
   so real h1/h2/h3 tags inside it would put a second h1 and a run of
   out-of-order sections into the outline of the page carrying the picture.
   The specimen uses paragraphs and borrows the styling - the same trick, for
   the same reason, as .preview-frame .result-name. Never use either class
   outside a shot: a real heading must be a real heading. */
h1, h2, h3, h4, .shot-title, .shot-heading {
  font-family: var(--font-serif);
  font-weight: 600;
  line-height: 1.14;
  color: var(--ink);
  margin: 0 0 var(--space-4);
  letter-spacing: -0.015em;
  text-wrap: balance;
}

h1 { font-size: clamp(2.2rem, 5vw, 3.4rem); }
h2 { font-size: clamp(1.6rem, 3.2vw, 2.3rem); margin-top: var(--space-6); }
h3 { font-size: clamp(1.25rem, 2vw, 1.5rem); margin-top: var(--space-5); }
h4 { font-size: 1.1rem; }

p { margin: 0 0 var(--space-4); max-width: 70ch; text-wrap: pretty; }
li { text-wrap: pretty; }

a {
  color: var(--green);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2.5px;
  text-decoration-color: var(--link-underline);
  transition: color 160ms var(--ease-out), text-decoration-color 160ms var(--ease-out);
}

a:hover { color: var(--green-deep); text-decoration-color: currentColor; }

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 2px;
}

img { max-width: 100%; height: auto; display: block; }

ul, ol { padding-left: 1.4rem; margin: 0 0 var(--space-4); }
li { margin-bottom: var(--space-2); }

hr { border: 0; border-top: 1px solid var(--line); margin: var(--space-6) 0; }

blockquote {
  margin: var(--space-5) 0;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-soft);
  font-family: var(--font-serif);
  font-style: italic;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 var(--space-5);
  font-size: 1.1rem;
}

th, td {
  text-align: left;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}

th {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-soft);
  border-bottom: 2px solid var(--brass);
}

::selection { background: var(--brass-soft); color: var(--ink); }

/* -------------------------------------------------------------------------
   4. Layout primitives
   ------------------------------------------------------------------------- */
.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

.container-medium {
  max-width: var(--container-medium);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

.container-narrow {
  max-width: var(--container-narrow);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

.section { padding-block: var(--space-8); }
.section-tint { background: var(--paper-2); }
/* White, for a band that must separate itself from the paper above it (the
   homepage's "Why verification matters", which otherwise ran straight on
   from the hero). */
.section-surface { background: var(--surface); }
/* Rick's specified shell tone for the For Therapists badge/certificate band
   (18 Aug 2026), kept as the authored hex so it is exactly the colour he
   picked - the same warm family as paper-2, a shade deeper. */
.section-shell { background: #EEECE3; }
/* Collapse the top padding when a section follows another of the SAME
   background (e.g. two paper sections in a row), so the shared background
   does not read as one enormous empty gap. Applied per instance, never
   blanket, because colour-banded sections need their own top padding. */
.section-flush-top { padding-block-start: 0; }
.section-green {
  background: var(--green-deep);
  color: var(--on-green-soft);
}
.section-green h2, .section-green h3 { color: var(--on-green); }
.section-green p { line-height: 1.75; }
.section-green a { color: var(--on-green); text-decoration-color: var(--brass-on-dark); }
.section-green a:hover { color: var(--brass-soft); }
.section-green .text-muted { color: var(--on-green-soft); }

/* Quiet corner flourish: the logo's flow-wave lines, whisper-faint in the
   bottom-right of a green band. Opt-in per section via .section-flourish. */
.section-flourish { position: relative; overflow: hidden; }

.section-flourish > .container { position: relative; z-index: 1; }

.section-flourish::after {
  content: '';
  position: absolute;
  right: -5rem;
  bottom: -3rem;
  width: 30rem;
  height: 20rem;
  background: var(--flourish-image) no-repeat;
  background-size: contain;
  opacity: 0.12;
  pointer-events: none;
}

/* Light surfaces inside a green section keep the ink palette */
.section-green .verification-box,
.section-green .card { color: var(--ink-soft); }
.section-green .verification-box h2, .section-green .verification-box h3,
.section-green .card h2, .section-green .card h3 { color: var(--ink); }
.section-green .verification-box a:not(.chip),
.section-green .card a:not(.chip) { color: var(--green); text-decoration-color: var(--link-underline); }
.section-green .verification-box a:not(.chip):hover,
.section-green .card a:not(.chip):hover { color: var(--green-deep); }
.section-green .card .text-muted { color: var(--ink-mute); }
/* The featured card's Verified stamp is a plain .chip like every other one on
   the site now; keep its ink readable against the card on the green band. */
.section-green .card a.chip { color: var(--ink-soft); }

.section-lead {
  max-width: 44rem;
  color: var(--ink-soft);
  font-size: 1.2rem;
  font-family: var(--font-serif);
  line-height: 1.6;
}

.section-green .section-lead { color: var(--on-green-soft); }

.grid-2, .grid-3, .grid-4 {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
}

@media (min-width: 640px) {
  .grid-2 { grid-template-columns: repeat(2, 1fr); }
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  /* Media column on the LEFT. A visual swap, not a DOM reorder, so the single
     phone column still reads heading, argument, photograph. */
  .grid-2-media-first > :first-child { order: 2; }
  .grid-2-media-first > :nth-child(2) { order: 1; }
}
/* A two-column grid that stays one column until 960px, for a column holding
   something wide such as the profile specimen card (/how-we-differ, 23 Sep 2026:
   at about 740px the specimen's chips pushed the page sideways). */
@media (min-width: 640px) and (max-width: 959.98px) {
  .grid-2.grid-2-late { grid-template-columns: 1fr; }
}

/* The "Why verification matters" media half: one picture from 640px up, the
   same three claims as REAL TEXT below it. The reasoning, and the measurements
   that forced it, are in brands/mld/views/pages/home.php. Only one of the two
   is ever in the document flow, so neither can be read twice. */
.why-infographic { display: none; }

.why-checks {
  display: grid;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.why-checks > li {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}

/* .ledger-photo img above sets width:100% with a shadow, which is right for a
   photograph and wrong for a 56px line drawing. Same specificity, so this wins
   on source order - keep it below that rule. */
.ledger-photo .why-checks img {
  width: 56px;
  height: 56px;
  border-radius: 0;
  box-shadow: none;
}

.why-checks h3 { margin: 0 0 var(--space-2); font-size: 1.05rem; }
.why-checks p { margin: 0; max-width: none; }

@media (min-width: 640px) {
  .why-infographic { display: block; }
  .why-checks { display: none; }
  /* EXCEPT where the infographic was not rendered at all (31 Aug 2026). Panel
     3 of that artwork has "Massage therapists are not regulated by law in the
     UK" BAKED INTO THE PIXELS, which no template can edit, so a country where
     that is untrue gets no infographic and these cards carry the band at every
     width instead. The class is set in home.php beside the same condition that
     drops the image, so the two can never disagree. */
  .why-checks-always { display: grid; }
}

/* A band whose media half is an image with TEXT BAKED INTO IT holds off the
   two-column split until 960px. In a half-width column at 768px the image is
   only ~330px wide - NARROWER than the ~360px it gets in the single phone
   column - so the normal 640px split makes such an image least readable on
   exactly the device that most needs it readable. One opt-in class, because
   this is wrong for every band whose media half is a photograph. */
@media (max-width: 959.98px) {
  .grid-2.infographic-band { grid-template-columns: 1fr; }
}

@media (min-width: 960px) {
  .grid-3 { grid-template-columns: repeat(3, 1fr); }
  .grid-4 { grid-template-columns: repeat(4, 1fr); }
}

/* -------------------------------------------------------------------------
   5. Header
   ------------------------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--green-deep);
  color: var(--on-green);
  padding: var(--space-3) var(--space-5);
  z-index: 700; /* above the sticky header (600) */
}

.skip-link:focus { left: 0; color: var(--on-green); }

/* Site header, rebuilt 18 Aug 2026 (report section 5): one full dark-jade
   band, sticky at the top of the viewport. The founding strip (.notice-bar)
   above it stays in normal flow and scrolls away; main.js adds .is-scrolled
   past ~90px of scroll and the band compacts (86px -> 66px desktop,
   64px -> 58px phone), animated below unless the visitor prefers reduced
   motion. z-index 600 beats the finder bar (500); the skip link sits above
   both. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 600;
  background: var(--header-bg);
  color: var(--header-ink);
  border-bottom: 1px solid var(--on-green-hairline);
}

/* The compact state earns a whisper of shadow so the pinned band separates
   from content sliding under it; resting at page top it has none. */
.site-header.is-scrolled { box-shadow: 0 2px 10px oklch(0.27 0.045 168 / 0.25); }

.site-header-grid {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  height: var(--header-h);
  transition: height 200ms var(--ease-out);
}

.site-header.is-scrolled .site-header-grid { height: var(--header-h-scrolled); }

.wordmark { display: inline-flex; align-items: center; flex: none; }

/* Cream-on-dark lockup, mark + wordmark only (the strapline lives in the
   footer sign-off where it has room to be read). Width is the animated
   dimension; height follows the artwork's aspect. */
.wordmark-lockup {
  display: block;
  width: 255px;
  max-width: 100%;
  height: auto;
  transition: width 200ms var(--ease-out);
}

.site-header.is-scrolled .wordmark-lockup { width: 210px; }

.footer-brand img { max-width: min(420px, 100%); height: auto; }

/* Primary nav: pushed right against the utility cluster. */
.site-nav { margin-left: auto; }

.nav-list {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav-list li { margin: 0; }

/* Shared item typography (links and dropdown triggers alike). The 2px
   transparent bottom border is the gold-underline slot: 8px below the text
   via padding, revealed on hover/active, never a filled pill.

   NOWRAP, 31 Aug 2026. A nav label is a name, not prose: "Find a Therapist"
   broken across two lines reads as a cramped mistake and made the row two
   different heights. It happened because these are flex items with no
   nowrap, so once the row ran short of space the browser shrank them and
   wrapped the words INSIDE each item rather than letting the whole menu
   wrap - the flexible spend it found first. Both countries did it, the UK
   sooner because it carries a sixth item (Guides) the US does not.
   Wrapping the words is never the right answer here: the squeeze
   breakpoints below buy the room instead, and below 1024px the phone menu
   takes over. Rick's standing ruling is that a menu WRAPS and never scrolls
   sideways, so every width was measured for overflow, not just for looks. */
.nav-item > a,
.nav-sub-toggle {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  gap: 0.4rem;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: var(--space-2) 0;
  font: inherit;
  font-size: 0.97rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--header-ink);
  text-decoration: none;
  cursor: pointer;
  transition: color 160ms, border-color 160ms;
}

.nav-item > a:hover,
.nav-sub-toggle:hover,
.nav-item.is-active > a,
.nav-item.is-active > .nav-sub-toggle {
  color: var(--off-white);
  border-bottom-color: var(--header-accent);
}

/* Focus on the dark band: the site's blue focus ring drowns in jade, so
   header controls carry a gold ring instead. */
.nav-item > a:focus-visible,
.nav-sub-toggle:focus-visible,
.nav-sub a:focus-visible,
.header-login:focus-visible,
.header-cta:focus-visible,
.nav-toggle:focus-visible {
  outline: 2px solid var(--header-accent);
  outline-offset: 3px;
}

.nav-chevron { flex: none; transition: transform 160ms; }

.has-children.open > .nav-sub-toggle .nav-chevron { transform: rotate(180deg); }

/* Desktop utility cluster: a hairline separates the log-in link (quiet text
   utility) and the one persistent primary button from the content nav. */
.site-header-utility {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-left: var(--space-5);
  padding-left: var(--space-5);
  border-left: 1px solid var(--on-green-hairline-strong);
}

.header-login {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--header-ink);
}

/* The log-out icon (partial icon_logout) scales with its link's text and
   never shrinks it. */
.icon-logout { width: 1em; height: 1em; flex: none; }

.header-login:hover,
.header-login[aria-current="page"] {
  color: var(--off-white);
  text-decoration: underline;
  text-decoration-color: var(--header-accent);
  text-underline-offset: 0.3em;
}

/* The only persistent primary button in the header: cream fill, jade text,
   46px resting and 40px in the compact state. Hover warms toward gold
   without losing the dark-on-light contrast. */
.header-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2.875rem;
  padding-inline: 1.625rem;
  border-radius: var(--radius);
  background: var(--header-cta-bg);
  color: var(--header-cta-ink);
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition: background 160ms, height 200ms var(--ease-out);
}

.header-cta:hover { background: #c98f28; color: var(--header-cta-ink); }

.site-header.is-scrolled .header-cta { height: 2.5rem; }

/* Burger (phone only; desktop hides it below). 44px target. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-left: auto;
  background: none;
  border: 0;
  border-radius: var(--radius);
  color: var(--header-ink);
  cursor: pointer;
}

.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  display: block;
  width: 20px;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  position: relative;
  content: '';
  transition: transform 160ms, background 160ms;
}

.nav-toggle-bars::before { position: absolute; top: -6px; }
.nav-toggle-bars::after { position: absolute; top: 6px; }

/* Open state: the bars become the X (spec: burger swaps for a close icon). */
.nav-toggle[aria-expanded="true"] .nav-toggle-bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bars::after { transform: translateY(-6px) rotate(-45deg); }

/* ------------------------------ desktop ------------------------------- */
@media (min-width: 1024px) {
  /* Panel-only slots stay out of the desktop row. */
  .nav-panel-cta,
  .nav-panel-foot { display: none; }

  .has-children { position: relative; }

  /* Dropdown: cream panel, jade text, 10px under the trigger, restrained
     shadow. Hidden until main.js opens it (hover, click or keyboard); the
     no-JS fallback below shows it on :hover/:focus-within with no offset
     gap, so a pointer can always reach it. */
  .nav-sub {
    display: none;
    position: absolute;
    top: calc(100% + 10px);
    left: 50%;
    transform: translateX(-50%);
    min-width: 240px;
    margin: 0;
    padding: 8px;
    list-style: none;
    background: var(--surface);
    color: var(--green-deep);
    border-radius: 10px;
    box-shadow: 0 2px 6px oklch(0.27 0.045 168 / 0.12), 0 12px 28px oklch(0.27 0.045 168 / 0.18);
    z-index: 10;
  }

  .js .has-children.open > .nav-sub { display: block; }

  html:not(.js) .has-children:hover > .nav-sub,
  html:not(.js) .has-children:focus-within > .nav-sub {
    display: block;
    top: 100%;
  }

  .nav-sub a {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0.4rem 0.9rem;
    border-radius: 7px;
    color: var(--green-deep);
    font-size: 0.95rem;
    font-weight: 600;
    text-decoration: none;
  }

  .nav-sub a:hover,
  .nav-sub a:focus-visible,
  .nav-sub a[aria-current="page"] { background: var(--green-wash); }

  /* Dropdown links sit on the CREAM panel, not the dark band: the gold focus
     ring the shared rule gives them is ~1.9:1 on cream and fails WCAG 1.4.11.
     The blue focus ring is the site's default and reads clearly here. The
     trigger itself stays gold - it lives on the dark band. */
  .nav-sub a:focus-visible { outline-color: var(--focus); }

  /* The join action inside a dropdown separates from the informational
     links with a hairline (partial marks it nav-sub-cta). */
  .nav-sub .nav-sub-cta { border-top: 1px solid var(--line); margin-top: 4px; padding-top: 4px; }
  .nav-sub .nav-sub-cta a { font-weight: 700; }
}

/* Tablet-to-small-desktop squeeze (1024-1199px): the full row must fit
   without wrapping, so the flexible spends shrink first - never the item
   count, never below-readable text. */
@media (min-width: 1024px) and (max-width: 1199.98px) {
  .wordmark-lockup { width: 204px; }
  .site-header.is-scrolled .wordmark-lockup { width: 190px; }
  .nav-list { gap: var(--space-3); }
  .site-header-utility { gap: var(--space-3); margin-left: var(--space-3); padding-left: var(--space-3); }
  .header-cta { padding-inline: 0.9rem; }
  .nav-item > a, .nav-sub-toggle { font-size: 0.9rem; }
}

/* Small-desktop squeeze (1200-1279px), added 31 Aug 2026. This band had no
   rules at all and did not need any while nav labels were allowed to wrap
   inside themselves. Now they hold one line, the row is 26px wider than the
   space here, so the same flexible spends give it back - a milder dose than
   the band above, which is 176px narrower. Measured, not guessed: with the
   labels on one line the UK row overflowed at 1200px and 1024px and fitted
   at 1280px, so those are the two bands that needed buying and 1280 up is
   left alone. */
@media (min-width: 1200px) and (max-width: 1279.98px) {
  .wordmark-lockup { width: 216px; }
  .site-header.is-scrolled .wordmark-lockup { width: 198px; }
  .nav-list { gap: var(--space-4); }
  .site-header-utility { gap: var(--space-4); margin-left: var(--space-4); padding-left: var(--space-4); }
  .header-cta { padding-inline: 1rem; }
  .nav-item > a, .nav-sub-toggle { font-size: 0.92rem; }
}

/* ------------------------------- phone -------------------------------- */
@media (max-width: 1023.98px) {
  :root {
    --header-h: 4rem;
    --header-h-scrolled: 3.625rem;
    --header-offset: 3.625rem;
  }

  .site-header-grid { gap: var(--space-3); }

  .wordmark-lockup { width: 192px; }
  .site-header.is-scrolled .wordmark-lockup { width: 176px; }

  /* THE 29px SIDEWAYS SCROLL, fixed 1 Sep 2026, and the fix is about WHICH
     item gives way rather than about hiding anything.

     Measured on /about at a 320px viewport before the fix: clientWidth 320,
     scrollWidth 349. The phone row asks for 192 (lockup) + 12 + 73 (country
     switcher) + 12 + 8 + 44 (burger) = 341px inside a 280px content box, so
     it is 61px short. The lockup carried `flex: none`, so the browser took
     the shortfall out of the only item that WOULD shrink - the burger, which
     was rendering 32px wide instead of its designed 44px. So the overflow
     and a sub-minimum finger target were the same bug.

     Two rules settle it, and no page needs overflow-x: hidden (which would
     hide the symptom and break position: sticky):
       - the burger never shrinks. 44px is a finger target, not a preference.
       - the lockup is the one item allowed to shrink, so the row can never
         be wider than its container whatever else lands in it (a longer
         country label, a third control). It scales on its own aspect
         because the img is width + max-width: 100% + height: auto.
     Deliberately no min-width floor on the lockup: a floor is a number that
     goes stale the moment anything beside it changes width. */
  .nav-toggle { flex: none; }
  .wordmark { flex: 0 1 auto; min-width: 0; }

  /* Collapsed phone header is logo + burger ONLY (spec 5D): the login and
     CTA live inside the menu panel instead. */
  .site-header-utility { display: none; }

  .js .nav-toggle { display: inline-flex; }

  /* The open menu: a full-width warm off-white panel pinned directly under
     the header (main.js measures the header's bottom edge into
     --nav-panel-top at open), scrollable, body scroll locked behind it. */
  .js .site-nav {
    display: none;
    position: fixed;
    top: var(--nav-panel-top, var(--header-h));
    left: 0;
    right: 0;
    bottom: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    background: var(--paper);
    color: var(--ink);
    padding: var(--space-5) var(--space-5) var(--space-7);
    z-index: 590;
  }

  .js .site-nav.open { display: block; }

  /* Without JS there is no burger, so the nav renders inline in the band,
     wrapped onto its own rows; the grid's fixed height gives way so
     nothing clips. */
  html:not(.js) .site-header-grid { height: auto; min-height: var(--header-h); flex-wrap: wrap; padding-block: var(--space-3); }
  html:not(.js) .site-nav { flex-basis: 100%; }
  html:not(.js) .nav-panel-cta,
  html:not(.js) .nav-panel-foot { padding-block: var(--space-2); }

  .nav-panel-cta { margin-bottom: var(--space-4); }

  /* Full-width on the panel, but the same gold Apply to join button as the
     desktop header - one brand action colour everywhere, and gold reads
     clearly on the warm off-white panel. */
  .nav-panel-cta .header-cta {
    display: flex;
    width: 100%;
    height: 3rem;
  }

  /* Panel colours: jade text on the warm panel (scoped to .js so the no-JS
     in-band nav keeps the band's light-on-dark palette). */
  .js .nav-list { display: block; }

  /* No JS: the nav renders inline in the band. Stack the items instead of a
     nowrap flex row, which would run off a 320-430px screen and scroll the
     page sideways (the house rule: menus wrap, never scroll). Desktop is
     unaffected - this only fires below 1024px. */
  html:not(.js) .nav-list { display: block; }

  .js .nav-item { border-bottom: 1px solid var(--line); }
  /* The foot below the list draws its own top rule (.nav-panel-foot), so the
     last item's bottom rule made a doubled line with an empty band between
     (browser review, 22 Sep 2026). One rule, the foot's. */
  .js .nav-list > .nav-item:last-child { border-bottom: 0; }

  .js .nav-item > a,
  .js .nav-sub-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    min-height: 54px;
    padding: 0.6rem var(--space-2);
    border-bottom: 0;
    color: var(--green-deep);
    font-size: 1.05rem;
  }

  /* One restrained active marker: the gold left rule. */
  .js .nav-item.is-active > a,
  .js .nav-item.is-active > .nav-sub-toggle {
    box-shadow: inset 3px 0 0 var(--brass);
    padding-left: 0.85rem;
    color: var(--green-deep);
  }

  .js .nav-item > a:hover,
  .js .nav-sub-toggle:hover { color: var(--green); }

  .js .nav-item > a:focus-visible,
  .js .nav-sub-toggle:focus-visible,
  .js .nav-sub a:focus-visible,
  .js .nav-panel-foot .header-login:focus-visible,
  .js .nav-panel-cta .header-cta:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
  }

  /* Accordion children: indented, quieter, 46px targets. Closed until the
     trigger opens them; always open without JS. */
  .js .nav-sub {
    display: none;
    margin: 0;
    padding: 0 0 var(--space-3);
    list-style: none;
  }

  .js .has-children.open > .nav-sub { display: block; }

  html:not(.js) .nav-sub { list-style: none; margin: 0; padding-left: var(--space-4); }

  /* The panel already leads with Apply to join, so the accordion never
     repeats it (spec 5F) - the /join child is desktop-dropdown only. */
  .nav-sub .nav-sub-cta { display: none; }

  .js .nav-sub a {
    display: flex;
    align-items: center;
    min-height: 46px;
    padding: 0.35rem var(--space-2) 0.35rem 1.1rem;
    color: var(--ink-soft);
    font-size: 0.98rem;
    font-weight: 600;
    text-decoration: none;
  }

  .js .nav-sub a:hover { color: var(--green-deep); }

  .js .nav-sub a[aria-current="page"] { color: var(--green-deep); font-weight: 700; }

  /* Member login separated at the panel foot. */
  .nav-panel-foot {
    margin-top: var(--space-5);
    padding-top: var(--space-5);
    border-top: 1px solid var(--line-strong);
  }

  .js .nav-panel-foot .header-login {
    display: flex;
    align-items: center;
    min-height: 48px; /* was a ~25px text-line target, half the other rows */
    color: var(--green-deep);
    font-size: 1rem;
  }

  /* Body scroll locks while the menu is open (class set by main.js). */
  html.nav-locked, html.nav-locked body { overflow: hidden; }
}

@media (prefers-reduced-motion: reduce) {
  .site-header-grid,
  .wordmark-lockup,
  .header-cta,
  .nav-chevron,
  .nav-toggle-bars,
  .nav-toggle-bars::before,
  .nav-toggle-bars::after { transition: none; }
}

/* Founding announcement strip (operator setting notice_bar_text; empty =
   hidden). One line, never a multi-line banner: past the fit it ellipsises
   rather than wraps (spec 5A/5D). It sits above the sticky header in
   normal flow, so it scrolls away first. */
.notice-bar {
  background: #254338;
  color: var(--on-green);
  border-bottom: 1px solid var(--on-green-hairline);
}

.notice-bar p {
  max-width: none; /* centred <p> gotcha: the base 70ch cap pins it left */
  margin: 0;
  padding-block: 0.3rem;
  text-align: center;
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1.4;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.notice-bar a { color: inherit; }

/* Full line on tablet/desktop, short line on phones. Only one is ever in
   flow, so the strip stays one line. The short variant is allowed to wrap
   to a second line on the very narrowest phones rather than clip - it is
   short by design, so it never becomes a tall banner, and never losing the
   deadline beats a tidy ellipsis. */
.notice-bar-short { display: none; }
@media (max-width: 700px) {
  /* p.notice-bar-* to outrank the base .notice-bar p (nowrap+ellipsis):
     a lower-specificity class alone loses to it and the short line clips. */
  .notice-bar p.notice-bar-full { display: none; }
  .notice-bar p.notice-bar-short {
    display: block;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
}

/* -------------------------------------------------------------------------
   5C. Country controls (Phase 1 step 5, 29 Aug 2026)

   Two things, both of which render NOTHING while one country is live, so on
   a single-country brand not one byte of this ever reaches the page:

   .country-switch  the manual switcher, in the header and in the footer.
   .country-banner  the strip on every page except the homepage, which
                    redirects instead.

   The switcher is a native <details>, so it opens, closes and takes keyboard
   focus with no JavaScript at all. That matters more here than anywhere else
   on the site: this is the control that rescues a visitor the country guess
   got wrong, so it must not depend on a script having loaded.
   ------------------------------------------------------------------------- */

.country-switch {
  /* inline-block, because <details> is block by default and in the footer that
     made the control a full-width strip with a 48px label floating at its left
     end (measured at 1280px). In the header's flex row it makes no difference. */
  display: inline-block;
  position: relative;
  flex: none;
}

/* The trigger. SHORT LABEL ONLY ("UK"), which is what lets this sit in a
   375px header row beside a 192px lockup and the burger.

   OUTLINED, AND WITH A GLOBE, 31 Aug 2026 (Rick: the plain "UK" beside a
   chevron is easy to miss). The border is the part that does the work: it
   is what makes two characters read as a control rather than as a stray
   label. currentColor at 35% so the one rule serves both surfaces, the
   header's dark navy and the footer's paper, with no second colour to keep
   in step.

   DELIBERATELY NOT A FLAG. Windows renders no country-flag emoji at all, so
   a flag would show Rick's own visitors the bare letters "GB" and read as a
   broken image; and a flag names a nationality, while this control chooses a
   market. A globe says "somewhere else" without claiming either. */
.country-switch > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.55rem;
  border: 1px solid color-mix(in oklab, currentColor 35%, transparent);
  border-radius: var(--radius);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  list-style: none;          /* Firefox draws a marker without this */
  white-space: nowrap;
}

.country-switch > summary::-webkit-details-marker { display: none; }

.country-switch > summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.country-switch > summary:hover { border-color: currentColor; }

.country-switch-globe { flex: none; opacity: 0.85; }
.country-switch > summary:hover .country-switch-globe { opacity: 1; }

.country-switch-chevron { flex: none; transition: transform 160ms; }
.country-switch[open] > summary .country-switch-chevron { transform: rotate(180deg); }

/* The panel. Anchored to the trigger and lifted above the sticky header's
   own stacking context so it is not clipped by the band. */
.country-switch-list {
  position: absolute;
  right: 0;
  z-index: 20;
  min-width: 11rem;
  margin: 0.4rem 0 0;
  padding: 0.35rem;
  list-style: none;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.16);
}

.country-switch-footer .country-switch-list { right: auto; left: 0; bottom: 100%; margin: 0 0 0.4rem; }

.country-switch-list li { margin: 0; }

.country-switch-list a {
  display: block;
  padding: 0.45rem 0.6rem;
  border-radius: 4px;
  font-size: 0.9375rem;
  text-decoration: none;
  color: inherit;
}

.country-switch-list a:hover,
.country-switch-list a:focus-visible { background: var(--paper-2); }

.country-switch-list a[aria-current="true"] { font-weight: 700; }

/* In the dark header band the trigger is quiet cream text, like the login
   link beside it. The open panel stays light, because it is a menu. */
.site-header .country-switch > summary { color: var(--header-ink); }
.site-header .country-switch > summary:hover { color: var(--off-white); }

/* Two slots, one shown at a time, the same pattern the header's login link
   and call to action already use. Desktop shows the copy inside the utility
   cluster and hides the phone slot; phone hides the whole utility cluster and
   shows this one instead, beside the burger.

   BOTH RULES LIVE HERE, base first and the media query straight after. The
   phone override used to sit up in the header's own phone block, which is
   EARLIER in this file, so the base rule below it won at 375px and the control
   vanished on exactly the device it matters most on. Same specificity means
   source order decides (measured at 375px, 29 Aug 2026). */
.site-header-country { display: none; }

@media (max-width: 1023.98px) {
  /* margin-left: auto pushes the control and the burger to the right edge. */
  .site-header-country { display: block; margin-left: auto; }
  .site-header-country + .nav-toggle { margin-left: var(--space-2); }
  /* A finger-sized target, matching the 44px burger beside it. The label
     stays small; only the hit area grows. */
  .site-header-country .country-switch > summary { min-height: 44px; }
}

/* ------------------------------ the banner ---------------------------- */

/* One quiet line above the header, in normal flow like the founding strip,
   so it scrolls away and never covers content. Two links: go there, or stay
   here for good. "Stay here" writes the same cookie the switcher writes,
   which is what lets a visitor answer once and never be asked again. */
.country-banner {
  background: var(--green-wash);
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}

.country-banner-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.4rem 1rem;
  padding-block: 0.45rem;
}

.country-banner p {
  max-width: none;  /* the base 70ch cap pins a centred <p> to the left */
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.4;
}

.country-banner-actions { display: flex; gap: 1rem; }

.country-banner a { color: inherit; }
.country-banner-go { font-weight: 700; }
.country-banner-stay { opacity: 0.75; }

/* Print: the dark-jade header prints as a cream-on-white smear (browsers
   drop backgrounds, the lockup is cream artwork, the nav text is near-white),
   so drop the site chrome on every printed page. The certificate page has its
   own print block; this covers guides, profiles and check results, which are
   the pages people actually print. */
@media print {
  .site-header,
  .country-banner,
  .notice-bar { display: none !important; }
}

/* -------------------------------------------------------------------------
   6. Footer
   ------------------------------------------------------------------------- */
/* No margin above the footer: every page ends in a .section, whose own bottom
   padding is the gap, and the margin showed as a bare cream bar between the
   last band and the footer (Rick, 18 Aug 2026). */
.site-footer {
  background: var(--green-deep);
  color: var(--on-green-soft);
  padding-block: var(--space-7) var(--space-6);
  font-size: 0.95rem;
  line-height: 1.75;
}

.footer-brand {
  padding-bottom: var(--space-6);
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--on-green-hairline);
}

.footer-brand a { text-decoration: none; display: inline-block; }

.footer-columns {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--on-green-hairline);
}

@media (min-width: 640px) {
  .footer-columns { grid-template-columns: repeat(2, 1fr); }
}

/* Wide: one track per column the brand actually has (MLD ships four since
   the 18 Aug regroup, WhatCounsellor three) - auto-fit collapses the rest. */
@media (min-width: 960px) {
  .footer-columns { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
}

.footer-heading {
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--brass-soft);
  margin: 0 0 var(--space-4);
}

.footer-col ul { list-style: none; margin: 0; padding: 0; }
.footer-col li { margin-bottom: var(--space-3); }

.footer-col a { color: var(--on-green-soft); text-decoration: none; }
.footer-col a:hover { color: var(--on-green); text-decoration: underline; text-decoration-color: var(--brass-on-dark); }

/* Social links (24 Aug 2026): inline-drawn icons above the legal block.
   Renders only when the operator has set the URLs, so brands without
   accounts show nothing. currentColor keeps them on the footer palette. */
.footer-social {
  display: flex;
  gap: var(--space-4);
  margin-top: var(--space-5);
}
.footer-social a {
  color: var(--on-green-soft);
  display: inline-flex;
  padding: 0.25rem;
}
.footer-social a:hover { color: var(--brass-on-dark); }

.footer-legal {
  padding-top: var(--space-5);
  color: var(--on-green-mute);
  font-size: 0.85rem;
}

.footer-legal p { max-width: none; }
.footer-disclaimer { margin-bottom: 0; }

/* A LINK IN HERE WAS INVISIBLE, on every page of the site.
   These paragraphs sit on the dark footer, but their links had no colour of
   their own, so they fell back to the global a { color: var(--ink) } - which
   is #0F2137, the exact colour of the footer behind them. The underline
   showed and the words did not.
   The one that matters is the GeoNames attribution: postal code data is used
   under a Creative Commons Attribution licence, and an attribution nobody can
   read is not an attribution. Found 1 Sep 2026 in one of Rick's phone
   screenshots, where the line reads "Postal code data from ______". */
.footer-disclaimer a {
  color: var(--on-green-soft);
  text-decoration: underline;
}

.footer-disclaimer a:hover,
.footer-disclaimer a:focus-visible { color: var(--on-green); }

/* Phone number in the footer: a visible trust signal, a touch brighter than
   the surrounding legal text. */
.footer-phone {
  font-size: 1rem;
  color: var(--on-green-soft);
  margin-bottom: var(--space-4);
}
.footer-phone strong { color: var(--on-green); }
.footer-phone a { color: var(--on-green); text-decoration-color: var(--brass-on-dark); }

/* WHATSAPP, the lead contact where a country declares a number (the US,
   22 Sep 2026; country_whatsapp() and partial icon_whatsapp). The glyph is
   a filled icon that takes its link's colour and sits a touch below the
   baseline so it reads as part of the word; inside the header's flex links
   the link's own gap spaces it, so the margin comes off there. The landline
   under the footer's WhatsApp line steps down to the legal size and closes
   up to it, so WhatsApp reads first and the two read as one block. */
.icon-whatsapp { width: 1.1em; height: 1.1em; flex: none; vertical-align: -0.2em; margin-right: 0.35em; }
.header-whatsapp .icon-whatsapp { width: 1.15em; height: 1.15em; margin-right: 0; }
/* nowrap: a phone number must never break mid-number ("+44" orphaned on one
   line, "7887 731638" on the next, seen in the footer at 375 on 22 Sep 2026);
   the whole link drops to the next line as one piece instead. */
.whatsapp-link { white-space: nowrap; }
.footer-whatsapp { margin-bottom: var(--space-1); }
.footer-phone-secondary { font-size: 0.85rem; color: var(--on-green-mute); }
.footer-phone-secondary a { color: var(--on-green-soft); }

/* -------------------------------------------------------------------------
   7. Buttons
   ------------------------------------------------------------------------- */
.btn {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.01em;
  text-align: center;
  text-decoration: none;
  padding: 0.85rem 1.7rem;
  border-radius: var(--radius);
  border: 2px solid transparent;
  cursor: pointer;
  transition: background-color 180ms var(--ease-out), color 180ms var(--ease-out),
              border-color 180ms var(--ease-out), transform 180ms var(--ease-out);
}

.btn:active { transform: translateY(1px); }

.btn-primary {
  background: var(--green);
  color: var(--on-green);
}

.btn-primary:hover { background: var(--green-deep); color: var(--on-green); }

.btn-secondary {
  background: transparent;
  color: var(--green);
  border-color: var(--btn-secondary-border);
}

.btn-secondary:hover { border-color: var(--green); background: var(--green-wash); color: var(--green-deep); }

.btn-brass {
  background: var(--brass); /* Rick 17 Aug 2026: brass buttons are display brass with BLACK text (black on #DFA33A is ~9:1) */
  color: #000;
}

.btn-brass:hover { background: color-mix(in oklab, var(--brass), #000 12%); color: #000; }

.btn-sm { padding: 0.55rem 1.1rem; font-size: 0.92rem; }
.btn-lg { padding: 1.05rem 2.2rem; font-size: 1.08rem; }
.btn-block { display: block; width: 100%; }

.btn[disabled] { opacity: 0.55; cursor: not-allowed; }

.btn-row { display: flex; gap: var(--space-4); flex-wrap: wrap; margin-top: var(--space-5); }

/* -------------------------------------------------------------------------
   8. Forms
   ------------------------------------------------------------------------- */
.field { margin-bottom: var(--space-5); }

.field label {
  display: block;
  font-weight: 700;
  font-size: 1.1rem;
  margin-bottom: var(--space-2);
  color: var(--ink);
}

/* Hints follow any inline error and keep space before the next field. */
.field-hint {
  font-size: 1rem;
  color: var(--ink-mute);
  margin-top: var(--space-1);
  margin-bottom: 1.25rem;
}

/* The green tick under a file input once a file is chosen (Rick, 2 Sep
   2026): the browser's own filename did not read as "it worked". Written by
   main.js, so a page with JavaScript off keeps the browser's filename. */
.field-chosen {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--tone-success-ink);
  margin: var(--space-1) 0 0;
}
.field-chosen .field-chosen-tick { margin-right: 0.35em; }

/* List form of the same hint. A hint that makes three separate points reads
   as a wall when it is one paragraph - Rick, 16 Aug 2026, reviewing his own
   profile. It keeps the .field-hint class so the size, colour and spacing are
   identical to the paragraph form: only the layout differs, so the two never
   look like different components. */
ul.field-hint {
  list-style: disc;
  padding-left: 1.25rem;
}

ul.field-hint li + li { margin-top: 0.35rem; }

/* Required-field asterisk and the inline validation message under a field. */
.req { color: var(--danger); font-weight: 700; margin-left: 0.1rem; }

.field-error {
  color: var(--danger);
  font-size: 0.9rem;
  font-weight: 600;
  margin: var(--space-2) 0 0;
}

/* An invalid field gets a danger-toned border so the eye lands on it. */
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-color: var(--danger);
}

.join-required-note { margin-bottom: var(--space-5); }

input[type="text"], input[type="email"], input[type="tel"], input[type="url"],
input[type="password"], input[type="search"], input[type="number"], input[type="date"],
select, textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--ink-mute); /* >=3:1 boundary vs surface and paper (WCAG 1.4.11) */
  border-radius: var(--radius);
  padding: 0.75rem 0.95rem;
  transition: border-color 160ms var(--ease-out);
}

/* ONE select treatment for the whole site (Rick, 25 Jul 2026). There were
   three - the base rule's native arrow at 18px/400, the filter row at
   15.52px/600 grey with a chevron inset 14.4px, and the sort control at
   15.2px/600 with it inset 12.8px - which is exactly the inconsistency he
   spotted. Everything below now inherits font, weight and colour from the
   shared input rule above; only the chevron and the room made for it live
   here, so a component can never quietly re-style its dropdowns again.
   Component rules may still set width or height (the hero search field is
   deliberately taller); they must not set font-size, colour or the chevron. */
select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  padding-right: 2.75rem;
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: right 1rem center;
  background-size: 0.7rem auto;
}

/* The native option list is drawn by the browser and ignores CSS; Chromium's
   appearance: base-select opts into an in-page picker that does not, so the
   grey/white OS highlight becomes the brand green. Guarded, because Safari
   and Firefox have not shipped it - there the picker stays native, which is
   the same experience the site has always had, not a broken one. */
@supports (appearance: base-select) {
 /* Desktop pointers only. On a phone the native picker is a well-tuned
    bottom sheet and replacing it with an in-page list is a downgrade - and
    the whole point of opting in is the HOVER colour, which touch does not
    have. So phones keep the native control and lose nothing. */
 @media (hover: hover) and (pointer: fine) {
  select, ::picker(select) { appearance: base-select; }
  /* We draw our own chevron; suppress the one base-select adds. */
  select::picker-icon { display: none; }

  ::picker(select) {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow-2);
    padding: var(--space-1);
  }

  /* Options are real DOM children, so they are styled directly - NOT as
     descendants of the ::picker() pseudo-element, which does not match. */
  select option {
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius);
    color: var(--ink);
    background: transparent;
  }

  /* Rick: the OS grey-with-white-text highlight should read as brand. */
  select option:hover,
  select option:focus {
    background: var(--green);
    color: var(--on-green);
  }

  /* The value currently held, shown quietly so it is not mistaken for hover. */
  select option:checked {
    background: var(--green-wash);
    color: var(--green-deep);
  }
  select option:checked:hover {
    background: var(--green);
    color: var(--on-green);
  }

  /* The tick base-select puts against the chosen row, toned to the brand. */
  select option::checkmark { color: var(--green); }
 }
}

input:hover, select:hover, textarea:hover { border-color: var(--ink-soft); }

input:focus, select:focus, textarea:focus {
  outline: 3px solid var(--focus);
  outline-offset: 0;
  border-color: var(--focus);
}

textarea { min-height: 8.5rem; resize: vertical; }

/* File inputs: the native "Choose file" button gets a small accent-coloured
   button style, matched across the app (member dashboard, onboarding, preview,
   admin). Two rules (standard + webkit) because a browser drops a whole rule if
   it does not understand one selector in a group. */
input[type="file"] { font: inherit; color: var(--ink-soft); max-width: 100%; }
input[type="file"]::file-selector-button {
  font: inherit;
  font-weight: 700;
  font-size: 0.9rem;
  padding: 0.45rem 0.95rem;
  margin-right: var(--space-3);
  border: 0;
  border-radius: var(--radius);
  background: var(--brass); /* Rick 17 Jul: file buttons read as the ACCENT, not the same green as real buttons; 17 Aug: display brass with black text */
  color: #000;
  cursor: pointer;
  transition: background-color 160ms var(--ease-out);
}
input[type="file"]::file-selector-button:hover { background: color-mix(in oklab, var(--brass), #000 12%); color: #000; }
input[type="file"]::-webkit-file-upload-button {
  font: inherit;
  font-weight: 700;
  font-size: 0.9rem;
  padding: 0.45rem 0.95rem;
  margin-right: var(--space-3);
  border: 0;
  border-radius: var(--radius);
  background: var(--brass);
  color: #000;
  cursor: pointer;
}

.checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.checkbox-row input[type="checkbox"] {
  width: 1.2rem;
  height: 1.2rem;
  margin-top: 0.2rem;
  flex: none;
  accent-color: var(--green);
}

.checkbox-row label { font-weight: 400; margin: 0; }

.form-error {
  background: var(--danger-wash);
  border: 1px solid var(--tone-danger-border);
  border-radius: var(--radius);
  color: var(--tone-danger-ink);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}

.form-success {
  background: var(--verified-wash);
  border: 1px solid var(--tone-success-border);
  border-radius: var(--radius);
  color: var(--tone-success-ink);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}

/* Honeypot: visually removed, still present for bots */
.hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* -------------------------------------------------------------------------
   9. Chips (styled as inked stamps)
   ------------------------------------------------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  line-height: 1;
  padding: 0.42rem 0.7rem;
  /* Pill, not stamp (Rick, 21 Sep 2026): square corners made every chip
     look like a button. */
  border-radius: 100px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink-soft);
  white-space: nowrap;
}

.chip-verified { background: var(--verified-wash); border-color: var(--tone-verified-border); color: var(--tone-verified-ink); }
.chip-revalidated { background: var(--green-wash); border-color: var(--tone-neutral-border); color: var(--green-deep); }
.chip-available { background: var(--green-wash); border-color: var(--tone-neutral-border); color: var(--green-deep); }
.chip-founding, .chip-featured { background: var(--brass-soft); border-color: var(--brass); color: var(--tone-warn-ink); }
.chip-lapsed { background: var(--paper-2); border-color: var(--line-strong); color: var(--ink-mute); }
/* Admin field markers (Rick, 21 Sep 2026: "make it easy for me to see what
   i should absolutely be filling in"). Three states, one look each:
   Required = the operator must fill it before "Verify & send" will run;
   Automatic = the system writes it, the box exists only to correct it;
   nothing = optional detail. The marker sits after the label, and a
   Required field's box carries the same colour on its left edge so it
   reads at a glance down a long form. */
.chip-required { background: var(--danger-wash); border-color: var(--tone-danger-border); color: var(--danger); }
.chip-auto { background: var(--verified-wash); border-color: var(--tone-verified-border); color: var(--tone-verified-ink); }
.field-mark { margin-left: 0.5rem; vertical-align: middle; font-size: 0.66rem; padding: 0.3rem 0.5rem; }
.field-required > input, .field-required > select, .field-required > textarea { border-left: 4px solid var(--danger); }
.field-auto > input, .field-auto > select { border-left: 4px solid var(--tone-verified-border); background: var(--verified-wash); }
.field-key { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; align-items: center; margin: 0 0 var(--space-4); font-size: 0.92rem; color: var(--ink-soft); }
.field-key .chip { margin-right: 0.35rem; }
.chip-removed { background: var(--danger-wash); border-color: var(--tone-danger-border); color: var(--danger); }

.chip svg { flex: none; }

/* -------------------------------------------------------------------------
   10. Ledger motifs: index lists, entry captions, dotted leaders
   ------------------------------------------------------------------------- */

/* Numbered index list (trust points, steps): 01-04 in serif over hairlines */
.index-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 0;
  grid-template-columns: 1fr;
}

@media (min-width: 860px) {
  .index-list { grid-template-columns: repeat(2, 1fr); column-gap: var(--space-7); }
}

.index-list > li {
  display: flex;
  gap: var(--space-5);
  align-items: baseline;
  padding-block: var(--space-5);
  border-top: 1px solid var(--line);
  margin: 0;
}

.index-num {
  font-family: var(--font-serif);
  font-size: 1.7rem;
  font-weight: 600;
  color: var(--brass);
  flex: none;
  min-width: 2.4rem;
  font-variant-numeric: tabular-nums;
}

.index-list p { margin: 0; color: var(--ink-soft); font-size: 1.1rem; }
.index-list strong { display: block; color: var(--ink); font-size: 1.3rem; margin-bottom: 0.2rem; }

.section-green .index-list > li { border-top-color: var(--on-green-hairline-strong); }
.section-green .index-list p { color: var(--on-green-soft); }
.section-green .index-list strong { color: var(--on-green); }
.section-green .index-num { color: var(--brass-soft); }

/* Ledger caption bar under photographs */
.ledger-photo { position: relative; }

.ledger-photo img {
  width: 100%;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2);
}

.ledger-caption {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: var(--space-3);
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

.ledger-caption::before {
  content: '';
  display: block;
  width: 2rem;
  height: 1px;
  background: var(--brass);
}

/* Dotted leader rows (verification box, certificate) */
.leader-row {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin-bottom: var(--space-3);
  font-size: 0.97rem;
}

.leader-row .leader-label { font-weight: 600; color: var(--ink-soft); flex: none; }

.leader-row .leader-dots {
  flex: 1;
  border-bottom: 2px dotted var(--line-strong);
  transform: translateY(-4px);
  min-width: 1.5rem;
}

.leader-row .leader-value { font-weight: 600; color: var(--ink); text-align: right; }

/* A quieter second line inside a leader value - the training provider under
   its course (24 Aug 2026), so one row carries "what" and "where" without a
   repeated label pair per training. */
.leader-value-sub {
  display: block;
  font-weight: 400;
  font-size: 0.92em;
  color: var(--ink-mute);
}

/* -------------------------------------------------------------------------
   11. Hero (asymmetric editorial split)
   ------------------------------------------------------------------------- */
.hero {
  background: var(--paper);
  overflow: hidden;
}

.hero-grid {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
  align-items: center;
  padding-block: var(--space-7);
}

@media (min-width: 960px) {
  .hero-grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: var(--space-8);
    padding-block: var(--space-8);
  }
}

/* 3rem cap (was the global 3.4rem), Rick's doc 17 Aug 2026: the longer
   hero copy needs the room. */
.hero h1 { margin-bottom: var(--space-4); max-width: 13em; font-size: clamp(2.2rem, 5vw, 3rem); }

/* REVERSED 31 Aug 2026 (Rick's call): on a phone the paragraph reads ABOVE
   the search box again. The 14 Aug 2026 rule that lifted the box over the
   paragraph is DELETED rather than re-numbered, because the wanted visual
   order (eyebrow, heading, paragraph, form) is now the DOM order, so there
   is nothing left for it to do. Its flex column went with it: no other rule
   depended on it, and .hero-copy stacks the same as a block. */

/* Positioning line above the hero h1. Same voice as .welcome-eyebrow; its
   own rule because the hero sits on paper, not the welcome surface, and the
   welcome variant carries spacing this slot does not want. */
.hero-eyebrow {
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--green);
  max-width: none;
  margin-bottom: var(--space-3);
}

.hero-subhead {
  font-size: 1.3rem;
  font-family: var(--font-serif);
  color: var(--ink-soft);
  max-width: 44rem;
  margin-bottom: var(--space-6);
  line-height: 1.6;
}

/* ---- Full-bleed hero variant, added 20 Aug 2026 -------------------------
   The photograph runs edge to edge instead of sitting in a box beside the
   copy, which is what .hero-grid gives.

   OPT-IN AND ADDITIVE. A page keeps .hero-grid or .hero-stack unless it
   asks for .hero-bleed, so WhatCounsellor's homepage, the For Therapists
   hero and every other brand's hero are untouched by this block.

   Three art-directed frames, one per band (16:9 desktop, 4:3 tablet, 9:16
   phone), each composed with the therapist to one side of a soft empty
   field. The scrim below is INSURANCE for the widths in between - measured
   20 Aug 2026, a first attempt without enough of it laid the subhead
   across the therapist's body at 768px. Copy width is capped per band for
   the same reason: a paragraph allowed to run the full container always
   reaches her.

   THE PHONE DOES NOT OVERLAY, and that is deliberate. Measured at 390px:
   the eyebrow, heading and search box together are about 560px tall, while
   the pale field in the portrait frame is only ever about 48% of the hero,
   so the subhead - which sits LAST on a phone under the 14 Aug 2026 search
   box order rule - always landed on the therapist, at every hero height
   tried. Overlaying anyway would have meant either an unreadable paragraph
   or a wash so heavy the photograph disappeared. So on a phone the copy
   keeps clean paper and the photograph runs full width BELOW it. The
   portrait frame is still the right source there: at 390px it crops only
   vertically, so the therapist stays whole. */
.hero-bleed {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  min-height: 34rem;
  padding-block: var(--space-7);
}

.hero-bleed > .container {
  position: relative;
  z-index: 2;
  width: 100%;
}

.hero-bleed-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero-bleed-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 70% 50%;
}

/* Paper washing over the copy and clearing before the therapist.
   color-mix rather than a second hex keeps --paper the only definition of
   the colour, and mixing in oklab avoids the grey haze plain `transparent`
   gives when a gradient interpolates through transparent black. */
.hero-bleed-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    var(--paper) 0%,
    var(--paper) 30%,
    color-mix(in oklab, var(--paper) 70%, transparent) 55%,
    color-mix(in oklab, var(--paper) 0%, transparent) 78%
  );
}

.hero-bleed .hero-copy { max-width: 30rem; }

@media (min-width: 960px) {
  .hero-bleed {
    min-height: 40rem;
    padding-block: var(--space-8);
  }
  /* Wash strengthened to 80% paper at 44%, Rick's own gradient, 31 Aug 2026.
     The literal #f6f2ed first stop is his too; it is the value of --paper and
     sits at the same 0% position as the token, so it paints nothing. */
  .hero-bleed-media::after {
    background: linear-gradient(
      100deg,
      #f6f2ed 0%,
      var(--paper) 0%,
      color-mix(in oklab, var(--paper) 80%, transparent) 44%,
      color-mix(in oklab, var(--paper) 0%, transparent) 64%
    );
  }
  .hero-bleed .hero-copy { max-width: 36rem; }
}

/* PHONE: copy on paper, photograph full width beneath it. The media block
   is a sibling of .container, not inside it, so it still bleeds to both
   edges. Its DOM position is first, so order puts it second here. */
@media (max-width: 719px) {
  .hero-bleed {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    min-height: 0;
    padding-block: var(--space-6) 0;
  }
  .hero-bleed > .container { order: 1; }
  .hero-bleed-media {
    position: static;
    order: 2;
    height: 30rem;
    margin-top: var(--space-6);
  }
  .hero-bleed-media::after { content: none; }
  .hero-bleed-media img { object-position: 50% 88%; }
  .hero-bleed .hero-copy { max-width: none; }

  /* Rick, 20 Aug 2026: on a phone the eyebrow and heading CENTRE over the
     left-aligned paragraph and search card below them. A deliberate
     exception to the site-wide "mobile = all left" heading rule (16 Jul),
     scoped to .hero-bleed so no other page or brand moves. The h1 needs
     margin-inline too: it carries max-width 13em, and a narrower box with
     no auto margins stays pinned left however the text inside is aligned -
     the same trap that has now caught four separate centring jobs. */
  .hero-bleed .hero-eyebrow,
  .hero-bleed h1 { text-align: center; }
  .hero-bleed h1 { margin-inline: auto; }
}

.search-box {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  box-shadow: var(--shadow-2);
  max-width: 38rem;
}

.search-box label {
  display: block;
  font-weight: 700;
  margin-bottom: var(--space-3);
}

.search-box-row {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.search-box-row input[type="search"],
.search-box-row input[type="text"] {
  flex: 1 1 14rem;
  font-size: 1.05rem;
}

.search-box-row .btn { flex: none; }

/* The hero's "Need help with?" select (home.php): since 26 Aug 2026 (Rick's
   layout) it SHARES the row with the location input, and the submit button
   sits underneath on its own line (.search-box-submit). Type size matches
   the input; the chevron and everything else still come from the shared
   select rules - the one-select-treatment rule (25 Jul 2026) bars a
   component from restyling them. The colour pair below keeps the two
   fields' resting text the same shade: the input's placeholder and the
   select's unchosen "Any reason" state are both ink-mute, and each darkens
   to full ink once it holds a real value. */
.search-box-row .search-box-reason {
  flex: 1 1 12rem;
  font-size: 1.05rem;
}

.search-box-submit { margin-top: var(--space-3); }

.search-box-row input::placeholder { color: var(--ink-mute); opacity: 1; }

.search-box-row .search-box-reason:has(option[value=""]:checked) {
  color: var(--ink-mute);
}

/* Search fields wear the sage keyline (requested: 3px solid #92afa4).
   On the finder it marks the two controls that define the SEARCH - where and
   how far - against the filters that refine it, now that both sit on one bar. */
.search-box-row input[type="search"],
.search-box-row input[type="text"],
.search-box-row input[type="email"],
.search-box-reason,
.finder-field-loc input[type="search"],
.finder-field-radius select {
  border: 3px solid var(--search-keyline);
}

.search-microcopy {
  font-size: 0.9rem;
  color: var(--ink-mute);
  margin: var(--space-3) 0 0;
}

/* -------------------------------------------------------------------------
   12. Editorial image trio (no card chrome; hairline-ruled columns)
   ------------------------------------------------------------------------- */
.editorial-trio {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
}

@media (min-width: 860px) {
  .editorial-trio { grid-template-columns: repeat(3, 1fr); }
}

.editorial-item { border-top: 2px solid var(--green); padding-top: var(--space-4); }

.editorial-item img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-4);
}

.editorial-item h3 { margin-top: 0; font-size: 1.5rem; }
.editorial-item p { color: var(--ink-soft); font-size: 1.1rem; margin-bottom: 0; }

/* -------------------------------------------------------------------------
   13. Cards (kept for result entries and tier cards only)
   ------------------------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  box-shadow: var(--shadow-1);
}

.card h3 { margin-top: 0; }

.card-link-list { list-style: none; padding: 0; margin: 0; }
.card-link-list li { margin-bottom: var(--space-3); }

/* -------------------------------------------------------------------------
   13b. Product preview frame (marketing pages)
   ------------------------------------------------------------------------- */
/* A labelled specimen of a real product surface - a search result card, the
   patient's filter options - shown on a sales page so a prospective member
   sees the thing itself instead of a description of it.

   RULE: build the inside from the REAL component classes (.result-card and
   its children), never a redrawn mock. A hand-drawn imitation drifts the
   moment the real component changes, and then the sales page is advertising
   a product we do not ship. Every frame carries a visible label saying the
   contents are an example, because the names, numbers and reviews inside a
   specimen are illustrative and must never read as a real member's record. */
.preview-frame {
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  box-shadow: var(--shadow-1);
}

/* On a tinted band the warm frame is the same tone as the band behind it and
   the chrome disappears. Flip to the white surface there so the frame still
   reads as a frame. */
.section-tint .preview-frame { background: var(--surface); }

/* Sentence case, never uppercase: this is a label a visitor reads, and the
   house rule bans shouting in anything outward-facing. */
.preview-frame-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink-mute);
}

.preview-frame .result-card { margin-bottom: 0; flex-wrap: wrap; }

/* The specimen's name is a <p>, not the h3 the real card uses (a heading
   inside a hero picture would skip an outline level under the h1). The serif
   comes from the heading rule, so it has to be restated here. */
.preview-frame .result-name { font-family: var(--font-serif); }

/* A frame is always narrow (a hero column, half a two-column band), so the
   floating View profile button unfloats here exactly as it does on a phone.
   It must also WRAP to its own row: .result-card is a flex row, so merely
   returning the button to the flow turns it into a third column and starves
   the text column (it measured 157px of a 519px card). Keep the clearance
   list in step with the 540px rule in section 18 - if a line gains the
   clearance there it needs releasing here too. */
.preview-frame .result-cta { position: static; transform: none; flex-basis: 100%; margin-top: var(--space-4); }
.preview-frame .result-cta .btn { display: block; width: 100%; }
.preview-frame .result-main .result-name,
.preview-frame .result-main .result-meta,
.preview-frame .result-main .result-rating,
.preview-frame .result-main .result-google-note { padding-right: 0; }

/* Controls inside a frame are part of the picture, not working controls, so
   they never carry a pointer cursor. The specimen markup uses a span rather
   than an anchor for the same reason: a specimen must not offer a link to a
   profile that does not exist. */
.preview-frame .btn { cursor: default; }

.preview-frame-note {
  font-size: 0.85rem;
  color: var(--ink-mute);
  margin: var(--space-4) 0 0;
}

/* Row list inside a frame: the patient's filter options, one per line. */
.preview-list { list-style: none; margin: 0; padding: 0; }
.preview-list li { padding: var(--space-3) 0; border-top: 1px solid var(--line); }
.preview-list li:first-child { border-top: 0; padding-top: 0; }
.preview-list li:last-child { padding-bottom: 0; }

/* -------------------------------------------------------------------------
   13c. Laptop product shot (.device-macbook)
   ------------------------------------------------------------------------- */
/* A marketing product shot: a REAL page of this site, rendered at a real
   desktop width and photographed inside a laptop lid. Same rule as the
   preview frame above and for the same reason - the inside is built from the
   real component classes, never a redrawn mock, so it cannot drift into
   advertising a product we do not ship. The shot carries a visible caption
   saying the contents are an example, because every name, number and review
   inside a specimen is illustrative.

   HOW THE SCALING WORKS, and why it is not JavaScript. The screen holds the
   page at a real desktop width and shrinks it with a transform. Rendering at
   a real width is the whole point: the page inside lays out exactly as a
   visitor's own screen lays it out, including the 960px breakpoint that
   gives the profile its two columns.

   THE SCALE IS THE FIXED NUMBER AND THE INNER WIDTH FOLLOWS IT. The screen
   is whatever width the hero column leaves, and .device-screen-inner takes
   100% / scale of that, so the scaled page lands on the screen's own edges
   at any width. Sizing the two independently is what crops a shot: a
   narrower lid against an unchanged inner width cuts the right-hand third -
   the price and the whole Verification panel - off inside the screen.
   Nothing is measured and nothing is scripted, so the shot still renders
   correctly with scripting off.

   KEEP THE DERIVED INNER WIDTH ABOVE 960px, the profile's own two-column
   breakpoint, because a shot of the one-column phone layout is not what
   this is advertising. In the 552px hero column the screen is 464px, so
   0.46 derives 1009px. Lowering the scale RAISES the derived width.

   BELOW 1200px THERE IS NO SHOT. A laptop shrunk to a phone column is a grey
   smudge: at 360px the body text lands under 4px. The page shows the plain
   .preview-frame card there instead, and hides this. Pair the two with
   .device-shot-only / .device-shot-fallback so exactly one is in the
   document at any width - display:none, so a screen reader is never read
   both. */
/* THE WHOLE LAPTOP FITS INSIDE ITS HERO COLUMN (Rick, 19 Aug 2026: the hero
   read as pushed to the right). It used to be a fixed 760px lid in a 552px
   column, bleeding 200px past the container while the copy column started on
   the container's left edge - so at 1920px the page carried 352px of margin
   on the left and 115px on the right. The padding is exactly what the base's
   -30px side margins spend, so the BASE - the widest part of the laptop -
   lands on the column edge and mirrors the copy column opposite it. */
.device-macbook {
  --device-scale: 0.46;
  width: 100%;
  padding-inline: 30px;
}

.device-lid {
  position: relative;
  background: #2b2d31;
  border-radius: 16px;
  padding: 14px;
  box-shadow: 0 26px 50px oklch(0.27 0.045 168 / 0.20), 0 4px 12px oklch(0.27 0.045 168 / 0.14);
}

/* The camera, and the only detail the lid needs. A drawn notch, keyboard or
   trackpad reads as a picture OF a laptop; this reads as a laptop. */
.device-lid::before {
  content: "";
  position: absolute;
  top: 5px;
  left: 50%;
  transform: translateX(-50%);
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #45484d;
}

/* 16:10.4 is the MacBook Pro screen, near enough that nobody reads it as a
   television. The overflow crop is what makes it a photograph of a page
   rather than a boxed-in copy of one: content continues past the bottom
   edge exactly as it does on a real screen. */
.device-screen {
  background: var(--paper);
  border-radius: 4px;
  overflow: hidden;
  aspect-ratio: 16 / 10.4;
}

/* The inner page width is DERIVED from the screen, never authored: 100% of
   the screen divided by the scale, scaled back by the scale, is the screen
   width again at any column width. See the scale note at the top of this
   block before changing either number. */
.device-screen-inner {
  width: calc(100% / var(--device-scale));
  transform: scale(var(--device-scale));
  transform-origin: top left;
}

/* The base is wider than the lid, which is the one proportion that makes a
   laptop read as a laptop rather than a framed picture. */
.device-base {
  height: 13px;
  margin: 0 -30px;
  background: linear-gradient(180deg, #d5d8dc 0%, #b6bbc1 55%, #91969c 100%);
  border-radius: 0 0 12px 12px;
  position: relative;
  box-shadow: 0 8px 14px oklch(0.27 0.045 168 / 0.16);
}

.device-base::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 110px;
  height: 7px;
  background: #9aa0a6;
  border-radius: 0 0 7px 7px;
}

/* Default sizes for the paragraph stand-ins declared with the global heading
   rule. Containers that already size their own headings (the profile
   masthead, the verification box head, a profile section) override these,
   exactly as they override h1/h2/h3. A shot heading is never a measure of
   text, so the 70ch paragraph cap has to come off. */
.shot-title { font-size: clamp(1.6rem, 3.2vw, 2.3rem); margin-top: var(--space-6); max-width: none; }
.shot-heading { font-size: clamp(1.25rem, 2vw, 1.5rem); margin-top: var(--space-5); max-width: none; }

/* Same job and same voice as .preview-frame-caption: sentence case, quiet,
   and OUTSIDE the shot so it cannot be cropped away with it. */
.device-caption {
  margin: var(--space-4) 0 0;
  font-size: 0.85rem;
  color: var(--ink-mute);
}

/* The responsive pair. Neither is decoration: below the swap the fallback
   card IS the specimen, so it carries the same caption and the same
   fictional registration number. */
.device-shot-only { display: none; }

@media (min-width: 1200px) {
  .device-shot-only { display: block; }
  .device-shot-fallback { display: none; }
}

/* The lid needs no size bands any more - it is its column. The one band left
   is below 1280px, where the container has not yet reached its 1280px cap, so
   the hero column is under 552px and the derived inner width would fall
   through the profile's 960px two-column breakpoint. A slightly smaller scale
   buys it back: at the 1200px worst case the screen is 424px, and 0.44
   derives 964px. */
@media (min-width: 1200px) and (max-width: 1279.98px) {
  .device-macbook { --device-scale: 0.44; }
}

/* -------------------------------------------------------------------------
   14. Notices, disclaimers, verification box
   ------------------------------------------------------------------------- */
.notice {
  background: var(--green-wash);
  border: 1px solid var(--notice-border);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  margin: var(--space-5) 0;
  font-size: 1.125rem;
}

.disclaimer-box {
  background: var(--paper-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  margin: var(--space-6) 0 var(--space-5);
  font-size: 0.95rem;
  color: var(--ink-soft);
}

.verification-box {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  box-shadow: var(--shadow-1);
}

.verification-box-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--line);
}

.verification-box-head h2, .verification-box-head h3,
.verification-box-head .shot-heading {
  margin: 0;
  font-size: 1.25rem;
  /* Match the 34px seal height so seal-headed and text-only box heads
     rule at the same baseline in a two-box row. */
  min-height: 2.125rem;
  display: flex;
  align-items: center;
}

.verification-box-head .seal { color: var(--green); }

.verification-box-footer {
  font-size: 0.85rem;
  color: var(--ink-mute);
  border-top: 1px solid var(--line);
  padding-top: var(--space-3);
  margin-bottom: 0;
}
/* The footnote folded behind "What does Verified mean?" (Rick, 21 Sep 2026).
   Same look as the bio's "Read more" toggle; the paragraph keeps its own
   hairline once opened. */
.verification-more { border-top: 1px solid var(--line); margin-top: var(--space-3); padding-top: var(--space-2); }
.verification-more > summary {
  cursor: pointer;
  list-style: none;
  display: inline-block;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--green-deep);
  text-decoration: underline;
  text-decoration-color: var(--brass);
}
.verification-more > summary::-webkit-details-marker { display: none; }
.verification-more > .verification-box-footer { border-top: 0; padding-top: var(--space-2); }

/* -------------------------------------------------------------------------
   15. FAQ (details/summary)
   ------------------------------------------------------------------------- */
.faq-item { border-bottom: 1px solid var(--line); }

.faq-item summary {
  cursor: pointer;
  font-weight: 600;
  font-family: var(--font-serif);
  font-size: 1.3rem;
  padding: var(--space-4) var(--space-6) var(--space-4) 0;
  position: relative;
  list-style: none;
}

.faq-item summary:hover, .filter-sheet summary:hover { color: var(--green-deep); }

.faq-item summary::-webkit-details-marker { display: none; }

.faq-item summary::after {
  content: '+';
  position: absolute;
  right: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-serif);
  font-size: 1.5rem;
  color: var(--brass);
}

.faq-item[open] summary::after { content: '\2212'; }

.faq-item .faq-answer { padding: 0 0 var(--space-4); color: var(--ink-soft); }

/* -------------------------------------------------------------------------
   16. Breadcrumbs
   ------------------------------------------------------------------------- */
.breadcrumbs {
  font-size: 0.9rem;
  color: var(--ink-mute);
  padding-block: var(--space-4);
}

.breadcrumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; padding: 0; }
.breadcrumbs li { margin: 0; display: flex; gap: var(--space-2); align-items: center; }
.breadcrumbs li + li::before { content: '·'; color: var(--brass); }
.breadcrumbs a { color: var(--ink-mute); text-decoration: none; }
.breadcrumbs a:hover { color: var(--green); text-decoration: underline; }

/* -------------------------------------------------------------------------
   17. CTA band
   ------------------------------------------------------------------------- */
.cta-band {
  background: var(--green-deep);
  color: var(--on-green-soft);
  border-radius: var(--radius-lg);
  padding: clamp(2rem, 5vw, 3.5rem);
  position: relative;
  overflow: hidden;
}

/* Watermark: the logo's own flow-wave lines in the top-right corner, in
   place of the two abstract rings that used to sit there (Rick, 18 Aug
   2026). Same --flourish-image the green bands already use, so the mark on
   the page is the mark in the logo and there is no second version of it. */
.cta-band::after {
  content: '';
  position: absolute;
  right: -3rem;
  top: -2.5rem;
  width: 26rem;
  height: 17rem;
  background: var(--flourish-image) no-repeat right top;
  background-size: contain;
  opacity: 0.16;
  pointer-events: none;
}

.cta-band > * { position: relative; z-index: 1; }

.cta-band h2, .cta-band h3 { color: var(--on-green); margin-top: 0; }
.cta-band p { max-width: 44rem; line-height: 1.75; }

.cta-band .btn-primary { background: #DBA239; color: #000; } /* Rick's exact values, 17 Aug 2026 */
.cta-band .btn-primary:hover { background: color-mix(in oklab, #DBA239, #000 12%); color: #000; }
.cta-band .btn-secondary { color: var(--on-green); border-color: var(--cta-outline); }
.cta-band .btn-secondary:hover { background: var(--cta-outline-wash); color: var(--on-green); border-color: var(--on-green); }

/* -------------------------------------------------------------------------
   18. Result entries (search, town pages) - ledger entry styling
   ------------------------------------------------------------------------- */
.result-card {
  display: flex;
  gap: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-1);
  margin-bottom: var(--space-4);
  transition: box-shadow 200ms var(--ease-out), border-color 200ms var(--ease-out);
}

.result-card:hover { box-shadow: var(--shadow-2); border-color: var(--line-strong); }

.result-card.featured { border-color: var(--brass); }

.result-photo {
  flex: none;
  width: 8rem;
  height: 8rem;
  border-radius: 50%;
  overflow: hidden;
  background: var(--green-wash);
  display: grid;
  place-items: center;
  color: var(--green);
  font-family: var(--font-serif);
  font-size: 2.2rem;
}

.result-photo img { width: 100%; height: 100%; object-fit: cover; }

.result-main { flex: 1; min-width: 0; }

.result-name { font-size: 1.6rem; margin: 0 0 var(--space-1); }
.result-name a { text-decoration: none; color: var(--ink); }
.result-name a:hover { text-decoration: underline; text-decoration-color: var(--brass); }

.result-meta {
  font-size: 0.85rem;
  color: var(--ink-mute);
  margin-bottom: var(--space-3);
  font-variant-numeric: tabular-nums;
}

.result-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }

/* Provenance block (partials/provenance_chips.php): "Checked by us" over one
   row of stamps, "Told to us by the therapist" over another. Every chip inside
   keeps the plain .chip style deliberately - see the partial's header for why
   colour must not carry this distinction. The label is sentence case and quiet
   so it reads as a caption against the uppercase stamps it introduces. */
.provenance { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-3); }
.prov-row { display: flex; flex-direction: column; gap: 0.4rem; }
.prov-label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink-mute);
  line-height: 1.2;
}
.prov-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }

@media (max-width: 540px) {
  .result-card { flex-direction: column; }
}

/* -------------------------------------------------------------------------
   19. Map
   ------------------------------------------------------------------------- */
.map-frame {
  height: 26rem;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  overflow: hidden;
  background: var(--paper-2);
  /* Leaflet's own sheet puts controls at z-index 1000 and its marker and
     popup panes at 600/700. Without a stacking context here those numbers
     escape into the root and beat anything the page pins over the map - this
     is the third time that has bitten this page. One line ends the argument
     permanently: everything Leaflet does is now sealed inside the frame. */
  isolation: isolate;
}

/* Results stack in one column: map banner on top, then the full-width list. */
.search-layout {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
}

.search-layout .map-sticky { order: -1; }
/* 22rem in a ~1100px-wide banner is a 3:1 letterbox, and fitBounds satisfies
   both axes - so the short side chose the zoom and the long side filled the
   slack with France. Every doubling of height buys a zoom level; 28rem is
   enough to move a typical 50-mile search from zoom 8 to 9 without the map
   dominating the page. */
.search-layout .map-frame { height: 22rem; }

/* Only wide viewports have the problem: a narrow phone map has no excess
   width to fill, so it keeps the shorter banner rather than eating half the
   screen. */
@media (min-width: 720px) {
  .search-layout .map-frame { height: 28rem; }
}

/* --- The FINDER's map: a column beside the results, not a banner over them
       (Rick, 26 Jul 2026) ---------------------------------------------------
   Measured at 1440x800, the banner's top edge landed at 798px with the fold
   at 800: a sliver of something large and colourful peeking at the bottom of
   the screen is the strongest scroll cue an interface has, and it is what
   sent the first patient who tested this page past the filters entirely.
   Beside the results the map cannot do that, and it also finishes the
   over-zoom fix - the 3.1:1 letterbox was why fitBounds' short axis chose the
   zoom and the spare width filled with 260 miles of coastline.
   SCOPED to the finder: .search-layout is shared with town and lander pages,
   which have no filter bar and keep their single-column banner. */
@media (min-width: 960px) {
  .search-layout.search-layout-finder {
    grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
    /* start, not the default stretch: a stretched grid item fills the row
       height, which leaves a sticky child with zero travel. Same pattern as
       .profile-layout. */
    align-items: start;
  }
  /* Second in DOM and second visually, so focus order matches what is seen. */
  .search-layout.search-layout-finder .map-sticky {
    order: 0;
    position: sticky;
    /* Parked BELOW the pinned filter bar, not at the top of the viewport.
       The bar is opaque and sticks at top:0, so a map parked at --space-4
       had its top strip buried - Leaflet's +/- zoom buttons included, which
       made them unclickable for the whole scroll. The bar's height is not a
       constant (the active-filter chips live in it and it re-wraps), so
       main.js measures it into --finder-bar-h; the fallback is the measured
       one-row height for the no-JS case, where the bar still sticks. */
    top: calc(var(--header-offset) + var(--finder-bar-h, 8rem) + var(--space-4));
  }
  .search-layout.search-layout-finder .map-frame { height: min(34rem, calc(100vh - var(--header-offset) - 8rem)); }
}

/* Below the column breakpoint the map goes AFTER the results rather than
   above them. On a 375px phone the banner alone pushed the first therapist
   past 1600px; the point of the page is the therapists. */
@media (max-width: 959.98px) {
  .search-layout.search-layout-finder .map-sticky { order: 1; }
}

/* -------------------------------------------------------------------------
   20. Pricing tiers
   ------------------------------------------------------------------------- */
.tier-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 720px) { .tier-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1080px) { .tier-grid { grid-template-columns: repeat(4, 1fr); } }

.tier-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  box-shadow: var(--shadow-1);
  display: flex;
  flex-direction: column;
  height: 100%;
  position: relative;
}

.tier-card.best-value { border: 2px solid var(--brass); }

.tier-flag {
  position: absolute;
  top: -0.85rem;
  left: var(--space-5);
  background: var(--brass-deep); /* white text needs >=4.5:1: brass-deep = 5.4:1 */
  color: var(--off-white);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.28rem 0.7rem;
  border-radius: var(--radius-stamp);
}

/* Self-sufficient so span.tier-name (/join) and h2.tier-name (/pricing)
   render identically; also lifts tier names into the 1.3rem heading step. */
.tier-name {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 1.3rem;
  color: var(--ink);
  line-height: 1.14;
  margin: 0 0 var(--space-3);
}

.tier-price {
  font-family: var(--font-serif);
  font-size: 2.1rem;
  font-weight: 700;
  color: var(--green-deep);
  margin-bottom: var(--space-1);
  font-variant-numeric: tabular-nums;
}

.tier-price-note { font-size: 0.9rem; color: var(--ink-mute); margin-bottom: var(--space-4); }

/* Card scale is scoped to cards; verification boxes and profiles inherit
   body size so the tick lists read as one system with their neighbours. */
.tier-features { list-style: none; padding: 0; margin: 0 0 var(--space-5); }
.tier-card .tier-features { font-size: 0.95rem; }
.verification-box .tier-features li { margin-bottom: var(--space-3); }

.tier-features li {
  padding-left: 1.5rem;
  position: relative;
  margin-bottom: var(--space-2);
}

.tier-features li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.42rem;
  width: 0.85rem;
  height: 0.45rem;
  border-left: 2px solid var(--verified);
  border-bottom: 2px solid var(--verified);
  transform: rotate(-45deg);
}

.tier-card .btn { margin-top: auto; }

/* -------------------------------------------------------------------------
   21. Prose (long-form legal/guide content)
   ------------------------------------------------------------------------- */
.prose { max-width: var(--container-narrow); margin-inline: auto; }
.prose h2 { margin-top: var(--space-7); }
.prose h3 { margin-top: var(--space-6); }
.prose li { margin-bottom: var(--space-3); }
/* An opening heading gets its top gap from the section padding, not an extra
   margin: without this, a prose block that starts with an h2 has a bigger gap
   above the heading than below it. */
.prose > :first-child { margin-top: 0; }

.prose-meta {
  font-size: 0.9rem;
  color: var(--ink-mute);
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-6);
}

/* A version line inside a centred page header (Verification Standard, Badge
   Terms) is part of the title block, not a prose rule: no hairline, tighter,
   and margin-inline auto because the global p max-width would otherwise pin
   its box left while the header centres everything else. */
.page-header .prose-meta {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: var(--space-3);
  margin-inline: auto;
}

/* -------------------------------------------------------------------------
   22. Page headers (interior pages)
   ------------------------------------------------------------------------- */
.page-header {
  padding-block: var(--space-7) var(--space-6);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}

.page-header h1 { margin-bottom: var(--space-3); }
.page-header .lead {
  font-size: 1.2rem;
  font-family: var(--font-serif);
  color: var(--ink-soft);
  max-width: 46rem;
  margin-bottom: 0;
}

/* Page titles (Rick, 16 Jul 2026): on desktop EVERY page-title block is
   centred (the homepage hero is its own component, not a .page-header, so it
   keeps its layout; admin pages keep their functional left alignment). On
   mobile all page titles read left-aligned - the base state below the
   breakpoint. */
@media (min-width: 640px) {
  body:not(.page-admin) .page-header { text-align: center; }
  body:not(.page-admin) .page-header .lead { margin-inline: auto; }
}

/* Compact variant, used by the finder once a search has run (Rick, 26 Jul
   2026). The full block is 218px of a 800px screen restating what the visitor
   already typed, and it pushed the map's top edge to within two pixels of the
   fold - which is what made people scroll straight past the filters. The h1
   and the /verification-standard link both stay; only their weight changes.
   A modifier on the element, not a body class: .page-header is used by 41
   templates and the body class does not vary with search state. */
.page-header-compact { padding-block: var(--space-5) var(--space-4); }
.page-header-compact h1 {
  font-size: clamp(1.55rem, 3.2vw, 2.15rem);
  margin-bottom: var(--space-2);
}
.page-header-compact .lead { font-size: 1.02rem; }
/* Compact spacing, but a headline that still outranks the section headings
   below it (h2 is 2.3rem). Used on /how-we-differ, 23 Sep 2026. */
.page-header-headline { padding-bottom: var(--space-7); }
.page-header-headline h1 { font-size: clamp(1.75rem, 3.6vw, 2.6rem); margin-bottom: var(--space-5); }
.page-header-headline .lead { color: oklch(0.32 0.02 165); } /* a shade darker than --ink-soft (Rick, 23 Sep 2026) */
/* Headline and intro stay centred on phones too (Rick, 23 Sep 2026); .hero-stack-text otherwise turns left below 640px. */
@media (max-width: 639.98px) {
  .page-header-headline h1, .page-header-headline .lead { text-align: center; }
}

/* -------------------------------------------------------------------------
   23. Dashboard and admin (functional, same tokens)
   ------------------------------------------------------------------------- */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  margin-bottom: var(--space-5);
}

.panel h2 { margin-top: 0; font-size: 1.45rem; } /* Rick 17 Jul: larger section titles, dashboard + admin */

.stat-row { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); }

.stat {
  background: var(--green-wash);
  border-radius: var(--radius);
  padding: var(--space-4);
  text-align: center;
}

.stat-number {
  font-family: var(--font-serif);
  font-size: 1.9rem;
  font-weight: 700;
  color: var(--green-deep);
  display: block;
  font-variant-numeric: tabular-nums;
}

.stat-label { font-size: 0.85rem; color: var(--ink-soft); }

.table-scroll { overflow-x: auto; }

.status-pill {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-stamp);
  background: var(--paper-2);
  color: var(--ink-soft);
}

.status-pill.ok { background: var(--verified-wash); color: var(--tone-verified-ink); }
.status-pill.warn { background: var(--brass-soft); color: var(--tone-warn-ink); }
.status-pill.bad { background: var(--danger-wash); color: var(--danger); }

/* -------------------------------------------------------------------------
   24. Error pages
   ------------------------------------------------------------------------- */
.error-page { text-align: center; padding-block: var(--space-8); }

.error-code {
  font-family: var(--font-serif);
  font-size: 5rem;
  font-weight: 700;
  color: var(--brass);
  line-height: 1;
  margin-bottom: var(--space-4);
}

.error-page .search-box { margin-inline: auto; text-align: left; margin-top: var(--space-6); }
.error-page p { margin-inline: auto; }

.error-detail {
  text-align: left;
  overflow: auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4);
  font-size: 0.85rem;
}

/* -------------------------------------------------------------------------
   25. Reveal-on-scroll (progressive enhancement; JS adds .reveal-ready)
   ------------------------------------------------------------------------- */
.reveal { opacity: 1; transform: none; }

.js.reveal-ready .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out);
}

.js.reveal-ready .reveal.revealed { opacity: 1; transform: none; }

.js.reveal-ready .reveal:nth-child(2) { transition-delay: 70ms; }
.js.reveal-ready .reveal:nth-child(3) { transition-delay: 140ms; }
.js.reveal-ready .reveal:nth-child(4) { transition-delay: 210ms; }

@media (prefers-reduced-motion: reduce) {
  .js.reveal-ready .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  .btn, a, input, select, textarea, .result-card { transition: none !important; }
}

/* -------------------------------------------------------------------------
   26. Utilities
   ------------------------------------------------------------------------- */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.text-muted { color: var(--ink-mute); }
.text-small { font-size: 1.1rem; }
.text-nowrap { white-space: nowrap; }

/* Wider column gap for two-column editorial sections */
@media (min-width: 640px) {
  .grid-gap-lg { gap: var(--space-8); }
}
.text-center { text-align: center; }
.text-left { text-align: left; }
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }
.mt-4 { margin-top: var(--space-4); }
.mt-6 { margin-top: var(--space-6); }
.mt-7 { margin-top: var(--space-7); }

/* Secret-field state badge (admin settings): shows whether a password/API key
   is stored, without ever revealing it. */
.secret-badge {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 0.12em 0.6em;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.5;
  vertical-align: middle;
}
.secret-badge.is-set { background: var(--green-wash); color: var(--green); }
.secret-badge.is-unset { background: var(--line); color: var(--ink-mute); }

/* Editable long-text setting (e.g. the AI drafting instructions). */
.settings-textarea {
  width: 100%;
  min-height: 12rem;
  font-size: 0.9rem;
  line-height: 1.55;
  resize: vertical;
}
.mx-auto { margin-inline: auto; }

/* =========================================================================
   Page-specific sections are appended below by the relevant build package.
   Keep every addition token-driven: no new colours, no new fonts.
   ========================================================================= */

/* --- Homepage --- */
/* The hero search prompt reads as a small title above the field. */
.page-home .search-box label { font-size: 1.25rem; }

.trust-strip {
  background: var(--surface);
  border-block: 1px solid var(--line);
  padding-block: var(--space-5);
}

/* First row of the trust strip needs no hairline above it on desktop */
@media (min-width: 860px) {
  .trust-strip .index-list > li:nth-child(-n+2) { border-top: 0; }
}

/* Mobile (single column): only the very first row drops its hairline, so the
   strip's own top border is not doubled by a stray rule above "01". */
.trust-strip .index-list > li:first-child { border-top: 0; }

/* Featured members band: cards centre in the committed-green surface at any
   count (one card at launch must not strand two empty columns), and the
   entries carry the ledger scale used in search/town results. */
.featured-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
}

@media (min-width: 640px) {
  .featured-grid {
    grid-template-columns: repeat(auto-fit, minmax(16rem, 24rem));
    justify-content: center;
  }
}

.featured-name { margin-top: 0; font-size: 1.3rem; }
.featured-name a { text-decoration: none; color: var(--ink); }
.featured-name a:hover { text-decoration: underline; text-decoration-color: var(--brass); }

.featured-meta {
  font-size: 0.85rem;
  color: var(--ink-mute);
  margin-bottom: var(--space-3);
  font-variant-numeric: tabular-nums;
}

.guide-link {
  display: block;
  font-family: var(--font-serif);
  font-size: 1.6rem; /* Rick, 2 Sep 2026 */
  font-weight: 600;
  text-decoration: none;
  color: var(--ink);
  padding: var(--space-4) var(--space-6) var(--space-4) 0;
  position: relative;
}

.guide-link::after {
  content: '\2192';
  position: absolute;
  right: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  color: var(--brass);
  transition: transform 200ms var(--ease-out);
}

.guide-link:hover { color: var(--green); }
.guide-link:hover::after { transform: translate(4px, -50%); }

li.faq-item { list-style: none; margin: 0; }

/* --- Admin navigation --- */
.admin-nav {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--space-6);
}

.admin-nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  list-style: none;
  margin: 0;
  padding: var(--space-3) 0;
}

.admin-nav li { margin: 0; }

.admin-nav a {
  text-decoration: none;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--ink-soft);
  padding: var(--space-2) 0;
  border-bottom: 2px solid transparent;
}

.admin-nav a:hover, .admin-nav a[aria-current="page"] {
  color: var(--green-deep);
  border-bottom-color: var(--brass);
}

.admin-nav-logout { margin-left: auto; }

/* --- Guides hub and articles --- */
.guides-hub-list { max-width: 52rem; margin-block: var(--space-6); margin-inline: auto; }
.guides-hub-list + .disclaimer-box { max-width: 52rem; margin-inline: auto; }
.guide-link-desc { margin: -0.4rem 0 var(--space-4); max-width: 46rem; font-size: 1.125rem; } /* 18px at the 16px root (Rick, 2 Sep 2026) */

.guide-article { padding-block: var(--space-6) var(--space-7); } /* phone top gap doubled, Rick 2 Sep 2026: it sat too tight under the header */

@media (min-width: 640px) {
  .guide-article { padding-block: var(--space-8) var(--space-7); }
}

/* Guide titles follow the site-wide rule: centred on desktop, left on
   mobile; the article body stays editorial left at every width. */
@media (min-width: 640px) {
  .guide-article > h1 { text-align: center; }
}

.guide-intro {
  font-family: var(--font-serif);
  font-size: 1.2rem;
  line-height: 1.65;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--line);
  padding-bottom: var(--space-5);
  margin-bottom: var(--space-5);
}

/* --- Package: static-pages (B5 /for-therapists, B6 /pricing,
       B7 /verification-standard, B9 /about, B16 /faq) ---
   Token-driven only: existing custom properties, no new colours or fonts. */

/* For-therapists hero, on white (crisper than the warm-bone interior). MLD
   uses the two-column .hero-grid below; WhatCounsellor still uses the older
   centred .hero-stack, so BOTH sets of rules stay live. Everything specific
   to the two-column version is keyed off .hero-grid rather than the page
   class, so a WhatCounsellor deploy inherits none of it by accident. */
/* Rick's specified hero tone for this page (18 Aug 2026), kept as the
   authored hex so it is exactly the colour he picked. */
.page-for-therapists .hero { background: #F7F6F0; }

/* Report section 13 asks for roughly 52% copy / 48% preview, against the
   home hero's 7/5. An even split gives the product-preview column the width
   a real .result-card needs (8rem photo plus a readable text column) without
   squeezing the headline. */
@media (min-width: 960px) {
  .page-for-therapists .hero-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* From 1200px the preview column stops holding a result card and starts
   holding the laptop shot, which fits its column exactly - so the column
   width IS the shot size, and every pixel the gap gives back buys screen.
   The 6rem gap is sized for two columns of TEXT; a laptop carries its own
   drop shadow and needs less air beside it, and the shot is short enough at
   an even split that it stops before the Verification panel - the one part
   of the profile this page is selling. 3rem widens BOTH columns by 24px, so
   the headline gets more room rather than less.
   Uneven splits were tried first and all of them cost the headline its
   second line: it breaks to three below a 526px column. */
@media (min-width: 1200px) {
  .page-for-therapists .hero-grid { gap: var(--space-7); }
}

/* The stacked hero wants a 3rem gap under the subhead before its lone CTA.
   The two-column version carries buttons AND the founding price in the same
   column, so it takes the standard rhythm instead. */
.page-for-therapists .hero-grid .hero-subhead { margin-bottom: var(--space-5); }

/* Report section 13 wants the three hero proof points in three desktop
   columns. The shared .index-list is two columns, which would strand the
   third on a row of its own, so this strip takes three - and the rule that
   drops the hairline above the first ROW has to count to three here too,
   or 03 grows a stray line above it. Scoped to the page, and only where a
   .hero-grid proves this is the two-column MLD version of the page. */
@media (min-width: 860px) {
  .page-for-therapists .trust-strip .index-list { grid-template-columns: repeat(3, 1fr); }
  .page-for-therapists .trust-strip .index-list > li:nth-child(-n+3) { border-top: 0; }
}

/* The two plan cards here are a straight side-by-side comparison, so they
   match height and their Apply buttons line up on the same line. Boxes that
   carry a billing radio group keep the shared align-items:start, because
   stretching those strands the controls in mid-air.
   WhatCounsellor's For Therapists page has no plan cards, so it inherits
   nothing from this. */
@media (min-width: 800px) {
  .page-for-therapists .join-two-boxes { align-items: stretch; }
  .page-for-therapists .tier-box { display: flex; flex-direction: column; }
  .page-for-therapists .tier-box .tier-features { flex: 1 0 auto; }
}

/* /pricing joined the equal-height rule on 18 Aug 2026 (report section 14)
   once MLD's rate boxes moved out of the cards and the newly qualified rate
   became a banner beneath them.
   THE CONDITION IS THE RATE BOXES, NOT THE PAGE. /pricing is one shared
   controller and one shared body class, and WhatCounsellor has its own
   pricing view whose cards DO still carry .tier-billing groups - a
   .page-pricing scope would have stretched them and stranded their controls,
   which is the exact failure the block above avoids. :has() states the real
   invariant instead, and self-corrects if either brand's cards change.
   Kept in its own block on purpose: a selector list is dropped whole when one
   of its selectors will not parse, so pairing this with the rule above would
   cost For Therapists its equal-height cards on any engine without :has().
   Without :has() this simply does not apply and the cards fall back to the
   shared align-items:start, which is the pre-18-Aug layout. */
@media (min-width: 800px) {
  .page-pricing .join-two-boxes:not(:has(.tier-billing)) { align-items: stretch; }
  .page-pricing .join-two-boxes:not(:has(.tier-billing)) .tier-box { display: flex; flex-direction: column; }
  .page-pricing .join-two-boxes:not(:has(.tier-billing)) .tier-features { flex: 1 0 auto; }
}

.hero-stack { padding-block: var(--space-7); }

.hero-stack-text {
  max-width: 46rem;
  margin-inline: auto;
  text-align: center;
}

.hero-stack-text h1,
.hero-stack-text .hero-subhead,
.hero-stack-text p { margin-inline: auto; }

/* For-therapists hero: a wider measure than the home hero so the subhead
   holds fewer, more confident lines. */
.page-for-therapists .hero-stack-text { max-width: 53rem; }
.page-for-therapists .hero-subhead { max-width: 53rem; margin-bottom: var(--space-7); }

.hero-stack-text .btn-row { justify-content: center; }

/* Mobile: sales-hero text reads left like every other title block. */
@media (max-width: 639.98px) {
  .hero-stack-text { text-align: left; }
  .hero-stack-text .btn-row { justify-content: flex-start; }
  .hero-stack-text .hero-secondary-link { align-self: flex-start; }
}
.hero-stack-photo { margin-top: var(--space-6); }
.hero-stack-photo img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }

.hero-secondary-link {
  align-self: center;
  font-weight: 600;
}

.under-hero {
  font-size: 0.9rem;
  color: var(--ink-mute);
  margin: var(--space-4) 0 0;
}

#what-you-get { scroll-margin-top: var(--space-6); }

/* Comparison table: the register column reads as the committed entry */
.compare-table th:last-child,
.compare-table td:last-child { background: var(--green-wash); }
.compare-table td:last-child { color: var(--ink); font-weight: 500; }
.compare-table td:first-child { color: var(--ink-mute); }
.compare-table th:last-child { color: var(--green-deep); border-bottom-color: var(--green); }
/* A feature table: the first column names the feature, so it keeps the main
   text colour instead of the muted row-label grey (/how-we-differ, 23 Sep 2026). */
.compare-table-feature td:first-child { color: var(--ink); }
@media (min-width: 640px) {
  .compare-table-feature th:first-child, .compare-table-feature td:first-child { width: 38%; }
  .compare-table-feature td { padding-block: var(--space-4); } /* Rick, 23 Sep 2026 */
}
/* A centred measure a little wider than --container-narrow, for a feature table under a stacked header. */
.measure-wide { max-width: 62rem; margin-inline: auto; } /* 62rem, Rick 23 Sep 2026 */
/* Tick and cross marks for a comparison table (/how-we-differ, 23 Sep 2026).
 Every mark carries a visually-hidden Yes or No beside it for screen readers. */
.cmp-mark {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1;
}
/* A true green on purpose (Rick, 23 Sep 2026): the brand's --green and --verified render teal-navy. A brand may retune --mark-yes. */
.cmp-mark-yes { background: var(--mark-yes, oklch(0.56 0.15 148)); color: #fff; }
.cmp-mark-no { background: var(--danger); color: #fff; }
/* Not determined from outside: a neutral dash, never a cross. */
.cmp-mark-unknown { background: var(--line-strong); color: var(--ink); }
.cmp-cell { display: flex; gap: var(--space-3); align-items: flex-start; }
.cmp-cell .cmp-mark { margin-top: 0.125rem; }
.compare-table-marks .cmp-col-mark { text-align: center; background: var(--danger-wash); } /* its own tone, apart from the Features column; grey hid the rules (Rick, 23 Sep 2026) */
/* Placed as a box, not on the text baseline, so it levels with the Register tick in every font. */
.compare-table-marks .cmp-col-mark .cmp-mark { display: grid; margin: 0.125rem auto 0; }
@media (min-width: 640px) {
  .compare-table-marks th:first-child, .compare-table-marks td:first-child { width: 34%; }
  .compare-table-marks .cmp-col-mark { width: 9rem; }
  /* A framed table with a rule either side of the mark column (Rick, 23 Sep 2026). */
  /* Rounded 20px frame (Rick, 23 Sep 2026): separate borders so the radius takes,
     and the corner cells are rounded too so their backgrounds stay inside it. */
  .compare-table-marks { border: 1px solid oklch(0.72 0.02 110); border-collapse: separate; border-spacing: 0; border-radius: 20px; }
  .compare-table-marks thead th:first-child { border-top-left-radius: 19px; }
  .compare-table-marks thead th:last-child { border-top-right-radius: 19px; }
  .compare-table-marks tbody tr:last-child td { border-bottom: 0; }
  .compare-table-marks tbody tr:last-child td:first-child { border-bottom-left-radius: 19px; }
  .compare-table-marks tbody tr:last-child td:last-child { border-bottom-right-radius: 19px; }
  /* The Features column on white (Rick, 23 Sep 2026). */
  .compare-table-marks th:first-child, .compare-table-marks td:first-child { background: #ffffff; }
  .compare-table-marks .cmp-col-mark { border-inline: 1px solid oklch(0.72 0.02 110); }
}
/* Phones: the mark sits under its column name, in line with the Register's tick. */
@media (max-width: 639.98px) {
  .compare-table-marks td.cmp-col-mark { text-align: left; }
  /* Each row card clearly framed on phones (Rick, 23 Sep 2026). */
  .compare-table.compare-table-marks tr { border-color: oklch(0.72 0.02 110); border-radius: var(--radius-lg); }
  .compare-table-marks td.cmp-col-mark .cmp-mark { margin-inline: 0; }
}

/* Phones: each row becomes its own small card, the first cell as its title and
   every other cell under the column name from its data-label (added 23 Sep
   2026 for /how-we-differ; three columns in 335px left one or two words a line). */
@media (max-width: 639.98px) {
  .compare-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .compare-table, .compare-table tbody, .compare-table tr, .compare-table td { display: block; width: 100%; }
  .compare-table tr {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden;
    margin-bottom: var(--space-4);
  }
  .compare-table td { border-bottom: 0; padding: var(--space-3) var(--space-4); }
  .compare-table td:first-child { color: var(--ink); font-weight: 700; padding-bottom: 0; }
  .compare-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--ink-soft);
    margin-bottom: 0.2rem;
  }
  .compare-table td:last-child[data-label]::before { color: var(--green-deep); }
  /* A feature table's card title keeps a gap above its tinted text. */
  .compare-table-feature td:first-child { padding-bottom: var(--space-3); }
}

.compare-note {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-soft);
  max-width: 62ch;
  margin-top: var(--space-5);
  margin-bottom: 0;
}

/* Founding offer table */
.offer-table strong { color: var(--green-deep); }

/* The founding offer table explains each tier via a "?" tooltip in column 1
   instead of a heavy paragraph block. No scroll wrapper (short content wraps
   on mobile), so the bubbles are never clipped. */
.tier-cell {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.offer-table .tip-icon {
  width: 1.35rem;
  height: 1.35rem;
  min-width: 1.35rem;
  min-height: 1.35rem;
  font-size: 0.75rem;
}

.offer-table .tip-bubble { text-transform: none; letter-spacing: normal; width: 18rem; }

@media (max-width: 560px) {
  .offer-table { font-size: 0.9rem; }
  .offer-table th, .offer-table td { padding: var(--space-2) var(--space-3); }
}

/* Verification standard page */
@media (min-width: 860px) {
  .index-list-stack { grid-template-columns: 1fr; }
}

.means-not-list { list-style: none; padding: 0; margin: 0; }

.means-not-list li {
  padding-left: 1.5rem;
  position: relative;
  margin-bottom: var(--space-3);
}

.means-not-list li::before {
  content: '';
  position: absolute;
  left: 0.1rem;
  top: 0.68rem;
  width: 0.75rem;
  height: 2px;
  background: var(--ink-mute);
}

.school-list { list-style: none; padding: 0; margin: var(--space-5) 0; max-width: 46rem; }

.school-list li {
  margin: 0;
  padding: var(--space-4) 0;
  border-top: 1px solid var(--line);
}

.school-list li:last-child { border-bottom: 1px solid var(--line); }

.school-list strong {
  font-family: var(--font-serif);
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--ink);
}

.school-list .school-detail { color: var(--ink-soft); }

/* Pricing page */
.tier-note {
  font-size: 0.85rem;
  color: var(--ink-mute);
  margin: var(--space-3) 0 0;
}

.tier-card .tier-note { margin-top: var(--space-3); }

.pricing-foot {
  border-top: 2px solid var(--brass);
  padding-top: var(--space-5);
  max-width: 60rem;
  margin-inline: auto;
}

.pricing-foot ul { list-style: none; padding: 0; margin: 0; }

.pricing-foot li {
  font-size: 0.95rem;
  color: var(--ink-soft);
  margin-bottom: var(--space-3);
  max-width: 75ch;
}

/* About page: founder sign-off styled as a ledger signature line */
.founder-sig {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-family: var(--font-serif);
  font-size: 1.2rem;
  margin-top: var(--space-5);
}

/* The name and the role are two LINES. They used to be two flex items with a
   <br> between them, which flex discards, so they sat side by side. */
.founder-sig-lines { display: block; }
.founder-sig-role { display: block; font-size: 0.95rem; color: var(--ink-soft); }

.founder-sig::before {
  content: '';
  display: block;
  width: 2rem;
  height: 1px;
  /* Sits on the NAME's optical centre now the byline is two lines. */
  margin-top: 0.85em;
  background: var(--brass);
  flex: none;
}

/* -------------------------------------------------------------------------
   Package: legal-contact (contact page routes list + form panel).
   Legal pages reuse the existing .prose layout and need no new styles.
   Token-driven only: no new colours, no new fonts.
   ------------------------------------------------------------------------- */

.contact-grid { align-items: start; }

/* Ruled list of contact routes, in the ledger hairline idiom. */
.contact-routes {
  list-style: none;
  margin: 0;
  padding: 0;
}

.contact-routes li {
  margin: 0;
  padding-block: var(--space-5);
  border-top: 1px solid var(--line);
}

.contact-routes li:first-child {
  border-top: 0;
  padding-top: 0;
}

.contact-routes li:last-child {
  border-top: 1px solid var(--brass-soft);
}

.contact-routes p {
  margin: 0;
  color: var(--ink-soft);
}

.contact-routes p strong { color: var(--ink); }

/* The form panel mirrors the verification-box treatment. */
.contact-form-panel {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-1);
}

/* Both form panels (contact + profile enquiry) share one heading size and
   run the heading straight into the form, no internal rule. */
.contact-form-panel h2, .enquiry-section h2 {
  margin-top: 0;
  font-size: 1.5rem;
  margin-bottom: var(--space-5);
}

/* Inline field validation message. */
.field-error { color: var(--danger); }

/* =========================================================================
   Package: search-towns
   Find a Therapist search page (/find-a-therapist) and town pages
   (/mld-therapist/[town]). Merged into main.css by the orchestrator.
   Token-driven only: existing custom properties, no new colours, no fonts.
   ========================================================================= */

.finder-field label {
  display: block;
  font-weight: 700;
  font-size: 0.95rem;
  margin-bottom: var(--space-2);
}

/* The location field is the ONLY growing item on the bar, and that is what
   keeps the row honest: whatever width is left over goes into it, so the bar
   always ends exactly where it should. Two rows could not do that - one of
   them always had slack, which became either an empty gap or an 848px box
   for an eight-character postcode. Its basis is small so it yields to the
   fixed controls first and only then takes the remainder - CRUCIALLY, flex
   line-breaking uses these basis sizes, so the basis total (not the grown
   widths) is what decides whether the row stays on one line. */
.finder-field-loc { flex: 1 1 6rem; min-width: 0; }
.finder-field-radius { flex: 0 0 auto; width: 8rem; }
.finder-filters-row > .btn { flex: 0 0 auto; }

/* Orientation line between the landing bar and the landing photo, centred on
   the same 60rem column so the three read as one composed block. Explicit
   max-width:none because the base p{max-width:70ch} pins a text-align:center
   paragraph left of centre (the recurring centred-paragraph gotcha). */
/* Check a Therapist utility strip (18 Aug 2026, report 5G): its home in
   the find journey now it is out of the global header. One quiet tinted
   row under the search card, in every search state, visually below the
   Search button in priority. On the landing state it sits on the same
   60rem column as the bar and photo. */
.check-strip {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  max-width: 60rem;
  margin: var(--space-4) auto 0;
  padding: var(--space-2) var(--space-4);
}

.check-strip-text {
  max-width: none; /* centred <p> gotcha */
  margin: 0;
  font-size: 0.92rem;
  color: var(--ink-soft);
}

.check-strip-link {
  flex: none;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--green-deep);
  text-decoration: underline;
  text-decoration-color: var(--link-underline);
  text-underline-offset: 0.2em;
}

.check-strip-link:hover { color: var(--green); }

@media (max-width: 599.98px) {
  .check-strip { flex-direction: column; text-align: center; padding-block: var(--space-3); }
}

/* --- Landing state photo: matches the landing bar's 60rem (Rick, 26 Jul) --- */
/* FOUNDING WINDOW: THE NOTICE AND THE PHOTOGRAPH AS ONE BAND (Rick, 2 Sep
   2026). Notice left, portrait photograph right, equal height: the copy sets
   the box's height and the photograph is cover-fitted to match it, so the
   two edges always line up whatever the wording does. Below 960px they stack
   as they always did. The row only exists while the window is open; the
   public page keeps the full-width landing photograph below, untouched. */
@media (min-width: 960px) {
  .finder-prelaunch-row {
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: var(--space-6);
    align-items: stretch;
    margin-block: var(--space-6) var(--space-7);
  }
  .finder-prelaunch-row .prelaunch-notice { max-width: none; margin: 0; }
  .finder-prelaunch-row .finder-landing-side {
    max-width: none;
    margin: 0;
    display: flex;
    flex-direction: column;
  }
  .finder-prelaunch-row .finder-landing-side img {
    flex: 1 1 0;
    min-height: 0;
    height: auto;
    object-fit: cover;
  }
}

.finder-landing {
  max-width: 60rem;
  margin-block: var(--space-5) var(--space-7);
  margin-inline: auto;
}
.finder-landing .ledger-caption { display: flex; justify-content: center; max-width: none; }
.finder-landing .ledger-caption::after {
  content: '';
  display: block;
  width: 2rem;
  height: 1px;
  background: var(--brass);
}

/* --- Layout: filters live in the search panel; results own the full width --- */
.finder-layout {
  margin-bottom: var(--space-7);
}

/* --- The sticky filter bar ------------------------------------------------
   The bar has to keep up with the results, or a visitor who scrolls loses
   every control and has no way back short of scrolling to the top - which is
   exactly what happened to the first patient who tested the page.
   position: sticky only travels inside its own containing block, so the bar
   sits in .finder (which spans the controls AND the results) rather than
   inside the search form, which ends a few pixels below it. Its controls stay
   wired to the form with form="search-form".
   The z-index is not decorative: Leaflet's panes sit at 400+, and this file
   already records two shipped bugs from losing that stacking fight. */
.finder {
  position: relative;
}

.finder-bar {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  padding: var(--space-4) var(--space-5);
  margin-block: var(--space-5) var(--space-6);
}

/* Before a search has run the bar is the page's hero: 60rem = the 960px Rick
   specified (26 Jul), controls at the site's hero-search height rather than
   the compact toolbar height, and the landing photo matches its width so the
   two read as one composed block. */
.finder-bar-landing { max-width: 60rem; margin-inline: auto; --filter-control-h: 3.4rem; }

/* It sticks only while COLLAPSED. Open, it is 670px of panel, and pinning
   that to the top of the viewport would bury the results it exists to
   narrow; so it drops back into flow and main.js scrolls it into view, which
   also stops the button opening a panel the visitor cannot see.
   Gated on BOTH :has() and the js-finder class main.js sets, because each
   guard covers a way the bar could otherwise strand the panel: without
   :has() the panel is always open, so the bar would be permanently pinned at
   full height; without JS nothing scrolls the panel into view, so opening it
   while scrolled would render it off-screen above the visitor. In either
   case a bar that does not stick is the honest degradation. */
/* Not below 720px. There the seven controls wrap to about 400px of bar, and
   pinning half a phone screen costs more than it saves - the bar now starts
   at 469px on a 375px-wide phone, so it is on screen when you arrive without
   needing to be nailed there. A compact stuck state (condition + All filters
   only) would earn stickiness back on a phone; that needs stuck-detection
   and is its own piece of work. */
@media (min-width: 720px) {
  @supports selector(:has(*)) {
    .js-finder .finder:has(.finder-more:not([open])) .finder-bar {
      position: sticky;
      /* Parked under the pinned compact site header, not at the viewport
         top, where the header band would bury it (18 Aug 2026). */
      top: var(--header-offset);
      z-index: 500;
    }
  }
}

/* --- Filter sheet: a details element, collapsed by default so it works
       everywhere without JS; main.js opens it on wide viewports --- */
.filter-sheet {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}

.filter-sheet summary {
  cursor: pointer;
  list-style: none;
  font-weight: 700;
  padding: var(--space-4) var(--space-5);
  position: relative;
}

.filter-sheet summary::-webkit-details-marker { display: none; }

.filter-sheet summary::after {
  content: '+';
  position: absolute;
  right: var(--space-5);
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-serif);
  font-size: 1.4rem;
  color: var(--brass);
}

.filter-sheet[open] summary::after { content: '\2212'; }
.filter-sheet[open] summary { border-bottom: 1px solid var(--line); }

.filter-body { padding: var(--space-5); }

.filter-group {
  border: 0;
  padding: 0;
  margin: 0 0 var(--space-5);
  min-width: 0;
}

/* A fieldset's legend is anchored to the top EDGE of the border box and
   ignores the fieldset's top padding, so inside a padded card it rendered
   sitting on the card's rim rather than within it. Floating takes it out of
   that special rendering model and into normal flow, where the 20px padding
   contains it like everything else. */
.filter-group legend {
  float: left;
  width: 100%;
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1.3;
  padding: 0;
  margin-bottom: var(--space-3);
}

/* The float needs clearing, or the first sibling's box starts alongside it. */
.filter-group legend + * { clear: both; }

.filter-group .checkbox-row {
  margin-bottom: var(--space-3);
  align-items: center;
  gap: var(--space-2);
}

.filter-group .checkbox-row input[type="radio"] {
  width: 1.1rem;
  height: 1.1rem;
  flex: none;
  margin: 0;
  accent-color: var(--green);
}

.filter-group .checkbox-row input[type="checkbox"] { margin-top: 0; }
.filter-group .checkbox-row label { font-size: 0.95rem; }

.filter-reset { margin: var(--space-4) 0 0; }

/* --- Tooltips (filter toggles, Featured label): CSS-only bubble --- */
.tip {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.tip-icon {
  display: inline-grid;
  place-items: center;
  width: 1.5rem;   /* 24px min target (WCAG 2.5.8) */
  height: 1.5rem;
  min-width: 24px;
  min-height: 24px;
  border: 1px solid var(--ink-mute);
  border-radius: 50%;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 0.8rem;
  line-height: 1;
  color: var(--ink-mute);
  cursor: help;
}

.tip-bubble {
  position: absolute;
  z-index: 30;
  bottom: calc(100% + 0.55rem);
  left: 50%;
  transform: translateX(-50%);
  width: 15rem;
  max-width: 74vw;
  background: var(--green-deep);
  color: var(--on-green);
  font-family: var(--font-sans);
  font-size: 0.92rem;
  font-weight: 400;
  line-height: 1.55;
  letter-spacing: normal;
  text-transform: none;
  white-space: normal;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  opacity: 0;
  visibility: hidden;
  /* display:none as well as hidden (21 Sep 2026): an absolutely positioned
     bubble that is merely invisible still widens the page, and on a phone
     the three Services bubbles pushed the profile to 603px of scrollable
     width at a 375px viewport. Gone from layout until shown. */
  display: none;
  transition: opacity 160ms var(--ease-out);
  pointer-events: none;
}

.tip:hover .tip-bubble,
.tip:focus .tip-bubble,
.tip:focus-within .tip-bubble {
  display: block;
  opacity: 1;
  visibility: visible;
}

/* Escape-dismissed (WCAG 1.4.13); re-arms on pointer/focus leave via main.js */
.tip.tip-dismissed .tip-bubble { display: none !important; opacity: 0 !important; visibility: hidden !important; }

/* --- Results microcopy --- */
/* Count on the left, sort on the right: a sort is not a filter, so it belongs
   with the results it orders rather than in the filter panel. */
.results-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-5);
  margin-bottom: var(--space-5);
}

.results-count {
  font-family: var(--font-serif);
  font-size: 1.3rem;
  font-weight: 600;
  margin-bottom: 0;
  font-variant-numeric: tabular-nums;
}

.results-sort-control {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.results-sort-control label {
  font-weight: 700;
  font-size: 0.95rem;
  white-space: nowrap;
}

/* Width only - see the one select rule; height + centring match the finder
   bar controls exactly, so "Nearest first" is the same species of control
   as "Need help with?". */
.results-sort-control select {
  width: auto;
  height: var(--filter-control-h, 2.75rem);
  padding-block: 0;
  align-items: center;
  align-content: center;
  white-space: nowrap;
}

/* Wraps to its own line inside the head, so the head's gap owns the spacing. */
.results-sort { flex-basis: 100%; margin: 0; }

/* --- Result card extras (shared partial) --- */
/* View profile floats at the right, centred on the photo's band, without
   reserving a full-height column - so the chips tile the full card width. */
.result-card { position: relative; }

.result-cta {
  position: absolute;
  right: var(--space-6);
  top: calc(var(--space-6) + 4rem); /* padding + half the 8rem photo */
  transform: translateY(-50%);
}

/* Keep every line that shares the floating button's vertical band clear of it.
   The rating and the Google note belong here too: they sit in the same band and
   only ever escaped because they were short. Both lists must stay in step, or a
   line gets the clearance on desktop and keeps it once the button unfloats. */
.result-main .result-name,
.result-main .result-meta,
.result-main .result-rating,
.result-main .result-google-note { padding-right: 10.5rem; }

@media (max-width: 540px) {
  .result-cta { position: static; transform: none; margin-top: var(--space-3); }
  .result-cta .btn { display: block; width: 100%; }
  .result-main .result-name,
  .result-main .result-meta,
  .result-main .result-rating,
  .result-main .result-google-note { padding-right: 0; }
}

a.chip { text-decoration: none; }
a.chip:hover { border-color: var(--verified); }

/* "Matches your search: ..." - the one line that says why this member is in
   these results. Its own line rather than more chips, and quieter than the
   chip row above it: the dated verification chip is the strongest claim on
   the card and nothing added later may out-shout it. Full width, because it
   sits below the floating View profile button's band. */
.result-matches {
  margin: var(--space-2) 0 0;
  max-width: none;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

.result-matches-label {
  font-weight: 600;
  color: var(--ink);
}

/* Shortlist tick. Ships hidden (the `hidden` attribute) and main.js reveals
   it, so a no-JS visitor never meets a control that would do nothing. Sits
   under the floating View profile button in the same column, which is why it
   needs no positioning of its own and follows that button's mobile collapse
   for free. */
.compare-tick {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  margin-top: var(--space-2);
  font-size: 0.85rem;
  color: var(--ink-soft);
  cursor: pointer;
  user-select: none;
}

/* A class selector beats the user agent's [hidden] { display: none }, so
   `display: flex` above silently defeated the attribute the markup and the
   gate both rely on: the tick rendered for a no-JS visitor and beside a
   two-result search, which is exactly what it must never do. Any component
   that sets display and ships hidden needs this line. */
.compare-tick[hidden] { display: none; }

.compare-tick input { cursor: pointer; }
.compare-tick.is-disabled { opacity: 0.45; cursor: default; }
.compare-tick.is-disabled input { cursor: default; }

/* The tray. Fixed to the bottom so it stays reachable while the visitor keeps
   scrolling results, and translated out of the way entirely when the
   shortlist is empty - a permanently parked bar over the last result row is
   just lost screen. */
.compare-tray {
  position: fixed;
  z-index: 500; /* above the Leaflet map, which sits at 400+ */
  left: 50%;
  bottom: var(--space-4);
  transform: translate(-50%, calc(100% + var(--space-6)));
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-2);
  transition: transform 0.2s var(--ease-out);
  max-width: calc(100vw - var(--space-4) * 2);
}

.compare-tray.is-open { transform: translate(-50%, 0); }

.compare-tray-count {
  margin: 0;
  max-width: none;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink);
}

.compare-tray-clear {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 0.85rem;
  color: var(--ink-mute);
  text-decoration: underline;
  cursor: pointer;
}

/* One selected is not a comparison, so the button reads as not-yet rather
   than broken (main.js also blocks the click). */
.compare-tray-go.is-disabled {
  opacity: 0.5;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .compare-tray { transition: none; }
}

.result-featured-flag { margin-bottom: var(--space-2); }

/* The Featured chip sits at the card's left edge; anchor its bubble left so
   the disclosure never clips off-screen on narrow phones. */
.result-featured-flag .tip-bubble { left: 0; transform: none; }

/* Trust-strip tip (the Aftercare recognition note): the icon ends its line,
   so a bubble anchored to the ICON depends on where the line happens to
   wrap - centred it poked past the right edge of a phone screen (a hidden
   bubble still widens the scrollable area, which showed as a sideways-
   scroll gap on Rick's phone, 18 Aug 2026), and icon-right-anchored it
   clipped off the LEFT edge instead. Anchor it to the LIST ITEM, whose
   left edge is always on screen and whose width always fits the bubble. */
.trust-strip .index-list > li { position: relative; }
.trust-strip .tip { position: static; }
/* The li is still the horizontal anchor - its left edge is always on screen
   and its width always fits the bubble, which is what stopped the sideways
   scroll on Rick's phone. But it was ALSO the vertical anchor, and
   bottom:100% on a tall two-column row put the bubble above the whole row,
   roughly 300px from the icon that opened it (Rick, 18 Aug 2026). Opening
   DOWNWARD from the row's baseline puts it back under the icon, which ends
   the row's last line, and it still cannot clip at either edge. */
.trust-strip .tip-bubble {
  left: 0;
  right: auto;
  top: calc(100% - var(--space-4));
  bottom: auto;
  transform: none;
  max-width: 100%;
}

/* --- Notify-me block (reuses the cta-band component) --- */
.notify-block {
  margin-block: var(--space-6);
  margin-inline: auto;
  max-width: 52rem;
  scroll-margin-top: var(--space-5);
}

.notify-block .notify-form { max-width: 34rem; }
.notify-block .search-box-row { margin-top: var(--space-2); }
.notify-block .notify-consent { color: var(--on-green-soft); }
.notify-block .notify-consent a { color: var(--on-green); text-decoration-color: var(--brass-on-dark); }
.notify-block .form-success { border-color: var(--brass-on-dark); }

/* --- Pre-launch search notice (app/lib/prelaunch.php) ---------------------
   Sits inside .cta-band.notify-block, so it inherits the green band, the
   brass button and the max-width. Only three things are new: two labelled
   fields side by side, the label colour, and the Follow row.

   THE LABELS NEED THE OVERRIDE. Global .field label is var(--ink), which is
   near-black on a dark green band - the very failure the brass ruling of
   22 Aug caught on the badge. --on-green is the band's own foreground. */
.prelaunch-notice .prelaunch-fields {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.prelaunch-notice .field { margin-bottom: 0; }
.prelaunch-notice .field label { color: var(--on-green); }

/* The submit sits in its own paragraph, whose top margin is 0 by default -
   which put the button's edge flat against the bottom of the First name box
   with no gap at all (measured in Chrome, 22 Aug 2026). It needs more air
   than the gap BETWEEN the two fields, not less, or the button reads as a
   third field. */
.prelaunch-notice .prelaunch-submit { margin-top: var(--space-4); }

/* Two columns only where both fields still hold a real name and a real
   email side by side. Below that they stack, which is also what every
   phone width gets. */
@media (min-width: 34rem) {
  .prelaunch-notice .prelaunch-fields { grid-template-columns: 1fr 1fr; }
}

/* The Follow links: a small icon plus the words, never an icon alone -
   "an icon with no label is a guess" and these two are the only outbound
   links on the block. Drawn inline (social_icon_path), so nothing is
   requested from Instagram or Facebook and no visitor is reported to them.
   44px of vertical hit area for a thumb, and wrap rather than scroll. */
.prelaunch-social {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
}

.prelaunch-social a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  color: var(--on-green);
  text-decoration-color: var(--brass-on-dark);
}

.prelaunch-social a:hover,
.prelaunch-social a:focus-visible { text-decoration-color: var(--on-green); }

.prelaunch-social svg { flex: none; }

/* One shared control height for every finder control, INCLUDING the sort
   dropdown beside the result count - it sat at 55px against the bar's 44px
   and read as a different species (Rick, 26 Jul). On .finder so the landing
   variant can override it for the hero. */
.finder { --filter-control-h: 2.75rem; }

/* THE bar: where, how far, what for, the quick toggles, More filters, and
   Search always at the far right (Rick, 26 Jul). One shared control height is
   what makes seven controls read as one bar rather than a search box with
   filters bolted underneath - which is how the old two-row version read, and
   why it got skipped entirely. The 12px gap is deliberate: it is what keeps
   the row on ONE line at the container width even with the badge showing and
   "Lymphoedema management" selected - the two states that used to tile
   "All filters" onto a second row. */
.finder-filters-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
}

/* Fixed-height controls need their block padding REMOVED, not fought. The
   base input rule carries 12px top and bottom; inside a 44px border-box that
   left a ~15px window for a 30px line, so the text rendered dropped and
   clipped (Rick's 26 Jul screenshot). With no block padding the fixed height
   centres a single line natively in inputs, and align-content centres the
   option text that base-select renders as real DOM. */
.finder-filters-row input[type="search"],
.finder-filters-row select,
.finder-filters-row > .btn {
  height: var(--filter-control-h);
  padding-block: 0;
}

.finder-filters-row > .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* base-select gotchas, MEASURED not assumed (26 Jul): the select renders as
   inline-FLEX, so vertical centring needs align-ITEMS - align-content alone
   leaves the value slot at the top, which is the "still not centred" bug
   Rick caught twice. And the selected option is real DOM, so in a
   width-capped select a long label WRAPS and paints below the border box.
   One line, centred, always. */
.finder-filters-row select {
  align-items: center;
  align-content: center;
  white-space: nowrap;
}

/* Sizes to its content (the label or the selected option, whichever is
   wider), because ellipsising the site's most important filter would be
   worse than letting the location field give up some slack. */
.finder-field-condition { flex: 0 0 auto; }
.finder-field-condition select { width: 100%; min-width: 11rem; }
.finder-field-condition label { white-space: nowrap; }

/* The bar's tooltips open DOWNWARD. Pinned to the top of the viewport there
   is nothing above the bar to open into. Scoped to the bar, because .tip is
   shared with result cards and both brands' pricing tables. */
.finder-bar .tip-bubble {
  bottom: auto;
  top: calc(100% + 0.55rem);
}

/* NOTE: the two surface toggles are NOT hidden on narrow viewports, however
   tempting the ~40px of sticky-bar height is. Their real <input> lives here
   and the panel copies are bare mirror labels, so a display:none here takes
   the only focusable control for Home visits and Taking new clients out of
   the tab order entirely - pointer-operable only, WCAG 2.1.1. The row already
   wraps, so on a phone they simply take their own line. */

/* On a phone every field takes its own full-width line - the standard calm
   mobile form - except the two toggles, which pair up. A first attempt kept
   desktop-ish widths and left the radius stranded alone at 128px, which is
   the exact stranded-control sin the design rules ban. */
@media (max-width: 559.98px) {
  .finder-field-loc,
  .finder-field-condition,
  .finder-more,
  .finder-filters-row > .btn { flex: 1 1 100%; }
  .finder-field-radius { flex: 1 1 100%; width: auto; }
  .finder-field-condition select { min-width: 0; }
  .finder-more > summary { width: 100%; justify-content: center; }
}

/* Labelled filter control: the label is visible now (an unlabelled dropdown
   reading "Any reason" tells you nothing about what it filters). */
.finder-filter-field label {
  display: block;
  font-weight: 700;
  font-size: 0.95rem;
  margin-bottom: var(--space-2);
}

.finder-filters-row > .tip { height: var(--filter-control-h); }

/* More filters expands IN FLOW, on its own full-width line of the filter row.
   It was an overlay anchored to the button; that was wrong twice over. The
   map's Leaflet panes sit at z-index 400+, so an overlay under them can never
   win a stacking fight, and a panel anchored to a control halfway down the
   page runs off the bottom of the viewport as soon as it holds more than a
   few filters - which is the exact direction this list is meant to grow.
   In flow it costs a downward push of the map and results, and in exchange it
   can never underlap anything, never leaves the viewport, and behaves
   identically at every width with no JS at all. */
/* Sits at the right-hand end of the SAME row as the other controls. The panel
   it opens is a sibling of that row, not a child of this element, so the
   button can be inline while the panel spans the full width beneath - see the
   :has() rules below. */
/* NO margin-left:auto. An auto margin eats free space BEFORE flex-grow gets
   a look in, so pushing this button to the right edge would starve the
   location field of the slack it is there to absorb - and put the gap back
   between the last chip and the button, which is what it looked like before
   (535px of nothing at 1280). It sits where it falls, next to the filters it
   opens. */
.finder-more {
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

/* The button keeps its place at the right of the panel; only the body below
   it is full width, so opening the panel never moves the control you clicked. */
.finder-more > summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: fit-content;
  height: var(--filter-control-h);
  padding: 0 2.2rem 0 var(--space-4);
  border: 1px solid var(--filter-line);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 0.97rem;
  white-space: nowrap;
}

.finder-more[open] > summary { border-color: var(--green); }
.finder-more[open] > summary { border-bottom: 1px solid var(--green); }

.finder-more > summary::after { right: var(--space-4); }

.filter-count {
  display: inline-grid;
  place-items: center;
  min-width: 1.4rem;
  height: 1.4rem;
  padding-inline: 0.35rem;
  border-radius: var(--radius-stamp);
  background: var(--green);
  color: var(--on-green);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

/* The badge SLOT is always rendered and merely invisible at zero, so the
   button is the same width whether or not filters are applied. When it
   appeared from nowhere it widened the button, tipped the row past the
   container, and tiled "All filters" onto its own line (Rick, 26 Jul). */
.filter-count-empty { visibility: hidden; }

/* The panel: a sibling of the filter row, revealed by :has() when the details
   above is open. Progressive enhancement in the honest direction - a browser
   without :has() never hides it, so every filter stays reachable rather than
   being sealed behind a toggle that does nothing.
   ONE flat panel, no headed sub-sections (Rick, 26 Jul: with five controls
   plus the treatments band, "Your therapist" and "Appointments" were
   furniture): a row of tick filters, a row of dropdowns, the treatments
   band, the provenance line. */
.finder-bar > .filter-body {
  margin-top: var(--space-4);
  padding: var(--space-5) 0 0;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

@supports selector(:has(*)) {
  .finder-bar:has(.finder-more:not([open])) > .filter-body { display: none; }
}

/* --- Pre-launch notice dialog (layout.php + main.js) ---
   Native <dialog>: the browser centres it and paints ::backdrop; these rules
   only dress it in the .welcome-* single-message vocabulary. No transition,
   so prefers-reduced-motion needs no override. z-index is irrelevant to a
   modal dialog (top layer), so Leaflet's 400+ and the bar's 500 cannot sit
   over it. */
/* Browsers without <dialog> support have no UA rule hiding a closed dialog,
   so without this the element renders as a permanent inline box (and its
   method="dialog" button degrades to a query-stripping GET). Supporting
   browsers are unaffected: showModal() sets the open attribute. */
dialog:not([open]) { display: none; }

.notice-modal {
  /* Rick, 23 Aug 2026: 40rem wide. min() so a phone never scrolls sideways. */
  min-width: min(40rem, calc(100vw - 2 * var(--space-5)));
  max-width: min(40rem, calc(100vw - 2 * var(--space-5)));
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2, var(--shadow-1));
  padding: clamp(1.5rem, 4vw, 2.5rem);
  text-align: center;
}

.notice-modal::backdrop { background: rgba(26, 34, 30, 0.55); }

.notice-modal h2 { margin: 0 0 var(--space-3); }

/* 1.1rem, Rick 23 Aug 2026 (supersedes his 17 Aug 16px call), left-aligned. */
.notice-modal p {
  max-width: none;
  margin: 0 0 var(--space-4);
  font-size: 1.1rem;
  line-height: 1.6;
  text-align: left;
}

.notice-modal p:last-of-type { margin-bottom: var(--space-5); }

.notice-modal form { margin: 0; }

/* CTA link + close button pair (rendered when the dialog has a link set):
   centred in the dialog, no inherited top margin. */
.notice-modal-actions { justify-content: center; margin-top: 0; }

/* The X close: the standard escape hatch in the standard corner. The dialog
   is position:fixed by UA default, so it is its own containing block; 44px
   square keeps the target phone-sized without crowding the heading. */
.notice-modal-x { position: absolute; top: var(--space-2); right: var(--space-2); }
.notice-modal-x button {
  width: 44px;
  height: 44px;
  border: 0;
  background: none;
  color: var(--ink-soft, var(--ink));
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  border-radius: var(--radius-sm, 6px);
}
.notice-modal-x button:hover, .notice-modal-x button:focus-visible { color: var(--ink); background: var(--tint, rgba(0, 0, 0, 0.05)); }

/* --- Image dialog (26 Aug 2026): an inline "i" button that opens an
   example image in a modal. First use: the reviews-badge example on the
   membership cards (partials/reviews_badge_tip.php). Same native <dialog>
   primitive as the notice dialog above - the browser centres it, paints
   ::backdrop, and carries Escape and the focus trap; main.js opens it from
   data-dialog-open and closes it on a backdrop click. --- */

/* The trigger reuses the .tip-icon circle as a real <button>: strip the
   UA button chrome, keep the circle. Sits inline after a tick-list line. */
button.img-dialog-open {
  background: none;
  padding: 0;
  margin-left: var(--space-1);
  vertical-align: text-bottom;
  cursor: pointer;
  font-style: italic; /* <button> does not inherit .tip-icon's serif italic look by default */
}

.img-dialog {
  max-width: min(32rem, calc(100vw - 2 * var(--space-5)));
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2, var(--shadow-1));
  padding: clamp(1.25rem, 4vw, 2rem);
  text-align: center;
}

.img-dialog::backdrop { background: rgba(26, 34, 30, 0.55); }

/* Room on the right so the title never sits under the X. */
.img-dialog h2 {
  margin: 0 0 var(--space-4);
  font-size: 1.15rem;
  padding: 0 2.25rem;
}

.img-dialog img {
  max-width: 100%;
  height: auto;
  border-radius: var(--radius);
}

/* The X close, same shape as the notice dialog's. */
.img-dialog-x { position: absolute; top: var(--space-2); right: var(--space-2); margin: 0; }
.img-dialog-x button {
  width: 44px;
  height: 44px;
  border: 0;
  background: none;
  color: var(--ink-soft, var(--ink));
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  border-radius: var(--radius-sm, 6px);
}
.img-dialog-x button:hover, .img-dialog-x button:focus-visible { color: var(--ink); background: var(--tint, rgba(0, 0, 0, 0.05)); }

/* --- Member photo gallery (1 Sep 2026): the grid on a public profile and
   the lightbox it opens. Standard members show up to six photographs,
   Featured up to twenty; the grid draws six tiles and turns the last one
   into "+N more" when there are more behind it.

   The gallery EXTENDS .img-dialog rather than replacing it: the dialog
   markup carries both classes, so the border, the backdrop, the padding
   and the X are inherited and only the wider frame and the paging controls
   are new. There is no lightbox library on this site and this is why one
   was never needed. --- */

/* --- The profile contents menu (1 Sep 2026): a quiet row of jump links
   directly under the masthead, on profiles long enough to be worth a map.

   NOT STICKY, on purpose. The site header is already pinned and, below
   960px, so is the Book online bar at the foot of the screen; a third fixed
   strip would close a phone screen in from both ends. This scrolls away with
   the masthead, which is where a reader looks for a contents list.

   IT WRAPS, it never scrolls sideways. That is the standing rule for every
   menu here, and a horizontal scroller is exactly what a row of five links
   turns into on a narrow screen if you let it. --- */
.profile-nav {
  /* A BAND OF ITS OWN. This was --surface, which is exactly what the
     masthead above it uses, so the two ran together as one pale block
     divided by a hairline and the menu could be missed entirely. That also
     broke the standing rule that no two consecutive bands may share a
     background (Rick, 25 Aug 2026); he caught it by eye on the live page.

     The fallback is --paper-2 rather than a literal, so a brand with no sand
     in its pack gets its own neutral rather than inheriting this one. */
  background: var(--band-sand, var(--paper-2, #eaeaea));
  border-bottom: 1px solid var(--line);
}

.profile-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* space-2, not space-4, since 2 Sep 2026: Rick found the band too thick,
     and every pixel here is a pixel of the action card under the fold. */
  gap: var(--space-1) var(--space-6);
  padding-block: var(--space-2);
}

.profile-nav li { margin: 0; }

/* The span is the pitch page's laptop specimen, where nothing may be a real
   link but the menu still has to look like the menu it stands in for. */
.profile-nav a,
.profile-nav li > span {
  display: inline-block;
  /* 44px of height for a thumb, made of padding rather than a set height, so
     a wrapped second line still reads as one row of links. */
  padding: 0.6rem 0;
  font-size: 0.95rem;
  /* The FULL ink, not the soft grey. On sand the soft grey is about 6:1 and
     reads washed out; the full ink is about 11:1 and is what makes weight
     600 look deliberate rather than merely heavy. */
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

/* On a desktop the links are a mouse target, so the 44px thumb height above
   is not needed and the band slims to one comfortable row. AFTER the base
   rule on purpose: same specificity, so the later one wins. */
@media (min-width: 960px) {
  .profile-nav a,
  .profile-nav li > span { padding: 0.45rem 0; }
}

.profile-nav a:hover,
.profile-nav a:focus-visible {
  color: var(--ink);
  border-bottom-color: var(--accent, var(--ink));
}

/* The site header is sticky, so without this the reader jumps to a heading
   that lands BEHIND it. Same value and same pattern as #what-you-get and the
   dashboard sections already use.

   Deliberately no scroll-behavior: smooth here. Every other anchor on this
   site jumps instantly, and making one page behave differently is worse than
   either choice on its own. Smooth scrolling is a site-wide decision, not a
   detail of this feature. */
.profile-main .profile-section { scroll-margin-top: var(--space-6); }

/* The footer's social icons and the country control share one row, so the
   switcher reads as placed beside them rather than dropped underneath.
   Wraps rather than squashing on a narrow phone. */
.footer-social-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  /* THE SPACE ABOVE BELONGS TO THE ROW, NOT TO ONE CHILD IN IT.
     .footer-social carries its own margin-top from when it stood alone, and
     inside a centred flex row that margin pushes the icons' box down while
     the country control has none, so the control sat visibly higher than the
     icons beside it. Rick spotted it straight away. The row takes the
     spacing, and the child's own margin is cleared below. */
  margin-top: var(--space-5);
}

/* Cleared here rather than at .footer-social itself, so the icons keep their
   original spacing on any brand or page that still renders them outside this
   row. */
.footer-social-row .footer-social { margin-top: 0; }

/* --- Back to top (1 Sep 2026): one small button, bottom right, on every
   page once the reader is a screen down. The site has genuinely long pages -
   the guides, a busy town page, a full member profile - and none of them had
   a way back up but the scrollbar.

   `hidden` IS THE NO-JAVASCRIPT STATE. A fixed button that nothing can
   dismiss or operate is worse than no button, which is the same contract
   .profile-sticky-cta carries two rules down. --- */
.back-to-top {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: 40;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line-strong, var(--line));
  border-radius: 50%;
  background: var(--surface, #fff);
  color: var(--ink);
  cursor: pointer;
  box-shadow: var(--shadow-2, var(--shadow-1));
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 160ms ease, transform 160ms ease;
}

.back-to-top[hidden] { display: none; }
.back-to-top.is-shown { opacity: 1; transform: none; }

.back-to-top:hover,
.back-to-top:focus-visible { background: var(--tint, rgba(0, 0, 0, 0.05)); }

/* THE LIFT IS NOT IN CSS. Two different fixed bars can own the bottom of
   the screen - the profile's action bar, and the cookie bar on every page a
   first-time visitor opens - and either or both may be there. main.js
   measures whichever are actually visible and sets `bottom` directly, so
   this cannot go stale when a third one is added. */

@media (prefers-reduced-motion: reduce) {
  .back-to-top { transition: none; transform: none; }
}

/* .gallery-grid and .gallery-grid-shot were retired on 2 Sep 2026 with the
   in-page profile grid: the masthead cluster (.profile-head-gallery) and the
   pitch page's laptop specimen both draw .gallery-tile in a grid of their
   own. */

.gallery-tile {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  /* The box is 4:3 whatever the photograph is, and the image covers it. That
     is what turns a mixed bag of portrait and landscape phone photographs
     into one tidy grid instead of a ragged wall. */
  aspect-ratio: 4 / 3;
  background: var(--tint, rgba(0, 0, 0, 0.04));
}

.gallery-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 180ms ease;
}

@media (hover: hover) {
  .gallery-tile:hover img { transform: scale(1.03); }
}

@media (prefers-reduced-motion: reduce) {
  .gallery-tile img { transition: none; }
  .gallery-tile:hover img { transform: none; }
}

.gallery-tile:focus-visible {
  outline: 2px solid var(--accent, var(--ink));
  outline-offset: 2px;
}

/* The "+N more" tile. A scrim over the last thumbnail rather than a separate
   empty box, so the grid stays a complete rectangle and the reader can still
   see there is a photograph underneath the number. */
.gallery-tile-more::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(15, 33, 55, 0.58);
}

.gallery-more-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  color: #fff;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* --- The lightbox itself --- */

.gallery-dialog {
  /* Much wider than the plain .img-dialog, which is sized for a small
     example graphic. A room photograph needs the screen. */
  max-width: min(64rem, calc(100vw - 2 * var(--space-4)));
  width: 100%;
  padding: var(--space-3);
}

.gallery-dialog-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Cap the picture at most of the viewport height so a portrait photograph
     cannot push the caption and the counter off the bottom of the screen. */
  max-height: 72vh;
}

.gallery-dialog-img {
  max-width: 100%;
  max-height: 72vh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius);
}

/* THE RESET AT THE TOP OF THIS FILE SETS img { display: block }, AND AN
   AUTHOR RULE BEATS THE USER AGENT'S [hidden] { display: none }. Without
   this line every photograph in the gallery renders at once, side by side,
   and the lightbox shows the whole set instead of one. Found by LOOKING at
   the page: the break tests read the markup, and the markup was correct.

   Same trap, same fix as .compare-tick[hidden], .join-panel[hidden],
   .field-error[hidden] and .cookie-bar[hidden] elsewhere in this file.

   It also restores the reason the images are safe to ship: display:none is
   what stops a browser fetching the five photographs nobody has paged to
   yet, so a twenty-photograph gallery costs a visitor one image, not
   twenty. */
.gallery-dialog-img[hidden] { display: none; }

.gallery-dialog-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  text-align: left;
  font-size: 1.2rem;
  /* THE CENTRED <p> GOTCHA, the same one .sample-flag p carries a note about.
     The base stylesheet caps every <p> at 70ch for readable prose, so this
     row was 70ch wide inside a dialog up to 64rem, and space-between put the
     counter in the middle of the picture instead of at its right edge. */
  max-width: none;
}

.gallery-dialog-caption {
  color: var(--ink);
  /* Take the room the counter does not need, so the counter is pushed to the
     far edge even when the caption is a couple of words. */
  flex: 1 1 auto;
}

.gallery-dialog-count {
  color: var(--ink-soft, var(--ink));
  white-space: nowrap;
  /* Right-aligned against the edge of the picture, not floating mid-frame. */
  text-align: right;
  flex: 0 0 auto;
}

/* Paging arrows, sitting over the picture at the vertical middle. 44px is
   the minimum reliable touch target, and these are the controls most likely
   to be used with a thumb. */
.gallery-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface, #fff);
  color: var(--ink);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--shadow-1);
}

.gallery-prev { left: var(--space-2); }
.gallery-next { right: var(--space-2); }

/* THE CLOSE BUTTON IS A CONTROL OVER A PHOTOGRAPH, so it gets the same
   treatment as the arrows either side of it. The plain .img-dialog-x is a
   bare glyph with no background, which is right on a small white example
   dialog and invisible over a picture of a sunlit room. Sharing the shape
   also fixes the hover: the arrows sat on a solid surface and the close
   button on nothing, so the same hover token read as two different colours.
   Scoped to .gallery-dialog, so the reviews-badge dialog is untouched. */
.gallery-dialog .img-dialog-x { top: var(--space-3); right: var(--space-3); }

.gallery-nav,
.gallery-dialog .img-dialog-x button {
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface, #fff);
  color: var(--ink);
  box-shadow: var(--shadow-1);
}

.gallery-nav:hover,
.gallery-nav:focus-visible,
.gallery-dialog .img-dialog-x button:hover,
.gallery-dialog .img-dialog-x button:focus-visible {
  background: var(--tint, rgba(0, 0, 0, 0.05));
  color: var(--ink);
}

/* --- The dashboard's own gallery editor --- */

.gallery-manage {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--space-3);
}

.gallery-manage-item {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-2);
  background: var(--surface, #fff);
}

.gallery-manage-item img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-sm, 6px);
}

.gallery-manage-item label {
  display: block;
  margin-top: var(--space-2);
  font-size: 0.85rem;
}

.gallery-manage-item input[type="text"],
.gallery-manage-item input[type="number"] { width: 100%; }

/* A photograph the member holds but her current allowance does not show.
   Dimmed rather than hidden, because she has to be able to see it to choose
   which ones DO show, and because a photograph that vanished with no
   explanation reads as data we lost. */
.gallery-manage-item.is-over-cap { opacity: 0.55; }
.gallery-manage-item.is-over-cap img { filter: grayscale(1); }
.gallery-over-cap-note {
  display: block;
  margin-top: var(--space-1);
  font-size: 0.8rem;
  color: var(--ink-soft, var(--ink));
}

/* Surgery-type sub-facet: a full-width band of its own directly under the
   bar row, visible whenever its parent condition is chosen - never inside
   the More filters panel, because it continues the bar's primary question
   ("Need help with?"... which surgery?) and a collapsed panel would hide it
   from exactly the patient it was built for. Its own row also means it can
   never tile the one-line bar, the fault facet_short exists to prevent
   (13 Aug 2026). */
.finder-bar > .finder-subfacet {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
}

/* Tick filters sit side by side and wrap; a forced row's note breaks below
   its own control without disturbing its neighbours. */
.filter-checks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-7);
}

/* The three dropdowns share one line on desktop, each a readable width
   rather than a third of the page. */
.filter-selects {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: 1fr;
}

@media (min-width: 720px) {
  .filter-selects { grid-template-columns: repeat(3, minmax(0, 18rem)); }
}

.filter-selects .finder-filter-field { margin: 0; }

.filter-body .filter-group { margin: 0; }

.filter-group-note {
  font-size: 0.9rem;
  color: var(--ink-mute);
  margin: 0 0 var(--space-3);
}

/* A group sub-heading that FOLLOWS a chip grid (the Training band's
   "Further training" after the schools row) needs air above it, or it sits
   on the rim of the row it does not belong to and the two groups read as
   one run of ticks. */
.filter-group .filter-check-grid + .filter-group-note {
  margin-top: var(--space-5);
}

/* One filter per row. SHORT LABEL FIRST, explanation only where it earns its
   place - that is the whole difference between this and the version Rick read
   as "a lot of information": a paragraph under every tick turned six controls
   into 851px. Safety-relevant notes stay visible; the rest sit behind the
   keyboard-reachable "?" icon. Never behind hover alone - .tip-bubble has
   pointer-events:none and no tap trigger, so a hover-only note does not exist
   on a phone. */
.filter-row { margin-bottom: var(--space-3); }
.filter-row:last-child { margin-bottom: 0; }

.filter-row-control {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.filter-row-note {
  display: block;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--ink-mute);
  margin-top: var(--space-1);
  padding-left: 1.7rem; /* clears the chip's tick box */
}

/* The provenance mark. It replaces two headed cards and two paragraphs: at
   the moment of choosing a filter the question is "what do I want?", not "who
   told you that?". Built from the verified tokens so whatcounsellor's
   theme.css re-skins it for free. */
.filter-checked-pill {
  display: inline-block;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--tone-verified-border);
  border-radius: var(--radius-stamp);
  background: var(--verified-wash);
  color: var(--tone-verified-ink);
  font-family: var(--font-sans);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.4;
  white-space: nowrap;
}

.filter-provenance {
  margin: 0;
  max-width: none;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--ink-mute);
}

.filter-body .finder-filter-field select { width: 100%; }
.filter-body .finder-filter-field label { display: flex; align-items: center; gap: 0.5rem; }

/* One line under a select, for a control that needs a word of reassurance
   rather than a "?" bubble. The school filter is the only one that does: its
   options are five surnames a patient has no way to rank, and the honest
   answer (you almost certainly do not need to choose) has to be visible
   rather than hidden behind an icon, or it does not do its job. */
.filter-field-note {
  margin: var(--space-2) 0 0;
  max-width: none;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--ink-mute);
}

/* The treatments band keeps its hairline so it reads as its own section of
   the panel - it is the one block Rick singled out as reading clearly, and
   its shape is unchanged. */
.finder-bar > .filter-body > .filter-group-wide {
  border-top: 1px solid var(--line);
  padding-top: var(--space-5);
}

/* Ticks in columns, not one tall stack: nine stacked checkboxes made the
   declared side four times the height of the verified side and pushed the
   price and gender controls off the bottom. Auto-fit means the count can
   grow without the layout being redesigned again. */
.filter-check-grid {
  display: grid;
  gap: var(--space-3) var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}

/* The roomy variant: a wider column floor for lists carrying long names
   (the Training band's course entries), so a name stays on one line and
   the chips wrap as balanced rows instead of stranding one on its own. */
.filter-check-grid-roomy {
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

/* Wrapping labels stay hanging-indented past their tick box. */
.filter-check-grid .filter-chip {
  white-space: normal;
  align-items: flex-start;
}
.filter-check-grid .filter-chip::before { flex: none; margin-top: 0.1rem; }

/* Says out loud that the register narrowed the results itself. */
.results-notice {
  background: var(--verified-wash);
  border-left: 3px solid var(--verified);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-4);
  font-size: 0.95rem;
  max-width: none;
}

/* A forced filter is shown ticked but not changeable; the tick still reads as
   applied rather than greyed into looking inactive. */
.filter-chip-input:disabled + .filter-chip { cursor: default; }
.filter-chip-input:disabled + .filter-chip::before {
  color: var(--surface);
  background: var(--verified);
  border-color: var(--verified);
}


/* --- Applied filters: one removable chip each, so nothing narrows the
       results invisibly --- */
.active-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
}

.active-filters a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem var(--space-3);
  border: 1px solid var(--filter-line);
  border-radius: var(--radius-stamp);
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--filter-ink);
  background: var(--surface);
}

.active-filters a:hover {
  border-color: var(--green);
  color: var(--green-deep);
}

.active-filters a span[aria-hidden] { font-size: 1.05rem; line-height: 1; }

.active-filters-clear a {
  border: 0;
  padding-inline: 0;
  text-decoration: underline;
  background: none;
}

/* The condition dropdown matches the checkbox toggles: same grey, and a
   custom chevron padded in from the right edge */
/* Width only. Type, colour and the chevron come from the one select rule. */
.finder-filters-row select { width: auto; }

.finder-filters-row .filter-reset { margin: 0; }

/* Toggle chips: visible, instant, one tap. The input stays real for
   no-JS submits and accessibility; the label wears the pill. */
.filter-chip-input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  cursor: pointer;
  font-size: 0.97rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--filter-ink);
  white-space: nowrap;
  transition: color 160ms var(--ease-out);
  /* Enlarge the hit area to >=24px (WCAG 2.5.8) without shifting layout. */
  padding-block: 3px;
  margin-block: -3px;
}

.filter-chip::before {
  content: '\2713';
  display: grid;
  place-items: center;
  width: 1.15rem;
  height: 1.15rem;
  font-size: 0.75rem;
  line-height: 1;
  color: transparent;
  background: var(--surface);
  border: 2px solid var(--filter-line);
  border-radius: var(--radius-stamp);
  transition: background-color 160ms var(--ease-out), border-color 160ms var(--ease-out);
}

.filter-chip:hover::before { border-color: var(--green); }

.filter-chip-input:checked + .filter-chip { color: var(--green-deep); }

.filter-chip-input:checked + .filter-chip::before {
  color: var(--surface);
  background: var(--green);
  border-color: var(--green);
}

.filter-chip-input:focus-visible + .filter-chip {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* --- Town pages --- */
.town-listing { margin-block: var(--space-6); }

.town-links { padding-block: var(--space-6) var(--space-4); max-width: 52rem; }

.town-nearby, .town-guides { max-width: none; }

.town-blocks p { color: var(--ink-soft); }

/* Pack-authored local editorial (brands/<key>/town_content/<slug>.php) */
.town-local .container { max-width: 52rem; }
.town-local p { color: var(--ink-soft); }
.town-local section + section { margin-block-start: var(--space-6); }

/* =========================================================================
   Package: profile-check
   Therapist profile pages, the enquiry flow and Check a Therapist.
   Token-driven additions only; merged into main.css by the orchestrator.
   ========================================================================= */

/* --- Profile masthead (header block, B3) --------------------------------- */
/* space-5, not space-6, since 2 Sep 2026: every pixel of masthead height is a
   pixel of the action card pushed under the fold on a 1366 x 768 laptop. */
.profile-masthead {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  padding-block: var(--space-5);
}

.profile-header {
  display: flex;
  gap: var(--space-6);
  align-items: center;
  flex-wrap: wrap;
}

.profile-photo {
  flex: none;
  width: 12.5rem;
  height: 12.5rem;
  border-radius: 50%;
  overflow: hidden;
  background: var(--green-wash);
  display: grid;
  place-items: center;
  color: var(--green);
  font-family: var(--font-serif);
  font-size: 2.4rem;
  /* The ring (Rick, 21 Sep 2026, after VerifiedPractitioner's portraits): a
     thin paper gap then a band in the verified steel blue, drawn as shadows
     so the circle and its overflow clip are untouched. */
  /* Ring colour #4d6d94 is Rick's own pick (21 Sep 2026), a lighter steel
     than --verified; kept literal because he chose the value. */
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 5px #4d6d94, 0 0 0 8px rgba(47, 90, 112, 0.14), 0 8px 20px rgba(0, 0, 0, 0.12);
  margin: 8px;
}

.profile-photo img { width: 100%; height: 100%; object-fit: cover; }

.profile-head-main { flex: 1; min-width: min(100%, 20rem); }

/* ON A PHONE THE MASTHEAD IS CENTRED (Rick, 1 Sep 2026). Below 960px the
   layout stacks and the photograph is already centred, so a left-aligned
   name, subline, rating and chip rows under a centred portrait read as an
   alignment fault rather than a decision. Above 960px this is the left
   column of a two-column layout and stays left. */
/* IN THE MASTHEAD THE PROVENANCE LABELS SIT INLINE WITH THEIR CHIPS (Rick,
   2 Sep 2026): "Checked by us" then its stamps on one line, "Told to us by
   the therapist" then its stamps on the next. Stacked, the two captions cost
   the masthead two extra rows, read as bunched, and pushed the action card
   under the fold on a 1366 x 768 laptop. Result cards keep the stacked
   layout: their column is too narrow for a caption and chips to share a
   line. */
.profile-head-main .provenance { gap: var(--space-2); margin-bottom: 0; }
.profile-head-main .prov-row { flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.profile-head-main .prov-label { flex: none; }

@media (max-width: 719px) {
  /* A size down on a phone (Rick, 2 Sep 2026). The stamps and their captions
     are the proof, so they stay in view rather than folding behind a tap;
     they were simply taking a third of the first screen. */
  .profile-head-main .chip { font-size: 0.66rem; padding: 0.34rem 0.55rem; letter-spacing: 0.06em; }
  .profile-head-main .prov-label { font-size: 0.78rem; }
}

@media (max-width: 959px) {
  /* The portrait too (Rick, 2 Sep 2026): once the row wraps, the photograph
     sat on a row of its own at the LEFT edge under a centred name. */
  .profile-header { justify-content: center; }
  .profile-head-main { text-align: center; }
  .profile-head-main .prov-row { justify-content: center; }
  /* The pills row is a flex box, so text-align cannot centre it (Rick,
     21 Sep 2026: Founding member and Taking new clients sat left on a phone). */
  .profile-founding { justify-content: center; }
  /* The base stylesheet caps a <p> at 70ch for readable prose, and a capped
     block cannot centre itself - it strands at the left. Same gotcha the
     sample-flag rule carries a note about. */
  .profile-head-main p { max-width: none; margin-left: auto; margin-right: auto; }
  /* The chip rows are flex, so text-align does not reach them. */
  .profile-head-main .prov-chips,
  .profile-head-main .dash-chips,
  .profile-head-main .profile-rating { justify-content: center; }
  .profile-head-main .social-row { justify-content: center; }
}

/* Action buttons sit to the right, centred against the profile photo */
.profile-head-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
  align-self: center;
  min-width: 13rem;
}

@media (max-width: 719px) {
  .profile-head-actions { width: 100%; flex-direction: row; flex-wrap: wrap; }
  .profile-head-actions .btn { flex: 1 1 12rem; }
}

/* Declared social addresses (app/lib/social.php): a row of icon links under
   the profile's action buttons. A LIST, so the count is announced; the
   bullets are removed rather than the semantics. Each hit area is 44px so a
   thumb can land on it, which is why the padding is on the link and not the
   svg. */
.social-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
  justify-content: center;
}

.social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  color: var(--ink-mute);
  text-decoration: none;
}

/* An inline SVG sits on the text baseline, and two glyphs of different
   height then land at different heights inside the same circle (Rick,
   21 Sep 2026: the Facebook and Instagram icons did not line up). As a
   block in a centred flex box, both sit dead centre. */
.social-link svg { display: block; }

.social-link:hover,
.social-link:focus-visible {
  color: var(--green);
  background: var(--paper-2);
}


@media (min-width: 720px) {
  .profile-head-actions { margin-left: auto; flex: none; }
}

/* THE MASTHEAD PHOTO CLUSTER (2 Sep 2026): up to four of the member's own
   photographs where the action buttons used to be, each opening the
   gallery's lightbox. A fixed 2 x 2 box on desktop, so a therapist with one
   room photograph gets one wide tile and a therapist with twenty gets four
   and a "+16 more" - never a giant tile and never an orphan. On a phone it
   is a slim strip under the name that costs the screen about eighty pixels.
   The tiles reuse .gallery-tile, so the border, the 4:3 box and the hover
   are the gallery's own. */
.profile-head-gallery {
  display: grid;
  /* 8.5rem tiles (was 9.5rem): the 2 x 2 box then stands 212px tall, close to
     the 200px portrait beside it, instead of setting the masthead's height on
     its own. */
  grid-template-columns: repeat(2, 8.5rem);
  gap: var(--space-2);
  align-self: center;
  margin-left: auto;
  flex: none;
}
.profile-head-gallery .gallery-more-label { font-size: 0.95rem; }
@media (min-width: 960px) {
  .profile-head-gallery[data-count="1"] .gallery-tile,
  .profile-head-gallery[data-count="3"] .gallery-tile:first-child { grid-column: span 2; }
  /* Two photographs stand upright (Rick, 2 Sep 2026, from a side-by-side
     mock-up): a pair of 4:3 tiles made one thin row beside the portrait,
     and a 3:4 pair matches the portrait's height instead. */
  .profile-head-gallery[data-count="2"] .gallery-tile { aspect-ratio: 3 / 4; }
}
@media (max-width: 959px) {
  .profile-head-gallery {
    width: 100%;
    margin-left: 0;
    grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr));
  }
}

.profile-head-main h1,
.profile-head-main .shot-title { margin-bottom: var(--space-2); font-size: clamp(2rem, 4vw, 2.9rem); } /* space-2 again: since 21 Sep 2026 the subline sits directly under the name, so the room Rick asked for goes under the subline instead */

/* The founding mark, on its own line between the subline and the provenance
   rows (Rick, 21 Sep 2026: name, then "Verified MLD Therapist in
   Cheltenham", then the pills). Kept out of the provenance chip block below
   it, which is deliberately one uniform colourless style split into
   checked/declared rows. */
.profile-founding { margin-bottom: var(--space-3); display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
/* The provenance pills ("Checked by us" / "Told to us by ...") on the
   masthead are softly rounded rather than full pills (Rick, 21 Sep 2026:
   4px on those two rows only, after a first pass at 10px; Founding member
   and Taking new clients keep the 100px pill). */
.profile-head-main .prov-chips .chip { border-radius: 4px; }
/* The "i" inside the availability pill: the member's own note on hover or
   focus, in body type, anchored under the pill. */
.chip .chip-tip { margin-left: 0.15rem; }
/* The icon must not make its pill taller than the pill beside it (Rick,
   21 Sep 2026: "taking new clients pill is bigger than founding member"):
   16px, and the negative vertical margin keeps the chip's box at the text
   line while the circle overlaps the padding. */
.chip .chip-tip { display: inline-flex; align-items: center; }
.chip .chip-tip .tip-icon { width: 16px; height: 16px; min-width: 16px; min-height: 16px; font-size: 0.6rem; margin: -0.4rem 0; }
/* Opens DOWNWARDS (Rick, 21 Sep 2026: Rachel's longer note opened upwards and
   ran under the site header). Anchored to the icon's left edge on a desktop,
   to its right edge on a phone where the pills are centred near the edge. */
.chip .chip-tip .tip-bubble { top: calc(100% + 0.6rem); bottom: auto; left: 0; transform: none; text-transform: none; letter-spacing: normal; font-weight: 400; font-size: 0.85rem; line-height: 1.45; white-space: normal; width: 18rem; max-width: calc(100vw - 2rem); text-align: left; }
@media (max-width: 720px) {
  .chip .chip-tip .tip-bubble { left: auto; right: 0; }
}

.profile-subline {
  /* Rick, 21 Sep 2026: the subline is the profile's one-line summary and
     read too faintly at 0.97rem in mute ink. */
  color: var(--ink);
  font-weight: 600;
  font-size: 1.2rem;
  margin-bottom: var(--space-4);
  font-variant-numeric: tabular-nums;
}

.profile-header .btn-row { margin-top: var(--space-5); }

/* --- Profile layout: verification box first on mobile, right on desktop -- */
.profile-layout {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 960px) {
  .profile-layout { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--space-7); }
  .profile-layout .profile-main { order: 1; }
  .profile-layout .profile-aside { order: 2; position: sticky; top: calc(var(--header-offset) + var(--space-4)); }
}

.profile-section { margin-bottom: var(--space-7); }
.profile-section:last-child { margin-bottom: 0; }
/* The profile body band is space-7, not the site's space-8 (Rick, 2 Sep
   2026): with the contents menu directly above, 64px read as a hole before
   the first heading. An earlier padding on the About section itself stacked
   on top of this and doubled the gap; it is gone. */
.profile-body { padding-block: var(--space-7); }
.profile-section h2,
.profile-section .shot-title { margin-top: 0; }
/* Sub-headings inside About (help-with, treatments): clearly subordinate to
   the section title, clearly senior to body text. */
.profile-section h3,
.profile-section .shot-heading { font-size: 1.45rem; margin-top: var(--space-6); margin-bottom: var(--space-3); }

/* Hero rating line: stars link down to #reviews. */
.profile-rating { margin-bottom: var(--space-3); max-width: none; }
.profile-rating a { text-decoration: none; color: var(--ink); }
.profile-rating a:hover strong { text-decoration: underline; }

/* Member-declared facts folded into About when the standalone section went
   (qualified year, background) - quiet, one line. */
.profile-about-meta { color: var(--ink-mute); font-size: 0.95rem; }

/* The mobile enquiry bar. Shown by main.js between the masthead button leaving
   the screen and the enquiry section arriving; see the note in profile.php.

   It fades rather than slides. A fixed element translated below bottom:0 can
   add to the scrollable area in some browsers, and this file has already paid
   for one below-the-fold overflow bug; opacity changes nothing about layout.
   visibility rides with it so a keyboard or screen reader cannot reach two
   buttons that are not on screen.

   z-index 500 for the same reason the finder bar uses it: Leaflet's panes sit
   at 400+ and the profile carries a map. It stays under the sticky header
   (600), which it can never meet anyway. */
.profile-sticky-cta {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 500;
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
  background: var(--surface);
  border-top: 1px solid var(--line);
  box-shadow: 0 -6px 24px rgba(11, 45, 34, 0.12);
  opacity: 0;
  visibility: hidden;
  transition: opacity 180ms ease, visibility 0s linear 180ms;
}

.profile-sticky-cta[hidden] { display: none; }

.profile-sticky-cta.is-shown {
  opacity: 1;
  visibility: visible;
  transition: opacity 180ms ease, visibility 0s;
}

/* Equal halves, and no inherited button margin - the bar is one row or it is
   a mess on a 320px screen. */
.profile-sticky-cta .btn { flex: 1 1 0; min-width: 0; margin: 0; }

@media (min-width: 960px) {
  /* The rail's quick card takes over here, so the bar must not exist at all. */
  .profile-sticky-cta { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .profile-sticky-cta,
  .profile-sticky-cta.is-shown { transition: none; }
}

/* THE PROFILE'S ONE ACTION CARD (2 Sep 2026): price from, the action, the
   phone, the website, the social icons. Every width, because the masthead
   no longer carries any button. It renders in the rail when the masthead is
   showing photographs and in the masthead's right-hand column when there
   are none - partials/profile_quick.php. */
.profile-quick {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  padding: var(--space-5);
  margin-bottom: var(--space-5);
  text-align: center;
}

.profile-quick-fee { margin: 0 0 var(--space-4); max-width: none; color: var(--ink-soft); }
/* Book online and Send an enquiry on ONE row where the card is wide enough
   (Rick, 2 Sep 2026). Each button asks for 9rem, so in the rail they sit
   side by side and in a narrow card (the masthead column, a phone) they
   stack, which is the same rule the phone bar follows. */
.profile-quick-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* inline-flex so the label sits in the vertical middle when its neighbour
   wraps to two lines and stretches the row (Rick, 2 Sep 2026: "Book online"
   sat at the top of a tall button beside a two-line "Send an enquiry"). */
.profile-quick-cta { flex: 1 1 9rem; min-width: 0; margin: 0; display: inline-flex; align-items: center; justify-content: center; }
.profile-quick-phone { margin: var(--space-3) 0 0; max-width: none; font-size: 0.95rem; color: var(--ink-mute); }
/* One contact row (Rick, 21 Sep 2026): phone, website and social icons on
   a line under the buttons; wraps on a narrow card. The social list keeps
   its own centred flex row, so it sits inline with the text beside it. */
.profile-quick-contact { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--space-1) var(--space-4); margin-top: var(--space-3); }
.profile-quick-contact .profile-quick-phone { margin: 0; }
.profile-quick-contact .social-row { gap: 0; }
.profile-quick-contact .social-link { width: 40px; height: 40px; }
/* The member's own website and social addresses: one quiet row at the foot
   of the card under a hairline. Declared, unchecked, and in their contact
   card rather than anywhere near the Verification box. */
.profile-quick-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
}
.profile-quick-site { font-size: 0.95rem; }
/* The pitch page's laptop specimen draws the same line as a <span>, because
   nothing inside a picture of a page may be a real link; it still has to
   LOOK like the link it is standing in for. */
.device-screen span.profile-quick-site { color: var(--green); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2.5px; }
/* The specimen's aside is narrower than a real rail at the laptop's scale, so
   the two buttons get tighter side padding and may not wrap their label:
   "Send an enquiry" on two lines inside a picture reads as a broken button. */
.device-screen .profile-quick-cta { padding-inline: var(--space-3); white-space: nowrap; }
/* In the masthead (a member with no photographs) the card IS the column. */
.profile-head-actions .profile-quick { width: 100%; min-width: 16rem; margin-bottom: 0; }

/* Reviews (collected through VerifiedPractitioner, synced nightly) */
.review-entry { border-top: 1px solid var(--line); padding-block: var(--space-4); }
.review-entry:last-of-type { border-bottom: 1px solid var(--line); }

.review-head { display: flex; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; margin-bottom: var(--space-2); }

.review-rating { color: var(--brass-deep); font-weight: 700; font-size: 0.85rem; }

/* Star glyphs (partial stars.php): brass fills over a hairline outline.
   Sized in ems so they scale with the line they sit in. */
.stars { display: inline-flex; align-items: center; gap: 0.1em; vertical-align: -0.12em; }
.stars svg { width: 1.05em; height: 1.05em; display: block; }
.stars-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Aggregate line at the top of the profile reviews section. */
.review-aggregate {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 1.15rem;
  margin-bottom: var(--space-4);
}
.review-aggregate .stars { font-size: 1.3rem; }

/* The member's public response under a review: ruled in, quieter voice. */
.review-reply {
  margin-top: var(--space-3);
  padding-left: var(--space-4);
  border-left: 2px solid var(--brass-soft);
  color: var(--ink-soft);
  font-size: 0.97rem;
}

/* Star rating line on result cards (data-gated: reviews present only). */
.result-rating {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: calc(-1 * var(--space-2)) 0 var(--space-3);
  font-size: 0.97rem;
}
.result-rating { flex-wrap: wrap; }
.result-rating .stars { font-size: 1.05rem; }
/* The Google signpost is review information, neither an operator check nor a
   member claim, so it sits under the rating rather than in the provenance rows
   - and on its OWN line, so it can never be read as the VP star figure. */
.result-google-note {
  color: var(--ink-mute);
  font-size: 0.9rem;
  margin: calc(-1 * var(--space-2)) 0 var(--space-3);
}

/* Two-source review CARDS (profile + the member's all-reviews page).

   Built on .verification-box - the same labelled-source card that holds the
   Verification panel on this page - so the Reviews section reads as part of
   the page rather than bolted on (Rick, 28 Aug 2026, after seeing the
   unstyled version live). Head = source mark + heading, body = the rating
   and the entries, footer = the muted attribution line. Two clearly
   labelled sources, never combined.

   STACKED, never side by side: this section sits in .profile-main, which is
   7fr of a 7fr/5fr grid from 960px, so a two-column split lands at roughly
   300px a card. Measured, not preferred. */
.review-card + .review-card { margin-top: var(--space-5); }

/* The Google mark sits at the head, at the seal's optical weight. */
.review-card-google .verification-box-head .icon-google { flex: none; }

/* The headline figure. Larger than a review's own stars so the card has one
   obvious reading order: rating, then evidence. */
.review-aggregate-lg {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  font-size: 1.35rem;
  margin: 0 0 var(--space-4);
}
.review-aggregate-lg .stars { font-size: 1.5rem; }
.review-aggregate-lg strong { font-variant-numeric: tabular-nums; }
.review-aggregate-count {
  font-size: 0.95rem;
  color: var(--ink-mute);
}

/* An empty Google line (JS not run, no key, Google unreachable) must not
   leave a gap above the link - the card still reads as complete. */
.review-aggregate-lg:empty { display: none; }

.review-card-link { font-size: 0.95rem; }

/* The entries sit between the rating and the footer, separated by hairlines
   rather than boxes - a card inside a card reads as noise. */
/* ONE hairline between entries, never doubled and never missing. The
   first-of-type reset MUST use the CHILD combinator: written as a descendant
   it also matched the first review inside the <details>, which stripped that
   review's rule AND its top padding so it collided with the one above the
   moment the expander opened (Rick spotted it, 28 Aug 2026). Entries revealed
   by the expander are not direct children of .review-list, so they keep the
   base rule and separate correctly. */
.review-card .review-entry { border-bottom: 0; }
/* The first entry KEEPS its rule (28 Aug 2026): with the heading block gone,
   this hairline is the only thing separating the rating summary from the
   reviews themselves. Every entry now carries a top border and nothing
   carries a bottom one, so the run is evenly ruled and never doubled -
   including the entries the expander reveals, which are not direct
   children of .review-list. */
.review-card .review-list { margin-bottom: var(--space-4); }

/* THE SOURCE LINE - one rule, both cards, so they cannot drift apart. Reads
   "[mark] ***** 4.6 from 18 reviews on <source>". The mark, the stars and
   (Rick, 28 Aug 2026 - a "Google reviews" heading above them read as clunky
   when the line already names its source). Ships hidden; main.js unhides it
   only once a real figure has landed, so a failed fetch never leaves a lone
   Google mark asserting nothing. */
.review-source-line {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  font-size: 1.35rem;
  margin: 0 0 var(--space-4);
}
.review-source-line .icon-google,
.review-source-line .wordmark-seal { flex: none; }
.review-source-line .stars { font-size: 1.5rem; }
.review-source-line strong { font-variant-numeric: tabular-nums; }
/* The source name wraps as ONE unit on a phone, never splitting
   "reviews on The MLD Register" across two lines mid-phrase. */
.review-source-line .review-aggregate-count { white-space: nowrap; }

/* The way back from the leave-a-review form. */
.review-form-back { margin-top: var(--space-5); font-size: 0.95rem; }
.review-card-more { margin: 0 0 var(--space-4); }

/* The leave-a-review invitation under the cards. */
.review-invite { font-size: 0.97rem; margin-top: var(--space-5); }

/* The all-reviews page's back link, above the heading. */
.reviews-all-back { font-size: 0.95rem; margin-bottom: var(--space-3); }
/* Enquiry form panel */
.enquiry-section {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-1);
  scroll-margin-top: var(--space-6);
}

/* Long review lists: five show, the rest sit behind a native expander so a
   member with a hundred reviews does not become a hundred-screen page. Inline
   rather than a link off to Google or VP: the visitor who is reading reviews
   is two scrolls from the enquiry form, and sending them to another tab is
   how a directory loses the enquiry. Google's own full list keeps its
   external link above. */
.reviews-more > summary,
.bio-more > summary {
  cursor: pointer;
  list-style: none;
  display: inline-block;
  margin-block: var(--space-4) 0;
  font-weight: 600;
  color: var(--green-deep);
  text-decoration: underline;
  text-decoration-color: var(--brass);
}
.reviews-more > summary::-webkit-details-marker,
.bio-more > summary::-webkit-details-marker { display: none; }
.reviews-more[open] > summary { display: none; }

/* Profile footer: report link + disclaimer line */
.profile-footer {
  margin-top: var(--space-7);
  border-top: 1px solid var(--line);
  padding-top: var(--space-5);
  font-size: 0.9rem;
  color: var(--ink-mute);
}

.profile-footer p { max-width: none; }

/* Enquiry confirmation screen */
.enquiry-success { padding-block: var(--space-7); }
.enquiry-success .enquiry-success-seal { color: var(--green); margin-bottom: var(--space-4); }

/* --- Check a Therapist (B8) ------------------------------------------- */
.page-check .container-narrow { max-width: 56rem; }
/* The single search input already carries the sage keyline; drop the panel
   chrome so /check is not a box drawn immediately inside another box. */
.page-check .search-box {
  max-width: none;
  background: transparent;
  border: 0;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
}

.check-results { margin-top: var(--space-6); }

/* Check cards carry an avatar (left) like search results; inherit the
   flex row from .result-card. */
.check-card .result-main { max-width: none; }
.check-card .result-name, .check-card .result-meta { padding-right: 0; }
.check-card .result-name { margin-bottom: var(--space-2); }

/* Name block and "View full profile" on one row, the button centred on the
   name; leader rows sit below at full width. */
.check-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}

.check-head-name { min-width: 0; }
.check-head-name .result-meta { margin-bottom: 0; }
.check-head .btn { flex: none; }

.check-checklist { grid-template-columns: 1fr; }

.check-footer-line { margin-top: var(--space-7); }
.check-footer-line p { max-width: none; }

/* =========================================================================
   pkg-badge - /verify verification card page (badge click-through)
   Token-driven additions only; merged into main.css by the orchestrator.
   ========================================================================= */

/* The card sits in a narrow measure, like a certificate on the desk. */
.verify-wrap { max-width: 34rem; }

/* Name and status chip stacked beside the seal in the box head. */
.verify-head-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}

/* =========================================================================
   Package: join-stripe (join flow + Stripe hand-off)
   Staged CSS for merge into public/assets/css/main.css by the orchestrator.
   Token-driven only: existing custom properties, no new colours or fonts.
   ========================================================================= */

/* Join flow progress indicator: Choose tier -> Your details -> Payment ->
   Documents (A2). One stepper for the whole journey; main.js advances it
   live between the two /join panels. Ruled steps with serif index numbers,
   echoing the ledger index motif. */
.join-progress {
  list-style: none;
  display: flex;
  gap: var(--space-4);
  padding: 0;
  margin: var(--space-5) auto var(--space-6);
  max-width: var(--container-narrow);
}

.join-progress li {
  flex: 1;
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0;
  padding-top: var(--space-3);
  border-top: 2px solid var(--line);
  color: var(--ink-mute);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.join-progress li.is-current { border-top-color: var(--brass); color: var(--ink); }
.join-progress li.is-done { border-top-color: var(--green); color: var(--green-deep); }

.join-progress-num { font-family: var(--font-serif); font-size: 1.05rem; font-weight: 600; }

/* Narrow screens: four steps will not fit on one row. "Documents" alone has a
   min-content width wider than a quarter of a 320px viewport, and a flex item
   defaults to min-width:auto, so it refused to shrink and pushed the whole
   page sideways - 55px at 320 and 15px at 360, on the paid signup funnel.
   Two by two instead. minmax(0,1fr) caps the track, and min-width:0 is needed
   on BOTH boxes: on the li because it is a grid item, and again on the label
   because the li is itself a flex container, so the span carries its own
   min-width:auto. Without the second one a longer label, or the fallback font
   shown during font-display:swap, could still push the row out. */
@media (max-width: 540px) {
  .join-progress {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3) var(--space-4);
  }
  .join-progress li { min-width: 0; }
  .join-progress-label { min-width: 0; overflow-wrap: anywhere; }
}

/* JS-gated controls (join flow Next/Back): visible only once main.js has
   stamped html.js, so the no-JS render never shows a dead button. */
html:not(.js) .js-only { display: none !important; }

/* Join panels: the two /join screens (Choose your tier / Your details).
   Both render stacked without JS; main.js hides one at a time and animates
   the entry. Hidden inputs still submit - [hidden] never disables fields. */
.join-panel[hidden] { display: none; }

.join-panel-actions {
  /* The submit button stays first in the DOM (so Enter submits the form);
     row-reverse renders it on the RIGHT with the back link on the left. */
  display: flex;
  flex-direction: row-reverse;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-5);
  margin-top: var(--space-6);
}

/* Step 1 has ONE control and nothing to sit opposite it, so space-between
   pinned "Next: your details" hard against the right edge of a full-width
   panel with nothing beside it (Rick, 20 Aug 2026). Centre it, and give the
   panel room underneath so the button is not the last pixel on the page. */
.join-panel-actions-single {
  justify-content: center;
  padding-bottom: var(--space-7);
}

/* A button dressed as an inline link (e.g. "Back to tiers" on /join). */
.btn-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 1rem;
  color: var(--green);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2.5px;
  cursor: pointer;
}

.btn-link::before { content: "\2190\00a0"; }

.btn-link:hover { text-decoration-thickness: 2px; }

@media (prefers-reduced-motion: no-preference) {
  .join-panel-enter { animation: join-panel-in 0.28s ease both; }
  @keyframes join-panel-in {
    from { opacity: 0; transform: translateY(0.5rem); }
    to { opacity: 1; transform: none; }
  }
}

/* Stripe Embedded Checkout container: the card form renders here inside a
   Stripe-hosted iframe. Reserve height so the page does not jump while the
   iframe loads; Stripe's own UI paints over the paper background. */
.stripe-embed {
  margin: var(--space-6) 0;
  min-height: 30rem;
  border-radius: var(--radius);
}

.stripe-embed-loading { padding: var(--space-6) 0; text-align: center; }

/* The Payment Link fallback block starts hidden and is revealed by JS only
   when the embed fails. .btn-row's display:flex would defeat the [hidden]
   attribute (equal specificity, later in the cascade), so pin it. */
[data-stripe-embed-fallback][hidden] { display: none; }

/* Concierge editable preview (/dashboard/preview): the member's finished profile
   as an editable form; the approve / save actions sit at the foot of the form. */
.preview-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-6);
}
.preview-actions .btn { min-width: 12rem; }
.preview-actions-note {
  margin: var(--space-3) 0 0;
  font-size: 0.9rem;
  color: var(--ink-mute);
}

/* The join content sits under the page header; give it room to breathe so
   the stepper is not cramped against the header. */
.page-join main > .container { padding-top: var(--space-7); }

/* Post-payment confirmation (/welcome): a single centred column with one clear
   next-step CTA. Deliberately NOT the /join/pending status leader-table - at
   the payment moment a status table only competes with the action we want. */
.welcome-confirm {
  max-width: 60rem;
  text-align: center;
  padding-bottom: var(--space-8);
}
.welcome-hero {
  max-width: 44rem;
  margin-inline: auto;
}
.welcome-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 4rem;
  height: 4rem;
  border-radius: 50%;
  background: var(--green-wash);
  color: var(--green);
  margin-bottom: var(--space-4);
}
.welcome-eyebrow {
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--green);
  max-width: none;
  margin-bottom: var(--space-3);
}
.welcome-hero h1 { margin-bottom: var(--space-4); }
.welcome-hero .lead {
  font-family: var(--font-serif);
  font-size: 1.2rem;
  color: var(--ink-soft);
  max-width: 40rem;
  margin-inline: auto;
  margin-bottom: var(--space-6);
}
/* The instruction paragraphs under the welcome lead: left-aligned inside the
   centred hero, because a run of centred paragraphs makes the eye hunt for the
   start of every line. */
.welcome-body {
  max-width: 40rem;
  margin: 0 auto var(--space-6);
  text-align: left;
  color: var(--ink-soft);
}
.welcome-body p { margin: 0 0 var(--space-4); }
.welcome-body p:last-child { margin-bottom: 0; }

.welcome-cta { margin-bottom: var(--space-4); }
.welcome-cta .btn { min-width: 15rem; }
.welcome-note {
  font-size: 0.95rem;
  color: var(--ink-mute);
  max-width: 34rem;
  margin-inline: auto;
  margin-bottom: 0;
}
.welcome-rule {
  margin-block: var(--space-8);
  border-top-color: var(--line);
}
.welcome-steps-title {
  margin-top: 0;
  margin-bottom: var(--space-6);
}
.welcome-steps {
  list-style: none;
  padding: 0;
  margin: 0 auto var(--space-8);
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
  text-align: left;
  max-width: 30rem;
}
@media (min-width: 760px) {
  .welcome-steps {
    grid-template-columns: repeat(3, 1fr);
    max-width: none;
    gap: var(--space-7);
  }
}
.welcome-steps > li { margin: 0; }
.welcome-step-num {
  display: block;
  font-family: var(--font-serif);
  font-size: 1.7rem;
  font-weight: 600;
  color: var(--brass);
  font-variant-numeric: tabular-nums;
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--line);
}
.welcome-steps h3 { margin: 0 0 var(--space-2); font-size: 1.2rem; }
.welcome-steps p { margin: 0; color: var(--ink-soft); font-size: 1.05rem; }
.welcome-reassure {
  max-width: 44rem;
  margin-inline: auto;
  margin-bottom: var(--space-4);
  font-size: 0.95rem;
  color: var(--ink-mute);
}
.welcome-reassure:last-child { margin-bottom: 0; }

/* Concierge onboarding (/dashboard before a member is live): ONE focused,
   guided screen - the member's three things (answers, address, documents), or
   the waiting / approval state. Deliberately no section menu, no self-service
   panels, no competing CTAs. Engine-level (tokens only) so every concierge
   brand inherits it. */
.onboard-intro {
  font-size: 1.125rem; /* 18px, Rick 2 Sep 2026 */
  color: var(--ink-soft);
  margin: 0 auto var(--space-6);
  max-width: 44rem;
  text-align: center;
}
/* LEFT-ALIGNED once there is more than a sentence or two. Centring is right
   for a one-line reassurance and wrong for five paragraphs: every line starts
   in a different place, so the eye has to hunt for the next one. Rick has
   caught centred body copy before. The single-paragraph states above keep the
   centred treatment. */
.onboard-intro.onboard-intro-long { text-align: left; }
.onboard-intro p { margin: 0; }
.onboard-intro p + p { margin-top: var(--space-4); }
/* The "we don't start until you press Submit" line is the whole point of the
   screen, and .onboard-intro p { margin: 0 } was running it straight into the
   paragraph above as one block. */
.onboard-intro .onboard-intro-note { margin-top: var(--space-4); }
.onboard-intro strong { color: var(--ink); }

.onboard-progress { margin-bottom: var(--space-6); text-align: center; }

.onboard-step-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.onboard-step-head h2 { margin: 0; }
.onboard-step-hint {
  margin: 20px 0px 40px 0px; /* Rick, 21 Aug 2026 */
  /* 1.125rem (18px), Rick 2 Sep 2026 (was 1.1rem, 21 Aug 2026): this is the
     sentence that explains the step, and it was set smaller than the field
     hints underneath it. */
  font-size: 1.125rem;
  color: var(--ink-mute);
}
.onboard-num {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: var(--brass-soft);
  color: var(--brass-deep);
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 1.15rem;
  font-variant-numeric: tabular-nums;
}
.onboard-step-status { margin-left: auto; flex: 0 0 auto; }
.onboard-step.is-done .onboard-num {
  background: var(--green-wash);
  color: var(--green);
}
.onboard-summary {
  margin: 0 0 var(--space-2);
  color: var(--ink-soft);
}
.onboard-form { margin-top: var(--space-2); }

/* ---------------------------------------------------------------------------
   The onboarding WIZARD (20 Aug 2026): one step per screen, with a stage bar.
   On a text step every stage is a <button type=submit name=goto>, so the three
   selectors below have to style a button, a link and a span identically -
   otherwise the bar visibly changes shape between step kinds. Hence the reset
   on .wizard-step rather than three separate rules.
   --------------------------------------------------------------------------- */
.wizard-bar { margin: 0 0 var(--space-6); }

.wizard-count {
  margin: 0 0 var(--space-3);
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink-mute);
  letter-spacing: 0.02em;
}

.wizard-steps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}

.wizard-step {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 700;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

/* A <span> for the current step: no pointer, and it must not look pressable. */
.wizard-steps .is-current .wizard-step {
  cursor: default;
  border-color: var(--green);
  background: var(--green-wash);
  color: var(--ink);
}

.wizard-steps .is-done .wizard-step { border-color: var(--verified); }

.wizard-step:hover,
.wizard-step:focus-visible { border-color: var(--line-strong); }
.wizard-steps .is-current .wizard-step:hover { border-color: var(--green); }

.wizard-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--paper-2);
  color: var(--ink-mute);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}
.wizard-steps .is-done .wizard-num { background: var(--verified-wash); color: var(--verified); }
.wizard-steps .is-current .wizard-num { background: var(--green); color: var(--on-green); }

/* ALL SEVEN STEPS ON ONE ROW, SPREAD ACROSS THE COLUMN (Rick, 21 Aug 2026).
   They used to be tightened until they fit, which put all seven in the left
   third of a wide column and read as a bunched-up cluster rather than a trail
   across the page. Each stage now takes an EQUAL share of the row, so the bar
   spans its container at any width and the chips line up as a proper progress
   trail.

   `min-width` is what keeps it honest. Flex items with `flex: 1 1 0` shrink
   rather than wrap, so without a floor the labels would squash to nothing on a
   narrow desktop instead of moving to a second line. With it, the row WRAPS -
   which is the required behaviour: a stage a member cannot see is a stage they
   miss, and this bar must never scroll sideways. Same ruling as .dash-nav. */
@media (min-width: 900px) {
  .wizard-steps { gap: var(--space-1); }
  .wizard-step { gap: var(--space-1); padding: var(--space-2); }
  .wizard-num { width: 1.4rem; height: 1.4rem; font-size: 0.78rem; }
}

/* 1000px, NOT 900px, and the number is measured rather than chosen. The wizard
   column tops out at 880px, and seven chips wide enough for "About you" at
   0.9rem/700 need about 881px of it. At a 900px viewport the column is only
   813px, so equal shares there wrapped to two rows and left "Review" stretched
   across the whole second row on its own. The column reaches its 880px cap at
   about a 1000px viewport, which is where this can start. */
@media (min-width: 1000px) {
  .wizard-steps { gap: var(--space-2); }
  .wizard-steps > li { flex: 1 1 0; min-width: 7rem; }
  .wizard-step {
    width: 100%;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-2);
  }
}

/* Below 560px the labels stack the bar into a wall of chips. The NUMBERS stay,
   because "step 4 of 7" plus a numbered trail is still a position; the words
   are what will not fit. The count line above keeps the meaning for anyone
   reading the labels aloud. */
@media (max-width: 559px) {
  .wizard-steps .wizard-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .wizard-steps .is-current .wizard-label { position: static; width: auto; height: auto; overflow: visible; clip-path: none; }
  .wizard-step { padding: var(--space-2); }
}

.wizard-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}
.wizard-nav .btn-primary { margin-left: auto; }

.wizard-form h2 { margin-top: 0; }

/* "Add another ..." - a <details> so a hidden row can never be unreachable,
   dressed as the quiet secondary control it is. */
.onboard-more { margin: 0 0 var(--space-5); }
.onboard-more > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  color: var(--ink-soft);
  font-size: 0.95rem;
  cursor: pointer;
  list-style: none;
}
.onboard-more > summary::-webkit-details-marker { display: none; }
.onboard-more > summary::before { content: '+'; font-weight: 700; color: var(--green); }
/* An open disclosure shows a minus, to pair with the plus above. The bytes
   here were U+0091 followed by '2' - a dash lost in a Windows-1252 paste -
   so opening "Add another address" drew a missing-glyph box and a 2 where
   the close affordance belongs (Rick, 21 Aug 2026). A plain ASCII hyphen on
   purpose: the file stays ASCII and a hyphen is the exact pair of the plus. */
.onboard-more[open] > summary::before { content: '-'; }
.onboard-more > summary:hover,
.onboard-more > summary:focus-visible { border-color: var(--green); color: var(--ink); }
.onboard-more-body { padding-top: var(--space-4); }

/* A heading for a group of fields that is NOT a <label>, because each field in
   the group carries its own. Matches .field label exactly so the certificate
   boxes read as one question with the rest of the form. */
.field-group-label {
  display: block;
  font-weight: 700;
  font-size: 1.125rem; /* 18px, Rick 2 Sep 2026 */
  margin: 0 0 var(--space-2);
  color: var(--ink);
}

/* One numbered box per training certificate. The old control was a single
   multi-file input: the only way to send three certificates at once was to
   hold Ctrl in the file dialog, and nothing on screen said so (Rick, 21 Aug
   2026). */
.doc-slots { margin: 0 0 var(--space-3); }
.doc-slot + .doc-slot { margin-top: var(--space-3); }
.doc-slot-label {
  display: block;
  margin: 0 0 var(--space-1);
  font-weight: 600;
  font-size: 1rem;
  color: var(--ink-soft);
}

/* One address inside the onboarding step's single form. Four sets of the
   same six fields in one column read as one enormous address, so each is a
   panel carrying its own name (Rick, 21 Aug 2026). */
.location-block {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  margin: 0 0 var(--space-5);
  background: var(--surface);
}
.location-block-title {
  margin: 0 0 var(--space-6);
  font-size: 1.6rem;
  font-weight: 600;
}
.location-block .location-remove { margin: var(--space-2) 0 0; }

/* The onboarding wizard runs its own rhythm: the page is a form, not a
   marketing band, so the section padding that suits a landing page leaves a
   gap above the first question (Rick, 21 Aug 2026). */
.page-onboarding .section { padding-block: var(--space-6); }

/* Air under the last button on the uploads step. */
.onboard-upload-submit { margin-bottom: var(--space-6); }

/* One price row: name, minutes, price. */
.fee-row,
.training-row {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
  margin-bottom: var(--space-4);
}
@media (min-width: 640px) {
  .fee-row { grid-template-columns: 2fr 0.7fr 0.9fr auto; }
  .training-row { grid-template-columns: 1.4fr 1fr 0.7fr; }
  /* One label wraps to two lines and the others do not, which left the three
     inputs on three different baselines. Aligning to the END of each cell puts
     the CONTROLS in a row, which is the thing being read as a row. */
  .fee-row, .training-row { align-items: end; }
}
.fee-row .field,
.training-row .field { margin-bottom: 0; }
.fee-remove { justify-self: start; min-height: 44px; }
[data-fee-status]:empty { margin: 0; }

/* NARROW SCREENS: three things each refuse to shrink, and together they pushed
   the training step 145px wider than a 320px phone (measured 21 Aug 2026).
   1. A grid item's min-width is auto, so a cell never goes below its content.
   2. A <select> sizes to its LONGEST option, and the school list contains
      "Aftercare Academy: Advanced MLD & Treatment Protocols".
   3. A <fieldset> has min-inline-size: min-content in every browser, so it
      then refuses to shrink around the row and takes the page with it.
   All three need answering; fixing any two leaves the page scrolling sideways.
   The fee rows carry the same shape and the same fix. */
.dash-fieldset { min-width: 0; }
.fee-row .field,
.training-row .field { min-width: 0; }
.fee-row select, .fee-row input,
.training-row select, .training-row input { max-width: 100%; }

/* One training and the certificate that evidences it, as a single block
   (Rick, 21 Aug 2026). The three claims stay on one row where there is width
   for them; the certificate goes UNDER them at every width rather than
   becoming a fourth column, because a file input is the widest control on the
   page and squeezing it into a quarter of the row hides the filename the
   browser prints beside it - which is the only confirmation the member gets
   that she chose the right file. */
.training-block {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* Tight side padding on a phone, where this card sits inside the fieldset's
     padding inside the container's: three sets of generous inline padding left
     the school picker 179px wide on a 320px screen. */
  padding: var(--space-4) var(--space-3);
  margin: 0 0 var(--space-4);
  background: var(--surface);
}
@media (min-width: 640px) {
  .training-block { padding: var(--space-4) var(--space-5); }
}
.training-block .training-row { margin-bottom: 0; }

/* The Course list for one school, and the "not enough yet" note under it.
   ONE GROUP PER SCHOOL is rendered and main.js hides the ones that do not
   match the chosen school; without JavaScript they all show, each under its
   own school heading, and the server still reads only the matching one. So
   this must look deliberate in both states, not like a form that failed to
   collapse. */
.training-courses { margin-top: var(--space-4); }
.training-course-group + .training-course-group {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px dashed var(--line);
}
.training-course-group fieldset.field { border: 0; padding: 0; margin: 0 0 var(--space-4); }
.training-course-group .checkbox-row { margin-bottom: var(--space-2); }
/* The whole row IS the label here, so the base .checkbox-row label rule (which
   targets a label INSIDE the row) never reaches it and the course names
   rendered bold, like field headings. */
.training-course-group label.checkbox-row { font-weight: 400; margin: 0 0 var(--space-2); }
.training-course-note {
  background: var(--brass-soft);
  border: 1px solid var(--brass);
  border-radius: var(--radius);
  color: var(--tone-warn-ink);
  padding: var(--space-3) var(--space-4);
  margin: var(--space-3) 0 0;
  font-size: 0.95rem;
}
.training-cert {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px dashed var(--line);
}
.training-cert input[type="file"] { max-width: 100%; }

/* "We have X on file." Says what we hold and nothing more. */
.onboard-received {
  margin: 0 0 var(--space-2);
  font-size: 0.95rem;
  color: var(--ink-soft);
}

/* Certificates on file that match no claimed training. Listed so nothing a
   member sent can look lost. */
.onboard-other-docs {
  margin: 0 0 var(--space-5);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--line);
  background: var(--paper-2);
}
.onboard-doc-list {
  margin: 0 0 var(--space-2);
  padding-left: 1.2em;
  font-size: 0.95rem;
  color: var(--ink-soft);
}

/* A file the browser refused before the post: too big to send. Red, because
   unlike the certificate tally this one did NOT save. */
.field-error {
  margin: var(--space-2) 0 0;
  font-size: 0.95rem;
  color: var(--tone-danger-ink);
  font-weight: 600;
}
.field-error[hidden] { display: none; }

/* "Your photo" inside the About you step: a heading within a step, not a step
   heading. */
.onboard-subhead {
  font-size: 1.15rem;
  margin-bottom: var(--space-3);
}

/* A warning that is not an error: the certificate tally is a nudge, and it
   must not use the red that means "we did not save this". */
.form-warning {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--brass);
  background: var(--paper-2);
  color: var(--ink-soft);
  font-size: 0.95rem;
}

/* The training/insurance claims. Set apart from the story questions above:
   these are facts we check, not words we publish, and the member should be able
   to see that at a glance. */
.onboard-claims {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5) var(--space-2);
  margin: 0 0 var(--space-5);
}
.onboard-claims > legend {
  float: left;              /* a fieldset legend ignores the fieldset's padding */
  width: 100%;
  font-weight: 700;
  font-size: 1.2rem; /* every dashboard legend, Rick 2 Sep 2026 */
  color: var(--ink);
  margin-bottom: var(--space-2);
}
.onboard-claims > legend + .field-hint { clear: both; }

/* The member's own photo step: current photo (or initials) beside the upload. */
.onboard-photo-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
  flex-wrap: wrap;
}
/* min-width: 0 is the whole fix (21 Aug 2026). A flex item defaults to
   min-width: auto, so it refuses to shrink below its content's min-content
   width. The upload control ("Choose file" button + "No file chosen") is wider
   than a 360px phone once the 96px avatar and the gap are taken off, so the
   row pushed the whole page sideways and the member could scroll the preview
   left and right. The .field selector matches the approve-and-edit preview
   screen, which uses .field rather than .onboard-form. */
.onboard-photo-row .onboard-form,
.onboard-photo-row .field { margin-top: 0; flex: 1 1 16rem; min-width: 0; }
.onboard-photo-row input[type="file"] { max-width: 100%; }
.onboard-photo-current {
  flex: 0 0 auto;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--brass-soft);
  display: flex;
  align-items: center;
  justify-content: center;
}
.onboard-photo-current img { width: 100%; height: 100%; object-fit: cover; }
.onboard-photo-empty {
  font-family: var(--font-serif);
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--brass-deep);
}
.onboard-num svg { display: block; }

.onboard-edit { margin-top: var(--space-2); }
.onboard-edit > summary {
  cursor: pointer;
  color: var(--green);
  font-size: 0.95rem;
  font-weight: 600;
}
.onboard-edit-body { margin-top: var(--space-4); }

.onboard-confirm {
  text-align: center;
  max-width: 40rem;
  margin: 0 auto var(--space-6);
}
.onboard-confirm p { margin: 0 auto; max-width: 38rem; }
/* max-width: none on these centred <p>s: without it they inherit the base
   p{max-width:70ch}, which pins the box left so text-align:center lands the
   content left of the true page centre. */
.onboard-cta { text-align: center; margin-bottom: var(--space-6); max-width: none; }
.onboard-cta .btn { min-width: 15rem; }

/* The centred .onboard-received block that stood here was DEAD and was
   deleted on 21 Aug 2026. It styled a confirmation on the old "Photo and
   documents" step; that step is gone, no view in any brand referenced it any
   more, and it sat AFTER the live rule of the same name - so the receipt line
   beside a certificate box inherited a centred, 22rem-wide box and read as
   floating in the middle of a left-aligned field. The live rule is up beside
   .training-block. */

.onboard-change {
  border-top: 1px solid var(--line);
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  text-align: center;
}
.onboard-change > summary {
  cursor: pointer;
  color: var(--green);
  font-weight: 600;
}
.onboard-change-body { margin-top: var(--space-5); text-align: left; }
.onboard-change h2 { font-size: 1.05rem; margin-bottom: var(--space-3); }

/* The member's own "I have finished" panel. Centred and set apart from the
   numbered steps: it is the one thing on the page that ends the screen, so it
   must not read as a fifth step. */
.onboard-submit {
  text-align: center;
  border-color: var(--brass-soft);
}
.onboard-submit h2 { margin-top: 0; }
.onboard-submit p { margin: 0 auto var(--space-4); max-width: 38rem; }
.onboard-submit .onboard-form { margin-top: 0; }
.onboard-submit .btn { min-width: 15rem; }
.onboard-todo {
  list-style: none;
  padding: 0;
  margin: 0 auto var(--space-5);
  max-width: 34rem;
  text-align: left;
  display: inline-block;
}
.onboard-todo li { margin-bottom: var(--space-2); }

.onboard-reassure {
  font-size: 0.9rem;
  color: var(--ink-mute);
  margin: var(--space-6) auto 0;
  max-width: 40rem;
  text-align: center;
}
.onboard-signout {
  margin-top: var(--space-6);
  max-width: none;
  text-align: center;
  font-size: 0.9rem;
}
.onboard-signout a { color: var(--ink-mute); display: inline-flex; align-items: center; gap: 0.35rem; }

.join-fieldset {
  border: 0;
  padding: 0;
  margin: 0 0 var(--space-7);
  min-width: 0;
}

/* /join tier chooser: two boxes - Registered (with its billing options) and
   Featured - replacing the old four-card row. The four Stripe tier keys are
   unchanged; the billing radios inside a box submit the right one. */
.join-two-boxes {
  display: grid;
  gap: var(--space-5);
  align-items: start;
}

@media (min-width: 800px) {
  .join-two-boxes { grid-template-columns: 1fr 1fr; }
}

.tier-box {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  padding: clamp(var(--space-5), 4vw, var(--space-6));
  transition: border-color 180ms var(--ease-out), box-shadow 180ms var(--ease-out);
}

.tier-box:has(input[type="radio"]:checked) {
  border-color: var(--green);
  box-shadow: 0 0 0 1px var(--green), var(--shadow-2);
}

.tier-box-head { margin-bottom: var(--space-4); }
.tier-box-name { font-size: 1.4rem; margin: 0 0 var(--space-1); }
.tier-box-price {
  font-family: var(--font-serif);
  font-size: 1.7rem;
  font-weight: 700;
  color: var(--green-deep);
  margin: 0 0 var(--space-2);
}
/* The VAT note sits inside the price line but reads as small print: body
   font, normal weight, muted - "£75/year including VAT". */
.tier-box-vat {
  font-family: var(--font-sans);
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--ink-mute);
  white-space: nowrap;
}
.tier-box-sub { color: var(--ink-soft); margin: 0; }
.tier-plus-lead { font-weight: 600; margin: 0 0 var(--space-3); }
.tier-box .tier-card-note { margin-top: var(--space-4); }
/* The join button sat flush against the rates boxes (Rick, 17 Aug 2026). */
.tier-box > .btn-block { margin-top: var(--space-5); }

.tier-billing-label {
  font-weight: 700;
  font-size: 0.95rem;
  margin: var(--space-5) 0 var(--space-3);
}

.tier-billing-group { display: flex; flex-direction: column; gap: var(--space-3); }

.tier-billing {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  cursor: pointer;
  transition: border-color 140ms var(--ease-out), background 140ms var(--ease-out);
}

.tier-billing:hover { border-color: var(--line-strong); }
.tier-billing:has(input:checked) { border-color: var(--green); background: var(--green-wash); }
.tier-billing input { margin-top: 0.25rem; flex-shrink: 0; }
.tier-billing-text { display: flex; flex-direction: column; gap: 0.15rem; }
.tier-billing-price { font-weight: 700; font-size: 1.02rem; color: var(--ink); }
.tier-billing-note { font-size: 1rem; color: var(--ink-mute); line-height: 1.45; }

/* Small best-value marker for a billing option (used on /pricing). */
.tier-billing-flag {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tone-warn-ink);
  background: var(--brass-soft);
  border-radius: var(--radius-stamp);
  padding: 0.1rem 0.4rem;
  margin-left: 0.35rem;
  vertical-align: 0.08em;
}

/* On /pricing the billing rows are informational, not selectable: drop the
   pointer cursor and hover affordance so they do not read as clickable. */
.pricing-tiers .tier-billing { cursor: default; }
.pricing-tiers .tier-billing:hover { border-color: var(--line); }

/* The standard-comparison line sits tight under the tier cards */
.join-fieldset > .text-small { margin: var(--space-5) 0 0; }

.join-fieldset legend { padding: 0; }

.tier-pick {
  display: flex;
  flex-direction: column;
  cursor: pointer;
  font-weight: 400;
  transition: border-color 180ms var(--ease-out), box-shadow 180ms var(--ease-out);
}

.tier-pick .tier-radio-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.tier-pick input[type="radio"] {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  accent-color: var(--green);
}

.tier-pick .tier-name { margin: 0; }
.tier-pick .tier-price { display: block; }

.tier-price-strap {
  display: block;
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--green-deep);
  margin-bottom: var(--space-1);
}

.tier-pick .tier-price-note { display: block; }

.tier-card-note {
  display: block;
  font-size: 0.85rem;
  color: var(--ink-mute);
  border-top: 1px solid var(--line);
  padding-top: var(--space-3);
  margin-top: auto;
}

.tier-pick:hover { border-color: var(--line-strong); box-shadow: var(--shadow-2); }
.tier-pick:has(input[type="radio"]:checked) { border-color: var(--green); box-shadow: var(--shadow-2); }
.tier-pick:has(input[type="radio"]:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }

/* Application form fields */
.join-fields { max-width: var(--container-narrow); margin-inline: auto; margin-bottom: var(--space-8); }

.field-grid {
  display: grid;
  gap: 0 var(--space-5);
  grid-template-columns: 1fr;
}

@media (min-width: 640px) {
  .field-grid-2 { grid-template-columns: repeat(2, 1fr); }
  /* Label-grow fallback: inputs line up when one LABEL wraps. */
  .field-grid-2 > .field { display: flex; flex-direction: column; }
  .field-grid-2 > .field > label { flex-grow: 1; }
  /* Subgrid (all modern browsers): label, control and hint each share a row
     across the pair, so the inputs sit level whatever wraps - a two-line
     label OR a hint present on only one side. */
  @supports (grid-template-rows: subgrid) {
    .field-grid-2 { grid-template-rows: repeat(4, auto); }
    .field-grid-2 > .field { display: grid; grid-template-rows: subgrid; grid-row: span 4; align-content: start; }
    .field-grid-2 > .field > label { align-self: end; }
  }
}

/* Optional additional practice locations */
.join-extra-locations { margin-bottom: var(--space-5); }

.join-extra-locations summary {
  cursor: pointer;
  font-weight: 700;
  color: var(--green);
  padding-block: var(--space-2);
}

.join-extra-locations summary:hover { color: var(--green-deep); }

/* Ruled ledger idiom, matching the dashboard's additional-locations block,
   rather than boxes-within-the-form. */
.location-row {
  border-top: 1px solid var(--line);
  padding: var(--space-4) 0 0;
  margin: var(--space-4) 0 0;
}

.location-row-title {
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: var(--space-3);
}

/* =========================================================================
   pkg-dashboard - member auth pages (/login, /forgot-password,
   /reset-password), the member dashboard (/dashboard) and the printable
   membership certificate (/dashboard/certificate).
   Token-driven additions only; merged into main.css by the orchestrator.
   ========================================================================= */

/* --- Auth pages: a single calm panel, like a form on good paper --------- */
.auth-wrap { max-width: 26rem; margin-inline: auto; }

.auth-panel { padding: var(--space-6); }

.auth-seal {
  display: flex;
  justify-content: center;
  color: var(--green);
  margin-bottom: var(--space-5);
}

.auth-links { margin: var(--space-5) 0 0; font-size: 0.95rem; }

.auth-aside { text-align: center; }

/* --- Dashboard layout: sticky section nav beside the panels ------------- */
/* Member dashboard (16 Jul 2026): one narrow column, Overview open at the
   top, every other section an admin-section accordion. Anchor targets land
   clear of the viewport edge. */
.dash-section .panel,
.dash-section .admin-section { scroll-margin-top: var(--space-6); }

/* Dashboard header (Rick, 17 Jul 2026 v2 polish): title + welcome on the
   LEFT, "View your public profile" on the RIGHT (opens a new tab); wraps
   to stacked-left on small screens. Overrides the global centred rule. */
body .page-header.dash-header { text-align: left; }
body .page-header.dash-header .lead { margin-inline: 0; }
.dash-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4) var(--space-6);
  flex-wrap: wrap;
}
.dash-header-title h1 { margin-bottom: var(--space-2); }
.dash-header-action { max-width: none; margin: 0; }

/* ---- Dashboard v2 (Rick, 17 Jul 2026): top menu + sub-tabs + 75/25 grid.
   The menu band mirrors the admin-nav idiom; the sub-tabs are quiet pills;
   the right column holds the quick-action cards. */
.dash-menu { background: var(--surface); border-bottom: 1px solid var(--line); }
.dash-menu ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-5);
  list-style: none;
  margin: 0;
  padding: var(--space-3) 0;
}
.dash-menu li { margin: 0; }
.dash-menu a {
  text-decoration: none;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--ink-soft);
  padding: var(--space-2) 0;
  border-bottom: 2px solid transparent;
}
.dash-menu a:hover, .dash-menu a.is-active { color: var(--green-deep); border-bottom-color: var(--brass); }

.dash-subtabs { background: var(--paper-2); border-bottom: 1px solid var(--line); }
.dash-subtabs ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  list-style: none;
  margin: 0;
  padding: var(--space-2) 0;
}
.dash-subtabs li { margin: 0; }
.dash-subtabs a {
  text-decoration: none;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--ink-soft);
  padding: 0.3rem 0.8rem;
  border-radius: 999px;
}
.dash-subtabs a:hover { color: var(--green-deep); }
.dash-subtabs a[aria-current="page"] { background: var(--green-wash); color: var(--green-deep); }

/* THESE ROWS WRAP. They were briefly made to scroll sideways in one line to
   save height on a phone; Rick reversed it on 15 Aug 2026, and the reason is
   the one that matters more than the height: a member who cannot see an item
   does not know to look for it, so the menu items past the fold were simply
   never found. Every item is visible at every width, on as many lines as
   that takes. Do not reintroduce a horizontal scroll here. */

.dash-grid { display: grid; gap: var(--space-6); align-items: start; }
@media (min-width: 960px) {
  .dash-grid { grid-template-columns: 3fr 1fr; }
  .dash-grid-single { grid-template-columns: 1fr; }
}
.dash-main { min-width: 0; }
.dash-side .panel { padding: var(--space-4); }
.dash-side h2 { margin-bottom: var(--space-3); }
.dash-side .checklist { font-size: 0.92rem; }
.dash-quick-links { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }

/* Sidebar availability buttons breathe (Rick 17 Jul), and the small print
   underneath keeps its distance from the buttons (Rick 17 Aug). */
.dash-availability-form { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }

/* Referral link/message each carry their own Copy button (Rick 17 Jul). */
.dash-copy-row { display: flex; gap: var(--space-2); align-items: flex-start; }
.dash-copy-row textarea { flex: 1; min-width: 0; }
.dash-copy-btn { flex: none; display: inline-flex; align-items: center; gap: 0.4rem; }
.dash-quick-links a { font-weight: 600; }
.dash-copy-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: var(--green);
  text-decoration: underline;
  text-decoration-color: var(--link-underline);
  cursor: pointer;
}
.dash-copy-link:hover { color: var(--green-deep); }

/* For-therapists "proof" section: the live badge and the specimen
   certificate side by side (Rick, 17 Jul 2026). */
.proof-grid { display: grid; gap: var(--space-6); align-items: stretch; }
@media (min-width: 860px) { .proof-grid { grid-template-columns: 1fr 1fr; } }
.proof-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
}
.proof-card h3 { margin: var(--space-5) 0 var(--space-3); }
.proof-visual { display: flex; justify-content: center; }
.proof-visual img {
  display: block;
  width: 100%;
  max-width: 340px;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.proof-visual-badge {
  align-items: center;
  background: var(--green-deep);
  border-radius: var(--radius);
  padding: var(--space-8) var(--space-5);
}
.proof-visual-badge svg { width: min(300px, 100%); height: auto; }

/* The Overview's calm status line: green = all fine, brass = a date is
   coming up, grey = received/awaiting us. */
.dash-status {
  border: 1px solid var(--line);
  border-left: 5px solid var(--green);
  background: var(--green-wash);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}
.dash-status p { max-width: none; }
.dash-status-warn { border-left-color: var(--brass-deep); background: var(--brass-soft); }
.dash-status-info { border-left-color: var(--ink-mute); background: var(--paper-2); }

.dash-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  margin-bottom: var(--space-4);
}

/* Two form fields side by side where there is room. */
.dash-field-pair { display: grid; gap: 0 var(--space-4); }

@media (min-width: 720px) {
  .dash-field-pair { grid-template-columns: 1fr 1fr; }
  /* Label-grow fallback: inputs line up when one LABEL wraps (the pair cells
     are equal height, so growing the shorter label pushes its input down). */
  .dash-field-pair > .field { display: flex; flex-direction: column; }
  .dash-field-pair > .field > label { flex-grow: 1; }
  /* Subgrid (all modern browsers): label, control and hint each share a row
     across the pair, so the inputs sit level whatever wraps - a two-line
     label OR a hint present on only one side (the label-grow fallback alone
     pushed the hint-less field's input down by the hint height, which is
     exactly the "misaligned password fields" Rick saw on the New password /
     New password again pair). */
  @supports (grid-template-rows: subgrid) {
    .dash-field-pair { grid-template-rows: repeat(4, auto); }
    .dash-field-pair > .field { display: grid; grid-template-rows: subgrid; grid-row: span 4; align-content: start; }
    .dash-field-pair > .field > label { align-self: end; }
  }
}

/* Grouped checkboxes read as one ruled ledger block. */
.dash-fieldset {
  border: 2px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  margin: 0 0 var(--space-5);
}

.dash-fieldset legend {
  font-weight: 700;
  font-size: 1.2rem; /* Rick, 2 Sep 2026 */
  padding-inline: var(--space-2);
}

.dash-fieldset .checkbox-row:last-of-type { margin-bottom: var(--space-2); }

/* --- Onboarding checklist and completion meter (A2 step 8) -------------- */
.meter {
  height: 0.6rem;
  border-radius: 999px;
  background: var(--paper-2);
  border: 1px solid var(--line);
  overflow: hidden;
}

.meter-fill {
  height: 100%;
  background: var(--green);
  border-radius: 999px;
  transition: width 300ms var(--ease-out);
}

.meter-done-0 { width: 0; }
.meter-done-1 { width: 16.7%; }
.meter-done-2 { width: 33.3%; }
.meter-done-3 { width: 50%; }
.meter-done-4 { width: 66.7%; }
.meter-done-5 { width: 83.3%; }
.meter-done-6 { width: 100%; }

.meter-count { margin-top: var(--space-2); }

.checklist { list-style: none; margin: var(--space-4) 0 0; padding: 0; counter-reset: checklist; }

.checklist li {
  counter-increment: checklist;
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--line);
  margin: 0;
}

.checklist li::before {
  content: counter(checklist, decimal-leading-zero);
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--brass);
  flex: none;
  min-width: 1.6rem;
}

.checklist .checklist-done { color: var(--ink-mute); }
.checklist .checklist-done span { text-decoration: line-through; text-decoration-color: var(--line-strong); }
.checklist .checklist-done::after {
  content: '\2713';
  margin-left: auto;
  color: var(--verified);
  font-weight: 700;
}

/* --- Photo section ------------------------------------------------------ */
.dash-photo-row { display: flex; gap: var(--space-5); flex-wrap: wrap; align-items: flex-start; }

/* ROUND, matching the public profile and the result cards. It was a rounded
   square here only, so a member saw one shape while previewing and another
   the moment the photo went live. */
.dash-photo-current img,
.dash-photo-empty {
  width: 10rem;
  height: 10rem;
  border-radius: 50%;
  border: 1px solid var(--line);
  object-fit: cover;
}

.dash-photo-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--green-wash);
  color: var(--green);
  font-family: var(--font-serif);
  font-size: 2.4rem;
  font-weight: 600;
}

.dash-photo-choose { flex: 1; min-width: 16rem; }

/* THE HIDDEN-INPUT-AND-STYLED-LABEL PATTERN IS GONE (21 Aug 2026), and with it
   .photo-file-input, .photo-file-label, .field label.btn and .photo-submit.
   It only ever worked because choosing a file submitted the form on the spot,
   so nobody needed to see which file was chosen. The photo now waits for the
   profile form's own Save button, so the member has to be able to SEE what she
   picked and change her mind - which a plain file input does by itself, with
   JavaScript switched off as well. The wizard has always done it that way and
   this page now matches it. Do not restore a hidden input here. */

/* Mid-save the submit button says so and stops taking a second press. */
.btn.is-busy { opacity: 0.65; pointer-events: none; }

/* A file-size message names the file, and a filename is one unbreakable word.
   Without this it pushes its column - and the page - sideways on a phone. */
.field-error,
.field-hint { overflow-wrap: anywhere; }

/* --- Locations manager --------------------------------------------------- */
.location-entry {
  border-top: 1px solid var(--line);
  padding: var(--space-5) 0 var(--space-4);
  position: relative;
}

.location-grid { display: grid; gap: 0 var(--space-4); }

@media (min-width: 720px) {
  .location-grid { grid-template-columns: 1fr 1fr; }
}

.location-remove { margin-top: var(--space-3); }

.btn-danger-ghost {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--danger);
}

.btn-danger-ghost:hover { border-color: var(--danger); background: var(--danger-wash); color: var(--danger); }

.location-add { border-top: 1px solid var(--line); padding-top: var(--space-4); }

.location-add summary {
  cursor: pointer;
  font-weight: 700;
  color: var(--green);
  margin-bottom: var(--space-4);
}

/* --- Badge embed --------------------------------------------------------- */
.dash-badge-preview {
  display: inline-block;
  padding: var(--space-4);
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* Badge size variations: each row shows the badge rendered at its real size
   on a preview panel, beside its dimensions and embed code. */
.badge-size {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  align-items: center;
  padding: var(--space-5) 0;
  border-top: 1px solid var(--line);
}

.badge-size:first-of-type { border-top: 0; padding-top: var(--space-2); }

.badge-size-preview {
  flex: none;
  display: grid;
  place-items: center;
  min-width: 17rem;
  min-height: 6.5rem;
  padding: var(--space-4);
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.badge-size-detail { flex: 1; min-width: 16rem; }

.badge-size-label {
  display: block;
  font-weight: 700;
  font-size: 0.95rem;
  margin-bottom: var(--space-2);
  font-variant-numeric: tabular-nums;
}

/* Tall enough to show the whole snippet without an inner scroll bar: the
   code is three lines that wrap to eight or nine on a phone, and a box that
   scrolls hides half of what the member is about to copy. main.js grows it
   to its exact content; this min-height is the no-JS floor. */
.badge-embed-code {
  font-size: 0.85rem;
  min-height: 10rem;
  color: var(--ink-soft);
  background: var(--paper-2);
  overflow: hidden;
}

/* --- Embed widget preview page (/embed/preview/*) ------------------------ */
/* The site CSP has no 'unsafe-inline' for styles, so the iframe is sized
   here rather than with a style attribute (which browsers would ignore). */
.embed-preview-frame { display: block; width: 100%; height: 520px; border: 0; }

/* The copyable snippet shows in full, soft-wrapped: copying still yields
   the single unbroken line. */
.embed-snippet {
  margin: 0 0 var(--space-5);
  padding: var(--space-4);
  font-size: 0.85rem;
  color: var(--ink-soft);
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* --- Membership certificate ---------------------------------------------- */
.cert-actions { margin-bottom: var(--space-5); }

.certificate {
  max-width: 46rem;
  margin-inline: auto;
  background: var(--surface);
  border: 2px solid var(--green);
  border-radius: var(--radius);
  padding: var(--space-3);
}

.certificate-inner {
  border: 1px solid var(--brass);
  padding: clamp(var(--space-5), 6vw, var(--space-7));
  text-align: center;
}

.certificate-seal { display: flex; justify-content: center; color: var(--green); margin-bottom: var(--space-4); }

/* The short centred lines need max-width cleared, otherwise the base
   p{max-width:70ch} left-aligns their box inside the centred certificate. */
.certificate-register {
  font-family: var(--font-serif);
  font-size: 1.35rem;
  letter-spacing: 0.02em;
  color: var(--green-deep);
  margin-bottom: var(--space-2);
  max-width: none;
}

.certificate-kicker {
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--brass-deep);
  margin-bottom: var(--space-5);
  max-width: none;
}

.certificate-name {
  font-size: clamp(2rem, 5vw, 2.8rem);
  margin: 0 auto var(--space-3);
  max-width: none;
}

.certificate-line { color: var(--ink-soft); margin-inline: auto; }

/* The founding mark, set in the certificate's own kicker vocabulary so the
   printed page and the PDF read as one design. Source stays sentence case;
   the capitals are typography, applied here. */
.certificate-founding {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--brass-deep);
  margin: var(--space-4) auto 0;
  max-width: none;
}

.certificate-details {
  max-width: 26rem;
  margin: var(--space-6) auto;
  text-align: left;
}

.certificate-footer-note {
  font-size: 0.85rem;
  color: var(--ink-mute);
  max-width: 36rem;
  margin: var(--space-6) auto var(--space-4);
}

.certificate-entity {
  font-family: var(--font-serif);
  font-size: 0.95rem;
  color: var(--green-deep);
  margin-bottom: 0;
  max-width: none;
}

/* Print: hide the site chrome, let the certificate own the page (A4). */
@media print {
  body.page-certificate .site-header,
  body.page-certificate .site-footer,
  body.page-certificate .skip-link,
  body.page-certificate .dash-menu,
  body.page-certificate .dash-subtabs,
  body.page-certificate .dash-noprint { display: none !important; }

  body.page-certificate { background: var(--surface); }

  body.page-certificate .section { padding-block: 0; }

  body.page-certificate .certificate {
    max-width: none;
    border-width: 2px;
    box-shadow: none;
    page-break-inside: avoid;
  }
}

/* =========================================================================
   pkg-admin-core - admin sign-in, TOTP enrolment, dashboard and settings
   Token-driven additions only; merged into main.css by the orchestrator.
   ========================================================================= */

/* The admin nav partial renders at page top level: keep its band full-bleed
   while its list lines up with .container content. */
.admin-nav ul {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

/* Admin screens breathe a little less than public editorial pages. */
.admin-main { padding-block: var(--space-6) var(--space-8); }

/* Sign-in: a narrow certificate on the desk. */
.admin-auth-panel {
  max-width: 28rem;
  margin-inline: auto;
  padding: var(--space-6);
}

.admin-auth-panel h1 { font-size: clamp(1.7rem, 3vw, 2.1rem); }

.admin-auth-seal { color: var(--green); }
.admin-auth-seal .seal-tick { color: var(--brass); }

/* Registration-index numerals on large seal lockups (seal.php $index):
   always brass, in any context. Dormant until a caller passes $index. */
.seal-index { color: var(--brass); }

/* TOTP enrolment. */
.admin-enrol-panel {
  max-width: 42rem;
  margin-inline: auto;
  padding: var(--space-6);
}

.admin-enrol-panel h1 { font-size: clamp(1.7rem, 3vw, 2.1rem); }

.admin-enrol-subhead {
  font-size: 1.05rem;
  margin-top: var(--space-5);
  margin-bottom: var(--space-2);
}

/* The base32 setup key: large, grouped, unmistakable. */
.totp-key {
  font-size: 1.3rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums;
  color: var(--green-deep);
  background: var(--green-wash);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  overflow-wrap: anywhere;
}

/* The otpauth:// URI shown as plain text for apps that accept it. */
.totp-uri {
  font-size: 0.85rem;
  color: var(--ink-soft);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  overflow-wrap: anywhere;
}

/* Dashboard stats that double as links into their queue or list. */
a.stat {
  display: block;
  text-decoration: none;
  transition: background-color 160ms var(--ease-out);
}

a.stat:hover { background: var(--verified-wash); }
a.stat:hover .stat-number { color: var(--green); }
a.stat .stat-label { display: block; }

.admin-quick-links {
  list-style: none;
  padding-left: 0;
  margin-bottom: 0;
}

.admin-quick-links li {
  margin: 0;
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--line);
}

.admin-quick-links li:last-child { border-bottom: 0; padding-bottom: 0; }

.admin-quick-links a { font-weight: 600; }

.admin-quick-links .text-small { display: block; }

/* Settings form: readable measure, panels stacked. */
.admin-form { max-width: 46rem; }

.admin-founding-note { margin-bottom: var(--space-5); }

/* Inline field error (same rule as pkg-legal-contact; harmless duplicate). */
.field-error { color: var(--danger); }

/* --- Package: admin-ops (admin applications queue, member CRUD,
       enquiry log, email log) ---
   Token-driven only: existing custom properties, no new colours or fonts. */

/* Top gap comes from the hoisted .admin-nav band's margin (same as the admin
   dashboard); the container itself only pads the bottom. */
.admin-page { padding-block: 0 var(--space-8); }

/* ADMIN TEXT IS NOT PUBLIC PROSE.
   The global `p { max-width: 70ch }` is a READING MEASURE: about 70 characters
   a line is the comfortable span for someone reading an article, and it is
   right for every public page. It is wrong here. Admin panels sit inside the
   80rem shell, so 70ch left roughly a third of every panel empty while the
   text stacked up in a narrow column - Rick asked why, 16 Aug 2026, looking at
   the document pre-check panel. These screens are SCANNED, one member after
   another, not read.
   92ch, not `none`: an unbounded line across 1280px is genuinely hard to scan
   because the eye loses the start of the next line, so the cap is raised to
   use the panel rather than removed. Anything that sets its own max-width
   (.admin-steps, the centred blocks) still wins on specificity or later
   ordering. Only <p> is touched: list items and definition lists were never
   capped, so widening them was not the complaint and narrowing them would be a
   change with no reason behind it. */
.admin-page p { max-width: 92ch; }

.admin-page-head { margin-bottom: var(--space-5); }
.admin-page-head .admin-title { font-size: clamp(1.7rem, 3vw, 2.2rem); margin-bottom: var(--space-2); }
.admin-page-head p { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }

.admin-backlink { font-size: 0.92rem; margin-bottom: var(--space-4); }
.admin-backlink a { color: var(--ink-soft); text-decoration: none; }
.admin-backlink a:hover { color: var(--green-deep); text-decoration: underline; }

/* Queue and log filter tabs */
.admin-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  margin: 0 0 var(--space-5);
  padding: 0;
}

.admin-tabs li { margin: 0; }

.admin-tabs a {
  display: inline-block;
  padding: 0.35rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-soft);
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
}

.admin-tabs a:hover { border-color: var(--line-strong); color: var(--green-deep); }

.admin-tabs a[aria-current="page"] {
  background: var(--green-wash);
  border-color: var(--green);
  color: var(--green-deep);
}

/* Tables in the admin panels */
.admin-table { width: 100%; font-size: 0.95rem; }
.admin-table td { vertical-align: top; }
.admin-table .status-pill { white-space: nowrap; }
/* Members list: a clear gap between the name and its chips (Rick, 21 Sep
   2026: the Featured and Founding chips sat hard against the name). */
.member-name-cell strong { margin-right: 0.55rem; }
.member-name-cell .chip + .chip { margin-left: 0.2rem; }
/* The pre-check verdict table: one heading row per document read (18 Sep 2026). */
.doc-check-table tr.doc-check-group th { text-align: left; font-weight: 700; padding-top: var(--space-4); border-bottom: 2px solid var(--line); }

/* Two-column working layout (applicant beside documents, and similar) */
.admin-split { display: grid; gap: var(--space-5); align-items: start; }

@media (min-width: 900px) {
  .admin-split { grid-template-columns: 1fr 1fr; }
}

/* Definition lists on detail pages */
.admin-dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-2) var(--space-5); margin: 0 0 var(--space-5); }
.admin-dl dt { color: var(--ink-mute); font-size: 0.9rem; padding-top: 0.1rem; }
.admin-dl dd { margin: 0; }

.admin-plain-list { list-style: none; margin: 0 0 var(--space-5); padding: 0; }
.admin-plain-list li { margin: 0 0 var(--space-3); padding-bottom: var(--space-3); border-bottom: 1px solid var(--line); }
.admin-plain-list li:last-child { border-bottom: 0; }

/* A multi-line alert needs air between its lines. Without it the warning reads
   as one block and the instruction at the end - the only part that tells the
   operator what to DO - is the part that gets skimmed past. Scoped to p + p,
   so every single-paragraph error message is untouched. */
.form-error p + p,
.form-success p + p { margin-top: var(--space-2); }
/* The dashboard flash sits in a centred card: centre its words too (Rick, 2 Sep 2026). */
.page-dashboard .dash-flash { text-align: center; }

/* The document pre-check summary. Short bullet points, plain disc markers, one
   fact each - the shape Rick asked for on 16 Aug 2026 in place of the
   paragraph that used to sit here. */
.doc-check-summary { margin: 0 0 var(--space-4); padding-left: 1.3rem; }
.doc-check-summary li { margin-bottom: var(--space-2); }
.doc-check-summary li:last-child { margin-bottom: 0; }

.admin-prewrap { white-space: pre-wrap; }

/* Side-by-side row actions (nominations queue: update form + delete form) */
.admin-inline-forms { display: flex; flex-wrap: wrap; gap: var(--space-5); align-items: end; }
.admin-inline-forms form:first-child { flex: 1 1 22rem; }

/* Inline document viewer */
.doc-figure { margin: 0 0 var(--space-5); }
.doc-figure figcaption { margin-bottom: var(--space-3); }
.doc-figure figcaption strong { display: block; }

.doc-figure img {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

/* The A5 verification checklist (working aid, not stored) */
.check-list { list-style: none; margin: 0; padding: 0; max-width: 52rem; }
.check-list li { margin: 0; border-bottom: 1px solid var(--line); }
.check-list li:last-child { border-bottom: 0; }
.check-list label { display: flex; gap: var(--space-3); align-items: flex-start; padding: var(--space-3) 0; cursor: pointer; }
.check-list input[type="checkbox"] { flex: none; width: 1.15rem; height: 1.15rem; margin-top: 0.2rem; accent-color: var(--green); }

/* Form layouts inside panels */
.admin-form-grid { display: grid; gap: 0 var(--space-5); }
/* One rule between school rows on the trainings editor (Rick, 18 Sep 2026).
   The field above already carries its own bottom margin, so the rule only
   adds space below. */
.admin-training-rule { border-top-width: 2px; margin: 0 0 var(--space-5); }
/* An Other-courses row: Course, Detail and Certificate document on ONE line
   from a comfortable width (Rick, 18 Sep 2026); two columns below that. */
@media (min-width: 900px) {
  .admin-form-grid.admin-form-grid-3 { grid-template-columns: minmax(0, 2fr) minmax(0, 1.4fr) minmax(0, 2fr); }
}

@media (min-width: 700px) {
  .admin-form-grid { grid-template-columns: 1fr 1fr; }
}

/* A fee item's three fields (name / minutes / price) on ONE row from tablet up;
   name gets the most width, minutes and price stay narrow. minmax(0,..) stops a
   long name from blowing the grid out. Shared by the admin concierge builder,
   the member editable preview and the member dashboard fee editors. */
.fee-fields-row { display: grid; gap: 0 var(--space-4); }
@media (min-width: 640px) {
  .fee-fields-row { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr); }
}

.admin-check-grid { display: grid; gap: var(--space-2); margin-bottom: var(--space-5); }

@media (min-width: 700px) {
  .admin-check-grid { grid-template-columns: 1fr 1fr; }
}

.check-label { display: flex; gap: var(--space-2); align-items: flex-start; font-size: 0.97rem; cursor: pointer; }
.check-label input[type="checkbox"] { flex: none; width: 1.05rem; height: 1.05rem; margin-top: 0.25rem; accent-color: var(--green); }

/* Status-change blocks (lapse / reinstate / remove) */
.admin-status-form { padding-bottom: var(--space-4); margin-bottom: var(--space-4); border-bottom: 1px solid var(--line); }
.admin-status-form h3 { font-size: 1.05rem; }
.admin-status-form .btn { margin-top: var(--space-3); }

/* Destructive areas keep the same tokens, flagged with the danger hue */
.danger-panel { border-color: var(--danger); }
.danger-panel > h2 { color: var(--danger); }

.admin-error-text { color: var(--danger); }

/* ---- Admin member page: stage banner + accordion sections ------------------
   The stage banner shows where a member is (a stepper) and the one next action;
   its tone (action/wait/done/warn) tints the accent. The record is then broken
   into collapsible <details> accordion sections. */
.admin-stage {
  --stage-accent: var(--ink-mute);
  border: 1px solid var(--line);
  border-left: 5px solid var(--stage-accent);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
  display: grid;
  gap: var(--space-4);
}
/* Outstanding side jobs. Sits under the stage banner and OUTSIDE every
   accordion, because the thing it exists to say was previously only visible to
   someone who already knew to open the right collapsed section. */
.admin-todo {
  border: 1px solid var(--danger);
  border-left: 5px solid var(--danger);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}
.admin-todo-title { margin: 0 0 var(--space-3); font-size: 1.05rem; color: var(--danger); }
.admin-todo-list { list-style: none; padding: 0; margin: 0; }
.admin-todo-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--line);
}
.admin-todo-list li:first-child { border-top: 0; padding-top: 0; }
.admin-todo-list li > span { flex: 1 1 22rem; }
.admin-todo-list .btn { flex: 0 0 auto; }

.admin-stage-action { --stage-accent: var(--brass-deep); }
.admin-stage-wait   { --stage-accent: var(--ink-mute); }
.admin-stage-done   { --stage-accent: var(--green); }
.admin-stage-warn   { --stage-accent: var(--danger); }

.admin-stage-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
}
.admin-stage-step {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 1rem; /* Rick, 2 Sep 2026 */
  color: var(--ink-mute);
}
.admin-stage-dot {
  flex: 0 0 auto;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--paper-2);
  color: var(--ink-mute);
  border: 1px solid var(--line);
}
.admin-stage-step.is-done { color: var(--ink-soft); }
.admin-stage-step.is-done .admin-stage-dot { background: var(--green-wash); color: var(--green); border-color: var(--green-wash); }
.admin-stage-step.is-current { color: var(--ink); font-weight: 700; }
.admin-stage-step.is-current .admin-stage-dot { background: var(--stage-accent); color: var(--on-green); border-color: var(--stage-accent); }
.admin-stage-step-label { white-space: nowrap; }

.admin-stage-label { font-size: 1.125rem; } /* 18px, Rick 2 Sep 2026 */
.admin-stage-badge {
  display: inline-block;
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.12em 0.55em;
  border-radius: 999px;
  background: var(--stage-accent);
  color: var(--on-green);
  vertical-align: middle;
  margin-right: var(--space-2);
}
.admin-stage-next { color: var(--ink-soft); margin-top: var(--space-1); }
.admin-stage-now .btn { margin-top: var(--space-3); }
/* Guided review screen: a link and a form side by side under the draft. */
.admin-review-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); margin-top: 1.25rem; } /* 20px clear of the draft box (Rick, 18 Sep 2026) */
/* Photos panel: profile photo + gallery tiles, each with its own small form. */
.admin-photos { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: var(--space-4); }
.admin-photo-card { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--paper-2); padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
.admin-photo-card img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius); display: block; }
.admin-photo-empty { aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center; border: 1px dashed var(--line); border-radius: var(--radius); color: var(--ink-mute); font-size: 0.9rem; }
.admin-photo-label { margin: 0; font-weight: 700; font-size: 0.9rem; }
.admin-photo-card .field { margin: 0; }
.admin-photo-card input[type=file] { font-size: 0.85rem; max-width: 100%; }
/* The file control and its button sat touching: the form is ONE flex child
   of the card, so the card's gap never reached inside it (Rick, 18 Sep 2026).
   The actions row holds Replace beside Download. */
.admin-photo-card form { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3); }
.admin-photo-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.btn-icon { display: inline-flex; align-items: center; gap: 0.4rem; }
.btn-icon svg { width: 1em; height: 1em; flex: none; }
.btn-icon-only { display: inline-flex; align-items: center; justify-content: center; padding: 0.55rem 0.7rem; }
.btn-icon-only svg { width: 1.15em; height: 1.15em; flex: none; }
/* Shown only when main.js has NOT run (the mirror of .js-only): a plain
   submit button behind a file input that otherwise uploads on choice. */
html.js .js-hide { display: none !important; }
/* The drafted bio, read before it is sent: body size, boxed, never the public pull-quote style. */
.admin-draft-text { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--paper-2); padding: var(--space-4) var(--space-5); white-space: normal; }
.admin-draft-text p { margin: 0 0 var(--space-3); }

.admin-section {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-4);
}
.admin-section-head {
  list-style: none;
  cursor: pointer;
  padding: var(--space-4) var(--space-5);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  border-radius: var(--radius-lg);
}
.admin-section-head::-webkit-details-marker { display: none; }
.admin-section-head:hover { background: var(--paper-2); }
.admin-section-title { font-family: var(--font-serif); font-size: 1.2rem; font-weight: 600; }
.admin-section-head::after {
  content: "";
  margin-left: auto;
  width: 0.65rem;
  height: 0.65rem;
  border-right: 2px solid var(--ink-mute);
  border-bottom: 2px solid var(--ink-mute);
  transform: rotate(45deg);
  transition: transform 150ms var(--ease-out);
}
.admin-section[open] > .admin-section-head::after { transform: rotate(-135deg); }
.admin-section[open] > .admin-section-head {
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.admin-section-body { padding: var(--space-5); }
.admin-section-body > :first-child { margin-top: 0; }

/* Numbered operator instructions. One step per line, using the full width of
   the panel: the global `p { max-width: 70ch }` is a reading measure for public
   prose and has no business narrowing an admin instruction. */
.admin-steps {
  margin: 0 0 var(--space-5);
  padding-left: 1.6rem;
  max-width: none;
  color: var(--ink-soft);
}
.admin-steps > li {
  max-width: none;
  margin-bottom: var(--space-3);
  padding-left: 0.3rem;
}
.admin-steps > li:last-child { margin-bottom: 0; }
.admin-steps > li::marker { font-weight: 700; color: var(--ink); }
.admin-steps strong { color: var(--ink); }

/* The live version: which step you are on, at a glance. */
.admin-steps-live > li { margin-bottom: var(--space-4); }
.admin-steps-live > li.is-done { color: var(--ink-mute); }
.admin-steps-live > li.is-done::marker { color: var(--green); }
.admin-steps-live > li.is-done > strong { color: var(--ink-mute); }
.admin-steps-live > li.is-current {
  background: var(--brass-soft);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  margin-left: -0.4rem;
}
.admin-steps-live > li.is-current::marker { color: var(--brass-deep); }
.admin-steps-live > li.is-todo { color: var(--ink-mute); }
/* An AUTOMATIC step is not the operator's turn and must never be highlighted
   as one. Muted like a todo, but it can never take the brass "do this next"
   treatment because the class is exclusive. */
.admin-steps-live > li.is-auto { color: var(--ink-mute); }
.admin-steps-live .status-pill { margin-left: var(--space-2); vertical-align: middle; }

/* VerifiedPractitioner setup block: the seven values to copy across. */
.admin-copyblock {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  margin: var(--space-5) 0;
  background: var(--paper);
}
.admin-copy-list { margin: 0; }
.admin-copy-list dt {
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--ink-soft);
  margin-top: var(--space-3);
}
.admin-copy-list dt:first-of-type { margin-top: 0; }
.admin-copy-list dd {
  margin: var(--space-1) 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
}
.admin-copy-value {
  flex: 1 1 18rem;
  word-break: break-word;
  font-family: var(--font-sans);
}
.admin-copy-empty { color: var(--ink-mute); font-style: italic; }
.admin-copy-btn { flex: 0 0 auto; }
.admin-copy-btn.is-copied { border-color: var(--green); color: var(--green); }
/* Clipboard refused: the text is selected instead, and the button says so. */
.admin-copy-btn.is-copy-failed { border-color: var(--brass-deep); color: var(--brass-deep); }
.admin-copy-value::selection { background: var(--brass-soft); }
.admin-section-danger { border-color: var(--danger); }
.admin-section-danger > .admin-section-head .admin-section-title { color: var(--danger); }

/* Filter bar on list pages */
.filter-bar { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: flex-end; margin-bottom: var(--space-5); }
.filter-bar .field { margin-bottom: 0; }
.filter-bar input[type="search"] { min-width: 18rem; }

/* Inline row-level action forms */
.admin-inline-form { display: inline-block; margin: 0 var(--space-2) var(--space-2) 0; }
/* A course name is long ("LTA Fill and Flush: Lymphoedema Management of the Arm
   and Trunk"), and an operator cannot rename what they cannot read. The field
   shrinks with the column rather than clipping the name. */
.admin-course-rename { display: block; }
.admin-course-rename input[type="text"] { width: 100%; min-width: 0; margin-bottom: var(--space-2); }
.admin-courses-table td:first-child { min-width: 24rem; }
.admin-course-flags { margin: var(--space-2) 0 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
.admin-course-flags:empty { display: none; }

/* Enquiry excerpts expand in place; content never leaves the page */
.admin-excerpt { max-width: 26rem; }
.admin-excerpt summary { cursor: pointer; color: var(--ink-soft); }
.admin-excerpt summary:hover { color: var(--green-deep); }
.admin-excerpt p { margin-top: var(--space-3); }

/* Pagination on the long logs */
.pagination { display: flex; gap: var(--space-4); align-items: center; margin-top: var(--space-5); }

/* ===========================================================================
   Compare page (/compare) and the profile's experience note
   =========================================================================== */

/* The member's own words about the one term this brand asks about. A quote
   block, because that is what it is: not our finding, theirs. Deliberately in
   About and never in the Verification card, so it can never borrow the
   authority of a fact somebody checked. */
.profile-experience-note {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--line-strong);
  background: var(--paper-2);
  color: var(--ink-soft);
  font-style: normal;
}

/* Wide content scrolls inside its own box. The page body must never scroll
   sideways, and three member columns plus the row labels will exceed a phone
   long before they exceed a desktop. */
.cmp-scroll {
  overflow-x: auto;
  margin-bottom: var(--space-5);
}

.cmp-table {
  width: 100%;
  border-collapse: collapse;
  min-width: 34rem;
}

.cmp-table th,
.cmp-table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line);
}

/* The row labels stay put while the member columns scroll under them:
   without this, scrolling right on a phone leaves a grid of bare values with
   nothing saying what any of them are. */
.cmp-table tbody th[scope="row"],
.cmp-table tfoot th[scope="row"] {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--paper);
  font-weight: 600;
  color: var(--ink-soft);
  min-width: 11rem;
}

.cmp-table thead th { border-bottom: 2px solid var(--line-strong); }

/* THE HONEST PART OF THIS TABLE. A grid implies every row was established the
   same way; these headings are what stop it saying that. Styled as loud as a
   section heading precisely because they are load-bearing, not decoration. */
.cmp-group th {
  position: sticky;
  left: 0;
  background: var(--green-wash);
  color: var(--green-deep);
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--line-strong);
}

/* The column heads are inside <th>, and the base th rule uppercases with
   letter-spacing - which is right for a row LABEL and wrong for a person's
   name and town. Reset it here rather than weakening the base rule. */
.cmp-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 10rem;
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--ink);
}

.cmp-head img {
  width: 4rem;
  height: 4rem;
  border-radius: 50%;
  object-fit: cover;
  margin-bottom: var(--space-1);
}

.cmp-name { font-weight: 600; }

.cmp-town {
  font-size: 0.85rem;
  color: var(--ink-mute);
}

/* Absence must read as absence, never as a fact. A blank cell in a comparison
   is ambiguous - did nobody ask, or did they say no? - so every empty value
   says which, quietly. */
.cmp-none { color: var(--ink-mute); }

.cmp-sub {
  display: block;
  margin-top: var(--space-1);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--ink-mute);
}

.cmp-list {
  margin: 0;
  padding-left: 1.1rem;
  font-size: 0.9rem;
  line-height: 1.6;
}

.cmp-note { max-width: none; }

/* -------------------------------------------------------------------------
   Icon cards (For Therapists "What membership includes", About governance).
   One icon set, one layout: the icon holds a fixed column on the left so
   every heading in a row starts on the same line, and it drops above the
   text on a phone rather than squeezing the words into a narrow gutter.
   ------------------------------------------------------------------------- */
.card-iconed {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
}

.card-iconed-body { min-width: 0; }

.card-icon {
  flex: none;
  width: 3.25rem;
  height: 3.25rem;
  margin-top: 0.15rem;
}

@media (max-width: 479.98px) {
  .card-iconed { flex-direction: column; gap: var(--space-3); }
}

/* The About governance grid: the cards were far apart and tight inside.
   Rick, 18 Aug 2026. */
.page-about .verification-box { padding: var(--space-6); }

@media (min-width: 640px) {
  .page-about .grid-2 { gap: var(--space-5); }
}

/* The product specimen's label now sits UNDER the card as one line. */
.preview-frame-caption {
  margin: var(--space-3) 0 0;
  text-align: center;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--ink-mute);
}

/* Heading for the homepage trust strip, which had none at all. */
.trust-strip-title { margin: 0 0 var(--space-4); }

/* -------------------------------------------------------------------------
   Cookie consent bar (19 Aug 2026)

   Rendered only where analytics is configured, and starts hidden: JS reveals
   it for a visitor who has not answered yet. Fixed to the bottom rather than
   the top so it never fights the sticky header or the founding notice strip,
   both of which live at the top of the viewport.

   Colours and button treatment are the .cta-band pattern, not a new one:
   dark jade ground, Rick's gold for the affirmative action (black on gold,
   ~9:1) and a cream outline for the other. Reusing that pairing is what
   keeps the bar looking like part of the site rather than a plugin.
   ------------------------------------------------------------------------- */
.cookie-bar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 60;
  background: #254338;
  color: var(--on-green);
  border-top: 1px solid var(--on-green-hairline);
  box-shadow: 0 -6px 24px rgb(0 0 0 / 0.18);
}

.cookie-bar[hidden] { display: none; }

.cookie-bar-inner {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding-block: var(--space-4);
  flex-wrap: wrap;
}

.cookie-bar-text {
  max-width: none; /* centred <p> gotcha: the base 70ch cap strands it left */
  margin: 0;
  flex: 1 1 22rem;
  font-size: 0.9375rem;
  line-height: 1.5;
}

.cookie-bar-text a { color: inherit; }

.cookie-bar-actions {
  display: flex;
  gap: var(--space-3);
  flex: 0 0 auto;
}

/* 44px minimum touch target: .btn-sm alone lands near 38px, which is fine
   for a link in a paragraph and too small for the only two controls on a
   bar a visitor must dismiss on a phone. */
.cookie-bar .btn {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.cookie-bar .btn-primary { background: #DBA239; color: #000; }
.cookie-bar .btn-primary:hover { background: color-mix(in oklab, #DBA239, #000 12%); color: #000; }
.cookie-bar .btn-secondary { color: var(--on-green); border-color: var(--cta-outline); }
.cookie-bar .btn-secondary:hover { background: var(--cta-outline-wash); color: var(--on-green); border-color: var(--on-green); }

/* On a phone the two buttons share the full width rather than stacking into
   a tall bar that covers the page behind it. */
@media (max-width: 560px) {
  .cookie-bar-inner { gap: var(--space-4); }
  .cookie-bar-actions { flex: 1 1 100%; }
  .cookie-bar-actions .btn { flex: 1 1 0; }
}

/* The bar is a live control, not part of the document. */
@media print {
  .cookie-bar { display: none !important; }
}

/* -------------------------------------------------------------------------
   Sample profile framing (19 Aug 2026)

   Only ever rendered on /sample-profile. The flag has to read as a label on
   the page rather than as part of the listing, so it takes the brass accent
   used for status elsewhere rather than the jade the profile itself uses -
   if it looked like the profile, it would be read as the profile.
   ------------------------------------------------------------------------- */
.sample-flag {
  background: var(--paper-2);
  border-bottom: 1px solid var(--brass-soft);
}

.sample-flag p {
  max-width: none; /* centred <p> gotcha: the base 70ch cap strands it left */
  margin: 0;
  padding-block: var(--space-3);
  font-size: 0.9375rem;
  line-height: 1.5;
}

/* The therapist-facing close beneath the specimen. Jade band so it reads as
   the site talking, not as more of the fictional listing above it. */
.sample-cta {
  margin-top: var(--space-7);
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  background: #254338;
  color: var(--on-green);
}

.sample-cta h2 { margin-top: 0; color: var(--on-green); }
.sample-cta p { max-width: 60ch; }
.sample-cta .btn-primary { background: #DBA239; color: #000; }
.sample-cta .btn-primary:hover { background: color-mix(in oklab, #DBA239, #000 12%); color: #000; }
.sample-cta .btn-secondary { color: var(--on-green); border-color: var(--cta-outline); }
.sample-cta .btn-secondary:hover { background: var(--cta-outline-wash); color: var(--on-green); border-color: var(--on-green); }

/* Leader rows on narrow phones (19 Aug 2026).

   .leader-label is flex:none, so a long label plus the dots' 1.5rem minimum
   plus the value cannot fit at 320px and the value is pushed off the right
   edge, taking the whole page's scroll width with it. Found on
   /sample-profile, where "Insurance document checked ... 19 June 2026"
   overflowed by 9px - but it is the SHARED verification card, so the first
   real member profile would have done exactly the same on a small phone.

   Under 420px the row becomes label-above-value and the dots go: a dotted
   leader only means anything when both ends sit on one line. Used by the
   profile, the dashboard, the certificate and both join pages, all of which
   gain the same behaviour and none of which relied on the overflow. */
@media (max-width: 420px) {
  .leader-row { flex-wrap: wrap; gap: 0.15rem; }
  .leader-row .leader-label { flex: 1 1 100%; }
  .leader-row .leader-dots { display: none; }
  .leader-row .leader-value { text-align: left; }
}

/* =========================================================================
   ADMIN, 25 Aug 2026 (Rick): a settings screen you can find things on, and
   a navigation bar that is not fourteen items wide.
   ========================================================================= */

/* The settings column was 46rem hard against the left edge of a wide
   container, so it read as a page with a hole in it. Centre it. */
.admin-form { margin-inline: auto; }

/* FOLDING PANELS. Native <details>, so the screen still opens and closes
   with JavaScript off; main.js only reports which folds were left open so
   the server can remember them. They all start closed - thirteen panels
   open at once is a scroll, not a screen. */
.panel-fold { padding: 0; }

.panel-fold > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  cursor: pointer;
  list-style: none;
}
.panel-fold > summary::-webkit-details-marker { display: none; }
.panel-fold > summary:hover { background: var(--paper); }

.panel-fold-title { margin: 0; font-size: 1.45rem; }

/* Chevron drawn in CSS rather than shipped as markup: one less thing that
   can fall out of step with the open state. */
.panel-fold > summary::after {
  content: "";
  flex: none;
  width: 0.55rem;
  height: 0.55rem;
  margin-right: 0.25rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  opacity: 0.5;
  transition: transform 160ms;
}
.panel-fold[open] > summary::after { transform: rotate(-135deg); }

/* The panel's padding moved onto the summary, so the body indents itself. */
.panel-fold > :not(summary) { margin-inline: var(--space-5); }
.panel-fold[open] > :last-child { margin-bottom: var(--space-5); }

/* Settings: separate country panels and a compact, persistent action bar. */
.settings-workspace { max-width: 72rem; }
.settings-toolbar {
  position: sticky; top: calc(var(--header-offset) + .5rem); z-index: 20;
  display: flex; align-items: end; gap: var(--space-3);
  padding: var(--space-4); margin-bottom: var(--space-5);
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: 0 3px 14px rgb(0 0 0 / 8%);
}
.settings-jump { flex: 1; min-width: 0; }
.settings-jump label { display: block; font-weight: 600; margin-bottom: var(--space-2); }
.settings-jump select { width: 100%; }
.settings-workspace .panel-fold { scroll-margin-top: calc(var(--header-offset) + 8rem); }
.settings-fields { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 var(--space-5); }
.settings-fields .field { min-width: 0; }
.settings-fields .settings-textarea { min-height: 8rem; }
.settings-subheading {
  grid-column: 1 / -1; margin: var(--space-4) 0;
  padding-bottom: var(--space-2); border-bottom: 1px solid var(--line);
  font-size: 1rem; color: var(--ink-soft);
}
@media (min-width: 760px) {
  .settings-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .settings-field-wide { grid-column: 1 / -1; }
}
@media (max-width: 600px) {
  .settings-toolbar { flex-wrap: wrap; }
  .settings-jump { flex-basis: 100%; }
  .settings-toolbar .btn { flex: 1; }
  .settings-workspace .panel-fold { scroll-margin-top: calc(var(--header-offset) + 12rem); }
}

/* GROUPED ADMIN NAV. Reuses the public header's dropdown component - the same
   .has-children / .nav-sub-toggle / .nav-sub markup and the same main.js
   handler - so the codebase has ONE dropdown, not two. Only the colours
   change, because the header band is dark and this one is light. */
.admin-nav .has-children { position: relative; }

.admin-nav .nav-sub-toggle {
  font-size: 0.95rem;
  color: var(--ink-soft);
  padding: var(--space-2) 0;
}
.admin-nav .nav-sub-toggle:hover,
.admin-nav .has-children.is-active > .nav-sub-toggle {
  color: var(--ink);
  border-bottom-color: var(--brass);
}

.admin-nav .nav-sub {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  transform: none;
  min-width: 14rem;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: var(--shadow-2);
  z-index: 20;
}
.js .admin-nav .has-children.open > .nav-sub { display: block; }
html:not(.js) .admin-nav .has-children:hover > .nav-sub,
html:not(.js) .admin-nav .has-children:focus-within > .nav-sub { display: block; top: 100%; }

.admin-nav .nav-sub li { margin: 0; }
.admin-nav .nav-sub a {
  display: block;
  padding: 0.45rem 0.7rem;
  border-radius: 7px;
  border-bottom: 0;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--ink-soft);
}
.admin-nav .nav-sub a:hover,
.admin-nav .nav-sub a[aria-current="page"] { background: var(--paper); color: var(--ink); }

/* -------------------------------------------------------------------------
   Procedure ticker (.ticker-strip) - added 6 Sep 2026 for the MLD homepage
   (Rick's ask): a thin dark strip under the hero that slides a list of
   procedures across the screen. Engine-level so any brand can use it; the
   words live in the page that prints them. HOW THE LOOP WORKS: the page
   prints the list twice inside .ticker-track, the track is as wide as its
   content, and the keyframe slides it left by exactly half its own width,
   so the second copy arrives where the first started and the loop has no
   visible seam. Each item carries its own leading champagne diamond, so the
   spacing is identical across the seam. The strip pauses on hover so a
   reader can hold a word still, and prefers-reduced-motion stops the slide:
   the row stands still and scrolls sideways, the duplicate is hidden.
   Colours are tokens only (--green-deep is the dark band ground in every
   brand, --brass-on-dark the accent cut made for it), so the strip follows
   each brand's theme.css without a rule here changing. The two edge fades
   are pseudo-elements in the band's own colour, so the words melt into the
   band at each side rather than being cut off flat. --ticker-h is the
   strip's fixed height; the hero reads the same token to reserve the room.
   ------------------------------------------------------------------------- */
.ticker-strip,
.hero-bleed:has(.ticker-strip) { --ticker-h: 2.75rem; }

.ticker-strip {
  position: relative;
  overflow: hidden;
  background: var(--green-deep);
  color: var(--paper);
  height: var(--ticker-h);
  display: flex;
  align-items: center;
}

.ticker-strip::before,
.ticker-strip::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 4rem;
  z-index: 1;
  pointer-events: none;
}

.ticker-strip::before {
  left: 0;
  background: linear-gradient(90deg, var(--green-deep), color-mix(in oklab, var(--green-deep) 0%, transparent));
}

.ticker-strip::after {
  right: 0;
  background: linear-gradient(270deg, var(--green-deep), color-mix(in oklab, var(--green-deep) 0%, transparent));
}

.ticker-track {
  display: flex;
  flex: none;
  width: max-content;
  animation: ticker-scroll 36s linear infinite;
  will-change: transform;
}

.ticker-strip:hover .ticker-track { animation-play-state: paused; }

.ticker-list {
  display: flex;
  flex: none;
  gap: var(--space-6);
  margin: 0;
  padding: 0 var(--space-6) 0 0;
  list-style: none;
}

.ticker-list li {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

.ticker-list li::before {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  flex: none;
  transform: rotate(45deg);
  background: var(--brass-on-dark);
}

@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .ticker-strip { overflow-x: auto; scrollbar-width: thin; }
  .ticker-track { animation: none; }
  .ticker-list[aria-hidden="true"] { display: none; }
  .ticker-strip::before,
  .ticker-strip::after { content: none; }
}

/* Ticker INSIDE the hero copy (Rick, 6 Sep 2026, third placement): on a
   phone the strip sits between the subhead sentence and the search card;
   on desktop it stays below the copy and the photograph as the hero's
   bottom edge, where it has been since it was the hero's sibling. The
   markup therefore lives in .hero-copy after .hero-subhead, and on desktop
   it is lifted out of the flow and pinned to the hero's bottom edge: the
   hero reserves the strip's height in its bottom padding and its
   min-height, so the copy keeps its centre and the visible photograph its
   crop (the media block stops above the strip). The container loses its
   position so the strip anchors to the hero rather than to it, and the
   photograph moves to z-index -1 inside the hero's own stacking context
   (isolation: isolate) so the in-flow copy still paints above it. Scoped
   with :has() so a hero without a ticker, on any brand or page, does not
   move. */
.hero-bleed:has(.ticker-strip) {
  padding-bottom: calc(var(--space-7) + var(--ticker-h));
  min-height: calc(34rem + var(--ticker-h));
}

.hero-bleed:has(.ticker-strip) > .container { position: static; }
.hero-bleed:has(.ticker-strip) > .hero-bleed-media { z-index: -1; bottom: var(--ticker-h); }

.hero-bleed:has(.ticker-strip) .ticker-strip {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
}

@media (min-width: 960px) {
  .hero-bleed:has(.ticker-strip) {
    padding-bottom: calc(var(--space-8) + var(--ticker-h));
    min-height: calc(40rem + var(--ticker-h));
  }
}

/* Private admin email history; keep long addresses and message URLs contained. */
.email-history-table { min-width: 960px; }
.email-history-table td { overflow-wrap: anywhere; }
/* Email log columns (Rick, 18 Sep 2026): the two pill columns take only what
   their pill needs, the date, names and address never break mid-word (the
   .table-scroll wrapper scrolls sideways instead when a screen is narrow),
   and the address and subject get the room. First name fits ten characters. */
.email-history-table .email-date { white-space: nowrap; }
.email-history-table td:nth-child(2), .email-history-table td:nth-child(3) { white-space: nowrap; min-width: 6rem; }
.email-history-table td:nth-child(4) { min-width: 16rem; white-space: nowrap; }
.email-history-table td:nth-child(5) { min-width: 12rem; }
.email-history-table th:nth-child(6), .email-history-table td:nth-child(6) { width: 1%; white-space: nowrap; }
/* Delivery sizes itself: pill-wide until a bounce reason appears, then up to 18rem for the reason to read on two or three lines. */
.email-history-table td:nth-child(7) .admin-error-text { display: inline-block; max-width: 18rem; }
.email-view-link { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border: 1px solid var(--border, #ddd9cf); border-radius: 8px; }
.email-view-link:hover, .email-view-link:focus-visible { background: var(--paper, #f5f4f0); outline: 2px solid currentColor; outline-offset: 2px; }
.email-message-meta { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px 24px; }
.email-message-meta dt { font-weight: 600; }
.email-message-meta dd { margin: 0; overflow-wrap: anywhere; }
.email-message-body { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.75; border-top: 1px solid var(--border, #ddd9cf); margin-top: 24px; padding-top: 24px; }
.email-history-table .email-date { white-space: nowrap; }
.email-webhook-url code { display: inline-block; padding: 0.5rem 0.75rem; overflow-wrap: anywhere; background: var(--paper-2); border-radius: var(--radius-sm, 6px); }
/* The Therapists / Admin / All switch on the email log (18 Sep 2026). */
.email-audience { display: inline-flex; border: 1px solid var(--line); border-radius: var(--radius-sm, 6px); overflow: hidden; }
.email-audience a { display: inline-block; padding: 0.55rem 0.95rem; min-height: 44px; text-decoration: none; color: var(--ink); background: var(--paper); font-weight: 600; line-height: 1.6; }
.email-audience a + a { border-left: 1px solid var(--line); }
.email-audience a:hover, .email-audience a:focus-visible { background: var(--paper-2); }
.email-audience a[aria-current="true"] { background: var(--ink); color: var(--paper); }
.filter-bar .field .label { display: block; margin-bottom: 0.35rem; font-weight: 600; }
/* The message dialog: the email as sent, inside a sandboxed frame sized to its content. */
.email-dialog { position: relative; width: min(56rem, calc(100vw - 2 * var(--space-5))); max-height: calc(100vh - 2 * var(--space-5)); border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow-2, var(--shadow-1)); padding: 0; overflow: auto; }
.email-dialog::backdrop { background: rgba(26, 34, 30, 0.55); }
.email-dialog-head { padding: var(--space-4) var(--space-5); padding-right: 4rem; border-bottom: 1px solid var(--line); }
.email-dialog-head h2 { margin: 0; font-size: 1.1rem; overflow-wrap: anywhere; }
.email-dialog-meta { margin: var(--space-2) 0 0; font-size: 0.9rem; color: var(--ink-soft); overflow-wrap: anywhere; }
.email-dialog-body { padding: var(--space-4) var(--space-5); }
.email-dialog-body .notice { margin-bottom: var(--space-4); }
.email-dialog-body .email-message-body { border-top: 0; margin-top: 0; padding-top: 0; }
.email-frame { display: block; width: 100%; border: 0; min-height: 10rem; background: #fff; }

/* PHONE: back in the flow of the copy, between the subhead and the search
   card, and bled to both screen edges with the margin trick: the copy box
   is the viewport minus the container's two gutters, so half its width
   minus half the viewport is exactly one gutter, negative. */
@media (max-width: 719px) {
  .hero-bleed:has(.ticker-strip) { padding-bottom: 0; min-height: 0; }
  .hero-bleed:has(.ticker-strip) > .hero-bleed-media { z-index: auto; bottom: auto; }
  .hero-bleed:has(.ticker-strip) .ticker-strip {
    position: relative;
    margin-block: var(--space-6);
    margin-inline: calc(50% - 50vw);
  }
}

/* -------------------------------------------------------------------------
   Profile page tightening (19 Sep 2026): the bio behind "Read more", the
   declared lists in two columns under their own Services heading, the
   practice facts in leader rows beside the map, the rail sticking by its
   bottom edge when it is taller than the window, and on phones the
   Verification card moving below About so a reader meets the person before
   the paperwork. Rick's brief of 19 Sep 2026, after his and ChatGPT's
   reviews of Rachel Fincham's preview.
   ------------------------------------------------------------------------- */

/* "Read more about Claire" sits straight after the last visible paragraph
   (no top margin) and, once open, reads "Show less" above the rest. The
   shared summary style is on .reviews-more > summary further up. */
.bio-more > summary { margin-block: 0; }
.bio-more[open] > summary { margin-bottom: var(--space-3); }
.bio-more:not([open]) .bio-more-close,
.bio-more[open] .bio-more-open { display: none; }

/* The "?" beside each Services sub-heading carries that list's own caveat,
   so the provenance is one sentence under the section heading rather than a
   paragraph over every list. The bubble anchors left, in body type. */
.profile-section h3 .tip { margin-left: var(--space-2); vertical-align: middle; }
.profile-section h3 .tip-bubble {
  left: 0;
  transform: none;
  width: 18rem;
  max-width: 80vw;
}
/* On a phone the "?" sits at the END of the heading, so a bubble growing
   rightwards from it left the page. Anchor it to the icon's right edge and
   let it grow leftwards instead (21 Sep 2026). */
@media (max-width: 720px) {
  .profile-section h3 .tip-bubble { left: auto; right: 0; }
}
.profile-section h3 .tip-bubble {
  white-space: normal;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
}

/* Location & fees: facts in leader rows across the whole column. The map
   that used to sit beside them went on 21 Sep 2026 (Rick): Get directions
   does its job, and the address now has room to sit on one line. */
.profile-practice { margin-bottom: var(--space-5); }
.profile-practice .leader-row { font-size: 1rem; }
.profile-practice .leader-label { flex: 0 1 auto; min-width: 0; }
.profile-practice .leader-value-sub a { font-weight: 600; }

/* A rail taller than the window cannot be shown whole, and pinned by its top
   its bottom is unreachable. That is the normal case on a small or zoomed-in
   screen (Rick, 19 Sep 2026: older patients zoom in) and for a member with
   several checked trainings. main.js measures and marks it is-tall: the rail
   then flows with the page and ONLY the action card stays pinned, so Book
   online and Enquire are always on screen while the Verification card reads
   at the top and scrolls away like ordinary content, sliding under the card.
   The rail stretches to the column's height so the card can travel the whole
   way down to the enquiry form. Without the script nothing changes. */
@media (min-width: 960px) {
  .profile-layout .profile-aside.is-tall { position: static; align-self: stretch; }
  .profile-layout .profile-aside.is-tall .profile-quick { position: sticky; top: calc(var(--header-offset) + var(--space-4)); z-index: 2; }
}

/* A location as a small stack, not a leader row (Rick, 19 Sep 2026: the name
   wrapping against a wrapping address read as a mess): the name on its own
   line, the address beneath with Get directions at the right, dropping under
   the address where the line is short. */
.profile-place { margin-bottom: var(--space-4); }
.profile-place-name { margin: 0 0 0.15rem; max-width: none; font-weight: 600; }
.profile-place-address { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.25rem var(--space-4); margin: 0; max-width: none; }
.profile-place-address > span { min-width: 0; }
.profile-place-directions { white-space: nowrap; font-weight: 600; }
.profile-place-note { margin: 0.25rem 0 0; max-width: none; font-size: 0.92rem; color: var(--ink-mute); }

/* Phones and tablets: the aside and the main column dissolve into the one
   grid (display: contents) so their children can be ordered as a reader
   wants them - the action card, then About, then the Verification card,
   then everything else. Before this the whole rail came first and a reader
   met the paperwork before a word about the person. Nothing folds: the card
   is the proof and is still complete, only lower. */
@media (max-width: 959.98px) {
  .profile-layout { gap: var(--space-7); }
  .profile-layout .profile-aside,
  .profile-layout .profile-main { display: contents; }
  .profile-layout .profile-quick { order: 1; margin-bottom: 0; }
  .profile-layout .profile-main > .profile-section { order: 4; margin-bottom: 0; }
  .profile-layout #about { order: 2; }
  .profile-layout .verification-box { order: 3; }
}
