/* ==========================================================================
   Compliance 360 — food safety and regulatory compliance consulting.

   Hand-written static site, no build step: this is the only stylesheet.

   Direction: "editorial authority". Source Serif 4 for display, Inter for
   everything else, on warm paper-toned surfaces rather than pure white. The
   serif echoes the wordmark in the logo; a compliance consultancy should read
   as established and precise rather than as a SaaS landing page.

   Theme: light by default, dark via prefers-color-scheme. Deliberately NOT
   :root[data-theme] driven by an inline <head> script — that is what forces
   dtesar.com's CSP to carry a SHA-256. Driving the swap off the media query
   keeps this site on script-src 'self' with no hash to maintain.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Fonts — self-hosted latin subset, variable weight axis.
   Vendored from Fontsource (SIL OFL 1.1 — see /fonts/OFL.txt and README.txt).
   Keeps the CSP first-party: style-src 'self'; font-src 'self'.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('/fonts/inter-latin-var.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-display: swap;
  font-weight: 200 900;
  src: url('/fonts/source-serif-latin-var.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Source Serif 4';
  font-style: italic;
  font-display: swap;
  font-weight: 200 900;
  src: url('/fonts/source-serif-latin-var-italic.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------------------
   Tokens — light (default)

   Colours are derived from the existing brand marks:
     navy     #1F3A5F  ->  oklch(34.7% 0.073 257)
     mid-blue #4A7EBB  ->  oklch(58.4% 0.110 253)
     green    #7AC143  ->  oklch(73.8% 0.174 134)

   The green is darkened on light surfaces so it clears contrast as text; it is
   reserved as a SEMANTIC "verified / compliant" accent (checkmarks,
   certification badges) and is never used decoratively.
   -------------------------------------------------------------------------- */

:root {
  color-scheme: light dark;

  /* Warm paper rather than pure white — the whole point of the direction. */
  --bg: oklch(98.6% 0.005 85);
  --bg-tint: oklch(96.6% 0.009 85);
  --surface: oklch(99.4% 0.003 85);
  --surface-hover: oklch(97.2% 0.008 85);
  --border: oklch(89% 0.011 85);
  --border-strong: oklch(78% 0.015 85);

  --text: oklch(23% 0.021 257);
  --text-muted: oklch(45% 0.018 257);
  --text-faint: oklch(56% 0.014 257);
  --on-accent: oklch(99% 0.004 85);

  --accent: oklch(34.7% 0.073 257);
  --accent-hover: oklch(28% 0.078 257);
  --accent-bright: oklch(52% 0.115 253);
  --accent-soft: oklch(58.4% 0.11 253 / 0.1);

  /* Hue 136 is the logo's green (#7AC143 -> oklch(73.8% 0.174 134)) carried
     into the token system rather than left as a stray hex in the SVG. The mark
     itself now paints from --verified and --accent-bright, so the brand's two
     colours and the site's two accents are the same two hues everywhere. */
  --verified: oklch(52% 0.14 136);
  --verified-soft: oklch(52% 0.14 136 / 0.13);

  /* Required-field marks and error states. Deliberately NOT --accent-bright:
     an asterisk in link blue reads as decoration, not as an obligation. */
  --alert: oklch(52% 0.18 27);
  --alert-soft: oklch(52% 0.18 27 / 0.1);

  /* Rating stars. The empty state is a warm ghost gold, not grey — flat grey
     stars read as "disabled", which is the opposite of "please rate us". */
  --star: oklch(70% 0.155 74);
  --star-empty: oklch(58% 0.03 76);

  --font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-display: 'Source Serif 4', Georgia, 'Times New Roman', serif;

  /* Type scale — a modular progression with a FLOOR under every heading level.

     The previous scale let the headings shrink 13-36% between 1440 and 390
     while body text held at 17px, so h3 landed at 18.1px on a phone: 1.1px
     above body, which is not a heading. Each level now clamps to a minimum
     that keeps it a clear step above the one below it at EVERY width.

                    390px      1440px
       body         17.0       17.0
       --text-lg    18.0       20.0    h4, hero lede
       --text-xl    21.0       24.0    h3
       --text-2xl   26.5       32.0    h2
       --text-3xl   32.0       42.0    h1 (interior pages)
       --text-hero  34.0       53.0    h1 (home hero)

     Each clamp is written as min / (intercept + slope) / max where the middle
     term is solved to hit exactly the min at 390px and the max at 1440px, so
     the endpoints are the design and the middle interpolates linearly. */
  --text-xs: 0.78rem;
  --text-sm: 0.875rem;
  --text-base: 1.0625rem;
  --text-lg: clamp(1.125rem, 1.079rem + 0.19vw, 1.25rem);
  --text-xl: clamp(1.3125rem, 1.243rem + 0.286vw, 1.5rem);
  --text-2xl: clamp(1.656rem, 1.529rem + 0.524vw, 2rem);
  --text-3xl: clamp(2rem, 1.768rem + 0.952vw, 2.625rem);
  --text-hero: clamp(2.125rem, 1.684rem + 1.81vw, 3.3125rem);

  --leading-tight: 1.18;
  --leading-snug: 1.35;
  --leading-normal: 1.68;

  --space-1: 0.5rem;
  --space-2: 0.875rem;
  --space-3: 1.25rem;
  --space-4: 2rem;
  --space-5: 3rem;
  --space-6: 4.5rem;
  --space-section: clamp(3.25rem, 2.2rem + 3.6vw, 5.5rem);

  --radius: 6px;
  --radius-lg: 12px;
  --content-max: 1120px;
  --prose-max: 66ch;

  --shadow: 0 1px 2px oklch(30% 0.03 257 / 0.05), 0 6px 22px oklch(30% 0.03 257 / 0.06);

  --duration: 170ms;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
}

/* --------------------------------------------------------------------------
   Tokens — dark. Colour roles only; type, space and motion never change.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Theme resolution — three states.

     no data-theme attribute  -> follow the operating system (the default)
     data-theme="light"       -> explicit light, even if the OS is dark
     data-theme="dark"        -> explicit dark, even if the OS is light

   The attribute is set by /assets/theme.js, which is a RENDER-BLOCKING
   external script in <head>. External, not inline, so script-src 'self' still
   needs no hash; blocking, so the attribute lands before first paint and an
   explicit choice never flashes the wrong theme.

   color-scheme is set per state as well, so native widgets (scrollbars, select
   dropdowns, form controls) follow the chosen theme rather than the OS one.

   !! The dark declarations appear TWICE below and must stay identical. They
      cannot be merged: one is conditional on a media query and the other is
      not, and CSS has no way to share a body across that boundary. Edit both.
   -------------------------------------------------------------------------- */

/* System dark — but an explicit light choice outranks it. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: oklch(17.5% 0.021 257);
    --bg-tint: oklch(21% 0.024 257);
    --surface: oklch(22.5% 0.026 257);
    --surface-hover: oklch(26% 0.028 257);
    --border: oklch(32% 0.026 257);
    --border-strong: oklch(44% 0.03 257);

    --text: oklch(94% 0.006 85);
    --text-muted: oklch(76% 0.012 257);
    --text-faint: oklch(64% 0.014 257);
    --on-accent: oklch(16% 0.02 257);

    /* Chroma 0.145, not 0.098. The pale low-chroma blue read as a disabled
       control: the primary button was hard to tell from the secondary one, and
       the masthead CTA sank into the bar. This is the same hue at full brand
       saturation, still light enough to carry near-black label text. */
    --accent: oklch(70% 0.145 253);
    --accent-hover: oklch(78% 0.13 253);
    --accent-bright: oklch(74% 0.125 253);
    --accent-soft: oklch(74% 0.125 253 / 0.18);

    --verified: oklch(78% 0.165 136);
    --verified-soft: oklch(78% 0.165 136 / 0.16);

    --alert: oklch(72% 0.16 27);
    --alert-soft: oklch(72% 0.16 27 / 0.14);

    --star: oklch(82% 0.16 78);
    --star-empty: oklch(66% 0.028 78);

    --shadow: 0 1px 2px oklch(0% 0 0 / 0.3), 0 8px 26px oklch(0% 0 0 / 0.32);
  }
}

/* Explicit dark — outranks a light operating system. */
:root[data-theme="dark"] {
    --bg: oklch(17.5% 0.021 257);
    --bg-tint: oklch(21% 0.024 257);
    --surface: oklch(22.5% 0.026 257);
    --surface-hover: oklch(26% 0.028 257);
    --border: oklch(32% 0.026 257);
    --border-strong: oklch(44% 0.03 257);

    --text: oklch(94% 0.006 85);
    --text-muted: oklch(76% 0.012 257);
    --text-faint: oklch(64% 0.014 257);
    --on-accent: oklch(16% 0.02 257);

    /* Chroma 0.145, not 0.098. The pale low-chroma blue read as a disabled
       control: the primary button was hard to tell from the secondary one, and
       the masthead CTA sank into the bar. This is the same hue at full brand
       saturation, still light enough to carry near-black label text. */
    --accent: oklch(70% 0.145 253);
    --accent-hover: oklch(78% 0.13 253);
    --accent-bright: oklch(74% 0.125 253);
    --accent-soft: oklch(74% 0.125 253 / 0.18);

    --verified: oklch(78% 0.165 136);
    --verified-soft: oklch(78% 0.165 136 / 0.16);

    --alert: oklch(72% 0.16 27);
    --alert-soft: oklch(72% 0.16 27 / 0.14);

    --star: oklch(82% 0.16 78);
    --star-empty: oklch(66% 0.028 78);

    --shadow: 0 1px 2px oklch(0% 0 0 / 0.3), 0 8px 26px oklch(0% 0 0 / 0.32);
}

:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

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

html {
  scrollbar-gutter: stable;
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  background-color: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  /* 450, not 600. Source Serif's weight axis runs 200-900; a lighter cut is
     what makes the editorial direction read as considered rather than loud. */
  font-weight: 450;
  line-height: var(--leading-tight);
  letter-spacing: -0.006em;
  text-wrap: balance;
}

h3, h4 { font-weight: 500; }

h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }

p { text-wrap: pretty; }

a {
  color: var(--accent-bright);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

a:hover { color: var(--accent-hover); }

ul, ol { list-style: none; }

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

:focus-visible {
  outline: 2px solid var(--accent-bright);
  outline-offset: 2px;
  border-radius: 2px;
}

::selection {
  background: var(--accent-soft);
}

.skip {
  position: absolute;
  left: -9999px;
  top: var(--space-1);
  padding: var(--space-1) var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  z-index: 20;
}

.skip:focus {
  left: var(--space-1);
}

.wrap {
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: clamp(1.15rem, 0.6rem + 2.2vw, 2.25rem);
}

.prose { max-width: var(--prose-max); }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Masthead
   -------------------------------------------------------------------------- */

/* position: relative is load-bearing — the mobile disclosure panel is
   absolutely positioned against the header, so it drops flush under it and
   spans the full viewport width rather than the width of its flex cell. */
.site-header {
  position: relative;
  border-bottom: 1px solid var(--border);
}

.masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1);
  padding-block: var(--space-2);
}

@media (min-width: 62rem) {
  .masthead {
    gap: var(--space-2) var(--space-4);
    padding-block: var(--space-3);
  }
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--text);
  text-decoration: none;
  font-family: var(--font-display);
  font-size: 1.18rem;
  font-weight: 500;
  letter-spacing: -0.012em;
}

.brand svg {
  width: 1.9rem;
  height: 1.9rem;
  flex: none;
}

@media (min-width: 62rem) {
  .brand svg {
    width: 2.1rem;
    height: 2.1rem;
  }
}

.brand-360 { color: var(--accent-bright); }

/* The mark's two arrows used to be hardcoded #7AC143 / #4A7EBB — the only two
   colours on the site that came from nowhere in the palette and did not adapt
   to the theme. They now paint from the same tokens as the tick bullets and the
   links, so the logo is part of the colour system rather than an exception to
   it. Attributes on the SVG, not a style attribute: style-src carries no hash. */
.mk-green { stroke: var(--verified); }
.mk-green-f { fill: var(--verified); }
.mk-blue { stroke: var(--accent-bright); }
.mk-blue-f { fill: var(--accent-bright); }

/* --------------------------------------------------------------------------
   Navigation — one breakpoint, two layouts.

   Below 62rem the row is brand + call + theme + a <details> disclosure holding
   the links. Above it the links sit inline and the disclosure disappears.

   62rem, not 48rem: that is the width at which the brand, five links, the
   consultation button and the toggle stop colliding and the row stops wrapping.

   The two navs are separate elements rather than one element re-flowed, because
   a closed <details> hides its own content in a way author CSS cannot reliably
   override across browsers. Only ever one of them is rendered — the other is
   display:none, which also removes it from the accessibility tree, so there is
   no duplicate landmark.
   -------------------------------------------------------------------------- */

.nav-desktop { display: none; }

@media (min-width: 62rem) {
  .nav-desktop {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-3);
    font-size: var(--text-sm);
  }
}

.nav-desktop a {
  color: var(--text-muted);
  text-decoration: none;
  padding-block: 0.35rem;
  border-bottom: 2px solid transparent;
  transition: color var(--duration) var(--ease), border-color var(--duration) var(--ease);
}

.nav-desktop a:hover { color: var(--text); }

.nav-desktop a[aria-current='page'] {
  color: var(--text);
  border-bottom-color: var(--accent-bright);
}

/* --------------------------------------------------------------------------
   Contact topbar

   A slim band above the masthead carrying phone, email and hours. Standard for
   professional services and it earns its place: buyers of compliance work
   frequently phone rather than fill in a form, and this keeps the number on
   every page without crowding the navigation.
   -------------------------------------------------------------------------- */

/* Desktop only. On a phone this band cost ~56px of an 844px screen to render
   the phone number at 12px in --text-faint, where it read as decoration; the
   masthead Call button carries that job below 62rem instead. */
.topbar { display: none; }

@media (min-width: 62rem) {
  .topbar { display: block; }
}

.topbar {
  background: var(--bg-tint);
  border-bottom: 1px solid var(--border);
  font-size: var(--text-xs);
  color: var(--text-faint);
}

.topbar-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.3rem var(--space-3);
  padding-block: 0.5rem;
}

.topbar a {
  color: var(--text-muted);
  text-decoration: none;
  font-weight: 500;
}

.topbar a:hover {
  color: var(--accent-bright);
  text-decoration: underline;
}

.topbar p + p {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem var(--space-3);
}

/* --------------------------------------------------------------------------
   Masthead actions — persistent CTA + theme toggle
   -------------------------------------------------------------------------- */

.masthead-actions {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

@media (min-width: 62rem) {
  .masthead-actions { gap: var(--space-2); }
}

.btn.btn-sm {
  padding: 0.5rem 0.95rem;
  font-size: var(--text-xs);
}

/* The consultation button is the desktop CTA; on mobile the same action lives
   inside the disclosure panel, where it has room for the full label. */
.btn.nav-cta { display: none; }

@media (min-width: 62rem) {
  .btn.nav-cta {
    display: inline-flex;
    white-space: nowrap;
  }
}

/* Call button — mobile only, because the topbar carries the number on desktop.
   Someone with a health inspector in the lobby wants one tap to a person, not
   a form; this is that tap. Accent-outlined so it reads as an action rather
   than as another piece of chrome. */
.btn.btn-call {
  padding: 0.5rem 0.6rem;
  gap: 0.35rem;
  white-space: nowrap;
  color: var(--accent-bright);
  border-color: var(--accent-bright);
  background: var(--accent-soft);
}

.btn.btn-call:hover {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
}

@media (min-width: 62rem) {
  .btn.btn-call { display: none; }
}

/* Below ~384px the label and the brand start fighting for the same row. */
@media (max-width: 24rem) {
  .btn-call-text { display: none; }
}

.btn .icon {
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
}

/* --------------------------------------------------------------------------
   Mobile navigation disclosure

   <details>/<summary>, not a scripted menu. It opens, closes, is reachable by
   keyboard and reports its expanded state with JavaScript disabled or blocked
   — which is the right default under a CSP that carries no script hashes.
   /assets/nav-menu.js adds only Escape-to-close and outside-click-to-close.
   -------------------------------------------------------------------------- */

@media (min-width: 62rem) {
  .navdis { display: none; }
}

.navdis-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.4rem;
  height: 2.4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--duration) var(--ease), border-color var(--duration) var(--ease);
}

/* Suppress the UA disclosure triangle. Both are needed: ::marker covers
   current browsers, list-style covers the older WebKit implementation. */
.navdis-btn { list-style: none; }
.navdis-btn::-webkit-details-marker { display: none; }
.navdis-btn::marker { content: ''; }

.navdis-btn:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

.navdis-btn .icon {
  width: 1.3rem;
  height: 1.3rem;
}

.navdis .icon-close { display: none; }
.navdis[open] .icon-burger { display: none; }
.navdis[open] .icon-close { display: block; }

.navdis[open] .navdis-btn {
  color: var(--text);
  border-color: var(--border-strong);
}

.navdis-panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 30;
  display: grid;
  gap: var(--space-3);
  padding: var(--space-2) clamp(1.15rem, 0.6rem + 2.2vw, 2.25rem) var(--space-4);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow);
}

.navdis-nav { display: grid; }

.navdis-nav a {
  padding-block: 0.75rem;
  color: var(--text);
  font-size: var(--text-lg);
  font-weight: 550;
  text-decoration: none;
  border-bottom: 1px solid var(--border);
}

.navdis-nav a:hover { color: var(--accent-bright); }

.navdis-nav a[aria-current='page'] { color: var(--accent-bright); }

.navdis-cta {
  display: grid;
  gap: var(--space-1);
}

.navdis-cta .btn { justify-content: center; }

.navdis-meta {
  font-size: var(--text-xs);
  color: var(--text-faint);
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.2rem;
  height: 2.2rem;
  padding: 0;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--duration) var(--ease), border-color var(--duration) var(--ease);
}

.theme-toggle:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

.ticon {
  width: 1.05rem;
  height: 1.05rem;
}

/* Which glyph shows is driven entirely by the root attribute, so the script
   only ever sets data-theme and the label — it never swaps icons itself. */
.ticon-light,
.ticon-dark { display: none; }

:root[data-theme="light"] .ticon-system,
:root[data-theme="dark"] .ticon-system { display: none; }

:root[data-theme="light"] .ticon-light { display: block; }
:root[data-theme="dark"] .ticon-dark { display: block; }

/* --------------------------------------------------------------------------
   Hero — asymmetric editorial. Type leads; there is no stock photograph.
   -------------------------------------------------------------------------- */

.hero {
  padding-block: clamp(3rem, 2rem + 5vw, 6.5rem) var(--space-section);
  border-bottom: 1px solid var(--border);
}

/* Full-bleed tint band. Sections sit outside .wrap so the background spans the
   viewport; the .wrap inside each one holds the measure. */
.section-tint {
  background: var(--bg-tint);
}

.hero h1 {
  font-size: var(--text-hero);
  max-width: 19ch;
  margin-block: var(--space-3) var(--space-4);
}

.hero .lead {
  font-size: var(--text-lg);
  font-weight: 350;
  color: var(--text-muted);
  max-width: 56ch;
}

.eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.55rem;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.eyebrow li::after {
  content: '·';
  margin-left: 0.55rem;
  color: var(--border-strong);
}

.eyebrow li:last-child::after { content: none; }

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

/* --------------------------------------------------------------------------
   Proof strip — the four figures directly under the home page hero.

   Every competing consultancy leads with a number and this site led with none,
   so a first-time visitor had nothing to size the practice against above the
   fold. Every value here is checkable against site.config.json: the five
   standards, the seven written recommendations, the one-business-day reply
   commitment and the location. Nothing is estimated, and nothing goes in here
   that cannot be pointed at somewhere else on the site.
   -------------------------------------------------------------------------- */

.proofbar {
  background: var(--bg-tint);
  border-bottom: 1px solid var(--border);
}

/* Explicit column counts, not auto-fit. The dividing hairline below has to
   know which cells begin a row, and auto-fit does not tell you: at 768px it
   settled on three columns and put a leading rule on the orphaned fourth cell.
   1 / 2 / 4 is also the right rhythm — four facts never want to be three. */
.proofbar-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-3) var(--space-4);
  padding-block: var(--space-4);
}

@media (min-width: 34rem) {
  .proofbar-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 62rem) {
  .proofbar-list { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* A hairline between cells rather than a box around each: this is one strip of
   facts, not four cards. Drawn only on cells that do not start a row. */
@media (min-width: 34rem) {
  .proofbar-list li:nth-child(even) {
    padding-left: var(--space-4);
    border-left: 1px solid var(--border);
  }
}

@media (min-width: 62rem) {
  .proofbar-list li:nth-child(n + 2) {
    padding-left: var(--space-4);
    border-left: 1px solid var(--border);
  }
}

.proofbar .figure {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 450;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--accent-bright);
  font-variant-numeric: tabular-nums;
}

.proofbar .label {
  display: block;
  margin-top: 0.35rem;
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text);
}

.proofbar .sub {
  display: block;
  margin-top: 0.2rem;
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   Interior-page hero.

   Every page used to open with the same block — kicker, serif h1, grey lede —
   pinned to the left 45% with the right 55% empty, and by the third page a
   reviewer had stopped reading headings at all. Interior pages now set the lede
   BESIDE the headline rather than under it: it fills the measure, and it gives
   each page a different silhouette from the home page's portrait split.
   -------------------------------------------------------------------------- */

.hero-lede {
  display: grid;
  gap: var(--space-3) var(--space-5);
  align-items: end;
}

@media (min-width: 56rem) {
  .hero-lede {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  }
}

.hero-lede .lead {
  max-width: 46ch;
  margin-bottom: 0.2em;
}

/* A short factual strip under the hero. Different facts per page, so the pages
   stop being interchangeable at a glance — and it is real information, not
   decoration. */
.hero-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem var(--space-4);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.hero-facts strong {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Hero split: copy on the left, founder portrait on the right. The minmax
   floor is wide enough that the portrait drops below the copy rather than
   squeezing, so no breakpoint media query is needed. */
.hero-split {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  align-items: center;
}

.hero-portrait {
  margin: 0;
  /* Anchored to the right edge of the measure, so it lines up with the nav
     above it and the image band below rather than floating mid-column. */
  justify-self: end;
}

/* Once the grid has collapsed to one column there is no right edge to hug,
   so centre it instead. This is the only breakpoint query in the file — the
   rest of the layout is fluid — and it exists because justify-self cannot be
   made conditional on the auto-fit column count. */
@media (max-width: 48rem) {
  .hero-portrait {
    justify-self: center;
  }
}

/* 19rem, not 15.5rem, and a head-and-shoulders source rather than a full-body
   one. At 248px wide from a full-body frame the founder's face was ~44px on a
   phone — the site's single trust asset, rendered too small to read as a face.
   The file is now cropped to 4:5 at the shoulders, so at 304px the face lands
   near 90px without the image floating in dead space on a wide screen. */
.hero-portrait img {
  width: 100%;
  max-width: 19rem;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
}

.hero-portrait figcaption {
  margin-top: var(--space-2);
  max-width: 19rem;
  font-size: var(--text-sm);
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.35rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 550;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--duration) var(--ease),
    border-color var(--duration) var(--ease), transform var(--duration) var(--ease);
}

.btn:hover {
  background: var(--surface-hover);
  border-color: var(--text-faint);
  color: var(--text);
  transform: translateY(-1px);
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.btn-primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--on-accent);
}

.btn[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

/* --------------------------------------------------------------------------
   Sections
   -------------------------------------------------------------------------- */

.section {
  padding-block: var(--space-section);
  border-bottom: 1px solid var(--border);
}

.section:last-of-type { border-bottom: none; }

.section-head {
  max-width: var(--prose-max);
  margin-bottom: var(--space-5);
}

.section-head p {
  color: var(--text-muted);
  margin-top: var(--space-2);
}

.kicker {
  display: block;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-bright);
  margin-bottom: var(--space-1);
}

/* --------------------------------------------------------------------------
   Service tracks + generic card grid
   -------------------------------------------------------------------------- */

.grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.card h3 { letter-spacing: -0.01em; }

.card p { color: var(--text-muted); }

.card-link {
  margin-top: auto;
  padding-top: var(--space-1);
  font-size: var(--text-sm);
  font-weight: 550;
}

/* Checklists — the green is semantic here: each line is a thing that gets done. */
.checklist {
  display: grid;
  gap: 0.6rem;
}

/* Nine service lines stacked one per row made the manufacturing block roughly
   three times the height of the restaurant block under an identically weighted
   heading, and a café owner reads block height as importance. Two columns
   brings the two tracks back to comparable presence without cutting anything. */
.checklist-cols {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  column-gap: var(--space-4);
}

/* The restaurant track's three lines set as a monthly cycle rather than a short
   list — full width, same facts, no longer an afterthought beside a nine-item
   checklist. */
.cycle {
  counter-reset: cycle;
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}

.cycle li {
  counter-increment: cycle;
  padding: var(--space-3) var(--space-4) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--verified);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  line-height: var(--leading-snug);
}

.cycle li::before {
  content: counter(cycle, decimal-leading-zero);
  display: block;
  margin-bottom: var(--space-1);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  color: var(--verified);
  font-variant-numeric: tabular-nums;
}

.cycle-note {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-faint);
}

.checklist li {
  position: relative;
  padding-left: 1.75rem;
  color: var(--text-muted);
  line-height: var(--leading-snug);
}

.checklist li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.42em;
  width: 1.05rem;
  height: 1.05rem;
  border-radius: 50%;
  background: var(--verified-soft);
}

/* Optically centred in the disc, which the previous numbers were not.

   Rotating a w x h L-shape by -45deg puts its ink centre 0.3536*h BELOW the
   element's own centre — the corner of the tick swings down while both arm
   ends swing up. So the element is offset UP by that amount rather than being
   centred on the disc, and the horizontal offset is a plain (disc - w) / 2.
     disc centre  = 0.525rem, 0.42em + 0.525rem
     ink offset   = 0.3536 * 0.26rem = 0.092rem
     top          = 0.42em + 0.525 - 0.092 - 0.13 = 0.42em + 0.303rem */
.checklist li::after {
  content: '';
  position: absolute;
  left: 0.295rem;
  top: calc(0.42em + 0.303rem);
  width: 0.46rem;
  height: 0.26rem;
  border-left: 1.8px solid var(--verified);
  border-bottom: 1.8px solid var(--verified);
  transform: rotate(-45deg);
}

/* --------------------------------------------------------------------------
   Tag list — inert labels.

   These used to be pills identical to the jump links on /certifications, and
   reviewers clicked them and concluded the site was broken. A pill shape on
   this site now means "this navigates"; anything that does not navigate is set
   as a marked list instead, which no one mistakes for a button.
   -------------------------------------------------------------------------- */

.taglist {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.taglist li {
  position: relative;
  padding-left: 0.95rem;
}

.taglist li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.35rem;
  height: 0.35rem;
  border-radius: 50%;
  background: var(--verified);
}

/* --------------------------------------------------------------------------
   Certifications — anchored explainer sections
   -------------------------------------------------------------------------- */

.cert {
  padding-block: var(--space-5) var(--space-4);
  border-top: 1px solid var(--border);
  scroll-margin-top: var(--space-3);
}

.cert-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  margin-bottom: var(--space-3);
}

/* The scheme name is the page's scannable anchor and the reason anyone is on
   it, so it is set a full level above a generic h3 — h2 size, at the sans
   weight that makes an acronym read as a name rather than as running text.
   It was previously quieter than the letterspaced column labels below it,
   which inverted the hierarchy on the site's main decision page. */
.cert-head h2 {
  font-family: var(--font-sans);
  font-size: var(--text-2xl);
  font-weight: 650;
  letter-spacing: -0.015em;
}

.cert-head .name {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.badge {
  padding: 0.2rem 0.6rem;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--verified);
  background: var(--verified-soft);
  border-radius: 999px;
}

.cert-body {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}

/* Demoted to match: these are column labels, not headings. Tighter tracking
   and a lighter weight so they stop out-shouting the scheme name above them. */
.cert-body h3 {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.055em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: var(--space-1);
}

.cert-body p { color: var(--text-muted); }

.cert-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: var(--space-4);
}

/* Pill shape + link colour + a tinted ground: the site's one visual signal
   that a chip navigates. See .taglist for the inert counterpart. */
.cert-nav a {
  padding: 0.35rem 0.85rem;
  font-size: var(--text-sm);
  font-weight: 550;
  text-decoration: none;
  color: var(--accent-bright);
  background: var(--accent-soft);
  border: 1px solid transparent;
  border-radius: 999px;
  transition: background-color var(--duration) var(--ease),
    border-color var(--duration) var(--ease), color var(--duration) var(--ease);
}

.cert-nav a:hover {
  background: var(--surface);
  border-color: var(--accent-bright);
  text-decoration: underline;
}

/* --------------------------------------------------------------------------
   Testimonials
   -------------------------------------------------------------------------- */

/* align-items: start, not the grid default of stretch. Stretching made every
   card in a row as tall as the longest quote in it, which left up to ~180px of
   empty card between a short quote and its own attribution and visually
   severed the two. Ragged bottoms are the lesser problem. */
.quotes {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  align-items: start;
}

/* Five cards in a three-column grid also leaves a hole in the last row. Column
   flow fills bottom-up instead, so there is no empty cell and each card is
   exactly as tall as its own contents. */
.quotes-cols {
  columns: 3 20rem;
  column-gap: var(--space-3);
}

.quotes-cols .quote {
  display: block;
  break-inside: avoid;
  margin-bottom: var(--space-3);
}

.quotes-cols .quote figcaption { margin-top: var(--space-3); }

.quote {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-bright);
  border-radius: var(--radius);
}

/* A one-line outcome above each quote, drawn strictly from that person's own
   words. A recommendation is read as a wall of praise; the outcome line is what
   makes it scannable, and it is the single most persuasive device the competing
   consultancies use. It never claims more than the quote beneath it does. */
.quote .outcome {
  font-size: var(--text-sm);
  font-weight: 650;
  line-height: var(--leading-snug);
  letter-spacing: -0.005em;
  color: var(--text);
}

.quote .outcome::before {
  content: '';
  display: block;
  width: 1.6rem;
  margin-bottom: 0.6rem;
  border-top: 2px solid var(--verified);
}

.quote blockquote {
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 380;
  line-height: 1.6;
  color: var(--text);
}

.quote figcaption {
  margin-top: auto;
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}

.quote .who {
  font-weight: 600;
  color: var(--text);
}

.quote .role,
.quote .rel {
  display: block;
  color: var(--text-faint);
}

.quote .rel {
  margin-top: 0.35rem;
  font-size: var(--text-xs);
  font-style: italic;
}

/* References are colleagues and managers, not clients. They used to use the
   identical card, so the two groups were distinguishable only by their heading
   — set as marginalia instead: no surface, no shadow, a quiet rule in place of
   the accent edge. Declared after .quote so these overrides actually land. */
.quotes-refs {
  display: grid;
  gap: var(--space-4) var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  align-items: start;
}

.quote.quote-ref {
  display: block;
  padding: 0 0 0 var(--space-3);
  background: none;
  border: 0;
  border-left: 2px solid var(--border-strong);
  border-radius: 0;
  box-shadow: none;
}

.quote.quote-ref blockquote {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 400;
  line-height: var(--leading-normal);
  color: var(--text-muted);
}

.quote.quote-ref figcaption { margin-top: var(--space-2); }

.quote.quote-ref .rel { font-style: normal; }

/* References are marginalia, so their outcome line is too — no accent rule. */
.quote.quote-ref .outcome {
  font-size: var(--text-xs);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.quote.quote-ref .outcome::before { content: none; }

/* Featured pull quote — the one on the homepage hero seam. */
.pullquote {
  max-width: 44ch;
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

.pullquote blockquote {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 380;
  font-size: var(--text-xl);
  line-height: 1.5;
}

.pullquote figcaption {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   Forms
   -------------------------------------------------------------------------- */

.form-layout {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
  align-items: start;
}

/* The form is the point of the page and gets the wide column. An even split
   squeezed the two-up field rows into ~170px columns while the explainer
   beside it took a full half of the page. */
@media (min-width: 56rem) {
  .form-layout-primary {
    grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr);
    gap: var(--space-6);
  }
}

.form-col {
  display: grid;
  gap: var(--space-3);
  align-content: start;
}

/* The form column carried a visually-hidden heading while the column beside it
   showed one, so the page looked like it had a single, mislabelled section. */
.form-col > h2,
.form-aside > h2 {
  font-size: var(--text-xl);
}

.form-col > .form-intro {
  color: var(--text-muted);
  margin-top: calc(var(--space-3) * -1 + 0.3rem);
}

.form {
  display: grid;
  gap: var(--space-3);
}

.field {
  display: grid;
  gap: 0.4rem;
  /* Without this, a field carrying a hint stretches its rows to match the
     taller sibling in a .field-row, pushing the neighbouring input out of
     alignment. */
  align-content: start;
}

.field label {
  font-size: var(--text-sm);
  font-weight: 550;
}

.field .hint {
  font-size: var(--text-xs);
  color: var(--text-faint);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.62rem 0.8rem;
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  transition: border-color var(--duration) var(--ease);
}

.field textarea {
  min-height: 9rem;
  resize: vertical;
}

.field input:hover,
.field textarea:hover,
.field select:hover {
  border-color: var(--text-faint);
}

.field-row {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  align-items: start;
}

/* An asterisk in link blue reads as decoration. --alert is the site's one
   "you must do something about this" colour, shared with the error status. */
.req {
  color: var(--alert);
  font-weight: 700;
}

/* Honeypot. Hidden from people and from assistive tech, reachable by bots. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.status {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

.status[data-state='ok'] {
  border-color: var(--verified);
  background: var(--verified-soft);
}

.status[data-state='error'] {
  border-color: var(--alert);
  background: var(--alert-soft);
}

.form-alt {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   "What happens next" — sits beside the contact form. Setting expectations
   before someone submits is the cheapest lever on form completion.
   -------------------------------------------------------------------------- */

.steps {
  counter-reset: step;
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.steps li {
  position: relative;
  padding-left: 2.6rem;
  counter-increment: step;
}

.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0.1rem;
  display: grid;
  place-items: center;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-bright);
  font-size: var(--text-xs);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.steps strong {
  display: block;
  font-weight: 600;
}

.steps span {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* The same three steps set across the page rather than down a column, for the
   home page, where they are a section of their own rather than an aside. */
.steps.steps-row {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: var(--space-4);
  margin-top: 0;
}

.steps.steps-row strong {
  margin-bottom: 0.2rem;
  font-size: var(--text-lg);
  font-family: var(--font-display);
  font-weight: 500;
}

/* --------------------------------------------------------------------------
   Credentials panel — /about

   What every competitor states plainly and this site did not: what the person
   is actually qualified to do. Every line is supported either by the service
   list or by a named recommendation; the formal certificates and registration
   numbers are still outstanding from Tatiana and are tracked as
   $credentialsNote in site.config.json rather than being guessed at here.
   -------------------------------------------------------------------------- */

.creds {
  margin-top: var(--space-4);
  padding: var(--space-4);
  background: var(--bg-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.creds h3 {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: var(--space-3);
}

.creds .checklist {
  gap: 0.75rem;
}

.creds .checklist li { color: var(--text); }

/* The founder's role line, set under her name so the portrait caption does not
   have to repeat it. The name previously appeared three times in ~300px. */
/* p.role-line, not .role-line: .section-head p is (0,1,1) and would otherwise
   out-specify a bare class no matter where it is declared. */
p.role-line {
  margin-top: var(--space-1);
  font-size: var(--text-lg);
  font-weight: 400;
  color: var(--text-muted);
}

/* Inline field error, injected by the form scripts next to the offending
   control. Uses --alert (not the link blue) so it cannot be mistaken for a
   link, and carries an icon-free text marker so colour is never the only
   signal. */
.field-error {
  margin-top: 0.1rem;
  font-size: var(--text-xs);
  font-weight: 550;
  line-height: var(--leading-snug);
  color: var(--alert);
}

.field [aria-invalid="true"] {
  border-color: var(--alert);
}

.field [aria-invalid="true"]:focus-visible {
  outline-color: var(--alert);
}

/* --------------------------------------------------------------------------
   Contact details block
   -------------------------------------------------------------------------- */

.steps + .details {
  margin-top: var(--space-5);
}

.details {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--bg-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.details dt {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 0.2rem;
}

.details dd { color: var(--text); }

.details a { text-decoration: none; }
.details a:hover { text-decoration: underline; }

/* --------------------------------------------------------------------------
   Call to action strip
   -------------------------------------------------------------------------- */

.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-4);
  background: var(--bg-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}

.cta h2 { max-width: 20ch; }

.cta p {
  color: var(--text-muted);
  max-width: 44ch;
  margin-top: var(--space-1);
}

/* A closing strip may offer a secondary action, but the primary one always
   comes first and always reads "Book a free consultation". */
.cta-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--border);
}

.footer {
  padding-block: var(--space-5) var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-faint);
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
}

.footer nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
}

.footer nav a,
.footer a {
  color: var(--text-muted);
  text-decoration: none;
}

.footer nav a:hover,
.footer a:hover {
  color: var(--text);
  text-decoration: underline;
}

.footer-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
}

/* --------------------------------------------------------------------------
   Imagery

   Real photographs, self-hosted under /images so img-src 'self' holds. Every
   <img> carries explicit width/height so nothing shifts as they decode, and
   each is served as WebP with a JPEG fallback via <picture>.
   -------------------------------------------------------------------------- */

/* Contained, not full-bleed. Edge-to-edge made a 1280px source upscale to the
   viewport, which blew the faces up and made the band dominate a page where
   everything else sits inside the measure. */
.band {
  padding-block: var(--space-section);
  border-bottom: 1px solid var(--border);
}

/* aspect-ratio + max-height, not a fixed clamp height.

   A clamped height against a fluid width meant the ratio moved with the
   viewport: at 1440 the box was 1048x272, a 3.85:1 letterbox cut out of a
   1.5:1 photograph, so ~60% of the frame was thrown away and what survived was
   a strip of torsos. Pinning the ratio to the source's own 3:2 and capping the
   height instead means the crop is never worse than about 2.5:1 on the widest
   screen, and a phone gets the whole photograph uncropped. */
.band img {
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  max-height: 26rem;
  object-fit: cover;
  object-position: center top;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
}

.band figcaption {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-faint);
}

/* Founder portrait beside prose. Collapses to a single column on narrow
   viewports via the same auto-fit idiom used elsewhere. */
.split {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  align-items: start;
}

/* Two equal columns left the portrait's own column ~180px wider than the
   portrait, so the founder floated in a pocket of empty page while the prose
   and the credentials panel beside it ran long. The image column is now sized
   to the image. */
@media (min-width: 56rem) {
  .split {
    grid-template-columns: 19rem minmax(0, 1.55fr);
  }
}

.portrait {
  margin: 0;
}

.portrait img {
  width: 100%;
  max-width: 19rem;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
}

.portrait figcaption {
  max-width: 19rem;
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   Star rating

   Five radio buttons in REVERSE DOM order, styled as stars. Not a JS widget:
   radios are keyboard-navigable, announced correctly by screen readers, and
   submit fine with JavaScript disabled. Reverse order is what lets the
   `:checked ~ label` sibling selector light up the stars to the left.
   -------------------------------------------------------------------------- */

.rating {
  border: 0;
  gap: var(--space-1);
}

.rating legend {
  font-size: var(--text-sm);
  font-weight: 550;
  margin-bottom: 0.4rem;
}

.stars {
  display: inline-flex;
  flex-direction: row-reverse;
  justify-content: flex-end;
  gap: 0.15rem;
}

.stars input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

/* Fixed width so an outline glyph and a filled glyph from a fallback font can
   never disagree on advance width and shift the row as you hover across it. */
.stars label {
  cursor: pointer;
  width: 1.85rem;
  text-align: center;
  font-size: 1.9rem;
  line-height: 1;
  color: var(--star-empty);
  transition: color var(--duration) var(--ease), transform var(--duration) var(--ease);
}

/* Outline for empty, solid for filled. Colour alone was doing this job and it
   could not: a filled glyph in any grey light enough to read as "not chosen"
   is too pale to clear 3:1 as a control, and any grey dark enough to clear it
   looks chosen. The shape carries the state; the gold confirms it. */
.stars label::before {
  content: '\2606'; /* WHITE STAR — the unrated state */
}

.stars input:checked ~ label::before,
.stars label:hover::before,
.stars label:hover ~ label::before {
  content: '\2605'; /* BLACK STAR */
}

/* The checked star and every star before it (visually to its left). */
/* Gold, not the site's green. Five green stars read as five pass/fail ticks;
   gold is the one place a second accent hue is worth spending. */
.stars input:checked ~ label,
.stars label:hover,
.stars label:hover ~ label {
  color: var(--star);
}

.stars label:hover {
  transform: scale(1.08);
}

/* The radio is visually hidden, so the focus ring has to land on its label. */
.stars input:focus-visible + label {
  outline: 2px solid var(--accent-bright);
  outline-offset: 2px;
  border-radius: 2px;
}

/* --------------------------------------------------------------------------
   Consent checkbox
   -------------------------------------------------------------------------- */

.consent label {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-size: var(--text-sm);
  font-weight: 400;
  line-height: var(--leading-snug);
  color: var(--text-muted);
  cursor: pointer;
}

.consent input {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  margin-top: 0.22rem;
  accent-color: var(--accent-bright);
}

/* A single spacing utility, used for the "read more" line under a card grid.
   Deliberately the only utility class in the file — inline style attributes are
   forbidden by style-src 'self', and one named class beats a second stylesheet. */
.more {
  margin-top: var(--space-4);
}

/* Phone numbers and short labels that must never break mid-token. The contact
   page was wrapping "(551) 334-8589" across two lines mid-number. */
.nowrap { white-space: nowrap; }

/* --------------------------------------------------------------------------
   Touch and viewport hygiene
   -------------------------------------------------------------------------- */

/* Removes the ~300ms double-tap-zoom delay on every control without
   disabling pinch zoom (never set user-scalable=no). */
a,
button,
summary,
label,
input,
select,
textarea {
  touch-action: manipulation;
}

/* Default iOS tap flash is a grey box that fights the palette; replace it with
   the same tint used for text selection. */
a,
button,
summary {
  -webkit-tap-highlight-color: var(--accent-soft);
}

/* Scrolling the open nav panel must not chain to the page behind it. */
.navdis-panel {
  overscroll-behavior: contain;
}

/* Full-bleed bands run edge to edge, so respect notches and home indicators. */
.topbar-inner,
.masthead,
.wrap {
  padding-inline: max(
    clamp(1.15rem, 0.6rem + 2.2vw, 2.25rem),
    env(safe-area-inset-left),
    env(safe-area-inset-right)
  );
}

.site-footer {
  padding-bottom: env(safe-area-inset-bottom);
}

/* --------------------------------------------------------------------------
   Motion — honour the user's preference.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    transform: none !important;
  }
}
