/* ============================================================
   ALEXANDRU BADIU — ZX SPECTRUM THEME (shared)
   Black paper, bright-8 accents, rainbow tape border.
   ============================================================ */
:root{
  --bg:#000000;
  --paper:#f6f6f6;
  --ink:#eaeaea;
  --dim:#9a9a9a;
  --faint:#6a6a6a;
  /* Spectrum BRIGHT 8 */
  --s-blue:#0000ff;
  --s-red:#ff0000;
  --s-mag:#ff00ff;
  --s-grn:#00ff00;
  --s-cyan:#00ffff;
  --s-yel:#ffff00;
  --s-wht:#ffffff;
  --accent:var(--s-mag);
  --rule:#262626;
  --card:#0c0c0c;
  /* The pixel face, in one place so it can be swapped in a line. Silkscreen
     is drawn on an 8px grid, so every size below is a whole multiple of 8 and
     lands on real pixels; anything else gets scaled and softens. It is also
     narrower and lighter than Press Start 2P, so the display sizes are about a
     third larger than they were under that face. */
  --font-pixel:"Silkscreen",monospace;
  /* reading mode (long-form articles) */
  --bg-read:#0c0c0c;
  --read-measure:100%; /* body fills the reading shell so it aligns with the header */
}
*{box-sizing:border-box;}
html{scroll-behavior:smooth;}
body{
  margin:0;
  /* anchors .dive-floor to the bottom of the document rather than the viewport */
  position:relative;
  background:var(--bg);
  color:var(--ink);
  font-family:"Spline Sans Mono",ui-monospace,monospace;
  font-size:16px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
a{color:inherit;}
em{font-style:italic;color:var(--paper);}
.mono{font-family:"Spline Sans Mono",ui-monospace,monospace;}

/* ---- Rainbow tape border frame (always-on chrome) ---- */
.tape-frame{position:fixed;inset:0;pointer-events:none;z-index:40;}
.tape-frame .bar{position:absolute;}
.tape-frame .top,.tape-frame .bot{left:0;right:0;height:8px;
  background:repeating-linear-gradient(90deg,
    var(--s-red) 0 14px,var(--s-yel) 14px 28px,var(--s-grn) 28px 42px,var(--s-cyan) 42px 56px);}
.tape-frame .top{top:0;}
.tape-frame .bot{bottom:0;}
.tape-frame .lft,.tape-frame .rgt{top:0;bottom:0;width:8px;
  background:repeating-linear-gradient(0deg,
    var(--s-blue) 0 14px,var(--s-mag) 14px 28px,var(--s-cyan) 28px 42px,var(--s-yel) 42px 56px);}
.tape-frame .lft{left:0;}
.tape-frame .rgt{right:0;}

/* ---- shell ---- */
/* Top padding matches the statusbar's sticky offset (8px), so the header starts
   at its collapsed size instead of drifting up through empty space on scroll. */
.wrap{max-width:820px;margin:0 auto;padding:8px 28px 80px;}
section{margin-top:72px;}

/* ---- status / sub nav ---- */
.statusbar{display:flex;align-items:center;justify-content:space-between;
  gap:14px;font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:12px;color:var(--dim);
  padding:18px 0 12px;border-bottom:1px solid var(--rule);flex-wrap:wrap;
  position:sticky;top:8px;background:var(--bg);z-index:30;}
/* Full-bleed background behind the header. The .wrap is centred, so the header is
   only column-width; a full-width figure's hover decoration (the -2px lift and the
   box-shadow) pokes a few px past the column on both sides and would show above the
   sticky bar. This 100vw panel sits at the header's own stacking level (z-index:-1
   keeps it behind the name/links) so it covers that spill. It's the same black as
   the page, so it's invisible except for the fix. */
.statusbar::before{content:"";position:absolute;top:0;bottom:0;left:50%;margin-left:-50vw;
  width:100vw;background:var(--bg);z-index:-1;pointer-events:none;}
/* Extend the statusbar's bottom border across the whole viewport as a waterline,
   so the boat in the margin sits on a surface rather than floating. The .wrap is
   centred, so left:50% + margin-left:-50vw + width:100vw spans edge to edge; it
   rides the sticky statusbar automatically and stays flush with the border. */
.statusbar::after{content:"";position:absolute;bottom:-1px;left:50%;margin-left:-50vw;
  width:100vw;height:1px;background:var(--rule);pointer-events:none;}
.statusbar .left{display:flex;align-items:center;gap:10px;min-width:0;}
.statusbar .brand{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--dim);}
.statusbar .brand:hover{color:var(--s-yel);}
.blink{width:9px;height:14px;background:var(--s-grn);display:inline-block;
  animation:blink 1s steps(2) infinite;}
@keyframes blink{50%{opacity:0;}}
.statusbar .grp{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
.statusbar button,.navlink{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:11px;
  background:transparent;color:var(--dim);border:1px solid var(--rule);
  padding:5px 10px;cursor:pointer;letter-spacing:.04em;text-decoration:none;display:inline-block;}
.statusbar button:hover,.navlink:hover{color:var(--bg);background:var(--s-yel);border-color:var(--s-yel);}
.navlink.active{color:var(--bg);background:var(--accent);border-color:var(--accent);}
/* Desktop has room for the links inline, so the toggle only exists on phones.
   Needs .statusbar to outrank the ".statusbar button" rule just above. */
.statusbar .navtoggle{display:none;}
/* The sound toggle only pays off with a pointer: the bleeps fire on the loader
   and on project-card hover, and there is no hover on a touch screen. */
@media (hover:none){
  #sound-btn{display:none;}
}

/* ---- avatar dither block ---- */
.avatar{flex:0 0 auto;width:128px;height:128px;position:relative;
  border:3px solid var(--ink);image-rendering:pixelated;background:var(--card);}
.avatar canvas{display:block;width:100%;height:100%;image-rendering:pixelated;}
/* Pixel, against the tiering doc's Tier 2 for this slot: it's a screen-memory
   joke on a photo, so it wants to look like a machine wrote it. 8px is under
   the doc's 11px floor, but it is Silkscreen's native grid size, so it lands on
   whole pixels and reads cleanly. This one is tied to the face: it would need
   to go back up to 11px if the pixel font ever changed. */
.avatar .tag{position:absolute;bottom:-1px;left:-1px;
  font-family:var(--font-pixel);font-size:8px;color:var(--bg);
  background:var(--s-yel);padding:4px 5px;letter-spacing:.05em;}

/* ---- page head ---- */
.page-head{margin-top:52px;}
.eyebrow{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:600;font-size:12.5px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--s-mag);margin-bottom:16px;
  display:flex;align-items:center;gap:9px;}
.eyebrow::before{content:"";width:9px;height:9px;background:var(--s-mag);flex:0 0 auto;}
.page-head h1{font-family:var(--font-pixel);font-size:32px;line-height:1.4;margin:0 0 18px;
  color:var(--paper);text-shadow:3px 3px 0 var(--s-blue);}
.blurb{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:16px;line-height:1.7;color:var(--dim);margin:0;}

/* shared link style in prose */
.linkish,.prose a,.blurb a,.now-block a,.now-foot a,.writing-foot a{
  color:var(--s-cyan);text-decoration:none;border-bottom:2px solid var(--s-cyan);padding-bottom:1px;}
.linkish:hover,.prose a:hover,.blurb a:hover,.now-block a:hover,.now-foot a:hover,.writing-foot a:hover{
  color:var(--bg);background:var(--s-cyan);}

/* ---- section label ---- */
.slabel{display:flex;align-items:center;gap:14px;margin-bottom:26px;}
/* 12px, not the 16px the 8px-grid rule would prefer: 16 made the badge taller
   than the heading beside it. On a 2x screen 12px is still a whole 3x of the
   8px design grid, so it only softens on a 1x display. */
.slabel .k{font-family:var(--font-pixel);font-size:12px;color:var(--bg);
  background:var(--accent);padding:7px 9px;letter-spacing:.05em;white-space:nowrap;}
.slabel h2,.slabel h3{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;font-size:20px;margin:0;color:var(--paper);}
.slabel .line{flex:1;height:2px;background:repeating-linear-gradient(90deg,var(--rule) 0 6px,transparent 6px 12px);}

/* ---- prose ---- */
.prose p{font-size:16px;line-height:1.7;color:var(--ink);margin:0 0 20px;}
.prose p:last-child{margin-bottom:0;}

/* ---- timeline (bio) ---- */
.timeline{border-left:2px solid var(--rule);padding-left:26px;margin-left:6px;display:flex;flex-direction:column;gap:30px;}
.tl-entry{position:relative;}
.tl-entry::before{content:"";position:absolute;left:-33px;top:6px;width:10px;height:10px;
  background:var(--rule);}
.tl-entry.current::before{background:var(--s-grn);box-shadow:0 0 0 3px rgba(0,255,0,.18);}
.tl-when{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;
  font-size:11.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--s-yel);margin-bottom:10px;}
.tl-entry.current .tl-when{color:var(--s-grn);}
.tl-entry p{margin:0;font-size:16px;line-height:1.7;color:var(--dim);}

/* ---- posts mini (bio) ---- */
.posts-mini{list-style:none;margin:0;padding:0;border-top:1px solid var(--rule);}
.posts-mini li{display:grid;grid-template-columns:110px 1fr auto;gap:16px;align-items:baseline;
  padding:16px 4px;border-bottom:1px solid var(--rule);}
.pm-date{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:12px;color:var(--faint);}
.pm-title{font-size:16px;color:var(--ink);}
.posts-mini li.draft .pm-title{color:var(--dim);}
.pm-meta{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;
  font-size:10px;letter-spacing:.10em;text-transform:uppercase;color:var(--bg);background:var(--s-cyan);padding:4px 6px;}
.posts-mini li.draft .pm-meta{background:var(--rule);color:var(--dim);}
.writing-foot{margin-top:24px;font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:13px;color:var(--dim);}

/* ---- now blocks ---- */
/* The /now page is one Markdown body: an h3 per area, then paragraphs. The gap
   above a heading has to carry the section break on its own, since there are no
   rules between areas. */
.now-block{margin-top:44px;}
.now-block h3{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;
  font-size:11.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--s-mag);
  margin:44px 0 16px;line-height:1.5;}
.now-block h3:first-child{margin-top:0;}
.now-block p{font-size:16px;line-height:1.7;color:var(--ink);margin:0 0 16px;}
.now-block p:last-child{margin-bottom:0;}
/* Below the devlog sits the standing-interest section, which isn't part of
   "now" at all. A rule above it and a different heading colour keep it from
   reading as one more status block. */
.now-aside{margin-top:56px;padding-top:30px;border-top:1px solid var(--rule);}
.now-block h2{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;
  font-size:11.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--s-cyan);
  margin:0 0 18px;line-height:1.5;}
.now-stamp{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:12px;color:var(--faint);
  display:flex;align-items:center;gap:8px;}
/* Under the blurb in the page header, where it dates the page. */
.page-head .now-stamp{margin-top:18px;}

/* ---- devlog (on /now) ---- */
/* Project updates, newest first, grouped under the project they belong to. The
   left rule and markers are the bio timeline's, because that is what these are:
   a timeline that hasn't finished yet. */
.devlog{display:flex;flex-direction:column;gap:36px;}
/* Each group is a <section>, so it inherits the global 72px top margin; the
   flex gap above does the spacing here. */
.dl-project{margin-top:0;}
.dl-head{display:flex;align-items:baseline;gap:12px;margin-bottom:16px;}
.dl-head a{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;font-size:17px;
  color:var(--paper);text-decoration:none;border-bottom:2px solid var(--rule);padding-bottom:1px;}
.dl-head a:hover{color:var(--bg);background:var(--s-cyan);border-color:var(--s-cyan);}
.dl-status{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;
  font-size:10px;letter-spacing:.10em;text-transform:uppercase;color:var(--bg);
  background:var(--s-cyan);padding:4px 6px;}
.dl-entry{position:relative;border-left:2px solid var(--rule);padding-left:26px;margin-left:6px;
  padding-bottom:22px;}
.dl-entry:last-child{padding-bottom:0;}
.dl-entry::before{content:"";position:absolute;left:-7px;top:5px;width:10px;height:10px;
  background:var(--rule);}
.dl-project .dl-entry:first-of-type::before{background:var(--s-grn);}
.dl-when{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;
  font-size:11.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--s-yel);
  margin-bottom:10px;}
.dl-title{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:600;font-size:16px;
  color:var(--paper);margin:0 0 8px;}
.dl-entry p{font-size:16px;line-height:1.7;color:var(--dim);margin:0 0 12px;}
.dl-entry p:last-child{margin-bottom:0;}
.dl-entry ul,.dl-entry ol{margin:0 0 12px;padding-left:20px;}
.dl-entry li{font-size:16px;line-height:1.7;color:var(--dim);margin:0 0 4px;}
.dl-entry a{color:var(--s-cyan);text-decoration:none;border-bottom:2px solid var(--s-cyan);padding-bottom:1px;}
.dl-entry a:hover{color:var(--bg);background:var(--s-cyan);}

/* The /devlog page runs every entry together, newest first, so each one has to
   name its own project rather than sitting under a project heading. */
/* 44px is the gap every other page leaves between the header and its body. On
   /now the surrounding .now-block supplies it; here the list is the body. */
.dl-full{margin-top:44px;gap:34px;}
.dl-full .dl-entry{padding-bottom:0;}
.dl-full .dl-entry:first-child::before{background:var(--s-grn);}
.dl-when .sep{color:var(--faint);margin:0 6px;}
.dl-when .dl-proj{color:var(--s-cyan);text-decoration:none;
  border-bottom:2px solid var(--s-cyan);padding-bottom:1px;}
.dl-when .dl-proj:hover{color:var(--bg);background:var(--s-cyan);}
/* Out to the full devlog, from the trimmed version on /now. */
.dl-more{margin-top:28px;font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:13px;}
.dl-more a{color:var(--s-cyan);text-decoration:none;
  border-bottom:2px solid var(--s-cyan);padding-bottom:1px;}
.dl-more a:hover{color:var(--bg);background:var(--s-cyan);}
.now-stamp .pulse{width:8px;height:8px;background:var(--s-grn);animation:blink 1.4s steps(2) infinite;}
.now-foot{margin-top:56px;padding-top:22px;border-top:1px solid var(--rule);
  font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:13px;color:var(--dim);line-height:1.8;}

/* ---- uses ---- */
/* The page is one Markdown body: an h2 per group, then a list where each item is
   one thing. The list is laid out as tags rather than rows, so a group reads as a
   glance instead of a column to work down. Same chip as .pe-stack on /projects. */
.uses-prose h2{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;
  font-size:11.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--s-yel);
  margin:44px 0 18px;line-height:1.5;}
.uses-prose h2:first-child{margin-top:48px;}
.uses-prose ul{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:8px;}
.uses-prose li{margin:0;font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:13px;
  line-height:1.5;color:var(--ink);border:1px solid var(--rule);padding:6px 11px;}
/* A tag that links keeps the site's cyan, and fills on hover like every other
   link. The border rather than the text carries the colour, so a row of tags
   doesn't read as a row of shouting links. */
.uses-prose li:has(a){border-color:#123c3c;}
.uses-prose li a{color:var(--s-cyan);text-decoration:none;}
.uses-prose li:has(a):hover{background:var(--s-cyan);border-color:var(--s-cyan);}
.uses-prose li:has(a):hover a{color:var(--bg);}

/* ---- contact ---- */
.cmail{margin-top:44px;display:flex;flex-direction:column;gap:12px;}
.cmail-label{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:11px;color:var(--faint);
  text-transform:uppercase;letter-spacing:.07em;}
.cmail a{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:500;font-size:25px;line-height:1.5;color:var(--s-cyan);
  text-decoration:none;border-bottom:3px solid var(--s-cyan);padding-bottom:6px;width:fit-content;
  word-break:break-all;}
.cmail a:hover{color:var(--bg);background:var(--s-cyan);}
@media (max-width:640px){.cmail a{font-size:18px;}}
.calt{margin-top:48px;padding-top:28px;border-top:1px solid var(--rule);}
.alt-links{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:16px;}
.alt{display:flex;flex-direction:column;gap:6px;}
.alt-label{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:11px;color:var(--faint);text-transform:uppercase;letter-spacing:.07em;}
.alt a{color:var(--s-cyan);text-decoration:none;border-bottom:1px solid var(--s-cyan);width:fit-content;font-size:16px;}
.alt-note{font-size:13px;color:var(--dim);}
/* An entry that points at two places for the same thing, on one line. */
.alt-pair{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;}
.alt-pair .dot{color:var(--rule);}

/* ---- projects page ---- */
.project-list{display:flex;flex-direction:column;border-top:2px solid var(--rule);margin-top:8px;}
/* scroll-margin clears the sticky statusbar when arriving on a #project anchor. */
.project-entry{display:grid;grid-template-columns:170px 1fr;gap:28px;padding:32px 4px;border-bottom:2px solid var(--rule);
  scroll-margin-top:84px;}
/* The last entry's rule would sit just above the .now-foot rule with only blank
   space between the two, so let the foot's border close the list instead. */
.project-entry:last-child{border-bottom:none;}
.project-entry:target{background:var(--card);box-shadow:-14px 0 0 var(--card),14px 0 0 var(--card);}
.project-entry:target .pe-name{color:var(--accent);}
.pe-thumb canvas{width:170px;height:114px;border:2px solid var(--rule);image-rendering:pixelated;display:block;}
/* Real project thumbnail (a logo/graphic); same frame as the seed canvas. */
.pe-thumb-img{width:170px;height:114px;border:2px solid var(--rule);object-fit:cover;display:block;}
.pe-idx{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;
  font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin-top:12px;}
.pe-head{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:12px;}
.pe-name{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;font-size:17px;color:var(--paper);
  margin:0;line-height:1.35;letter-spacing:-.005em;}
.pe-status{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:10px;letter-spacing:.06em;
  padding:4px 8px;text-transform:uppercase;white-space:nowrap;}
.pe-status.active{background:var(--s-grn);color:var(--bg);}
.pe-status.wip{background:var(--s-yel);color:var(--bg);}
.pe-status.archived{background:var(--rule);color:var(--dim);}
.pe-meta{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:12px;color:var(--faint);
  margin-bottom:14px;display:flex;gap:9px;flex-wrap:wrap;align-items:center;}
.pe-meta .dot{color:var(--s-cyan);}
.pe-desc{font-size:15px;color:var(--dim);line-height:1.7;margin:0 0 16px;max-width:60ch;}
.pe-desc p{margin:0;}
.pe-stack{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px;}
.pe-stack span{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:11px;color:var(--ink);
  border:1px solid var(--rule);padding:4px 9px;}
.pe-links{display:flex;gap:20px;flex-wrap:wrap;}
.pe-links a{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:13px;color:var(--s-cyan);
  text-decoration:none;border-bottom:2px solid var(--s-cyan);padding-bottom:1px;}
.pe-links a:hover{color:var(--bg);background:var(--s-cyan);}
@media (max-width:640px){
  .project-entry{grid-template-columns:1fr;gap:16px;}
  .pe-thumb canvas,.pe-thumb-img{width:100%;height:120px;}
  .pe-idx{display:none;}
}

/* ---- doors ---- */
.doors{display:flex;flex-direction:column;border-top:2px solid var(--rule);}
.door{display:flex;align-items:center;gap:18px;padding:22px 6px;text-decoration:none;
  border-bottom:2px solid var(--rule);transition:padding-left .15s ease,background .15s ease;}
.door:hover{padding-left:20px;background:linear-gradient(90deg,rgba(255,0,255,.08),transparent);}
.door .num,.door .door-num{font-family:var(--font-pixel);font-size:16px;color:var(--s-mag);min-width:34px;}
.door .body,.door .door-body{flex:1;display:flex;flex-direction:column;}
.door .kick,.door .door-kicker{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:11px;color:var(--faint);
  text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px;}
.door .ttl,.door .door-title{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;font-size:19px;color:var(--paper);}
.door .dsc,.door .door-desc{font-size:14px;color:var(--dim);margin-top:5px;}
.door .arr,.door .door-arrow{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;font-size:18px;color:var(--s-cyan);}

/* ---- footer ---- */
/* ---- decorative diving scene (Glug Glug, CRL 1984) ---- */
/* Purely ornamental: everything below is aria-hidden and pointer-events:none.
   Sprites are extracted by scripts/extract_sprites.py and scaled x3 with
   nearest-neighbour, so 16x32 diver -> 48x96 etc. The rail is a centred box the
   same width as .wrap, so sprites can be pinned just outside the text column
   with right:calc(100% + Npx) and never overlap content. */
/* Above the statusbar (z-index 30), below the tape frame (40). The statusbar's
   ::before is a full-viewport-width panel, so at z-index 5 it painted over the
   boat's cabin, which rides above the waterline and so overlaps the header's
   band. Nothing in here sits over the content column -- the sprites are pinned
   outside .dive-rail -- so nothing can cover the header's own text. */
.dive{position:fixed;inset:0;pointer-events:none;z-index:35;overflow:hidden;display:none;}
/* The rail holds the boat, hose and diver; on load it sails in from the left as
   one piece so the hose stays attached. The children's own transforms (scroll
   descent, header tracking) compose under this. .dive clips it while off-screen. */
.dive-rail{position:absolute;left:50%;top:0;bottom:0;width:820px;margin-left:-410px;}
/* Start value is viewport-relative so the boat begins just off the left edge on
   any width (the boat's right edge rests at 50vw-458px; -50vw+440px clears it). */
@keyframes rig-in{from{transform:translate3d(calc(-50vw + 440px),0,0);}to{transform:translate3d(0,0,0);}}
.dive img{display:block;image-rendering:pixelated;}
/* All sprites scale x2 (native px -> display px). The statusbar's bottom border
   is the waterline: the pink cabin rides above it, the green hull below. The boat
   is 40x16 -> 80x32, split evenly, so top:52 puts that split on the ~68px border
   (the header now starts collapsed, 22px higher than before). */
.dive-boat{position:absolute;right:calc(100% + 48px);top:52px;width:80px;height:32px;}
/* The boat and diver are easter-egg links to the Glug Glug page: re-enable
   pointer events (the rest of .dive is pointer-events:none) and hint the click. */
.dive-boat,.dive-diver{pointer-events:auto;cursor:pointer;}
/* Clip runs from just under the hull to the foot of the viewport; the hose is a
   tall strip inside it whose bottom edge is translated up to the diver, so the
   dot pattern never stretches. Boat, hose and helmet centre all land at the same
   x: the diver's helmet sits 9px into its box and the boat's hose nub 13px into
   its, so the offsets below put all three at 115px from the content edge. */
.dive-hose-clip{position:absolute;right:calc(100% + 114px);top:84px;bottom:0;width:2px;overflow:hidden;}
.dive-hose{position:absolute;left:0;bottom:0;width:2px;height:300vh;
  background:repeating-linear-gradient(to bottom,var(--s-yel) 0 2px,transparent 2px 4px);
  will-change:transform;}
.dive-diver{position:absolute;right:calc(100% + 92px);top:98px;width:32px;height:64px;
  will-change:transform;}
/* Seabed and treasure are anchored to the document, not the viewport, so they
   sit at the true bottom of the page. */
.dive-floor{position:absolute;left:0;right:0;bottom:0;height:16px;z-index:0;
  pointer-events:none;background:url("/sprites/seabed.png") repeat-x left bottom;background-size:16px 16px;
  image-rendering:pixelated;}
/* Treasures, scattered along the floor. The band is a full-width strip on the
   seabed; each item's x is set by JS (see Base.astro) to a randomised spot from
   the content edge rightward, so none sits under the diver's landing area and
   none overlaps a mine's chain. */
.dive-hoard{position:absolute;left:0;right:0;bottom:16px;height:32px;display:none;}
.dive-hoard img{position:absolute;bottom:0;image-rendering:pixelated;}
.dive-t1{width:48px;height:32px;}
.dive-t2{width:32px;height:32px;}
.dive-t3{width:48px;height:32px;}
/* The one crab on the floor walks its own patrol, so it isn't placed by the
   scatter script. It starts level with the content column's left edge (50% -
   380px, just clear of the diver's landing spot) and travels 620px right, which
   keeps it inside the column even at the 1200px breakpoint. It paints after the
   hoard, so it passes in front of the treasures rather than behind them. */
.dive-crab-walk{position:absolute;bottom:16px;left:50%;margin-left:-380px;
  width:32px;height:20px;display:none;animation:crab-walk 34s linear infinite;}
@media (min-width:1200px){.dive-crab-walk{display:block;}}
/* Both ends hold the crab still for 4% of the cycle while scaleX crosses zero,
   which reads as it turning around before it heads back. */
@keyframes crab-walk{
  0%{transform:translateX(0) scaleX(1);}
  46%{transform:translateX(620px) scaleX(1);}
  50%{transform:translateX(620px) scaleX(-1);}
  96%{transform:translateX(0) scaleX(-1);}
  100%{transform:translateX(0) scaleX(1);}}
/* The legs, on a two-frame sheet: crab-walk.png is 32x10, two 16x10 poses with
   the legs on alternating sides raised (scripts/make_crab_frames.py). Drawn at
   x2, so the sheet is 64x20 and each step slides it a frame across. steps(2)
   snaps between the poses instead of tweening them. */
.dive-crab{width:32px;height:20px;image-rendering:pixelated;
  background:url("/sprites/crab-walk.png") 0 0/64px 20px;
  animation:crab-legs .36s steps(2) infinite;}
@keyframes crab-legs{to{background-position:-64px 0;}}
@media (min-width:1200px){.dive{display:block;}}
@media (min-width:1200px){.dive-hoard{display:block;}}
/* Game mines: a spiky bomb floating on a chain that rises from the seabed.
   Anchored to the document bottom like the treasures. The chain is drawn with a
   red repeating-gradient (same trick as the diver's hose), so only the mine
   itself needs a sprite. The two float at different heights; the left one is
   offset well clear of the diver's descent line so they never overlap. */
.dive-mines{position:absolute;top:0;left:0;right:0;height:100%;z-index:0;
  pointer-events:none;overflow:hidden;display:none;}
@media (min-width:1200px){.dive-mines{display:block;}}
.dive-bomb{position:absolute;bottom:16px;left:50%;width:2px;}
.dive-bomb::before{content:"";position:absolute;left:0;bottom:0;width:2px;height:100%;
  background:repeating-linear-gradient(to bottom,var(--s-red) 0 2px,transparent 2px 4px);}
.dive-bomb img{position:absolute;left:50%;bottom:calc(100% - 3px);transform:translateX(-50%);
  width:32px;height:32px;image-rendering:pixelated;}
.dive-bomb-left{margin-left:-448px;height:150px;}
.dive-bomb-right{margin-left:505px;height:212px;}
/* Fish sit at fixed points down the page, in the right margin just past the
   content column (left:50% + 420px clears the 820px column). Anchored to the
   document so they scroll with the page; each drifts a few px very slowly, out
   of sync via animation-delay. */
.dive-school{position:absolute;top:0;left:0;right:0;height:100%;z-index:0;
  pointer-events:none;overflow:hidden;display:none;}
/* Inner wrapper carries the load-time slide-in from the right, kept separate
   from the fish so their drift transform isn't overwritten. */
.dive-shoal{position:absolute;inset:0;}
/* Mirror of the boat: start just off the right edge on any width (the leftmost
   fish rests at 50vw+428px; 50vw-408px pushes it past the viewport). */
@keyframes shoal-in{from{transform:translate3d(calc(50vw - 408px),0,0);}to{transform:translate3d(0,0,0);}}
.dive-fish{position:absolute;left:50%;width:48px;height:32px;image-rendering:pixelated;
  animation:fish-drift 12s ease-in-out infinite;}
/* x staggered so the three don't sit on one vertical line (safe band at 1200px
   is roughly margin-left 412-544: clears the content column, no overflow). */
.dive-fish-1{top:24%;margin-left:428px;}
.dive-fish-2{top:49%;margin-left:522px;animation-delay:-6s;}
.dive-fish-3{top:73%;margin-left:462px;animation-delay:-11s;}
@keyframes fish-drift{0%,100%{transform:translate3d(0,0,0);}
  30%{transform:translate3d(-5px,-4px,0);}
  65%{transform:translate3d(-12px,4px,0);}}
/* Two enemies from the game join the fish in the right margin: same document
   anchoring, staggered into the gaps between the fish rows. The jellyfish bobs
   gently up and down; the squid drifts sideways like the fish. Sprites pulled
   from an in-game screen grab. */
.dive-jelly,.dive-squid{position:absolute;left:50%;image-rendering:pixelated;}
.dive-jelly{width:28px;height:30px;animation:jelly-drift 7s ease-in-out infinite;}
.dive-squid{width:30px;height:28px;animation:fish-drift 12s ease-in-out infinite;}
/* Enemies staggered down the right margin, into the gaps left by the fish rows
   (24/49/73%) and each other, with varied margin-left inside the safe band and
   out-of-sync drift delays so they never sit in a line or pulse together. */
.dive-jelly-1{top:13%;margin-left:472px;}
.dive-jelly-2{top:40%;margin-left:512px;animation-delay:-4s;}
.dive-squid-1{top:60%;margin-left:500px;animation-delay:-8s;}
.dive-squid-2{top:86%;margin-left:452px;animation-delay:-2s;}
@keyframes jelly-drift{0%,100%{transform:translate3d(0,0,0);}
  50%{transform:translate3d(-2px,-11px,0);}}
@media (min-width:1200px){.dive-school{display:block;}}
/* The slide-in is attached to the reveal state, not paused/resumed, so it
   restarts cleanly every time the page is revealed. The home page reveals when
   the tape loader finishes (body gets .revealed); other pages carry .no-loader
   and reveal at once. Guarded by no-preference so reduced-motion skips it and
   the sprites simply appear in place. Replaying the tape re-runs the entrance. */
@media (prefers-reduced-motion:no-preference){
  .revealed .dive-rail,.no-loader .dive-rail{
    animation:rig-in 2.8s cubic-bezier(.2,.75,.3,1) both;}
  .revealed .dive-shoal,.no-loader .dive-shoal{
    animation:shoal-in 2.8s cubic-bezier(.2,.75,.3,1) .2s both;}
}
/* No scroll-driven movement: the diver stays where it starts, hose stays short,
   and the fish and the crab hold still. */
@media (prefers-reduced-motion:reduce){
  .dive-diver,.dive-hose{transform:none !important;}
  .dive-fish,.dive-jelly,.dive-squid{animation:none;}
  .dive-crab-walk,.dive-crab{animation:none;}
  /* The slide-in is only defined under no-preference, so nothing to disable. */
}
.foot-credit{margin-top:14px;font-family:"Spline Sans Mono",ui-monospace,monospace;
  font-size:11px;color:var(--faint);line-height:1.6;}
.foot-credit a{color:var(--s-cyan);text-decoration:none;border-bottom:1px solid var(--s-cyan);}
.foot-credit a:hover{color:var(--bg);background:var(--s-cyan);border-color:var(--s-cyan);}
.foot{margin-top:64px;padding-top:22px;border-top:1px solid var(--rule);
  display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px;
  font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:12px;color:var(--faint);}
.foot a{color:var(--dim);text-decoration:none;border-bottom:1px solid var(--rule);}
.foot a:hover{color:var(--s-yel);border-color:var(--s-yel);}

/* ---- entrance ---- */
.enter{opacity:0;transform:translateY(10px);}
/* Set the resting state directly (opacity:1, no transform) and play the reveal
   WITHOUT `forwards`. With `forwards` the filling transform animation keeps the
   element on its own composited layer for good, which paints above the sticky
   header on hover (e.g. a figure's magenta hover border bleeding over the bar).
   Dropping the fill releases that layer once the .5s animation ends. */
.revealed .enter,.no-loader .enter{opacity:1;transform:none;
  animation:rise .5s cubic-bezier(.2,.7,.3,1);}
@keyframes rise{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:none;}}

@media (max-width:640px){
  .posts-mini li{grid-template-columns:1fr;gap:6px;}
  .alt-links{grid-template-columns:1fr;}
  .page-head h1{font-size:19px;}
  /* Was static: the bar doesn't stick on a phone, but it still has to be the
     positioning context for the menu panel hanging off its bottom edge. */
  .statusbar{position:relative;}

  /* ---- phone menu ----
     The five links plus the controls need about 415px and the column has ~337px,
     so they fold into a panel behind a MENU toggle. Listed the way a Spectrum
     program menu is: numbered options, one per line, pick a number. */
  .statusbar .navtoggle{display:inline-block;}
  .statusbar.js-nav .grp{
    display:none;position:absolute;top:100%;left:0;right:0;z-index:31;
    flex-direction:column;align-items:stretch;gap:0;
    background:var(--bg);border:1px solid var(--rule);border-top:none;
    padding:6px;counter-reset:menuitem;}
  .statusbar.js-nav .grp.open{display:flex;}
  .statusbar.js-nav .grp .navlink{border:none;padding:11px 10px;font-size:13px;}
  .statusbar.js-nav .grp .navlink::before{counter-increment:menuitem;
    content:counter(menuitem) "  ";color:var(--s-yel);}
  .statusbar.js-nav .grp .navlink.active{background:var(--accent);color:var(--bg);}
  /* The yellow index is unreadable on the magenta active row. */
  .statusbar.js-nav .grp .navlink.active::before{color:var(--bg);}
  /* The controls sit under the links, on their own row, still boxed. */
  .statusbar.js-nav .grp .iconbtn{margin:6px 0 0 10px;align-self:flex-start;}
}
@media (prefers-reduced-motion:reduce){
  .enter{opacity:1;transform:none;animation:none;}
}

/* ============================================================
   READING MODE — long-form articles
   The 8-bit chrome (tape frame, status bar, pixel eyebrows)
   still frames the page, but the reading surface itself is
   calm: softer paper, generous leading, a wider measure. It
   stays in the site's mono. A proportional serif was tried
   here and read as a different website bolted onto this one.
   ============================================================ */
body.read{background:var(--bg-read);}

/* reading pages share the same shell width as the rest of the site */

/* ---- article header ---- */
.article-head{margin-top:44px;padding-bottom:26px;border-bottom:2px solid var(--rule);}
.article-head .eyebrow{margin-bottom:20px;}
.article-title{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;font-size:30px;line-height:1.25;
  letter-spacing:-.01em;color:var(--paper);margin:0 0 22px;text-wrap:balance;}
.article-standfirst{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:17px;line-height:1.7;color:var(--dim);
  margin:0 0 26px;max-width:var(--read-measure);font-style:italic;}
.article-meta{display:flex;gap:11px;flex-wrap:wrap;align-items:center;
  font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:12.5px;color:var(--faint);}
.article-meta .dot{color:var(--s-cyan);}
.article-meta .tag{color:var(--s-yel);}
.article-meta .byline{color:var(--dim);}

/* ---- article body: the comfortable reading column ---- */
.article-body{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:16.5px;line-height:1.8;color:var(--ink);
  max-width:var(--read-measure);margin:36px 0 0;}
.article-body > *{max-width:var(--read-measure);}
.article-body p{margin:0 0 24px;}
.article-body h2{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;font-size:20px;line-height:1.35;
  color:var(--paper);margin:52px 0 16px;padding-left:16px;border-left:4px solid var(--accent);}
.article-body h3{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;font-size:17px;line-height:1.4;
  color:var(--paper);margin:40px 0 12px;}
.article-body a{color:var(--s-cyan);text-decoration:none;border-bottom:2px solid var(--s-cyan);padding-bottom:1px;}
.article-body a:hover{color:var(--bg);background:var(--s-cyan);}
.article-body strong{font-weight:700;color:var(--paper);}
.article-body em{font-style:italic;color:var(--paper);}
.article-body ul,.article-body ol{margin:0 0 24px;padding-left:24px;}
.article-body li{margin-bottom:10px;padding-left:4px;}
.article-body li::marker{color:var(--s-mag);}
.article-body hr{border:none;border-top:2px dashed var(--rule);margin:44px 0;max-width:var(--read-measure);}

/* pull quote — big, cyan, still serif */
.article-body blockquote{margin:36px 0;padding:6px 0 6px 24px;border-left:3px solid var(--s-mag);}
.article-body blockquote p{font-family:"Spline Sans Mono",ui-monospace,monospace;font-style:italic;font-size:17px;line-height:1.7;
  color:var(--paper);margin:0;}
.pullquote{font-family:"Spline Sans Mono",ui-monospace,monospace;font-style:italic;font-size:19px;line-height:1.6;
  color:var(--s-cyan);margin:44px 0;padding:0;border:none;text-wrap:balance;}

/* inline + block code — mono, terminal-flavoured */
.article-body code{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:.8em;
  background:#141414;border:1px solid var(--rule);padding:1px 6px;color:var(--s-cyan);}
.article-body pre{background:var(--card);border:2px solid var(--rule);padding:20px 22px;
  overflow-x:auto;margin:0 0 26px;}
.article-body pre code{background:none;border:none;padding:0;color:var(--ink);
  font-size:13.5px;line-height:1.7;display:block;}

/* figures */
.article-body figure{margin:36px 0;}
/* Smooth by default: these are photographs more often than not. Add class
   "pixel" to the <figure> for screenshots that should stay crisp. */
.article-body figure canvas,.article-body figure img,.article-body p > img{
  display:block;width:100%;height:auto;border:2px solid var(--rule);}
.article-body figure.pixel canvas,.article-body figure.pixel img{
  image-rendering:pixelated;}
/* A bare Markdown image sits in its own paragraph; give it the figure's spacing
   so ![alt](/img/x.jpg) looks deliberate without needing the HTML. */
.article-body p > img{margin:36px 0;}
/* A caption written across several lines in MDX arrives wrapped in a <p>;
   don't let it pick up body-paragraph sizing and spacing. */
.article-body figcaption p{margin:0;font-size:inherit;line-height:inherit;color:inherit;}
.article-body figcaption a{color:var(--s-cyan);text-decoration:none;border-bottom:1px solid var(--s-cyan);}
.article-body figcaption a:hover{color:var(--bg);background:var(--s-cyan);}
.article-body figcaption{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:12.5px;
  color:var(--faint);margin-top:12px;text-align:center;line-height:1.6;}

/* article footer: prev/next + back to writing */
.article-foot{margin-top:56px;padding-top:26px;border-top:2px solid var(--rule);
  display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap;
  font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:13px;}
.article-foot a{color:var(--s-cyan);text-decoration:none;border-bottom:1px solid var(--s-cyan);padding-bottom:1px;}
.article-foot a:hover{color:var(--bg);background:var(--s-cyan);}
.article-foot .nx{text-align:right;}
.article-foot .nx-kick,.article-foot .pv-kick{display:block;font-size:10px;color:var(--faint);
  text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px;border:none;}

/* ---- WRITING index (article listing) ---- */
.writing-list{display:flex;flex-direction:column;border-top:2px solid var(--rule);margin-top:8px;}
/* One column: date, then title, then excerpt, then meta. The date used to sit
   in a 118px gutter, which left the left third of every row empty and never
   baseline-aligned against a 20px title. Same stack as the devlog entries. */
.wl-entry{display:block;padding:24px 6px;
  border-bottom:2px solid var(--rule);text-decoration:none;}
/* The title carries the hover. Shifting the whole row moved the text under the
   pointer, which is what made it feel unsteady. */
.wl-entry:hover .wl-title{color:var(--accent);}
.wl-date{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;
  font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--s-yel);
  line-height:1.5;margin-bottom:9px;}
.wl-entry.draft .wl-date{color:var(--faint);}
.wl-title{font-family:"Spline Sans Mono",ui-monospace,monospace;font-weight:700;font-size:20px;line-height:1.3;
  color:var(--paper);margin:0 0 8px;letter-spacing:-.01em;transition:color .12s ease;}
.wl-entry.draft .wl-title{color:var(--dim);}
.wl-excerpt{font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:15px;line-height:1.65;color:var(--dim);
  margin:0 0 12px;max-width:64ch;}
.wl-meta{display:flex;gap:11px;flex-wrap:wrap;align-items:center;
  font-family:"Spline Sans Mono",ui-monospace,monospace;font-size:12px;color:var(--faint);}
.wl-meta .tag{color:var(--s-cyan);}
.wl-meta .dot{color:var(--rule);}
.wl-meta .badge{color:var(--bg);background:var(--rule);padding:3px 7px;
  font-size:10px;letter-spacing:.04em;}
.wl-entry.draft .wl-meta .badge{background:var(--s-yel);color:var(--bg);}

@media (max-width:640px){
  .wl-entry{grid-template-columns:1fr;gap:10px;}
  .article-title{font-size:30px;}
  .article-body{font-size:17.5px;}
}
