:root{
  --bg:#05070b;
  --panel:rgba(255,255,255,.018);
  --pane-bg:rgba(7,10,16,.74);
  --line:rgba(126,196,224,.13);
  --line-2:rgba(126,196,224,.075);
  --txt:#e7eef5;
  /* Both greys are a step lighter than they were. Measured against the panels
     they actually sit on, the old pair came out at 9.0:1 and 6.1:1, and 6.1 is
     where the secondary copy stopped being readable: light text on near black
     needs more separation than the same pair does on white. These measure 10.2
     and 8.0, which puts every text role on the page over 7:1. Size was not the
     binding constraint here, this was. */
  --dim:#a8bccd;
  --dim-2:#93a6bb;
  --cy:#4fd8ff;
  --cy-2:#8ae9ff;
  --amber:#ffc078;
  --red:#ff8a8a;
  /* One type scale, six steps, and every text role maps onto one of them.
     There were twenty-one sizes here between 9px and 16.4px, which is not a
     scale, it is an accumulation. The steps run a little larger than the sizes
     they replace, because the same size reads smaller as light text on a near
     black ground. Where a step went up, the leading under it came down, which
     holds the whole change to about 250px on a page of roughly 9,500. Adding a
     twenty-second size is the thing to not do. */
  --t-1:12px;    /* mono, uppercase, wide tracking: tags, counters, chips */
  --t-2:12.8px;  /* mono labels read as words: nav, fold titles, links */
  --t-3:13.4px;  /* mono at reading length: the consoles, the skills grid */
  --t-4:14.8px;  /* sans captions and meta that sit beside something else */
  --t-5:16px;    /* secondary paragraphs */
  --t-6:17px;    /* primary paragraphs */
  --nav-h:58px;
  --ease:cubic-bezier(.22,.61,.36,1);
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
  --sans:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--txt);
  font-family:var(--sans);font-size:16px;line-height:1.62;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
a{color:inherit;text-decoration:none}
h1,h2,h3,h4{margin:0;font-weight:600;letter-spacing:-.022em;line-height:1.14}
p{margin:0}
ul{margin:0;padding:0;list-style:none}
img{max-width:100%}
:focus-visible{outline:2px solid var(--cy);outline-offset:3px;border-radius:2px}
.skip{
  position:absolute;left:-9999px;top:0;z-index:90;
  background:var(--bg);color:var(--txt);border:1px solid var(--line);
  font-family:var(--mono);font-size:var(--t-3);padding:10px 14px;
}
.skip:focus{left:12px;top:10px}

/* ---------- atmosphere ---------- */
.glow{
  position:fixed;inset:0;z-index:1;pointer-events:none;
  background:
    radial-gradient(920px 640px at 72% 12%,rgba(79,216,255,.03),transparent 62%),
    radial-gradient(760px 520px at 8% 84%,rgba(79,216,255,.014),transparent 60%);
}
.grain{
  position:fixed;inset:0;z-index:1;pointer-events:none;
  opacity:0;mix-blend-mode:overlay;display:none;background-size:150px 150px;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='150' height='150'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

/* ---------- nav ---------- */
.nav{
  position:sticky;top:0;z-index:40;height:var(--nav-h);
  background:rgba(5,7,11,.72);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line-2);
}
.nav-in{
  max-width:1060px;margin:0 auto;height:var(--nav-h);padding:0 28px;
  display:flex;align-items:center;gap:26px;
}
.mark{display:flex;align-items:center;gap:9px;margin-right:auto;min-width:0}
.mark-dot{
  width:7px;height:7px;border-radius:50%;background:var(--cy);
  box-shadow:0 0 10px var(--cy),0 0 22px rgba(79,216,255,.6);flex:none;
}
.mark-txt{font:500 var(--t-4)/1 var(--sans);letter-spacing:.01em;white-space:nowrap}
.mark-txt span{color:var(--dim);font-family:var(--mono);font-size:var(--t-2);letter-spacing:.12em;margin-left:10px}
.nav a.nl{
  position:relative;font-family:var(--mono);font-size:var(--t-2);letter-spacing:.14em;
  text-transform:uppercase;color:var(--dim);padding:6px 0;transition:color .25s ease;white-space:nowrap;
}
.nav a.nl::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--cy);
  transform:scaleX(0);transform-origin:left;transition:transform .3s var(--ease);
}
.nav a.nl:hover{color:var(--txt)}
.nav a.nl.on{color:var(--cy-2)}
.nav a.nl.on::after{transform:scaleX(1)}
.prog{position:absolute;left:0;bottom:-1px;height:1px;width:0;background:linear-gradient(90deg,rgba(79,216,255,.15),var(--cy));box-shadow:0 0 8px rgba(79,216,255,.55)}

/* ---------- layout ---------- */
.wrap{
  position:relative;z-index:2;max-width:1060px;margin:0 auto;padding:0 28px 140px;
}
section{padding:38px 0;scroll-margin-top:calc(var(--nav-h) + 26px)}
.eyebrow{
  font-size:var(--t-6);font-weight:560;letter-spacing:-.005em;
  color:#d8e4ee;display:flex;align-items:center;gap:12px;margin:0;font-weight:400;
  scroll-margin-top:calc(var(--nav-h) + 26px);
}
.eyebrow::after{content:"";flex:1;height:1px;background:var(--line-2)}

/* panels: anything carrying body copy sits on one */
.card,.pane{
  border:1px solid var(--line-2);border-radius:6px;
  background:var(--pane-bg);
  box-shadow:0 24px 60px -30px rgba(0,0,0,.8);
  margin-top:20px;
}
/* no overflow:hidden here, it would kill position:sticky inside the card */
.card > section:first-child{border-radius:6px 6px 0 0}
.card > section:last-child,.card > details:last-child{border-radius:0 0 6px 6px}
.pane{padding:28px 30px}
.card > section{padding:28px 30px}
.card > section + section,
.card > details + section,
.card > section + details{border-top:1px solid var(--line-2)}
.pane > section{padding:0}

/* reveal */
.js [data-reveal]{opacity:0;transform:translateY(14px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
.js [data-reveal].in{opacity:1;transform:none}

/* ---------- hero ---------- */
/* overflow-x:hidden on body would make body a scroll container, and that
   silently breaks position:sticky for the rig stages. The only thing that
   overflowed was the hero canvas, so it gets clipped at its own section. */
#about{
  position:relative;overflow:hidden;
  min-height:calc(100svh - var(--nav-h));
  display:grid;grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr);gap:44px;align-items:center;
  padding:60px 0 40px;
}
/* the real robot, first thing, where the generated face used to be */
.hero-vid{margin:0;align-self:center}
.hero-vid .vid-frame{border-color:var(--line)}
.now{margin-top:16px;color:#c6d4e0;font-size:var(--t-6);line-height:1.6;max-width:52ch}
.now b{color:var(--txt);font-weight:560}
#heroRig{
  position:absolute;right:-4%;top:46%;transform:translateY(-50%);
  width:min(62%,720px);height:min(88%,620px);
  opacity:.92;pointer-events:none;
  /* the head has to fade out at its own edges or it reads as a cropped box */
  -webkit-mask-image:radial-gradient(72% 62% at 52% 50%,#000 42%,rgba(0,0,0,.55) 72%,transparent 100%);
  mask-image:radial-gradient(72% 62% at 52% 50%,#000 42%,rgba(0,0,0,.55) 72%,transparent 100%);
}
.hero-in{position:relative;z-index:1}
.kick{font-family:var(--mono);font-size:var(--t-2);letter-spacing:.22em;text-transform:uppercase;color:var(--cy);opacity:.92}
h1{font-size:clamp(2.8rem,7vw,5rem);margin:18px 0 0;letter-spacing:-.038em;font-weight:600}
.role{margin-top:10px;font-size:var(--t-6);color:var(--dim)}
.lead{
  margin-top:22px;font-size:clamp(1.14rem,1.7vw,1.4rem);line-height:1.56;color:#c6d4e0;
  max-width:46ch;font-weight:380;
  text-shadow:0 1px 18px rgba(5,7,11,.9);
}
.lead b{color:var(--txt);font-weight:560}
.meta{display:flex;flex-wrap:wrap;gap:10px;margin-top:30px}
.meta span{
  font-family:var(--mono);font-size:var(--t-1);letter-spacing:.1em;color:var(--dim);
  border:1px solid var(--line);border-radius:2px;padding:6px 11px;background:rgba(7,10,16,.6);
}
.meta span.live{color:var(--cy-2);border-color:rgba(79,216,255,.28)}
.does{margin-top:34px;display:grid;gap:11px;max-width:47ch}
.does li{display:flex;gap:13px;align-items:baseline;color:var(--dim);font-size:var(--t-6);text-shadow:0 1px 14px rgba(5,7,11,.9)}
.does b{font-family:var(--mono);font-size:var(--t-1);letter-spacing:.12em;color:rgba(79,216,255,.9);flex:none;width:22px}
.does i{font-style:normal;color:#cfdae4}
.fine{margin-top:16px;color:var(--dim-2);font-size:var(--t-5);line-height:1.56;max-width:56ch}
#about .fine{text-shadow:0 1px 14px rgba(5,7,11,.9)}
.cue{
  margin-top:46px;font-family:var(--mono);font-size:var(--t-1);letter-spacing:.2em;
  text-transform:uppercase;color:var(--dim-2);display:flex;align-items:center;gap:10px;
}
.cue::after{
  content:"";width:1px;height:26px;
  background:linear-gradient(180deg,rgba(79,216,255,.55),transparent);
  animation:cueDrop 2.4s var(--ease) infinite;
}
@keyframes cueDrop{0%{transform:scaleY(.2);transform-origin:top;opacity:0}35%{opacity:1}100%{transform:scaleY(1);transform-origin:top;opacity:0}}

/* ---------- project cards ---------- */
.pj-top{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
.pj-no{font-family:var(--mono);font-size:var(--t-1);letter-spacing:.18em;color:var(--cy);opacity:.78}
.pj-tag{font-family:var(--mono);font-size:var(--t-1);letter-spacing:.16em;color:var(--dim-2);text-transform:uppercase}
h3{font-size:1.54rem;margin:13px 0 0;letter-spacing:-.024em}
.one{margin-top:12px;color:#c5d2df;font-size:var(--t-6);line-height:1.6;max-width:58ch}
/* three short proof points under each project, for the reader who only skims */
.proof{margin-top:16px;display:grid;gap:9px;max-width:62ch}
.proof li{position:relative;padding-left:20px;color:#cfdae4;font-size:var(--t-6);line-height:1.5}
.proof li::before{content:"";position:absolute;left:2px;top:.6em;width:6px;height:6px;border-radius:50%;background:var(--cy);opacity:.8}
.proof code{font-family:var(--mono);font-size:.88em;color:#cfe6f2}
.beat{background:rgba(79,216,255,.016)}
.beat .bk{font-size:var(--t-6);font-weight:560;color:#d8e4ee}
.beat p{margin-top:9px;color:var(--dim);font-size:var(--t-6);line-height:1.56;max-width:62ch}
.beat p em{font-style:normal;color:#d6e1ea}
.beat p + p{margin-top:9px}
.chips{display:flex;flex-wrap:wrap;gap:7px;margin-top:18px}
.chips b{
  font:400 var(--t-1)/1 var(--mono);letter-spacing:.09em;color:var(--dim);
  border:1px solid var(--line);border-radius:2px;padding:5px 9px;
}

/* status chips */
.stat{display:flex;flex-wrap:wrap;gap:7px;margin-top:14px}
.stat b{
  font:400 var(--t-1)/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--dim);
  border:1px solid var(--line);border-radius:2px;padding:6px 10px;
  display:inline-flex;align-items:center;gap:7px;
}
.stat b.live{color:var(--cy-2);border-color:rgba(79,216,255,.34)}
.stat b.live::before{
  content:"";width:5px;height:5px;border-radius:50%;background:var(--cy);
  box-shadow:0 0 8px var(--cy);animation:blip 2.4s ease-in-out infinite;
}
@keyframes blip{0%,100%{opacity:1}50%{opacity:.3}}
.stat b.warm{color:var(--amber);border-color:rgba(255,192,120,.3)}
.stat b.win{color:#ffdcaa;border-color:rgba(255,192,120,.36);background:rgba(255,192,120,.055)}

/* ---------- video facades ---------- */
/* A lone video ran the full width of the card, which is a 529px-tall thumbnail
   for something you have not decided to watch yet. Three of those were a
   quarter of the page. Capped, it is still the biggest thing in its section. */
.vids{display:grid;gap:14px;margin-top:20px}
.vids:not(.trio) .vid{max-width:560px}
.vids.trio{grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:16px}
.vid{margin:0}
.vid-frame{
  position:relative;display:block;overflow:hidden;
  border:1px solid var(--line);border-radius:4px;background:#05080d;
  aspect-ratio:16 / 9;
}
.vid-btn{
  position:absolute;inset:0;width:100%;height:100%;
  padding:0;margin:0;border:0;background:none;cursor:pointer;display:block;
}
.vid-btn img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;
  transition:filter .3s ease,transform .45s ease;
}
.vid-frame::after{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:2;
  background:linear-gradient(180deg,rgba(5,7,11,0) 55%,rgba(5,7,11,.28));
}
.vid-frame[data-on]::after{display:none}
.vid-play{
  position:absolute;left:50%;top:50%;width:54px;height:54px;margin:-27px 0 0 -27px;z-index:3;
  border:1px solid rgba(79,216,255,.55);border-radius:50%;
  background:rgba(6,16,24,.55);box-shadow:0 0 22px rgba(79,216,255,.2);
  transition:border-color .25s ease,background .25s ease,transform .25s ease;
}
.vid-play::before{
  content:"";position:absolute;left:21px;top:16px;
  border-left:16px solid rgba(168,240,255,.94);
  border-top:11px solid transparent;border-bottom:11px solid transparent;
}
.vid-btn:hover img{transform:scale(1.025)}
.vid-btn:hover .vid-play{border-color:var(--cy);background:rgba(12,32,44,.7);transform:scale(1.06)}
.vid-frame:hover{border-color:rgba(79,216,255,.34)}
.vid-embed{position:absolute;inset:0;width:100%;height:100%;border:0;z-index:4}
.ph-frame img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;
  transition:filter .3s ease,transform .45s ease;
}
.ph-frame:hover img{transform:scale(1.025)}
.ph-frame:hover{border-color:rgba(79,216,255,.34)}
.vid-meta{display:block;margin-top:9px}
.vid-meta b{display:block;font:500 var(--t-4)/1.34 var(--sans);color:#d8e4ee}
/* The caption title is a link to the video on YouTube. A slow connection
   should never be a dead end, and it costs no extra furniture on the page. */
.vid-out{color:inherit;transition:color .2s ease}
.vid-out span{margin-left:5px;font-size:.82em;opacity:.55}
.vid-out:hover,.vid-out:focus-visible{color:var(--cy-2)}
.vid-out:hover span,.vid-out:focus-visible span{opacity:1}
.vid-meta i{display:block;font-style:normal;margin-top:3px;font-size:var(--t-4);color:var(--dim-2)}
.vid-lead{font-size:var(--t-4);font-weight:560;color:var(--dim);margin-top:22px}

/* ---------- spec rows ---------- */
.spec{margin:18px 0 0;border-top:1px solid var(--line-2)}
.spec > div{
  display:grid;grid-template-columns:170px minmax(0,1fr);gap:18px;
  padding:11px 0;border-bottom:1px solid var(--line-2);
}
.spec dt{
  font-size:var(--t-4);font-weight:560;
  color:#cfe0ec;padding-top:2px;
}
.spec dd{margin:0;color:var(--dim);font-size:var(--t-5);line-height:1.58}
.spec dd em{font-style:normal;color:#d6e1ea}
.spec dd code{font-family:var(--mono);font-size:.9em;color:#cfe6f2}
.plain{
  margin-top:14px;padding:12px 14px;max-width:62ch;
  border-left:2px solid rgba(79,216,255,.4);background:rgba(79,216,255,.03);
  color:#c5d2df;font-size:var(--t-6);line-height:1.6;
}
.plain b{color:#e8f2f8;font-weight:600}

/* ---------- repo link row ---------- */
.repo{
  display:inline-flex;align-items:center;gap:12px;margin-top:18px;
  border:1px solid var(--line);border-radius:3px;padding:12px 15px;background:var(--panel);
  transition:border-color .25s ease,transform .25s ease;
}
.repo:hover{border-color:rgba(79,216,255,.32);transform:translateY(-1px)}
.repo b{display:block;font:500 var(--t-4)/1.3 var(--sans);color:#dae5ee}
.repo i{display:block;font-style:normal;font-family:var(--mono);font-size:var(--t-1);letter-spacing:.1em;color:var(--dim-2);margin-top:3px}
/* Scoped to the arrow. The row's title and subtitle live in a second span, so
   the bare selector was fading the only two things in the row worth reading to
   80 per cent, which took the subtitle down to 4.2:1. */
.repo span[aria-hidden]{font-family:var(--mono);font-size:var(--t-2);color:var(--cy);opacity:.8;margin-left:6px}

/* ---------- open source strip ---------- */
.lab{margin-top:22px;border-top:1px solid var(--line-2)}
.lab-item{
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px;align-items:start;
  padding:18px 0;border-bottom:1px solid var(--line-2);
}
.lab-item h4{font-size:var(--t-6)}
.lab-item p{margin-top:7px;color:var(--dim);font-size:var(--t-5);line-height:1.58;max-width:58ch}
.lab-item .stat{margin-top:0;justify-content:flex-end}

/* ---------- skills ---------- */
.sk-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px;margin-top:22px}
.sk-grid div{
  border:1px solid var(--line-2);border-radius:3px;padding:13px 14px;background:var(--panel);
  font-family:var(--mono);font-size:var(--t-3);letter-spacing:.06em;color:#cbd7e2;
  display:flex;align-items:center;gap:10px;transition:border-color .25s ease,color .25s ease;
}
.sk-grid div::before{content:"";width:4px;height:4px;background:rgba(79,216,255,.6);flex:none;border-radius:50%}
.sk-grid div:hover{border-color:rgba(79,216,255,.3);color:var(--txt)}
.note{margin-top:18px;color:var(--dim-2);font-size:var(--t-5);max-width:56ch;line-height:1.56}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;gap:11px;margin-top:22px;
  font-family:var(--mono);font-size:var(--t-2);letter-spacing:.14em;text-transform:uppercase;
  color:var(--cy-2);border:1px solid rgba(79,216,255,.32);border-radius:2px;
  padding:12px 17px;background:rgba(79,216,255,.045);
  transition:border-color .25s ease,background .25s ease,transform .25s ease;
}
.btn:hover{border-color:var(--cy);background:rgba(79,216,255,.09);transform:translateY(-1px)}
.btn::after{content:"→";opacity:.75}

/* ---------- contact ---------- */
.mail{
  display:inline-block;margin-top:22px;font-size:clamp(1.3rem,3vw,2.05rem);font-weight:560;letter-spacing:-.028em;
  color:var(--txt);border-bottom:1px solid rgba(79,216,255,.32);padding-bottom:5px;
  transition:color .25s ease,border-color .25s ease;word-break:break-word;
}
.mail:hover{color:var(--cy-2);border-color:var(--cy)}
.links{display:flex;gap:11px;margin-top:26px;flex-wrap:wrap}
.links a{
  font-family:var(--mono);font-size:var(--t-2);letter-spacing:.12em;color:var(--dim);text-transform:uppercase;
  border:1px solid var(--line);border-radius:2px;padding:9px 14px;transition:color .25s ease,border-color .25s ease;
}
.links a:hover{color:var(--cy-2);border-color:rgba(79,216,255,.32)}
.foot{margin-top:44px;padding-top:20px;border-top:1px solid var(--line-2);display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap}
.foot span{font-family:var(--mono);font-size:var(--t-1);letter-spacing:.13em;color:var(--dim-2)}

/* ---------- annotated rigs ---------- */
/* The rig breaks out past the 1060 measure the rest of the page reads at. It
   is a diagram, not a paragraph, and the exploded view needs somewhere to put
   eight callouts. Bounded in px against the viewport so it can never start a
   horizontal scrollbar: body must not have overflow-x hidden, that silently
   kills the sticky stage. */
.rigwrap{display:grid;grid-template-columns:minmax(0,1.24fr) minmax(0,1fr);gap:30px;align-items:start}
@media (min-width:1180px){ .rigwrap{width:1180px;margin-left:calc(50% - 590px)} }
@media (min-width:1330px){ .rigwrap{width:1290px;margin-left:calc(50% - 645px)} }
/* The stage scrolls with the page. Pinning it while the list slid past read
   as the page getting stuck, on a phone most of all. */
.rig-stage{
  position:relative;
  height:min(70vh,580px);
  border:1px solid var(--line);border-radius:5px;overflow:hidden;
  background:
    repeating-linear-gradient(0deg,rgba(122,190,220,.034) 0 1px,transparent 1px 46px),
    repeating-linear-gradient(90deg,rgba(122,190,220,.034) 0 1px,transparent 1px 46px),
    radial-gradient(78% 68% at 50% 46%,rgba(79,216,255,.055),transparent 70%),
    linear-gradient(180deg,rgba(9,15,22,.92),rgba(5,8,12,.96));
}
.rig-canvas{position:absolute;inset:0;width:100%;height:100%;display:block;touch-action:pan-y;cursor:grab}
.rig-canvas:active{cursor:grabbing}
.rig-stage::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:none;
}
/* Hardware chips, pinned inside the stage so they stay with the diagram they
   drive while the feature list scrolls past. */
.hw{
  position:absolute;left:12px;right:12px;bottom:11px;z-index:3;
  display:flex;flex-wrap:wrap;gap:5px;justify-content:center;
}
.hw button{
  font-family:var(--sans);font-size:var(--t-1);letter-spacing:0;
  color:var(--dim);background:rgba(6,10,16,.72);cursor:pointer;
  border:1px solid var(--line);border-radius:2px;padding:4px 8px;
  transition:color .2s ease,border-color .2s ease,background .2s ease;
}
.hw button:hover,.hw button:focus-visible{color:var(--cy-2);border-color:rgba(79,216,255,.4)}
.hw button.on{color:var(--cy-2);border-color:rgba(79,216,255,.5);background:rgba(79,216,255,.09)}
.parts.feat li::before{color:var(--cy);opacity:.75}
.hw-line{
  margin:16px 0 0;padding-top:14px;border-top:1px solid var(--line-2);
  color:var(--dim);font-size:var(--t-5);line-height:1.56;
}
.hw-line b{color:#cddae5;font-weight:560}
.hw-line.src{margin-top:10px;padding-top:0;border-top:0;color:var(--dim-2);font-size:var(--t-4)}

/* ---------- consoles ----------
   A transcript beside the work, on its own. It used to be pinned inside the
   diagram and driving it; two things moving at once made both harder to read,
   and the diagram already had a job. */
.termwrap{padding-top:0 !important}
.term{
  margin-top:12px;max-width:660px;
  border:1px solid rgba(79,216,255,.15);border-radius:4px;
  background:linear-gradient(180deg,rgba(6,10,16,.92),rgba(4,7,11,.96));
  box-shadow:0 20px 44px -28px rgba(0,0,0,.9);
  overflow:hidden;
}
/* A real title bar. The three lights are decoration and do nothing, which is
   why they are aria-hidden along with the rest of the console; reset is the
   only control, and it sits where a window's controls sit. */
.term-hd{
  display:flex;align-items:center;gap:7px;
  padding:8px 10px 8px 12px;border-bottom:1px solid rgba(79,216,255,.1);
  background:linear-gradient(180deg,rgba(79,216,255,.04),rgba(79,216,255,.015));
  font-family:var(--mono);font-size:var(--t-1);letter-spacing:.14em;text-transform:uppercase;
  color:var(--dim-2);
}
.term-hd i{display:none;width:8px;height:8px;border-radius:50%;flex:none}
.term-hd .d1{background:rgba(255,138,138,.55)}
.term-hd .d2{background:rgba(255,192,120,.55)}
.term-hd .d3{background:rgba(110,214,155,.55)}
.term-hd span{margin-left:5px}
/* A fixed height, not a minimum. A console that grows a line at a time pushes
   everything below it down eleven times while someone is reading it. Fixed, it
   scrolls its own oldest lines away instead and the page never moves. */
.term-body{
  padding:11px 12px 13px;height:250px;overflow:hidden;
  font-family:var(--mono);font-size:var(--t-3);line-height:1.6;
}
.tl{display:flex;gap:8px;animation:tlIn .22s var(--ease) both}
@keyframes tlIn{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
.tl b{flex:none;width:9px;text-align:center;font-weight:400;color:var(--dim-2)}
.tl i{font-style:normal;color:var(--dim);word-break:break-word}
.tl-cmd b{color:var(--cy)}
.tl-cmd i{color:#e7eef5}
.tl-cmd.typing i::after{
  content:"";display:inline-block;width:6px;height:11px;margin-left:2px;
  background:var(--cy);vertical-align:-1px;animation:cur .9s steps(1) infinite;
}
@keyframes cur{0%,50%{opacity:1}51%,100%{opacity:0}}
.tl-tool b{color:var(--cy)}
.tl-tool i{color:#cfe6f2}
.tl-sub{margin-left:17px}
.tl-sub i{color:var(--dim-2)}
.tl-ok b{color:#6ed69b}
.tl-bad b{color:var(--red)}
.tl-bad i{color:#ffc9c9}
.tl-wait i,.tl-end i{color:var(--dim-2)}
.tl-end{margin-top:6px}
.termwrap .fine{margin-top:14px}
.fine code{font-family:var(--mono);font-size:.9em;letter-spacing:0;color:#cfe6f2}
.term-again{
  margin-left:auto;cursor:pointer;
  font:inherit;letter-spacing:.13em;
  color:var(--dim);background:none;
  border:1px solid rgba(126,196,224,.16);border-radius:2px;padding:4px 9px;
  transition:color .2s ease,border-color .2s ease,background .2s ease;
}
.term-again:hover,.term-again:focus-visible{
  color:var(--cy-2);border-color:rgba(79,216,255,.38);background:rgba(79,216,255,.07);
}

.pl{
  display:inline-block;margin-top:16px;
  font-family:var(--mono);font-size:var(--t-1);letter-spacing:.13em;text-transform:uppercase;
  color:var(--cy-2);border:1px solid rgba(79,216,255,.26);border-radius:2px;padding:6px 11px;
  transition:border-color .2s ease,background .2s ease;
}
.pl:hover,.pl:focus-visible{border-color:rgba(79,216,255,.5);background:rgba(79,216,255,.07)}
.pl span{opacity:.7;margin-left:4px}
.pl + .fold{margin-top:10px}

.rc{display:none;position:absolute;width:14px;height:14px;border:1px solid rgba(79,216,255,.24);pointer-events:none}
.rc-tl{top:10px;left:10px;border-right:0;border-bottom:0}
.rc-tr{top:10px;right:10px;border-left:0;border-bottom:0}
.rc-bl{bottom:10px;left:10px;border-right:0;border-top:0}
.rc-br{bottom:10px;right:10px;border-left:0;border-top:0}
.rig-meta{
  position:absolute;top:14px;left:16px;z-index:2;pointer-events:none;
  flex-direction:column;gap:3px;font-family:var(--mono);
}
.rig-meta .k{font-size:var(--t-1);letter-spacing:.16em;color:var(--dim-2)}
.rig-meta .v{font-family:var(--sans);font-size:13px;letter-spacing:0;color:#9fb2c4}
.rig-ctl{position:absolute;right:12px;bottom:44px;z-index:3;display:flex;gap:6px}
.rig-burst{
  cursor:pointer;
  font-family:var(--mono);font-size:var(--t-1);letter-spacing:.13em;text-transform:uppercase;
  color:var(--dim);background:rgba(6,12,18,.8);
  border:1px solid var(--line);border-radius:2px;padding:8px 11px;
  transition:color .25s ease,border-color .25s ease,background .25s ease;
}
.rig-burst:hover{color:var(--cy-2);border-color:rgba(79,216,255,.35)}
.rig-burst.on{color:var(--cy-2);border-color:rgba(79,216,255,.45);background:rgba(79,216,255,.09)}

.parts{margin:14px 0 0;padding:0;list-style:none;counter-reset:pt}
.parts li{
  position:relative;padding:17px 0 17px 34px;cursor:default;
  border-top:1px solid var(--line-2);
}
.parts li:first-child{border-top:0}
.parts li::before{
  counter-increment:pt;content:counter(pt,decimal-leading-zero);
  position:absolute;left:0;top:19px;
  font-family:var(--mono);font-size:var(--t-1);letter-spacing:.1em;
  color:var(--dim-2);transition:color .25s ease;
}
.parts li::after{
  content:"";position:absolute;left:19px;top:18px;bottom:18px;width:1px;
  background:var(--cy);opacity:0;transition:opacity .3s ease;
}
.parts li b{display:block;font:560 var(--t-4)/1.34 var(--sans);color:#cddae5;transition:color .25s ease}
.parts li i{display:block;font-style:normal;margin-top:5px;color:var(--dim);font-size:var(--t-5);line-height:1.56}
.parts li i em{font-style:normal;color:#cfe6f2}
.parts li.on::before{color:var(--cy)}
.parts li.on::after{opacity:.55}
.parts li.on b{color:var(--cy-2)}
.parts li:focus-visible{outline:2px solid var(--cy);outline-offset:4px;border-radius:2px}

/* A certificate is supporting evidence, not a trophy. Folded away by default,
   opens to a plain image with the credential id beside it. */
.cert{margin-top:14px}
/* Cyan at rest, the same as .pl and the fold bands, because a grey box on a
   grey page is only a control once you have already hovered it, and a phone
   never hovers anything. */
.cert summary{
  cursor:pointer;display:inline-block;list-style:none;
  font-family:var(--mono);font-size:var(--t-1);letter-spacing:.13em;text-transform:uppercase;
  color:var(--cy-2);border:1px solid rgba(79,216,255,.3);border-radius:2px;padding:7px 12px;
  background:rgba(79,216,255,.05);
  transition:color .2s ease,border-color .2s ease,background .2s ease;
  -webkit-tap-highlight-color:rgba(79,216,255,.14);
}
.cert summary::-webkit-details-marker{display:none}
.cert summary::after{content:" +";opacity:.75;margin-left:2px}
.cert[open] summary::after{content:" \2212";opacity:.75;margin-left:2px}
.cert summary:hover,.cert summary:focus-visible,.cert summary:active{
  border-color:var(--cy);background:rgba(79,216,255,.12)
}
.cert img{
  display:block;margin:14px 0 0;max-width:min(100%,560px);height:auto;
  border:1px solid var(--line);border-radius:3px;
}
.cert > p{margin:10px 0 0;font-size:var(--t-4);line-height:1.58;color:var(--dim-2);max-width:56ch}
.cert > p b{color:var(--dim);font-weight:500}

/* ---------- folds ---------- */
/* Two sizes. `.more` is a band across the card: the detail behind a project,
   which most readers skip and one reader in ten wants all of. `.fold` is a
   chip, for a single extra thing beside what is already open.
   Both summaries say what is inside and how much of it there is, so closing
   something is not the same as hiding it. */
/* The band used to be a grey label, a grey sentence, and a 22px box holding a
   plus sign parked seven hundred pixels from that label at the right edge. At
   a glance it was one more row of prose and nothing in it said it opened. Four
   changes, every one of them borrowed from a part of the page that is already
   a control: the tint is visible rather than notional, the left edge takes the
   2px cyan rule a .plain aside uses, the label takes cyan, and the marker says
   the word inside the same bordered chip as .btn. Nothing new was invented,
   the row is assembled out of things that already read as buttons. */
.more{background:rgba(79,216,255,.032)}
.more > summary{
  position:relative;cursor:pointer;list-style:none;padding:22px 30px;
  display:flex;align-items:center;gap:14px 18px;flex-wrap:wrap;
  transition:background .2s ease;
  -webkit-tap-highlight-color:rgba(79,216,255,.14);
}
.more > summary::-webkit-details-marker{display:none}
/* The same 2px cyan rule that marks a .plain aside, so the band has an edge
   the eye catches on before it reads a word of it. Inset, because the card
   rounds its bottom corners and a bar to the corner would sit outside them. */
.more > summary::before{
  content:"";position:absolute;left:0;top:14px;bottom:14px;width:2px;
  background:var(--cy);opacity:.5;border-radius:0 2px 2px 0;
  transition:opacity .2s ease;
}
.more > summary b{
  font-size:var(--t-5);
  color:var(--cy-2);font-weight:560;flex:none;transition:color .2s ease;
}
.more > summary i{
  font-style:normal;color:var(--dim-2);font-size:var(--t-4);line-height:1.5;flex:1 1 16ch;
}
/* A word, not a glyph: a plus sign is a hint, "open" is an instruction. Both
   words are held to one width so the row does not shift when it is clicked. */
.more > summary::after{
  content:"open";margin-left:auto;flex:none;
  font-family:var(--mono);font-size:var(--t-1);letter-spacing:.14em;text-transform:uppercase;
  color:var(--cy-2);min-width:78px;padding:8px 12px;text-align:center;
  border:1px solid rgba(79,216,255,.34);border-radius:2px;
  background:rgba(79,216,255,.05);
  transition:color .2s ease,border-color .2s ease,background .2s ease;
}
.more[open] > summary::after{content:"close"}
/* A phone has no hover, so the resting state above has to carry the whole
   signal and these only sharpen it. Gated on a real pointer, or a tap leaves
   the band stuck in its hover state until something else is touched. */
@media (hover:hover){
  .more > summary:hover{background:rgba(79,216,255,.06)}
  .more > summary:hover::before{opacity:.9}
  .more > summary:hover::after{color:#dcf7ff;border-color:var(--cy);background:rgba(79,216,255,.13)}
}
.more > summary:focus-visible{background:rgba(79,216,255,.06)}
.more > summary:active{background:rgba(79,216,255,.09)}
.more > summary:active::after{color:#dcf7ff;border-color:var(--cy);background:rgba(79,216,255,.16)}
.more-in > section{padding:0 30px 26px}
.more-in > section + section{padding-top:24px;border-top:1px solid var(--line-2)}
.more-in .vid-lead:first-child{margin-top:0}

.fold{margin-top:18px}
.fold > summary{
  cursor:pointer;display:inline-block;list-style:none;
  font-family:var(--mono);font-size:var(--t-1);letter-spacing:.13em;text-transform:uppercase;
  color:var(--cy-2);border:1px solid rgba(79,216,255,.3);border-radius:2px;padding:7px 12px;
  background:rgba(79,216,255,.05);
  transition:color .2s ease,border-color .2s ease,background .2s ease;
  -webkit-tap-highlight-color:rgba(79,216,255,.14);
}
.fold > summary::-webkit-details-marker{display:none}
.fold > summary::after{content:" +";opacity:.75;margin-left:2px}
.fold[open] > summary::after{content:" \2212";opacity:.75;margin-left:2px}
.fold > summary:hover,.fold > summary:focus-visible,.fold > summary:active{
  border-color:var(--cy);background:rgba(79,216,255,.12)
}
.fold .vids{margin-top:14px}

/* the pull requests, listed by what they fixed */
.prs{margin-top:10px;display:grid;gap:8px;max-width:64ch}
.prs li{position:relative;padding-left:18px;font-size:var(--t-5);line-height:1.5}
.prs li::before{content:"";position:absolute;left:2px;top:.62em;width:5px;height:5px;border-radius:50%;background:var(--cy);opacity:.8}
.prs a{color:var(--dim);border-bottom:1px solid var(--line);transition:color .2s ease,border-color .2s ease}
.prs a:hover,.prs a:focus-visible{color:var(--cy-2);border-color:var(--cy)}
.lab-item .pl{margin-top:14px}

/* what he did in a role, one line each */
.did{margin-top:8px;display:grid;gap:5px}
.did li{position:relative;padding-left:16px}
.did li::before{content:"";position:absolute;left:2px;top:.66em;width:5px;height:5px;border-radius:50%;background:var(--dim-2)}

/* a real screenshot, shown as-is */
.shot{margin:14px 0 0;max-width:620px}
.shot img{display:block;width:100%;height:auto;border-radius:8px;border:1px solid var(--line)}
.shot.build{max-width:520px;margin-top:22px}
.shot figcaption{margin-top:9px;color:var(--dim-2);font-size:var(--t-4);line-height:1.5}

/* ---------- theme toggle ----------
   Light paper is the default, dark is one click away and remembered. */
.theme{
  flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;padding:0;margin-left:-6px;cursor:pointer;
  border:1px solid var(--line);border-radius:50%;background:transparent;color:var(--dim);
  transition:color .2s ease,border-color .2s ease;
}
.theme:hover,.theme:focus-visible{color:var(--cy-2);border-color:var(--cy)}
.theme svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.theme .sun,html.dark .theme .moon{display:none}
html.dark .theme .sun{display:block}
a.mark{color:inherit;padding:9px 0}
/* thumb-sized: the video titles are links out to YouTube */
.vid-out{display:inline-block;padding:4px 0}

/* ---------- mobile ---------- */
@media (max-width:860px){
  :root{--nav-h:54px}
  .wrap{padding:0 20px 100px}
  .nav-in{padding:0 20px;gap:16px}
  .mark-txt span{display:none}
  .pane,.card > section{padding:24px 20px}
  .sk-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .vids.trio{grid-template-columns:minmax(0,1fr)}
  .lab-item{grid-template-columns:minmax(0,1fr)}
  .lab-item .stat{justify-content:flex-start;margin-top:10px}
  #about{min-height:0;grid-template-columns:minmax(0,1fr);gap:26px;padding:40px 0 30px}
  .rigwrap{grid-template-columns:minmax(0,1fr);gap:6px}
  /* On one column the feature list scrolls underneath the pinned stage, and
     the gradients alone let it show through as ghost text. A solid colour
     under the layers keeps the stage a stage. */
  .rig-stage{height:min(52vh,400px);background-color:#070b10}
  .parts li i{font-size:var(--t-4)}
  .more > summary{padding:20px}
  .more-in > section{padding:0 20px 22px}
}
/* Six items and a wordmark do not fit across a phone. Shrinking the type to
   make them fit is the wrong trade: it is already the smallest text here. So
   the strip scrolls instead, and the name stays pinned to the left edge with a
   short fade so the links look like they run underneath it. */
@media (max-width:640px){
  .nav-in{
    gap:18px;overflow-x:auto;overscroll-behavior-x:contain;
    scrollbar-width:none;-ms-overflow-style:none;
  }
  .nav-in::-webkit-scrollbar{display:none}
  .mark{
    position:sticky;left:0;z-index:1;flex:none;margin-right:4px;padding-right:16px;
    background:linear-gradient(90deg,#080b11 62%,rgba(8,11,17,0));
  }
  .nav a.nl{flex:none}
  .nav a.nl:last-child{padding-right:4px}
  /* A strip cut off at the edge reads as a broken menu, not a scrolling one.
     The fade says there is more, and the spacer lets the last link scroll
     clear of the fade. */
  /* The theme toggle has to stay on screen, so rather than a strip that
     scrolls, the phone menu drops the links that do not fit. Every section
     is still one scroll away. */
  nav.nav a.nl{font-size:14px;letter-spacing:.06em}
  nav.nav .nav-in{gap:14px}

  /* Six chips wrap to three rows on a phone, which buries a fifth of the
     diagram and lands on the exploded-view button. One row that scrolls keeps
     the stage clear and costs the same height it does on a laptop. */
  .hw{
    left:10px;right:10px;flex-wrap:nowrap;justify-content:flex-start;
    overflow-x:auto;overscroll-behavior-x:contain;
    scrollbar-width:none;-ms-overflow-style:none;
    -webkit-mask-image:linear-gradient(90deg,#000 88%,transparent);
    mask-image:linear-gradient(90deg,#000 88%,transparent);
  }
  .hw::-webkit-scrollbar{display:none}

  /* Touch targets. Measured on a 375px viewport: the hardware chips were 21px
     tall, the two stage buttons 29px and the nav links 30px. 24px is the floor
     that WCAG asks for and a thumb is nearer 44px wide, so everything a finger
     is meant to hit gets padding rather than a bigger typeface. The chips cost
     the stage nine pixels of height, which is worth it for the one control the
     whole diagram is driven by. */
  .hw{bottom:9px}
  .hw button{flex:none;padding:8px 10px}
  .rig-ctl{bottom:48px}
  .rig-burst{padding:11px 12px}
  .nav a.nl{padding:13px 0}
  .fold > summary,.cert summary{padding:10px 13px}
  .pl{padding:10px 13px}
  /* The summary is a row on a laptop: label, blurb, marker. At 375px the blurb
     was squeezed into a 16-character column, so it drops to its own line. The
     label and the button keep the first line between them, which is where a
     thumb looks and costs the band no extra height. */
  .more > summary{padding:18px 20px}
  .more > summary b{flex:1 1 auto}
  .more > summary i{order:1;flex:1 0 100%;margin-top:7px}
  .more > summary::after{min-width:72px;padding:9px 11px}

  /* A 130px window scrolling a log is worse than a subtitle, so on a phone the
     console keeps the last two lines and spans the stage. */
  .term-body{height:172px;font-size:var(--t-2);padding:10px 11px 12px;line-height:1.6}
}
@media (max-width:480px){
  /* the dot stays as the way home; every section keeps its link */
  .mark-txt{display:none}
  nav.nav a.nl{font-size:13px;letter-spacing:.02em}
  nav.nav .nav-in{gap:12px}
}
@media (max-width:370px){
  nav.nav a.nl[href="#experience"]{display:none}
}
@media (max-width:560px){
  .lead{font-size:1.02rem}
  /* sized to fit on one line down to 320px, so it never splits as gmail.co / m */
  .mail{font-size:clamp(.95rem,4.6vw,1.3rem);word-break:normal}
  .spec > div{grid-template-columns:minmax(0,1fr);gap:4px}
  .vid-play{width:46px;height:46px;margin:-23px 0 0 -23px}
  .vid-play::before{left:18px;top:14px;border-left-width:14px;border-top-width:9px;border-bottom-width:9px}
  .cue{margin-top:32px}
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .stat b.live::before{animation:none}
  .cue::after{animation:none;opacity:.5}
  .repo:hover,.btn:hover{transform:none}
  .js [data-reveal]{opacity:1;transform:none}
  *{transition-duration:.01ms !important}
}
