/* ============================================================================
   main.css — DevelopEd component system  ·  Direction: "Systems" (engineered dark)
   ----------------------------------------------------------------------------
   Token-driven (all values from tokens.css). Sectioned + commented. Built on a
   shared class vocabulary so Home AND the not-yet-rebuilt pages read the same
   system.

   MOTION RAILS (do not weaken):
   R1  Reveal never hides content without JS. Hidden-initial states are scoped to
       `html.js` (class set before paint) — no JS ⇒ everything visible.
   R2  All keyframe/entrance motion lives inside
       @media (prefers-reduced-motion: no-preference). Reduced-motion ⇒ static +
       fully visible. Transitions self-neutralize (tokens zero --dur-* there).
   Transform/opacity only; zero layout animation; zero CLS (media boxes reserved).

   Approved above-spec juice: E1 robustness · E2 button press · E3 ticker pause ·
   E4 reduced-data · J1 hero glow-follow (JS) · J2 cap cage draw · J3 wordmark dot
   draw-on · J5 card depth · J9 nav indicator · J10 CTA glow · J12 portrait
   duotone→color · J13 directional reveals · J15 cross-page view transitions.
   ============================================================================ */

/* ============================================================
   0. SELF-HOSTED FONTS (variable woff2, latin + latin-ext)
   Note: decorative glyphs (→ ← ✦ ✓ ☰ ▋ ▚) sit outside the latin subsets and
   render from system fallbacks BY DESIGN — geometric symbols are visually
   stable across platforms and not worth a third subset per family.
   ============================================================ */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/fonts/space-grotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/fonts/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/ibm-plex-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ============================================================
   1. RESET / BASE
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; color-scheme: dark; }              /* E1 */
html, body { overflow-x: hidden; }                                        /* zero h-scroll safety */
body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
}
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: none; }
input, textarea, select { font: inherit; color: inherit; }
img, svg, video { display: block; max-width: 100%; height: auto; }
[hidden] { display: none !important; }
::selection { background: rgba(124,160,255,.30); color: #fff; }

/* Smooth in-page scrolling only when motion is welcome (JS also honors this). */
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
section[id], [data-scroll-target] { scroll-margin-top: 80px; }

/* Focus — visible for keyboard users everywhere. */
:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; border-radius: 2px; }
:focus:not(:focus-visible) { outline: none; }

/* Skip link */
.skip-link {
  position: fixed; left: 12px; top: 12px; z-index: 200;
  background: var(--color-accent); color: var(--color-on-accent);
  font: 600 14px/1 var(--font-sans); padding: 12px 16px; border-radius: var(--radius-sm);
  transform: translateY(-150%); transition: transform var(--dur-fast) var(--ease-out);   /* transform-only */
}
.skip-link:focus { transform: translateY(0); }

/* E1 note: content-visibility:auto was evaluated but dropped — it makes
   getBoundingClientRect() read intrinsic (not real) sizes for skipped subtrees,
   which would misplace the golden-path in-page smooth-scroll (home.js). On a
   short 6-section page the render-perf gain didn't justify that risk. E1's other
   parts (color-scheme:dark, backdrop-filter fallback) are kept. */

/* ============================================================
   2. LAYOUT PRIMITIVES
   ============================================================ */
.container { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: var(--section-y); }
.section--alt {
  background: var(--color-surface);
  border-top: 1px solid var(--color-hair);
  border-bottom: 1px solid var(--color-hair);
}

/* ============================================================
   3. TYPOGRAPHY HELPERS
   ============================================================ */
.eyebrow {
  font: var(--fw-medium) var(--fs-small)/1 var(--font-mono);
  letter-spacing: .02em; color: var(--color-accent);
}
/* Eyebrow links (e.g. "← back to work") get a 44px+ hit area without moving
   the visual baseline: padding grows the target, negative margin refunds it. */
.eyebrow a { color: inherit; display: inline-block; padding-block: 16px; margin-block: -16px; position: relative; }
.eyebrow a:hover { color: var(--color-text); }
.section-title {
  margin-top: 16px;
  font: var(--fw-semibold) var(--fs-h2)/1.08 var(--font-display);
  letter-spacing: -.02em; color: var(--color-text); text-wrap: balance;
}
.lead {
  max-width: 46ch;
  font: var(--fw-regular) var(--fs-body)/var(--lh-body) var(--font-sans);
  color: var(--color-text-muted);
}
.prose { max-width: 68ch; color: var(--color-text-muted); font: var(--fw-regular) var(--fs-body)/1.65 var(--font-sans); }
.prose p + p { margin-top: 1em; }
.prose a { color: var(--color-accent); }
.prose a:hover { color: var(--color-accent-hover); }

/* ============================================================
   4. BUTTONS + LINKS
   ============================================================ */
.btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--touch-min); height: 54px; padding: 0 26px;
  font: var(--fw-semibold) 16px/1 var(--font-sans);
  border-radius: var(--radius-sm); cursor: pointer;
  transition: transform var(--dur-base) var(--ease-out), background var(--dur-base), border-color var(--dur-base);
}
.btn--primary { background: var(--color-accent); color: var(--color-on-accent); }
.btn--primary:hover { background: var(--color-accent-hover); transform: translateY(-2px); }
.btn--primary:active { transform: translateY(0) scale(.99); }                      /* E2 */
/* J10 — CTA glow: static shadow on a pseudo-layer, only OPACITY animates. */
.btn--primary::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: 0 10px 34px rgba(124,160,255,.38), 0 0 0 1px rgba(124,160,255,.35);
  opacity: 0; transition: opacity var(--dur-base) var(--ease-out);
}
.btn--primary:hover::after { opacity: 1; }
.btn--ghost { background: transparent; color: var(--color-text); border: 1px solid var(--color-border); }
.btn--ghost:hover { border-color: var(--color-accent); transform: translateY(-2px); }
.btn--ghost:active { transform: translateY(0) scale(.99); }                        /* E2 */

/* Mono / text link with sliding arrow. The spec animates the flex gap; we get
   the identical slide by transforming the wrapped arrow span instead (layout
   props stay un-animated per the motion rail). */
.link-mono {
  display: inline-flex; align-items: center; gap: 8px; min-height: var(--touch-min);
  font: var(--fw-semibold) 14px/1 var(--font-mono); color: var(--color-accent); cursor: pointer;
}
.link-mono:hover { color: var(--color-accent-hover); }
.link-mono .arrow, .work-card__link .arrow { display: inline-block; transition: transform var(--dur-fast) var(--ease-out); }
.link-mono:hover .arrow, .work-card__link:hover .arrow { transform: translateX(4px); }

/* ============================================================
   5. HEADER + NAV (shared sticky shell)
   ============================================================ */
.site-header {
  position: sticky; top: 0; z-index: var(--z-header);
  border-bottom: 1px solid transparent;
  transition: background var(--dur-base) ease, border-color var(--dur-base) ease;
}
.site-header.is-scrolled { background: color-mix(in oklab, #0B0D11 80%, transparent); border-bottom-color: var(--color-border); }
@supports ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
  .site-header.is-scrolled { -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); }
}
@supports not ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
  .site-header.is-scrolled { background: color-mix(in oklab, #0B0D11 92%, transparent); }   /* E1 fallback */
}
.nav { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding-block: 16px; }

/* Wordmark: signal dot + "DevelopEd" as ONE word, one color. */
.brand {
  display: inline-flex; align-items: center;
  font: var(--fw-semibold) 20px/1 var(--font-display); letter-spacing: -.01em; color: var(--color-text);
}
/* "DevelopEd" is ONE word, one color. No flex gap here, or the "Develop" text
   node and <b>Ed</b> become separate flex items with a space between them. */
.brand b { font-weight: inherit; color: inherit; }
.brand::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%; flex: none; margin-right: 9px;
  background: var(--color-accent); box-shadow: var(--glow-accent);
}
@media (prefers-reduced-motion: no-preference) {
  .brand::before { animation: dotIn .6s var(--ease-out) both; }   /* J3 — one-time draw-on */
}

.nav-links { display: flex; align-items: center; gap: 26px; list-style: none; padding: 0; }
.nav-links > li { display: inline-flex; }
.nav-links a:not(.btn) {
  position: relative;
  font: var(--fw-medium) 14px/1 var(--font-mono); color: var(--color-text-muted);
  /* 44px hit area (14 + 2x15) pulled back to the designed 26px visual row */
  padding-block: 15px; margin-block: -9px; transition: color var(--dur-fast);
}
.nav-links a:not(.btn):hover, .nav-links a[aria-current]:not(.btn) { color: var(--color-text); }
/* J9 — animated accent indicator under mono nav links. */
.nav-links a:not(.btn)::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 8px; height: 1px;
  background: var(--color-accent); transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-base) var(--ease-out);
}
.nav-links a:not(.btn):hover::after, .nav-links a[aria-current]:not(.btn)::after { transform: scaleX(1); }
/* 16px (not the spec's 14px): the button font floor (>=16px) wins. */
.nav-links .btn { height: 44px; padding: 0 18px; font-size: 16px; font-family: var(--font-mono); font-weight: var(--fw-medium); }

/* Hamburger */
.nav-toggle {
  display: none; align-items: center; justify-content: center;
  width: 44px; height: 44px; border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  color: var(--color-text); font-size: 18px; cursor: pointer;
}

/* Mobile nav (the single JS breakpoint, per spec). */
@media (max-width: 820px) {
  .nav-toggle { display: inline-flex; }
  .nav-links {
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--color-surface); border-bottom: 1px solid var(--color-border);
    padding: 12px var(--gutter) 20px;
    opacity: 0; transform: translateY(-10px); pointer-events: none;
    /* visibility keeps the closed menu out of the tab order / accessibility tree;
       the 0s delayed flip lets the fade-out finish before it goes hidden. */
    visibility: hidden;
    transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out),
                visibility 0s linear var(--dur-base);
  }
  .nav-links.is-open {
    opacity: 1; transform: translateY(0); pointer-events: auto; visibility: visible;
    transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out),
                visibility 0s;
  }
  .nav-links > li { display: block; }
  /* margin-block:0 resets the desktop hit-area's negative margins — without it
     the panel links overlap and the hairline dividers ride up over the text. */
  .nav-links a:not(.btn) { display: block; padding: 14px 8px; margin-block: 0; font-size: 16px; color: var(--color-text); border-bottom: 1px solid var(--color-hair); }
  .nav-links a:not(.btn)::after { display: none; }
  .nav-links .btn { margin-top: 12px; width: 100%; height: 52px; font-size: 16px; font-weight: var(--fw-semibold); }
  /* No-JS safety: with no JS the panel must not trap the links off-screen. */
  html:not(.js) .nav-links { position: static; opacity: 1; transform: none; pointer-events: auto; visibility: visible; }
}

/* ============================================================
   6. FOOTER
   ============================================================ */
.site-footer { border-top: 1px solid var(--color-border); padding-block: 44px 40px; }
.site-footer__grid { display: flex; flex-wrap: wrap; gap: 24px; align-items: center; justify-content: space-between; }
.site-footer p { font: var(--fw-regular) 13px/1.5 var(--font-sans); color: var(--color-text-muted); }
/* The build IS the pitch — a quiet, true, technical-buyer line. */
.footer-note { margin-top: 6px; font: var(--fw-medium) 12px/1.5 var(--font-mono); color: var(--color-text-muted); opacity: .8; }
.site-footer nav { display: flex; flex-wrap: wrap; gap: 12px 20px; align-items: center; font: var(--fw-medium) 13px/1 var(--font-mono); color: var(--color-text-muted); }
.site-footer nav a { transition: color var(--dur-fast); min-height: var(--touch-min); display: inline-flex; align-items: center; }
.site-footer nav a:hover { color: var(--color-text); }
/* Footer wordmark variant per spec: 18px text, 8px dot, softer glow. */
.site-footer .brand { font-size: 18px; }
.site-footer .brand::before { width: 8px; height: 8px; box-shadow: 0 0 8px var(--color-accent); }

/* ============================================================
   7. HERO (Home)
   ============================================================ */
.hero { position: relative; overflow: hidden; min-height: clamp(560px, 86vh, 880px); display: flex; align-items: center; }
.hero__inner {
  position: relative; z-index: 1; width: 100%;
  display: flex; flex-wrap: wrap; align-items: center; gap: clamp(32px, 5vw, 64px);
  padding-block: 56px;
}
.hero__col-text { flex: 1.25 1 400px; min-width: 0; }
.hero__col-media { flex: 1 1 300px; min-width: 0; max-width: 420px; }
.hero__eyebrow { font: var(--fw-medium) var(--fs-small)/1 var(--font-mono); letter-spacing: .02em; color: var(--color-accent); }
.hero__title {
  margin-top: 18px;
  font: var(--fw-semibold) var(--fs-hero)/1.04 var(--font-display);
  letter-spacing: var(--tracking-tight); color: var(--color-text); text-wrap: balance;
}
.hero__title .accent { color: var(--color-accent); }
.hero__sub { margin-top: 22px; max-width: 46ch; font: var(--fw-regular) var(--fs-body)/1.6 var(--font-sans); color: var(--color-text-muted); }
.hero__ctas { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; margin-top: 30px; }
.hero__ctas .btn--ghost { padding: 0 24px; }
.hero__status { display: flex; flex-wrap: wrap; gap: 18px; align-items: center; margin-top: 30px; font: var(--fw-medium) var(--fs-small)/1 var(--font-mono); color: var(--color-text-muted); }
.hero__status .sep { opacity: .55; }
.status-live { display: inline-flex; align-items: center; gap: 8px; }
.status-dot { position: relative; width: 8px; height: 8px; border-radius: 50%; background: var(--color-success); display: inline-block; }
/* Pulse ring on a pseudo-element (transform/opacity only — compositor lane),
   visually matching the spec's expanding box-shadow recipe. */
.status-dot::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(91,227,160,.5); opacity: 0;
}
.cursor-blink { color: var(--color-accent); }
.hero__scrollcue {
  position: absolute; left: 50%; bottom: 20px; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  font: var(--fw-medium) 12px/1 var(--font-mono); color: var(--color-text-muted); opacity: .7;
}
.hero__scrollcue .arrow { font-size: 13px; }

/* Ambient layers (decorative, aria-hidden). Parallax targets get will-change. */
.hero__gridbg {
  position: absolute; inset: -60px; pointer-events: none; z-index: 0; will-change: transform;
  background-image: linear-gradient(rgba(255,255,255,.04) 1px, transparent 1px),
                    linear-gradient(90deg, rgba(255,255,255,.04) 1px, transparent 1px);
  background-size: 40px 40px;
  -webkit-mask-image: radial-gradient(ellipse 80% 70% at 62% 32%, #000 30%, transparent 76%);
          mask-image: radial-gradient(ellipse 80% 70% at 62% 32%, #000 30%, transparent 76%);
}
.hero__glow {
  position: absolute; width: 640px; height: 640px; right: -120px; top: -140px;
  pointer-events: none; z-index: 0; will-change: transform, opacity;
  background: radial-gradient(circle, rgba(124,160,255,.20), transparent 62%);
}

/* ============================================================
   8. PORTRAIT — clean framed photo (real color, no filter effect).
   Hero image is pre-cropped to its 4:5 frame exactly. The About page shows
   4:5 images in the 5:6 frame — a deliberate ~4% centered vertical cover-crop
   (subjects are center-framed, no distortion). Hairline frame + caption.
   ============================================================ */
/* Layer-promote only the node home.js actually parallaxes per frame. */
.hero .portrait__frame { will-change: transform; }
.portrait__frame {
  position: relative; overflow: hidden; border-radius: var(--card-radius);
  border: 1px solid var(--color-border); box-shadow: var(--shadow-lg);
  background: var(--color-surface);
}
.portrait--hero .portrait__frame { aspect-ratio: 4 / 5; }
.portrait--about .portrait__frame { aspect-ratio: 5 / 6; box-shadow: var(--shadow-md); }
.portrait__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; }
.portrait__caption {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 16px;
  font: var(--fw-medium) 12px/1.3 var(--font-mono); color: var(--color-text);
  /* scrim strong enough for AA over bright photo bottoms */
  background: linear-gradient(transparent, rgba(11,13,17,.55) 35%, rgba(11,13,17,.88));
  text-shadow: 0 1px 6px rgba(11,13,17,.9);
}

/* ============================================================
   9. TICKER STRIP
   ============================================================ */
.ticker { overflow: hidden; padding-block: 14px; background: var(--color-surface); border-top: 1px solid var(--color-hair); border-bottom: 1px solid var(--color-hair); }
.ticker__track { display: flex; width: max-content; white-space: nowrap; font: var(--fw-medium) 12px/1 var(--font-mono); letter-spacing: .12em; text-transform: uppercase; color: var(--color-text-muted); }
.ticker__group { display: inline-flex; }
.ticker__sep { color: var(--color-accent); margin: 0 22px; }
@media (prefers-reduced-motion: no-preference) {
  /* 102s = the spec's 34s x 3 list-copies per half (keeps the designed px/s pace). */
  .ticker__track { animation: ticker 102s linear infinite; }
  .ticker:hover .ticker__track, .ticker:focus-within .ticker__track { animation-play-state: paused; }   /* E3 */
}
@media (prefers-reduced-data: reduce) { .ticker__track { animation: none; } }                            /* E4 */

/* ============================================================
   10. CAPABILITIES (numbered cage)
   ============================================================ */
.cap-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 44px; }
.cap-head .supporting { margin: 0; max-width: 34ch; font: var(--fw-regular) var(--fs-body)/1.6 var(--font-sans); color: var(--color-text-muted); }
.cap-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); border-top: 1px solid var(--color-border); border-left: 1px solid var(--color-border); }
.cap { position: relative; border-bottom: 1px solid var(--color-border); border-right: 1px solid var(--color-border); padding: 26px 24px; transition: background var(--dur-base); }
.cap:hover { background: var(--color-surface); }
.cap__num { font: var(--fw-medium) 12px/1 var(--font-mono); color: var(--color-accent); }
.cap__name { margin: 14px 0 8px; font: var(--fw-semibold) var(--fs-h3)/1.2 var(--font-display); color: var(--color-text); }
.cap__desc { font: var(--fw-regular) 14px/1.55 var(--font-sans); color: var(--color-text-muted); }
/* Services page: "what it delivers" bullets inside a capability cell. */
.cap__deliv { margin: 14px 0 0; padding: 0 0 0 2px; list-style: none; display: grid; gap: 7px; }
.cap__deliv li { font: var(--fw-medium) 13px/1.5 var(--font-mono); color: var(--color-text-muted); display: flex; gap: 9px; }
.cap__deliv li::before { content: "→"; color: var(--color-accent); flex: none; }
.cap--cta { display: flex; flex-direction: column; justify-content: center; background: color-mix(in oklab, var(--color-accent) 8%, var(--color-bg)); }
.cap--cta .cap__name { margin: 0; }
.cap--cta .link-mono { margin-top: 14px; align-self: flex-start; }
/* J2 — accent left-border draws in on reveal. Drawn by DEFAULT (no-JS and
   reduced-motion users see the final state); hidden only pre-reveal when JS
   and motion are both available (R1/R2). */
.cap::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background: var(--color-accent); transform: scaleY(1); transform-origin: top; }
@media (prefers-reduced-motion: no-preference) {
  .cap::before { transition: transform var(--dur-slow) var(--ease-out); }
  html.js .cap[data-reveal]:not(.is-revealed)::before {
    transform: scaleY(0);
    animation: forceScaleY .6s var(--ease-out) 2.8s forwards;   /* same net as forceReveal */
  }
}

/* ============================================================
   11. WORK CARDS (shared: Home featured + /work grid)
   ============================================================ */
.work-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); gap: 22px; }
.work-card { position: relative; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: var(--card-radius); overflow: hidden; display: flex; flex-direction: column; }
.work-card--live { cursor: pointer; transition: transform var(--dur-base) var(--ease-out), border-color var(--dur-base), box-shadow var(--dur-base); }
.work-card--live:hover, .work-card--live:focus-within { transform: translateY(-4px); border-color: var(--color-accent); box-shadow: 0 24px 50px rgba(0,0,0,.5); }
.work-card--soon { border-style: dashed; }

/* Media / thumbnail */
.work-card__media { position: relative; aspect-ratio: 16 / 10; overflow: hidden; background: linear-gradient(150deg, color-mix(in oklab, var(--color-accent) 26%, var(--color-bg)), var(--color-bg)); display: flex; align-items: center; justify-content: center; color: var(--color-text-muted); font: var(--fw-medium) 12px/1 var(--font-mono); letter-spacing: .08em; }
.work-card__media > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-base) var(--ease-out); }
/* J5 — depth on live-card hover: subtle zoom on the real artwork. */
.work-card--live:hover .work-card__media > img,
.work-card--live:focus-within .work-card__media > img { transform: scale(1.04); }
/* coming-soon thumbnail */
.work-card--soon .work-card__media { background: var(--color-surface-2); }
.work-card--soon .work-card__media::before { content: ""; position: absolute; inset: 0; background: repeating-linear-gradient(135deg, rgba(255,255,255,.03) 0 10px, transparent 10px 20px); }

/* Badges — placed in media (Home) or body (grid). */
/* 12px (not the spec's 10px): the pipeline font floor (nothing <12px) wins over
   the spec's self-contradicting micro-size; letter-spacing keeps the look. */
.badge { display: inline-flex; align-items: center; gap: 7px; font: var(--fw-semibold) 12px/1 var(--font-mono); letter-spacing: .1em; text-transform: uppercase; padding: 6px 10px; border-radius: 6px; }
.badge--shipped { color: var(--color-on-accent); background: var(--color-success); }
.badge--shipped .status-dot { width: 6px; height: 6px; background: var(--color-on-accent); }
.badge--soon { color: var(--color-text-muted); border: 1px solid var(--color-border); }
.work-card__media .badge { position: absolute; top: 12px; right: 12px; }

/* Body */
.work-card__body { padding: 22px 22px 24px; display: flex; flex-direction: column; flex: 1; }
.work-card__body > .badge { align-self: flex-start; margin-bottom: 12px; }
.work-card--soon .work-card__body { opacity: .85; }
.work-card__title { font: var(--fw-semibold) var(--fs-h3)/1.2 var(--font-display); color: var(--color-text); }
.work-card__hook { margin-top: 8px; font: var(--fw-regular) 15px/1.55 var(--font-sans); color: var(--color-text-muted); }
.work-card__meta { margin-top: 8px; font: var(--fw-medium) 13px/1.5 var(--font-mono); color: var(--color-text-muted); }
.work-card__foot { margin-top: auto; padding-top: 20px; }
.work-card__link { display: inline-flex; align-items: center; gap: 8px; min-height: var(--touch-min); font: var(--fw-semibold) 14px/1 var(--font-mono); color: var(--color-accent); }
.work-card__link:hover { color: var(--color-accent-hover); }

/* Chips */
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; list-style: none; padding: 0; }
.chip { font: var(--fw-medium) 12px/1 var(--font-mono); color: var(--color-text-muted); border: 1px solid var(--color-border); padding: 7px 11px; border-radius: var(--radius-full); }
.chip--meta { font-size: 12px; padding: 9px 13px; background: var(--color-surface); }

/* Filter bar (/work) */
.filter-bar { display: flex; flex-wrap: wrap; gap: 8px; }
html:not(.js) .filter-bar { display: none; }
.filter-btn { font: var(--fw-medium) 12px/1 var(--font-mono); color: var(--color-text-muted); border: 1px solid var(--color-border); padding: 9px 13px; border-radius: var(--radius-full); cursor: pointer; transition: color var(--dur-fast), border-color var(--dur-fast); min-height: var(--touch-min); }
.filter-btn:hover { color: var(--color-text); border-color: var(--color-accent); }
.filter-btn[aria-pressed="true"] { background: var(--color-accent); color: var(--color-on-accent); border-color: var(--color-accent); }

/* ============================================================
   12. SERVICES (list) — /services
   ============================================================ */
.service-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); gap: 1px; background: var(--color-border); border: 1px solid var(--color-border); border-radius: var(--card-radius); overflow: hidden; }
.service { background: var(--color-bg); padding: 28px 26px; transition: background var(--dur-base); }
.service:hover { background: var(--color-surface); }
.service h3 { margin: 14px 0 10px; font: var(--fw-semibold) var(--fs-h3)/1.2 var(--font-display); color: var(--color-text); }
.service p { font: var(--fw-regular) 15px/1.6 var(--font-sans); color: var(--color-text-muted); }

/* ============================================================
   13. ABOUT — /about + Home about
   ============================================================ */
.about__grid { display: flex; flex-wrap: wrap; gap: clamp(32px, 5vw, 64px); align-items: center; }
.about__grid > * { flex: 1 1 320px; min-width: 0; }
.about__figure { max-width: 400px; }
.about__figure img { width: 100%; height: auto; border-radius: var(--card-radius); border: 1px solid var(--color-border); box-shadow: var(--shadow-md); }
.pull-quote { margin-top: 24px; padding: 16px 20px; border-left: 2px solid var(--color-accent); font: var(--fw-medium) var(--fs-h3)/1.4 var(--font-display); color: var(--color-text); }
.meta-chips { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 24px; }

/* ============================================================
   14. TESTIMONIALS (honest empty-state; ready for real quotes)
   ============================================================ */
.testimonials:not(:has(.testimonial)) .testimonials__list { display: none; }
.testimonials:has(.testimonial) .testimonials__empty { display: none; }
.testimonials__empty { position: relative; overflow: hidden; border: 1px dashed var(--color-border); border-radius: var(--radius-lg); padding: clamp(28px, 5vw, 48px); background: var(--color-surface); }
.testimonials__empty .glyph { position: absolute; top: -10px; left: 20px; font: var(--fw-bold) 120px/1 var(--font-display); color: var(--color-hair); }
.testimonials__empty .body { position: relative; }
.testimonials__empty .headline { margin-top: 18px; max-width: 48ch; font: var(--fw-medium) var(--fs-h3)/1.5 var(--font-display); color: var(--color-text); }
.testimonials__empty .sub { margin-top: 12px; font: var(--fw-regular) 15px/1.6 var(--font-sans); color: var(--color-text-muted); }
.testimonials__empty .status { margin-top: 20px; display: inline-flex; align-items: center; gap: 8px; font: var(--fw-medium) 12px/1 var(--font-mono); color: var(--color-text-muted); }
.testimonials__empty .status .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--color-text-muted); display: inline-block; }
/* Real testimonial cards — styled NOW so the future append is paste-only:
   drop a .testimonial into .testimonials__list and the empty panel swaps out. */
.testimonials__list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); gap: 22px; }
.testimonial { margin: 0; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--card-radius); padding: 26px 24px; display: flex; flex-direction: column; gap: 16px; }
.testimonial__quote { font: var(--fw-medium) var(--fs-h3)/1.5 var(--font-display); color: var(--color-text); }
.testimonial__by { margin-top: auto; font: var(--fw-medium) 12px/1.5 var(--font-mono); color: var(--color-text-muted); }
.testimonial__by .name { color: var(--color-text); }

/* ============================================================
   15. CONTACT (form + success/error) — Home + /contact
   ============================================================ */
.contact__grid { display: flex; flex-wrap: wrap; gap: clamp(32px, 5vw, 64px); }
.contact__grid > * { flex: 1 1 320px; min-width: 0; }
.contact__alt { margin-top: 26px; display: flex; flex-direction: column; gap: 12px; }
.contact__alt a, .contact__alt div { display: inline-flex; align-items: center; gap: 10px; font: var(--fw-medium) 14px/1 var(--font-mono); color: var(--color-text); }
.contact__alt a { min-height: var(--touch-min); }
.contact__alt a:hover { color: var(--color-accent); }
.contact__alt .arrow { color: var(--color-accent); }

.form { display: grid; gap: 16px; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: clamp(22px, 4vw, 32px); }
/* No-JS: the form can't deliver without fetch() (a bare submit would GET the
   message into the query string and lose it) — show the honest email path. */
html:not(.js) .form { display: none; }
.form-nojs { font: var(--fw-regular) 15px/1.6 var(--font-sans); color: var(--color-text-muted); }
.form-nojs a { color: var(--color-accent); }
html.js .form-nojs { display: none; }
.form__note { margin-top: 4px; font: var(--fw-medium) 13px/1.5 var(--font-mono); color: var(--color-text-muted); }
.field { display: grid; gap: 8px; }
.field label { font: var(--fw-medium) 12px/1 var(--font-mono); color: var(--color-text-muted); }
.field input, .field textarea, .field select { height: 50px; padding: 0 14px; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-sm); color: var(--color-text); font: var(--fw-regular) 16px/1 var(--font-sans); transition: border-color var(--dur-fast); }
.field textarea { height: auto; padding: 12px 14px; line-height: 1.5; resize: vertical; }
.field select { cursor: pointer; }
.field select:invalid, .field select option[value=""] { color: var(--color-text-muted); }
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--color-accent); }
.field label .opt { color: var(--color-text-muted); opacity: .75; text-transform: none; }
.field--error input, .field--error textarea { border-color: var(--color-error); }
.field__error { font: var(--fw-medium) 12px/1.4 var(--font-mono); color: var(--color-error); min-height: 0; }
.form .btn { width: 100%; }
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form__status { font: var(--fw-regular) 14px/1.5 var(--font-sans); padding: 12px 14px; border-radius: var(--radius-sm); }
.form__status.is-ok { color: var(--color-success); border: 1px solid color-mix(in oklab, var(--color-success) 40%, transparent); }
.form__status.is-err { color: var(--color-error); border: 1px solid color-mix(in oklab, var(--color-error) 40%, transparent); }

/* Success panel (Home #contact) */
.form-success { display: none; flex-direction: column; align-items: flex-start; gap: 12px; background: var(--color-bg); border: 1px solid var(--color-success); border-radius: var(--radius-lg); padding: clamp(26px, 4vw, 36px); opacity: 0; transform: translateY(8px); }
@media (prefers-reduced-motion: no-preference) { .form-success { transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out); } }
.form-success.is-visible { display: flex; }
.form-success.is-shown { opacity: 1; transform: none; }
.form-success .check { width: 46px; height: 46px; border-radius: 50%; background: color-mix(in oklab, var(--color-success) 20%, var(--color-bg)); display: flex; align-items: center; justify-content: center; font: var(--fw-semibold) 22px/1 var(--font-sans); color: var(--color-success); }
.form-success .title { font: var(--fw-semibold) var(--fs-h3)/1.2 var(--font-display); color: var(--color-text); }
.form-success .msg { font: var(--fw-regular) 15px/1.55 var(--font-sans); color: var(--color-text-muted); }

/* ============================================================
   16. CASE STUDY — /work/champchase
   ============================================================ */
.case__header { max-width: 68ch; }
.case__title { margin-top: 12px; font: var(--fw-semibold) var(--fs-h2)/1.08 var(--font-display); letter-spacing: -.02em; color: var(--color-text); }
.case__meta { display: flex; flex-wrap: wrap; gap: 10px 22px; margin-top: 20px; font: var(--fw-medium) 13px/1 var(--font-mono); color: var(--color-text-muted); }
.case__figure { margin-top: 40px; border: 1px solid var(--color-border); border-radius: var(--card-radius); background: var(--color-surface); overflow: hidden; }
.media-todo { aspect-ratio: 16 / 9; display: flex; align-items: center; justify-content: center; padding: 24px; text-align: center; font: var(--fw-medium) 13px/1.5 var(--font-mono); color: var(--color-text-muted); background: linear-gradient(150deg, color-mix(in oklab, var(--color-accent) 14%, var(--color-bg)), var(--color-bg)); }
.case__section { max-width: 68ch; margin-top: 44px; }
.case__section h2 { font: var(--fw-semibold) var(--fs-h3)/1.2 var(--font-display); color: var(--color-text); margin-bottom: 12px; }
.prose ul { margin: .6em 0 0; padding-left: 1.2em; }
.prose li { margin-top: .5em; }
.prose li::marker { color: var(--color-accent); }
.prose strong { color: var(--color-text); font-weight: var(--fw-semibold); }
.case__meta .badge { align-self: center; }
/* Case-study "by the numbers" band — hairline cage, real shipped figures. */
.stat-band {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  border-top: 1px solid var(--color-border); border-left: 1px solid var(--color-border);
  margin-top: 44px;
}
.stat { border-bottom: 1px solid var(--color-border); border-right: 1px solid var(--color-border); padding: 22px 20px; display: grid; gap: 6px; }
.stat__num { font: var(--fw-semibold) clamp(1.7rem, 3vw, 2.2rem)/1.05 var(--font-display); color: var(--color-text); font-variant-numeric: tabular-nums; }
.stat__label { font: var(--fw-medium) 12px/1.4 var(--font-mono); color: var(--color-text-muted); }

/* Case-study screenshot gallery (portrait phone captures, 1290x2796 ratio).
   Boxes match the capture ratio exactly so nothing is cropped; hover gives a
   subtle zoom (transform-only) and brightens the caption. */
.case__gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 14px; margin-top: 44px; }
.case__gallery figure { margin: 0; }
.case__gallery picture { display: block; overflow: hidden; border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: var(--color-surface-2); }
.case__gallery img { display: block; width: 100%; aspect-ratio: 1290 / 2796; object-fit: cover; transition: transform var(--dur-base) var(--ease-out); }
.case__gallery figure:hover img { transform: scale(1.03); }
.case__gallery figcaption { margin-top: 8px; text-align: center; font: var(--fw-medium) 12px/1.4 var(--font-mono); color: var(--color-text-muted); transition: color var(--dur-fast); }
.case__gallery figure:hover figcaption { color: var(--color-text); }

/* ============================================================
   17. 404
   ============================================================ */
.notfound { min-height: 60vh; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px; text-align: center; padding-block: var(--section-y); }
.notfound h1 { font: var(--fw-semibold) var(--fs-h2)/1.08 var(--font-display); letter-spacing: -.02em; color: var(--color-text); }
.notfound .lead { margin-inline: auto; }
.notfound p:last-child { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }

/* ============================================================
   18. SCROLL REVEAL (JS-driven; R1/R2 safe)
   Hidden-initial states apply ONLY when JS is present AND motion is welcome,
   and ONLY until .is-revealed lands. Motion uses the `translate` property so
   the `transform` channel stays free for component hovers (card lift, etc.).
   reveal.js strips the reveal hooks (data-reveal attr / .reveal classes) once
   the entrance settles, so component transitions fully return afterwards.
   Safety nets: reveal.js force-reveals everything at 2.6s (per spec), and the
   forceReveal keyframe below shows content even if reveal.js never runs.
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
  html.js [data-reveal],
  html.js .reveal:not(.reveal--stagger) {
    transition: opacity .6s var(--ease-out), translate .6s var(--ease-out);
  }
  html.js [data-reveal]:not(.is-revealed),
  html.js .reveal:not(.reveal--stagger):not(.is-revealed) {
    opacity: 0; translate: 0 22px;
    animation: forceReveal .6s var(--ease-out) 2.8s forwards;
  }
  html.js [data-reveal="left"]:not(.is-revealed)  { translate: -28px 0; }   /* J13 */
  html.js [data-reveal="right"]:not(.is-revealed) { translate: 28px 0; }    /* J13 */

  /* staggered children */
  html.js .reveal--stagger > * {
    transition: opacity .6s var(--ease-out), translate .6s var(--ease-out);
  }
  html.js .reveal--stagger:not(.is-revealed) > * {
    opacity: 0; translate: 0 18px;
    animation: forceReveal .6s var(--ease-out) 2.8s forwards;
  }
  html.js .reveal--stagger.is-revealed > *:nth-child(2) { transition-delay: 70ms; }
  html.js .reveal--stagger.is-revealed > *:nth-child(3) { transition-delay: 140ms; }
  html.js .reveal--stagger.is-revealed > *:nth-child(4) { transition-delay: 210ms; }
  html.js .reveal--stagger.is-revealed > *:nth-child(5) { transition-delay: 280ms; }
  html.js .reveal--stagger.is-revealed > *:nth-child(6) { transition-delay: 350ms; }
  html.js .reveal--stagger.is-revealed > *:nth-child(n+7) { transition-delay: 420ms; }
}
@keyframes forceReveal { to { opacity: 1; translate: 0 0; } }
@keyframes forceScaleY { to { transform: scaleY(1); } }

/* ============================================================
   19. HERO ENTRANCE + AMBIENT KEYFRAMES (R2: motion-welcome only)
   Default (reduced-motion / no CSS-anim support) = fully visible + static.
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
  .hero__eyebrow { animation: riseIn .42s var(--ease-out) both; }
  .hero__title   { animation: riseIn .5s var(--ease-out) both; animation-delay: .05s; }
  .hero__sub     { animation: riseIn .5s var(--ease-out) both; animation-delay: .12s; }
  .hero__ctas    { animation: riseIn .5s var(--ease-out) both; animation-delay: .18s; }
  .hero__status  { animation: riseIn .5s var(--ease-out) both; animation-delay: .24s; }
  .portrait--hero{ animation: riseIn .6s var(--ease-out) both; animation-delay: .1s; }

  .hero__glow    { animation: pulseGlow 7s var(--ease-in-out) infinite; }
  .status-dot::after { animation: pulseRing 2.4s var(--ease-out) infinite; }
  .badge--shipped .status-dot::after { animation-duration: 2.2s; }
  .cursor-blink  { animation: blink 1.1s step-end infinite; }
  .hero__scrollcue { animation: floatY 2.6s var(--ease-in-out) infinite; }
}
@media (prefers-reduced-data: reduce) { .hero__glow { animation: none; } }   /* E4 */

@keyframes riseIn   { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@keyframes dotIn    { from { opacity: 0; transform: scale(0); } 60% { opacity: 1; } to { opacity: 1; transform: scale(1); } }
@keyframes blink    { 50% { opacity: 0; } }
@keyframes pulseGlow{ 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
@keyframes pulseRing { 0% { transform: scale(1); opacity: .9; } 70%, 100% { transform: scale(2.6); opacity: 0; } }
@keyframes floatY   { 0%, 100% { transform: translateX(-50%) translateY(0); } 50% { transform: translateX(-50%) translateY(6px); } }
@keyframes ticker   { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ============================================================
   20. CROSS-PAGE VIEW TRANSITIONS (J15) — enhancement only.
   Zero cost + no animation where unsupported; no layout animation.
   ============================================================ */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }
