/* =========================================================================
   Adeline Cook — portfolio
   Design system: charcoal / light-grey shells, fluorescent "plastic" accents.
   ========================================================================= */

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; }
body { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
img, svg, canvas { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; }
:where(h1,h2,h3,h4,h5,h6) { font-weight: inherit; font-size: inherit; }

/* =========================================================================
   TOKENS
   ========================================================================= */
:root {
  /* type */
  --f-display: "Outfit", "Segoe UI", system-ui, sans-serif;
  --f-body: "Hanken Grotesk", "Segoe UI", system-ui, sans-serif;
  --f-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;

  /* fluorescent object colours — identical in both themes, they are "plastic" */
  --fl-lime:    #B8FF3C;
  --fl-cyan:    #24E8FF;
  --fl-magenta: #FF3DCE;
  --fl-violet:  #A05CFF;
  --fl-orange:  #FF8A1F;
  --fl-yellow:  #FFE23C;

  /* motion */
  --e-out: cubic-bezier(0.16, 1, 0.3, 1);
  --e-io:  cubic-bezier(0.65, 0, 0.35, 1);
  --t-fast: 160ms;
  --t-mid: 320ms;
  --t-slow: 620ms;

  /* metrics */
  --gutter: clamp(1.25rem, 4vw, 4rem);
  --maxw: 1240px;
  --maxw-read: 68ch;
  --r-lg: 22px;
  --r-md: 15px;
  --r-sm: 10px;
}

/* ---------- dark (default): charcoal ---------- */
:root, :root[data-theme="dark"] {
  color-scheme: dark;

  --bg:        #141518;
  --bg-2:      #1A1B1F;
  --bg-3:      #212227;
  --ink:       #F4F5F7;
  --ink-2:     #C9CCD3;
  --mute:      #979CA6;
  --dim:       #6C7079;

  --rule:      rgba(255,255,255,0.10);
  --rule-soft: rgba(255,255,255,0.055);

  /* panel: translucent glass over charcoal */
  --panel-a:    rgba(255,255,255,0.052);
  --panel-b:    rgba(255,255,255,0.016);
  --panel-brd:  rgba(255,255,255,0.11);
  --panel-lip:  rgba(255,255,255,0.13);
  --panel-blur: 16px;
  --panel-sat:  135%;
  --sh-1: rgba(0,0,0,0.34);
  --sh-2: rgba(0,0,0,0.40);
  --sh-3: rgba(0,0,0,0.46);

  /* readable accent text (fluoro is legible on charcoal) */
  --ac-lime:    #C8FF66;
  --ac-cyan:    #5CEEFF;
  --ac-magenta: #FF74DA;
  --ac-violet:  #BE8CFF;
  --ac-orange:  #FFAB55;
  --ac-yellow:  #FFE96B;

  --glow-strength: 0.62;
  --scrim: rgba(20,21,24,0.72);
  /* bottom-edge fade over a cropped screenshot. Deliberately dark in both
     themes: every capture is a dark UI, so a pale fade reads as glare. */
  --shot-fade: rgba(6,7,9,0.5);
}

/* ---------- light: cool grey ---------- */
:root[data-theme="light"] {
  color-scheme: light;

  --bg:        #E7E9EC;
  --bg-2:      #DFE2E6;
  --bg-3:      #F4F6F8;
  --ink:       #14161A;
  --ink-2:     #2E323A;
  --mute:      #565C66;
  --dim:       #767C86;

  --rule:      rgba(18,22,28,0.14);
  --rule-soft: rgba(18,22,28,0.07);

  --panel-a:    rgba(255,255,255,0.80);
  --panel-b:    rgba(255,255,255,0.46);
  --panel-brd:  rgba(18,22,28,0.11);
  --panel-lip:  rgba(255,255,255,0.95);
  --panel-blur: 14px;
  --panel-sat:  150%;
  --sh-1: rgba(30,36,48,0.10);
  --sh-2: rgba(30,36,48,0.13);
  --sh-3: rgba(30,36,48,0.16);

  /* darkened-but-still-rich accent text (fluoro itself fails contrast on grey) */
  --ac-lime:    #4B7A00;
  --ac-cyan:    #00707F;
  --ac-magenta: #B5127E;
  --ac-violet:  #6B29C4;
  --ac-orange:  #9A4A00;
  --ac-yellow:  #7A5C00;

  --glow-strength: 0.95;
  --scrim: rgba(231,233,236,0.74);
  --shot-fade: rgba(6,7,9,0.5);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    color-scheme: light;
    --bg: #E7E9EC; --bg-2: #DFE2E6; --bg-3: #F4F6F8;
    --ink: #14161A; --ink-2: #2E323A; --mute: #565C66; --dim: #767C86;
    --rule: rgba(18,22,28,0.14); --rule-soft: rgba(18,22,28,0.07);
    --panel-a: rgba(255,255,255,0.80); --panel-b: rgba(255,255,255,0.46);
    --panel-brd: rgba(18,22,28,0.11); --panel-lip: rgba(255,255,255,0.95);
    --panel-blur: 14px; --panel-sat: 150%;
    --sh-1: rgba(30,36,48,0.10); --sh-2: rgba(30,36,48,0.13); --sh-3: rgba(30,36,48,0.16);
    --ac-lime: #4B7A00; --ac-cyan: #00707F; --ac-magenta: #B5127E;
    --ac-violet: #6B29C4; --ac-orange: #9A4A00; --ac-yellow: #7A5C00;
    --glow-strength: 0.95; --scrim: rgba(231,233,236,0.74);
    --shot-fade: rgba(6,7,9,0.5);
  }
}

/* =========================================================================
   BASE
   ========================================================================= */
body {
  font-family: var(--f-body);
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  line-height: 1.62;
  color: var(--ink-2);
  background: var(--bg);
  overflow-x: hidden;
  transition: background-color var(--t-mid) var(--e-out), color var(--t-mid) var(--e-out);
}

::selection { background: var(--fl-lime); color: #0B0C0E; }

:focus-visible {
  outline: 2px solid var(--fl-cyan);
  outline-offset: 3px;
  border-radius: 4px;
}

/* =========================================================================
   LAYOUT
   ========================================================================= */
.shell { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.shell-wide { max-width: 1500px; }

section[id] { scroll-margin-top: 92px; }

.band { position: relative; padding-block: clamp(5rem, 11vw, 9.5rem); }
.band + .band { padding-top: 0; }

/* =========================================================================
   TYPE
   ========================================================================= */
.display {
  font-family: var(--f-display);
  font-weight: 200;
  letter-spacing: -0.03em;
  line-height: 0.98;
  color: var(--ink);
  text-wrap: balance;
}
.d-xxl { font-size: clamp(2.9rem, 9.5vw, 7.4rem); }
.d-xl  { font-size: clamp(2.3rem, 5.6vw, 4.4rem); }
.d-lg  { font-size: clamp(1.75rem, 3.4vw, 2.9rem); letter-spacing: -0.025em; }
.d-md  { font-size: clamp(1.3rem, 2vw, 1.7rem); letter-spacing: -0.02em; font-weight: 300; line-height: 1.14; }

.display strong, .display b { font-weight: 500; color: var(--ink); }

/* the signature fluoro period */
.dot { color: var(--fl-lime); font-weight: 400; }
:root[data-theme="light"] .dot { color: var(--ac-lime); }
@media (prefers-color-scheme: light) { :root:not([data-theme]) .dot { color: var(--ac-lime); } }

.eyebrow {
  font-family: var(--f-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--mute);
  display: flex; align-items: center; gap: 0.65em;
}
.eyebrow::before {
  content: ""; inline-size: 1.6em; block-size: 2px; border-radius: 2px; flex: none;
  background: var(--eyebrow-c, var(--fl-lime));
  box-shadow: 0 0 10px color-mix(in srgb, var(--eyebrow-c, var(--fl-lime)) 70%, transparent);
}
.eyebrow.plain::before { display: none; }

.lede { font-size: clamp(1.06rem, 0.98rem + 0.42vw, 1.3rem); line-height: 1.55; color: var(--ink-2); max-width: 58ch; text-wrap: pretty; }
.prose { max-width: var(--maxw-read); }
.prose p + p { margin-top: 1.05em; }
.muted { color: var(--mute); }
.mono { font-family: var(--f-mono); font-size: 0.78rem; letter-spacing: 0.06em; }

/* =========================================================================
   PANEL — the signature surface
   Each panel carries --hue (a fluoro colour) that tints its border,
   its inner bottom bloom, and its label.
   ========================================================================= */
.panel {
  --hue: var(--fl-cyan);
  position: relative;
  border-radius: var(--r-lg);
  border: 1px solid color-mix(in srgb, var(--hue) 26%, var(--panel-brd));
  background:
    linear-gradient(180deg, var(--panel-a), var(--panel-b)),
    radial-gradient(120% 100% at 50% 118%, color-mix(in srgb, var(--hue) 13%, transparent), transparent 66%);
  backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  box-shadow:
    inset 0 1px 0 var(--panel-lip),
    inset 0 -22px 34px -28px color-mix(in srgb, var(--hue) 78%, transparent),
    0 1px 2px var(--sh-1),
    0 10px 24px -8px var(--sh-2),
    0 34px 60px -22px var(--sh-3);
  padding: clamp(1.4rem, 2.6vw, 2.2rem);
  transition: transform var(--t-mid) var(--e-out), box-shadow var(--t-mid) var(--e-out),
              border-color var(--t-mid) var(--e-out);
}

/* a hairline of pure fluoro along the top edge — the "plastic lip" */
.panel::before {
  content: "";
  position: absolute; inset-inline: 14%; inset-block-start: -1px;
  block-size: 1px; border-radius: 2px; pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--hue), transparent);
  opacity: calc(var(--glow-strength) * 0.9);
}

.panel.is-interactive { cursor: pointer; }
.panel.is-interactive:hover,
.panel.is-interactive:focus-within {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--hue) 52%, var(--panel-brd));
  box-shadow:
    inset 0 1px 0 var(--panel-lip),
    inset 0 -22px 34px -26px color-mix(in srgb, var(--hue) 92%, transparent),
    0 1px 2px var(--sh-1),
    0 14px 30px -8px var(--sh-2),
    0 44px 76px -24px var(--sh-3),
    0 0 44px -14px color-mix(in srgb, var(--hue) 62%, transparent);
}

.panel-flat { padding: 0; overflow: hidden; }

/* Hue helpers live at the END of this file — see the block after the
   utilities. Component rules that declare their own `--hue` default would
   otherwise win on source order and flatten every card to the same colour. */

/* =========================================================================
   BUTTONS
   ========================================================================= */
.btn {
  display: inline-flex; align-items: center; gap: 0.6em;
  font-family: var(--f-mono); font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.15em; text-transform: uppercase; text-decoration: none;
  padding: 0.95em 1.5em; border-radius: 999px; border: 1px solid var(--rule);
  color: var(--ink); background: transparent; cursor: pointer;
  transition: all var(--t-fast) var(--e-out);
  white-space: nowrap;
}
.btn:hover { border-color: color-mix(in srgb, var(--ink) 42%, transparent); transform: translateY(-1px); }
.btn .arw { transition: transform var(--t-fast) var(--e-out); }
.btn:hover .arw { transform: translateX(3px); }

/* the plastic pill — glossy, extruded, fluorescent */
.btn-fluoro {
  --hue: var(--fl-lime);
  color: #0C0E0A;
  border: none;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, #fff 34%, var(--hue)) 0%,
      var(--hue) 46%,
      color-mix(in srgb, #000 22%, var(--hue)) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.72),
    inset 0 -2px 5px -1px rgba(0,0,0,0.28),
    0 2px 5px -1px rgba(0,0,0,0.28),
    0 12px 26px -8px color-mix(in srgb, var(--hue) calc(var(--glow-strength) * 74%), transparent),
    0 0 0 1px color-mix(in srgb, var(--hue) 46%, transparent);
}
.btn-fluoro:hover {
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.82),
    inset 0 -2px 5px -1px rgba(0,0,0,0.28),
    0 3px 7px -1px rgba(0,0,0,0.3),
    0 18px 38px -10px color-mix(in srgb, var(--hue) calc(var(--glow-strength) * 88%), transparent),
    0 0 0 1px color-mix(in srgb, var(--hue) 60%, transparent);
}
.btn-fluoro:active { transform: translateY(0); }

/* =========================================================================
   CHROME — top bar
   ========================================================================= */
.topbar {
  position: fixed; inset-block-start: 0; inset-inline: 0; z-index: 60;
  display: flex; align-items: center; gap: 1rem;
  padding: 0.85rem var(--gutter);
  transition: background-color var(--t-mid) var(--e-out), border-color var(--t-mid) var(--e-out),
              box-shadow var(--t-mid) var(--e-out), backdrop-filter var(--t-mid);
  border-block-end: 1px solid transparent;
  /* Declared transparent rather than `none` so the shadow has something to
     interpolate from — `none` would pop instead of fade. */
  box-shadow: 0 6px 18px -10px transparent;
}
.topbar.is-stuck {
  background: var(--scrim);
  backdrop-filter: blur(18px) saturate(150%);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  border-block-end-color: var(--rule);
  box-shadow: 0 6px 18px -10px var(--sh-2);
}

.brand { display: inline-flex; align-items: center; gap: 0.6rem; text-decoration: none; color: var(--ink); }
.brand svg { inline-size: 26px; block-size: auto; }
.brand-name {
  font-family: var(--f-display); font-weight: 300; font-size: 1.02rem;
  letter-spacing: -0.015em; white-space: nowrap;
}

.topnav { display: flex; align-items: center; gap: 0.25rem; margin-inline-start: auto; }
.topnav a {
  font-family: var(--f-mono); font-size: 0.68rem; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--mute); text-decoration: none; padding: 0.55em 0.8em; border-radius: 999px;
  transition: color var(--t-fast) var(--e-out), background-color var(--t-fast) var(--e-out);
}
.topnav a:hover { color: var(--ink); background: var(--rule-soft); }
.topnav a.is-active { color: var(--ink); }

/* Narrow screens keep the whole nav. The brand wordmark goes instead of the
   links — the logo still identifies the page, and every other route to the
   rest of the site is in this bar. */
@media (max-width: 900px) {
  .topbar { gap: 0.5rem; padding-inline: clamp(0.8rem, 3.5vw, var(--gutter)); }
  .brand-name { display: none; }
  .topnav { gap: 0; }
  .topnav a { font-size: 0.62rem; letter-spacing: 0.08em; padding: 0.55em 0.55em; }
}
@media (max-width: 430px) {
  .topnav a { font-size: 0.56rem; letter-spacing: 0.04em; padding: 0.55em 0.4em; }
}

.icon-btn {
  inline-size: 34px; block-size: 34px; display: grid; place-items: center;
  border-radius: 999px; border: 1px solid var(--rule); background: transparent;
  color: var(--mute); cursor: pointer; flex: none;
  transition: all var(--t-fast) var(--e-out);
}
.icon-btn:hover { color: var(--ink); border-color: color-mix(in srgb, var(--ink) 34%, transparent); }
.icon-btn svg { inline-size: 15px; block-size: 15px; }
:root[data-theme="light"] .i-moon, :root:not([data-theme]) .i-moon { display: none; }
:root[data-theme="light"] .i-sun,  :root:not([data-theme]) .i-sun  { display: block; }
:root[data-theme="dark"] .i-sun  { display: none; }
:root[data-theme="dark"] .i-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .i-sun { display: none; }
  :root:not([data-theme]) .i-moon { display: block; }
}

/* =========================================================================
   BACKGROUND CANVAS
   ========================================================================= */
#chipfield {
  position: fixed; inset: 0; z-index: 0;
  inline-size: 100%; block-size: 100%;
  pointer-events: none;
  opacity: 0; transition: opacity 900ms var(--e-out);
}
#chipfield.is-ready { opacity: 1; }
/* Past the hero the field steps back so body copy always wins. The panels
   have their own backdrop, so chips read beautifully behind those. */
#chipfield.is-ready.is-quiet { opacity: 0.34; }

/* CSS fallback when WebGL is unavailable or motion is reduced */
.bg-fallback {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(52vw 46vw at 12% 8%,  color-mix(in srgb, var(--fl-violet)  22%, transparent), transparent 62%),
    radial-gradient(44vw 40vw at 88% 22%, color-mix(in srgb, var(--fl-cyan)    20%, transparent), transparent 62%),
    radial-gradient(48vw 44vw at 70% 82%, color-mix(in srgb, var(--fl-magenta) 18%, transparent), transparent 62%),
    radial-gradient(40vw 38vw at 22% 70%, color-mix(in srgb, var(--fl-lime)    16%, transparent), transparent 62%);
  filter: blur(30px);
}
.has-webgl .bg-fallback { display: none; }

/* Lifts page content above the WebGL background. The top bar is deliberately
   excluded — it sets its own `position: fixed` and a higher z-index above, and
   this rule would otherwise override it and let the bar scroll away. */
main, footer { position: relative; z-index: 2; }

/* =========================================================================
   3D SCROLL — elements arriving out of depth
   ========================================================================= */
.stage { perspective: 1400px; perspective-origin: 50% 42%; }

.rise {
  opacity: 0;
  transform: translate3d(0, 42px, -260px) rotateX(11deg);
  transform-style: preserve-3d;
  transition:
    opacity 780ms var(--e-out),
    transform 980ms var(--e-out);
  will-change: transform, opacity;
}
.rise.is-in { opacity: 1; transform: translate3d(0,0,0) rotateX(0deg); }
.rise[data-delay="1"] { transition-delay: 80ms; }
.rise[data-delay="2"] { transition-delay: 160ms; }
.rise[data-delay="3"] { transition-delay: 240ms; }
.rise[data-delay="4"] { transition-delay: 320ms; }
.rise[data-delay="5"] { transition-delay: 400ms; }

@media (prefers-reduced-motion: reduce) {
  .rise { opacity: 1; transform: none; transition: none; }
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* =========================================================================
   HERO
   ========================================================================= */
.hero {
  min-block-size: 100svh;
  display: grid; align-content: center;
  padding-block: clamp(7rem, 16vh, 11rem) clamp(3rem, 8vh, 6rem);
  position: relative;
}
.hero-inner { display: grid; gap: clamp(1.5rem, 3vw, 2.2rem); }

.hero h1 { margin-block-start: 0.2em; }
.hero-role {
  font-family: var(--f-display); font-weight: 300;
  font-size: clamp(1.05rem, 0.94rem + 0.62vw, 1.5rem);
  letter-spacing: -0.012em; color: var(--ink-2); max-width: 30ch;
}
.hero-role em { font-style: normal; color: var(--ink); font-weight: 400; }

.hero-actions { display: flex; flex-wrap: wrap; gap: 0.7rem; align-items: center; }

/* live GitHub card, front and centre */
.gh-card {
  --hue: var(--fl-violet);
  display: inline-flex; align-items: center; gap: 0.85rem;
  padding: 0.7rem 1.15rem 0.7rem 0.85rem; border-radius: 999px;
  text-decoration: none; color: var(--ink);
  border: 1px solid color-mix(in srgb, var(--hue) 30%, var(--panel-brd));
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  box-shadow: inset 0 1px 0 var(--panel-lip), 0 8px 20px -8px var(--sh-2);
  transition: all var(--t-fast) var(--e-out);
}
.gh-card:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--hue) 58%, var(--panel-brd));
  box-shadow: inset 0 1px 0 var(--panel-lip), 0 14px 28px -10px var(--sh-2),
              0 0 34px -12px color-mix(in srgb, var(--hue) 70%, transparent);
}
.gh-card svg { inline-size: 22px; block-size: 22px; flex: none; }
.gh-meta { display: grid; line-height: 1.2; }
.gh-meta .gh-handle { font-family: var(--f-mono); font-size: 0.82rem; letter-spacing: 0.01em; }
.gh-meta .gh-sub {
  font-family: var(--f-mono); font-size: 0.62rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mute);
}

/* scroll cue */
.scroll-cue {
  position: absolute; inset-block-end: clamp(1.5rem, 4vh, 3rem); inset-inline-start: var(--gutter);
  display: flex; align-items: center; gap: 0.7rem;
  font-family: var(--f-mono); font-size: 0.62rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--dim);
}
.scroll-cue .rail {
  inline-size: 52px; block-size: 1px; background: var(--rule); position: relative; overflow: hidden;
}
.scroll-cue .rail::after {
  content: ""; position: absolute; inset-block: 0; inline-size: 40%;
  background: var(--fl-lime); box-shadow: 0 0 8px var(--fl-lime);
  animation: railrun 2.4s var(--e-io) infinite;
}
@keyframes railrun { 0% { transform: translateX(-110%); } 100% { transform: translateX(280%); } }

/* =========================================================================
   MARQUEE (stack keywords)
   ========================================================================= */
.marquee {
  --speed: 46s;
  position: relative; overflow: hidden;
  padding-block: 1.1rem;
  border-block: 1px solid var(--rule-soft);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.marquee-track { display: flex; inline-size: max-content; animation: marq var(--speed) linear infinite; }
.marquee:hover .marquee-track { animation-play-state: paused; }
.marquee-run { display: flex; align-items: center; gap: 2.4rem; padding-inline-end: 2.4rem; }
.marquee-run span {
  font-family: var(--f-mono); font-size: 0.72rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--mute); white-space: nowrap;
}
.marquee-run span::before { content: "▪"; color: var(--fl-cyan); margin-inline-end: 0.9em; font-size: 0.6em; vertical-align: 0.25em; }
@keyframes marq { to { transform: translateX(-50%); } }

/* =========================================================================
   GRIDS
   ========================================================================= */
.grid { display: grid; gap: clamp(0.9rem, 1.6vw, 1.3rem); }
.g-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); }
.g-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 270px), 1fr)); }
.g-4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr)); }

.section-head { display: grid; gap: 1rem; margin-block-end: clamp(2rem, 4vw, 3.2rem); max-width: 70ch; }

/* =========================================================================
   DEVICE SHOT — a screenshot that leans out of the page as you scroll
   ========================================================================= */
.shot-stage { perspective: 1600px; perspective-origin: 50% 40%; }

.device-shot {
  --hue: var(--fl-cyan);
  position: relative;
  border-radius: var(--r-lg);
  padding: 8px;
  border: 1px solid color-mix(in srgb, var(--hue) 24%, var(--panel-brd));
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  transform-style: preserve-3d;
  transform:
    translate3d(0, var(--tilt-y, 0px), var(--tilt-z, 0px))
    rotateX(var(--tilt-x, 0deg));
  transition: box-shadow var(--t-mid) var(--e-out);
  box-shadow:
    inset 0 1px 0 var(--panel-lip),
    0 2px 6px var(--sh-1),
    0 24px 50px -16px var(--sh-2),
    0 60px 110px -40px var(--sh-3),
    0 0 70px -30px color-mix(in srgb, var(--hue) 60%, transparent);
  will-change: transform;
}
.device-shot img { inline-size: 100%; block-size: auto; border-radius: calc(var(--r-lg) - 8px); }
.device-shot[data-lightbox] { cursor: zoom-in; }
.device-shot[data-lightbox]:focus-visible {
  outline: 2px solid var(--hue);
  outline-offset: 5px;
}

/* -------------------------------------------------------------------------
   SCROLLING SHOT — a tall screenshot cropped to a 3:4 window you drag
   vertically. The image is always full-width; only the window scrolls.
   ------------------------------------------------------------------------- */
.shot-view { position: relative; }

.shot-scroll {
  aspect-ratio: 3 / 4;
  overflow-x: hidden;
  overflow-y: auto;
  border-radius: calc(var(--r-lg) - 8px);
  background: color-mix(in srgb, var(--hue) 5%, transparent);
  cursor: grab;
  /* let the browser own vertical panning on touch; we only take over on mouse */
  touch-action: pan-y;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--hue) 40%, transparent) transparent;
}
.shot-scroll.is-drag { cursor: grabbing; }
.shot-scroll::-webkit-scrollbar { inline-size: 6px; }
.shot-scroll::-webkit-scrollbar-track { background: transparent; }
.shot-scroll::-webkit-scrollbar-thumb {
  border-radius: 999px;
  background: color-mix(in srgb, var(--hue) 40%, transparent);
}
/* the image itself: full width, natural height, square corners (the window
   already carries the radius) */
.shot-scroll img {
  display: block; inline-size: 100%; block-size: auto;
  border-radius: 0;
  user-select: none; -webkit-user-select: none; -webkit-user-drag: none;
}

/* bottom fade — a hint that there is more image below the crop */
.shot-view::after {
  content: ""; position: absolute; inset-inline: 0; inset-block-end: 0;
  block-size: 10%; pointer-events: none;
  border-end-start-radius: calc(var(--r-lg) - 8px);
  border-end-end-radius: calc(var(--r-lg) - 8px);
  background: linear-gradient(to bottom, transparent, var(--shot-fade, rgb(0 0 0 / 0.5)));
  opacity: 1; transition: opacity var(--t-mid) var(--e-out);
}
.shot-view.is-end::after { opacity: 0; }

/* drag affordance: a little scroll-track glyph with a bobbing dot */
.shot-hint {
  display: flex; flex-direction: column; align-items: center; gap: 0.25em;
  padding-block: 0.6rem 0;
  font-family: var(--f-mono); font-size: 0.42rem; letter-spacing: 0.09em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ink) 44%, transparent);
}
/* narrow tiles wrap the label; keep it centred under the glyph */
.shot-hint span { text-align: center; text-wrap: balance; }
.shot-hint svg { block-size: 2.4em; inline-size: auto; flex: none; }
.shot-hint .sh-track { fill: none; stroke: currentColor; stroke-width: 1.5; opacity: 0.6; }
.shot-hint .sh-chev {
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round; opacity: 0.8;
}
.shot-hint .sh-dot { fill: var(--hue, currentColor); }
@keyframes shot-hint-bob {
  0%, 100% { transform: translateY(-3.6px); }
  50%      { transform: translateY(3.6px); }
}
.shot-hint .sh-dot { animation: shot-hint-bob 2.2s var(--e-out) infinite; }

.device-shot figcaption {
  font-family: var(--f-mono); font-size: 0.66rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mute);
  padding: 0.9rem 0.3rem 0.15rem;
}
/* small caption tucked under the bottom-left corner of the frame */
.device-shot figcaption.corner {
  font-size: 0.6rem; letter-spacing: 0.16em;
  padding: 0.55rem 0.2rem 0.1rem; text-align: start;
}
/* -------------------------------------------------------------------------
   SHOT PLANE — two shots that tilt together, so they read as one surface
   leaning out of the page rather than two independently hinged cards. The
   tilt lives on this wrapper; the figures inside must not carry data-tilt.
   ------------------------------------------------------------------------- */
.shot-plane {
  display: grid; gap: clamp(1rem, 2.2vw, 1.8rem);
  grid-template-columns: 1fr;
  max-inline-size: 1040px; margin-inline: auto;
  transform-style: preserve-3d;
  transform:
    translate3d(0, var(--tilt-y, 0px), var(--tilt-z, 0px))
    rotateX(var(--tilt-x, 0deg));
  will-change: transform;
}
@media (min-width: 800px) {
  .shot-plane { grid-template-columns: 1fr 1fr; align-items: start; }
  /* Equal columns, and the crop borrows the photograph's aspect ratio, so the
     two images are the same rectangle rather than two near-misses. Overrides
     the 3:4 default only inside the plane. */
  .shot-plane .shot-scroll { aspect-ratio: 1139 / 1381; }
}
/* The --tilt-* custom properties inherit into the figures, whose own transform
   reads them too. Left alone, every child would apply the plane's rotation a
   second time. The plane owns the tilt; the figures sit flat on it. */
.shot-plane .device-shot { transform: none; will-change: auto; }

/* a screenshot with its own explanation underneath */
.surface { display: grid; gap: 0.35rem; align-content: start; }
.surface h3 { margin-block-start: 0.5rem; }
.surface p { font-size: 0.95rem; }

.shot-pair { display: grid; gap: clamp(0.9rem, 2vw, 1.4rem); grid-template-columns: 1fr; }
@media (min-width: 900px) { .shot-pair { grid-template-columns: 1.55fr 1fr; align-items: start; } }

/* =========================================================================
   PROJECT CARDS — front-page portfolio grid
   ========================================================================= */
.proj-grid { display: grid; gap: clamp(1rem, 2vw, 1.5rem); grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr)); }

.proj-card {
  --hue: var(--fl-cyan);
  position: relative; display: flex; flex-direction: column;
  border-radius: var(--r-lg); overflow: hidden; text-decoration: none;
  border: 1px solid color-mix(in srgb, var(--hue) 24%, var(--panel-brd));
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  box-shadow:
    inset 0 1px 0 var(--panel-lip),
    inset 0 -30px 42px -30px color-mix(in srgb, var(--hue) 100%, transparent),
    0 1px 2px var(--sh-1), 0 10px 24px -8px var(--sh-2), 0 34px 60px -22px var(--sh-3);
  transition: transform var(--t-mid) var(--e-out), box-shadow var(--t-mid) var(--e-out),
              border-color var(--t-mid) var(--e-out);
}
/* fluoro hairline along the bottom edge, matching the accented label */
.proj-card::after {
  content: ""; position: absolute; inset-inline: 16%; inset-block-end: 0;
  block-size: 1px; border-radius: 2px; pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--hue), transparent);
  opacity: calc(var(--glow-strength) * 0.85);
}
a.proj-card:hover {
  transform: translateY(-4px);
  border-color: color-mix(in srgb, var(--hue) 52%, var(--panel-brd));
  box-shadow:
    inset 0 1px 0 var(--panel-lip),
    inset 0 -22px 34px -26px color-mix(in srgb, var(--hue) 92%, transparent),
    0 1px 2px var(--sh-1), 0 16px 34px -8px var(--sh-2), 0 46px 80px -24px var(--sh-3),
    0 0 48px -16px color-mix(in srgb, var(--hue) 62%, transparent);
}

.proj-media {
  position: relative; aspect-ratio: 16 / 10; overflow: hidden;
  border-block-end: 1px solid color-mix(in srgb, var(--hue) 18%, var(--panel-brd));
  background: color-mix(in srgb, var(--hue) 7%, transparent);
}
.proj-media img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }

/* Obvious, on-brand placeholder — clearly not a finished asset. */
.proj-media.is-placeholder {
  display: grid; place-items: center; gap: 0.5rem;
  background:
    repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--hue) 9%, transparent) 0 10px,
      transparent 10px 20px),
    color-mix(in srgb, var(--hue) 6%, transparent);
}
.proj-media.is-placeholder::after {
  content: "Image pending";
  font-family: var(--f-mono); font-size: 0.62rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: color-mix(in srgb, var(--ink) 46%, transparent);
  border: 1px dashed color-mix(in srgb, var(--ink) 26%, transparent);
  padding: 0.5em 0.9em; border-radius: 999px;
}

.proj-body { padding: clamp(1.1rem, 2vw, 1.5rem); display: grid; gap: 0.45rem; align-content: start; flex: 1; }
.proj-meta {
  font-family: var(--f-mono); font-size: 0.62rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ac, var(--mute));
}
.proj-name { font-family: var(--f-display); font-weight: 400; font-size: 1.2rem; letter-spacing: -0.02em; color: var(--ink); line-height: 1.15; }
.proj-desc { color: var(--mute); font-size: 0.95rem; }
.proj-more {
  margin-block-start: auto; padding-block-start: 0.7rem;
  font-family: var(--f-mono); font-size: 0.63rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ac, var(--ink));
  display: inline-flex; align-items: center; gap: 0.5em;
}
a.proj-card:hover .proj-more .arw { transform: translateX(3px); }
.proj-more .arw { transition: transform var(--t-fast) var(--e-out); }

/* draft marker so unfinished copy is impossible to miss */
.is-draft .proj-desc, .draft-text {
  color: var(--dim); font-style: italic;
  border-inline-start: 2px dashed color-mix(in srgb, var(--fl-orange) 55%, transparent);
  padding-inline-start: 0.7rem;
}
.proj-card.featured { grid-column: 1 / -1; }
@media (min-width: 880px) {
  .proj-card.featured { flex-direction: row; align-items: stretch; }
  .proj-card.featured .proj-media { aspect-ratio: auto; inline-size: 54%; border-block-end: 0;
    border-inline-end: 1px solid color-mix(in srgb, var(--hue) 18%, var(--panel-brd)); }
  .proj-card.featured .proj-body { inline-size: 46%; align-content: center; gap: 0.6rem; padding: clamp(1.5rem, 3vw, 2.4rem); }
  .proj-card.featured .proj-name { font-size: clamp(1.5rem, 2.4vw, 2rem); }
}


/* =========================================================================
   PROJECT CARD SPECTRUM
   The work grid is one rainbow, red through to indigo, spread once across the
   whole list — no repeats, so position in the grid reads as position in the
   sweep. Six h-* fluoros cannot do that many distinct steps, so these cards
   opt out of the hue helpers and take an OKLCH ramp instead: even spacing in
   OKLCH keeps the steps looking even, where plain HSL clumps in the greens.
   --ac is derived from --hue because fluoro is legible on charcoal but has to
   be darkened on grey. Add or remove a card and the ramp below needs its
   stop count changed to match.
   ========================================================================= */
.proj-grid > .proj-card { --eyebrow-c: var(--hue); --ac: var(--hue); }
:root[data-theme="light"] .proj-grid > .proj-card {
  --ac: color-mix(in oklab, var(--hue) 48%, #0B0D11);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .proj-grid > .proj-card {
    --ac: color-mix(in oklab, var(--hue) 48%, #0B0D11);
  }
}
.proj-grid > .proj-card:nth-child(1)  { --hue: oklch(0.8 0.21 25); }
.proj-grid > .proj-card:nth-child(2)  { --hue: oklch(0.8 0.21 47); }
.proj-grid > .proj-card:nth-child(3)  { --hue: oklch(0.8 0.21 69); }
.proj-grid > .proj-card:nth-child(4)  { --hue: oklch(0.8 0.21 91); }
.proj-grid > .proj-card:nth-child(5)  { --hue: oklch(0.8 0.21 113); }
.proj-grid > .proj-card:nth-child(6)  { --hue: oklch(0.8 0.21 135); }
.proj-grid > .proj-card:nth-child(7)  { --hue: oklch(0.8 0.21 158); }
.proj-grid > .proj-card:nth-child(8)  { --hue: oklch(0.8 0.21 180); }
.proj-grid > .proj-card:nth-child(9)  { --hue: oklch(0.8 0.21 202); }
.proj-grid > .proj-card:nth-child(10) { --hue: oklch(0.8 0.21 224); }
.proj-grid > .proj-card:nth-child(11) { --hue: oklch(0.8 0.21 246); }
.proj-grid > .proj-card:nth-child(12) { --hue: oklch(0.8 0.21 268); }
.proj-grid > .proj-card:nth-child(13) { --hue: oklch(0.8 0.21 290); }

/* =========================================================================
   MEASURE BARS
   ========================================================================= */
.measure { display: grid; gap: 1.1rem; margin-block-start: 1.2rem; }
.measure-row { display: grid; gap: 0.4rem; }
.measure-head {
  display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  font-family: var(--f-mono); font-size: 0.66rem; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--mute);
}
.measure-head b { font-family: var(--f-display); font-weight: 400; font-size: 1.05rem; letter-spacing: -0.01em; color: var(--ink); text-transform: none; }
.measure-track {
  position: relative; block-size: 26px; border-radius: 7px;
  background: var(--rule-soft); border: 1px solid var(--panel-brd); overflow: hidden;
}
.measure-fill {
  position: absolute; inset-block: 0; inset-inline-start: 0; border-radius: 6px;
  background: linear-gradient(180deg,
    color-mix(in srgb, #fff 30%, var(--hue)) 0%, var(--hue) 48%,
    color-mix(in srgb, #000 20%, var(--hue)) 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.55),
              0 0 22px -6px color-mix(in srgb, var(--hue) calc(var(--glow-strength) * 85%), transparent);
}
.measure-val {
  position: absolute; inset-block: 0; display: flex; align-items: center;
  font-family: var(--f-mono); font-size: 0.7rem; letter-spacing: 0.06em; color: var(--ink);
  padding-inline: 0.6rem;
}

/* loop diagram */
.loop svg { inline-size: 100%; block-size: auto; max-inline-size: 640px; margin-inline: auto; display: block; }
.loop .l-node { fill: var(--panel-a); stroke: color-mix(in srgb, var(--hue, var(--fl-cyan)) 45%, var(--panel-brd)); }
.loop .l-txt { font-family: var(--f-mono); font-size: 8.5px; letter-spacing: 0.11em; fill: var(--ink); text-transform: uppercase; }
.loop .l-sub { font-family: var(--f-body); font-size: 8px; fill: var(--mute); }
.loop .l-arc { fill: none; stroke: var(--fl-cyan); stroke-width: 1.5; stroke-dasharray: 4 4; opacity: 0.75; }

/* =========================================================================
   DEMO CALL TO ACTION
   ========================================================================= */
.demo-cta {
  margin-block-start: clamp(1.6rem, 3vw, 2.4rem);
  display: flex; flex-wrap: wrap; gap: 1.4rem 2.5rem;
  align-items: center; justify-content: space-between;
}
.demo-cta .btn { flex: none; }

/* =========================================================================
   STAT STRIP
   ========================================================================= */
.stat-strip {
  display: grid; gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr));
  border-radius: var(--r-lg); overflow: hidden;
  border: 1px solid var(--panel-brd);
  background: var(--rule-soft);
  box-shadow: inset 0 1px 0 var(--panel-lip), 0 14px 34px -14px var(--sh-2);
}
.stat {
  padding: clamp(1.1rem, 2vw, 1.6rem);
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  display: grid; gap: 0.3rem; align-content: start;
}
.stat-n {
  font-family: var(--f-display); font-weight: 300;
  font-size: clamp(1.7rem, 3.4vw, 2.5rem); letter-spacing: -0.03em; line-height: 1;
  color: var(--ink);
}
.stat-n em { font-style: normal; font-size: 0.5em; color: var(--ac, var(--mute)); letter-spacing: 0; }
.stat-l {
  font-family: var(--f-mono); font-size: 0.63rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mute); line-height: 1.45;
}

/* =========================================================================
   TIMELINE
   ========================================================================= */
.timeline { position: relative; display: grid; gap: clamp(1rem, 2vw, 1.4rem); padding-inline-start: 1.9rem; }
.timeline::before {
  content: ""; position: absolute; inset-block: 0.9rem 1.2rem; inset-inline-start: 5px;
  inline-size: 2px; border-radius: 2px;
  background: linear-gradient(180deg,
    var(--fl-cyan), var(--fl-lime), var(--fl-yellow), var(--fl-magenta), transparent);
  opacity: 0.55;
}
.tl-item { position: relative; }
.tl-item::before {
  content: ""; position: absolute; inset-inline-start: -1.9rem; inset-block-start: 1.55rem;
  inline-size: 12px; block-size: 12px; border-radius: 4px;
  background: var(--hue, var(--fl-cyan));
  border: 2px solid var(--bg);
  box-shadow: 0 0 14px color-mix(in srgb, var(--hue, var(--fl-cyan)) 75%, transparent);
}
.tl-when {
  font-family: var(--f-mono); font-size: 0.64rem; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--ac, var(--mute)); margin-block-end: 0.35rem;
}
.tl-what { font-family: var(--f-display); font-weight: 400; font-size: 1.12rem; letter-spacing: -0.015em; color: var(--ink); margin-block-end: 0.4rem; }

/* =========================================================================
   TAGS
   ========================================================================= */
.tags { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.tag {
  font-family: var(--f-mono); font-size: 0.62rem; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--mute);
  padding: 0.42em 0.75em; border-radius: 999px;
  border: 1px solid var(--rule); background: var(--rule-soft);
  white-space: nowrap;
}
.tag.on { color: var(--ac, var(--ink)); border-color: color-mix(in srgb, var(--hue, var(--fl-cyan)) 46%, transparent); }

/* =========================================================================
   CV
   ========================================================================= */
.cv-role { display: grid; gap: 0.55rem; padding-block: clamp(1.3rem, 2.4vw, 1.9rem); border-block-start: 1px solid var(--rule-soft); }
.cv-role:first-of-type { border-block-start: 0; }
.cv-head { display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; align-items: baseline; justify-content: space-between; }
.cv-title { font-family: var(--f-display); font-weight: 400; font-size: clamp(1.08rem, 1.7vw, 1.32rem); letter-spacing: -0.018em; color: var(--ink); }
.cv-org { color: var(--ac, var(--mute)); }
.cv-when { font-family: var(--f-mono); font-size: 0.66rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--mute); white-space: nowrap; }
.cv-role ul { margin: 0; padding-inline-start: 1.05rem; display: grid; gap: 0.28rem; }
.cv-role li { color: var(--ink-2); }
.cv-role li::marker { color: var(--hue, var(--fl-cyan)); }

.skill-block { display: grid; gap: 0.5rem; }
.skill-block h4 {
  font-family: var(--f-mono); font-size: 0.64rem; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--ac, var(--mute));
}

/* =========================================================================
   ARCHITECTURE DIAGRAM
   ========================================================================= */
.arch { inline-size: 100%; block-size: auto; overflow-x: auto; }
.arch svg { inline-size: 100%; min-inline-size: 620px; block-size: auto; }
.arch .a-box { fill: var(--panel-a); stroke: var(--panel-brd); }
.arch .a-lbl { font-family: var(--f-mono); font-size: 9px; letter-spacing: 0.1em; fill: var(--mute); text-transform: uppercase; }
.arch .a-name { font-family: var(--f-display); font-size: 13px; font-weight: 400; fill: var(--ink); }
.arch .a-line { stroke: var(--rule); stroke-width: 1.2; fill: none; }
.arch .a-flow { stroke-width: 1.6; fill: none; stroke-dasharray: 4 5; }

/* =========================================================================
   WORK CARDS
   ========================================================================= */
.work-when {
  font-family: var(--f-mono); font-size: 0.63rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ac, var(--mute));
}
.work-title { font-family: var(--f-display); font-weight: 400; font-size: 1.24rem; letter-spacing: -0.02em; color: var(--ink); }

/* =========================================================================
   CALLOUT
   ========================================================================= */
.callout {
  --hue: var(--fl-lime);
  border-inline-start: 2px solid var(--hue);
  padding-inline-start: 1.2rem;
  font-size: 1.06rem; color: var(--ink-2); max-width: 62ch;
}

/* =========================================================================
   PORTRAIT
   ========================================================================= */
/* Byline scale on purpose: small enough to read as a human touch rather than
   as a headshot being assessed. */
.byline { display: flex; align-items: center; gap: 1rem; margin-block-end: 1.6rem; }
.byline .portrait { inline-size: 88px; flex: none; padding: 4px; border-radius: 16px; }
.byline .portrait img { border-radius: 12px; }
.byline .portrait::after { inset: 4px; border-radius: 12px; opacity: 0.5; }
.byline-meta { display: grid; gap: 0.15rem; }
.byline-name { font-family: var(--f-display); font-weight: 400; font-size: 1.05rem; color: var(--ink); letter-spacing: -0.015em; }
.byline-role { font-family: var(--f-mono); font-size: 0.63rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--mute); }

.portrait-wrap { display: grid; align-content: start; justify-items: start; }
.portrait {
  --hue: var(--fl-cyan);
  position: relative; margin: 0;
  inline-size: min(100%, 168px);
  border-radius: var(--r-lg);
  padding: 7px;
  border: 1px solid color-mix(in srgb, var(--hue) 22%, var(--panel-brd));
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(var(--panel-sat));
  box-shadow:
    inset 0 1px 0 var(--panel-lip),
    0 2px 4px var(--sh-1),
    0 18px 36px -12px var(--sh-2),
    0 44px 80px -28px var(--sh-3);
}
.portrait img {
  inline-size: 100%; block-size: auto;
  border-radius: calc(var(--r-lg) - 7px);
  display: block;
  filter: contrast(1.05);
}
/* The portrait is monochrome, so a fluoro wash across it reads as a deliberate
   duotone rather than a stray tint — and ties the photo to the palette. */
.portrait::after {
  content: ""; position: absolute; inset: 7px; border-radius: calc(var(--r-lg) - 7px);
  pointer-events: none;
  background: linear-gradient(155deg,
    color-mix(in srgb, var(--fl-cyan) 34%, transparent) 0%,
    color-mix(in srgb, var(--fl-cyan) 10%, transparent) 32%,
    transparent 58%,
    color-mix(in srgb, var(--fl-violet) 30%, transparent) 100%);
  mix-blend-mode: soft-light;
}

/* =========================================================================
   FOOTER
   ========================================================================= */
footer {
  border-block-start: 1px solid var(--rule-soft);
  padding-block: clamp(3rem, 6vw, 5rem) 2.5rem;
  margin-block-start: clamp(4rem, 8vw, 7rem);
}
.foot-grid { display: flex; flex-wrap: wrap; gap: 2rem 3rem; align-items: flex-start; justify-content: space-between; }
.foot-links { display: grid; gap: 0.5rem; }
.foot-links a {
  font-family: var(--f-mono); font-size: 0.74rem; letter-spacing: 0.06em;
  color: var(--mute); text-decoration: none; transition: color var(--t-fast);
  display: inline-flex; align-items: center; gap: 0.5em;
}
.foot-links a:hover { color: var(--ink); }
.foot-links a::before { content: "→"; color: var(--fl-lime); opacity: 0.7; }
.colophon { font-family: var(--f-mono); font-size: 0.66rem; letter-spacing: 0.1em; color: var(--dim); text-transform: uppercase; }

/* utilities */
.sr-only {
  position: absolute; inline-size: 1px; block-size: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.stack { display: grid; gap: 1rem; }
.stack-sm { display: grid; gap: 0.5rem; }
.row { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }

/* =========================================================================
   LIGHTBOX — a full-viewport screenshot viewer. Opens fitted to the width of
   the screen (tall captures stay readable), then zooms with the wheel or a
   two-finger pinch and pans by dragging. Built once, lazily, by site.js.
   ========================================================================= */
.lightbox {
  position: fixed; inset: 0; z-index: 200;
  background: color-mix(in srgb, var(--bg) 30%, rgb(0 0 0 / 0.88));
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  opacity: 0; visibility: hidden;
  transition: opacity 220ms var(--e-out), visibility 220ms linear;
  overflow: hidden; overscroll-behavior: contain; touch-action: none;
}
.lightbox.is-open { opacity: 1; visibility: visible; }

.lightbox-img {
  position: absolute; inset-block-start: 0; inset-inline-start: 0;
  transform-origin: 0 0;
  max-inline-size: none; max-block-size: none;
  will-change: transform;
  cursor: grab;
  user-select: none; -webkit-user-select: none; -webkit-user-drag: none;
}
.lightbox.is-drag .lightbox-img { cursor: grabbing; }

.lightbox-close {
  position: absolute; inset-block-start: clamp(0.7rem, 2vw, 1.2rem);
  inset-inline-end: clamp(0.7rem, 2vw, 1.2rem);
  z-index: 2;
  display: grid; place-items: center;
  inline-size: 2.6rem; block-size: 2.6rem;
  border-radius: 999px; border: 1px solid var(--rule);
  background: var(--scrim); color: var(--ink);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--e-out), transform var(--t-fast) var(--e-out);
}
.lightbox-close:hover { border-color: var(--fl-cyan); transform: rotate(90deg); }
.lightbox-close svg { inline-size: 1.05rem; block-size: 1.05rem; }

.lightbox-bar {
  position: absolute; inset-block-end: clamp(0.7rem, 2vw, 1.2rem);
  inset-inline: 0; z-index: 2;
  display: flex; justify-content: center; pointer-events: none;
}
.lightbox-bar span {
  font-family: var(--f-mono); font-size: 0.58rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mute);
  background: var(--scrim); border: 1px solid var(--rule);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  padding: 0.5em 1em; border-radius: 999px; text-align: center;
  max-inline-size: min(92vw, 60ch);
}

/* =========================================================================
   HUE HELPERS — deliberately last.
   Components (.panel, .proj-card, .device-shot, .portrait, .gh-card) each
   declare a fallback `--hue`. Because these helpers have equal specificity,
   they must come later in the source to win. Moving them earlier silently
   flattens every card to its component default.
   ========================================================================= */
.h-lime    { --hue: var(--fl-lime);    --eyebrow-c: var(--fl-lime);    --ac: var(--ac-lime); }
.h-cyan    { --hue: var(--fl-cyan);    --eyebrow-c: var(--fl-cyan);    --ac: var(--ac-cyan); }
.h-magenta { --hue: var(--fl-magenta); --eyebrow-c: var(--fl-magenta); --ac: var(--ac-magenta); }
.h-violet  { --hue: var(--fl-violet);  --eyebrow-c: var(--fl-violet);  --ac: var(--ac-violet); }
.h-orange  { --hue: var(--fl-orange);  --eyebrow-c: var(--fl-orange);  --ac: var(--ac-orange); }
.h-yellow  { --hue: var(--fl-yellow);  --eyebrow-c: var(--fl-yellow);  --ac: var(--ac-yellow); }
.ac { color: var(--ac, var(--ac-lime)); }
