/* =========================================================================
   Dip Studio - dip-site core design system (full-site static rebuild)
   Ported from the dip-homepage Astro build (2026-07-01 redesign) and adapted
   for the multi-page static architecture. Monochrome + whitespace-driven;
   bright green is a rare spark. Buttons change color on hover, never magnetic.
   ========================================================================= */

/* --- Fonts: self-hosted. DM Sans variable (body), General Sans (display,
   stand-in wired for a one-line swap to FeatureHeadline once licensed). --- */
@font-face {
  font-family: 'DM Sans Variable';
  src: url('/assets/fonts/dm-sans-latin-wght-normal.woff2') format('woff2-variations');
  font-weight: 100 1000; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'General Sans';
  src: url('/assets/fonts/general-sans-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'General Sans';
  src: url('/assets/fonts/general-sans-600.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}

:root {
  /* --- Color (confirmed from the live site CSS + DB, 2026-06-18) --- */
  --ink: #141412;
  --paper: #ffffff;
  --gray-50: #f8f8f8;
  --warm-100: #e0ded8;
  --gray-300: #a5a4a4;   /* decorative only - fails contrast as text */
  --gray-600: #666666;
  --dark: #141412;
  --dark-2: #1c1c19;
  --accent: #00ff00;   /* Alex's review 2026-09-06: neon, was #3ddc58 */
  --accent-ink: #0c2a13;

  --on-light: var(--ink);
  --on-light-muted: var(--gray-600);
  --on-dark: #f4f3ef;
  --on-dark-muted: #a8a7a1;
  --on-warm-muted: #55534e;  /* AA on the warm #e0ded8 band */
  --hairline: #e7e6e1;
  --hairline-dark: rgba(244, 243, 239, 0.14);

  /* --- Type --- */
  --font-body: "DM Sans Variable", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  --font-display: "General Sans", "DM Sans Variable", system-ui, sans-serif;

  --fs-label: 0.75rem;
  /* reading tier lands at 18.5px desktop (house standard), ~17.3px at 375 */
  --fs-body: clamp(1.08rem, 1.01rem + 0.34vw, 1.15625rem);
  --fs-lead: clamp(1.18rem, 1.05rem + 0.6vw, 1.5rem);
  --fs-h3: clamp(1.5rem, 1.2rem + 1.2vw, 2.4rem);
  /* reined in 2026-08-23 (David: section headlines too big); homepage hero
     keeps its own clamp in home.css */
  --fs-h2: clamp(2.1rem, 1.2rem + 3.2vw, 4.4rem);
  --fs-h1: clamp(2.6rem, 1rem + 4.9vw, 6.4rem);
  --fs-statement: clamp(2.1rem, 1.2rem + 4.2vw, 5.2rem);

  /* shared motion rhythm */
  --dur-zoom: 0.8s;
  --dur-media-fade: 0.8s;

  /* --- Space --- */
  --gutter: clamp(1.25rem, 0.6rem + 3.5vw, 5.5rem);
  --container: 1560px;
  --section-y: clamp(5.5rem, 3rem + 10vw, 12rem);

  --radius: 16px;
  --radius-lg: 30px;
  --radius-pill: 999px;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

  --header-h: 80px;
}

/* ---------------------------------------------------------------- reset */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-stopped { overflow: hidden; }

body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

/* the [hidden] attribute must beat any author display value. Without this,
   `el.hidden = true` silently does nothing to anything we style with
   display:grid/flex (it bit .form and .form__success both). */
[hidden] { display: none !important; }

img, picture, video, svg { display: block; max-width: 100%; }
img, video { height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
ul { list-style: none; padding: 0; }
:focus { outline: none; }
:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; border-radius: 4px; }
.on-dark :focus-visible, .section--dark :focus-visible, .site-footer :focus-visible,
.contact-panel :focus-visible, .est-hero :focus-visible, body.page--dark :focus-visible,
body.page--dark-hero .site-header:not(.is-stuck) :focus-visible { outline-color: var(--paper); }
::selection { background: var(--accent); color: var(--accent-ink); }

.skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 1000;
  background: var(--ink); color: var(--paper);
  padding: 0.6rem 1rem; border-radius: var(--radius);
  transform: translateY(-150%); transition: transform 0.2s var(--ease-out);
}
.skip-link:focus { transform: translateY(0); }

/* ---------------------------------------------------------------- layout */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.section { padding-block: var(--section-y); position: relative; }
.section--dark { background: var(--dark); color: var(--on-dark); }
.section--soft { background: var(--gray-50); }
/* alternating tones this close need a real edge */
.section--soft { border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }

/* numbered editorial label, e.g. "(02) Our expertise"; also used on h2 so the
   outline reads section by section (SEO pass 2026-09-04) */
.sec-label, h2.sec-label {
  font-family: var(--font-body); line-height: 1.5; letter-spacing: 0.16em; margin: 0;
}
.sec-label {
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gray-600);
  display: inline-flex; align-items: baseline; gap: 0.7rem;
}
.section--dark .sec-label, .on-dark .sec-label { color: var(--on-dark-muted); }
.sec-label .idx { color: var(--ink); font-family: var(--font-display); letter-spacing: 0.02em; }
/* inline links inside the testimonials and the award line */
.tst__role a, .proof__sub a { color: inherit; text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--accent); }
.tst__role a:hover, .proof__sub a:hover { color: var(--accent); }
.section--dark .sec-label .idx, .on-dark .sec-label .idx { color: var(--on-dark); }

/* ---------------------------------------------------------------- type */
h1, h2, h3 { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.025em; line-height: 1.0; }
.display-h1 { font-size: var(--fs-h1); line-height: 0.96; letter-spacing: -0.035em; }
.display-h2 { font-size: var(--fs-h2); line-height: 1.0; letter-spacing: -0.03em; text-wrap: balance; }
.statement { font-size: var(--fs-statement); line-height: 1.06; letter-spacing: -0.03em; font-family: var(--font-display); font-weight: 600; }
.h3 { font-size: var(--fs-h3); line-height: 1.08; letter-spacing: -0.02em; }
.lead { font-size: var(--fs-lead); line-height: 1.45; }
.muted { color: var(--gray-600); }
.section--dark .muted, .on-dark .muted { color: var(--on-dark-muted); }
.dot { color: var(--accent); }

/* green sweep underline link */
.spark-link {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-weight: 500;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 0% 2px;
  background-repeat: no-repeat;
  background-position: 0 100%;
  transition: background-size 0.4s var(--ease-out);
  padding-bottom: 2px;
}
.spark-link .arrow { transition: transform 0.4s var(--ease-out); }
.spark-link:hover, .spark-link:focus-visible { background-size: 100% 2px; }
/* links inside running text keep the underline at rest: on touch there is no
   hover, and weight alone does not read as a link (launch review 2026-09-04) */
p .spark-link, .page-hero__lede .spark-link, li .spark-link { background-size: 100% 2px; }
/* cards: hovering anywhere on the card lights its link */
.pj-card:hover .spark-link, .blog-card:hover .spark-link, .pj-card:focus-visible .spark-link, .blog-card:focus-visible .spark-link { background-size: 100% 2px; }
.pj-card:hover .spark-link .arrow, .blog-card:hover .spark-link .arrow { transform: translateX(5px); }
.spark-link:hover .arrow, .spark-link:focus-visible .arrow { transform: translateX(5px); }

/* ------------------------------------------------------------ buttons
   Color change on hover (David's rule). Never magnetic, never cursor-follow. */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.6rem;
  padding: 1rem 1.75rem;
  border-radius: var(--radius-pill);
  font-weight: 600;
  font-size: 0.875rem;
  line-height: 1;
  white-space: nowrap;
  transition: background-color 0.35s var(--ease-out), color 0.35s var(--ease-out), border-color 0.35s var(--ease-out);
}
.btn--primary { background: var(--ink); color: var(--paper); }
.btn--primary:hover, .btn--primary:focus-visible { background: var(--accent); color: var(--accent-ink); }
.section--dark .btn--primary, .on-dark .btn--primary, .contact-panel .btn--primary { background: var(--paper); color: var(--ink); }
.section--dark .btn--primary:hover, .on-dark .btn--primary:hover, .contact-panel .btn--primary:hover,
.section--dark .btn--primary:focus-visible, .on-dark .btn--primary:focus-visible, .contact-panel .btn--primary:focus-visible { background: var(--accent); color: var(--accent-ink); }
.btn--outline { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--ink); }
.btn--outline:hover, .btn--outline:focus-visible { background: var(--ink); color: var(--paper); }
.section--dark .btn--outline, .on-dark .btn--outline { color: var(--on-dark); box-shadow: inset 0 0 0 1.5px var(--hairline-dark); }
.section--dark .btn--outline:hover, .on-dark .btn--outline:hover { background: var(--paper); color: var(--ink); box-shadow: none; }
.btn--play { background: var(--accent); color: var(--accent-ink); }
.btn--play:hover, .btn--play:focus-visible { background: var(--paper); color: var(--ink); }
.btn--lg { padding: 1.15rem 2.2rem; font-size: 0.95rem; }
.btn .icon { width: 1.05em; height: 1.05em; }

/* ---------------------------------------------------------------- header */
.site-header {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  height: var(--header-h);
  display: flex; align-items: center;
  transition: background-color 0.4s var(--ease-out), box-shadow 0.4s var(--ease-out), backdrop-filter 0.4s var(--ease-out);
}
.site-header__inner { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; }
.site-header.is-stuck {
  background: rgba(255, 255, 255, 0.85);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  box-shadow: 0 1px 0 var(--hairline);
}
/* dark page variant (estimator): header sits on ink */
body.page--dark .site-header.is-stuck { background: rgba(20, 20, 18, 0.82); box-shadow: 0 1px 0 var(--hairline-dark); }
body.page--dark { background: var(--dark); color: var(--on-dark); }
body.page--dark .site-header .nav__link, body.page--dark .wordmark { color: var(--on-dark); }
body.page--dark .nav-toggle span { background: var(--on-dark); }
body.page--dark .site-header .wordmark img { filter: invert(1) brightness(1.6); }
body.page--dark .site-header .btn--primary { background: var(--paper); color: var(--ink); }
body.page--dark .site-header .btn--primary:hover,
body.page--dark .site-header .btn--primary:focus-visible { background: var(--accent); color: var(--accent-ink); }

/* dark-HERO pages (case studies, about): on-dark header until it sticks,
   then the white bar takes over and the defaults return */
body.page--dark-hero .site-header:not(.is-stuck) .nav__link { color: var(--on-dark); }
body.page--dark-hero .site-header:not(.is-stuck) .wordmark img { filter: invert(1) brightness(1.6); }
body.page--dark-hero .site-header:not(.is-stuck) .nav-toggle span { background: var(--on-dark); }
body.page--dark-hero .site-header:not(.is-stuck) .btn--primary { background: var(--paper); color: var(--ink); }
body.page--dark-hero .site-header:not(.is-stuck) .btn--primary:hover,
body.page--dark-hero .site-header:not(.is-stuck) .btn--primary:focus-visible { background: var(--accent); color: var(--accent-ink); }

.wordmark { display: inline-flex; align-items: center; }
.wordmark img { height: 30px; width: auto; }
.site-footer .wordmark img, .section--dark .wordmark img { filter: invert(1) brightness(1.6); }
body.page--dark .site-header .wordmark img { filter: invert(1) brightness(1.6); }
/* the header mark is inline SVG on currentColor, so it needs the colour the
   img filters used to supply on an un-stuck dark hero */
body.page--dark-hero .site-header:not(.is-stuck) .wordmark { color: var(--on-dark); }

/* ------------------------------------------------------- live header mark
   Dip's real wordmark rebuilt as strokeable primitives so it can animate.
   Geometry is byte-for-byte the brand SVG (dip-logo.svg): bowls r8.47794 at
   cx 10.4779 / 40.9986, stems at x 18.957 / 25.7383 / 32.5195, stroke 3.1068.
   Two states, both driven from js/core.js:
     .is-assembling  the mark draws itself in, construction guides flashing
                     (fires when the sticky bar engages, never on first load)
     .is-cycling     the two bowls open a gap and counter-rotate
                     (fires when the pointer enters the header, anywhere)
   Timings are David's from the design file. Path lengths are exact:
   bowl circumference 53.2685, stems 11.3039 / 15.8681 / 11.3039. */
.dipmark { display: block; width: 52px; height: 30px; color: inherit; }
.dipmark__ink circle, .dipmark__ink line {
  fill: none; stroke: currentColor; stroke-width: 3.1068;
}
.dipmark__ink line { stroke-linecap: square; }
.dipmark__guides line { stroke: currentColor; stroke-width: 0.5; opacity: 0; }
.dipmark__bowl { --len: 53.2685; }
.dipmark__stem--d { --len: 11.3039; }
.dipmark__stem--i { --len: 15.8681; }
.dipmark__stem--p { --len: 11.3039; }

@keyframes dipDraw { from { stroke-dashoffset: var(--len); } to { stroke-dashoffset: 0; } }
@keyframes dipGuide { 0% { opacity: 0; } 18% { opacity: 0.3; } 62% { opacity: 0.3; } 100% { opacity: 0; } }
/* 31% of the circumference opens as a gap, matching the design file's 46.8/150.8 */
@keyframes dipOpen {
  0%   { stroke-dasharray: 53.2685 0; }
  18%  { stroke-dasharray: 36.74 16.53; }
  80%  { stroke-dasharray: 36.74 16.53; }
  100% { stroke-dasharray: 53.2685 0; }
}
@keyframes dipSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes dipSpinRev { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }

.dipmark.is-assembling .dipmark__ink > * {
  stroke-dasharray: var(--len);
  animation: dipDraw var(--dur) cubic-bezier(0.33, 0, 0.15, 1) var(--delay) both;
}
.dipmark.is-assembling .dipmark__bowl--d { --delay: 0ms;   --dur: 720ms; }
.dipmark.is-assembling .dipmark__stem--d { --delay: 260ms; --dur: 420ms; }
.dipmark.is-assembling .dipmark__stem--i { --delay: 400ms; --dur: 340ms; }
.dipmark.is-assembling .dipmark__stem--p { --delay: 500ms; --dur: 400ms; }
.dipmark.is-assembling .dipmark__bowl--p { --delay: 560ms; --dur: 690ms; }
.dipmark.is-assembling .dipmark__guides line { animation: dipGuide 1250ms linear both; }

/* the bowls are a 180deg rotation of each other in the real mark, so they
   counter-rotate here rather than spinning in parallel */
.dipmark.is-cycling .dipmark__bowl {
  transform-box: fill-box; transform-origin: center;
}
.dipmark.is-cycling .dipmark__bowl--d {
  animation: dipOpen 1100ms cubic-bezier(0.65, 0, 0.35, 1) both,
             dipSpin 1100ms cubic-bezier(0.65, 0, 0.35, 1) both;
}
.dipmark.is-cycling .dipmark__bowl--p {
  animation: dipOpen 1100ms cubic-bezier(0.65, 0, 0.35, 1) 140ms both,
             dipSpinRev 1100ms cubic-bezier(0.65, 0, 0.35, 1) 140ms both;
}
@media (prefers-reduced-motion: reduce) {
  .dipmark.is-assembling .dipmark__ink > *,
  .dipmark.is-assembling .dipmark__guides line,
  .dipmark.is-cycling .dipmark__bowl { animation: none; stroke-dasharray: none; }
}

.nav { display: flex; align-items: center; gap: clamp(1rem, 2vw, 2.2rem); }
.nav__list { display: flex; align-items: center; gap: clamp(1rem, 1.6vw, 2rem); }
.nav__link { font-size: 0.9rem; font-weight: 500; color: var(--ink); position: relative; padding-block: 0.4rem; }
.nav__link::after { content: ""; position: absolute; left: 0; bottom: 0; height: 2px; width: 100%; background: var(--accent); transform: scaleX(0); transform-origin: right; transition: transform 0.35s var(--ease-out); }
.nav__link:hover::after, .nav__link:focus-visible::after { transform: scaleX(1); transform-origin: left; }
.nav__link[aria-current="page"]::after { transform: scaleX(1); }

.nav-toggle { display: none; }

@media (max-width: 919.98px) {
  .nav__list, .header-cta { display: none; }
  /* tablets keep the header pill (600 to 919px); phones get the sticky bar instead */
  .nav-toggle { display: inline-flex; flex-direction: column; justify-content: center; align-items: center; gap: 5px; padding: 0.6rem; min-width: 44px; min-height: 44px; margin-right: -0.6rem; z-index: 120; }
  .nav-toggle span { width: 24px; height: 2px; background: var(--ink); transition: transform 0.3s var(--ease-out), opacity 0.3s; }
  /* the sheet slides in UNDER the fixed header (header is z 100 and its own
     stacking context, so nothing inside it can ever sit above a z 110
     overlay; with the sheet at 90 the wordmark and the X stay reachable).
     Launch review 2026-09-04: the menu could not be closed on any page. */
  .mobile-nav {
    position: fixed; inset: 0; z-index: 90; background: var(--paper); color: var(--ink);
    padding: calc(var(--header-h) + 1.5rem) var(--gutter) 2rem;
    display: flex; flex-direction: column; gap: 0.25rem;
    transform: translateY(-100%); transition: transform 0.5s var(--ease-out);
    overflow-y: auto; visibility: hidden;
  }
  .mobile-nav.is-open { transform: translateY(0); visibility: visible; }
  body.nav-open { overflow: hidden; }
  .mobile-nav a { font-family: var(--font-display); font-weight: 500; font-size: 1.6rem; padding: 0.7rem 0; border-bottom: 1px solid var(--hairline); color: var(--ink); }
  .mobile-nav a[aria-current="page"] { color: var(--gray-600); }
  /* the CTA inside the sheet is a real button again: .mobile-nav a (0,1,1)
     used to beat .btn (0,1,0) and strip its padding and face */
  .mobile-nav .btn { margin-top: 1.5rem; align-self: flex-start; font-size: 1rem; font-family: var(--font-body); padding: 1rem 1.75rem; border-bottom: 0; min-height: 48px; }
  .mobile-nav .btn.btn--primary { background: var(--ink); color: var(--paper); }
  .mobile-nav .btn.btn--primary:hover, .mobile-nav .btn.btn--primary:focus-visible { background: var(--accent); color: var(--accent-ink); }
  .mobile-nav__more { margin-top: auto; padding-top: 2rem; display: grid; gap: 0.2rem; }
  .mobile-nav__more a { font-family: var(--font-body); font-size: 1rem; color: var(--gray-600); border-bottom: 0; padding: 0.55rem 0; }
  .mobile-nav__more a:hover, .mobile-nav__more a:focus-visible { color: var(--ink); }
  /* while the sheet is open the bar sits on paper on every page variant */
  body.nav-open .site-header { background: var(--paper); box-shadow: 0 1px 0 var(--hairline); }
  body.nav-open .site-header .wordmark, body.page--dark.nav-open .site-header .wordmark,
  body.page--dark-hero.nav-open .site-header:not(.is-stuck) .wordmark, .page--home.nav-open .site-header.is-on-dark .wordmark { color: var(--ink); }
  body.nav-open .nav-toggle span, body.page--dark.nav-open .nav-toggle span,
  body.page--dark-hero.nav-open .site-header:not(.is-stuck) .nav-toggle span, .page--home.nav-open .site-header.is-on-dark .nav-toggle span { background: var(--ink); }
  .nav-toggle.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav-toggle.is-open span:nth-child(2) { opacity: 0; }
  .nav-toggle.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
}
@media (min-width: 920px) { .mobile-nav { display: none; } }
@media (min-width: 600px) and (max-width: 919.98px) { .header-cta { display: inline-flex; } }

/* visually hidden, available to assistive tech */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* -------------------------------------------------------- page hero (interior)
   Oversized editorial opener shared by interior pages. */
.page-hero { padding-top: calc(var(--header-h) + clamp(3.5rem, 8vw, 7rem)); padding-bottom: clamp(2.5rem, 6vw, 5rem); }
.page-hero__kicker {
  font-size: var(--fs-label); letter-spacing: 0.18em; text-transform: uppercase; font-weight: 600;
  display: inline-flex; align-items: center; gap: 0.7rem; margin-bottom: 1.4rem;
}
.page-hero__kicker::before { content: ""; width: 2rem; height: 2px; background: var(--accent); }
.page-hero h1 { max-width: 16ch; }
/* the two index pages carry sentence-length h1s: a wider measure keeps the
   first cards inside the first screen at 1440 (launch review 2026-09-04) */
.page-hero--wide h1 { max-width: 26ch; }
.page-hero__lede { margin-top: 1.6rem; max-width: 52ch; font-size: var(--fs-lead); color: var(--gray-600); }
.page-hero__actions { display: flex; flex-wrap: wrap; gap: 0.8rem; margin-top: 2rem; }
.page-hero--404 { min-height: 40svh; }
.section--dark .page-hero__lede, .on-dark .page-hero__lede { color: var(--on-dark-muted); }

/* legal pages: a measured column on the page edge, headings grouped with their text */
.section--flush-top { padding-top: 0; }
.legal { max-width: calc(72ch + 2 * var(--gutter)); display: grid; gap: 0.6rem; }
.legal h2 { margin-top: 1.4rem; }
.legal h2:first-child { margin-top: 0; }
.legal__meta { margin-top: 1rem; font-size: 0.9rem; color: var(--gray-600); }
.legal__intro { font-size: 1.1rem; line-height: 1.6; max-width: 62ch; }
.legal__toc { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 1.2rem 0 0.6rem; }
.legal__toc a { font-size: 0.8rem; letter-spacing: 0.06em; text-transform: uppercase; font-weight: 600; color: var(--gray-600); border: 1px solid var(--hairline); border-radius: var(--radius-pill); padding: 0.55rem 0.9rem; transition: color 0.2s, border-color 0.2s; }
.legal__toc a:hover, .legal__toc a:focus-visible { color: var(--ink); border-color: var(--ink); }
.legal h3 { font-size: 1.05rem; font-weight: 600; margin-top: 0.9rem; }
.legal__list { display: grid; gap: 0.5rem; padding-left: 1.2rem; }
.legal__list li { padding-left: 0.2rem; }
.legal code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.9em; background: var(--gray-50); border: 1px solid var(--hairline); border-radius: 4px; padding: 0.05em 0.35em; }
.legal__address { line-height: 1.7; }
.legal h2[id] { scroll-margin-top: calc(var(--header-h, 80px) + 1rem); }
/* ---------------------------------------------------------- media helpers */
.frame { overflow: clip; border-radius: var(--radius); background: var(--gray-50); }
.frame img, .frame video { width: 100%; height: 100%; object-fit: cover; }

.player {
  position: relative; border-radius: var(--radius-lg); overflow: clip; background: var(--ink);
  aspect-ratio: 16 / 9;
  isolation: isolate;
}
.player video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.player__play {
  position: absolute; z-index: 3; inset: 0; margin: auto; width: max-content; height: max-content;
  display: none; align-items: center; gap: 0.7rem;
}
.player.has-js .player__play { display: inline-flex; }
/* touch and narrow screens: a short label, bottom-left, off the subject's
   face (launch review 2026-09-04); hover devices keep the long centred pill */
.play__short { display: none; }
@media (hover: none), (max-width: 599.98px) {
  .play__long { display: none; }
  .play__short { display: inline; }
  .player.has-js .player__play { inset: auto auto 1rem 1rem; margin: 0; }
}
.player.is-playing .player__play { opacity: 0; visibility: hidden; pointer-events: none; }

/* ------------------------------------------------------- founder moment
   Shared by the homepage and about page (click-to-play studio video). */
.founder__portrait { width: 56px; height: 56px; border-radius: 50%; overflow: clip; flex: none; display: block; }
.founder__portrait img { width: 100%; height: 100%; object-fit: cover; }
.founder__byline { display: flex; align-items: center; gap: 0.9rem; }

/* ------------------------------------------------------------------ forms
   16px minimum on every input at every breakpoint (iOS zoom guard). */
.form { display: grid; gap: 1rem; }
.form__row { display: grid; gap: 1rem; }
@media (min-width: 700px) { .form__row--split { grid-template-columns: 1fr 1fr; } }
.form input, .form select, .form textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 1rem; /* 16px floor */
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: 0.95rem 1.1rem;
  transition: border-color 0.25s var(--ease-out);
}
.form textarea { min-height: 130px; resize: vertical; }
.form input:focus-visible, .form select:focus-visible, .form textarea:focus-visible {
  outline: none; border-color: var(--ink);
}
.form ::placeholder { color: var(--gray-600); opacity: 1; }
/* dark surface variant */
.on-dark .form input, .on-dark .form select, .on-dark .form textarea,
.section--dark .form input, .section--dark .form select, .section--dark .form textarea,
.contact-panel .form input, .contact-panel .form select, .contact-panel .form textarea,
.site-footer .form input, .site-footer .form select, .site-footer .form textarea {
  background: transparent; color: var(--on-dark); border-color: rgba(244, 243, 239, 0.42);
}
.on-dark .form ::placeholder, .section--dark .form ::placeholder, .contact-panel .form ::placeholder, .site-footer .form ::placeholder { color: var(--on-dark-muted); }
.on-dark .form input:focus-visible, .on-dark .form select:focus-visible, .on-dark .form textarea:focus-visible,
.section--dark .form input:focus-visible, .section--dark .form select:focus-visible, .section--dark .form textarea:focus-visible,
.contact-panel .form input:focus-visible, .contact-panel .form select:focus-visible, .contact-panel .form textarea:focus-visible,
.site-footer .form input:focus-visible, .site-footer .form select:focus-visible, .site-footer .form textarea:focus-visible { border-color: var(--accent); }
/* the native option list is drawn by the OS on its own light popup, so the
   options must NOT inherit the dark surface's light text: ink on paper in
   every dark context (the .on-dark case bit the booking page's time zone
   select, David 2026-09-04) */
.on-dark .form option, .section--dark .form option, .contact-panel .form option, .site-footer .form option { color: var(--ink); background: var(--paper); }
/* the submit is the panel's secondary action (Book a Free Call is the one primary in view) */
.contact-panel .form [type="submit"] { justify-self: start; box-shadow: inset 0 0 0 1.5px rgba(244, 243, 239, 0.42); }
.contact-panel .form [type="submit"]:hover, .contact-panel .form [type="submit"]:focus-visible { background: var(--paper); color: var(--ink); box-shadow: none; }
body.page--contact .contact-panel { padding-bottom: clamp(4rem, 8vw, 7rem); }
/* single-line fields on the contact band echo the estimator's pill language */
.contact-panel .form input, .contact-panel .form select { border-radius: var(--radius-pill); }
.form__hp { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }
.form__status { font-size: 0.95rem; }
.form__status.is-error { color: #e4574f; }
.form__success { display: grid; gap: 0.6rem; padding: 1.4rem 0; }
.form__success h3 { font-size: var(--fs-h3); }
.form__success .btn { justify-self: start; margin-top: 0.4rem; }

/* post-submit follow-up: one-click source attribution (asked politely
   AFTER the lead is already captured, so it costs no conversions) */
.form__followup { display: grid; gap: 1.1rem; justify-items: start; margin-top: 1.4rem; padding-top: 1.6rem; border-top: 1px solid var(--hairline); }
.contact-panel .form__followup, .section--dark .form__followup, .on-dark .form__followup { border-top-color: var(--hairline-dark); }
.form__followup-q { max-width: 44ch; }
.form__followup-options { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.form__followup-options button {
  padding: 0.8rem 1.3rem; border-radius: var(--radius-pill);
  font-size: 0.9rem; font-weight: 500; line-height: 1;
  border: 1.5px solid var(--ink);
  transition: background-color 0.3s var(--ease-out), color 0.3s var(--ease-out), border-color 0.3s var(--ease-out);
}
.form__followup-options button:hover,
.form__followup-options button:focus-visible { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.contact-panel .form__followup-options button, .section--dark .form__followup-options button, .on-dark .form__followup-options button {
  color: var(--on-dark); border-color: rgba(244, 243, 239, 0.42);
}
.contact-panel .form__followup-options button:hover, .section--dark .form__followup-options button:hover, .on-dark .form__followup-options button:hover,
.contact-panel .form__followup-options button:focus-visible, .section--dark .form__followup-options button:focus-visible, .on-dark .form__followup-options button:focus-visible {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.form__followup-done { font-weight: 600; }

/* ------------------------------------------- end-of-page CTA (#contact)
   Every page closes on this. No form (the contact form lives only on
   /contact/) and no background texture: an editorial plate on flat ink.
   The section element MUST also carry `on-dark`. That class is what flips
   buttons, labels, and focus rings to their dark-surface variants; without
   it .btn--outline renders ink-on-ink and disappears completely. */
.cta-band {
  /* one step lifted off the footer, which is flat --dark. Same-ink on same-ink
     made the closing statement read as the top of the footer instead of as its
     own plate. */
  background: var(--dark-2);
  color: var(--on-dark);
  /* the preceding section is often dark too, so the edge has to be drawn */
  border-top: 1px solid var(--hairline-dark);
}
.cta-band .container { display: grid; gap: clamp(2.5rem, 5vw, 4rem); }
.cta-band__say { display: grid; gap: 1.4rem; align-content: start; }
/* the last word on the page, so it runs at statement scale, not h2 scale */
.cta-band__title {
  color: var(--on-dark);
  font-size: var(--fs-statement);
  line-height: 0.98;
  letter-spacing: -0.035em;
  max-width: 15ch;
  text-wrap: balance;
}
.cta-band__do { display: grid; gap: 1.6rem; justify-items: start; align-content: end; }
.cta-band__sub { color: var(--on-dark-muted); font-size: var(--fs-lead); max-width: 30ch; }
.cta-band__actions { display: flex; flex-wrap: wrap; gap: 1rem; }
.cta-band__email { padding-block: 0.5rem; background-position: 0 calc(100% - 0.4rem); }
/* the secondary needs a boundary you can actually see against the ink
   (WCAG 1.4.11 wants 3:1 for a control's visual boundary) */
.cta-band .btn--outline { box-shadow: inset 0 0 0 1.5px rgba(244, 243, 239, 0.42); }
/* David 2026-09-03: the closing Book a Free Call starts green, goes paper on hover */
.cta-band .btn--primary { background: var(--accent); color: var(--accent-ink); }
.cta-band .btn--primary:hover, .cta-band .btn--primary:focus-visible { background: var(--paper); color: var(--ink); }
/* stacked pills of two different widths read as unresolved, so match them */
@media (max-width: 559px) {
  .cta-band__actions { display: grid; grid-template-columns: 1fr; gap: 0.8rem; width: 100%; }
  .cta-band__actions .btn { width: 100%; }
}
@media (min-width: 920px) {
  .cta-band .container {
    grid-template-columns: 1.15fr 0.85fr;
    column-gap: clamp(3rem, 6vw, 6rem);
    align-items: end;
  }
}
/* on /contact/ the form itself is the closing action, so the generic band
   would just repeat it */
body.page--contact .cta-band { display: none; }

/* -------------------------------- contact page form panel (/contact/ only) */
.contact-panel { background: var(--dark); color: var(--on-dark); }
.contact-panel__grid { display: grid; gap: clamp(2.5rem, 6vw, 5rem); }
.contact-panel__intro { display: grid; gap: 1.2rem; align-content: start; }
.contact-panel__intro h2 { max-width: 12ch; color: var(--on-dark); }
.contact-panel__sub { color: var(--on-dark-muted); font-size: var(--fs-lead); max-width: 32ch; }
.contact-panel__meta { display: grid; gap: 1.4rem; justify-items: start; margin-top: 0.6rem; }
@media (min-width: 920px) {
  .contact-panel__grid { grid-template-columns: 0.9fr 1.1fr; column-gap: clamp(3rem, 6vw, 6rem); }
}

.site-footer { background: var(--dark); color: var(--on-dark); padding-block: clamp(3rem, 5vw, 4.5rem) 2.5rem; border-top: 1px solid var(--hairline-dark); }
.footer__top { display: grid; gap: clamp(2.5rem, 5vw, 4rem); }
.footer__brand { display: grid; gap: 1.3rem; max-width: 34ch; }
/* the handwritten signature writes itself into the brand slot on every page
   (js/footer-signature.js, David 2026-09-03). The wordmark and the old
   static mark only step aside once the animated one is actually in, so a
   page with no JS or reduced motion keeps what it always had. */
.v2-sig { width: clamp(200px, 24vw, 340px); margin-block: clamp(0.25rem, 1vw, 0.75rem); }
.v2-sig__svg { display: block; width: 100%; height: auto; overflow: visible; }
.v2-sig__full { opacity: 1; }   /* fully open until the script takes over */
.v2-sig--footer { width: clamp(240px, 24vw, 400px); margin: 0; }
.footer__brand.has-sig-anim > .wordmark,
.footer__brand.has-sig-anim > .footer__sig { display: none; }
.site-footer { border-top: 1px solid rgba(244, 243, 239, 0.22); }
.footer__sig { height: 2.6rem; width: auto; opacity: 0.9; }
.footer__cols { display: grid; gap: 2rem; grid-template-columns: repeat(2, 1fr); }
/* phones: the newsletter takes its own full row instead of a half-width cell */
@media (max-width: 759.98px) { .footer__col:nth-child(3) { grid-column: 1 / -1; } .newsletter { max-width: 420px; } }
.footer__col h3 { font-size: 0.78rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--on-dark-muted); font-weight: 600; margin-bottom: 1rem; font-family: var(--font-body); }
.footer__col ul { display: grid; gap: 0.7rem; }
.footer__col a { color: var(--on-dark); opacity: 0.82; transition: opacity 0.2s, color 0.2s; font-size: 0.96rem; display: inline-block; padding-block: 0.3rem; margin-block: -0.3rem; }
.footer__col a:hover { opacity: 1; color: var(--accent); }
.newsletter { display: grid; gap: 0.8rem; max-width: 38ch; }
/* the field and the button stack: side by side in a footer column the
   input collapsed to ~90px and you could not read your own address
   (David 2026-09-03). The markup wraps them in an inline-styled flex span,
   hence the !important. */
.newsletter form > span { display: grid !important; gap: 0.6rem; }
.newsletter button { justify-self: start; }
.newsletter input {
  flex: 1; background: transparent; border: 1px solid rgba(244, 243, 239, 0.42); color: var(--on-dark);
  border-radius: var(--radius-pill); padding: 0.85rem 1.2rem; font: inherit; font-size: 1rem; min-width: 0; width: 100%; min-height: 48px;
}
.newsletter input::placeholder { color: var(--on-dark-muted); }
.newsletter input:focus-visible { border-color: var(--accent); outline: 3px solid var(--paper); outline-offset: 3px; }
/* David 2026-09-03: Sign up starts paper, goes green on hover */
.newsletter button { background: var(--paper); color: var(--ink); border-radius: var(--radius-pill); padding: 0.8rem 1.2rem; font-weight: 600; flex: none; transition: background-color 0.3s var(--ease-out), color 0.3s var(--ease-out); }
.newsletter button:hover, .newsletter button:focus-visible { background: var(--accent); color: var(--accent-ink); }
.footer__bottom {
  margin-top: clamp(3rem, 6vw, 5rem); padding-top: 1.8rem; border-top: 1px solid var(--hairline-dark);
  display: flex; flex-wrap: wrap; gap: 1rem 2rem; align-items: center; justify-content: space-between;
  font-size: 0.85rem; color: var(--on-dark-muted);
}
.footer__social { display: flex; gap: 1.2rem; }
.footer__social a, .footer__legal a { display: inline-block; padding-block: 0.5rem; margin-block: -0.5rem; }
.footer__social a:hover { color: var(--accent); }
/* copyright + privacy travel together so the bottom bar keeps three items */
.footer__legal { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem 1.5rem; }
.footer__legal a { color: var(--on-dark-muted); transition: color 0.2s; }
.footer__legal a:hover, .footer__legal a:focus-visible { color: var(--accent); }
.footer__mark { font-family: var(--font-display); font-weight: 600; letter-spacing: 0.22em; text-transform: uppercase; font-size: 0.72rem; color: var(--on-dark-muted); }
@media (min-width: 760px) { .footer__cols { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 920px) { .footer__top { grid-template-columns: 1.1fr 1.9fr; } }

/* ============================================================= MOTION
   Hidden states apply ONLY under html.motion-ready (added pre-paint when
   motion is allowed). No-JS and reduced-motion users see everything.
   Entrances park at opacity .001, never 0 (LCP is recorded at .001). */
html.motion-ready [data-reveal] { opacity: 0.001; }
html.motion-ready [data-reveal="up"] { transform: translateY(36px); }
html.motion-ready [data-reveal="image"] { clip-path: inset(0 0 100% 0); opacity: 1; transform: none; }

/* heading reveal (js/heading-reveal.js, port of the Claude Design handoff):
   the real heading text stays in flow and goes transparent while an SVG
   layer draws the same lines over it; guides map the line, letters stroke
   in, ink fills. Color only ever changes on the host, never the layout. */
.hr-host { position: relative; }
/* while the letters draw, the real text is NEAR-transparent, not transparent:
   the browser still paints it (so it counts as the page's largest text and
   is not hidden text), it is just invisible under the SVG copy. Browsers
   without relative color syntax keep the transparent fallback. Once the
   draw finishes the script removes the layer and these classes, so the
   heading is plain text again. */
.hr-host.is-armed, .hr-host.is-drawn,
.hr-host.is-armed :not(.hr-layer, .hr-layer *), .hr-host.is-drawn :not(.hr-layer, .hr-layer *) { color: transparent; color: rgb(from currentColor r g b / 0.004); }
@media print { .hr-layer { display: none !important; } .hr-host, .hr-host * { color: inherit !important; } .hr-host .dot { color: var(--accent) !important; } }
.hr-line { position: relative; }
.hr-host .line { overflow: visible; }
html.motion-ready .hr-host .line > span { animation: none !important; }
.hr-layer { position: absolute; inset: 0; display: block; pointer-events: none; }
.hr-layer svg { position: absolute; width: 100%; overflow: visible; display: block; }
@keyframes hrDraw { from { stroke-dashoffset: var(--len, 600); } to { stroke-dashoffset: 0; } }
@keyframes hrFill { from { fill-opacity: 0; } to { fill-opacity: 1; } }
@keyframes hrStrokeOut { from { stroke-opacity: 1; } to { stroke-opacity: 0; } }
@keyframes hrGuideIn { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes hrGuideInY { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes hrGuideOut { from { opacity: 1; } to { opacity: 0; } }

@keyframes dip-rise { from { opacity: 0.001; transform: translateY(36px); } to { opacity: 1; transform: none; } }
@keyframes dip-unmask { from { transform: translateY(112%); } to { transform: translateY(0); } }

@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; scroll-behavior: auto !important; }
  html.motion-ready [data-reveal] { opacity: 1 !important; transform: none !important; clip-path: none !important; }
}

/* ------------------------------------------- sticky mobile CTA (mcta)
   Under 920px only, JS-managed (html.has-mcta), see the block in core.js.
   A bottom bar with the primary Book a Free Call and a phone button that
   opens a Call / Text sheet. It hides itself while ANY other Book a Free
   Call button, the contact form panel or the footer is on screen, while a
   field has focus or the keyboard is open, and while the mobile nav is
   open. Documented exception to the one-primary-per-view rule in
   WEB_DESIGN_STANDARDS (2026-09-04). */
.mcta, .mcta-sheet { display: none; }
@media (max-width: 919.98px) {
  html.has-mcta .mcta {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 105;
    /* call button first, then the primary. The DOM order matches, so the
       tab order and the reading order follow the visual order. */
    display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.6rem; align-items: center;
    padding: 0.6rem var(--gutter) calc(0.6rem + env(safe-area-inset-bottom, 0px));
    background: rgba(255, 255, 255, 0.92);
    -webkit-backdrop-filter: saturate(180%) blur(14px); backdrop-filter: saturate(180%) blur(14px);
    box-shadow: 0 -1px 0 var(--hairline);
    transform: translateY(0); visibility: visible;
    transition: transform 0.3s var(--ease-out), visibility 0s linear 0s;
  }
  html.has-mcta .mcta.is-hidden,
  html.has-mcta body.nav-open .mcta { transform: translateY(110%); visibility: hidden; transition: transform 0.3s var(--ease-out), visibility 0s linear 0.3s; }
  .mcta__book { width: 100%; min-height: 48px; }
  .mcta__phone {
    width: 48px; height: 48px; border-radius: 50%; flex: none;
    display: grid; place-items: center;
    color: var(--ink); background: var(--paper); box-shadow: inset 0 0 0 1.5px var(--ink);
    transition: background-color 0.3s var(--ease-out), color 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
  }
  .mcta__phone svg { width: 20px; height: 20px; }
  .mcta__phone:hover, .mcta__phone:focus-visible, .mcta__phone[aria-expanded="true"] { background: var(--accent); color: var(--accent-ink); box-shadow: inset 0 0 0 1.5px var(--accent); }
  html.has-mcta .mcta-sheet { display: block; position: fixed; inset: 0; z-index: 130; }
  .mcta-sheet__backdrop { position: absolute; inset: 0; background: rgba(20, 20, 18, 0.55); opacity: 0; transition: opacity 0.3s var(--ease-out); }
  .mcta-sheet__panel {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: var(--paper); color: var(--ink);
    border-radius: 22px 22px 0 0;
    padding: 1.4rem var(--gutter) calc(1.4rem + env(safe-area-inset-bottom, 0px));
    display: grid; gap: 0.8rem;
    transform: translateY(100%); transition: transform 0.35s var(--ease-out);
  }
  .mcta-sheet.is-open .mcta-sheet__backdrop { opacity: 1; }
  .mcta-sheet.is-open .mcta-sheet__panel { transform: translateY(0); }
  .mcta-sheet__title { font-family: var(--font-display); font-weight: 600; font-size: 1.25rem; letter-spacing: -0.02em; padding-right: 3rem; margin-bottom: 0.3rem; }
  .mcta-sheet__panel .btn { width: 100%; min-height: 52px; }
  .mcta-sheet__close {
    position: absolute; top: 0.6rem; right: calc(var(--gutter) - 0.6rem);
    width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center;
    color: var(--ink); transition: background-color 0.3s var(--ease-out), color 0.3s var(--ease-out);
  }
  .mcta-sheet__close svg { width: 14px; height: 14px; }
  .mcta-sheet__close:hover, .mcta-sheet__close:focus-visible { background: var(--accent); color: var(--accent-ink); }
}
@media (prefers-reduced-motion: reduce) {
  .mcta, .mcta-sheet__panel, .mcta-sheet__backdrop { transition: none !important; }
}
