/* ============================================================
   The design system, ported verbatim from the prototype.
   Tokens, tiers, components, motion — this file IS the spec;
   Tailwind provides the engine + utilities around it.
   ============================================================ */
/* ============================================================
   PROTOTYPE — Apple-inspired minimal portfolio (build 2026-07-16b)
   Single file. One-page layout (#work/#about/#contact scroll to sections);
   projects are hash-routed deep links (#project/<slug>)
   Notes for Tailwind translation are kept in class naming.

   Case-study frontmatter schema (agreed pre-port; every hand-synced copy of
   these values in PROJECTS/TITLES/CASE_KIND/cards/prev-next maps to a field):
     slug, title, company, role, year, scope, kind (pro|personal), order,
     lede, note, thumb, heroAlt, excerpt (work-card one-liner; distinct from lede).
     Stats and pull quotes stay in the MDX body as components, not frontmatter.

   NOTE (July 2026): the schema sketch above is prototype-era archaeology —
   src/content.config.ts is the living contract (lede/excerpt/thumb/heroAlt
   all retired). Kept for provenance only.

   Known placeholder stubs (deliberate, pending real content):
   - All six projects and their work cards are full placeholders,
     awaiting the real content work (archived drafts in git)
   ============================================================ */

:root {
  /* Tailwind Zinc palette (light mode) */
  --ink: #18181b;          /* zinc-900 — near-black text */
  --ink-secondary: #71717a;/* zinc-500 — secondary gray */
  --ink-tertiary: #a1a1aa; /* zinc-400 — DECORATION ONLY (hairlines, markers); fails AA for text */
  --ink-on-alt: #52525b;   /* zinc-600 — secondary text on --surface-alt cards (zinc-500 misses 4.5:1 there) */
  --ink-faint: #d4d4d8;    /* zinc-300 — decorative only: punctuation, hairlines */
  --surface: #ffffff;      /* white */
  --surface-alt: #f4f4f5;  /* zinc-100 — cards, bands */
  --nav-bg: rgba(24, 24, 27, 0.8); /* zinc-900/80 */
  --line: #e4e4e7;         /* zinc-200 */
  --focus: #0071e3;        /* functional blue: focus states only */
  --radius-lg: 24px;
  --radius-md: 18px;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1); /* true deceleration: entrances land instead of drifting */
  --bar-line: #27272a; /* mode-invariant hairline under the fixed-dark bars (equals dark --line by design) */
  /* the fixed-dark sub-theme (nav, case bar, contact band, lightbox) is mode-invariant: its own ink family */
  --surface-fixed: #18181b;          /* zinc-900 — the fixed-dark surfaces themselves */
  --ink-on-fixed: #f4f4f5;           /* zinc-100 — primary text on fixed-dark */
  --ink-on-fixed-secondary: #a1a1aa; /* zinc-400 — secondary text on fixed-dark (6.9:1) */
  --ink-on-fixed-tertiary: #d4d4d8;  /* zinc-300 — interactive resting state on fixed-dark (11:1): one step brighter than secondary because it invites the hover to --ink-on-fixed */
  --ink-on-fixed-faint: #3f3f46;     /* zinc-700 — decorative only on fixed-dark (hairline underlines); the same value --ink-faint takes in dark mode, so faintness reads equal across surfaces */
  --focus-on-fixed: #2997ff;         /* light-mode --focus fails contrast on zinc-900 */
  --surface-alt-hover: #e4e4e7;      /* zinc-200 — hover shift for alt-surface cards */
  --measure: min(80%, 78ch);         /* the site's prose measure: bio, case prose, stacked tablet lists */
  --space-section-xl: 168px;         /* the contact-band finale rhythm */
  --max: 1024px;      /* Tailwind max-w-5xl (64rem) */
  /* spacing scale (8px base) */
  --space-section: 96px;
  --space-section-lg: 144px;
  --space-page-top: 128px;
}

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

/* scroll-behavior: JS owns all smooth scrolling (tracked scroll + explicit behavior options);
   CSS smooth would hijack the instant two-arg/boolean scroll forms used for restores and page switches */

html {
  color-scheme: light dark; /* declares the page's dual identity: browsers style chrome and UI accordingly */
  /* Root canvas in the chrome color: iOS Safari's bars and rubber-band overscroll take their
     backdrop from here, and the page is bookended dark in both modes (nav above, contact band
     below) - so the bars and overscroll always blend with the adjacent surface. Safari rejects
     dark theme-color values in light appearance, which is why the meta alone is not enough. */
  background: var(--surface-fixed);
}
body {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
  background: var(--surface);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  text-wrap: pretty; /* SITE RULE: no orphaned words. Inherits everywhere; prose avoids short last lines */
}
/* headings and short display text wrap balanced instead: near-equal lines, never a stranded word */
h1, h2, h3, .lede, .closer-title, .closer-sub, .hero-sub, figcaption { text-wrap: balance; }

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }

:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
/* headings the router focuses for screen-reader landings (tabindex=-1) are not user-reachable:
   no ring - otherwise keyboard-initiated navigations (typed URLs, Enter) outline the page title */
[tabindex="-1"]:focus-visible { outline: none; }
.nav :focus-visible,
.case-bar :focus-visible,
.contact-band :focus-visible { outline-color: var(--focus-on-fixed); } /* the fixed-dark surfaces are zinc-900 in both modes */

@media (prefers-reduced-motion: reduce) {
  a.work-card:hover .visual img,
  a.work-card:hover .visual svg { transform: none; } /* remove the zoom rather than snapping it */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }
  .reveal { opacity: 1 !important; transform: none !important; }
}

.wrap {
  max-width: var(--max); margin: 0 auto;
  padding: 0 max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left)); /* notch-safe gutters */
}

.skip-link {
  position: absolute; left: 50%; top: 8px; transform: translateX(-50%);
  z-index: 200;
  padding: 10px 20px;
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font-size: 14px; font-weight: 500;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
  clip-path: inset(100%); /* hidden until focused */
}
.skip-link:focus-visible { clip-path: none; }
#work, #about, #experience, #contact { scroll-margin-top: 56px; } /* section ids equal their hashes (native pre-paint anchor scrolls); jumps land flush under the sticky nav */
::selection { background: var(--ink); color: var(--surface); }
.contact-band ::selection, .case-bar ::selection, .nav ::selection { background: var(--ink-on-fixed); color: var(--surface-fixed); } /* inverted on the fixed-dark surfaces */

/* Apple-style continuous corners (squircles) on containers and media surfaces.
   Pills stay perfectly round: buttons, the segmented control, and the skip link keep
   their circular caps (a squircle flattens a full pill). Progressive: browsers
   without corner-shape keep the standard border-radius everywhere. */
@supports (corner-shape: squircle) {
  .portrait-card, .work-card, .work-card .visual,
  .intro-stat, .cap, .case-hero, .figure .frame, .figure-portrait .frame,
  .case-adj, .lb-stage img { corner-shape: squircle; }
  /* corner-shape is not inherited: the object-hairline overlays (both modes) inherit the radius VALUE
     but would paint as round rings inside squircle frames (nested corners); shape them to match */
  .portrait-card::after,
  .work-card::after,
  .intro-stat::after,
  .cap::after,
  .case-adj::after,
  .case-hero::after,
  .figure .frame::after { corner-shape: squircle; }
}

/* ---------- NAV ---------- */
.nav {
  position: sticky; top: 0; z-index: 100;
  background: var(--nav-bg);
  -webkit-backdrop-filter: saturate(180%) blur(20px); /* prefixed first: the minifier keeps the last of the pair, which must be the standard form */
  backdrop-filter: saturate(180%) blur(20px);
}
.nav::after { /* persistent bottom hairline: one shared stroke treatment across both bars */
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--bar-line); pointer-events: none;
}
.nav-inner {
  max-width: var(--max); margin: 0 auto;
  height: 56px; /* 32px icon + 12px padding above and below */
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left));
}
.nav a {
  color: var(--ink-on-fixed);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
  opacity: 0.7;
  padding: 18px 12px; /* tap target spans the 56px bar */
  transition: opacity 0.2s ease;
}
.nav a:hover { opacity: 0.9; }
.nav a.active { opacity: 1; }
.nav .nav-brand {
  font-size: 15px;
  font-weight: 600;
  opacity: 0.95;
  padding-left: 0;
}
.nav .nav-brand:hover { opacity: 1; }
.nav-links { display: flex; align-items: center; }
.nav-links a:last-child { margin-right: -12px; } /* optical: text edge on the 24px grid */

/* ---------- MOBILE MENU (Apple-style full-screen overlay) ---------- */
.nav-toggle,
.mobile-close {
  /* identical 32px boxes in identical bar positions = zero shift on toggle */
  position: relative;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
/* invisible 12px halo takes the 32px box to a 56px tap target, past Apple's 44px floor */
.nav-toggle::after,
.mobile-close::after { content: ""; position: absolute; inset: -12px; }
.nav-toggle { display: none; color: var(--ink-on-fixed); } /* icon buttons + menu links skip hover states: touch-first surfaces */
/* two bars that morph between hamburger and X */
.icon-bars { position: relative; display: block; width: 18px; height: 16px; }
.icon-bars .bar {
  position: absolute;
  left: 0;
  width: 18px;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  transition: transform 0.3s ease, top 0.3s ease; /* symmetric reversible morph: plain ease is the sanctioned exception */
}
.icon-bars .bar-1 { top: 3px; }
.icon-bars .bar-2 { top: 11px; }
/* menu open: the close button's bars rotate into an X (and back on close) */
.mobile-menu.open .bar-1 { top: 7px; transform: rotate(45deg); }
.mobile-menu.open .bar-2 { top: 7px; transform: rotate(-45deg); }
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--surface);
  padding: 0 max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left));
  overflow-y: auto; /* short landscape viewports can still reach every link */
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.3s ease, visibility 0s linear 0.3s; /* delay keeps it rendered while fading out */
}
.mobile-menu-bar {
  height: 56px; /* mirrors .nav-inner so the close icon lands exactly on the open icon */
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
.mobile-menu.open {
  visibility: visible;
  opacity: 1;
  transition: opacity 0.3s ease;
}
.mobile-close { color: var(--ink); }
.mobile-links { margin-top: 32px; display: grid; }
.mobile-links a {
  font-size: clamp(44px, 13vw, 64px); /* full display scale: four links own the overlay like a hero */
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink);
  padding: 14px 0;
}
/* gentle cascade, Apple-style */
.mobile-menu.open .mobile-links a { animation: linkIn 0.35s var(--ease-out) both; }
.mobile-links a:nth-child(2) { animation-delay: 0.04s; }
.mobile-links a:nth-child(3) { animation-delay: 0.08s; }
.mobile-links a:nth-child(4) { animation-delay: 0.12s; }
@keyframes linkIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- PAGES ---------- */
.page { display: none; }
.page.visible { display: block; animation: pageIn 0.35s var(--ease-out) backwards; } /* same clock as the VT fade: one page transition, two engines */
/* fill-mode note: "both" would retain the animation's transform/opacity forever,
   which creates a backdrop root and silently breaks backdrop-filter (the frosted
   case bar) for everything inside the page. "backwards" releases it on finish. */
@keyframes pageIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- TYPE ---------- */
.hero {
  text-align: center;
  padding: 112px max(24px, env(safe-area-inset-right)) 64px max(24px, env(safe-area-inset-left)); /* tightened so the portrait crests the fold on laptops */
}
.hero h1 {
  font-size: clamp(40px, 6vw, 72px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.12;
}
.hero h1 .h1-line2 { display: block; } /* second line inherits full ink; the break alone carries the hierarchy */
/* authored phone re-break (hero.h1Phone in home.mdx): two break sets live in
   the markup; each tier shows its own <br>s and hides the other's, and every
   hidden break leaves an NBSP "join" span in its place — the headline has no
   soft wrap points, so it renders exactly the authored lines at every width */
.hero h1 .h1-br-phone, .hero h1 .h1-join-phone { display: none; }
.hero .hero-sub {
  margin-top: 24px;
  font-size: 17px;
  color: var(--ink-secondary);
  letter-spacing: -0.01em;
}
.hero .hero-actions {
  margin-top: 40px;
  display: flex; justify-content: center; gap: 16px; flex-wrap: wrap;
}
.hero .hero-actions .btn { min-width: 180px; } /* the pair renders at matching width */
#page-notfound .hero { padding-bottom: var(--space-section-lg); }

.page-title {
  font-size: clamp(36px, 5vw, 64px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.08;
}

/* ---------- CASE LOCAL NAV ----------
   A second row of top chrome: same glass as the nav, holding only the way
   back. On mobile it hides on scroll-down and returns on scroll-up. */
.case-bar {
  position: sticky; top: 56px; z-index: 90; /* rides directly beneath the 56px nav */
  background: var(--nav-bg); /* same zinc-900/80 glass as the nav */
  -webkit-backdrop-filter: saturate(180%) blur(20px); /* prefixed first: the minifier keeps the last of the pair, which must be the standard form */
  backdrop-filter: saturate(180%) blur(20px);
  transition: transform 0.25s var(--ease-out);
}
.case-bar::after { /* same persistent bottom hairline as the nav */
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--bar-line); pointer-events: none;
}
.case-bar.bar-hidden {
  transform: translateY(-100%);
  pointer-events: none;
  visibility: hidden; /* leaves the tab order while off-screen */
  transition: transform 0.25s var(--ease-out), visibility 0s linear 0.25s;
}
.case-bar .wrap {
  height: 48px;
  display: flex; align-items: center; gap: 16px;
}
.case-bar-title {
  margin-left: auto; /* right edge of the bar */
  min-width: 0; /* allow the ellipsis when titles run long on phones */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink-on-fixed-secondary);
}
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.page-head {
  padding: var(--space-page-top) max(24px, env(safe-area-inset-right)) 24px max(24px, env(safe-area-inset-left));
  max-width: var(--max);
  margin: 0 auto;
}
.page-head .eyebrow + .page-title { margin-top: 16px; }
.page-head .lede {
  margin-top: 24px;
  max-width: 672px; /* Tailwind max-w-2xl: deck stays narrower than the wide body */
  font-size: 19px;
  line-height: 1.5;
  color: var(--ink-secondary);
  letter-spacing: -0.01em;
}

.eyebrow {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-secondary);
}


/* ---------- BUTTONS (monochrome) ---------- */
.btn {
  display: inline-block;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.01em;
  border-radius: 999px;
  padding: 13px 28px;
  cursor: pointer;
  border: none;
  transition: transform 0.15s ease, opacity 0.2s ease, background 0.2s ease;
}
.btn-primary { background: var(--ink); color: var(--surface); } /* self-flips in dark mode, like the seg chip */
.btn-primary:hover { background: #3f3f46; } /* DELIBERATE hardcode (zinc-700): a hover half-step between --ink and --surface that no semantic token names; tokenizing a one-off would be noise */
.btn-secondary {
  background: transparent;
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}
.btn-secondary:hover { background: rgba(24, 24, 27, 0.05); } /* 5% here vs 8% on dark surfaces: light-on-dark needs the stronger shift to read */

/* ---------- HERO ENTRANCE (plays when a visit lands on the hero at the top) ---------- */
@keyframes heroIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } } /* same rise as .reveal: one content-entrance distance */
@keyframes heroInFade { from { opacity: 0; } to { opacity: 1; } }
.page.hero-enter.visible { animation: none; } /* the entrance owns the load; no compound rise under pageIn */
.hero-enter .hero h1 { animation: heroIn 0.6s var(--ease-out) 0.05s backwards; }
.hero-enter .hero .hero-sub { animation: heroIn 0.6s var(--ease-out) 0.16s backwards; }
.hero-enter .hero .hero-actions { animation: heroIn 0.6s var(--ease-out) 0.27s backwards; }
.hero-enter .portrait-card { animation: heroInFade 0.6s var(--ease-out) 0.33s backwards; } /* opacity only: the scrub owns this element's geometry */
.hero-enter .portrait-card img { animation: portraitSettle 3.2s var(--ease-out) 0.33s backwards; } /* the photo settles into its frame and keeps breathing ~0.5s after everything else lands: the entrance speaks the work-card hover language (inner-image scale inside a clipped frame, invisible to the scrub) */
@keyframes portraitSettle { from { transform: scale(1.16); } } /* a real zoom-settle: the one theatrical beat the entrance is allowed */
.hero-enter-chrome .nav { animation: heroInFade 0.5s var(--ease-out) backwards; } /* the chrome arrives first and quietly: opacity only, chrome never rises */
.hero-enter > section,
.hero-enter .intro-stats { animation: heroInFade 0.6s var(--ease-out) 0.5s backwards; } /* everything below the hero follows the cascade; container-level opacity never fights the stats' scroll-linked hold */

/* ---------- HERO PORTRAIT ---------- */
.portrait-card {
  margin: 0 auto;
  border-radius: var(--radius-lg);
  overflow: hidden;
  position: relative; /* anchor for the object-hairline overlay (both modes) */
  aspect-ratio: 4 / 3; /* matches the photo's native ratio: full bleed, no crop */
  background: var(--surface-fixed); /* zinc-900 in both modes */
}
.portrait-card svg,
.portrait-card img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---------- INTRO STATS (alt exploration): at-a-glance credibility ---------- */
.intro-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  margin-top: 24px; /* reads as one bento block with the portrait */
}
.intro-stat {
  background: var(--surface-alt);
  border-radius: var(--radius-lg);
  padding: 32px 28px; /* the standard card padding */
  text-align: center;
}
.intro-stat .num {
  font-size: clamp(28px, 3.4vw, 40px);
  font-weight: 700;
  letter-spacing: -0.03em;
}
.intro-stat .label {
  max-width: 24ch; /* forces an even two-line wrap at card widths */
  margin: 6px auto 0;
  font-size: 14px;
  line-height: 1.4;
  color: var(--ink-on-alt);
  letter-spacing: -0.005em;
  text-wrap: balance; /* evens the lines so no word is orphaned */
}

/* ---------- SECTIONS ---------- */
section { padding: var(--space-section) 0; }
/* extra breathing room on the home page */
#page-home section { padding: var(--space-section-lg) 0; }


/* quiet top-of-template back affordance */
.back-link {
  align-self: stretch; /* full 48px hit target */
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-on-fixed-tertiary); /* brighter than the static .case-bar-title (secondary): the link is interactive, the title is a label */
  transition: color 0.2s ease;
}
.back-link .chev {
  width: 7px; height: 7px; flex: none;
  border-left: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform 0.25s var(--ease-out);
}
.back-link:hover { color: var(--ink-on-fixed); }
.back-link:hover .chev { transform: translateX(-3px) rotate(45deg); } /* motion lives in the glyph */

/* small utility patterns (named for the Tailwind port) */
.row-between { display: flex; justify-content: space-between; align-items: center; gap: 16px 24px; flex-wrap: wrap; }
.exp-resume-mobile { display: none; } /* the section-closing resume link exists for the mobile layout only */
.section-intro { margin-bottom: 40px; }

/* ---------- WORK GRID ---------- */
.work-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 24px;
}
.work-card {
  background: var(--surface-alt);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
a.work-card { transition: background 0.3s ease, transform 0.15s ease; }
.work-card .visual { overflow: hidden; position: relative; } /* clipped zoom; anchor for the object-hairline overlay (both modes) */
.work-card .visual svg,
.work-card .visual img { transition: transform 0.45s var(--ease-out); }
a.work-card:hover .visual svg,
a.work-card:hover .visual img { transform: scale(1.04); } /* confident restraint: a nudge, not a crop */
a.work-card:hover { background: var(--surface-alt-hover); } /* same shift as the prev/next cards */
/* THE PRESS SCALE: 0.985 on every clickable card/button (here, .case-adj, .lb-btn).
   It rides plain `ease` via each element's transition — the sanctioned exception:
   a symmetric reversible morph, not an entrance (see the motion doctrine above). */
a.work-card:active { transform: scale(0.985); }
.btn:active { transform: scale(0.985); }
/* Toast: one transient confirmation chip, anchored above the button that was clicked
   (JS sets left/bottom from the button's rect). role=status makes it the live region -
   screen readers hear what sighted users see */
.toast {
  position: fixed;
  transform: translate(-50%, 6px); /* rises away from the button into place */
  z-index: 200;
  padding: 12px 20px;
  border-radius: 999px; /* pill: perfectly round caps, per the squircle policy */
  background: var(--ink-on-fixed); /* inverse chip: light on the dark band, echoing the primary button it confirms */
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  color: var(--surface-fixed);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none; /* purely informational: never blocks what's under it */
  transition: opacity 0.25s ease, transform 0.25s var(--ease-out);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
.work-card .visual { aspect-ratio: 16 / 10; border-radius: var(--radius-lg) var(--radius-lg) 0 0; } /* radius matches the card so the object hairline follows the corners. Ratio DECIDED July 2026: cards stay 16:10 (galleries are 4:3); cards take custom art via card.image */
.work-card .visual svg,
.work-card .visual img { width: 100%; height: 100%; object-fit: cover; display: block; }
.work-card .meta { padding: 24px 28px 32px; }
.work-card h3 {
  font-size: 21px; font-weight: 600; letter-spacing: -0.015em; /* card-title tier: between body 17/19 and section 22-28 */
}
.work-card .card-co {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-on-alt);
  margin-bottom: 6px;
}
.work-card p {
  margin-top: 6px;
  font-size: 15px; line-height: 1.5;
  color: var(--ink-on-alt);
}

.reveal.instant { transition: none; } /* elements in the first viewport skip the entrance */

/* ---------- VIEW TRANSITIONS (progressive; falls back to pageIn) ---------- */
.vt .page.visible { animation: none; } /* the transition handles the entrance */
html[data-entrance] .page.visible { animation: none; } /* PORT ADAPTATION: the head gate decided the hero entrance owns this load; pageIn stands down before the bundle stamps .hero-enter */
/* PORT ADAPTATION: real pages now - cross-document view transitions give the
   same fade the SPA router drew, with zero router. Browsers without support
   fall back to pageIn. Reduced motion never opts in. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
/* chrome is exempt: the nav and case bar get their own snapshot groups so the
   root crossfade animates page content only */
.nav { view-transition-name: site-nav; }
.case-bar { view-transition-name: case-bar; }
/* the old page holds fully opaque underneath while the new one fades in over
   it — fading both at once dips combined alpha below 1 and flashes the canvas */
::view-transition-old(root) { animation: none; }
::view-transition-new(root) { animation: vtIn 0.35s var(--ease-out) both; }
@keyframes vtIn { from { opacity: 0; transform: translateY(8px); } }
/* prev/next handoffs slide from their direction of travel */
html[data-vt-dir="next"]::view-transition-new(root) { animation-name: vtInNext; }
html[data-vt-dir="prev"]::view-transition-new(root) { animation-name: vtInPrev; }
@keyframes vtInNext { from { opacity: 0; transform: translateX(24px); } }
@keyframes vtInPrev { from { opacity: 0; transform: translateX(-24px); } }

/* ---------- SEGMENTED CONTROL (work filter) ---------- */
.seg {
  position: relative;
  display: inline-grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr; /* equal segments, so the chip can slide by its own width */
  background: var(--surface-alt);
  border-radius: 999px;
  padding: 4px;
}
.seg::before {
  content: "";
  position: absolute;
  top: 4px; bottom: 4px; left: 4px;
  width: calc(50% - 4px);
  border-radius: 999px;
  background: var(--ink); /* the chip takes the primary treatment, like btn-primary */
  transition: transform 0.25s var(--ease-out);
}
.seg[data-active="personal"]::before { transform: translateX(100%); }
.seg button {
  position: relative; /* rides above the chip */
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink-on-alt);
  padding: 12px 20px; /* ~41px segments + 4px track padding: 44px+ touch floor */
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.25s var(--ease-out); /* same clock as the chip */
}
.seg button:hover { color: var(--ink); } /* segments share one track: the chip moves, segments do not press individually */
.seg button[aria-pressed="true"] { color: var(--surface); } /* inverse label on the primary chip, both modes */
.work-card.card-hidden { display: none; }

/* ---------- REVEAL CHOREOGRAPHY ----------
   Content arrives in reading order, not as a block. Delays are additive
   to the shared .reveal transition; prefers-reduced-motion disables all. */
.work-grid .work-card:nth-child(even) { transition-delay: 0.08s; }
.cap-grid .cap:nth-child(2) { transition-delay: 0.06s; }
.cap-grid .cap:nth-child(3) { transition-delay: 0.12s; }
.intro-stats .intro-stat:nth-child(2) { transition-delay: 0.06s; }
.intro-stats .intro-stat:nth-child(3) { transition-delay: 0.12s; }

/* ---------- ABOUT ---------- */
.about-solo { max-width: var(--measure); } /* flush left; capped so the measure holds across screen sizes */
.bio-band { background: var(--surface-alt); } /* full-bleed zinc: the page's mid-scroll beat (elevated zinc-900 in dark mode) */
.bio-band .eyebrow { margin-bottom: 20px; color: var(--ink-on-alt); }
.about-solo .about-h2 {
  /* matches the hero/contact display family, one step below the hero */
  font-size: clamp(32px, 4.5vw, 56px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin-top: 0; /* the band's padding owns the offset; was an inline style */
}
.prose p {
  font-size: 17px; line-height: 1.65;
  color: var(--ink);
  letter-spacing: -0.005em;
}
.prose p + p { margin-top: 20px; }
.prose p.muted { color: var(--ink-secondary); }
/* PORT ADAPTATION: the bio renders from markdown (no per-paragraph classes).
   Structure carries the voicing instead: first paragraph spacing under the
   header, and paragraphs three onward go muted - same result as the
   prototype's hand-classed markup. */
.about-solo .prose h2 + p { margin-top: 20px; }
.about-solo .prose p:nth-of-type(n + 3) { color: var(--ink-secondary); }

/* about page section scaffolding */
.about-section { margin-top: var(--space-page-top); } /* section gap tracks the page-top rhythm per tier */
.about-story .about-section:first-child { margin-top: 0; } /* the section's own padding covers it */
.about-h2 {
  /* DUAL-RANK CLASS: the section h2 tier everywhere it appears — home sections,
     case Overview/Impact/Coverage headlines, and the next/prev card titles all
     share this one ramp on purpose (one voice for every second-level heading).
     Rename would touch every surface; the "about-" prefix is historical. */
  font-size: clamp(22px, 2.6vw, 28px);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-top: 8px;
}

/* Company-led list: one date model (dates belong to roles), one structure at every
   breakpoint. Right-aligned tabular dates on a hairline list - the newsroom grammar. */
.exp-list { border-top: 1px solid var(--line); margin-top: 12px; }
.exp-row {
  display: grid;
  grid-template-columns: 220px 1fr; /* company | roles (dates hold the right edge within) */
  column-gap: 32px;
  align-items: baseline;
  padding: 28px 0;
  border-bottom: 1px solid var(--line);
}
.exp-row .company { font-size: clamp(22px, 2.6vw, 28px); font-weight: 700; letter-spacing: -0.02em; } /* same rank as the section header */
.exp-row .roles {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 16px;
}
.exp-row .roles li {
  display: grid;
  grid-template-columns: 1fr auto; /* title wraps, date holds the right edge */
  column-gap: 24px;
  align-items: baseline;
}
.role-title { font-size: 15px; font-weight: 500; color: var(--ink); letter-spacing: -0.005em; }
.role-team { font-size: 14px; color: var(--ink-secondary); margin-top: 2px; grid-column: 1; }
.role-date {
  font-size: 15px; /* same size as the role it sits beside; secondary color keeps it metadata */
  font-weight: 500;
  color: var(--ink-secondary);
  font-variant-numeric: tabular-nums; /* aligned figures down the right edge */
  white-space: nowrap;
}

.cap-grid-plain { gap: 48px 32px; }
.cap-grid-plain .cap { background: none; border-radius: 0; padding: 0; max-width: var(--measure); } /* principles are type, not inventory; prose takes the bio's measure */
.cap-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}
.cap {
  background: var(--surface-alt);
  border-radius: var(--radius-md);
  padding: 28px;
}
.cap h3 { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }
.cap ul {
  list-style: disc;
  margin-top: 16px;
  padding-top: 14px;
  padding-left: 18px;
  border-top: 1px solid var(--line);
  display: grid;
  gap: 6px;
}
.cap li::marker { color: var(--ink-tertiary); }
.cap li { font-size: 14px; line-height: 1.5; color: var(--ink-on-alt); }
.cap p { margin-top: 8px; font-size: 14px; line-height: 1.55; color: var(--ink-on-alt); }

/* ---------- FAQ (native details/summary, hairline-list grammar) ---------- */
.faq-list { border-top: 1px solid var(--line); }
.faq { border-bottom: 1px solid var(--line); }
.faq summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  padding: 24px 0;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover .faq-icon::before,
.faq summary:hover .faq-icon::after { background: var(--ink); }
.faq-icon { position: relative; flex: none; width: 14px; height: 14px; }
.faq-icon::before,
.faq-icon::after {
  content: "";
  position: absolute;
  background: var(--ink-secondary);
  border-radius: 1px;
  transition: transform 0.3s ease, background 0.2s ease; /* symmetric reversible morph: plain ease is the sanctioned exception */
}
.faq-icon::before { top: 6px; left: 0; width: 14px; height: 2px; }
.faq-icon::after { top: 0; left: 6px; width: 2px; height: 14px; }
/* plus collapses to minus when open */
.faq[open] .faq-icon::after { transform: scaleY(0); }
.faq-a {
  max-width: var(--measure); /* same measure as the bio at every tier */
  padding: 0 40px 22px 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-secondary);
}
/* smooth expand where supported (progressive enhancement) */
@supports (interpolate-size: allow-keywords) {
  .faq { interpolate-size: allow-keywords; } /* scoped: inherited by ::details-content only */
  .faq::details-content {
    block-size: 0;
    overflow: clip;
    transition: block-size 0.3s ease, content-visibility 0.3s allow-discrete;
  }
  .faq[open]::details-content { block-size: auto; }
  /* the answer settles into place rather than being un-clipped */
  .faq .faq-a { opacity: 0; transform: translateY(4px); transition: opacity 0.3s ease 0.08s, transform 0.3s var(--ease-out) 0.08s; }
  .faq[open] .faq-a { opacity: 1; transform: none; }
}

/* ---------- CONTACT BAND ----------
   Fixed zinc-900 in both modes (answers the nav bar). One action set:
   copy email + LinkedIn + resume. The address stays out of the markup. */
.contact-band {
  background: var(--surface-fixed);
  text-align: center;
  padding: var(--space-section-xl) 0 calc(28px + env(safe-area-inset-bottom, 0px)); /* sides belong to the inner .wrap: one gutter, not two */
  display: flex;
  flex-direction: column;
  justify-content: center; /* lockup centers if the band grows; no-op at natural height */
}
#page-home .contact-band { padding: var(--space-section-xl) 0 calc(28px + env(safe-area-inset-bottom, 0px)); } /* beats the #page-home section default so home and case pages close identically */
.closer-title {
  font-size: clamp(36px, 5vw, 64px); /* one step under the hero; matches the case-page h1 rank */
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink-on-fixed);
}
.closer-sub {
  margin-top: 24px; /* matches the hero's headline-to-sub rhythm */
  font-size: 17px;
  letter-spacing: -0.01em;
  color: var(--ink-on-fixed-secondary); /* zinc-400 on the band */
}
.closer-actions {
  margin-top: 40px;
  display: flex; justify-content: center; gap: 16px; flex-wrap: wrap;
}
.closer-actions .btn { min-width: 180px; } /* matched widths, like the hero pair */
.contact-band .btn-primary {
  background: var(--ink-on-fixed);
  color: var(--surface-fixed);
}
.contact-band .btn-primary:hover { background: #d4d4d8; } /* DELIBERATE hardcode (zinc-300): hover dim of the inverse chip; same one-off rationale as .btn-primary:hover */
.contact-band .btn-secondary {
  color: var(--ink-on-fixed);
  box-shadow: inset 0 0 0 1px #71717a; /* DELIBERATE hardcode (zinc-500): ring clears 3:1 on the zinc-900 band; --ink-on-fixed-secondary is a text token, not a border token */
}
.contact-band .btn-secondary:hover { background: rgba(244, 244, 245, 0.08); }
.contact-band .eyebrow { color: var(--ink-on-fixed-secondary); margin-bottom: 20px; }
/* the finale arrives as a sequence: eyebrow, then title, sub, actions */
.contact-band .closer-title { transition-delay: 0.06s; }
.contact-band .closer-sub { transition-delay: 0.12s; }
.contact-band .closer-actions { transition-delay: 0.18s; }
.closer-legal {
  margin-top: auto; /* pins to the band's bottom edge when the band grows */
  padding: var(--space-section-xl) max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left)); /* top value is the minimum gap, mirroring the band's top padding */
  font-size: 13px;
  color: var(--ink-on-fixed-secondary); /* zinc-400: 6.9:1 on the band */
}


/* ---------- CASE OUTRO ----------
   Case studies close exactly like the one-pager: the shared dark contact band.
   Hidden on home (which has its own) and on the 404. */
/* sticky-footer behavior is part of the band component: the body is a
   full-viewport column and the shared band absorbs any leftover height, so
   short pages (the 404 today) never show raw canvas beneath it. On pages
   whose content already fills the viewport this is a no-op. */
body {
  display: flex;
  flex-direction: column;
  min-height: 100svh;
}
/* PORT ADAPTATION: the band lives inside each page's <main>. CASCADE TRAP,
   learned the hard way: `.page.visible { display: block }` (two classes)
   outspecifies `main.page` (class + tag), so a bare main.page display:flex
   silently loses and the band never absorbs leftover height. The short
   pages that need it opt in BY ID (ids outrank both); tall pages stay
   block flow so their margin behavior is untouched. The colophon's band
   absorbs its leftover; the 404 has no band (nav-only chrome) — its hero
   takes the whole column and centers the lockup. */
main.page { flex: 1; } /* the flex-ITEM half always applies: main fills the body column */
#page-notfound, #page-colophon { display: flex; flex-direction: column; }
/* flex-column gotcha: in block flow, margin:0 auto + max-width = a full-
   width centered rail; as a flex ITEM, auto cross-axis margins instead
   shrink-wrap and center the BOX (the colophon header rendered centered).
   Explicit width restores the rail. */
#page-colophon .page-head, #page-colophon .colophon-body { width: 100%; }
#page-colophon > .contact-band { flex: 1; } /* absorbs the leftover: its justify-content centers the lockup and .closer-legal's margin-top:auto pins the legal line to the true bottom */

/* ---------- CASE STUDY (project template) ---------- */
.case-note {
  margin-top: 16px;
  max-width: 576px; /* Tailwind max-w-xl */
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-secondary);
}
.case-meta {
  display: grid;
  grid-template-columns: 1fr 1fr 2fr; /* Role · Year · Scope; scope takes half the band so the tag pills flow instead of stacking */
  gap: 24px;
  margin-top: 56px;
  padding: 28px 0; /* symmetric hairlines: the meta reads as a banded strip */
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.meta-label { /* the same overline as .eyebrow: one overline style, site-wide */
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-secondary);
}
.case-meta dd { margin-top: 6px; font-size: 15px; font-weight: 500; }
/* Scope tags: small pills in the meta band. System colors only — the
   alt surface in both modes; pills stay perfectly round per the squircle policy. */
.meta-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.meta-tag {
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  color: var(--ink);
  background: var(--surface-alt);
  border-radius: 999px;
  padding: 6px 12px;
}
/* .case-hero: the first board frame, styled like every frame; the class is now purely presentational */
.case-hero {
  border-radius: var(--radius-lg);
  overflow: hidden;
  position: relative; /* anchor for the object-hairline overlay (both modes) */
  aspect-ratio: 16 / 9;
  background: var(--surface-alt);
}
.case-hero svg,
.case-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
.case-body {
  max-width: var(--max);
  margin: 0 auto;
  padding: 72px max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left)); /* matches the intra-body h2 rhythm */
}
.case-body h2,
.case-body p { max-width: var(--measure); } /* flush left; capped so the measure holds across screen sizes */
.case-body h2 {
  font-size: clamp(22px, 2.6vw, 28px); /* matches the home section headers */
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-top: 72px;
}
.case-body > :first-child { margin-top: 0; }
.case-body p {
  margin-top: 20px;
  font-size: 17px;
  line-height: 1.65; /* same voice as the bio prose; the 78ch cap carries the readability */
  color: var(--ink);
  letter-spacing: -0.005em;
}
.case-body p.muted { color: var(--ink-secondary); }
/* prose elements real case studies will introduce (none in the DOM yet):
   the global reset strips underlines and list styles, so style them here.
   Links speak THE TEXT LINK grammar (one dialect site-wide; the shared
   rules live in the text-link section and enumerate .case-body a). */
.case-body ul, .case-body ol { max-width: var(--measure); margin-top: 20px; padding-left: 22px; }
.case-body ul { list-style: disc; }
.case-body ol { list-style: decimal; }
.case-body li { font-size: 17px; line-height: 1.65; color: var(--ink); margin-top: 8px; }
.case-body li::marker { color: var(--ink-tertiary); }
.case-body h3 {
  max-width: var(--measure);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-top: 40px;
}
.case-body blockquote {
  max-width: var(--measure);
  margin-top: 24px;
  padding-left: 20px;
  border-left: 2px solid var(--line);
  font-size: 19px;
  line-height: 1.6;
  color: var(--ink-secondary);
}
.figure {
  max-width: var(--max); /* media sits on the same rail as the text */
  margin: 72px auto 0;
  padding: 0 max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left));
}
.figure .frame {
  border-radius: var(--radius-lg);
  overflow: hidden;
  position: relative; /* anchor for the object-hairline overlay (both modes) */
  aspect-ratio: 4 / 3; /* DECIDED July 2026: all gallery frames are 4:3 (full and duo) after an on-device trial; was 16 / 9 */
  background: var(--surface-alt);
}
.figure .frame svg,
.figure .frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* figure variant library (no DOM instances by design — the placeholder templates
   stay uniform; reach for these during the content work): */
.figure-auto .frame { aspect-ratio: auto; } /* uncropped: wordmarks, specimens, full screens */
.figure-auto .frame img, .figure-auto .frame svg { object-fit: contain; height: auto; }
.figure-full { max-width: none; padding: 0; }
.figure-full .frame { border-radius: 0; }
.figure-full figcaption { max-width: var(--max); margin-left: auto; margin-right: auto; padding: 0 24px; } /* caption keeps the gutter */
.figure-portrait .frame { aspect-ratio: 3 / 4; max-width: 480px; margin: 0 auto; }
.figure figcaption {
  margin-top: 14px;
  text-align: center; /* plate captions: centered under the artwork, museum-label style (decided July 2026) */
  font-size: 13px;
  color: var(--ink-secondary);
}
/* Simplified case template: summary up top, then an image board. The story lives
   in two statements and the captions; the artifacts carry the page.
   THE CASE RHYTHM (the spacing scale below is a system, not accidents):
   72 prose step (head->summary) / 48 sibling step (summary->stats, tablet grid gap)
   / 64 board step (gallery: one step tighter, images carry their own whitespace).
   Tablet compresses the set to 48/40/40. Change them together or not at all. */
.case-summary {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  max-width: var(--max);
  margin: 0 auto;
  padding: 72px max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left));
}
.case-summary p:not(.eyebrow) { /* the eyebrow is a <p> too: it keeps its overline style */
  margin-top: 16px;
  font-size: 17px;
  line-height: 1.65;
  letter-spacing: -0.005em;
  color: var(--ink-secondary); /* headline carries the message in ink; prose supports in gray */
}
/* Impact stats: the home hero's stat cards, verbatim (.intro-stats/.intro-stat
   carry the grid, squircles, dark mode, and phone stacking already) — this band
   just gives them the case pages' rail and rhythm. */
.case-stats-band {
  max-width: var(--max);
  margin: 48px auto 0;
  padding: 0 max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left));
}
.case-stats-band .intro-stats { margin-top: 0; } /* the home value spaces it under the portrait; the band spaces itself */
/* Coverage links: third-party evidence as ring-pill chips */
.case-links { margin-top: 28px; }
.case-links ul { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
/* Coverage chips: the ring-pill dialect means interactive (cf. btn-secondary);
   the filled pill stays reserved for passive tags (cf. meta-tag). */
.case-links a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.35; /* labels are sentences and may wrap inside the chip at tablet widths */
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line); /* the site hairline, same as every other edge */
  border-radius: 999px;
  padding: 7px 14px;
  text-decoration: none;
  transition: box-shadow 0.2s ease; /* ring color only: plain ease per the motion policy */
}
.case-links a svg { flex: none; color: var(--ink-secondary); } /* the external-hop arrow: stroked SVG per the site's icon convention (crisp and identical on every platform, unlike the ↗ glyph) */
.case-links a:hover { background: rgba(24, 24, 27, 0.05); box-shadow: inset 0 0 0 1px var(--ink-secondary); } /* the secondary button's hover tint + ring shift: chips are mini secondaries, never primaries */
@media (prefers-color-scheme: dark) {
  .case-links a:hover { background: rgba(244, 244, 245, 0.08); } /* light-on-dark needs the stronger shift, per the contact band's own comment */
}

/* Carousel frames: stacked pictures auto-crossfading on a 6s loop (project.js).
   Pure opacity, so plain ease per the motion policy; reduced-motion never starts
   the loop and the global clamp kills the fade — frame one, static. */
.frame-carousel .car-frame {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.8s ease;
}
.frame-carousel .car-frame.is-active { opacity: 1; }

/* Image board: tight rhythm so the frames read as one bento composition */
.case-gallery { margin-top: 64px; } /* one step tighter than the 72px prose rhythm: images carry their own whitespace */
.case-gallery .figure { margin-top: 32px; }
.case-gallery > :first-child { margin-top: 0; }
.figure-duo {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  max-width: var(--max);
  margin: 32px auto 0;
  padding: 0 max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left));
  align-items: start;
}
.figure-duo .figure { margin: 0; padding: 0; max-width: none; }
.figure-duo .frame { aspect-ratio: 4 / 3; }
.case-next {
  display: block;
  max-width: var(--max);
  margin: var(--space-section) auto 0; /* compresses per tier with every other section */
  padding: 0 max(24px, env(safe-area-inset-right)) 0 max(24px, env(safe-area-inset-left));
}
.case-next .wrap-inner {
  border-top: 1px solid var(--line); /* hairline sits on the text edge, inside the gutters */
  padding: 48px 0 var(--space-section);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
}
.case-adj .eyebrow { color: var(--ink-on-alt); }
.case-adj {
  display: flex;
  background: var(--surface-alt);
  border-radius: var(--radius-lg);
  padding: 32px;
  transition: background 0.2s ease, transform 0.15s ease;
}
.case-adj:hover { background: var(--surface-alt-hover); }
.case-adj:active { transform: scale(0.985); }
.case-adj-next { text-align: right; order: 1; } /* DOM leads with Next (stacked a11y order); desktop restores the prev-left convention */
.adj-text { flex: 1; } /* span the card: without this the text block hugs the left edge and text-align fights it */
.adj-text, .adj-text > span { display: block; } /* spans stack as lines; was inline styles on every instance */

/* ---------- LIGHTBOX (case galleries): fixed dark chrome in both modes ---------- */
.case-gallery .frame { cursor: zoom-in; }
.case-gallery .frame img { transition: transform 0.45s var(--ease-out); }
.case-gallery .frame:hover img { transform: scale(1.04); } /* the work-card nudge: signals the lightbox */
.lightbox {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  max-height: none;
  border: none;
  padding: 0;
  background: transparent;
  color: var(--ink-on-fixed);
}
.lightbox[open] { display: flex; flex-direction: column; }
.lightbox::backdrop {
  background: rgba(9, 9, 11, 0.82); /* zinc-950 glass: one layer deeper than the page canvas */
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px); /* same material recipe as the nav; standard form last so the minifier keeps it */
}
.lb-stage {
  flex: 1;
  min-height: 0; /* flex child must be allowed to shrink so the image sizes to the leftover */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 56px max(56px, env(safe-area-inset-right)) 0 max(56px, env(safe-area-inset-left));
}
.lb-media {
  /* the layer stack: fills the stage; carousel frames crossfade here */
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-height: 0;
}
.lb-media picture {
  /* size-transparent wrappers: fill the box and center, so the img's
     percentage caps still measure the stage, not content */
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-height: 0;
}
.lb-media .lb-layer { transition: opacity 1.8s ease; } /* same dissolve as the gallery plates */
.lb-media .lb-layer:not(.is-active) { opacity: 0; position: absolute; inset: 0; }
.lb-stage img {
  max-width: min(100%, 90vw);
  max-height: min(100%, 80dvh); /* the hard rule: the artwork never exceeds the viewport; ~10% breathing room each side */
  width: auto;
  height: auto; /* contain: never crop inside the lightbox */
  border-radius: var(--radius-md); /* one tier under the page frames: this is the artwork itself, not a card */
}
.lb-foot {
  display: flex;
  align-items: center;
  gap: 24px;
  max-width: var(--max);
  width: 100%;
  margin: 0 auto;
  padding: 20px max(24px, env(safe-area-inset-right)) max(24px, calc(16px + env(safe-area-inset-bottom, 0px))) max(24px, env(safe-area-inset-left));
}
.lb-caption { flex: 1; font-size: 13px; line-height: 1.5; color: var(--ink-on-fixed-secondary); }
.lb-count { font-size: 13px; color: var(--ink-on-fixed-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; } /* zinc-500 failed contrast on the scrim */
.lb-controls { display: flex; align-items: center; gap: 12px; }
.lb-btn {
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 999px; /* pill: stays perfectly round per the squircle policy */
  background: rgba(63, 63, 70, 0.6); /* zinc-700 glass */
  color: var(--ink-on-fixed);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background 0.2s ease, opacity 0.2s ease, transform 0.15s ease;
}
.lb-btn:hover { background: rgba(82, 82, 91, 0.75); } /* zinc-600 */
.lb-btn:active { transform: scale(0.985); }
/* (no :disabled styles: the ring wraps, so the arrows never disable) */
.lb-close { position: absolute; top: max(20px, env(safe-area-inset-top)); right: max(20px, env(safe-area-inset-right)); z-index: 1; }
body.lb-open { overflow: hidden; }
@media (prefers-reduced-motion: no-preference) {
  .lightbox[open] { animation: lbIn 0.35s var(--ease-out); }
  .lightbox[open]::backdrop { animation: lbFade 0.35s ease; }
}
@keyframes lbIn { from { opacity: 0; transform: scale(0.985); } }
@keyframes lbFade { from { opacity: 0; } }

/* ---------- REVEAL ON SCROLL ---------- */
.reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.55s var(--ease-out), transform 0.55s var(--ease-out); }
.intro-stats .reveal.hold { opacity: 0; transform: translateY(16px); } /* held past .in until the hero scrub releases them at full width */
.reveal.in { opacity: 1; transform: none; }

/* ---------- RESPONSIVE ---------- */
/* Short viewports at desktop width (14" MacBook, iPad landscape): tighten the
   hero rhythm. The old 16:9 cinematic crop is gone; the adaptive width scrub
   now handles keeping the photo above the fold without cropping. No pointer
   gate: tablets in landscape are exactly the height-starved case this serves. */
@media (min-width: 1024px) and (max-height: 900px) {
  .hero { padding: 80px 24px 48px; }
  .hero .hero-actions { margin-top: 32px; }
}

/* ---------- TABLET (640-1023px): compressed desktop, not stretched phone ----------
   Layout keeps its desktop structure; only spacing density steps down. Boundaries
   match Tailwind's sm (640) and lg (1024) exactly, so the port inherits the tiers. */
@media (max-width: 1023px) {
  :root {
    --space-section: 80px;
    --space-section-lg: 112px;
    --space-page-top: 104px;
  }
  .cap-grid { grid-template-columns: 1fr; } /* capabilities and principles stack: three columns are too dense below desktop */
  .exp-row { display: block; } /* company stacks above its roles below desktop */
  .exp-row .roles { margin-top: 14px; }
}

/* ---------- PHONE (<640px): the stacked grammar ---------- */
@media (max-width: 639px) {
  .hero h1 .h1-line2 { display: inline; } /* (fallback markup only) flows with the headline instead of forcing a break */
  .hero h1 .h1-br-wide, .hero h1 .h1-join-wide { display: none; }
  .hero h1 .h1-br-phone, .hero h1 .h1-join-phone { display: inline; }
  /* the authored three lines hold on every phone by scaling the display size
     to the widest line: "Design Leader based" ≈ 9.7em at -0.02em tracking
     (measured against Arial-metric Liberation Sans; SF runs a touch narrower).
     9.6 = that width adjusted for SF; 48px = the hero's side gutters.
     iPhone 15 ≈ 36px, 320px SE ≈ 28px; from ~432px up the 40px cap rules.
     If h1Phone's longest line ever changes, remeasure and retune this.
     NOTE: also applies to the fallback (no-h1Phone) markup — retune or
     remove alongside h1Phone if that field ever goes away. */
  .hero h1 { font-size: min(40px, calc((100vw - 48px) / 9.6)); }
  :root {
    --space-section: 64px;
    --space-section-lg: 88px;
    --space-page-top: 80px;
    --space-section-xl: 112px;
  }
  .nav-links { display: none; }
  .nav-toggle { display: flex; }
  .hero { padding: 88px max(24px, env(safe-area-inset-right)) 48px max(24px, env(safe-area-inset-left)); }
  .hero .hero-actions { flex-direction: column; align-items: stretch; } /* full container width below 640 */
  .hero .hero-actions .btn { min-width: 0; } /* stacked and full width */
  .closer-actions { flex-direction: column; align-items: stretch; } /* full container width below 640 */
  .closer-actions .btn { min-width: 0; }
  .work-grid { grid-template-columns: 1fr; }
  .seg { display: grid; width: 100%; } /* full-width; segments stay equal */
  .intro-stats { grid-template-columns: 1fr; gap: 16px; margin-top: 16px; } /* single vertical stack */
  .case-body h2, .case-body p, .about-solo,
  .case-body ul, .case-body ol, .case-body h3, .case-body blockquote,
  .cap-grid-plain .cap, .faq-a { max-width: none; } /* small screens use the full column */
  .case-body h2 { margin-top: 48px; } /* headings compress with the section rhythm */
  .contact-band, #page-home .contact-band { padding: var(--space-section-xl) 0 calc(28px + env(safe-area-inset-bottom, 0px)); } /* the id-scoped desktop rule must not outrank the mobile close */
  .closer-legal { padding-top: var(--space-section-xl); }
  .case-meta { grid-template-columns: 1fr; gap: 20px; margin-top: 40px; } /* one column: each field full-width; scope pills flow and wrap like a line of text */
  .case-links ul { display: grid; grid-auto-rows: 1fr; } /* full-width rows; 1fr rows keep every row the height of the tallest */
  .case-links li { display: flex; } /* the list item stretches to its row… */
  .case-links a { width: 100%; height: 100%; justify-content: space-between; } /* …and the pill fills it: if one label wraps, every pill matches its height. Label left, arrow at the far edge */
  .case-body { padding-top: 48px; }
  .figure { margin-top: 48px; }
  .case-summary { grid-template-columns: 1fr; gap: 40px; padding-top: 48px; }
  .case-gallery { margin-top: 40px; }
  .figure-duo { grid-template-columns: 1fr; gap: 32px; }
  .lb-stage { padding: 48px 16px 0; }
  .case-next .wrap-inner { padding-top: 32px; grid-template-columns: 1fr; gap: 16px; } /* stacked-card gap matches the stat cards */
  .case-adj, .case-adj-next { justify-content: center; text-align: center; } /* the text block is a flex item: center the item, then the lines inside it */
  .exp-resume-desktop { display: none; } /* the section-closing twin takes over below 640 */
  .exp-resume-mobile {
    display: block;
    width: 100%; /* full container width, same as the hero and contact stacks */
    margin-top: 32px;
    text-align: center;
  }
  .case-adj-next { order: 0; } /* stacked: DOM order rules, Next naturally on top */
  .case-bar-title { display: none; } /* phone: the bar belongs to the back affordance alone */
  .hero .hero-sub, .contact-band .closer-sub { text-wrap: wrap; } /* the two subtexts wrap identically on phones; not pretty - iOS Safari drops that keyword and balance would win */
}



/* ---------- DARK MODE (system preference, no toggle, site-wide) ----------
   The component overrides below share specificity with the base rules and
   rely on source order to win — nothing ABOVE this block may depend on
   beating it. Sections appended AFTER it (the July 2026 tail: object
   hairlines, touch batch, text links, colophon, print, THE SPLIT, and the
   round-two delight batch) must be token-driven or carry their own dark
   variants. HARD CONTRACT: the @media print block stays after this one —
   its light-token re-pins share :root specificity and win by order alone.
   The tail folds into the file's section structure at package extraction. */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    /* Tailwind Zinc palette (dark mode) */
    --ink: #f4f4f5;          /* zinc-100 — primary text */
    --ink-secondary: #a1a1aa;/* zinc-400 */
    --ink-tertiary: #71717a; /* zinc-500 — decoration only */
    --ink-on-alt: #a1a1aa;   /* zinc-400 — secondary text on zinc-900 cards */
    --ink-faint: #3f3f46;    /* zinc-700 — decorative: punctuation, hairline underlines */
    --focus: #2997ff;        /* Apple's dark-mode functional blue */
    --surface: #000000;      /* black */
    --surface-alt: #18181b;  /* zinc-900 — cards */
    --line: #27272a;         /* zinc-800 */
    --surface-alt-hover: #27272a; /* zinc-800 on the zinc-900 card */
  }
  /* components with hardcoded light-mode values */
  .btn-primary:hover { background: #d4d4d8; } /* zinc-300 */
  .btn-secondary:hover { background: rgba(244, 244, 245, 0.08); }
  .skip-link { box-shadow: 0 2px 12px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--line); }
}

/* Object hairlines, both modes: every card and standalone media frame gets
   a 1px inset ring in the mode's --line (zinc-200 light / zinc-800 dark).
   Drawn as an overlay so it paints ABOVE any imagery (an inset shadow hides
   under an <img>). Policy (July 2026): the ring belongs to the object, not
   the picture inside it — cards get it whole, never just their image area. */
.portrait-card::after,
.work-card::after,
.intro-stat::after,
.cap::after,
.case-adj::after,
.case-hero::after,
.figure .frame::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--line);
  pointer-events: none;
}
/* the overlay needs a positioned anchor; these cards had none before */
.work-card, .intro-stat, .cap, .case-adj { position: relative; }
/* principles reuse .cap markup but are typography, not cards (their own rule
   strips the background) — no card, no ring */
.cap-grid-plain .cap::after { content: none; }


/* ============================================================
   FINISHED TO THE TOUCH (delight batch, July 2026)
   ============================================================ */

/* Press grammar completed: gallery frames and coverage chips join the
   documented press scale (interactive elements only, per policy). */
.case-gallery .frame { transition: transform 0.15s ease; }
.case-gallery .frame:active { transform: scale(0.985); }
.case-links a { transition: box-shadow 0.2s ease, background 0.2s ease, transform 0.15s ease; }
.case-links a:active { transform: scale(0.985); }

/* The focus ring lands: contracts from 6px offset to its 3px rest, the
   same physics as every entrance. The reduced-motion clamp kills it. */
@keyframes focusSettle { from { outline-offset: 6px; } }
:focus-visible { animation: focusSettle 0.25s var(--ease-out); }

/* Keyboard parity: focus gets the same tactile response as hover. */
a.work-card:focus-visible { background: var(--surface-alt-hover); }
a.work-card:focus-visible .visual img,
a.work-card:focus-visible .visual svg { transform: scale(1.04); }
.case-gallery .frame:focus-visible img { transform: scale(1.04); }
.back-link:focus-visible { color: var(--ink-on-fixed); }
.back-link:focus-visible .chev { transform: translateX(-3px) rotate(45deg); }
.case-links a:focus-visible { background: rgba(24, 24, 27, 0.05); box-shadow: inset 0 0 0 1px var(--ink-secondary); }
@media (prefers-color-scheme: dark) {
  .case-links a:focus-visible { background: rgba(244, 244, 245, 0.08); }
}
@media (prefers-reduced-motion: reduce) {
  a.work-card:focus-visible .visual img,
  a.work-card:focus-visible .visual svg,
  .case-gallery .frame:focus-visible img { transform: none; }
}

/* The arrow wants to leave: the coverage-chip glyph leans 1px toward its
   destination on hover/focus — the back-chevron's rule, mirrored outward. */
.case-links a svg { transition: transform 0.25s var(--ease-out); }
.case-links a:hover svg,
.case-links a:focus-visible svg { transform: translate(1px, -1px); }

/* Browser chrome joins the palette (Windows scrollbars, mainly).
   Tokens swap in dark mode, so this adapts on its own. */
html { scrollbar-color: var(--ink-tertiary) var(--surface); }

/* Duo rows speak the stagger dialect (dormant until a duo ships;
   pairs with the per-figure reveals in ProjectGallery). */
.figure-duo .figure:nth-child(2) { transition-delay: 0.08s; }

/* ============================================================
   THE TEXT LINK (v2, July 2026 — supersedes the ink-step rule):
   links in running prose look like their sentence, plus a faint
   hairline underline at rest. Inherited color + underline is the
   one rule that renders identically in gray prose, full-ink rows,
   and the dark footer (the ink-step rule collapsed to zero where
   surrounding text was already full ink). Hover completes what has
   headroom: the underline sharpens to currentColor everywhere, and
   the ink completes on surfaces resting below full (FAQ gray, the
   fixed-dark footer). Color + decoration ride plain ease. */
.faq-a a, .closer-legal a, .case-body a, .colophon-body dd a {
  color: inherit; /* a link looks like its sentence */
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--ink-faint); /* the faint resting underline IS the affordance */
  text-underline-offset: 3px;
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}
.closer-legal a { text-decoration-color: var(--ink-on-fixed-faint); } /* the page families' --ink-faint doesn't apply on fixed-dark; this is its equal-faintness sibling (zinc-400 here read heavier than the light-surface underlines — light strokes on dark also bloom optically) */
.faq-a a:hover, .case-body a:hover, .colophon-body dd a:hover { color: var(--ink); text-decoration-color: currentColor; }
.closer-legal a:hover { color: var(--ink-on-fixed); text-decoration-color: currentColor; }
/* the legal line's segments never break internally: wraps fall between
   segments only (spans set in ContactBand), separators trailing */
.closer-legal span { white-space: nowrap; }

/* the print-only contact line stays out of the screen experience entirely */
.print-contact { display: none; }

/* ============================================================
   COLOPHON — the experience list's grammar, verbatim (decided
   July 2026 after a divergent first pass): same 220px/32px rail,
   same 28px row padding, same baseline alignment, and the dt
   speaks .exp-row .company's exact rank (22-28/700/-0.02em) —
   Approach, Type, Color read like company names. Stacks at the
   same ≤1023 tier the experience list does. The 64/96 block
   paddings remain the one local value (a subpage section, not a
   home band).
   ============================================================ */
.colophon-body { max-width: var(--max); margin: 0 auto; padding: 64px max(24px, env(safe-area-inset-right)) 96px max(24px, env(safe-area-inset-left)); }
.colophon-body dl { border-top: 1px solid var(--line); }
.colophon-body dl > div { display: grid; grid-template-columns: 220px 1fr; column-gap: 32px; align-items: baseline; padding: 28px 0; border-bottom: 1px solid var(--line); }
.colophon-body dt { font-size: clamp(22px, 2.6vw, 28px); font-weight: 700; letter-spacing: -0.02em; } /* .exp-row .company's rank, verbatim */
.colophon-body dd { margin: 0; font-size: 17px; line-height: 1.65; letter-spacing: -0.005em; color: var(--ink-secondary); } /* the site's prose rule: the dt carries the message in ink, the body supports in gray (same as case prose) */
@media (max-width: 1023px) {
  /* stacks at the experience list's own tier (28px labels can't hold a
     column below desktop, exactly like company names) */
  .colophon-body dl > div { display: block; }
  .colophon-body dd { margin-top: 8px; }
}

/* ============================================================
   PRINT — "identity that holds up on every surface, from print
   to product," made literally true. A recruiter's Cmd+P yields
   a clean black-on-white one-pager.
   ============================================================ */
@media print {
  /* re-pin the light tokens: a dark-mode user's print must not inherit dark */
  :root {
    --ink: #18181b; --ink-secondary: #71717a; --ink-tertiary: #a1a1aa;
    --ink-on-alt: #52525b; --ink-faint: #d4d4d8;
    --surface: #ffffff; --surface-alt: #f4f4f5; --line: #e4e4e7;
  }
  html, body { background: #ffffff; }

  /* chrome and interactive theater stand down on paper */
  .nav, .mobile-menu, .toast, .skip-link, .lightbox, .case-bar,
  .hero-actions, .seg, .closer-actions { display: none !important; }

  /* everything is already revealed on paper */
  .reveal { opacity: 1 !important; transform: none !important; }
  .portrait-card { width: 100% !important; } /* the scrub's inline width resets */

  /* the dark closer becomes a plain document footer */
  .contact-band { background: #ffffff; color: #18181b; padding: 32px 0 0; }
  .contact-band .eyebrow, .closer-sub { color: #71717a; }
  .closer-title { color: #18181b; }
  .closer-legal { color: #71717a; padding: 16px 24px; }
  .closer-legal a { color: #71717a; } /* the link rule (0,1,1) beats the line rule; without this the fixed-dark zinc-300 prints near-invisible */

  /* the copy-email chip can't work on paper: print the essentials instead */
  .print-contact { display: block; text-align: center; font-size: 14px; color: #18181b; margin-top: 8px; }

  /* external links show their destinations */
  .case-links a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 11px; color: #71717a; }

  /* paper-friendly pagination */
  .hero { padding: 24px; }
  .intro-stat, .work-card, .cap, figure, .exp-row { break-inside: avoid; }
}


/* ============================================================
   THE SPLIT — 404 display numerals (July 2026; UX + visual-
   engineering reviewed). The mark arrives whole, holds ~1.5s,
   fractures on a horizontal midline (top shears right, bottom
   left, one-shot on the one curve), then aftershocks: brief
   CRT-static fills of the glyphs, ~4 events per half per 6s,
   for 8 cycles (~48s) — then true stillness. The noise tile is
   a STATIC-seed SVG (achromatic by construction — procedural
   grays, zinc in spirit); per-blip background-position jumps
   supply the static feel with zero idle animation cost.
   SANCTIONED EXCEPTION (motion doctrine): blip cuts use
   step-end with 2-3px offsets on the NOISE layer only — a
   glitch is a cut, not a movement; no curve applies. The solid
   halves never move after the shear.
   ============================================================ */
#page-notfound .hero {
  flex: 1; /* no band on this page: the hero owns the column and centers */
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
}
/* the label speaks the site's body-copy tier verbatim (17/400/1.65/-0.005em,
   secondary ink) — same values as case prose and the bio */
#page-notfound .hero h1 {
  font-size: 17px;
  font-weight: 400;
  line-height: 1.65;
  letter-spacing: -0.005em;
  color: var(--ink-secondary);
}
.nf-404 {
  position: relative;
  margin-top: 8px; /* authored gap; at line-height 1 the numerals carry ~0.19em internal leading above cap, so perceived air scales with the type (~+40px at 220px, ~+20px at the 110px floor) — cap-height-aware, not drift */
  text-align: center; /* registration insurance: both halves + both noise
    pseudos center on the same axis regardless of ancestors */
  /* the reviewed value plus a 1px push per half (July 2026): quiet
     misregistration — a hairline fault, not a break-apart. Larger shears
     (up to half a glyph width) were explored and reverted by the owner's
     eye; this is the settled number. Change with caution. */
  --nf-shear: calc(clamp(7px, 1.5vw, 15px) + 1px);
}
.nf-404 span {
  display: block;
  position: relative; /* both halves are containing blocks for their noise
    pseudos in BOTH regimes (pre- and post-transform) — no silent re-parenting */
  font-size: clamp(110px, 24vw, 220px);
  font-weight: 700;
  letter-spacing: -0.04em;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* halves: negative side insets keep the shear from clipping; the bottom
   half starts half a pixel high so the resting mark never shows a
   fractional-DPI seam at the midline (ink is opaque; overlap is invisible) */
.nf-404 .nf-top { clip-path: inset(-5% -50% 50% -50%); }
.nf-404 .nf-bot { position: absolute; inset: 0; clip-path: inset(calc(50% - 0.5px) -50% -5% -50%); }
/* the beat, then the fracture: pageIn (0.35s) finishes, the whole mark
   holds ~1.15s more, then the halves shear over 0.5s and stay */
.nf-404 .nf-top { animation: nfShearR 0.5s var(--ease-out) 1.5s forwards; }
.nf-404 .nf-bot { animation: nfShearL 0.5s var(--ease-out) 1.5s forwards; }
@keyframes nfShearR { to { transform: translateX(var(--nf-shear)); } }
@keyframes nfShearL { to { transform: translateX(calc(var(--nf-shear) * -1)); } }
/* aftershocks: the noise fills the glyph shapes (background-clip: text).
   Flash timing is deliberate — no one-second window ever holds more than
   two combined flashes (WCAG 2.3.1 with headroom); halves run offset
   clocks so it never reads as a rhythm; 8 iterations, then rest. */
.nf-404 .nf-top::after, .nf-404 .nf-bot::after {
  content: "404";
  position: absolute; inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch' seed='7'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 140px 140px;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  opacity: 0;
  pointer-events: none;
}
.nf-404 .nf-top::after { animation: nfBlipA 6s step-end 2.3s 8; }
.nf-404 .nf-bot::after { animation: nfBlipB 6s step-end 2.3s 8; }
@keyframes nfBlipA {
  0%    { opacity: 0; transform: none; background-position: 0 0; }
  8%    { opacity: 1; transform: translateX(3px); background-position: -37px 18px; }
  10%   { opacity: 0; transform: none; }
  38%   { opacity: 0.75; background-position: 24px -51px; }
  39.6% { opacity: 0; }
  63%   { opacity: 1; transform: translateX(2px); background-position: -12px -29px; }
  65.2% { opacity: 0; transform: none; }
  85%   { opacity: 0.6; background-position: 41px 33px; }
  86.2% { opacity: 0; }
  100%  { opacity: 0; }
}
@keyframes nfBlipB {
  0%    { opacity: 0; transform: none; background-position: 0 0; }
  8.4%  { opacity: 1; transform: translateX(-3px); background-position: 19px -44px; }
  10.6% { opacity: 0; transform: none; }
  27%   { opacity: 0.8; transform: translateX(2px); background-position: -33px 27px; }
  28.8% { opacity: 0; transform: none; }
  47%   { opacity: 0.7; background-position: 8px 49px; }
  48.6% { opacity: 0; }
  75%   { opacity: 0.85; transform: translateX(-2px); background-position: -25px -16px; }
  77%   { opacity: 0; transform: none; }
  100%  { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  /* the global clamp collapses durations but NOT delays: zero them so the
     mark lands on the final split composition instantly (no 1.5s teleport).
     The blips' base state is opacity 0, so their clamped one-shot renders
     nothing. */
  .nf-404 .nf-top, .nf-404 .nf-bot,
  .nf-404 .nf-top::after, .nf-404 .nf-bot::after { animation-delay: 0s; }
}


/* ============================================================
   ROUND-TWO DELIGHT (July 2026) — platform surfaces + the
   contrast mapping. Everything here is token-driven or carries
   explicit scheme scoping, per the dark-block contract above.
   ============================================================ */

/* text-fragment arrivals (#:~:text= links — how recruiters share lines)
   highlight in the system's inverted ink, not browser yellow; same
   dialect as ::selection, including the fixed-dark inversion */
::target-text { background: var(--ink); color: var(--surface); }
.contact-band ::target-text, .case-bar ::target-text, .nav ::target-text { background: var(--ink-on-fixed); color: var(--surface-fixed); }

/* Safari: opening quotes hang into the margin so the optical text edge
   stays flush (the copy voice mandates curly quotes, so there is
   punctuation to hang). Scoped off phones: the narrow measure can't
   spare the margin. */
@media (min-width: 640px) {
  .prose p, .case-summary p, .faq-a, .colophon-body dd { hanging-punctuation: first; }
}

/* cap-height-true display lockups where the platform allows: trims the
   phantom leading above cap and below alphabetic so authored margins
   become optically true. Progressive — unsupported browsers keep the
   metrics every margin was originally tuned against. */
@supports (text-box: trim-both cap alphabetic) {
  .hero h1, .closer-title, .page-title { text-box: trim-both cap alphabetic; }
}

/* THE CONTRAST THEME: prefers-contrast gets its own semantic mapping over
   the same primitives — the Primitives model (themes = mappings), shipped
   as accessibility before the tool exists. Scheme-scoped explicitly so the
   light remap can never leak into dark. */
@media (prefers-contrast: more) and (prefers-color-scheme: light) {
  :root {
    --ink: #000000;
    --ink-secondary: #3f3f46;  /* zinc-700: secondary promoted a rank */
    --ink-on-alt: #3f3f46;
    --line: #a1a1aa;           /* zinc-400: hairlines step up to visible */
  }
}
@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
  :root {
    --ink: #ffffff;
    --ink-secondary: #d4d4d8;  /* zinc-300 */
    --ink-on-alt: #d4d4d8;
    --line: #52525b;           /* zinc-600 */
  }
}
@media (prefers-contrast: more) {
  :root {
    /* the fixed-dark family is mode-invariant, so its remap is too */
    --ink-on-fixed: #ffffff;
    --ink-on-fixed-secondary: #d4d4d8; /* zinc-300 */
    --ink-on-fixed-tertiary: #e4e4e7;  /* zinc-200 */
  }
}
