/* ============================================================
   The CV Method — base + component styles
   SPEC v2 §2.1 (import order) · §4 (component mapping) · §8 (a11y)

   Reproduces the shipped React library (components/*.jsx) in vanilla CSS.
   Every value comes from the token files — nothing is invented here except
   the AA-safe corrections listed under "Contrast corrections" below.

   NOTE: this file previously shipped as a stub that @imported a `tokens/`
   directory which does not exist in this handoff. The token files live at
   css/*.css and are imported by index.html in the §2.1 order, so the broken
   imports are gone.
   ============================================================ */

:root{
  /* ---- Contrast corrections (SPEC §2.2 "AA on every pair" is law) ----
     --grad-signal (mint→cyan→blue) and --grad-method both bottom out well
     below 4.5:1 as *text* on --canvas / --canvas-soft. This is the same
     family, darkened so every stop clears AA at any size on light:
       #0F766E on #F4F8F9 = 5.12:1   #0E7490 = 5.01:1   #1D4ED8 = 6.27:1
     Used for gradient numerals/headings on light, and for the primary
     button fill (white label needs >=4.5:1 against every stop). */
  --grad-signal-ink:linear-gradient(120deg,#0F766E 0%,#0E7490 45%,#1D4ED8 100%);
  --grad-cta:var(--grad-signal-ink);

  /* Hero word ("ships."). With the §7 Signal Field live the backdrop under the
     headline measures ~L0.055, where --grad-signal's #3B82F6 stop lands at
     2.71:1 — under the 3:1 large-text floor. This is the upper half of the
     same ramp; its darkest stop measures 4.65:1 there, and better still on the
     poster-only path. The deep blue stays in the field itself. */
  --grad-hero-word:linear-gradient(120deg,#6FD9C0 0%,#22D3EE 45%,#38BDF8 100%);

  --nav-h:72px;
}

/* motion.css zeroes --dur-fast/--dur/--dur-slow under reduced motion but not
   --stagger, so a staggered entrance would still hold elements at their `from`
   state for the delay. Completing the token set here keeps every §6 animation
   honouring reduced motion purely through tokens. (Worth pushing upstream into
   the design system's motion.css.) */
@media (prefers-reduced-motion:reduce){
  :root{--stagger:0s}
}

/* ============================================================
   1. Reset + base
   ============================================================ */
*,*::before,*::after{box-sizing:border-box}
html,body,h1,h2,h3,h4,p,ul,ol,li,figure,blockquote,dl,dd{margin:0;padding:0}
ul[class],ol[class]{list-style:none}
img,svg,video{max-width:100%;height:auto;display:block}

html{-webkit-text-size-adjust:100%}

body{
  font-family:var(--font-body);
  font-size:var(--fs-body);
  font-weight:var(--fw-body);
  line-height:var(--leading-body);
  color:var(--text);
  background:var(--bg);
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}

h1,h2,h3{
  font-family:var(--font-display);
  font-weight:var(--fw-display);
  line-height:var(--leading-display);
  letter-spacing:var(--tracking-display);
  text-wrap:balance;
}
h1{font-size:var(--fs-h1)}
h2{font-size:var(--fs-h2)}
h3{font-size:var(--fs-h3)}

p{text-wrap:pretty}
strong,b{font-weight:var(--fw-body-strong)}
em{font-style:italic}

/* ============================================================
   2. Layout
   ============================================================ */
.container{
  width:100%;
  max-width:var(--container);
  margin-inline:auto;
  padding-inline:var(--space-3);
}
.container--narrow{max-width:var(--container-narrow)}
@media (max-width:479px){
  .container{padding-inline:var(--space-2)}
}

.section{
  padding-block:var(--section-y);
  /* fixed nav must not cover anchor targets */
  scroll-margin-top:calc(var(--nav-h) + var(--space-1));
}
.section--soft{background:var(--canvas-soft)}
.section--dark{background:var(--bg-dark);color:var(--text-invert)}

.section-head{max-width:60ch;margin-bottom:var(--space-4)}
.section-head > * + *{margin-top:var(--space-1)}
.section-head p{color:var(--text-muted)}
.section--dark .section-head p{color:var(--text-muted-dark)}

/* ============================================================
   3. Accessibility primitives (SPEC §8)
   ============================================================ */
.skip-link{
  position:absolute;
  left:-9999px;
  top:0;
  z-index:200;
  padding:12px 18px;
  background:var(--ink-900);
  color:#fff;
  font-weight:var(--fw-body-strong);
  text-decoration:none;
  border-radius:0 0 var(--radius-sm) 0;
}
.skip-link:focus{left:0}

:focus-visible{
  outline:3px solid var(--focus-ring);
  outline-offset:2px;
  border-radius:6px;
}

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

/* ============================================================
   3b. A4 — section reveals (SPEC §6)
   rise + fade as each group enters view, staggered by --stagger to read as
   "a system of parts" assembling rather than one slab appearing.

   The hidden resting state is gated behind `@media (scripting: enabled)`.
   With JS disabled the rule never applies, so every section renders at full
   opacity and the page stays completely readable (SPEC §14) — no inline
   class-setting script, and therefore no CSP hash to maintain. Browsers
   without the `scripting` media feature also fall through to visible.

   Only transform and opacity animate, and both durations come from
   motion.css, so reduced motion drops straight to the final state.
   ============================================================ */
@media (scripting: enabled){
  .reveal{
    opacity:0;
    transform:translateY(16px);
    transition:
      opacity var(--dur) var(--ease-out),
      transform var(--dur) var(--ease-out);
    transition-delay:calc(var(--stagger) * var(--reveal-i, 0));
    will-change:opacity,transform;
  }
  .reveal.is-revealed{
    opacity:1;
    transform:none;
    will-change:auto;
  }
}

/* ============================================================
   4. core/Logo
   Mirrors components/core/Logo.jsx: inline SVG mark + live-text wordmark,
   so the lockup renders in real Space Grotesk. (The flat
   assets/logo/lockup-*.svg files bake the wordmark as an SVG text node and
   lose the typeface when referenced as an external image; they stay the
   source of truth for OG, favicons and anywhere a raster is required
   — SPEC §3.)
   ============================================================ */
.logo{
  display:inline-flex;
  align-items:center;
  min-height:var(--hit-min);   /* it is a link — SPEC §2.4 */
  gap:.5em;
  font-family:var(--font-display);
  letter-spacing:var(--tracking-display);
  line-height:1;
  text-decoration:none;
  white-space:nowrap;
}
.logo__mark{flex:none;width:1.75em;height:1.75em}
.logo__word{font-size:1.05rem}
.logo__word .t-the{font-weight:var(--fw-display-quiet)}
.logo__word .t-cv{font-weight:var(--fw-display)}
.logo__word .t-method{font-weight:var(--fw-display-quiet)}
/* tone: white — nav + footer over ink (SPEC §3) */
.logo--white{color:#fff}
.logo--white .t-the{color:#8FA3B4}  /* 7.6:1 on the ink nav */

/* ============================================================
   5. core/Button  (variants: primary · ghost · outline · quiet)
   ============================================================ */
.btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:.5rem;
  min-height:var(--hit-min);
  padding:14px 26px;
  border:none;
  border-radius:var(--radius-pill);
  font-family:var(--font-body);
  font-size:1rem;
  font-weight:var(--fw-body-strong);
  line-height:1.2;
  text-decoration:none;
  cursor:pointer;
}
/* every size clears --hit-min: SPEC §2.4 requires 44px on all tap targets,
   which overrides the 36px `sm` in components/core/Button.jsx */
.btn--sm{min-height:var(--hit-min);padding:10px 20px;font-size:.95rem}
.btn--lg{padding:18px 34px;font-size:1.05rem}

/* A7 — buttons: gradient shift + glow on hover/focus.
   `background-position` and `box-shadow` are paint properties, so instead of
   transitioning them the shifted gradient and the glow live on a second layer
   that is cross-faded with opacity alone (§6 master rule). `isolation:isolate`
   makes the button a stacking context, so the z-index:-1 layers paint above
   its own background but below the label — no markup wrapper needed. */
.btn--primary{
  position:relative;
  isolation:isolate;
  color:#fff;
  /* solid AA-safe fallback if the layers below never paint */
  background-color:var(--teal-ink);
  box-shadow:var(--shadow-2);
}
.btn--primary::before,
.btn--primary::after{
  content:"";
  position:absolute;
  inset:0;
  z-index:-1;
  border-radius:inherit;
  background-image:var(--grad-cta);
  background-size:180% 180%;
}
.btn--primary::before{background-position:0 50%}
.btn--primary::after{
  background-position:100% 50%;
  box-shadow:var(--shadow-glow);
  opacity:0;
  transition:opacity var(--dur) var(--ease-out);
}
.btn--primary:hover::after,
.btn--primary:focus-visible::after{opacity:1}
.btn--primary{transition:transform var(--dur-fast) var(--ease-out)}
.btn--primary:hover{transform:translateY(-2px)}

/* ghost/outline have no gradient to shift — the border and label brighten,
   which the reduced-motion path keeps as a plain colour change (§6 A7) */
.btn--ghost,.btn--outline,.btn--quiet{transition:transform var(--dur-fast) var(--ease-out)}
.btn--ghost:hover,.btn--outline:hover,.btn--quiet:hover{transform:translateY(-2px)}
/* ghost — dark backgrounds only; border raised to .38 alpha for the 3:1
   non-text contrast minimum (WCAG 1.4.11); .28 measured 2.39:1 on ink. */
.btn--ghost{
  background:transparent;
  border:1px solid rgba(255,255,255,.38);
  color:var(--text-invert);
}
.btn--outline{
  background:transparent;
  border:1px solid var(--border);
  color:var(--text);
}
.btn--quiet{background:var(--canvas-soft);color:var(--text)}

.btn[aria-disabled="true"]{opacity:.45;pointer-events:none}

/* ============================================================
   6. navigation/NavBar + mobile overlay
   NavBar.prompt.md: "The CTA is persistent and never scrolls away" — so the
   Book-a-call button stays in the bar at every width, outside the overlay.

   A1 (§6): transparent over the hero, frosted ink once past 40px. The frost
   rides on a pseudo-element cross-faded by opacity, so nothing repaints on
   scroll. `@media (scripting: enabled)` is what makes the transparent resting
   state safe: with JS off there is no scroll listener, so the bar stays
   frosted and never turns invisible over the white credibility strip.
   ============================================================ */
.nav{
  position:fixed;
  inset:0 0 auto 0;
  z-index:100;
  isolation:isolate;
  min-height:var(--nav-h);
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:var(--space-2);
  padding:var(--space-2) var(--space-3);
}
.nav::before{
  content:"";
  position:absolute;
  inset:0;
  z-index:-1;
  background:rgba(5,10,18,.82);
  backdrop-filter:blur(12px);
  -webkit-backdrop-filter:blur(12px);
  border-bottom:1px solid var(--border-dark);
}
@media (scripting: enabled){
  .nav::before{
    opacity:0;
    transition:opacity var(--dur) var(--ease-out);
  }
  .nav.is-scrolled::before{opacity:1}
  /* the overlay owns the screen, so the bar must read as part of it */
  .nav.is-menu-open::before{opacity:1}
}
.nav__actions{display:flex;align-items:center;gap:var(--space-2)}
.nav__links{
  display:flex;
  align-items:center;
  gap:var(--space-3);
}
.nav__link{
  display:inline-flex;
  align-items:center;
  min-height:var(--hit-min);
  padding-inline:4px;
  color:rgba(242,247,249,.86);
  font-size:.95rem;
  text-decoration:none;
}
.nav__link:hover,.nav__link:focus-visible{color:#fff}

/* hamburger — only exists below the overlay breakpoint */
.nav__toggle{
  display:none;
  align-items:center;
  justify-content:center;
  width:var(--hit-min);
  height:var(--hit-min);
  padding:0;
  border:1px solid var(--border-dark);
  border-radius:var(--radius-sm);
  background:transparent;
  color:var(--text-invert);
  cursor:pointer;
}
.nav__toggle svg{width:22px;height:22px}
.nav__toggle .icon-close,
.nav__toggle[aria-expanded="true"] .icon-open{display:none}
.nav__toggle[aria-expanded="true"] .icon-close{display:block}

/* ---- overlay menu ----
   Instant show/hide: P1 ships no motion, and Accordion/NavBar are decision
   zones. Hidden with `hidden` so it is out of the a11y tree when closed. */
.nav-menu{
  position:fixed;
  inset:0;
  z-index:99;
  display:flex;
  flex-direction:column;
  padding:calc(var(--nav-h) + var(--space-3)) var(--space-3) var(--space-4);
  overflow-y:auto;
  overscroll-behavior:contain;
  background:var(--ink-900);
  color:var(--text-invert);
}
.nav-menu[hidden]{display:none}
.nav-menu__link{
  display:flex;
  align-items:center;
  min-height:56px;
  padding:var(--space-1) 0;
  border-bottom:1px solid var(--border-dark);
  color:var(--text-invert);
  font-family:var(--font-display);
  font-weight:var(--fw-display-quiet);
  font-size:1.5rem;
  letter-spacing:var(--tracking-display);
  text-decoration:none;
}
.nav-menu__meta{margin-top:auto;padding-top:var(--space-4);color:var(--slate-300)}
.nav-menu__meta a{color:var(--cyan);text-decoration:underline;text-underline-offset:3px}

/* scroll lock while the overlay is open */
body.has-menu-open{overflow:hidden}

@media (max-width:860px){
  .nav__links{display:none}
  .nav__toggle{display:inline-flex}
}
.nav .btn--sm{white-space:nowrap}

@media (max-width:399px){
  /* Measured: lockup 155 + CTA/hamburger 179 + gutters = 398px of nav content,
     so below 400 they cannot all fit and squeezing the CTA wraps it onto two
     lines. SPEC §3: below the lockup's minimum, use the symbol alone. Clipped
     rather than display:none so the link keeps its accessible name. The full
     lockup returns at 400 and up. */
  .nav{padding-inline:var(--space-2);gap:var(--space-1)}
  .nav .logo__word{
    position:absolute;
    width:1px;height:1px;
    margin:-1px;
    overflow:hidden;
    clip-path:inset(50%);
    white-space:nowrap;
  }
}
@media (min-width:861px){
  /* never leave the overlay mounted across the breakpoint */
  .nav-menu{display:none}
}

/* ============================================================
   7. display/Eyebrow
   ============================================================ */
.eyebrow{
  font-family:var(--font-mono);
  font-size:.8rem;
  font-weight:500;
  letter-spacing:var(--tracking-eyebrow);
  text-transform:uppercase;
  color:var(--accent-ink);
}
.section--dark .eyebrow,.eyebrow--dark{color:var(--mint)}

.mono{
  font-family:var(--font-mono);
  font-size:var(--fs-small);
  letter-spacing:var(--tracking-mono);
}

/* Gradient display fills. On light use the AA-safe ink gradient; on dark
   use --grad-signal (lightest stop #6FD9C0 = 11.7:1, darkest #3B82F6 = 5.4:1
   on --ink-900). --grad-method is reserved for display-size text on ink. */
.grad-text{
  background-image:var(--grad-signal-ink);
  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;
  /* solid fallback for background-clip:text failures (SPEC §2.2) */
  -webkit-text-fill-color:transparent;
}
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  .grad-text{color:var(--teal-ink);-webkit-text-fill-color:var(--teal-ink)}
}
/* On ink, --grad-signal is the only AA-safe text gradient: measured against the
   rendered hero pixels its worst stop (#3B82F6) is 3.60:1, where --grad-method's
   blue end (#2563EB) came in at 2.56:1 — under the 3:1 large-text minimum.
   --grad-method stays a non-text fill (buttons, dividers, panels, scrims). */
.section--dark .grad-text{background-image:var(--grad-signal)}
.hero .grad-text{background-image:var(--grad-hero-word)}

/* ============================================================
   8. display/StatCard + display/Badge
   ============================================================ */
.stats{
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-3);
  justify-content:space-between;
}
.stat{flex:1 1 150px;min-width:140px}
.stat__value{
  font-family:var(--font-mono);
  font-weight:500;
  font-size:clamp(1.6rem,2vw + 1rem,2.4rem);
  line-height:1.1;
  /* A5: the count-up rewrites this text every frame. Tabular figures keep the
     glyphs from jittering, and main.js pins the final width before it starts
     counting so the reflow never reaches the layout (CLS stays 0). */
  font-variant-numeric:tabular-nums;
  background-image:var(--grad-signal-ink);
  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;
  -webkit-text-fill-color:transparent;
}
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  .stat__value{color:var(--teal-ink);-webkit-text-fill-color:var(--teal-ink)}
}
.stat__label{font-size:.9rem;color:var(--text-muted)}

.badges{
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-1);
  margin-top:var(--space-3);
}
.badge{
  display:inline-block;
  padding:8px 12px;
  border:1px solid var(--slate-300);
  border-radius:var(--radius-pill);
  font-family:var(--font-mono);
  font-size:.78rem;
  /* --slate-500 measured 4.45:1 on --canvas-soft; --text-muted is 5.13:1 */
  color:var(--text-muted);
}

/* ============================================================
   9. display/Card + display/Chip
   ============================================================ */
/* SPEC §8 breakpoints: xs <480 · sm 480–767 · md 768–1023 · lg 1024–1439 · xl ≥1440
   Method 2×2→1 · Proof/Outcomes 3→1 · Process 4→1, all collapsing at the
   md/sm boundary (767). Process takes an extra 4→2 step across md, where four
   tracks would leave ~170px cards — the spec fixes the endpoints, not the
   intermediate steps. */
.grid{display:grid;gap:var(--space-3)}
.grid--2{grid-template-columns:repeat(2,minmax(0,1fr))}
.grid--3{grid-template-columns:repeat(3,minmax(0,1fr))}
.grid--4{grid-template-columns:repeat(4,minmax(0,1fr))}

@media (max-width:1023px){
  .grid--4{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:767px){
  .grid--2,.grid--3,.grid--4{grid-template-columns:1fr}
}

.card{
  position:relative;
  isolation:isolate;
  padding:clamp(20px,4vw,28px);
  background:var(--surface-card);
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow-1);
}
/* A6 — cards lift on hover. The resting --shadow-1 stays on the element and
   --shadow-2 fades in on a layer behind it, so the lift is transform-only and
   the shadow swap is opacity-only (§6 master rule). */
.card::after{
  content:"";
  position:absolute;
  inset:0;
  z-index:-1;
  border-radius:inherit;
  box-shadow:var(--shadow-2);
  opacity:0;
  transition:opacity var(--dur) var(--ease-out);
  pointer-events:none;
}
.card{transition:transform var(--dur) var(--ease-out)}
@media (hover: hover){
  .card:hover{transform:translateY(-4px)}
  .card:hover::after{opacity:1}
}
/* proof cards are on ink and drop the shadow entirely (Card.jsx tone="dark") */
.card--dark::after{content:none}
.card--dark{
  background:var(--surface-card-dark);
  border-color:var(--border-dark);
  color:var(--text-invert);
  box-shadow:none;
}
.card__index{
  font-family:var(--font-mono);
  font-size:.85rem;
  color:var(--teal-ink);
}
.card--dark .card__index{color:var(--mint)}
.card__title{margin:var(--space-1) 0 6px;font-size:var(--fs-h3)}
.card__body{color:var(--text-muted)}
.card--dark .card__body{color:var(--text-muted-dark)}

/* proof cards: who it was for, then what actually changed */
.card__client{
  margin-bottom:10px;
  font-family:var(--font-mono);
  font-size:var(--fs-micro);
  letter-spacing:var(--tracking-mono);
  color:var(--mint);
}
/* The result is the whole point of the section, so it gets a rule and full
   text colour rather than the muted body treatment — it should be the line
   the eye lands on after the title. */
.card__result{
  margin-top:14px;
  padding-left:var(--space-2);
  border-left:2px solid var(--mint);
  color:var(--text-invert);
}

.chips{display:flex;flex-wrap:wrap;gap:var(--space-1);margin-top:14px}
.chip{
  padding:6px 10px;
  border:1px solid #E0E9EB;
  border-radius:var(--radius-pill);
  background:var(--canvas-soft);
  font-family:var(--font-mono);
  font-size:var(--fs-micro);
  /* --slate-500 is 4.45:1 on --canvas-soft; --text-muted is 5.13:1 */
  color:var(--text-muted);
}
.card--dark .chip,.chip--dark{
  background:var(--ink-700);
  border-color:var(--border-dark);
  color:var(--slate-300);
}

/* the `// TODO` markers held open until SPEC §13 content lands */
.todo{
  margin-top:12px;
  font-family:var(--font-mono);
  font-size:var(--fs-micro);
  color:var(--teal-ink);
}
.section--dark .todo,.card--dark .todo{color:var(--sky)}

/* ============================================================
   10. navigation/Accordion — native <details>, "flat and fast"
   ============================================================ */
.accordion details{border-bottom:1px solid var(--border);padding:var(--space-2) 0}
.accordion summary{
  display:flex;
  justify-content:space-between;
  align-items:center;
  gap:1rem;
  min-height:var(--hit-min);
  cursor:pointer;
  list-style:none;
  font-family:var(--font-display);
  font-weight:var(--fw-display-quiet);
  font-size:1.12rem;
  line-height:1.3;
  color:var(--text);
}
.accordion summary::-webkit-details-marker{display:none}
.accordion summary::after{
  content:"+";
  flex:none;
  font-size:1.4rem;
  line-height:1;
  color:var(--teal-ink);
}
.accordion details[open] summary::after{content:"\2013"}
.accordion__answer{margin-top:12px;color:var(--text-muted)}

/* ============================================================
   11. display/Divider + footer
   ============================================================ */
.divider{border:none;height:1px;background:var(--border);margin:0}
.divider--accent{height:3px;width:56px;border-radius:2px;background-image:var(--grad-method)}

.site-footer{
  padding-block:var(--space-5);
  background:var(--ink-900);
  color:var(--slate-300);
  font-size:.9rem;
}
.site-footer a{color:var(--cyan);text-decoration:underline;text-underline-offset:3px}
.site-footer a:hover{color:#fff}
/* The brand mark is a lockup, not a text link: no underline, and it keeps its
   own tone. SPEC §3 assigns lockup-horizontal-white to the footer, so the
   generic footer link colour must not win here. */
.site-footer .logo.logo--white{color:#fff;text-decoration:none}

.footer__grid{
  display:grid;
  grid-template-columns:minmax(0,1.4fr) repeat(2,minmax(0,1fr));
  gap:var(--space-4);
  padding-bottom:var(--space-4);
  border-bottom:1px solid var(--border-dark);
}
@media (max-width:767px){
  .footer__grid{grid-template-columns:1fr;gap:var(--space-3)}
}
.footer__tagline{margin-top:var(--space-2);max-width:34ch;color:var(--text-muted-dark)}
.footer__heading{
  margin-bottom:var(--space-1);
  font-family:var(--font-mono);
  font-size:var(--fs-micro);
  font-weight:500;
  letter-spacing:var(--tracking-eyebrow);
  text-transform:uppercase;
  color:var(--mint);
}
/* every footer link is a tap target, so each clears --hit-min on its own */
.footer__list a{
  display:inline-flex;
  align-items:center;
  min-height:var(--hit-min);
  color:var(--slate-300);
  text-decoration:none;
}
.footer__list a:hover,.footer__list a:focus-visible{color:#fff;text-decoration:underline}
.footer__list--contact a{color:var(--cyan)}

.footer__base{
  display:flex;
  flex-wrap:wrap;
  justify-content:space-between;
  gap:var(--space-1) var(--space-3);
  padding-top:var(--space-3);
}
.footer__legal{
  /* --slate-500 is 4.17:1 on --ink-900 — below AA for 14.4px text */
  color:var(--text-muted-dark);
}
