/* ============================================================
   RESET
   ============================================================ */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
img,svg{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer;text-align:left}
:focus{outline:none}
:focus-visible{outline:2px solid var(--accent-hi);outline-offset:3px;border-radius:2px}

/* ============================================================
   TOKENS
   ============================================================ */
:root{
  --bg:#0d0d0d;
  --content:#111;
  --elevated:#16161a;
  --hover:#1c1c20;
  --line:#222;
  --line-strong:#2c2c2c;
  --t1:#fff;
  --t2:#9a9a9a;
  --t3:#666;
  --t4:#444;
  --accent:#5e6ad2;
  --accent-hi:#828df5;
  --accent-soft:rgba(94,106,210,.16);
  --serif:'Newsreader',Georgia,serif;
  --sans:'Instrument Sans',system-ui,-apple-system,sans-serif;
  --mono:'IBM Plex Mono',ui-monospace,monospace;
  --plate:#e7e5e0;         /* mount for artwork drawn on white — see .pj-doc */
  --t-read:#b4b4b4;        /* long-form body — 9.4:1 on --bg, brighter than --t2 */
  --measure:38rem;         /* long-form column — ~70ch at 17px */
  --ease:cubic-bezier(.16,1,.3,1);
}

body{
  background:var(--bg);color:var(--t1);
  font-family:var(--sans);font-size:16px;line-height:1.55;
  overflow-x:hidden;
}
::selection{background:rgba(94,106,210,.34);color:#fff}

/* ============================================================
   PRIMITIVES
   ============================================================ */
.wrap{max-width:1120px;margin:0 auto;padding:0 28px}
section{padding:130px 0;position:relative}

.eyebrow{
  font-family:var(--mono);font-size:.6875rem;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;color:var(--t3);
  display:flex;align-items:center;gap:14px;margin-bottom:26px;
}
.eyebrow::after{content:"";height:1px;flex:1;background:var(--line)}

h1,h2,h3{font-family:var(--serif);font-weight:500;letter-spacing:-.02em;line-height:1.1}
h1{font-size:clamp(2.6rem,6.4vw,4.6rem);letter-spacing:-.035em;line-height:1.03}
h2{font-size:clamp(1.9rem,3.6vw,2.9rem);letter-spacing:-.025em}
h2.one{font-size:clamp(1.3rem,3.05vw,2.4rem);letter-spacing:-.03em}
h3{font-size:1.0625rem;font-family:var(--sans);font-weight:600;letter-spacing:-.005em;line-height:1.35}

.lede{font-size:1.1875rem;line-height:1.6;color:var(--t2);max-width:62ch}
.prose{color:var(--t2);max-width:62ch}
.mono{font-family:var(--mono);font-variant-numeric:tabular-nums}

.btn{
  display:inline-flex;align-items:center;gap:9px;
  padding:12px 22px;border-radius:9px;
  font-size:.9375rem;font-weight:600;
  transition:background 160ms ease,color 160ms ease,border-color 160ms ease;
}
.btn-accent{background:var(--accent);color:#fff}
.btn-accent:hover{background:var(--accent-hi)}
.btn-quiet{color:var(--t2);padding-left:0;padding-right:0}
.btn-quiet:hover{color:var(--t1)}
.btn-quiet span{border-bottom:1px solid var(--line-strong);padding-bottom:2px;transition:border-color 160ms ease}
.btn-quiet:hover span{border-color:var(--accent-hi)}
.arrow{font-family:var(--mono);font-size:.8125rem;opacity:.75}

/* reveal */
.rv{opacity:0;transform:translateY(14px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
.rv.in{opacity:1;transform:none}
.rv-2{transition-delay:.08s}.rv-3{transition-delay:.16s}.rv-4{transition-delay:.24s}

/* ============================================================
   NAV
   ============================================================ */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:60;
  border-bottom:1px solid transparent;
  transition:background 220ms ease,border-color 220ms ease,backdrop-filter 220ms ease;
}
.nav.stuck{background:rgba(13,13,13,.82);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-bottom-color:var(--line)}
.nav-in{display:flex;align-items:center;justify-content:space-between;height:66px}
.brand{display:flex;align-items:center;gap:10px;font-weight:600;letter-spacing:-.01em;font-size:.9375rem}
.brand .mk{width:20px;height:20px;flex:none}
.nav-links{display:flex;gap:30px;font-family:var(--mono);font-size:.75rem;letter-spacing:.04em;color:var(--t2)}
.nav-links a{transition:color 150ms ease}
.nav-links a:hover,.nav-links a.on{color:var(--accent-hi)}
.nav-cta{font-size:.8125rem;padding:9px 17px}

/* --- mobile drawer -----------------------------------------
   The trigger and the panel are both built by site.js out of the links that
   are already in the nav, so everything below is gated on .has-drawer and only
   applies once that script has run. Without it the links stay put and scroll
   sideways — degraded, but still a nav. */
.nav-toggle{display:none;padding:12px 0 12px 16px;color:var(--t2);transition:color 150ms ease}
.nav-toggle:hover{color:var(--t1)}
.nav-toggle .bars{display:block;width:21px;height:9px;position:relative}
.nav-toggle .bars::before,.nav-toggle .bars::after{
  content:"";position:absolute;left:0;right:0;height:1px;background:currentColor;
  transition:transform 260ms var(--ease),top 220ms var(--ease)}
.nav-toggle .bars::before{top:0}
.nav-toggle .bars::after{top:8px}
.nav-toggle[aria-expanded="true"] .bars::before{top:4px;transform:rotate(45deg)}
.nav-toggle[aria-expanded="true"] .bars::after{top:4px;transform:rotate(-45deg)}

.nav-drawer{display:none;border-top:1px solid var(--line)}
.nav.drawer-shown .nav-drawer{display:block}
.nav.menu-open{background:rgba(13,13,13,.97);backdrop-filter:blur(16px);
  -webkit-backdrop-filter:blur(16px);border-bottom-color:var(--line)}
.nav-drawer .wrap{padding-top:6px;padding-bottom:28px}
.nd-link,.nd-cta{opacity:0;transform:translateY(9px);
  transition:opacity .36s var(--ease),transform .36s var(--ease),color 150ms ease}
.nav.menu-open .nd-link,.nav.menu-open .nd-cta{opacity:1;transform:none}
.nd-link{display:block;padding:16px 0;border-bottom:1px solid var(--line);
  font-size:1.0625rem;font-weight:500}
.nd-link.on{color:var(--accent-hi)}
.nd-cta{display:flex;justify-content:center;width:100%;margin-top:22px}

.nav-scrim{position:fixed;inset:0;z-index:50;background:rgba(8,8,8,.6);
  opacity:0;pointer-events:none;transition:opacity 260ms ease}
.nav-scrim.on{opacity:1;pointer-events:auto}


/* ============================================================
   HERO
   ============================================================ */
.hero{padding:190px 0 110px}
.hero h1{max-width:15ch}
.hero .lede{margin-top:26px;font-size:1.25rem;max-width:57ch}
.hero-cta{display:flex;align-items:center;gap:26px;margin-top:40px;flex-wrap:wrap}

.from{margin-top:76px;border-top:1px solid var(--line);padding-top:18px;
  display:flex;align-items:baseline;gap:26px;flex-wrap:wrap}
.from dt{font-family:var(--mono);font-size:.6875rem;letter-spacing:.14em;text-transform:uppercase;color:var(--t4)}
.from dd{font-family:var(--mono);font-size:.8125rem;letter-spacing:.05em;color:var(--t2)}
.from dd em{font-style:normal;color:var(--t4);padding:0 9px}

/* ============================================================
   CAPABILITIES
   ============================================================ */
.caps{display:grid;grid-template-columns:1fr 1fr;gap:0;margin-top:8px;border-top:1px solid var(--line)}
.cap{padding:34px 40px 34px 0;border-bottom:1px solid var(--line)}
.caps .cap:nth-child(odd){border-right:1px solid var(--line)}
.caps .cap:nth-child(even){padding-left:40px;padding-right:0}
.cap h3{margin-bottom:11px}
.cap p{color:var(--t2);font-size:.9375rem;line-height:1.62}

/* ============================================================
   PHASE MAP  (signature)
   ============================================================ */
.pm-head{max-width:66ch}
.pm-head .prose{margin-top:22px;font-size:1.0625rem}

.pm-tabs{display:flex;gap:0;margin-top:52px;border-bottom:1px solid var(--line)}
.pm-tab{
  font-family:var(--mono);font-size:.75rem;letter-spacing:.13em;text-transform:uppercase;
  color:var(--t3);padding:13px 24px 13px 0;margin-right:22px;
  border-bottom:1px solid transparent;margin-bottom:-1px;
  transition:color 160ms ease,border-color 160ms ease;
}
.pm-tab:hover{color:var(--t2)}
.pm-tab.on{color:var(--accent-hi);border-bottom-color:var(--accent-hi)}

.pm-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:34px;
  transition:opacity 220ms ease}
.pm-grid.fade{opacity:0}
.pm-col{border:1px solid var(--line);border-radius:14px;padding:26px 24px 28px;background:var(--content)}
.pm-col.lead{border-color:rgba(94,106,210,.42);background:linear-gradient(180deg,rgba(94,106,210,.075),transparent 60%)}
.pm-when{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;min-height:24px}
.pm-when span{font-family:var(--mono);font-size:.75rem;letter-spacing:.13em;text-transform:uppercase;color:var(--t2)}
.pm-col.lead .pm-when span{color:var(--accent-hi)}
.pm-chip{
  font-family:var(--mono);font-size:.5625rem;letter-spacing:.11em;text-transform:uppercase;
  color:var(--accent-hi);border:1px solid rgba(130,141,245,.42);border-radius:5px;padding:3px 7px;white-space:nowrap;
}
.pm-col p{color:var(--t2);font-size:.9375rem;line-height:1.62}

/* ============================================================
   WORK TABLE  (expandable)
   ============================================================ */
.tbl{margin-top:44px;border-top:1px solid var(--line)}
.thead{
  display:grid;grid-template-columns:1.55fr 1.1fr .82fr 1.05fr 34px;gap:20px;
  padding:12px 0;border-bottom:1px solid var(--line);
  font-family:var(--mono);font-size:.625rem;letter-spacing:.16em;text-transform:uppercase;color:var(--t4);
}
.row{border-bottom:1px solid var(--line)}
.trig{
  display:grid;grid-template-columns:1.55fr 1.1fr .82fr 1.05fr 34px;gap:20px;align-items:center;
  width:100%;padding:21px 0;transition:background 160ms ease;
}
.trig:hover{background:var(--hover)}
.row.open .trig{background:var(--hover)}
.trig>*{min-width:0}
.c-lab{display:none}
.c-sys{font-weight:600;font-size:.9688rem;letter-spacing:-.005em}
.c-cli{color:var(--t3);font-size:.875rem}
.c-scl{font-family:var(--mono);font-size:.8125rem;color:var(--accent-hi);font-variant-numeric:tabular-nums}
.c-stk{display:flex;gap:6px;flex-wrap:wrap}
.chip{
  font-family:var(--mono);font-size:.625rem;letter-spacing:.05em;color:var(--t3);
  border:1px solid var(--line-strong);border-radius:6px;padding:3px 7px;white-space:nowrap;
}
.c-chev{
  font-family:var(--mono);font-size:1.0625rem;color:var(--t4);justify-self:end;line-height:1;
  transition:color 160ms ease,transform 260ms var(--ease);
}
.trig:hover .c-chev{color:var(--accent-hi)}
.row.open .c-chev{color:var(--accent-hi);transform:rotate(45deg)}

.det{display:grid;grid-template-rows:0fr;transition:grid-template-rows 280ms var(--ease)}
.row.open .det{grid-template-rows:1fr}
.det-in{overflow:hidden}
.det-in p{
  color:var(--t2);font-size:.9688rem;line-height:1.68;
  max-width:74ch;padding:0 0 26px;
  border-left:1px solid var(--line-strong);padding-left:20px;margin-left:2px;
}
.det-in p b{color:var(--t1);font-weight:500}

/* ============================================================
   CORTANA
   ============================================================ */
.ct{display:grid;grid-template-columns:.88fr 1.12fr;gap:64px;align-items:center;margin-top:44px}
.ct-claims{border-top:1px solid var(--line)}
.ct-claim{padding:19px 0;border-bottom:1px solid var(--line)}
.ct-claim dt{font-family:var(--mono);font-size:.6875rem;letter-spacing:.13em;text-transform:uppercase;color:var(--accent-hi);margin-bottom:7px}
.ct-claim dd{color:var(--t2);font-size:.9063rem;line-height:1.6}

/* product surface mock */
.surf{border:1px solid var(--line);border-radius:15px;background:var(--content);overflow:hidden;font-family:var(--mono)}
.surf-bar{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--line);background:var(--elevated)}
.surf-bar .d{width:7px;height:7px;border-radius:50%;background:var(--accent-hi)}
.surf-bar .t{font-size:.6875rem;letter-spacing:.11em;text-transform:uppercase;color:var(--t2)}
.surf-bar .r{margin-left:auto;font-size:.625rem;letter-spacing:.09em;color:var(--t4)}
.srow{display:grid;grid-template-columns:1fr 78px 68px 52px;gap:10px;align-items:center;
  padding:9px 16px;border-bottom:1px solid rgba(255,255,255,.035);font-size:.6875rem}
.srow.h{color:var(--t4);letter-spacing:.11em;text-transform:uppercase;font-size:.5938rem;background:rgba(255,255,255,.012)}
.srow .n{color:var(--t2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.srow .s,.srow .p,.srow .e{color:var(--t3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.srow.live{background:rgba(94,106,210,.10)}
.srow.live .n{color:var(--accent-hi)}
.dot{display:inline-block;width:5px;height:5px;border-radius:50%;margin-right:6px;vertical-align:middle}
.dot.go{background:#4cb782}.dot.wait{background:#c8a24a}.dot.idle{background:#555}.dot.done{background:#3a6b52}
.tag{border:1px solid rgba(130,141,245,.4);color:var(--accent-hi);border-radius:4px;padding:1px 5px;font-size:.5312rem;letter-spacing:.07em;margin-left:7px}
.surf-foot{padding:11px 16px;font-size:.625rem;letter-spacing:.1em;color:var(--t4);border-top:1px solid var(--line);background:var(--elevated)}


/* ============================================================
   CTA + FOOTER
   ============================================================ */
.cta{padding:150px 0;text-align:center;border-top:1px solid var(--line)}
.cta h2{font-size:clamp(2.1rem,4.6vw,3.5rem)}
.cta .btn{margin-top:36px}
footer{border-top:1px solid var(--line);padding:26px 0}
footer .wrap{display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap}
footer p{font-family:var(--mono);font-size:.6875rem;letter-spacing:.11em;text-transform:uppercase;color:var(--t4)}
footer a{color:var(--t3);transition:color 150ms ease}
footer a:hover{color:var(--accent-hi)}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media(max-width:960px){
  .ct{grid-template-columns:1fr;gap:40px}
  .pm-grid{grid-template-columns:1fr}
}
@media(max-width:820px){
  section{padding:96px 0}
  .hero{padding:150px 0 84px}
  .nav.has-drawer .nav-links{display:none}
  .nav.has-drawer .nav-toggle{display:block;margin-left:auto;margin-right:16px}
  /* no JS, no drawer: the row won't fit, so let it scroll rather than overflow */
  .nav-links{overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .nav-links::-webkit-scrollbar{display:none}
  .caps{grid-template-columns:1fr}
  .caps .cap:nth-child(odd){border-right:none}
  .caps .cap:nth-child(even){padding-left:0}
  .cap{padding:28px 0}
  .thead{display:none}
  .trig{grid-template-columns:1fr;gap:9px;padding:22px 0}
  .c-lab{display:block;font-family:var(--mono);font-size:.5625rem;letter-spacing:.15em;
    text-transform:uppercase;color:var(--t4);margin-bottom:4px}
  .c-chev{position:absolute;top:22px;right:0}
  .row{position:relative}
  .pm-tab{padding-right:0;margin-right:20px;font-size:.6875rem}
  .pm-tabs{overflow-x:auto;scrollbar-width:none}
  .pm-tabs::-webkit-scrollbar{display:none}
}
/* the drawer is a small-screen affordance only — never let a stale open state
   from a resize leave it hanging on desktop */
@media(min-width:821px){
  .nav-drawer,.nav-scrim{display:none!important}
}
@media(max-width:520px){
  .wrap{padding:0 20px}
  .from{gap:12px}
  .srow{grid-template-columns:1fr 66px 46px;font-size:.625rem}
  .srow .p{display:none}
}
@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *{animation:none!important;transition-duration:.01ms!important;transition-delay:0s!important}
  .rv{opacity:1;transform:none}
  .nd-link,.nd-cta{opacity:1;transform:none}
}

/* ============================================================
   PAGE HEADER  (shared by /projects and article pages)
   ============================================================ */
.phead{padding:172px 0 0}
.phead-in{border-bottom:1px solid var(--line);padding-bottom:52px}
.phead h1{font-size:clamp(2.3rem,5.2vw,3.9rem);max-width:16ch}
.phead .lede{margin-top:24px;font-size:1.1875rem;max-width:58ch}
.byline{
  margin-top:36px;font-family:var(--mono);font-size:.6875rem;
  letter-spacing:.14em;text-transform:uppercase;color:var(--t4);
}
.byline b{color:var(--t3);font-weight:400}

.back{
  display:inline-flex;align-items:center;gap:9px;
  font-family:var(--mono);font-size:.6875rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--t3);transition:color 150ms ease;
}
.back:hover{color:var(--accent-hi)}
.back .arrow{opacity:.6}

/* ============================================================
   BAND  (landing-page pointer out to a route)
   ============================================================ */
.band{margin-top:56px;border-top:1px solid var(--line)}
.band-row{
  /* fixed label column, not auto: each row is its own grid, so `auto` sizes the
     kicker per row and the titles step in and out with the label length */
  display:grid;grid-template-columns:132px 1fr auto;gap:28px;align-items:center;
  padding:26px 2px;border-bottom:1px solid var(--line);transition:background 160ms ease;
}
.band-row:hover{background:var(--hover)}
.band-k{
  font-family:var(--mono);font-size:.625rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--t4);white-space:nowrap;
}
.band-t{font-size:.9688rem;color:var(--t2)}
.band-t b{color:var(--t1);font-weight:600}
.band-go{
  font-family:var(--mono);font-size:.75rem;letter-spacing:.05em;color:var(--t3);
  white-space:nowrap;transition:color 160ms ease;
}
.band-row:hover .band-go{color:var(--accent-hi)}

/* ============================================================
   PROJECTS INDEX
   ============================================================ */
.pj-list{margin-top:0}
.pj{
  display:grid;grid-template-columns:1.06fr .94fr;gap:0 40px;
  padding:46px 0;border-bottom:1px solid var(--line);align-items:start;
}

/* video facade */
.pj-media{
  /* width:100% is load-bearing. This is a <button>, and a button sizes to its
     content even at display:block — so when the poster is replaced by an
     iframe, which has no intrinsic size, the tile collapses to the iframe's
     300px default and the video visibly shrinks the moment you press play. */
  position:relative;display:block;width:100%;aspect-ratio:16/9;overflow:hidden;
  border:1px solid var(--line);border-radius:12px;background:var(--content);
  transition:border-color 200ms ease;
}
.pj-media:hover{border-color:var(--line-strong)}
.pj-media img{width:100%;height:100%;object-fit:cover}
/* a screen recording isn't a video still — cropping it to 16:9 cuts lines off
   the top and bottom, and on this one the bottom is where the result is. Let it
   letterbox instead; the terminal's own background is black, so nothing shows. */
.pj-media.pj-shot{background:#000}
.pj-media.pj-shot img{object-fit:contain}
/* A drawing, not a recording. Two entries here are diagrams made on white — a
   wireframe beside the HTML it generates, a request path through Flask and a
   vector store. `pj-shot` is the wrong frame for them: contain-on-black puts a
   bright slab in a black box and the join is a hard edge. So mount them. The
   plate and the inset read as artwork pinned to a board, which is what these
   are, and the inset stops the white of the drawing running into the card's
   own border. No scrim and no play button — there is nothing behind them. */
.pj-media.pj-doc{background:var(--plate);padding:15px}
.pj-media.pj-doc img{object-fit:contain}
.pj-media.pj-shot .pj-scrim{background:rgba(13,13,13,.22)}
.pj-media.pj-shot:hover .pj-scrim{background:rgba(13,13,13,.1)}
.pj-media iframe{width:100%;height:100%;border:0;display:block}
.pj-scrim{
  position:absolute;inset:0;display:grid;place-items:center;
  background:linear-gradient(180deg,rgba(13,13,13,.18),rgba(13,13,13,.46));
  transition:background 220ms ease;
}
.pj-media:hover .pj-scrim{background:linear-gradient(180deg,rgba(13,13,13,.06),rgba(13,13,13,.3))}
.pj-play{
  width:56px;height:56px;border-radius:50%;display:grid;place-items:center;
  border:1px solid rgba(255,255,255,.42);background:rgba(13,13,13,.42);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  transition:transform 260ms var(--ease),border-color 200ms ease,background 200ms ease;
}
.pj-media:hover .pj-play{transform:scale(1.07);border-color:var(--accent-hi);background:rgba(94,106,210,.5)}
.pj-play svg{width:15px;height:17px;margin-left:3px;fill:#fff}

/* empty / pending video */
.pj-media.pending{
  border-style:dashed;border-color:var(--line-strong);
  background:repeating-linear-gradient(135deg,transparent 0 11px,rgba(255,255,255,.017) 11px 22px),var(--content);
  display:grid;place-items:center;
}
.pj-media.pending span,.pj-media.pj-written span{
  font-family:var(--mono);font-size:.625rem;letter-spacing:.17em;
  text-transform:uppercase;color:var(--t4);
}

/* Written, not filmed — an entry whose artifact is a post and a repo.
   The dashes and the hatch above mean "something is missing and it's coming."
   Nothing is missing here, so the border is solid and the field is plain: the
   same frame the videos get, holding a different kind of work. */
.pj-media.pj-written{display:grid;place-items:center}
/* A still with nothing behind it — no scrim, no play button, because there is
   nothing to play and a play button that opens an article is a small lie. The
   whole tile is the link instead, so the biggest thing in the row still goes
   somewhere. */
.pj-media.pj-still img{transition:opacity 220ms ease}
.pj-media.pj-still:hover img{opacity:.86}

/* meta column */
.pj-meta{min-width:0}
.pj-comp{
  font-family:var(--mono);font-size:.625rem;letter-spacing:.15em;text-transform:uppercase;
  color:var(--t3);display:flex;align-items:center;gap:12px;flex-wrap:wrap;
}
.award{
  font-family:var(--mono);font-size:.5625rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--accent-hi);border:1px solid rgba(130,141,245,.42);
  border-radius:5px;padding:3px 8px;white-space:nowrap;
}
.pj-title{
  font-family:var(--serif);font-weight:500;font-size:1.5rem;line-height:1.2;
  letter-spacing:-.02em;margin:14px 0 0;
}
.pj-deck{color:var(--t2);font-size:.9375rem;line-height:1.62;margin-top:11px;max-width:46ch}
/* A single line that outranks the deck — for the one thing about an entry a
   reader should not be able to skim past. Used once, on the talk. */
.pj-note{color:var(--t1);font-weight:600;font-size:.9375rem;line-height:1.5;margin-top:13px}
.pj-stk{display:flex;gap:6px;flex-wrap:wrap;margin-top:18px}
.pj-links{display:flex;gap:24px;flex-wrap:wrap;margin-top:22px}
.pj-links a{
  font-family:var(--mono);font-size:.75rem;letter-spacing:.03em;color:var(--t2);
  display:inline-flex;align-items:center;gap:7px;
  border-bottom:1px solid var(--line-strong);padding-bottom:3px;
  transition:color 160ms ease,border-color 160ms ease;
}
.pj-links a:hover{color:var(--accent-hi);border-bottom-color:var(--accent-hi)}
.pj-links a[aria-disabled="true"]{color:var(--t4);border-bottom-color:transparent;pointer-events:none}
/* an archival video sits below the current work in the same row — the rule
   under it goes, so it reads as an aside rather than a fourth equal thing */
.pj-links a.pj-archive{color:var(--t3);border-bottom-color:transparent}
.pj-links a.pj-archive:hover{color:var(--t2);border-bottom-color:var(--line-strong)}

/* scaffolding rows */
.pj.stub .pj-title,.pj.stub .pj-deck{color:var(--t3)}
.pj.stub .pj-title{font-style:italic}

/* ============================================================
   ARTICLE
   ============================================================ */
.art-hero{display:grid;grid-template-columns:1fr min(var(--measure),100%) 1fr;
  border-bottom:1px solid var(--line);padding-bottom:46px}
.art-hero>*{grid-column:2}
.art-hero h1{font-size:clamp(2.05rem,4.4vw,3.25rem);max-width:19ch;margin-top:20px}
.art-hero .lede{margin-top:22px;font-size:1.125rem;max-width:56ch}
.art-bar{display:flex;gap:26px;flex-wrap:wrap;align-items:center;margin-top:32px}

.art-body{
  display:grid;
  grid-template-columns:1fr min(var(--measure),100%) 1fr;
  padding:52px 0 40px;
}
.art-body>*{grid-column:2}
.art-body p{color:var(--t-read);font-size:1.0625rem;line-height:1.75;margin:0 0 1.35em}
.art-body strong{color:var(--t1);font-weight:600}
.art-body em{color:var(--t2)}
.art-body a{color:var(--accent-hi);border-bottom:1px solid rgba(130,141,245,.34);transition:border-color 150ms ease}
.art-body a:hover{border-bottom-color:var(--accent-hi)}
.art-body h2{
  font-family:var(--serif);font-weight:500;font-size:clamp(1.55rem,2.6vw,1.95rem);
  letter-spacing:-.022em;line-height:1.18;margin:68px 0 20px;
}
.art-body h2:first-child{margin-top:0}
.callout+h2,.art-body figure+h2{margin-top:26px}
.art-body figure+h3,.art-body figure+h4{margin-top:30px}
.art-body h3,.art-body h4{
  font-family:var(--sans);font-weight:600;font-size:1.0625rem;color:var(--t1);
  letter-spacing:-.005em;line-height:1.35;margin:48px 0 16px;
  padding-top:20px;border-top:1px solid var(--line);
}
.art-body ul,.art-body ol{color:var(--t-read);margin:0 0 1.5em;padding-left:1.45em}
.art-body li{margin-bottom:.62em;line-height:1.72}
.art-body li::marker{color:var(--t4)}
/* ordered steps carry meaning, so the number has to be readable */
.art-body ol>li::marker{color:var(--t3);font-family:var(--mono);font-size:.875em}
.art-body li>strong:first-child{color:var(--t1)}
.art-body code{
  font-family:var(--mono);font-size:.875em;color:var(--accent-hi);
  background:var(--accent-soft);border-radius:4px;padding:2px 6px;
}
.art-body hr{border:0;border-top:1px solid var(--line);margin:56px 0}

.callout{
  border-left:2px solid var(--accent);background:var(--accent-soft);
  border-radius:0 9px 9px 0;padding:17px 22px;margin:0 0 2.2em;
  color:var(--t2);font-size:.9688rem;line-height:1.66;
}
.callout strong{color:var(--t1)}

/* someone else's words — the docs, a spec, a warning we're quoting. Kept
   plainly different from .callout, which is us talking. */
.art-body blockquote{
  margin:0 0 1.6em;padding:1px 0 1px 22px;
  border-left:1px solid var(--line-strong);color:var(--t2);
}
.art-body blockquote p{color:inherit;font-size:1rem;line-height:1.7;margin:0 0 .85em}
.art-body blockquote p:last-child{margin-bottom:0}

/* ============================================================
   CODE
   A technical writeup is half code, so it gets the same care as the
   body type. No syntax colouring: the palette is one accent and a grey
   ladder, and a rainbow would be the loudest thing on the page.
   Comments drop back instead — at reading speed that's the only
   distinction that carries its weight.
   ============================================================ */
/* Wider than the prose measure, because 90-column code in a 70-column column
   scrolls on every other line — but starting on the same left edge as the
   paragraphs, not centred like a figure. A figure is an object you look at and
   centring reads as deliberate; code is text you read, and text that starts
   somewhere new each time it appears makes the reader find the margin again. */
.art-body .code{
  grid-column:2/4;justify-self:start;width:min(48rem,100%);
  margin:32px 0 36px;border:1px solid var(--line);border-radius:11px;
  background:var(--content);overflow:hidden;
}
/* The file the snippet came from, lifted out of its first comment line. The
   post's whole claim is that this is running code — the path is part of the
   argument, not a decoration, so it gets the frame rather than a line of it. */
.code-file{
  font-family:var(--mono);font-size:.6875rem;letter-spacing:.04em;color:var(--t2);
  padding:10px 18px;border-bottom:1px solid var(--line);
  background:rgba(255,255,255,.015);overflow-x:auto;white-space:nowrap;
}
/* several snippets are from a vendored dependency rather than the project, and
   the tail is the part you read — so the install prefix stays but recedes */
.code-file .dim{color:var(--t4)}
.art-body pre{
  margin:0;padding:17px 20px;overflow-x:auto;
  font-family:var(--mono);font-size:.8125rem;line-height:1.68;color:var(--t-read);
}
.art-body pre:focus-visible{outline-offset:-2px}
.art-body pre code{background:none;padding:0;color:inherit;font-size:inherit}
.art-body pre .c{color:var(--t4)}          /* whole-line comments */
.art-body pre::-webkit-scrollbar{height:9px}
.art-body pre::-webkit-scrollbar-thumb{background:var(--line-strong);border-radius:9px}
.art-body pre::-webkit-scrollbar-track{background:transparent}

/* figures break out of the measure */
.art-body figure{
  grid-column:1/-1;justify-self:center;width:min(58rem,100%);
  margin:46px 0;
}
.art-body figure a{
  display:block;border-radius:12px;overflow:hidden;background:#fff;
  box-shadow:0 22px 46px -26px rgba(0,0,0,.9);
  transition:transform 300ms var(--ease);
}
.art-body figure a:hover{transform:translateY(-2px)}
.art-body figure img{width:100%;height:auto;display:block}
.art-body figcaption{
  font-family:var(--mono);font-size:.6875rem;letter-spacing:.06em;line-height:1.7;
  color:var(--t4);margin:13px auto 0;text-align:center;
  /* a caption set to the width of the figure is a 110-character line — narrower
     than the thing it describes, so it reads as a caption rather than a row */
  max-width:64ch;
}
.art-body figcaption code{background:none;padding:0;color:var(--t3)}

/* a recording that plays where it sits. The poster is the run's last frame, so
   the still is already the summary and the click is the proof — which is also
   why it's a facade: the still is 65 KB and the recording is a megabyte and a
   half, and nobody should pay for that before they've decided to watch it. */
.fig-anim{
  position:relative;display:block;width:100%;overflow:hidden;
  border:1px solid var(--line);border-radius:12px;background:#000;
  transition:border-color 200ms ease;
}
.fig-anim:hover{border-color:var(--line-strong)}
.fig-anim img{width:100%;height:auto}
.fig-anim.playing{cursor:default}
/* flat and light, not the tile gradient: on a recording the last frame is the
   summary, and the summary lives at the bottom of the frame — a scrim that
   gets darker downwards would put its weight exactly on the part worth reading */
.fig-anim .pj-scrim{background:rgba(13,13,13,.22)}
.fig-anim:hover .pj-scrim{background:rgba(13,13,13,.1)}
.fig-anim:hover .pj-play{transform:scale(1.07);border-color:var(--accent-hi);
  background:rgba(94,106,210,.5)}

/* ============================================================
   ARCHIVE
   An older video kept for provenance. It is deliberately the quietest
   thing on the page and deliberately the last: it is the least current
   artefact here, and ranking it any higher would be telling the reader
   to look at the wrong one. A coda, on hairlines and space — not a
   filled box, which would read as a warning about our own work.
   ============================================================ */
.archive{
  grid-column:1/-1;justify-self:center;width:min(58rem,100%);
  margin-top:82px;padding-top:36px;border-top:1px solid var(--line);
  display:grid;grid-template-columns:1fr 1.04fr;gap:26px 44px;align-items:start;
}
.archive-k{
  grid-column:1/-1;
  font-family:var(--mono);font-size:.625rem;letter-spacing:.17em;
  text-transform:uppercase;color:var(--t3);
}
.archive h2{
  font-family:var(--serif);font-weight:500;font-size:1.35rem;letter-spacing:-.02em;
  line-height:1.25;margin:0 0 15px;
}
.archive p{font-size:.9688rem;line-height:1.68;color:var(--t2);margin:0 0 1em}
.archive p:last-child{margin-bottom:0}
.archive em{color:var(--t2);font-style:italic}
/* the one line to leave with, if they leave with one */
.archive strong{color:var(--t1);font-weight:600}

.archive-media{
  position:relative;display:block;width:100%;aspect-ratio:16/10;overflow:hidden;
  border:1px solid var(--line);border-radius:11px;background:var(--content);
  transition:border-color 200ms ease;
}
.archive-media:hover{border-color:var(--line-strong)}
.archive-media img{width:100%;height:100%;object-fit:cover;
  /* held back until you reach for it — the honest visual for "this is not the
     current thing", without a banner saying so */
  filter:grayscale(.6) brightness(.58);transition:filter 340ms var(--ease)}
.archive-media:hover img,.archive-media:focus-visible img,.archive-media.playing img{filter:none}
.archive-media iframe{width:100%;height:100%;border:0;display:block}
.archive-tag{
  position:absolute;top:11px;left:11px;z-index:2;
  font-family:var(--mono);font-size:.5625rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--t2);background:rgba(13,13,13,.72);border:1px solid var(--line-strong);
  border-radius:4px;padding:4px 8px;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
.archive-media.playing .archive-tag,.archive-media.playing .pj-scrim{display:none}
/* the archive poster is an anchor now, so it inherits the body-copy underline
   from `.art-body a` and loses three sides of its own frame. Put them back. */
.art-body a.archive-media{border:1px solid var(--line)}
.art-body a.archive-media:hover{border-color:var(--line-strong)}

.art-foot{border-top:1px solid var(--line);padding:44px 0 0;display:flex;
  justify-content:space-between;gap:22px;flex-wrap:wrap;align-items:center}

/* ============================================================
   RESPONSIVE  (routes added after the landing page)
   ============================================================ */
@media(max-width:960px){
  .pj{grid-template-columns:1fr;gap:0}
  .pj-meta{grid-column:1;margin-top:24px}
}
@media(max-width:820px){
  .phead{padding:132px 0 0}
  .pj{grid-template-columns:1fr;gap:0;padding:38px 0}
  .pj-meta{grid-column:1;margin-top:22px}
  .band-row{grid-template-columns:1fr auto;gap:14px}
  .band-k{grid-column:1/-1}
  .art-body{padding:44px 0 32px}
  .art-body figure{margin:34px 0}
  /* the diagrams are 2.5:1 and 1.95:1 — at this width the labels inside them
     stop being readable, so say out loud that the full size is one tap away */
  .art-body figcaption::after{content:" · Tap to open full size";color:var(--t3)}
  .art-body .code{margin:26px 0 30px}
  .art-body pre{padding:15px 16px;font-size:.78125rem}
  .code-file{padding:9px 16px}
  .archive{grid-template-columns:1fr;gap:24px;margin-top:62px}
}
@media(prefers-reduced-motion:reduce){
  .pj-media:hover .pj-play,.art-body figure a:hover{transform:none}
  /* the recording only ever animates on a click, so there is nothing to
     suppress here — but the poster shouldn't slide either */
  .archive-media img{transition:none}
}

/* the writeup's own copy of the demo — same facade, sized like a figure */
.art-video{grid-column:1/-1;justify-self:center;width:min(58rem,100%);margin:0 0 52px}
