/* ─────────────────────────────────────────────────────────────────────
   THEME
   Dark is the default and always the starting state. Light is opt-in and
   remembered. Every colour the site paints comes from a token in one of
   these two blocks, including the ones that used to be loose hex values
   in the CSS and the JS, so a theme swap needs no per-component work.

   Light accents are darkened versions of the dark ones: #29d3c6 is 1.9:1
   on white and unusable as text, so each accent has a light-mode twin
   verified at 4.5:1 or better on both surfaces.
   ───────────────────────────────────────────────────────────────────── */
:root{
  /* base */
  --void:#0d1117; --panel:#161c28; --line:#2a3448;
  /* text */
  --paper:#eef3f8; --dim:#93a1b8;
  /* accents */
  --cyan:#29d3c6; --lime:#b8e62e; --pink:#ff4d8d; --amber:#ffb224;
  /* violet joins for a fifth section. 8.28:1 on void, 7.46:1 on panel, which
     puts it in the same band as cyan and amber and above pink's 5.44 floor. */
  --violet:#b39dff;
  /* --line is structural and deliberately quiet at 1.52:1. Interactive
     controls use --edge instead, which clears the 3:1 of WCAG 1.4.11. */
  --edge:#5f6e8b;
  --panel2:#1e2636;      /* hover surface   */
  --sunk:#12242c;        /* pressed surface */
  --sunk-v:#1d1a2e;      /* pressed, violet */
  --unlit:#55688c;       /* name meter, dot not yet reached */
  --dead:#1b2430;        /* name meter, dot with nothing in it */
  --ringq:#2f3a4d;       /* portrait ring, quiet slot */
  --ringm:#7d3557;       /* portrait ring, morse slot at rest */
  --shadow:rgba(0,0,0,.45);
  --scrim:#000000;
}
:root[data-theme="light"]{
  --void:#f2f5f9; --panel:#ffffff; --line:#c4cedd;
  --paper:#0f151e; --dim:#55637a;
  --cyan:#0d7a70; --lime:#4a7000; --pink:#c2185b; --amber:#8a5300;
  --violet:#5847c4;
  --edge:#7f8c9f;
  --panel2:#eaeff6;
  --sunk:#e2f1ef;
  --sunk-v:#eeeafc;
  /* The wordmark is built from these two. At #a8b4c6 the unlit half of the name
     sat at 1.92:1 on the light background and simply could not be read before
     you scrolled. 4.48:1 puts it at the text bar, and above dark's own 3.37. */
  --unlit:#647285;
  --dead:#d2dae6;
  --ringq:#ccd5e2;
  --ringm:#e8a6c0;
  --shadow:rgba(15,21,30,.18);
  --scrim:#0f151e;
}
:root{

  --body:"Segoe UI",system-ui,-apple-system,Roboto,Helvetica,Arial,sans-serif;
  --mono:Consolas,"Cascadia Mono",ui-monospace,"Courier New",monospace;

  --px:4px;              /* base pixel unit */
  --gap:16px; --gap2:32px; --gap3:64px; --gap4:96px;
  --maxw:1160px;
  --ease-step:steps(5,end);
  --ease-spring:cubic-bezier(.2,.9,.3,1.4);
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth;background:var(--void)}
body{
  font-family:var(--body); background:var(--void); color:var(--paper);
  font-size:18px; line-height:1.65; -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img,canvas,svg,video{display:block}
/* Every pixel-type SVG is filled from a custom property rather than a baked
   hex, so it follows the theme. Any rule that flips a colour for a state, like
   a pressed segment, simply out-specifies this one and wins. */
.pxsvg path,.pxsvg rect{fill:var(--pxfill,var(--paper))}
/* held only for the frame in which the theme swaps, so nothing eases across */
html.theming *,html.theming *::before,html.theming *::after{
  transition:none!important;animation:none!important;
}
canvas,img{image-rendering:pixelated;image-rendering:crisp-edges}
img.photo{image-rendering:auto}
button{font-family:inherit;cursor:pointer;border:0;background:none;color:inherit}
a{color:var(--cyan);text-decoration:none}
a:hover{text-decoration:underline}
:focus-visible{outline:3px solid var(--lime);outline-offset:3px}

/* ───────────── nav ───────────── */
header{
  position:sticky;top:0;z-index:60;background:var(--void);
  border-bottom:2px solid var(--line);
}
.nav{
  max-width:var(--maxw);margin:0 auto;padding:12px 24px;
  display:flex;align-items:center;gap:24px;
}
/* the meter is the wordmark now: it spans the bar and spells the whole name */
.navtrace{flex:1;min-width:0;height:44px;display:flex;align-items:center}
.navtrace canvas{width:100%;height:44px;cursor:pointer}
/* single call to action in the bar now, in place of the section links */
.navactions{display:flex;gap:8px;flex-shrink:0}
.resumebtn{
  display:flex;align-items:center;gap:9px;
  padding:11px 15px;border:2px solid var(--cyan);background:var(--panel);
  box-shadow:4px 4px 0 var(--shadow);
  transition:background .15s var(--ease-step),transform .1s var(--ease-spring),box-shadow .1s;
}
.resumebtn svg{height:auto;width:auto;max-width:100%}
.resumebtn,.resumebtn:hover{text-decoration:none}
.resumebtn:hover{background:var(--sunk)}
.resumebtn:active{transform:translate(3px,3px);box-shadow:1px 1px 0 var(--shadow)}

.themebtn{
  display:flex;align-items:center;padding:11px 14px;
  border:2px solid var(--edge);background:var(--panel);
  transition:border-color .15s var(--ease-step),background .15s;
}
.themebtn svg{height:auto;width:auto;max-width:100%}
.themebtn:hover,.themebtn:focus-visible{border-color:var(--cyan);background:var(--sunk)}

/* ───────────── layout ───────────── */
main{max-width:var(--maxw);margin:0 auto;padding:0 24px}
section{padding:var(--gap4) 0;border-top:2px solid var(--line)}
section:first-of-type{border-top:0}
.eyebrow{display:flex;align-items:center;gap:12px;margin-bottom:20px}
.eyebrow svg{height:auto;width:auto;max-width:100%}
.eyebrow .rule{flex:1;height:4px}
h1,h2,h3{font-weight:700;letter-spacing:-.01em;line-height:1.2}
h2.sans{font-size:clamp(28px,3.4vw,40px);max-width:22ch;margin-bottom:16px}
h3{font-size:20.5px;margin-bottom:8px}
.lede{font-size:clamp(19.5px,2.1vw,23px);color:var(--paper);max-width:62ch}
.body{color:var(--dim);max-width:66ch}
.body + .body{margin-top:12px}
.mono{font-family:var(--mono);font-size:15px;color:var(--dim);letter-spacing:.04em}
.pixhead{margin-bottom:24px}
/* sized by HEIGHT so every section heading has identical glyph size
   regardless of word length; max-width keeps long words on screen */
.pixhead svg{height:clamp(40px,6.6vw,80px);width:auto;max-width:100%}

/* ───────────── hero ───────────── */
.hero{padding:var(--gap4) 0 var(--gap3);border-top:0}
.hero .headline{margin-bottom:28px}
.hero .headline svg{width:100%;max-width:700px;height:auto}

/* dither ramp band */
.ramp{display:block;width:100%;height:56px}
.ramp span{display:block;width:100%;height:7px}

/* ───────────── slabs (hard pixel cards) ───────────── */
.slab{
  background:var(--panel);border:2px solid var(--line);padding:22px;
  box-shadow:6px 6px 0 var(--shadow);
}
/* No box is singled out any more: picking one to highlight implied a ranking
   that was never real. Every slab lights its border on hover instead, in the
   colour of the section it sits in, so emphasis follows the reader. */
section{--sect:var(--cyan)}
#about{--sect:var(--pink)}
#glint{--sect:var(--cyan)}
#gametone{--sect:var(--lime)}
#fitjourney{--sect:var(--amber)}
#nuroute{--sect:var(--violet)}
.slab{transition:border-color .15s var(--ease-step)}
.slab:hover{border-color:var(--sect)}

/* finding to feature: the finding is the setup, the feature is the payoff, so
   the payoff gets the weight. Same left-border treatment as .insight. */
.f2f{display:flex;flex-direction:column}
.f2f .body{flex:1}
.became{
  margin-top:18px;padding:13px 15px;background:var(--void);
  border-left:4px solid var(--sect);
}
.became .lbl{display:block;margin-bottom:9px}
.became .lbl svg{height:9px;width:auto;display:block}
.became .feat{
  display:block;font-size:20px;font-weight:700;line-height:1.25;
  letter-spacing:-.01em;color:var(--sect);
}
/* fills its box edge to edge; splits into columns so the measure stays sane */
.slab.wide .body{max-width:none}
@media(min-width:900px){ .slab.wide .body{column-count:2;column-gap:44px} }
.grid{display:grid;gap:20px}
.g2{grid-template-columns:repeat(auto-fit,minmax(290px,1fr))}
.g3{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
.g4{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);gap:var(--gap2);align-items:start}
@media(max-width:860px){.split{grid-template-columns:1fr}}

/* ───────────── figures (never cropped) ───────────── */
figure{margin:0;display:flex;flex-direction:column}
.frame{
  background:var(--void);border:2px solid var(--line);
  display:grid;grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr);
  place-items:center;padding:12px;
}
.frame img,.frame video{
  max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;
}
.frame.tall{aspect-ratio:4/3}
.frame.wide{aspect-ratio:16/9}
.frame.pano{aspect-ratio:64/21}
figcaption{font-family:var(--mono);font-size:15px;color:var(--dim);margin-top:10px;line-height:1.5}
figcaption b{color:var(--cyan);font-weight:400}

/* ───────────── toggle / seg ───────────── */
.seg{display:inline-flex;border:2px solid var(--edge);background:var(--panel)}
.seg button{
  padding:11px 18px;display:flex;align-items:center;gap:8px;
  transition:background .15s var(--ease-step);border-right:2px solid var(--line);
}
.seg button:last-child{border-right:0}
.seg button svg{height:auto;width:auto;max-width:100%}
.seg button:hover{background:var(--panel2)}
.seg button[aria-pressed="true"]{background:var(--cyan)}
.seg button[aria-pressed="true"] svg .fg{fill:var(--void)}

/* ───────────── ledger ───────────── */
.ledger{list-style:none}
.ledger li{
  display:grid;grid-template-columns:132px minmax(0,1fr);gap:20px;align-items:start;
  padding:15px 0;border-top:2px solid var(--line);
}
.ledger li:last-child{border-bottom:2px solid var(--line)}
.ledger .k{display:flex;align-items:center;gap:8px;padding-top:3px}
.ledger .k svg{height:auto;width:auto;max-width:100%}
.ledger .v{color:var(--dim);font-size:17px;overflow-wrap:anywhere}
.ledger .v b{color:var(--paper);font-weight:600;display:block}
@media(max-width:620px){.ledger li{grid-template-columns:1fr;gap:6px}}

/* ───────────── maze explorer ───────────── */
.maze{display:grid;grid-template-columns:250px minmax(0,1fr);gap:20px;align-items:start}
@media(max-width:760px){.maze{grid-template-columns:1fr}}
.mission{
  display:block;width:100%;text-align:left;padding:13px 15px;
  border:2px solid var(--edge);background:var(--panel);margin-bottom:10px;
  transition:transform .18s var(--ease-spring),border-color .15s,background .15s;
}
.mission:hover{transform:translateX(4px);border-color:var(--dim)}
.mission[aria-pressed="true"]{border-color:var(--cyan);background:var(--sunk);transform:translateX(4px)}
.mission .nm{display:block;font-size:16.5px;font-weight:600;line-height:1.35}
.mission .sb{display:block;font-family:var(--mono);font-size:14px;color:var(--dim);margin-top:3px}
.mission[aria-pressed="true"] .sb{color:var(--cyan)}
.metric{margin-bottom:18px}
.metric .lab{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:7px}
.metric .lab svg{height:auto;width:auto;max-width:100%}
.pixbar{display:flex;gap:3px;height:18px}
.pixbar i{
  flex:1;background:var(--line);transform:scaleY(.35);transform-origin:center;
  transition:transform .3s var(--ease-spring),background .2s;
}
.pixbar i.on{transform:scaleY(1)}
.insight{
  margin-top:6px;padding:14px 16px;background:var(--void);
  border-left:4px solid var(--cyan);font-size:16.5px;color:var(--dim);
}
/* violet variant, so nu route reuses the mission list rather than cloning it */
.mission.v[aria-pressed="true"]{border-color:var(--violet);background:var(--sunk-v)}
.mission.v[aria-pressed="true"] .sb{color:var(--violet)}
.insight.v{border-left-color:var(--violet)}

/* ───────────── gametone device ───────────── */
.device{display:grid;grid-template-columns:auto minmax(0,1fr);gap:var(--gap2);align-items:center}
@media(max-width:760px){.device{grid-template-columns:1fr;justify-items:center}}
#gtsprite{width:248px;height:248px}
.ctl label{display:flex;align-items:center;gap:8px;margin:20px 0 9px}
.ctl label svg{height:auto;width:auto;max-width:100%}
.ctl label:first-child{margin-top:0}
input[type=range]{
  -webkit-appearance:none;appearance:none;width:100%;height:24px;background:var(--panel);
  border:2px solid var(--edge);cursor:ew-resize;
}
input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;width:24px;height:28px;background:var(--lime);border:2px solid var(--void);cursor:ew-resize;
}
input[type=range]::-moz-range-thumb{width:24px;height:28px;background:var(--lime);border:2px solid var(--void);border-radius:0;cursor:ew-resize}
.readout{
  display:flex;justify-content:space-between;align-items:center;gap:12px;
  margin-top:18px;padding:12px 14px;border:2px solid var(--line);background:var(--panel);
}
.readout svg{height:auto;width:auto;max-width:100%}
#gtstate svg{height:18px;width:auto}
#gtlevel svg{height:27px;width:auto}
.readout{transition:border-color .18s,background .18s}
.readout.ok{border-color:var(--lime)}
.readout.warm{border-color:var(--amber)}
.readout.hot{border-color:var(--pink)}
.readout.pulse{background:#1b2230}
.note{margin-top:12px;padding:13px 15px;font-size:16.5px;border-left:4px solid var(--cyan);background:var(--panel);color:var(--dim)}
.note b{color:var(--cyan);font-weight:600;display:block;margin-bottom:4px;letter-spacing:.02em}
.note.ok{border-left-color:var(--lime)}    .note.ok b{color:var(--lime)}
.note.warm{border-left-color:var(--amber)} .note.warm b{color:var(--amber)}
.note.hot{border-left-color:var(--pink)}   .note.hot b{color:var(--pink)}
.note.warn{border-left-color:var(--pink)}  .note.warn b{color:var(--pink)}

/* ───────────── drag gallery ───────────── */
.rail{
  display:flex;gap:16px;overflow-x:auto;padding-bottom:14px;
  scrollbar-width:thin;scrollbar-color:var(--line) transparent;cursor:grab;
  scroll-snap-type:x proximity;
}
.rail.drag{cursor:grabbing;scroll-snap-type:none}
.rail::-webkit-scrollbar{height:10px}
.rail::-webkit-scrollbar-track{background:var(--panel)}
.rail::-webkit-scrollbar-thumb{background:var(--line)}
.rail::-webkit-scrollbar-thumb:hover{background:var(--dim)}
.rail figure{flex:0 0 clamp(260px,42vw,440px);scroll-snap-align:start}
.rail .frame{aspect-ratio:4/3;width:100%}
.rail.drag img,.rail.drag video{pointer-events:none}

/* ───────────── reveal (pixel-stepped, not fade-up-everything) ───────────── */
.rv{opacity:0;transform:translateY(14px)}
.rv.in{opacity:1;transform:none;transition:opacity .3s var(--ease-step),transform .3s var(--ease-step)}

/* ───────────── footer ───────────── */
footer{border-top:2px solid var(--line);padding:var(--gap3) 0 var(--gap2);margin-top:var(--gap3)}
.foot{max-width:var(--maxw);margin:0 auto;padding:0 24px}
.foot svg{height:auto;width:auto;max-width:100%}

/* ───────────── left dot rail ─────────────
   Desktop only, and only where a real pointer exists. Dots until hovered,
   then labels appear. Lives in the empty margin, never over the content. */
#sidenav{display:none}
/* 1280 is the first width where the centred content leaves a margin wide
   enough for the dots to sit entirely outside it */
@media (min-width:1280px) and (hover:hover){
  #sidenav{
    display:flex;flex-direction:column;gap:9px;
    position:fixed;left:14px;top:50%;transform:translateY(-50%);z-index:70;
    padding:12px 10px;
    /* Five sections put 23 stops on the rail. The 24px target floor is fixed by
       WCAG 2.5.8, so height can only come out of the gaps, and past that the rail
       has to be allowed to scroll rather than clip its last few dots off-screen.
       On any normal window it never reaches this. */
    max-height:calc(100vh - 24px);overflow-y:auto;overscroll-behavior:contain;
    scrollbar-width:none;
  }
  #sidenav::-webkit-scrollbar{width:0;height:0}
  #sidenav button{
    display:flex;align-items:center;gap:11px;background:none;border:0;
    min-height:24px;padding:0;cursor:pointer;      /* WCAG 2.5.8 target size */
  }
  /* Plain easing here on purpose: steps() makes a colour or scale change land in
     five visible stages, which across eleven dots read as a flicker. */
  #sidenav .dot{
    flex:0 0 auto;width:10px;height:10px;background:var(--line);
    transition:background .18s ease,transform .18s ease;
  }
  #sidenav .sub .dot{width:7px;height:7px;margin-left:3px}
  #sidenav .lbl{
    opacity:0;transform:translateX(-5px);pointer-events:none;
    transition:opacity .18s ease,transform .18s ease;
    /* solid backing so a label stays readable if it reaches over the content.
       3px vertical keeps the whole button at exactly the 24px WCAG 2.5.8 floor;
       at 5px it was 28px, which across 23 stops overran short windows. */
    background:var(--void);padding:3px 9px;
  }
  /* Both sizes are exact multiples of the font's 9-row grid, so the glyphs stay
     crisp either way: 2x on a wide display, 1x when the gutter is a laptop's. */
  #sidenav .lbl svg{height:18px;width:auto;display:block}
  #sidenav.lbl1x .lbl svg{height:9px}
  #sidenav.lbl1x .lbl{padding:4px 7px}
  /* Labels are only ever allowed when JS has measured that they fit entirely
     inside the gutter beside the content. Without .canlabel they are removed
     from layout altogether, so the rail collapses to dots and can never reach
     over the text. .measuring is the one frame where they are laid out so the
     width can be read. */
  #sidenav:not(.canlabel):not(.measuring) .lbl{display:none}
  #sidenav.measuring .lbl{opacity:0;transition:none}
  #sidenav.canlabel:hover .lbl,#sidenav.canlabel:focus-within .lbl{opacity:1;transform:none}
  #sidenav button:hover .dot{transform:scale(1.5)}
  #sidenav button[aria-current="true"] .dot{transform:scale(1.5)}
}
/* tighten the gaps rather than the type if the window is too short to hold them */
@media (min-width:1280px) and (hover:hover) and (max-height:840px){
  #sidenav{gap:5px}
}
@media (min-width:1280px) and (hover:hover) and (max-height:700px){
  #sidenav{gap:3px;padding:8px 10px}
}

/* ───────────── back to top ───────────── */
#totop{
  position:fixed;right:20px;bottom:20px;z-index:90;
  width:44px;height:44px;padding:0;display:flex;align-items:center;justify-content:center;
  border:2px solid var(--line);background:var(--panel);color:var(--paper);
  opacity:0;pointer-events:none;transform:translateY(10px);
  transition:opacity .2s var(--ease-step),transform .2s var(--ease-spring),
             border-color .15s,background .15s;
}
#totop.on{opacity:1;pointer-events:auto;transform:none}
#totop:hover,#totop:focus-visible{border-color:var(--cyan);background:var(--sunk)}
#totop svg{height:13px;width:auto}
@media(max-width:560px){ #totop{right:14px;bottom:14px;width:46px;height:46px} }

/* ───────────── clickable images ───────────── */
figure.zoom .frame{cursor:zoom-in;position:relative;transition:border-color .16s var(--ease-step)}
figure.zoom .frame::after{
  content:"";position:absolute;right:10px;top:10px;width:20px;height:20px;
  background:var(--cyan);opacity:0;transition:opacity .16s var(--ease-step);
  clip-path:polygon(0 0,100% 0,100% 25%,25% 25%,25% 100%,0 100%);
}
figure.zoom .frame:hover,figure.zoom .frame:focus-within{border-color:var(--cyan)}
figure.zoom .frame:hover::after{opacity:1}
figure.zoom .frame button.hit{
  position:absolute;inset:0;width:100%;height:100%;background:none;border:0;cursor:zoom-in;z-index:2;
}

/* ───────────── lightbox ───────────── */
#lb{position:fixed;inset:0;z-index:120;display:none}
#lb.open{display:block}
#lb .scrim{position:absolute;inset:0;background:rgba(7,10,16,.93);image-rendering:pixelated}
#lb .inner{
  position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:14px;padding:clamp(14px,3.5vw,44px);
}
#lb .stage{
  flex:1;min-height:0;width:100%;display:flex;align-items:center;justify-content:center;
}
#lb img,#lb video{
  max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;
  image-rendering:auto;                    /* photos are not pixel art */
  border:2px solid var(--line);background:var(--void);
}
#lb .cap{font-family:var(--mono);font-size:15px;color:var(--dim);text-align:center;max-width:70ch}
#lb .cap b{color:var(--cyan);font-weight:400}
#lb .bar{display:flex;align-items:center;gap:12px}
.pxbtn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  border:2px solid var(--line);background:var(--panel);color:var(--paper);
  padding:10px 14px;min-height:42px;transition:border-color .15s var(--ease-step),background .15s var(--ease-step);
}
.pxbtn:hover,.pxbtn:focus-visible{border-color:var(--cyan);background:var(--sunk)}
.pxbtn[disabled]{opacity:.35;cursor:not-allowed}
.pxbtn svg{height:14px;width:auto}
#lb .close{position:absolute;top:14px;right:14px;z-index:4}

/* ───────────── about disclosure ───────────── */
.disc{
  display:flex;align-items:center;gap:14px;width:100%;text-align:left;
  border:2px solid var(--line);background:var(--panel);padding:18px 20px;
  transition:border-color .16s var(--ease-step),background .16s var(--ease-step);
}
.disc:hover,.disc:focus-visible{border-color:var(--pink);background:#1b1620}
.disc .tease{flex:1;min-width:0}
.disc .tease .t{font-size:18.5px;font-weight:600;color:var(--paper)}
.disc .tease .s{font-size:16.5px;color:var(--dim);margin-top:3px}
.disc .chev{flex-shrink:0;display:flex;align-items:center}
.disc .chev svg{height:12px;width:auto;transition:transform .24s var(--ease-spring)}
.disc[aria-expanded="true"] .chev svg{transform:rotate(180deg)}
.discpanel{overflow:hidden;height:0;transition:height .3s var(--ease-spring)}
.discpanel[hidden]{display:block}          /* we animate height, not display */
.discinner{padding-top:28px}
.disc{margin-top:28px;max-width:none}
.disc .lbl-less{display:none}
.disc[aria-expanded="true"] .lbl-more{display:none}
.disc[aria-expanded="true"] .lbl-less{display:block}
/* circular portrait: floated so the prose still fills the width around it.
   The photo itself stays smooth; the pixel language lives in the dot ring. */
/* flex, not float: the collapsed panel below establishes its own formatting
   context and would shrink to avoid a float, costing the prose its full width */
/* the portrait's top edge sits level with the top of the ABOUT ME heading */
.abouttop{display:flex;gap:34px;align-items:flex-start}
.aboutcol{flex:1;min-width:0}
.aboutcol .pixhead{margin-bottom:24px}
/* the portrait moved up to the hero, so the About heading now sits on its own */
#about > .pixhead{margin-bottom:24px}
.portrait{
  position:relative;flex:0 0 auto;order:2;
  width:clamp(148px,19vw,208px);aspect-ratio:1;margin-top:0;
}
.portrait figure{margin:0}
.portrait .frame{
  border:0;background:transparent;padding:0;display:block;position:relative;
  aspect-ratio:1;border-radius:50%;overflow:hidden;
  box-shadow:0 0 0 2px var(--line);
  transition:box-shadow .18s var(--ease-step);
}
.portrait .frame::after{display:none}          /* no zoom corner marker on a portrait */
.portrait .frame img{
  position:absolute;inset:0;width:100%;height:100%;max-width:none;max-height:none;
  object-fit:cover;object-position:center;    /* the source is already a square centred crop */
  image-rendering:auto;
  transition:transform .3s var(--ease-spring);
}
.portrait:hover .frame{box-shadow:0 0 0 2px var(--pink)}
.portrait:hover .frame img{transform:scale(1.05)}
.pring{position:absolute;inset:-13px;pointer-events:none}
.pring svg{width:100%;height:100%;display:block;overflow:visible}
/* In the hero the portrait matches the height of the headline block beside it.
   The width is set in JS, because aspect-ratio cannot derive one from a
   stretched flex height: the height depends on the row, and the row depends on
   the width. The clamp below is the fallback before that measurement lands. */
@media(min-width:641px){
  .hero .portrait{width:clamp(190px,24vw,380px);max-width:min(34vw,380px)}
}
@media(max-width:640px){
  .abouttop{flex-direction:column;gap:24px;align-items:center}
  .portrait{order:0;width:clamp(150px,44vw,190px);margin:0}
  .aboutcol{width:100%}
}

/* About is the one place on the site where reading is the point */
.apara{font-size:19.5px;line-height:1.75;color:var(--dim);max-width:none}
.apara.first{color:var(--paper);font-size:21.5px}
.apara + .apara{margin-top:28px}

/* ───────────── gametone deck ───────────── */
/* no scroll capture at all: the deck is an ordinary block and the arrows and
   position markers are the only way it advances */
.deckscroll{position:relative}
.deckstick{position:static}
.deckstage{
  position:relative;width:100%;aspect-ratio:16/10;max-height:64vh;
  background:var(--panel);border:2px solid var(--line);
}
.deckstage .card{
  position:absolute;inset:0;margin:0;opacity:0;pointer-events:none;
  transition:transform .3s var(--ease-spring),opacity .24s var(--ease-step);
  will-change:transform,opacity;
}
.deckstage .card .frame{position:absolute;inset:0;border:0;background:transparent;padding:14px}
.deckstage .card.on{opacity:1;pointer-events:auto;z-index:5}
/* once you've been through the deck it stops holding the scroll: the tall
   track collapses and the stage becomes an ordinary block you scroll past */
.deckscroll.freed{height:auto}
.deckscroll.freed .deckstick{position:static}
.deckbar{display:flex;align-items:center;gap:14px;margin-top:16px;flex-wrap:wrap}
.ticks{display:flex;gap:5px;flex:1;min-width:120px}
/* 24px tall for WCAG 2.5.8 target size; the visible bar stays 14px via ::before */
.ticks button{
  flex:1;min-width:24px;max-width:56px;height:24px;background:none;border:0;padding:0;
  position:relative;display:block;
}
.ticks button::before{
  content:"";position:absolute;left:0;right:0;top:5px;height:14px;background:var(--line);
  transition:background .18s var(--ease-step),transform .18s var(--ease-spring);
}
.ticks button:hover::before{background:var(--dim)}
.ticks button[aria-current="true"]::before{background:var(--lime);transform:scaleY(1.5)}
.deckcap{font-family:var(--mono);font-size:15px;color:var(--dim);margin-top:14px;min-height:3.2em;line-height:1.55}
.deckcap b{color:var(--lime);font-weight:400}

/* ───────────── footer ───────────── */
/* compact, centred: name on one line with the three links beneath it */
footer{border-top:2px solid var(--line);padding:var(--gap2) 0;margin-top:var(--gap3)}
.foot{text-align:center}
.footname{margin-bottom:0}
.footname svg{width:min(100%,620px);height:auto;display:block;margin:0 auto}
.footrule{height:4px;margin:16px auto 18px;max-width:620px}
.footlinks{display:flex;flex-wrap:wrap;gap:10px;justify-content:center}
.flink{
  display:inline-flex;align-items:center;gap:10px;
  border:2px solid var(--line);background:var(--panel);padding:10px 14px;min-height:44px;
  text-decoration:none;color:var(--paper);position:relative;overflow:hidden;
  transition:border-color .16s var(--ease-step),transform .16s var(--ease-spring);
}
.flink .lbl{display:flex;flex-direction:column;gap:3px;text-align:left}
.flink .lbl .k svg{height:8px;width:auto}
.flink .lbl .v{font-size:15px;color:var(--dim);transition:color .16s}
.flink:hover,.flink:focus-visible{border-color:var(--cyan);transform:translate(-2px,-2px);text-decoration:none}
.flink:hover{box-shadow:4px 4px 0 var(--cyan)}
.flink:hover .lbl .v{color:var(--paper)}
.flink .arrow{margin-left:auto;opacity:.4;transition:opacity .16s,transform .16s var(--ease-spring)}
.flink .arrow svg{height:11px;width:auto}
.flink:hover .arrow{opacity:1;transform:translateX(4px)}
.footmeta{margin-top:34px;font-family:var(--mono);font-size:13px;color:#5d6b82;line-height:1.7}

/* ───────────── large desktop ─────────────
   desktop is the base; this only lets big displays breathe. Body measure is
   capped in ch on .body/.lede, so widening the shell never lengthens lines. */
/* The rail sits in the gutter beside the content, so across the laptop band the
   content gives up 60px of width to guarantee the labels have somewhere to sit
   that is not on top of the text. Above 1800 there is room for both at full
   width. Gated on hover:hover because the rail never shows on touch. */
@media(min-width:1280px) and (max-width:1799px) and (hover:hover){
  :root{--maxw:1100px}
}
@media(min-width:1800px){
  :root{--maxw:1320px}
}

/* ───────────── tablet ───────────── */
@media(max-width:1000px){
  :root{--gap4:76px;--gap3:52px}
  .g4{grid-template-columns:repeat(2,minmax(0,1fr))}   /* 4 items read better as 2×2 than 3+1 */
}
@media(max-width:820px){
  .g3{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* ───────────── narrow screens ───────────── */
@media(max-width:700px){
  /* the meter is the name, so on narrow bars it gets its own full-width row
     rather than being squeezed down to initials beside the links */
  .nav{flex-wrap:wrap;gap:4px;padding:6px 16px 8px}
  .navactions{order:1;margin:0 auto}
  .navtrace{order:2;flex:0 0 100%;height:32px}
  .navtrace canvas{height:32px}
  main,.foot{padding:0 16px}
  section{padding:56px 0}
  .hero{padding:48px 0 40px}
  #chroma{height:clamp(150px,30vh,220px)}
  h2.sans{font-size:clamp(25px,5.6vw,32px)}
  .lede{font-size:clamp(18px,4.6vw,20.5px)}
}
@media(max-width:640px){
  .g2,.g3,.g4{grid-template-columns:1fr}      /* single column: no cramped two-ups */
  .mission{padding:15px}
}
@media(max-width:560px){
  .seg{flex-direction:column;width:100%}
  .seg button{border-right:0;border-bottom:2px solid var(--line);justify-content:center;padding:13px 16px}
  .seg button:last-child{border-bottom:0}
  .rail figure{flex:0 0 84vw}
  .slab{padding:16px}
  .readout{flex-direction:column;align-items:flex-start;gap:8px}
  .chromabar,.chromafoot{flex-wrap:wrap;gap:8px}
  /* the position markers get their own row so each stays a 24px target */
  .ticks{order:2;flex:0 0 100%;min-width:0}
}
@media(max-width:400px){
  main,.foot{padding:0 12px}
  .nav{padding:8px 12px}
}

/* ───────────── short viewports (landscape phones) ───────────── */
@media(max-height:520px) and (orientation:landscape){
  section{padding:40px 0}
  .hero{padding:32px 0 28px}
  #chroma{height:132px}
  :root{--gap4:48px;--gap3:36px}
}

/* touch devices - including tablets at desktop-ish widths, which width
   queries alone would miss. Targets go to 44px+, and native scrolling
   beats a fake grab cursor. */
@media(hover:none){
  .rail{cursor:default}
  .mission:hover{transform:none}
  .homebtn,.resumebtn{min-height:44px}
  .seg button{min-height:46px}
  .mission{min-height:56px}
  input[type=range]{height:26px}
  input[type=range]::-webkit-slider-thumb{height:38px;width:20px}
  input[type=range]::-moz-range-thumb{height:38px;width:20px}
}

/* Home plus Download Resume is tight on a 320px bar. This has to sit after the
   hover:none block, or the touch padding wins and overflows. */
@media(max-width:400px){
  .homebtn,.resumebtn{padding-left:9px;padding-right:9px}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}
  .rv{opacity:1;transform:none}
  .mission:hover,.mission[aria-pressed="true"]{transform:none}
  /* the deck becomes a plain list - every photograph visible, nothing pinned */
  .deckscroll{height:auto}
  .deckstick{position:static}
  .deckstage{position:static;aspect-ratio:auto;max-height:none;display:grid;gap:16px;border:0;background:transparent}
  .deckstage .card{position:static;opacity:1!important;transform:none!important;pointer-events:auto}
  .deckstage .card .frame{position:static;border:2px solid var(--line);background:var(--void);padding:12px;aspect-ratio:16/10}
  .deckbar{display:none}
}
/* ───────────── cartridge shelf ─────────────
   The landing page's project picker. Each cart is a real link, so it is
   focusable and readable; the grip ridges and connector teeth are boxes
   rather than a repeating-gradient, keeping the no-gradient rule intact.
   --acc is set per cart in JS from the project's accent token. */
.shelfwrap{margin-top:var(--gap3);position:relative}
/* the cart drops and reseats, the way you'd push one back into the slot */
.cart.blowing{animation:reseat .5s var(--ease-spring)}
@keyframes reseat{
  0%{transform:translateY(-16px)} 40%{transform:translateY(6px)} 100%{transform:translateY(0)}
}
.puff{position:absolute;width:0;height:0;pointer-events:none;z-index:5}
.puff i{
  position:absolute;left:0;top:0;width:4px;height:4px;background:var(--dim);
  animation:puff .8s ease-out forwards;
}
@keyframes puff{
  0%{opacity:0;transform:translate(0,0)}
  18%{opacity:.85}
  100%{opacity:0;transform:translate(calc(var(--dx) * 74px), calc(var(--dy) * -52px))}
}
/* CRT degauss: backdrop-filter on an overlay, so no containing block is
   created and every position:fixed element stays exactly where it was */
#degauss{
  position:fixed;inset:0;z-index:200;pointer-events:none;
  backdrop-filter:invert(1) hue-rotate(180deg) saturate(1.6);
  animation:degauss .58s steps(7,end) forwards;
}
@keyframes degauss{
  0%{opacity:1} 55%{opacity:1} 80%{opacity:.5} 100%{opacity:0}
}
@media (prefers-reduced-motion:reduce){
  .cart.blowing{animation:none}
  .puff,#degauss{display:none}
}
.shelf{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px;
  align-items:stretch;position:relative;z-index:2;
}
.shelfbase{
  height:16px;background:var(--panel);
  border:2px solid var(--line);border-top-width:4px;
  margin-top:-4px;position:relative;z-index:1;
}
/* ── the cartridge silhouette ──────────────────────────────────────────
   Both top corners are chamfered like a moulded console cartridge. The shape
   is a clip-path on the outer link, which paints the shell edge, and the same
   shape again on .shell, which paints the plastic: two nested clips give the
   bevel a real outline instead of a raw cut. box-shadow would be clipped away
   with them, so the drop shadow is a filter, which follows the clipped shape. */
.cart{
  --chamfer:20px;
  display:block;padding:3px;text-decoration:none;color:inherit;
  background:var(--line);                     /* the shell edge */
  clip-path:polygon(var(--chamfer) 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer),
                    100% 100%, 0 100%, 0 var(--chamfer));
  filter:drop-shadow(6px 6px 0 var(--shadow));
  transition:transform .18s var(--ease-spring),background .15s var(--ease-step),filter .18s;
}
.cart:hover,.cart:focus-visible{
  transform:translateY(-16px);background:var(--acc);
  filter:drop-shadow(6px 22px 0 var(--shadow));text-decoration:none;
}
.cart.eject{transform:translateY(-30px);background:var(--acc)}
.cart .shell{
  display:flex;flex-direction:column;height:100%;
  background:var(--panel);
  clip-path:polygon(calc(var(--chamfer) - 3px) 0, calc(100% - var(--chamfer) + 3px) 0,
                    100% calc(var(--chamfer) - 3px), 100% 100%, 0 100%, 0 calc(var(--chamfer) - 3px));
}
/* grip ridges across the top, the part you'd actually grab to pull it out */
.cart .grip{display:block;padding:13px 16px 11px}
.cart .grip i{display:block;height:3px;background:var(--line);transition:background .15s}
.cart .grip i + i{margin-top:3px}
.cart:hover .grip i,.cart:focus-visible .grip i,.cart.eject .grip i{background:var(--acc)}
/* the printed label, sitting in its recess on the face of the shell */
.cart .sticker{
  display:flex;flex-direction:column;flex:1;
  margin:0 12px;border:2px solid var(--line);background:var(--void);
  transition:border-color .15s;
}
.cart:hover .sticker,.cart:focus-visible .sticker,.cart.eject .sticker{border-color:var(--acc)}
.cart .ccover{display:block;background:var(--void);border-bottom:2px solid var(--line);transition:border-color .15s}
.cart .ccover img{display:block;width:100%;height:auto}
.cart:hover .ccover,.cart:focus-visible .ccover,.cart.eject .ccover{border-bottom-color:var(--acc)}
.cart .clabel{display:block;padding:15px 14px 16px;flex:1}
.cart .cno{display:block;margin-bottom:9px}
.cart .cno svg{height:9px;width:auto;display:block}
.cart .cname{display:block;margin-bottom:13px}
.cart .cname svg{height:18px;width:auto;max-width:100%;display:block}
.cart .ctag{display:block;color:var(--paper);font-size:16px;line-height:1.45;margin-bottom:13px}
.cart .cmeta{display:block;font-family:var(--mono);font-size:13.5px;color:var(--dim);letter-spacing:.04em}
/* the edge connector, recessed into the foot of the shell */
.cart .conn{
  display:flex;gap:3px;margin:12px;padding:6px;
  background:var(--void);border:2px solid var(--line);transition:border-color .15s;
}
.cart .conn i{flex:1;height:11px;background:var(--line);transition:background .15s}
.cart:hover .conn,.cart:focus-visible .conn,.cart.eject .conn{border-color:var(--acc)}
.cart:hover .conn i,.cart:focus-visible .conn i,.cart.eject .conn i{background:var(--acc)}
@media(max-width:1000px){ .shelf{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media(max-width:560px){
  .shelf{grid-template-columns:1fr;gap:14px}
  .cart{--chamfer:26px}
  .cart:hover,.cart:focus-visible{transform:translateY(-8px)}
}

/* back to the landing page, project pages only */
.homebtn{
  display:flex;align-items:center;padding:11px 14px;
  border:2px solid var(--edge);background:var(--panel);
  transition:border-color .15s var(--ease-step),background .15s;
}
.homebtn svg{height:auto;width:auto;max-width:100%}
.homebtn,.homebtn:hover{text-decoration:none}
.homebtn:hover,.homebtn:focus-visible{border-color:var(--cyan);background:var(--sunk)}
