/* ==========================================================================
   Hogan Portfolio — shared foundation
   --------------------------------------------------------------------------
   This file is IDENTICAL in every site folder. Do not edit a site's copy.
   Edit `_shared/css/base.css` and re-run `scripts/sync-shared.ps1`.

   Site identity lives in `css/theme.css`, which overrides the palette and the
   font-role variables declared here. Everything below is structural.

   Token architecture deliberately mirrors the Hogan Roofing design system
   (hoganroofs.com/_ds/...) so the roofing-adjacent sites sit in the same
   visual family and the rest inherit the same scale discipline.
   ========================================================================== */

/* --- Fonts: self-hosted variable woff2, no external CDN ------------------ */

@font-face {
  font-family: 'Archivo';
  src: url('../fonts/archivo-var-latin.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Hanken Grotesk';
  src: url('../fonts/hankengrotesk-var-latin.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Fraunces';
  src: url('../fonts/fraunces-var-latin.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url('../fonts/jetbrainsmono-var-latin.woff2') format('woff2-variations');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

/* --- Foundation tokens --------------------------------------------------- */

:root {
  /* Font roles. A theme reassigns these; it never restates the stacks. */
  --font-display: 'Archivo', 'Hanken Grotesk', system-ui, sans-serif;
  --font-sans: 'Hanken Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-serif: 'Fraunces', Georgia, 'Times New Roman', serif;
  /* The "data voice": numbers, stats, step counters. Prose is never mono.
     Only the roofing-adjacent themes opt in -- an unused @font-face is never
     fetched, so the other seven pay nothing for shipping the file. */
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --font-body: var(--font-sans);

  /* Type scale */
  --text-2xs: 0.6875rem;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-md: 1.125rem;
  --text-lg: 1.375rem;
  --text-xl: 1.75rem;
  --text-2xl: 2.25rem;
  --text-3xl: 3rem;
  --text-4xl: 3.75rem;
  --text-5xl: 4.75rem;

  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;
  --fw-extra: 800;
  --fw-black: 900;

  --leading-none: 1;
  --leading-tight: 1.12;
  --leading-snug: 1.28;
  --leading-normal: 1.5;
  --leading-relaxed: 1.65;

  --tracking-tight: -0.02em;
  --tracking-snug: -0.01em;
  --tracking-normal: 0;
  --tracking-wide: 0.02em;
  --tracking-caps: 0.08em;

  /* Spacing */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-16: 4rem;
  --space-20: 5rem;
  --space-24: 6rem;
  --space-32: 8rem;

  --container: 1180px;
  --container-sm: 720px;
  --gutter: var(--space-6);
  --section-y: clamp(3.5rem, 8vw, 6rem);

  /* Radius */
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --radius-xl: 20px;
  --radius-2xl: 28px;
  --radius-pill: 999px;
  --radius-card: var(--radius-lg);

  /* Elevation — tinted with the theme's shadow hue, not neutral black */
  --shadow-tint: 20, 18, 16;
  --shadow-xs: 0 1px 2px rgba(var(--shadow-tint), 0.06);
  --shadow-sm: 0 1px 2px rgba(var(--shadow-tint), 0.06), 0 2px 6px rgba(var(--shadow-tint), 0.06);
  --shadow-md: 0 2px 4px rgba(var(--shadow-tint), 0.06), 0 6px 16px rgba(var(--shadow-tint), 0.08);
  --shadow-lg: 0 8px 28px rgba(var(--shadow-tint), 0.12), 0 2px 6px rgba(var(--shadow-tint), 0.06);
  --shadow-xl: 0 20px 48px rgba(var(--shadow-tint), 0.18), 0 4px 12px rgba(var(--shadow-tint), 0.08);

  /* Motion */
  --dur-fast: 120ms;
  --dur-base: 200ms;
  --dur-slow: 320ms;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);

  /* Controls */
  --control-sm: 34px;
  --control-md: 42px;
  --control-lg: 52px;

  --ring-width: 3px;

  /* --- Semantic slots. A theme MUST define every one of these. ----------- */
  /* Defaults below are a neutral warm grey so an unthemed page still reads. */
  --ink-50:  #FAF8F7;
  --ink-100: #F1EDEB;
  --ink-200: #E2DCD8;
  --ink-300: #C8BFB9;
  --ink-400: #9E938C;
  --ink-500: #776C65;
  --ink-600: #58504A;
  --ink-700: #403935;
  --ink-800: #2B2521;
  --ink-900: #1C1815;
  --ink-950: #12100E;

  --brand: #335FA6;
  --brand-hover: #284C87;
  --brand-active: #1E3A68;
  --brand-soft: #EEF3FB;
  /* Brand colour lightened until it clears 4.5:1 on --surface-inverse. Every theme
     overrides this; the default below is computed for the default palette. */
  --brand-on-dark: #7AA0DC;
  /* Brand-coloured text on a LIGHT surface. Most themes' brands are dark enough to use
     as-is; a light brand (corbenhogan's flame orange) overrides this with a deeper shade.
     Set this in a theme, never --brand-contrast: a theme's :root would outrank the
     dark-context rules below, which are class selectors of equal specificity. */
  --brand-on-light: var(--brand);
  /* Brand-coloured text sitting on a --brand-soft chip (.badge, .card__icon). Those keep
     their own light background in every context, so they need the light-surface shade
     even inside a dark section. */
  --brand-ink: var(--brand);
  /* What brand-coloured TEXT actually uses. Re-pointed per context below so the same
     component reads correctly on a light or a dark surface without duplicating rules. */
  --brand-contrast: var(--brand-on-light);
  --on-brand: #FFFFFF;

  --accent: var(--brand);
  --accent-soft: var(--brand-soft);
  /* Same light/dark pair as the brand tokens, for themes that colour eyebrows or
     card numbers with the accent instead (roofmastery's gold). */
  --accent-on-light: var(--accent);
  --accent-on-dark: var(--accent);
  --accent-contrast: var(--accent-on-light);

  --surface-page: #FFFFFF;
  --surface-alt: var(--ink-50);
  --surface-card: #FFFFFF;
  --surface-sunken: var(--ink-100);
  --surface-inverse: var(--ink-950);

  --text-strong: var(--ink-950);
  --text-body: var(--ink-800);
  --text-muted: var(--ink-600);
  /* ink-400 measured 2.77-3.34:1 on white across the nine themes -- below AA on every
     one. This token is not decorative: it colours .breadcrumb, .field .hint, the brand
     tagline and form placeholders. ink-500 clears 4.5:1 on all nine (worst 4.77). */
  --text-faint: var(--ink-500);
  --text-inverse: #FFFFFF;
  --text-inverse-muted: rgba(255, 255, 255, 0.72);
  --text-link: var(--brand);

  --border-subtle: var(--ink-100);
  --border-default: var(--ink-200);
  --border-strong: var(--ink-300);
  --border-inverse: rgba(255, 255, 255, 0.14);

  --focus-ring: var(--brand);
  --ring-color: rgba(51, 95, 166, 0.35);

  --success: #1E9E5A;
  --success-soft: #E8F6EE;
  --danger: #D92D20;
  --danger-soft: #FCEAE7;

  /* Display font treatment — themes with a serif display override these */
  --display-weight: var(--fw-black);
  --display-tracking: -0.035em;
  --display-leading: var(--leading-tight);
}

/* --- Reset --------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

body {
  margin: 0;
  background: var(--surface-page);
  color: var(--text-body);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

img, svg, video { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 var(--space-4);
  color: var(--text-strong);
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  line-height: var(--display-leading);
  letter-spacing: var(--display-tracking);
  text-wrap: balance;
}

h1 { font-size: clamp(2.5rem, 6vw, var(--text-5xl)); }
h2 { font-size: clamp(2rem, 4.2vw, var(--text-3xl)); }
h3 { font-size: var(--text-xl); font-weight: var(--fw-bold); letter-spacing: var(--tracking-snug); }
h4 { font-size: var(--text-md); font-weight: var(--fw-bold); letter-spacing: var(--tracking-snug); }

p { margin: 0 0 var(--space-4); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a { color: var(--text-link); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { text-decoration-thickness: 2px; }

ul, ol { margin: 0 0 var(--space-4); padding-left: 1.25em; }
li { margin-bottom: var(--space-2); }
li:last-child { margin-bottom: 0; }

hr {
  border: 0;
  height: 1px;
  background: var(--border-default);
  margin: var(--space-10) 0;
}

strong, b { font-weight: var(--fw-bold); color: var(--text-strong); }

:focus-visible {
  outline: var(--ring-width) solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

::selection { background: var(--brand); color: var(--on-brand); }

/* --- Layout -------------------------------------------------------------- */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container--narrow { max-width: var(--container-sm); }

.section { padding-block: var(--section-y); }
.section--tight { padding-block: clamp(2.5rem, 5vw, 3.5rem); }
.section--alt { background: var(--surface-alt); }
.section--sunken { background: var(--surface-sunken); }

.section--dark {
  background: var(--surface-inverse);
  color: var(--text-inverse-muted);
}
.section--dark h1,
.section--dark h2,
.section--dark h3,
.section--dark h4 { color: var(--text-inverse); }
/* Every dark context, not just .section--dark. --text-link is tuned for light surfaces,
   so on a dark one it can fail badly: corbenhogan's flame-700 link measured 3.04:1 inside
   a .card--dark. .site-footer has its own link rules further down and is not repeated.

   :not(.btn) matters: a .btn carries its own background and its own label colour
   (var(--on-brand)), so forcing #fff here overrode it and put white on corbenhogan's
   flame fill at 3.44:1. That was already true of the original .section--dark a rule --
   widening it to the other dark contexts simply spread it to more buttons. */
.section--dark a:not(.btn),
.hero--dark a:not(.btn),
.hero--gradient a:not(.btn),
.cta-band a:not(.btn),
.card--dark a:not(.btn) { color: #fff; }

.section-head {
  max-width: 62ch;
  margin-bottom: var(--space-12);
}
.section-head--center {
  margin-inline: auto;
  text-align: center;
}

.grid { display: grid; gap: var(--space-6); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }

.split {
  display: grid;
  gap: clamp(var(--space-8), 5vw, var(--space-16));
  align-items: start;
  grid-template-columns: 1fr;
}
@media (min-width: 880px) {
  .split { grid-template-columns: 1.15fr 0.85fr; }
  .split--even { grid-template-columns: 1fr 1fr; }
  .split--reverse > :first-child { order: 2; }
}

.stack > * + * { margin-top: var(--space-4); }
.stack--lg > * + * { margin-top: var(--space-8); }

/* --- Typographic helpers ------------------------------------------------- */

.eyebrow {
  display: inline-block;
  margin-bottom: var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--brand-contrast);
}
.eyebrow--muted { color: var(--text-faint); }
.eyebrow--on-dark { color: rgba(255, 255, 255, 0.66); }

.lede {
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
  max-width: 62ch;
}
.section--dark .lede { color: var(--text-inverse-muted); }

.prose { max-width: 68ch; }
.prose h2 { margin-top: var(--space-12); }
.prose h3 { margin-top: var(--space-8); }
.prose > :first-child { margin-top: 0; }

.text-center { text-align: center; }
.mx-auto { margin-inline: auto; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: -100px;
  z-index: 999;
  padding: var(--space-3) var(--space-5);
  background: var(--surface-inverse);
  color: var(--text-inverse);
  border-radius: var(--radius-md);
  font-weight: var(--fw-semibold);
  text-decoration: none;
  transition: top var(--dur-base) var(--ease-out);
}
.skip-link:focus { top: var(--space-4); }

/* --- Buttons ------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-lg);
  padding: 0 var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-wide);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background-color var(--dur-base) var(--ease-standard),
    border-color var(--dur-base) var(--ease-standard),
    color var(--dur-base) var(--ease-standard),
    transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}
.btn:hover { transform: translateY(-1px); text-decoration: none; }
.btn:active { transform: translateY(0); }

.btn--primary {
  background: var(--brand);
  color: var(--on-brand);
  box-shadow: 0 6px 18px var(--ring-color);
}
.btn--primary:hover { background: var(--brand-hover); box-shadow: 0 10px 24px var(--ring-color); }
.btn--primary:active { background: var(--brand-active); }

.btn--outline {
  background: transparent;
  color: var(--text-strong);
  border-color: var(--border-strong);
}
.btn--outline:hover { border-color: var(--text-strong); background: var(--surface-alt); }

.btn--ghost { background: transparent; color: var(--text-link); padding-inline: var(--space-3); }
.btn--ghost:hover { background: var(--brand-soft); }

/* .hero--gradient and .cta-band are dark surfaces too and were missing here, so an
   outline button on them kept its light-mode text colour. Measured on corbenhogan.com's
   hero: "Get a roof looked at" rendered at 1.00:1 -- var(--text-strong) on
   var(--surface-inverse), the same value twice. Any selector list that enumerates dark
   contexts must name ALL of them; that is what --brand-contrast avoids by using the
   cascade instead, but buttons need the explicit border colour too. */
.section--dark .btn--outline,
.hero--dark .btn--outline,
.hero--gradient .btn--outline,
.cta-band .btn--outline {
  color: #fff;
  border-color: var(--border-inverse);
}
.section--dark .btn--outline:hover,
.hero--dark .btn--outline:hover,
.hero--gradient .btn--outline:hover,
.cta-band .btn--outline:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.4);
}

.btn--lg { min-height: 58px; padding-inline: var(--space-8); font-size: var(--text-base); }
.btn--block { width: 100%; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-8);
}
.btn-row--center { justify-content: center; }

/* Text link with arrow affordance */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-weight: var(--fw-semibold);
  text-decoration: none;
}
.link-arrow::after {
  content: '\2192';
  transition: transform var(--dur-base) var(--ease-out);
}
.link-arrow:hover::after { transform: translateX(3px); }

/* --- Header -------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--surface-page) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border-subtle);
}

.site-header > .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
  min-height: 72px;
}

.brand {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--fw-extra);
  letter-spacing: var(--tracking-tight);
  color: var(--text-strong);
  text-decoration: none;
  white-space: nowrap;
}
.brand span { color: var(--brand-ink); }
.brand small {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--text-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-faint);
  margin-top: 2px;
}

.site-nav { display: flex; align-items: center; gap: var(--space-1); }

.site-nav a {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-standard), background-color var(--dur-fast) var(--ease-standard);
}
.site-nav a:hover { color: var(--text-strong); background: var(--surface-alt); }
/* :not(.nav-cta) -- the CTA carries a brand fill and its own label colour. On every
   site's contact page the CTA IS the current page, so this rule won the cascade
   (0,2,1 over .site-nav .nav-cta's 0,2,0) and painted --text-strong onto the brand
   background: 2.31:1 on roofmastery.com. Nine contact pages, one selector. */
.site-nav a[aria-current='page']:not(.nav-cta) { color: var(--text-strong); }

.site-nav .nav-cta {
  margin-left: var(--space-3);
  padding: 0 var(--space-5);
  min-height: var(--control-md);
  display: inline-flex;
  align-items: center;
  background: var(--brand);
  color: var(--on-brand);
  border-radius: var(--radius-pill);
}
.site-nav .nav-cta:hover { background: var(--brand-hover); color: var(--on-brand); }

.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: var(--control-md);
  height: var(--control-md);
  padding: 0;
  background: transparent;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: var(--text-strong);
}
.nav-toggle span {
  position: relative;
  display: block;
  width: 16px;
  height: 1.5px;
  background: currentColor;
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-fast) linear;
}
.nav-toggle span::before,
.nav-toggle span::after {
  content: '';
  position: absolute;
  left: 0;
  width: 16px;
  height: 1.5px;
  background: currentColor;
  transition: transform var(--dur-base) var(--ease-out);
}
.nav-toggle span::before { top: -5px; }
.nav-toggle span::after { top: 5px; }
.nav-toggle[aria-expanded='true'] span { background: transparent; }
.nav-toggle[aria-expanded='true'] span::before { transform: translateY(5px) rotate(45deg); }
.nav-toggle[aria-expanded='true'] span::after { transform: translateY(-5px) rotate(-45deg); }

@media (max-width: 860px) {
  .nav-toggle { display: inline-flex; }
  .site-nav {
    position: absolute;
    inset: 100% 0 auto;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--space-3) var(--gutter) var(--space-6);
    background: var(--surface-page);
    border-bottom: 1px solid var(--border-default);
    box-shadow: var(--shadow-lg);
    display: none;
  }
  .site-nav[data-open='true'] { display: flex; }
  .site-nav a { padding: var(--space-4) var(--space-2); font-size: var(--text-md); border-radius: 0; }
  .site-nav a + a { border-top: 1px solid var(--border-subtle); }
  .site-nav .nav-cta {
    margin: var(--space-4) 0 0;
    justify-content: center;
    min-height: var(--control-lg);
    border-top: 0;
  }
}

/* --- Hero ---------------------------------------------------------------- */

.hero {
  position: relative;
  padding-block: clamp(4rem, 10vw, 7.5rem);
  overflow: hidden;
}

.hero__inner { max-width: 46ch; }
.hero h1 { margin-bottom: var(--space-6); }
.hero .lede { font-size: var(--text-lg); max-width: 54ch; }

/* --gradient carries the SAME dark text treatment as --dark, and must keep doing so.
   Its background is var(--surface-inverse) with a brand wash over it, so it is a dark
   surface whether or not --dark is also applied. When it only set a background, a hero
   marked `class="hero hero--gradient"` inherited light-mode text: h1 resolved to
   var(--text-strong) -> var(--ink-950), the exact value of --surface-inverse behind it.
   That shipped on corbenhogan.com's homepage and the headline rendered at a measured
   contrast ratio of 1.00 -- the same colour as its background, invisible except where the
   brand wash lightened it. The .eyebrow was fine (it used --on-dark, 18.78) which is
   precisely why it went unnoticed. Never give a dark background without the text colours. */
.hero--dark,
.hero--gradient {
  background: var(--surface-inverse);
  color: var(--text-inverse-muted);
}
.hero--dark h1,
.hero--gradient h1 { color: var(--text-inverse); }
.hero--dark .lede,
.hero--gradient .lede { color: var(--text-inverse-muted); }

.hero--gradient {
  background:
    radial-gradient(120% 90% at 12% 0%, color-mix(in srgb, var(--brand) 22%, transparent) 0%, transparent 62%),
    var(--surface-inverse);
}

.hero--soft {
  background:
    radial-gradient(100% 80% at 85% 0%, var(--brand-soft) 0%, transparent 60%),
    var(--surface-page);
}

/* --- Brand-coloured text on dark surfaces -------------------------------------
   Brand colours are chosen to sit on white, so most of them fail badly against
   --surface-inverse. Measured before this rule existed: 7 of 9 themes failed WCAG AA
   for .eyebrow / .stat__figure / .card__num on dark, mansionsoflakenona.com worst at
   1.56:1 and katelynhogan.com at 1.89:1 -- present but effectively unreadable.

   Done by re-pointing a variable rather than by adding descendant colour rules, because
   a LIGHT card sitting inside a dark section must keep the ordinary brand colour. The
   .card reset below does that: the card re-declares --brand-contrast for its own subtree,
   so nesting resolves correctly at any depth without :not() gymnastics.

   .badge and .card__icon are deliberately NOT included -- they carry their own
   --brand-soft background, so they stay legible on dark and re-pointing them would put
   light text on a light chip. */
.section--dark,
.hero--dark,
.hero--gradient,
.card--dark,
.cta-band,
.site-footer {
  --brand-contrast: var(--brand-on-dark);
  --accent-contrast: var(--accent-on-dark);
  /* Re-point the semantic TEXT tokens too, rather than overriding components one by one.
     34 components in this file hard-code --text-strong/-body/-muted/-faint, and every one
     of them was unreadable inside a dark section: .feature-list on roofmastery.com's
     about page rendered its <strong> at var(--ink-950) on var(--ink-950) -- 1.00:1,
     invisible -- and its <li> at 2.45:1. Enumerating fixes per component does not hold,
     because the next component someone adds inherits the bug. Doing it at the token layer
     means anything built from the design system adapts automatically. */
  --text-strong: var(--text-inverse);
  --text-body: var(--text-inverse-muted);
  --text-muted: var(--text-inverse-muted);
  --text-faint: rgba(255, 255, 255, 0.62);
  --border-subtle: var(--border-inverse);
  --border-default: var(--border-inverse);
}
/* Anything carrying its own OPAQUE LIGHT background must undo the dark re-pointing for
   its own subtree, or it ends up light-on-light. .card was handled from the start; the
   pills were not, and that produced white text on a white pill in corbenhogan.com's dark
   hero -- a failure this system introduced rather than found. The rule to remember when
   adding a component: if it paints its own light background, add it here. */
.card:not(.card--dark),
.pill-row li:not(.badge--on-dark),
.pill:not(.badge--on-dark) {
  /* A LIGHT card sitting inside a dark section must undo all of the above for its own
     subtree. Restore the light-surface shade, not the raw brand: on a theme whose brand
     is too light to be text (corbenhogan's flame orange), var(--brand) here reintroduced
     the exact 3.44:1 failure this system exists to prevent -- inside every card, which is
     precisely where .card__num lives.
     These values intentionally duplicate the :root defaults. Keep them in sync; there is
     no way to "unset back to :root" for a custom property that an ancestor has set. */
  --brand-contrast: var(--brand-on-light);
  --accent-contrast: var(--accent-on-light);
  --text-strong: var(--ink-950);
  --text-body: var(--ink-800);
  --text-muted: var(--ink-600);
  --text-faint: var(--ink-500);
  --border-subtle: var(--ink-100);
  --border-default: var(--ink-200);
}

.hero--split .container { display: grid; gap: clamp(var(--space-8), 5vw, var(--space-16)); align-items: center; }
@media (min-width: 900px) {
  .hero--split .container { grid-template-columns: 1.05fr 0.95fr; }
  .hero--split .hero__inner { max-width: none; }
}

/* --- Stat strip ---------------------------------------------------------- */

.stat-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1px;
  background: var(--border-default);
  border-block: 1px solid var(--border-default);
}
.stat {
  background: var(--surface-page);
  padding: var(--space-8) var(--space-6);
  text-align: center;
}
.stat__figure {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, var(--text-2xl));
  font-weight: var(--display-weight);
  letter-spacing: var(--display-tracking);
  line-height: 1;
  color: var(--brand-contrast);
  margin-bottom: var(--space-2);
}
.stat__label {
  display: block;
  font-size: var(--text-sm);
  color: var(--text-muted);
  max-width: 26ch;
  margin-inline: auto;
}

/* --- Cards --------------------------------------------------------------- */

.card {
  display: flex;
  flex-direction: column;
  padding: var(--space-8);
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-xs);
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-standard);
}
.card h3 { margin-bottom: var(--space-3); }
.card > :last-child { margin-bottom: 0; }
.card p { color: var(--text-muted); }

.card--interactive:hover,
a.card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: var(--border-strong);
}
a.card { text-decoration: none; color: inherit; }

.card--flush { padding: 0; border: 0; box-shadow: none; background: none; }

.card--accent {
  border-color: transparent;
  background: var(--brand-soft);
}

.card--dark {
  background: var(--surface-inverse);
  border-color: transparent;
  color: var(--text-inverse-muted);
}
.card--dark h3 { color: var(--text-inverse); }
.card--dark p { color: var(--text-inverse-muted); }

.card__num {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  color: var(--brand-contrast);
  margin-bottom: var(--space-4);
}

.card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-bottom: var(--space-5);
  border-radius: var(--radius-md);
  background: var(--brand-soft);
  color: var(--brand-ink);
  flex: none;
}
.card__icon svg { width: 22px; height: 22px; }

/* --- Feature list -------------------------------------------------------- */

.feature-list { list-style: none; margin: 0; padding: 0; }
.feature-list li {
  position: relative;
  padding-left: 1.9em;
  margin-bottom: var(--space-4);
  color: var(--text-muted);
}
.feature-list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--brand);
}
.feature-list strong { display: block; color: var(--text-strong); margin-bottom: 2px; }

/* --- Badges / pills ------------------------------------------------------ */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--brand-soft);
  color: var(--brand-ink);
  font-size: var(--text-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-wide);
}
.badge--outline {
  background: transparent;
  border: 1px solid var(--border-default);
  color: var(--text-muted);
}
.badge--on-dark {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  border: 1px solid var(--border-inverse);
}

.pill-row { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
.pill-row li { margin: 0; }
/* :not(.badge) -- .pill-row li has specificity (0,1,1) and sat AFTER .badge--on-dark
   (0,1,0), so it silently overrode the on-dark badge variant entirely. The hero pills on
   corbenhogan.com were the on-dark variant and had been getting plain-pill styling all
   along. */
.pill-row li:not(.badge),
.pill:not(.badge) {
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  color: var(--text-muted);
  background: var(--surface-card);
}

/* --- Placeholder chip ----------------------------------------------------
   The ONLY sanctioned way to show a detail that has not been supplied yet.
   Never leave a raw `[PLACEHOLDER: x]` token in page text, and never put one
   in an href, a tel:/mailto:, or a JSON-LD value — omit the field instead.
   `data-needs` is what a future session greps for.

   A chip must never be the sole carrier of its meaning: write full-contrast
   copy beside it saying what is missing.

   Colour is --text-muted, NOT --text-faint: faint is ~3.0:1 on white, which
   fails WCAG AA for body-size text. A chip is real information the reader
   needs, so it must be legible; italics and the dotted rule carry the
   "provisional" signal without relying on low contrast to do it. */

.placeholder {
  display: inline-block;
  color: var(--text-muted);
  font-style: italic;
  text-decoration: underline dotted var(--border-strong);
  text-underline-offset: 0.25em;
}
.placeholder::before { content: '\2014\00a0'; font-style: normal; }

/* --- Quote --------------------------------------------------------------- */

.quote {
  margin: 0;
  padding: var(--space-8);
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius-card);
}
.quote p {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--fw-medium);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-snug);
  color: var(--text-strong);
}
.quote footer {
  margin-top: var(--space-5);
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
}

/* --- CTA band ------------------------------------------------------------ */

.cta-band {
  padding-block: var(--section-y);
  background:
    radial-gradient(90% 120% at 100% 0%, color-mix(in srgb, var(--brand) 26%, transparent) 0%, transparent 60%),
    var(--surface-inverse);
  color: var(--text-inverse-muted);
  text-align: center;
}
.cta-band h2 { color: var(--text-inverse); max-width: 22ch; margin-inline: auto; }
.cta-band .lede { color: var(--text-inverse-muted); margin-inline: auto; }
.cta-band .btn-row { justify-content: center; }

/* --- FAQ (native details/summary) ---------------------------------------- */

.faq { border-top: 1px solid var(--border-default); }
.faq details {
  border-bottom: 1px solid var(--border-default);
}
.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
  padding: var(--space-6) 0;
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-snug);
  color: var(--text-strong);
  cursor: pointer;
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: '';
  flex: none;
  width: 12px;
  height: 12px;
  border-right: 2px solid var(--brand);
  border-bottom: 2px solid var(--brand);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--dur-base) var(--ease-out);
}
.faq details[open] summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.faq details > div { padding-bottom: var(--space-6); max-width: 72ch; color: var(--text-muted); }

/* --- Forms --------------------------------------------------------------- */

.form { display: grid; gap: var(--space-5); }

.field { display: grid; gap: var(--space-2); }
.field > label {
  font-size: var(--text-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-strong);
}
.field .hint { font-size: var(--text-xs); color: var(--text-faint); }

.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: var(--control-lg);
  padding: var(--space-3) var(--space-4);
  font-family: inherit;
  font-size: var(--text-base);
  color: var(--text-body);
  background: var(--surface-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.field textarea { min-height: 140px; padding-top: var(--space-3); resize: vertical; line-height: var(--leading-normal); }
.field input::placeholder,
.field textarea::placeholder { color: var(--text-faint); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 var(--ring-width) var(--ring-color);
}

.field--row { display: grid; gap: var(--space-5); grid-template-columns: 1fr; }
@media (min-width: 620px) { .field--row { grid-template-columns: 1fr 1fr; } }

/* Honeypot — visually and semantically hidden from real users */
.field--trap {
  position: absolute !important;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-status {
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: var(--fw-medium);
  display: none;
}
.form-status[data-state='success'] { display: block; background: var(--success-soft); color: #14603A; }
.form-status[data-state='error']   { display: block; background: var(--danger-soft);  color: #8A1A12; }
.form-status[data-state='pending'] { display: block; background: var(--surface-sunken); color: var(--text-muted); }

.form fieldset { border: 0; margin: 0; padding: 0; }

/* --- Footer -------------------------------------------------------------- */

.site-footer {
  padding-block: var(--space-16) var(--space-8);
  background: var(--surface-inverse);
  color: var(--text-inverse-muted);
  font-size: var(--text-sm);
}
/* Footer column headings are styled by position, not by tag level, so a page
   can pick the level that keeps its heading order legal. The sanctioned
   pattern is a visually-hidden <h2>Site footer</h2> followed by <h3> column
   headings: an h2 may follow ANY preceding level (moving up never skips), and
   h3 after h2 never skips — so it is valid on every page regardless of what
   <main> ended on. Styling only h4 here is what caused an h2 -> h4 skip on
   every page of every site in the 2026-08-13 rebuild. */
.site-footer h2,
.site-footer h3,
.site-footer h4 {
  color: var(--text-inverse);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  margin-bottom: var(--space-4);
}
.site-footer a { color: var(--text-inverse-muted); text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; }

.footer-grid {
  display: grid;
  gap: var(--space-10);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  padding-bottom: var(--space-10);
  border-bottom: 1px solid var(--border-inverse);
}
.footer-grid > div:first-child { grid-column: span 1; max-width: 34ch; }
@media (min-width: 900px) { .footer-grid { grid-template-columns: 1.6fr 1fr 1fr 1fr; } }

.footer-links { display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }

.footer-legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-8);
  justify-content: space-between;
  padding-top: var(--space-6);
  color: rgba(255, 255, 255, 0.5);
  font-size: var(--text-xs);
}

.footer-note {
  margin-top: var(--space-5);
  max-width: 90ch;
  /* 0.42 composited to ~4.09:1 on --surface-inverse, just under AA, on all nine themes.
     0.62 gives 7.37:1 at worst while staying visibly secondary. (.footer-legal above uses
     0.5, which measures clear of AA, so it is left alone.) */
  color: rgba(255, 255, 255, 0.62);
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
}

/* --- Breadcrumb ---------------------------------------------------------- */

.breadcrumb {
  /* --text-muted, not --text-faint. The breadcrumb is navigation, and it sits on
     .page-head's tinted surface rather than white, where ink-500 measured 4.47:1 --
     under AA by a hair. The tint is exactly why the white-background calculation was
     not enough on its own. */
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-6);
  padding: 0;
  list-style: none;
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.breadcrumb li { margin: 0; display: flex; gap: var(--space-2); align-items: center; }
.breadcrumb li + li::before { content: '/'; color: var(--border-strong); }
.breadcrumb a { color: var(--text-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--text-strong); }

/* --- Page header (interior pages) ---------------------------------------- */

.page-head {
  padding-block: clamp(3rem, 7vw, 5rem) clamp(2rem, 5vw, 3.5rem);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface-alt);
}
.page-head h1 { font-size: clamp(2.25rem, 5vw, var(--text-4xl)); margin-bottom: var(--space-5); }
.page-head .lede { margin-bottom: 0; }

/* --- Scroll reveal (progressive enhancement) ----------------------------- */

[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 620ms var(--ease-out), transform 620ms var(--ease-out);
  will-change: opacity, transform;
}
[data-reveal].is-visible { opacity: 1; transform: none; }

/* No-JS and reduced-motion users must still see everything. */
.no-js [data-reveal],
[data-reveal].is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; }
}

/* --- Print --------------------------------------------------------------- */

@media print {
  .site-header, .site-footer, .cta-band, .btn-row, .skip-link { display: none !important; }
  body { color: #000; background: #fff; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 0.8em; color: #555; }
}

/* --- Multi-step form (added 2026-08-13) ----------------------------------------
   Styling only for chrome that site.js builds at runtime. With JS off none of these
   elements exist, the fieldsets are all visible, and the form is an ordinary long
   form -- so there is deliberately no rule here that hides a step. Hiding is done
   with the `hidden` attribute by script, which cannot strand content if the script
   never runs. */

.wizard__bar {
  display: flex;
  gap: 6px;
  margin-bottom: var(--space-4);
}
.wizard__bar span {
  height: 3px;
  flex: 1;
  border-radius: var(--radius-pill);
  background: var(--border-default);
  transition: background var(--dur-base) var(--ease-out);
}
.wizard__bar span.is-done { background: var(--brand-contrast); }
.wizard__bar span.is-current { background: var(--brand); }

.wizard__count {
  margin: 0 0 var(--space-6);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.form.is-wizard [data-step] {
  border: 0;
  margin: 0;
  padding: 0;
  min-inline-size: 0;   /* fieldset defaults to min-content and breaks flex/grid children */
}
.form.is-wizard [data-step] legend {
  padding: 0;
  margin-bottom: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-snug);
  color: var(--text-strong);
}
.form.is-wizard [data-step] > .lede {
  margin-bottom: var(--space-6);
  font-size: var(--text-base);
}

.wizard__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  margin-top: var(--space-6);
}
.wizard__nav .btn--ghost[disabled] { opacity: 0.4; cursor: not-allowed; }
.wizard__nav [hidden] { display: none; }

/* Choice list — a radio group that reads as tappable cards rather than dots.
   The input stays in the DOM and keeps its native semantics and keyboard
   behaviour; it is visually replaced, never removed. */
.choice-list { display: grid; gap: var(--space-3); }
.choice {
  position: relative;
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-4) var(--space-5);
  border: 1.5px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.choice:hover { border-color: var(--border-strong); }
.choice input {
  margin: 0.2em 0 0;
  accent-color: var(--brand);
  flex: none;
}
.choice__text { display: block; }
.choice__text strong { display: block; color: var(--text-strong); }
.choice__text span { display: block; font-size: var(--text-sm); color: var(--text-muted); }
.choice:has(input:checked) { border-color: var(--brand); background: var(--brand-soft); }
/* :has() is well supported now, but the focus ring must not depend on it. */
.choice:focus-within { outline: var(--ring-width) solid var(--ring-color); outline-offset: 2px; }
