/* ============================================================
   EXACTLEE DESIGN & DEVELOPMENT — v2
   Dark precision-shop aesthetic. Copper accent. Mono machine labels.
   ============================================================ */

:root {
  --bg:        #0f110f;   /* near-black ink */
  --bg-2:      #161916;   /* raised surface */
  --bg-3:      #1e221e;   /* cards */
  --line:      #2a2f2a;   /* hairlines */
  --ink:       #ece7dd;   /* primary text */
  --ink-2:     #b5afa3;   /* secondary text */
  --ink-3:     #7d786e;   /* muted */
  --accent:    #e8894a;   /* copper */
  --accent-2:  #f0b896;   /* light copper */
  --accent-dim:#8a5630;
  --gold:      #f9dca5;   /* warm gold, lightened ~50% toward white */
  --paper:     #f5f1ea;
  --radius:    10px;
  --wrap:      1140px;
  --wrap-slim: 760px;
  --font-display: 'Space Grotesk', sans-serif;
  --font-body:    'Inter', sans-serif;
  --font-mono:    'JetBrains Mono', monospace;
}

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

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  .scroll-cue { animation: none !important; }
  /* First pass (2026-09-16) froze the ticker mid-scroll instead of letting its
     infinite-loop animation keep "flying by." That worked but left a
     trade-off: some words ran off the clipped right edge with nothing to
     scroll them into view. The ticker is aria-hidden (decorative keyword
     banner, nothing a screen reader announces), so there's no content lost
     by removing it outright instead — simpler, and the page just flows
     straight from the hero into the intro bio section (2026-09-18, per Lee). */
  .ticker { display: none; }
}

body {
  background-color: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
/* Background texture lives on a fixed, viewport-sized layer instead of on body.
   background-attachment: fixed janks on touch devices, and the old fallback
   (background-attachment: scroll + cover) sized the texture to the whole page
   height on phones, so it came out as one stretched band near the top and
   flat black below. A fixed layer stays viewport-sized everywhere (2026-09-16). */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    linear-gradient(rgba(0,0,0,.75), rgba(0,0,0,.75)),
    url("images/site-bg-texture.jpg");
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

::selection { background: var(--accent); color: #14100c; }

img, video { max-width: 100%; height: auto; display: block; }
a { color: var(--accent-2); text-decoration: none; transition: color .2s; }
a:hover { color: var(--accent); }

h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 700; line-height: 1.12; letter-spacing: -0.015em; }
h1 { font-size: clamp(38px, 6vw, 72px); }
h2 { font-size: clamp(28px, 4vw, 46px); }
h3 { font-size: clamp(20px, 2.4vw, 26px); }

p  { color: var(--ink-2); max-width: 68ch; }
strong { color: var(--ink); }

.wrap      { max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }
.wrap-slim { max-width: var(--wrap-slim); margin: 0 auto; padding: 0 24px; }

section { padding: 96px 0; position: relative; }
section.tight { padding: 140px 0 40px; }
#work { padding-top: 24px; }
/* the generic `section { padding: 96px 0 }` rule was stacking a full 96px
   under #intro on top of .intro-bio's own bottom padding AND #work's own
   top padding above, making the gap before "Some brief credentials" much
   bigger than it looks like it should be. Halving all three contributors
   here (2026-09-17, per Lee) roughly halves the total gap. */
#intro { padding-bottom: 48px; }

/* mono kicker label, like a drawing title block */
.kicker {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--accent);
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 18px;
}
.kicker::before { content: ""; width: 34px; height: 1px; background: var(--accent); flex: none; }

/* in the hero, the kicker now sits below the title instead of above it
   (2026-09-19, per Lee) — swap its own bottom margin for a top margin of
   the same size, and let .hero-sub's existing top margin carry the gap
   down to the paragraph. */
.hero .kicker { margin-top: 18px; margin-bottom: 0; }

.rule { border: 0; height: 1px; background: var(--line); }

/* ============ NAV ============ */
nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  background: rgba(15, 17, 15, .82);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
  transition: transform .35s ease;
}
nav.hidden { transform: translateY(-100%); }
.nav-inner {
  max-width: var(--wrap); margin: 0 auto; padding: 0 24px;
  display: flex; align-items: center; justify-content: space-between;
  height: 68px;
}
.logo img { height: 59px; width: auto; filter: brightness(0) invert(0.92); transition: filter .25s; }
.logo:hover img { filter: brightness(0) invert(0.92) sepia(1) saturate(3) hue-rotate(-15deg); }


.nav-links { list-style: none; display: flex; align-items: center; gap: 4px; }
.nav-links > li { position: relative; }
.nav-links > li > a {
  display: block; padding: 22px 14px;
  font-family: var(--font-mono); font-size: 12.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-2);
}
.nav-links > li > a:hover, .nav-links > li > a.current { color: var(--ink); }
.nav-links > li > a.current { color: var(--accent); }

.dropdown {
  list-style: none;
  position: absolute; top: 100%; left: 0; min-width: 230px;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: 0 0 var(--radius) var(--radius);
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity .2s, transform .2s, visibility .2s;
  padding: 8px 0;
  box-shadow: 0 18px 40px rgba(0,0,0,.5);
  /* long menus (Services) can run taller than the viewport — scroll the menu
     itself instead of letting the wheel fall through to the page underneath */
  max-height: calc(100vh - 100px); overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-color: var(--accent-dim) transparent;
}
.dropdown::-webkit-scrollbar { width: 8px; }
.dropdown::-webkit-scrollbar-track { background: transparent; }
.dropdown::-webkit-scrollbar-thumb { background: var(--accent-dim); border-radius: 4px; }
.dropdown::-webkit-scrollbar-thumb:hover { background: var(--accent); }
.has-dropdown:hover .dropdown, .has-dropdown:focus-within .dropdown {
  opacity: 1; visibility: visible; transform: translateY(0);
}
.dropdown a { display: block; padding: 9px 18px; font-size: 14px; color: var(--ink-2); }
.dropdown a:hover { color: var(--accent-2); background: rgba(232,137,74,.06); }

.nav-cta {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .1em; text-transform: uppercase;
  border: 1px solid var(--accent-dim); color: var(--accent-2) !important;
  padding: 9px 18px !important; border-radius: 6px; margin-left: 10px;
  transition: background .2s, border-color .2s;
}
.nav-cta:hover { background: rgba(232,137,74,.12); border-color: var(--accent); }

/* mobile nav */
.nav-toggle {
  display: none; background: none; border: 0; cursor: pointer; padding: 10px;
}
.nav-toggle span {
  display: block; width: 24px; height: 2px; background: var(--ink); margin: 5px 0;
  transition: transform .3s, opacity .3s;
}
@media (max-width: 900px) {
  .nav-toggle { display: block; }
  .nav-links {
    position: fixed; inset: 68px 0 auto 0; flex-direction: column; align-items: stretch; gap: 0;
    background: var(--bg-2); border-bottom: 1px solid var(--line);
    max-height: 0; overflow: hidden; transition: max-height .35s ease;
  }
  .nav-links.open { max-height: calc(100vh - 68px); overflow-y: auto; }
  .nav-links > li > a { padding: 16px 24px; }
  .dropdown {
    position: static; opacity: 1; visibility: visible; transform: none; border: 0; box-shadow: none;
    background: transparent; padding: 0 0 6px; max-height: none; overflow: visible;
  }
  .dropdown a { padding: 8px 40px; }
  .nav-cta { margin: 12px 24px 18px; text-align: center; }
  body.nav-open .nav-toggle span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  body.nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
  body.nav-open .nav-toggle span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
}

/* scroll progress bar */
#progress {
  position: fixed; top: 0; left: 0; height: 2px; width: 0;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  z-index: 200;
}

/* ============ HERO ============ */
.hero {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 140px 0 64px; position: relative; isolation: isolate;
}
@media (max-width: 700px) {
  .hero { min-height: 68svh; padding: 100px 0 40px; }
}
.hero-media {
  position: absolute; inset: 0; z-index: -2; overflow: hidden;
  background: var(--bg);
}
.hero-media iframe, .hero-media img {
  width: 100%; height: 100%; object-fit: cover; opacity: .38;
}
.hero-media video {
  width: 100%; height: 100%; object-fit: contain; opacity: .48;
}
@media (max-width: 900px) {
  .hero-media video { object-fit: cover; }
}
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(180deg, rgba(15,17,15,.55) 0%, rgba(15,17,15,.15) 45%, rgba(15,17,15,.94) 100%);
}
/* .hero .wrap has margin:0 auto (inherited from .wrap) for centering in normal
   flow, but .hero itself is display:flex; flex-direction:column, and
   flexbox gives cross-axis auto margins priority over align-items:stretch —
   so a .wrap whose only content is narrower than the hero (like the bare
   logo image below) was shrink-wrapping to that content's width and centering
   itself in the leftover space, instead of spanning the hero like every
   other .wrap here does (whose content, running text, happens to be wide
   enough to reach the edges anyway, which is why this only ever showed up
   on the logo). Pinning width:100% makes every .hero .wrap the same width
   regardless of what's inside it (2026-09-17). */
.hero .wrap { width: 100%; }
@media (min-width: 901px) {
  /* Leans the hero content right of dead-center on screens wider than
     --wrap (1140px), for compositional balance with the video behind it.
     The math only makes sense once the viewport is actually wider than
     --wrap — (100% - 1140px) is negative for every width from 901px up to
     1140px, which was silently shoving the whole hero (logo, headline,
     subhead) off the left edge of the screen on that whole range, tablets
     especially (2026-09-19, per Lee — a real device screenshot showed
     "Bingo, bango, bongo." missing its leading letters on a tablet).
     Clamping at 0 keeps the plain centered default until the viewport is
     genuinely wide enough for the rightward lean to make sense. */
  .hero .wrap { margin-left: max(0px, calc((100% - var(--wrap)) * 0.35)); margin-right: auto; }
}
.hero h1 { max-width: 15ch; }
.hero h1 .accent { color: var(--accent); }
.hero h1 .line { display: block; }
.hero-sub { margin-top: 26px; font-size: clamp(17px, 1.6vw, 20px); max-width: 56ch; }

/* hero logo removed from the homepage hero (2026-09-19, per Lee) — it sat
   above the kicker, duplicating the nav logo now that the nav is static. */
.hero-actions { margin-top: 38px; display: flex; flex-wrap: wrap; gap: 14px; align-items: center; }

/* word-by-word rise animation */
.line-mask { display: inline-block; overflow: hidden; vertical-align: bottom; }
.line-mask span { display: inline-block; transform: translateY(110%); animation: rise .8s cubic-bezier(.2,.7,.2,1) forwards; }
@keyframes rise { to { transform: translateY(0); } }

.scroll-cue {
  position: absolute; right: 34px; bottom: 34px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink-3); writing-mode: vertical-rl;
  display: flex; align-items: center; gap: 10px;
}
.scroll-cue::after {
  content: ""; width: 1px; height: 46px; background: var(--ink-3);
  animation: cue 2s ease-in-out infinite;
}
@keyframes cue { 0%,100% { transform: scaleY(.4); transform-origin: top; } 50% { transform: scaleY(1); } }
@media (max-width: 700px) { .scroll-cue { display: none; } }

/* ============ BUTTONS ============ */
.btn {
  display: inline-block; padding: 14px 28px; border-radius: 7px;
  font-family: var(--font-mono); font-size: 13px; letter-spacing: .08em; text-transform: uppercase;
  border: 1px solid var(--line); color: var(--ink);
  transition: transform .2s, border-color .2s, background .2s, color .2s;
}
.btn:hover { border-color: var(--accent); color: var(--accent-2); transform: translateY(-2px); }
.btn-solid { background: var(--accent); border-color: var(--accent); color: #14100c; font-weight: 600; }
.btn-solid:hover { background: var(--accent-2); border-color: var(--accent-2); color: #14100c; }

/* ============ STATS ============ */
.stats {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
  background: var(--line); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; margin-top: 64px;
}
.stat { background: var(--bg-2); padding: 28px 26px; }
.stat b {
  display: block; font-family: var(--font-display); font-weight: 700;
  font-size: clamp(26px, 3vw, 38px); color: var(--ink); letter-spacing: -0.02em;
}
.stat b .unit { color: var(--accent); }
.stat span { font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3); }
@media (max-width: 860px) { .stats { grid-template-columns: repeat(2, 1fr); } }

/* ============ TICKER ============ */
.ticker { overflow: hidden; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 18px 0; background: var(--bg-2); }
.ticker-track { display: flex; gap: 0; width: max-content; animation: ticker 46s linear infinite; }
.ticker:hover .ticker-track { animation-play-state: paused; }
.ticker-track span {
  font-family: var(--font-mono); font-size: 13px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-3); white-space: nowrap; padding: 0 18px; position: relative;
}
.ticker-track span::after { content: "·"; color: var(--accent); position: absolute; right: -4px; }
@keyframes ticker { to { transform: translateX(-50%); } }
@media (max-width: 700px) { .ticker-track { animation-duration: 100s; } }

/* ============ INTRO BIO ============ */
.intro-bio {
  display: grid; grid-template-columns: minmax(0, 240px) 1fr; gap: 48px;
  align-items: center; padding: 16px 24px 16px; border-bottom: 1px solid var(--line);
}
.intro-bio-photo { margin: 0; position: relative; }
.intro-bio-text p { color: var(--ink-2); font-size: 17px; }
.intro-bio-text .work-link { margin-top: 8px; }
@media (max-width: 700px) {
  .intro-bio { grid-template-columns: 1fr; gap: 28px; padding: 48px 24px 24px; }
  .intro-bio-photo { max-width: 200px; margin: 0 auto; }
  .intro-bio-text .kicker { justify-content: center; }
}

/* ============ REVEAL ON SCROLL ============ */
.reveal { opacity: 0; transform: translateY(28px); transition: opacity .8s ease, transform .8s cubic-bezier(.2,.7,.2,1); }
.reveal.in { opacity: 1; transform: none; }
.reveal-d1 { transition-delay: .1s; } .reveal-d2 { transition-delay: .2s; } .reveal-d3 { transition-delay: .3s; }

/* ============ WORK ROWS (homepage selected work) ============ */
.work-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(28px, 5vw, 72px);
  align-items: center; padding: 72px 0; border-top: 1px solid var(--line);
}
.work-row:first-of-type { border-top: 0; padding-top: 36px; }
/* the kicker right above it also gets a smaller margin, scoped to just this
   one so every OTHER kicker on the page keeps its normal 18px spacing
   (2026-09-17, per Lee: halve the gap on both sides of "Some brief
   credentials" specifically). */
#work .kicker { margin-bottom: 9px; }
.work-row.flip .work-media,
.work-row.flip > .slider { order: -1; }
.work-num {
  font-family: var(--font-mono); font-size: 12px; color: var(--ink-3); letter-spacing: .2em;
  display: block; margin-bottom: 14px;
}
.work-meta { font-family: var(--font-mono); font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--accent); margin-bottom: 12px; }
.work-row h3 { font-size: clamp(24px, 3vw, 34px); margin-bottom: 16px; }
.work-row p { font-size: 16.5px; }
.work-link { display: inline-block; margin-top: 18px; font-family: var(--font-mono); font-size: 13px; letter-spacing: .08em; text-transform: uppercase; }
.work-link::after { content: "\00a0→"; transition: margin-left .2s; }
.work-link:hover::after { margin-left: 6px; }
.work-link.gold { color: var(--gold); }
.work-link.gold:hover { color: var(--accent); }

.work-media { position: relative; border-radius: var(--radius); overflow: hidden; }
.work-media img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  transform: scale(1.04); transition: transform 1.1s cubic-bezier(.2,.7,.2,1);
}
.work-media:hover img { transform: scale(1.0); }
/* video variant — no forced aspect-ratio/crop, sized to the video's own native
   dimensions (this one's a portrait clip, hence the wider text column beside it).
   Shrunk to 70% of its column (~30% smaller) and centered, per feedback. */
.work-media-video { width: 70%; margin: 0 auto; }
.work-media-video video { width: 100%; display: block; }
/* Space Campers walkthrough clip only — bumped ~20% larger than the standard
   portrait-video sizing above (70% -> 84%), per feedback */
.work-media-video-lg { width: 84%; }
/* Whalebird clip only — both rows share the same column width, so this % is
   solved (given the two clips' different native aspect ratios: 540x624 vs
   720x900) to render at the same rendered height as the Space Campers video */
.work-media-video-md { width: 77.7%; }
/* WISErg clip only — this one's landscape (16:9), not portrait, so it sits in
   the standard (non-narrowed) column at full width like the photo it replaced,
   no shrink needed. */
.work-media-video-wide { width: 100%; margin: 0; }
.work-media::after {
  content: ""; position: absolute; inset: 0;
  border: 1px solid rgba(236,231,221,.12); border-radius: var(--radius); pointer-events: none;
}
/* this row's media is a portrait-aspect video rather than a 4:3 photo, so give the
   text column more of the row instead of the wider (1.15fr) share a photo gets.
   For .flip rows, .work-media gets order:-1, which (per the grid spec) also moves
   it into the FIRST track during auto-placement, not just visually first — so the
   flip variant needs the column widths reversed too, or the video ends up sized
   against the wide (text) track instead of the narrow (media) one. */
.work-row.work-media-portrait { grid-template-columns: 1.3fr .85fr; }
.work-row.flip.work-media-portrait { grid-template-columns: .85fr 1.3fr; }
@media (max-width: 860px) {
  .work-row { grid-template-columns: 1fr; padding: 56px 0; }
  .work-row.flip .work-media,
  .work-row.flip > .slider { order: 0; }
  .work-row.work-media-portrait { grid-template-columns: 1fr; }
  .work-row.flip.work-media-portrait { grid-template-columns: 1fr; }
  .work-row:first-of-type { padding-top: 28px; }
}

/* tags */
.project-tags { margin-top: 22px; display: flex; flex-wrap: wrap; gap: 8px; }
.project-tags span {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-2); border: 1px solid var(--line); border-radius: 999px; padding: 5px 12px;
  transition: border-color .2s, color .2s;
}
.project-tags span:hover { border-color: var(--accent-dim); color: var(--accent-2); }

/* ============ PIPELINE (idea → product) ============ */
.pipeline {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; margin-top: 56px;
  counter-reset: step;
}
.pipe-step {
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 24px 20px; position: relative; counter-increment: step;
  transition: transform .25s, border-color .25s;
}
.pipe-step:hover { transform: translateY(-4px); border-color: var(--accent-dim); }
.pipe-step::before {
  content: "0" counter(step);
  font-family: var(--font-mono); font-size: 11px; color: var(--accent); letter-spacing: .15em;
}
.pipe-step h4 { font-size: 16px; margin: 10px 0 8px; }
.pipe-step p { font-size: 13.5px; line-height: 1.55; color: var(--ink-3); }
@media (max-width: 960px) { .pipeline { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .pipeline { grid-template-columns: 1fr; } }

/* ============ SERVICE CARDS ============ */
.svc-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 52px; }
.svc-card {
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 30px 26px; transition: transform .25s, border-color .25s, background .25s;
  display: block; color: inherit;
}
.svc-card:hover { transform: translateY(-5px); border-color: var(--accent-dim); background: var(--bg-3); color: inherit; }
.svc-card .svc-icon { font-size: 22px; color: var(--accent); font-family: var(--font-mono); }
.svc-card h4 { margin: 14px 0 10px; font-size: 18px; }
.svc-card p { font-size: 14.5px; color: var(--ink-3); }
@media (max-width: 900px) { .svc-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .svc-grid { grid-template-columns: 1fr; } }

/* services groups */
.svc-group { border-top: 1px solid var(--line); padding: 44px 0; }
.svc-group h3 { color: var(--accent-2); margin-bottom: 18px; }
.svc-group ul { list-style: none; }
.svc-group li {
  color: var(--ink-2); padding: 9px 0 9px 20px; max-width: 68ch;
  position: relative;
}
.svc-group li::before {
  content: "•"; position: absolute; left: 0; color: var(--accent);
}

/* services cards (homepage) — full-width cards stacked vertically */
.svc-group-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; margin-top: 44px; }
.svc-group-grid .svc-group {
  border-top: none; padding: 0; margin: 0; min-width: 0;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius);
  transition: transform .25s, border-color .25s, background .25s;
}
.svc-group-grid .svc-group:hover { transform: translateY(-4px); border-color: var(--accent-dim); background: var(--bg-3); }

/* collapsed-by-default accordion (FAQ-style): summary is the only thing shown
   until a card is expanded, so the services list reads as a clean directory.
   Padding lives on summary itself (not the outer .svc-group) so the entire
   visible card is the click/hover target when collapsed, not just the text line. */
.svc-group-grid .svc-group summary {
  cursor: pointer; list-style: none; position: relative;
  padding: 30px 72px 30px 32px;
  display: flex; align-items: center;
}
.svc-group-grid .svc-group summary::-webkit-details-marker { display: none; }
.svc-group-grid .svc-group summary h3 { margin-bottom: 0; transition: color .2s; }
/* Collapsed service cards were different heights on phones whenever a title
   wrapped to 2-3 lines (1 line ~82px vs up to 150px for the longest titles).
   Below 600px: smaller heading size + tighter line-height/padding so even the
   longest titles fit in 1-2 lines, plus a shared min-height + flex centering
   so every closed card is the same thickness regardless of title length
   (2026-09-18, per Lee). */
@media (max-width: 600px) {
  .svc-group-grid .svc-group summary {
    padding: 18px 54px 18px 20px; min-height: 76px;
  }
  .svc-group-grid .svc-group summary h3 { font-size: 17px; line-height: 1.15; }
}
.svc-group-grid .svc-group summary:hover h3 { color: var(--accent-2); }
.svc-group-grid .svc-group summary::after {
  content: "+"; position: absolute; right: 32px; top: 50%; transform: translateY(-50%);
  font-family: var(--font-mono); font-size: 22px; line-height: 1; color: var(--accent);
  transition: transform .25s;
}
.svc-group-grid .svc-group[open] summary::after { transform: translateY(-50%) rotate(45deg); }
.svc-group-grid .svc-group summary ~ * { margin-top: 18px; }
.svc-group-grid .svc-group ul { padding: 0 32px; }
.svc-group-grid .svc-group .svc-photos { padding: 18px 32px 30px; }
.svc-group-grid .svc-group li { max-width: none; }

/* other places worked — compact card grid, no photos */
.other-work { margin-top: 44px; padding-top: 34px; border-top: 1px solid var(--line); }
.other-work > .kicker { margin-bottom: 8px; }
.other-work-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-top: 20px; }
.other-work-item {
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 20px 22px; min-width: 0; transition: transform .25s, border-color .25s, background .25s;
}
.other-work-item:hover { transform: translateY(-4px); border-color: var(--accent-dim); background: var(--bg-3); }
.other-work-item .ow-icon { width: 100%; height: auto; display: block; margin-bottom: 14px; }
.other-work-item h4 { font-size: 16px; margin-bottom: 8px; line-height: 1.3; }
.other-work-item h4 .ow-title { display: block; color: var(--accent-2); font-weight: 400; font-size: 13.5px; margin-top: 3px; }
.other-work-item p { color: var(--ink-2); max-width: none; margin: 0; font-size: 13.5px; line-height: 1.55; }
/* Four cards only really works side by side in one row — below that it was
   stacking into 2 columns, then 1, adding a long scroll of reading before
   ever reaching Services. Simpler to just skip the whole section on
   phone/tablet and let it reappear once the screen is wide enough to lay
   all four out across in a single row, the same width this grid already
   switched to 4-up at (2026-09-19, per Lee). */
@media (max-width: 900px) { .other-work { display: none; } }

/* continuously scrolling placeholder photo strip at the bottom of each services card
   — auto-scroll position is driven by JS (main.js) so it can freeze on card hover
   and be nudged by the prev/next arrows below */
.svc-photos { position: relative; overflow: hidden; margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); width: 100%; min-width: 0; }
/* touch-action: pan-y (2026-09-18, per Lee — added swipe support in main.js)
   tells the browser to keep handling vertical page-scroll natively here and
   hand horizontal drags to our JS instead, so a swipe never fights the page
   scrolling underneath the strip */
.svc-photos-track { display: flex; gap: 10px; width: max-content; will-change: transform; touch-action: pan-y; }
.svc-photos-track .ph {
  width: 239px; height: 166px; flex-shrink: 0; border-radius: 6px;
  background: var(--bg-3); border: 1px dashed var(--line);
  display: flex; align-items: center; justify-content: center; text-align: center; padding: 4px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3);
  cursor: pointer; transition: transform .25s ease; transform-origin: center;
}
.svc-photos-track .ph:hover { transform: scale(1.1); position: relative; z-index: 2; }
/* once a real photo is dropped in (an <img> inside .ph), drop the placeholder box styling
   and let the tile's width follow the photo's own aspect ratio at a fixed height — no cropping */
.svc-photos-track .ph:has(img) { width: auto; background: none; border: none; padding: 0; display: block; }
.svc-photos-track .ph img { height: 100%; width: auto; display: block; border-radius: 6px; }

/* prev/next arrows — hidden until the whole card is hovered, matching the
   freeze-in-place behavior of the strip itself */
.svc-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 34px; height: 34px; border-radius: 50%;
  background: rgba(15,17,15,.78); border: 1px solid var(--line); color: var(--ink);
  font-size: 17px; line-height: 1; cursor: pointer; z-index: 3; backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none; transition: opacity .2s ease, background .2s ease, border-color .2s ease;
}
/* scoped to real hover capability (2026-09-19, per Lee — same stuck-orange
   bug already fixed on .slider-btn on 2026-09-18, just never carried over to
   this button: touch devices apply :hover on tap and have no mouse to "leave"
   and clear it again, so without this the arrow flashes orange and stays
   that way). Mice/trackpads still get the highlight on hover as before. */
@media (hover: hover) {
  .svc-nav:hover { background: var(--accent); color: #14100c; border-color: var(--accent); }
}
.svc-nav-prev { left: 6px; }
.svc-nav-next { right: 6px; }
.svc-group:hover .svc-nav { opacity: 1; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .svc-group:hover .svc-nav { opacity: 1; pointer-events: auto; } }
/* no hover on touch screens, so the arrows would never appear; show them always
   and give them a finger-sized target (2026-09-16). NOTE: .tool-card .slider-btn's
   own touch-visible override used to live here too, but this block sits earlier
   in the file than the .tool-card rules further down (line ~840-ish) — equal
   specificity, so the later rule always won and silently cancelled this one out
   on every touch device (2026-09-18, per Lee — arrows never showed on the
   Projects page on his phone, on a real slider that's actually native-scrollable
   by touch, so nothing *looked* broken, there were just no arrows to see). Moved
   that override down next to .tool-card's own rules instead of re-fighting the
   cascade here. */
@media (hover: none) {
  .svc-nav { opacity: 1; pointer-events: auto; width: 44px; height: 44px; }
}

/* ============ FAQ ============ */
.faq { margin-top: 40px; border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  cursor: pointer; list-style: none; padding: 22px 40px 22px 0; position: relative;
  font-family: var(--font-display); font-weight: 600; font-size: 18px; color: var(--ink);
  transition: color .2s;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+"; position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  font-family: var(--font-mono); font-size: 20px; color: var(--accent); transition: transform .25s;
}
.faq details[open] summary::after { transform: translateY(-50%) rotate(45deg); }
.faq summary:hover { color: var(--accent-2); }
.faq details p { padding: 0 0 24px; }

/* ============ GALLERY SLIDERS ============ */
.slider { position: relative; margin: 30px 0 8px; }
.slider-track {
  display: flex; gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; -ms-overflow-style: none; padding-bottom: 4px;
}
.slider-track::-webkit-scrollbar { display: none; }

/* ---- Instagram profile grid ----------------------------------------
   27 photos as a compact 3x2 mini-grid per slide. Lives in an ordinary
   .work-row (titled flip), same two-column left/right layout as every
   other Whalebird section, so the .slider column is sized by the row's
   media-column width rather than the article's full width — that's what
   shrinks the tiles down, no bespoke width rules needed. Each .slide
   holds one 3-column .ig-grid "page"; the .ig-tile square-crop treatment
   is unchanged. */
.ig-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.ig-tile {
  position: relative; margin: 0; overflow: hidden;
  background: rgba(236,231,221,.04); border-radius: 2px;
}
.ig-tile::before { content: ""; display: block; padding-top: 100%; }
/* .ig-grid prefix bumps specificity above the generic ".slide img" rule
   (same 0,1,1 weight, but declared later in the file) so the mini tiles
   keep their own absolute-fill sizing instead of picking up that rule's
   border. */
.ig-grid .ig-tile img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; cursor: zoom-in; border: none;
  transition: transform .5s cubic-bezier(.2,.7,.2,1), opacity .35s ease;
}
.ig-tile:hover img { transform: scale(1.06); opacity: .82; }

.slide {
  flex: 0 0 min(720px, 86%); scroll-snap-align: start; margin: 0;
}
.slider.compact .slide { flex-basis: min(420px, 72%); }
.slider.slider-sm .slide { flex-basis: min(576px, 69%); }
.slide img, .slide video {
  width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--radius);
  cursor: zoom-in; border: 1px solid rgba(236,231,221,.08);
}
.slider.compact .slide img { aspect-ratio: 1 / 1; }
/* 2026-08-15, per Lee: try the home page's height-driven, native-aspect-ratio
   treatment (see .svc-photos-track .ph img above) on every .slider-sm slide,
   i.e. every slider on the startups page. The rules above force each slide's
   image into a fixed 3:2 box with object-fit:cover, which crops differently
   depending on the photo's actual ratio — that's the zoomed-in/zoomed-out look
   Lee flagged. This override instead fixes the slide's HEIGHT and lets width
   follow the image's own ratio, same as .ph img: no forced ratio, no cropping.
   Excludes .ig-slider (the Instagram collage grid) — its .slide holds a 3-col
   .ig-grid, not a direct <img>, so it needs its original fixed slide width to
   lay the grid out; the child-combinator image rule below already leaves its
   nested .ig-tile img alone since that img isn't a direct child of .slide. */
.slider.slider-sm:not(.ig-slider) .slide { flex: 0 0 auto; }
.slider.slider-sm .slide > img, .slider.slider-sm .slide > video {
  height: 266px; width: auto; max-width: 100%; aspect-ratio: auto; object-fit: contain;
}
.slider.slider-sm:not(.ig-slider) .slide { max-width: 100%; }
@media (max-width: 700px) {
  .slider.slider-sm .slide > img, .slider.slider-sm .slide > video { height: clamp(180px, 60vw, 266px); }
  /* Instagram collage: one full-width page per slide so the tiles are photos, not icons */
  .slider.ig-slider .slide { flex-basis: 100%; }
}
.slide figcaption {
  font-family: var(--font-mono); font-size: 12px; color: var(--ink-3); padding: 10px 2px 0;
  letter-spacing: .02em;
}
.slider-btn {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; border-radius: 50%;
  background: rgba(15,17,15,.78); border: 1px solid var(--line); color: var(--ink);
  font-size: 20px; cursor: pointer; z-index: 2; backdrop-filter: blur(4px);
  transition: background .2s, border-color .2s; display: flex; align-items: center; justify-content: center;
}
/* scoped to real hover capability (2026-09-18, per Lee — on a phone, tapping
   the arrow turned it orange and it just stayed that color, since touch
   devices apply :hover on tap and there's no mouse to "leave" and clear it
   again). Mice/trackpads still get the highlight on hover as before. */
@media (hover: hover) {
  .slider-btn:hover { background: var(--accent); color: #14100c; border-color: var(--accent); }
}
.slider-btn.prev { left: 12px; } .slider-btn.next { right: 12px; }
/* Briefly hid the arrow at the end of the track it couldn't scroll further
   in (2026-09-18), reverted the next day — on a fast swipe the missing
   button let a thumb land on the photo underneath by accident, and the
   opacity dip read as "broken" rather than gray. Both arrows now stay fully
   visible in both directions at all times (2026-09-19, per Lee). */
/* stand-in tile for a slide whose photo hasn't been dropped in yet — same
   footprint as .slide img so the slider doesn't resize once real photos land */
.slide-placeholder {
  width: 100%; aspect-ratio: 3 / 2; border-radius: var(--radius);
  border: 1px dashed var(--line); background: var(--bg-3);
  display: flex; align-items: center; justify-content: center; text-align: center; padding: 8px;
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3);
}

/* video embeds */
.video-embed { position: relative; padding-top: 56.25%; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); margin: 30px 0; background: #000; }
.work-media .video-embed { border: none; margin: 0; background: transparent; }
.video-embed iframe, .video-embed video { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.slide .video-embed { margin: 0; }

/* stand-in box for a video that hasn't been shot/edited yet — same aspect ratio
   as a real .video-embed so the layout doesn't shift once the video drops in */
.video-embed.placeholder { border: 1px dashed var(--line); background: var(--bg-3); }
.video-embed.placeholder span {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3);
}
.media-slider .slide { flex: 0 0 min(288px, 34.4%); }
.video-native { border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); margin: 30px 0; background: #000; }
.video-native video { width: 100%; }

/* ============ LIGHTBOX ============ */
#lightbox {
  position: fixed; inset: 0; z-index: 300; background: rgba(10,11,10,.95);
  display: none; align-items: center; justify-content: center; flex-direction: column;
  padding: 40px; cursor: zoom-out;
}
#lightbox.open { display: flex; animation: lbfade .25s ease; }
@keyframes lbfade { from { opacity: 0; } }
#lightbox img { max-width: 92vw; max-height: 82vh; object-fit: contain; border-radius: 6px; cursor: default; }
#lightbox figcaption { font-family: var(--font-mono); font-size: 13px; color: var(--ink-2); margin-top: 16px; }
#lightbox .lb-close {
  position: absolute; top: 22px; right: 26px; background: none; border: 0; color: var(--ink);
  font-size: 34px; cursor: pointer; line-height: 1;
}
#lightbox .lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  background: none; border: 1px solid var(--line); border-radius: 50%;
  width: 50px; height: 50px; color: var(--ink); font-size: 22px; cursor: pointer;
}
/* same stuck-on-tap issue as .svc-nav/.slider-btn — the lightbox opens
   straight from the services photo strips, so it gets the same fix
   (2026-09-19). */
@media (hover: hover) {
  #lightbox .lb-nav:hover { border-color: var(--accent); color: var(--accent); }
}
#lightbox .lb-prev { left: 26px; } #lightbox .lb-next { right: 26px; }

/* ============ PROJECT / STARTUP ARTICLES ============ */
.article {
  border-top: 1px solid var(--line); padding: 84px 0;
}

/* per-company differentiation on the startups page — each company's whole
   article sits inside its own rounded, colored outline instead of just a
   plain top hairline */
.co-space-campers, .co-whalebird, .co-wiserg {
  border: 2px solid var(--line);
  border-radius: 28px;
  padding: 64px clamp(24px, 5vw, 64px);
  margin: 56px 0;
  background: var(--bg);
}
.co-space-campers { border-color: #6f736c; }
.co-whalebird { border-color: #1e3766; }
.co-wiserg { border-color: #9ccc65; }
@media (max-width: 700px) {
  .co-space-campers, .co-whalebird, .co-wiserg { padding: 48px 20px; border-radius: 20px; }
}

.article-head { margin-bottom: 30px; }
.article-zone {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 12px;
}
.article-logo { height: 96px; width: auto; display: block; }
.article-logo-badge {
  display: inline-flex; align-items: center; background: #fff; padding: 10px 18px;
  border-radius: 10px;
}
.article-logo-badge .article-logo { height: 64px; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.article-meta { font-family: var(--font-mono); font-size: 12.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--accent); margin: 10px 0 14px; }
.article-lead { display: grid; gap: 18px; max-width: 76ch; }
.article-lead.full-bleed { max-width: none; }
.article-lead.full-bleed p { max-width: none; }
.page-hero p.lede.full-bleed { max-width: none; }
.section-divider { border-top: 1px solid var(--line); margin: 0 0 8px; }

/* top-of-page logo strip linking to each company section, styled as buttons
   to match the "other companies I've worked for" card treatment */
.company-logo-row {
  display: flex; justify-content: space-between; align-items: stretch;
  gap: 20px; margin: 40px 0 8px; flex-wrap: wrap;
}
.company-logo-row .company-logo-btn {
  display: flex; align-items: center; justify-content: center; flex: 1;
  height: 110px; padding: 18px 26px;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius);
  transition: transform .25s ease, border-color .25s ease, background .25s ease;
}
.company-logo-row .company-logo-btn:hover {
  transform: translateY(-4px); border-color: var(--accent-dim); background: var(--bg-3);
}
.company-logo-row .company-logo-btn img {
  max-height: 100%; max-width: 100%; width: auto; height: auto; object-fit: contain; display: block;
}
@media (max-width: 700px) {
  .company-logo-row { flex-direction: column; gap: 16px; margin: 32px 0 8px; }
  /* flex:1 (from the base rule, above) sets flex-basis:0%, which on the
     main axis overrides the "height" property for flex sizing purposes —
     harmless in the desktop row layout where flex:1 only governs WIDTH, but
     once this switches to a column layout the main axis becomes height, so
     flex:1 was letting flexbox's automatic min-height fall back to each
     button's own content size instead of the declared 90px. WISErg's logo
     (192x104, much squarer than the other two very wide/short logos) has a
     taller natural render at any given width, so its card was the one
     visibly growing past the other two. flex:none restores "height: 90px"
     as the actual governing size (2026-09-18, per Lee, verified with a
     headless render at 8 widths: WISErg's card no longer grows past 90px,
     its logo just scales down to fit like the CSS always intended). */
  .company-logo-row .company-logo-btn { height: 90px; width: 100%; flex: none; }
}
.sub-label {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-2); margin: 52px 0 4px; display: flex; align-items: center; gap: 12px;
}
.sub-label::after { content: ""; height: 1px; background: var(--line); flex: 1; }
/* bolder, accent-colored chapter divider (used between story chapters) */
.sub-label.chapter-divider {
  color: var(--accent); font-weight: 600; font-size: 13.5px; letter-spacing: .16em;
  margin: 64px 0 6px;
}
.sub-label.chapter-divider::after { background: var(--accent); opacity: .3; }

/* section title, bold + orange, sitting tight above the paragraph that
   follows, no divider line */
.section-title {
  font-family: var(--font-mono); font-weight: 700; font-size: 13.5px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--accent);
  margin: 56px 0 4px;
}
/* kill the work-row's own top border/padding when it's led by a section
   title immediately above it, so there's no second line and no extra gap */
.section-title + .work-row { border-top: none; padding-top: 0; }
/* same thing when the section title lives inside the row's own text column,
   right above its paragraph, instead of as a sibling above the whole row */
.work-row.titled { border-top: none; padding-top: 0; }
.work-row.titled .section-title { margin: 40px 0 4px; }

/* tool grid (tools & fixtures) */
.tool-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 36px; }
.tool-card {
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; transition: transform .25s, border-color .25s;
}
.tool-card:hover { transform: translateY(-4px); border-color: var(--accent-dim); }
.tool-card .slider { margin: 0; }
.tool-card .slider-track { gap: 0; }
.tool-card .slide { flex: 0 0 100%; }
.tool-card .slide img { aspect-ratio: 1 / 1; border-radius: 0; border: 0; }
/* a video-embed slide (16:9) sitting in a square .tool-card slot (see rule
   above) is shorter than its siblings, so it was landing pinned to the top
   of the stretched flex row with dead space below — Lee flagged this on the
   Aluminum Up-Feed Saw slider, 2026-09-02. Center it in the square instead. */
.tool-card .slide:has(.video-embed) { display: flex; align-items: center; }
.tool-card .slide:has(.video-embed) .video-embed { width: 100%; }
.tool-card .slider-btn { width: 34px; height: 34px; font-size: 16px; opacity: 0; transition: opacity .2s; }
.tool-card:hover .slider-btn { opacity: 1; }
/* no hover on touch screens, so the two rules above would never reveal these —
   show them always there, with a finger-sized target (2026-09-18, per Lee).
   This has to come after the two rules above: same specificity, so whichever
   is later in the file wins regardless of the media query around it. */
@media (hover: none) {
  .tool-card .slider-btn { opacity: 1; width: 44px; height: 44px; }
}
.tool-body { padding: 20px 20px 22px; }
.tool-body h4 { font-size: 16.5px; margin-bottom: 8px; }
.tool-body p { font-size: 13.5px; color: var(--ink-3); line-height: 1.55; }
.tool-tag {
  display: inline-block; margin-top: 12px;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent-2); border: 1px solid var(--accent-dim); border-radius: 999px; padding: 3px 10px;
}
@media (max-width: 900px) { .tool-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .tool-grid { grid-template-columns: 1fr; } }

/* ============ PIN-LOCKED VIDEO SLIDE ============
   A locked slide hides a client-sensitive video behind a PIN prompt (Static
   Mixer project, 2026-09-02, per Lee). Worth being clear about what this
   actually is: not real access control — there's no server here, so anything
   checking the PIN runs in the visitor's own browser and a determined,
   technical person could eventually pick it apart. What it does do: the real
   video ID is never sitting in the page source in plain text (it's XOR
   ciphered with the PIN itself, so even the PIN hash alone doesn't hand you
   the ID), and it only resolves once the correct PIN is typed in. That's a
   real deterrent for a casual visitor. The actual protection is the video
   being unlisted on YouTube; this is a second layer on top of that. */
.slide-locked .lock-panel {
  width: 100%; aspect-ratio: 1 / 1; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center; gap: 10px;
  background: var(--bg-3); padding: 24px;
}
.lock-panel .lock-icon { font-size: 28px; }
.lock-panel p { font-size: 13px; color: var(--ink-2); line-height: 1.5; max-width: 26ch; }
.lock-panel .btn { padding: 10px 20px; font-size: 12px; }
/* once unlocked, the slide's content is swapped for the real video-embed +
   a "watch on YouTube" link; reuse the existing .tool-card slide:has(.video-embed)
   centering rule (defined above) for vertical centering, and lay the video
   and link out in a column ourselves since that rule only centers a single row */
.unlocked-video { width: 100%; display: flex; flex-direction: column; gap: 10px; }
.unlocked-video .watch-yt-link {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent-2); text-align: center;
}
.unlocked-video .watch-yt-link:hover { color: var(--accent); }

#pin-modal {
  position: fixed; inset: 0; z-index: 310; background: rgba(10,11,10,.92);
  display: none; align-items: center; justify-content: center; padding: 24px;
}
#pin-modal.open { display: flex; animation: lbfade .2s ease; }
#pin-modal .pin-box {
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 32px; max-width: 340px; width: 100%; text-align: center; position: relative;
}
#pin-modal .pin-close {
  position: absolute; top: 12px; right: 14px; background: none; border: 0; color: var(--ink-3);
  font-size: 22px; cursor: pointer; line-height: 1;
}
#pin-modal .pin-close:hover { color: var(--ink); }
#pin-modal .pin-lock-icon { font-size: 26px; margin-bottom: 10px; }
#pin-modal h4 { font-size: 15px; margin-bottom: 8px; }
#pin-modal p { font-size: 13px; color: var(--ink-2); line-height: 1.5; margin-bottom: 18px; }
#pin-modal input[type="password"] {
  width: 100%; padding: 12px 14px; border-radius: 7px; border: 1px solid var(--line);
  background: var(--bg); color: var(--ink); font-family: var(--font-mono); font-size: 16px;
  letter-spacing: .15em; text-align: center; margin-bottom: 14px; box-sizing: border-box;
}
#pin-modal input:focus { outline: none; border-color: var(--accent); }
#pin-modal .pin-error { font-size: 12px; color: #e0654a; min-height: 16px; margin: -6px 0 12px; }
#pin-modal .pin-submit { width: 100%; }
#pin-modal .pin-nav-link { width: 100%; box-sizing: border-box; display: inline-block; }
/* .btn's own unconditional "display: inline-block" would otherwise beat the
   hidden attribute's default display:none on any .btn inside this modal
   (pin-submit, pin-nav-link) — force hidden to always win here. */
#pin-modal [hidden] { display: none !important; }
#pin-modal .pin-modal-msg { /* text set dynamically per use (locked video vs. hidden-projects nav) */ }

/* "Hidden Projects" entry point at the bottom of the projects page — PIN-gated,
   opens hidden-projects.html in a new tab once unlocked (2026-09-02, per Lee).
   Deliberately unlabeled: a plain black button, invisible-ish against the
   page's own near-black background, that only reveals itself with a small
   grey highlight on hover (2026-09-04, per Lee — no visible text/label). */
.hidden-projects-cta {
  text-align: center; padding: 10px 0 60px; margin-top: -20px;
}
.hidden-projects-btn {
  width: 60px; height: 26px; padding: 0; margin: 0 auto; display: block;
  background: #000; border: none; border-radius: 5px; cursor: pointer;
  transition: background .2s;
}
.hidden-projects-btn:hover, .hidden-projects-btn:focus-visible {
  background: #3a3a3a;
}
.hidden-projects-empty {
  text-align: center; color: var(--ink-3); font-family: var(--font-mono); font-size: 12px;
  letter-spacing: .06em; text-transform: uppercase; margin: 20px 0 60px;
}

/* ============ CONTACT ============ */
.contact-block {
  background: linear-gradient(160deg, #1a130d 0%, var(--bg-2) 60%);
  border-top: 1px solid var(--line);
  padding: 110px 0;
  text-align: left;
}
.contact-block h2 { max-width: 20ch; }
.contact-block p { margin-top: 18px; }
.contact-lines { margin-top: 34px; font-size: 17px; display: grid; gap: 10px; }
.contact-lines strong {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .15em; text-transform: uppercase;
  color: var(--ink-3); display: inline-block; width: 76px;
}
.contact-email {
  font-family: var(--font-display); font-size: clamp(22px, 3.4vw, 40px); font-weight: 700;
  display: inline-block; margin-top: 24px; color: var(--ink);
  border-bottom: 2px solid var(--accent); padding-bottom: 4px;
  transition: color .2s, border-color .2s;
}
.contact-email:hover { color: var(--accent-2); }

/* ============ FOOTER ============ */
footer { border-top: 1px solid var(--line); padding: 34px 0; }
footer .wrap { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px; }
footer span { font-family: var(--font-mono); font-size: 12px; color: var(--ink-3); letter-spacing: .04em; }
footer a { display: inline-block; padding: 6px 0; }

/* misc figures */
.media-figure { border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); }
.media-figure img { width: 100%; }
.media-figure figcaption { font-family: var(--font-mono); font-size: 12px; color: var(--ink-3); padding: 12px 16px; background: var(--bg-2); }

/* page hero (interior pages) */
.page-hero { padding: 170px 0 32px; }
.page-hero h1 { font-size: clamp(28.5px, 4.5vw, 54px); }
.page-hero + .wrap > .article:first-of-type { border-top: none; padding-top: 16px; }
/* ...except on the startups page, where Space Campers gets its own full
   rounded outline like the other two companies, not just the bare top edge */
.page-hero + .wrap > .article.co-space-campers:first-of-type { border-top: 2px solid #6f736c; padding-top: 64px; }
.page-hero p.lede { margin-top: 20px; font-size: 18px; }
/* startups page dropped its <h1> (2026-09-18, per Lee — "Startups" kicker
   already says it, the heading was redundant), which left the 170px top
   clearance built for nav+kicker+h1 looking like a big empty gap on phones
   with just the kicker sitting in it. Halve it there on small screens only;
   other interior pages still have an h1 to fill that space, so their hero
   padding stays as-is. */
@media (max-width: 700px) {
  body.startups .page-hero { padding-top: 85px; }
}

/* scope note */
.scope-note {
  margin-top: 56px; padding: 22px 26px; border: 1px dashed var(--accent-dim); border-radius: var(--radius);
  font-size: 14.5px; color: var(--ink-2); font-style: italic; max-width: none;
}


/* ============ RESUME PAGE ============ */
.resume-hero { padding: 150px 0 44px; border-bottom: 1px solid var(--line); }
.resume-name { font-size: clamp(32px, 5vw, 52px); }
.resume-role {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(15px, 2vw, 19px); color: var(--accent-2); margin-top: 8px;
}
.resume-contact {
  font-family: var(--font-mono); font-size: 12.5px; letter-spacing: .03em; color: var(--ink-3);
  margin-top: 18px; display: flex; flex-wrap: wrap; gap: 6px 10px; max-width: none;
}
.resume-contact a { color: var(--ink-2); }
.resume-contact a:hover { color: var(--accent-2); }
.resume-contact .sep { color: var(--line); }
.resume-summary { margin-top: 24px; font-size: 16.5px; max-width: 68ch; }
.resume-actions { margin-top: 28px; display: flex; gap: 12px; flex-wrap: wrap; }

.resume-section { padding: 48px 0; border-top: 1px solid var(--line); }
.resume-section h2 { font-size: clamp(20px, 2.4vw, 26px); margin-bottom: 28px; }

.resume-competencies { display: grid; grid-template-columns: 1fr 1fr; gap: 26px 40px; }
.resume-comp h3 {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent); font-weight: 500; margin-bottom: 7px;
}
.resume-comp p { font-size: 14.5px; color: var(--ink-2); max-width: none; }
@media (max-width: 700px) { .resume-competencies { grid-template-columns: 1fr; } }

.resume-entries > .resume-entry { border-top: 1px solid var(--line); padding: 28px 0; }
.resume-entries > .resume-entry:first-child { border-top: none; padding-top: 0; }
.resume-entry h3 { font-size: 18.5px; font-weight: 700; }
.resume-entry h3 span { color: var(--ink-2); font-weight: 500; }
.resume-entry-meta {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent); margin: 8px 0 4px;
}
.resume-entry ul { list-style: none; margin-top: 14px; display: grid; gap: 9px; }
.resume-entry li { position: relative; padding-left: 20px; font-size: 14.5px; color: var(--ink-2); max-width: 74ch; }
.resume-entry li::before { content: "•"; position: absolute; left: 0; color: var(--accent); }

.resume-note { font-size: 14.5px; color: var(--ink-2); max-width: 74ch; }
.resume-note em { color: var(--ink-3); font-style: italic; }
