@font-face{font-family:'Asimov Print B';src:url('fonts/AsimovPrintB.otf') format('opentype');font-weight:400 900;font-style:normal;font-display:swap}
@font-face{font-family:'Asimov Print B';src:url('fonts/AsimovPrintBItal.otf') format('opentype');font-weight:400 900;font-style:italic;font-display:swap}
/* ============================================================
   BASE: the system every page shares, case pages included.
   Loaded before site.css (and before the case pages' inline styles),
   so it defines tokens and site-wide behaviour, and the page styles
   build on it.

   Three jobs, in the order Shrey set them (18 Sep 2026):
   personality, hierarchy, readability.
   ============================================================ */

:root{
  /* TYPE SCALE. Five sizes and nothing between them.
     H1 hero headlines 64, H2 section headlines 40, H3 subheads 28,
     body 18, small 14 (captions, metadata, footnotes). Headings step
     down on narrow screens so a 64px line never wraps into a wall;
     body and small never shrink. 14px is the floor for any text. */
  --t-h1:clamp(2.5rem,5.2vw,4rem);        /* 40 -> 64 */
  --t-h2:clamp(1.875rem,3.4vw,2.5rem);    /* 30 -> 40 */
  --t-h3:clamp(1.375rem,2.3vw,1.75rem);   /* 22 -> 28 */
  --t-body:1.125rem;                      /* 18 */
  --t-small:.875rem;                      /* 14 */
  --lh-tight:1.02; --lh-head:1.1; --lh-body:1.6;

  /* SPACING SCALE. Every gap is one of these. */
  --s-1:4px;  --s-2:8px;  --s-3:12px; --s-4:16px; --s-5:24px;
  --s-6:32px; --s-7:48px; --s-8:64px; --s-9:96px; --s-10:128px;

  /* PALETTE AND FAMILIES. The locked three colours and the three faces.
     Defined here, not in site.css, because the case pages load only this
     file and tw.css. */
  --cream:#F7F4EC; --deep:#173CA8; --fluoro:#E8251F;
  --cream-rgb:247,244,236; --deep-rgb:23,60,168;
  --fd:'Asimov Print B','Geist',system-ui,sans-serif;
  --fd-track:-.015em; /* Asimov Print B is already tight; Geist needed more */
  --fb:'Inter','Geist',system-ui,sans-serif;
  --fu:'Geist Light','Geist',system-ui,sans-serif;
  --pad:clamp(1.2rem,4vw,3rem); --max:1240px;

  /* PAPER. A hand-drawn grid, not a ruled one (Shrey, 2 Oct 2026, with a
     reference image): 90px cells, lines that wobble between the crossings and
     are pinned at the tile edges so the 270px tile repeats without a seam.
     Two of every three lines sit lighter, the way a drawn grid does.
     Opacity is carried on the layer, not baked into the colour, so one number
     tunes the whole site. Keep it at or under .30: the test is that body copy
     over it reads exactly as it does on plain paper. */
  --paper:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22270%22%20height%3D%22270%22%20viewBox%3D%220%200%20270%20270%22%3E%3Cg%20fill%3D%22none%22%20stroke%3D%22%23173CA8%22%20stroke-width%3D%221.15%22%20stroke-linecap%3D%22round%22%3E%3Cpath%20d%3D%22M0.0%200.0%20Q-1.5%2022.5%20-1.1%2045.0%20Q-0.6%2067.5%200.1%2090.0%20Q-0.2%20112.5%20-0.5%20135.0%20Q0.3%20157.5%201.2%20180.0%20Q0.0%20202.5%200.5%20225.0%20Q0.6%20247.5%200.0%20270.0%22%20stroke-opacity%3D%220.30%22%2F%3E%3Cpath%20d%3D%22M90.0%200.0%20Q91.1%2022.5%2090.2%2045.0%20Q89.8%2067.5%2090.5%2090.0%20Q89.3%20112.5%2089.2%20135.0%20Q89.1%20157.5%2089.7%20180.0%20Q90.0%20202.5%2090.4%20225.0%20Q90.7%20247.5%2090.0%20270.0%22%20stroke-opacity%3D%220.22%22%2F%3E%3Cpath%20d%3D%22M180.0%200.0%20Q179.3%2022.5%20179.3%2045.0%20Q179.7%2067.5%20180.0%2090.0%20Q178.4%20112.5%20178.6%20135.0%20Q179.4%20157.5%20178.5%20180.0%20Q179.7%20202.5%20179.5%20225.0%20Q179.2%20247.5%20180.0%20270.0%22%20stroke-opacity%3D%220.30%22%2F%3E%3Cpath%20d%3D%22M0.0%200.0%20Q22.5%20-0.6%2045.0%20-0.7%20Q67.5%20-1.5%2090.0%20-0.3%20Q112.5%20-0.3%20135.0%201.9%20Q157.5%201.7%20180.0%201.8%20Q202.5%200.7%20225.0%20-0.6%20Q247.5%200.2%20270.0%200.0%22%20stroke-opacity%3D%220.30%22%2F%3E%3Cpath%20d%3D%22M0.0%2090.0%20Q22.5%2090.9%2045.0%2090.0%20Q67.5%2089.6%2090.0%2089.6%20Q112.5%2091.3%20135.0%2091.5%20Q157.5%2089.1%20180.0%2088.7%20Q202.5%2087.8%20225.0%2088.2%20Q247.5%2088.9%20270.0%2090.0%22%20stroke-opacity%3D%220.22%22%2F%3E%3Cpath%20d%3D%22M0.0%20180.0%20Q22.5%20180.0%2045.0%20181.5%20Q67.5%20180.4%2090.0%20179.8%20Q112.5%20180.7%20135.0%20181.0%20Q157.5%20179.2%20180.0%20178.6%20Q202.5%20180.6%20225.0%20181.6%20Q247.5%20181.1%20270.0%20180.0%22%20stroke-opacity%3D%220.30%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --paper-size:270px 270px;
  /* the alpha is baked into the tile: keep the strongest line at or under .30 */
  /* MOTION. Three speeds, two curves. */
  --d-press:90ms;  --d-1:180ms;  --d-2:420ms;  --d-3:700ms;
  --e-out:cubic-bezier(.16,1,.3,1);
  --e-spring:cubic-bezier(.34,1.56,.64,1);   /* the release overshoot */
}


/* ---------- PALETTE, RETRO (21 Sep 2026, Shrey: "a bit like Superman's") ----------
   The comic-book primaries, aged to newsprint so they sit on cream paper
   rather than shouting on white. Blue carries the type, red is the surface,
   yellow is the rare highlight. The ground was lifted from #EFE2BA to #F7F4EC on
   24 Sep 2026 at Shrey’s instruction: the same cream, taken towards white.
   Every pairing below improved, none regressed. Measured on #F7F4EC:
     --deep  #173CA8  cape blue      7.72  body type, rules, planes
     --accent #E8251F cape red       3.69  planes and type 24px and over ONLY
     --accent-type #B8141A           5.52  red as SMALL type
     --yellow #FFED10 Superman "Broom" yellow, trued to the source palette 23 Sep        ACCENT ONLY: marks, highlights, stars. Never a plane.
     --ink   #0D1633  print black         type on red or yellow planes
     --riso  #E00080  the star artefact's own ink; the star and nothing else
   Small type never sits directly on red (ink 3.55, cream 3.88 both fail):
   it gets a cream label under it. */
:root{
  --accent:#E8251F;
  --accent-type:#B8141A;
  --yellow:#FFED10;
  --ink:#0D1633;
  --riso:#E00080;
}
/* DARK (rebuilt 22 Sep 2026). The toggle used to flip a set of variables the
   home, work, me and contact pages never read, so it only recoloured the nav.
   Now the two base tokens swap: the cream ground becomes night blue and the
   blue type becomes paper, so every rule built on them follows. Red and
   yellow are unchanged. Measured on #101830: paper 13.71, red 3.94 (large
   type only, as in light), lifted red 6.29 for small accent type, yellow 10.86.
   Case pages keep their own scoped dark set in assets/tw/input.css. */
html[data-theme="dark"]{
  --accent-type:#FF6B5E;
  --paper:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22270%22%20height%3D%22270%22%20viewBox%3D%220%200%20270%20270%22%3E%3Cg%20fill%3D%22none%22%20stroke%3D%22%23EDE3C4%22%20stroke-width%3D%221.15%22%20stroke-linecap%3D%22round%22%3E%3Cpath%20d%3D%22M0.0%200.0%20Q-0.2%2022.5%20-1.7%2045.0%20Q-1.3%2067.5%200.9%2090.0%20Q0.3%20112.5%201.8%20135.0%20Q1.3%20157.5%201.9%20180.0%20Q0.8%20202.5%20-1.9%20225.0%20Q-1.4%20247.5%200.0%20270.0%22%20stroke-opacity%3D%220.18%22%2F%3E%3Cpath%20d%3D%22M90.0%200.0%20Q89.0%2022.5%2089.7%2045.0%20Q90.0%2067.5%2091.8%2090.0%20Q91.3%20112.5%2090.2%20135.0%20Q88.8%20157.5%2088.6%20180.0%20Q88.4%20202.5%2090.0%20225.0%20Q90.3%20247.5%2090.0%20270.0%22%20stroke-opacity%3D%220.13%22%2F%3E%3Cpath%20d%3D%22M180.0%200.0%20Q179.4%2022.5%20180.1%2045.0%20Q181.7%2067.5%20181.4%2090.0%20Q180.5%20112.5%20180.5%20135.0%20Q178.9%20157.5%20178.2%20180.0%20Q179.0%20202.5%20179.8%20225.0%20Q178.9%20247.5%20180.0%20270.0%22%20stroke-opacity%3D%220.18%22%2F%3E%3Cpath%20d%3D%22M0.0%200.0%20Q22.5%20-1.2%2045.0%20-1.5%20Q67.5%20-0.2%2090.0%201.1%20Q112.5%20-0.6%20135.0%20-1.5%20Q157.5%200.3%20180.0%201.3%20Q202.5%202.0%20225.0%200.6%20Q247.5%20-0.3%20270.0%200.0%22%20stroke-opacity%3D%220.18%22%2F%3E%3Cpath%20d%3D%22M0.0%2090.0%20Q22.5%2090.0%2045.0%2091.8%20Q67.5%2090.6%2090.0%2091.0%20Q112.5%2090.5%20135.0%2089.1%20Q157.5%2089.6%20180.0%2088.5%20Q202.5%2089.2%20225.0%2089.7%20Q247.5%2089.9%20270.0%2090.0%22%20stroke-opacity%3D%220.13%22%2F%3E%3Cpath%20d%3D%22M0.0%20180.0%20Q22.5%20181.4%2045.0%20181.1%20Q67.5%20180.2%2090.0%20179.8%20Q112.5%20181.6%20135.0%20181.8%20Q157.5%20180.9%20180.0%20181.7%20Q202.5%20180.3%20225.0%20179.6%20Q247.5%20180.3%20270.0%20180.0%22%20stroke-opacity%3D%220.18%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --cream:#101830; --deep:#EDE3C4;
  --cream-rgb:16,24,48; --deep-rgb:237,227,196;
  color-scheme:dark;
}

/* ---------- navigation, every page ----------
   Phones used to get no navigation at all: the links were hidden under
   820px on ten of fifteen pages. The site is judged in under thirty
   seconds, so every route stays one tap away. Two rows on phones: name
   and theme toggle, then the four links at a full tap height. The html
   body prefix outranks the page styles, which load after this file. */
html body nav .links a{font-size:var(--t-small)}
@media (max-width:820px){
  html body nav{flex-wrap:wrap;row-gap:0;width:100%;max-width:100vw;box-sizing:border-box}
  html body nav .nav-end{display:contents}
  html body nav .theme-toggle{order:2}
  html body nav .links{display:flex;order:3;flex-basis:100%;min-width:0;gap:var(--s-4);justify-content:flex-start;flex-wrap:wrap}
  html body nav .links a{padding:var(--s-3) 0;letter-spacing:.02em}

}

/* ---------- focus: always visible, never the browser default ---------- */
:where(a,button,summary,[role="button"],[tabindex]):focus-visible{
  outline:2px solid var(--accent);outline-offset:3px}

/* ---------- press feedback ----------
   Every click answers. assets/feel.js springs the target itself (it
   composes with the transforms components already use); this handles
   the colour side of the state change and kills the grey tap flash. */
:where(a,button,summary,[role="button"]){-webkit-tap-highlight-color:transparent}
.is-pressed{filter:brightness(.94)}

/* the email address copies itself as well as opening mail, and says so */
.copied-note{display:inline-block;margin-left:var(--s-3);font-family:var(--fu);
  font-size:var(--t-small);letter-spacing:.04em;opacity:0;transform:translateY(4px);
  transition:opacity var(--d-1) var(--e-out),transform var(--d-2) var(--e-spring);vertical-align:middle}
.copied-note.on{opacity:.8;transform:none}

/* ---------- clip reveal, for section headlines ----------
   The line rises out of its own baseline. Added by script only (the
   hiding class is never in the markup), and every path ends visible. */
html.js-clip .clip{clip-path:inset(-10% -5% 100% -5%);transform:translateY(.35em);
  transition:clip-path var(--d-3) var(--e-out),transform var(--d-3) var(--e-out)}
html.js-clip .clip.in{clip-path:inset(-10% -5% -20% -5%);transform:none}

@media (prefers-reduced-motion:reduce){
  html.js-clip .clip{clip-path:none;transform:none;transition:none}
  .copied-note{transition:none}
}

/* ---------- small labels hold AA at 14px ----------
   These were faded to .5-.6 opacity when they were 9-10px texture. At the
   14px floor they are reading text, and .8 is the lowest that clears 4.5:1
   on both cream and ink. The hero discipline line had a fixed colour that
   did not follow the theme. */
html body :is(.phead .no,.pfoot .sig,.lab,.fbtn,.pm,.no){opacity:.82}
html body .fbtn[aria-pressed="true"]{opacity:1}
html body .proof .no{opacity:1}


/* ---------- interface guidelines pass (20 Sep 2026) ---------- */
.skip{position:fixed;left:var(--s-3);top:var(--s-3);z-index:200;padding:var(--s-3) var(--s-4);
  background:#0D1633;color:#F7F4EC;font-family:var(--fu);font-size:var(--t-small);
  text-decoration:none;transform:translateY(-160%);transition:transform var(--d-1) var(--e-out)}
.skip:focus-visible{transform:none}
:where(h1,h2,h3,.cta,.wk-title,.pg-title,.lt-cta){text-wrap:balance}
:where(p,li){text-wrap:pretty}
:where(a,button,summary,[role="button"]){touch-action:manipulation}
:where([id]){scroll-margin-top:96px}

/* ============================================================
   THE ISLAND (20 Sep 2026)
   One object at the top of every page. Expanded: name, links, theme. As the
   reader scrolls down it contracts to the name and a live readout (the
   section they are in, the song playing, the address just copied); scrolling
   up, hovering, tapping or tabbing into it opens it again. Navigation never
   leaves the screen, which is the point: the old bar hid itself on the way
   down, exactly when a visitor was deciding whether to get in touch.
   Clashes, marked: the veto list bans centred layouts and pills. The island
   is both by definition; picked on reachability. The page stays asymmetric.
   ============================================================ */
html body nav#nav{position:fixed;top:var(--s-3);left:50%;right:auto;bottom:auto;transform:translateX(-50%);
  display:grid;grid-template-columns:max-content max-content;align-items:center;gap:var(--s-4);width:var(--island-w,max-content);max-width:calc(100vw - var(--s-5));
  min-height:48px;box-sizing:border-box;padding:var(--s-1) var(--s-2) var(--s-1) var(--s-5);margin:0;
  background:#0D1633;color:#F7F4EC;border:1px solid rgba(247,244,236,.18);border-radius:999px;z-index:90;
  font-family:var(--fu);font-size:var(--t-small);letter-spacing:.08em;text-transform:uppercase;
  overflow:hidden;
  transition:width 620ms var(--e-spring),padding 620ms var(--e-spring),background-color var(--d-1) ease}
html[data-theme="dark"] body nav#nav{background:#173CA8}
html body nav#nav a{color:#F7F4EC;text-decoration:none}
/* The sm monogram, cropped from the three directions on the supplied
   artboard (2 Oct). It lives here, not in site.css, because the case pages
   load base.css and tw.css only: in site.css it rendered at its natural
   881px on those pages and ran off a phone screen. */
/* The mark sits at the type's own size (2 Oct feedback: the logo was bigger
   than the words beside it). 1em of the nav's font-size, which is --t-small,
   so the two scale together for ever. */
html body nav#nav .sig-logo{display:block;width:auto;height:1.05em;object-fit:contain}
html body nav#nav .sig{grid-area:1/1;font-family:var(--fu);font-size:var(--t-small);font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;white-space:nowrap;padding:var(--s-3) 0}
html body nav#nav .nav-end{grid-area:1/2;min-width:0;display:flex;align-items:center;white-space:nowrap;
  transition:opacity 260ms ease,transform 420ms var(--e-spring)}
html body nav#nav .links{display:flex;gap:var(--s-5)}
html body nav#nav .links a{padding:var(--s-3) 0;white-space:nowrap;min-width:24px;text-align:center}
html body nav#nav .theme-toggle{width:40px;height:40px;margin-left:var(--s-3)}
.island-now{grid-area:1/2;min-width:0;display:flex;align-items:center;gap:var(--s-2);opacity:0;white-space:nowrap;pointer-events:none;
  font-family:var(--fu);font-size:var(--t-small);letter-spacing:.04em;
  transition:opacity 260ms ease,transform 420ms var(--e-spring);transform:translateY(4px)}
.inow-dot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--accent-type)}
nav#nav.is-live .inow-dot{animation:inow 1.2s ease-in-out infinite}
@keyframes inow{50%{opacity:.35}}
html body nav#nav.is-compact .nav-end{opacity:0;transform:none;pointer-events:none;transition:opacity 140ms ease}
html body nav#nav.is-compact .island-now{opacity:1;transform:none;pointer-events:auto;transition:opacity 220ms ease 320ms}
html body nav#nav.is-compact{cursor:pointer;padding-right:var(--s-4)}
html body nav#nav.is-compact .island-now{justify-self:start}
html body nav#nav.is-compact:is(:hover,:focus-within) .nav-end{opacity:1;transform:none;pointer-events:auto}
html body nav#nav.is-compact:is(:hover,:focus-within) .island-now{opacity:0;transform:none;transition:opacity 120ms ease}
html body nav#nav.is-compact:is(:hover,:focus-within){padding-right:var(--s-2)}
/* the way down tightens to the logo alone; the way up names the section again */
html body nav#nav.is-tight:not(:hover):not(:focus-within) .island-now{opacity:0;transform:none;transition:opacity 120ms ease}
html body nav#nav.is-tight:not(:hover):not(:focus-within){padding-right:var(--s-2)}
@media (max-width:820px){
  html body nav#nav{display:flex;left:var(--s-3);right:var(--s-3);width:auto;max-width:none;transform:none;flex-wrap:wrap;
    row-gap:0;border-radius:24px;padding:var(--s-1) var(--s-2) var(--s-1) var(--s-4)}
  html body nav#nav .nav-end{display:contents}
  html body nav#nav .island-now{order:1;flex:1;min-width:0}
  html body nav#nav .theme-toggle{order:2;margin-left:auto}
  html body nav#nav .links{order:3;flex-basis:100%;gap:var(--s-4);max-height:56px;overflow:hidden;
    transition:max-height var(--d-2) var(--e-spring),opacity var(--d-1) ease}
  html body nav#nav.is-compact .links{max-height:0;opacity:0;margin:0}
  html body nav#nav.is-compact:focus-within .links{max-height:56px;opacity:1}
  html body nav#nav.is-compact .island-now{max-width:none}
  html body nav#nav.is-compact .theme-toggle{display:none}
}
@media (prefers-reduced-motion:reduce){
  html body nav#nav,html body nav#nav .nav-end,.island-now,html body nav#nav .links{transition:none}
  html body nav#nav{width:auto}
  nav#nav.is-live .inow-dot{animation:none}
}

/* ---------- safe gutters (21 Sep 2026) ----------
   Copy was reaching the window edge. Every page container now keeps a gutter
   that also clears a notched phone's curved corners, and the fixed furniture
   (island, record) sits inside the same safe area. */html body .wrap,html body .desk-surface{
  padding-left:max(var(--pad),env(safe-area-inset-left,0px));
  padding-right:max(var(--pad),env(safe-area-inset-right,0px))}
@media (max-width:820px){
  html body nav#nav{left:max(var(--s-3),env(safe-area-inset-left,0px));
    right:max(var(--s-3),env(safe-area-inset-right,0px))}
}
html body .vinyl{right:max(clamp(.8rem,2vw,1.6rem),env(safe-area-inset-right,0px));
  bottom:max(clamp(.8rem,2vw,1.6rem),env(safe-area-inset-bottom,0px))}

/* ============================================================
   TYPE (23 Sep 2026, Reference Book row 07, marked Take)
   Headings    Geist Black (900), with a pressed-ink edge (see INK below)
   Body        Inter
   Annotation  Geist Light, one weight for every label, caption and nav item
   All three are OFL via Google Fonts, so the Milligram trial licence problem
   is gone. 'Geist Light' is an alias of the same Geist file pinned to 300, so
   every rule that sets a label's family gets the light cut without a weight.
   ============================================================ */
@font-face{font-family:'Geist Light';font-style:normal;font-weight:300;font-display:swap;
  src:url(https://fonts.gstatic.com/s/geist/v5/gyByhwUxId8gMEwcGFU.woff2) format('woff2')}
body{font-synthesis:none}

/* headings get the display tracking; the dither that used to sit here was
   removed 21 Sep 2026 at Shrey's instruction */
:where(main,.mag,footer) :is(h1,h2):not(.hero-name),.wk-title{letter-spacing:var(--fd-track)}

/* ---------- shared across every page (moved here 21 Sep 2026 so the case
   pages, which do not load site.css, get the same) ---------- */
/* ---------- CROSS-PAGE TRANSITION ----------
   The two skins meet at a navigation, so the switch must not snap.
   View Transitions cross-fades document to document where supported and
   is inert everywhere else, which is the right kind of enhancement: the
   site is not relying on it to be usable. 260ms, matching the enter
   timing used everywhere else. */
@view-transition{navigation:auto}
::view-transition-old(root),::view-transition-new(root){animation-duration:260ms;
  animation-timing-function:cubic-bezier(.16,1,.3,1)}
@media(prefers-reduced-motion:reduce){
  @view-transition{navigation:none}
}
/* island links: a rule draws under the one you are on or over */
html body nav#nav .links a{position:relative;text-decoration:none}
/* [WORK] becomes WORK with a rule under it: the brackets fade as the line
   draws, and come back when the pointer leaves. One gesture, two states. */
html body nav#nav .links a i{font-style:normal;display:inline-block;
  transition:opacity 180ms var(--e-out),transform 180ms var(--e-out)}
html body nav#nav .links a:is(:hover,:focus-visible,[aria-current="page"]) i{opacity:0}
html body nav#nav .links a:is(:hover,:focus-visible,[aria-current="page"]) i:first-of-type{transform:translateX(4px)}
html body nav#nav .links a:is(:hover,:focus-visible,[aria-current="page"]) i:last-of-type{transform:translateX(-4px)}
html body nav#nav .links a::after{content:"";position:absolute;left:.42em;right:100%;bottom:var(--s-2);height:2px;background:var(--accent);transition:right 220ms var(--e-out)}
html body nav#nav .links a:is(:hover,:focus-visible,[aria-current="page"])::after{right:.42em}
@media (prefers-reduced-motion:reduce){html body nav#nav .links a::after{transition:none}}

/* ---------- INK (Reference Book row 07) ----------
   Display headings read hard-pressed: a faint roughened edge and a hair of
   ink spread, from an SVG filter site.js adds to every page. Subtle on
   purpose; the words stay crisp at reading distance. */
:where(h1:not(.hero-name),h2,.said,.cta){filter:url(#ink)}
@media (forced-colors:active){:where(h1,h2,.cta){filter:none}}

/* ---------- ASSET PLACEHOLDERS (Reference Book rule 2) ----------
   Every missing icon, logo or image holds its exact slot as a labelled box,
   The slot is blank on the page (7 Oct): a ruled box awaiting an image reads
   as a layout, and the same box with a name and a ticket number in it reads as
   a bug to everyone except the person holding the ticket. Which slot is which
   lives in data-slot and in FILL-IN.md. */
.ph{display:grid;place-content:center;gap:2px;box-sizing:border-box;padding:6px;text-align:center;
  background:repeating-linear-gradient(135deg,#FCFAF4 0 8px,#F7F4EC 8px 16px);color:#0D1633;
  border:2px dashed #0D1633;font-family:var(--fu);font-size:11px;line-height:1.25;letter-spacing:.04em;text-transform:uppercase}
/* ---------- THE GAP REGISTER (7 Oct 2026) ----------
   Every missing thing on the site is now one component with one treatment and
   one code, G01 upward in page order, matched line for line by FILL-IN.md.
   Four different treatments were in use before this (a dashed box on /work and
   /contact, a dashed box in the magazine, a dashed utility box on the case
   pages, and two dead icon buttons in the contact row), so a visitor met the
   same absence looking four different ways and read it as four kinds of broken.
   The treatment is the red left rule the system already uses for a trade-off
   and a pull quote, which reads as an editor's margin note rather than a hole.
   Lives in base.css because the case pages load only base.css and tw.css. */
.slot{display:block;max-width:32em;margin:var(--s-5) 0;border-left:3px solid var(--fluoro);
  padding:var(--s-2) 0 var(--s-2) var(--s-4);font-family:var(--fu);font-size:var(--t-small);
  line-height:1.6;letter-spacing:.01em}
.slot b{font-family:var(--fd);font-weight:900;font-size:1em;letter-spacing:.08em;
  color:var(--accent-type);margin-right:.6em}

/* ---------- THE STAR LOADER (13 Oct 2026, at his instruction) ----------
   The site's own mark, drawn rather than spun: the stroke writes itself on,
   the fill lands, and the whole thing steps rather than glides because that is
   the idiom the ransom tiles already animate on. It lives in base.css because
   every route carries a loading page now, not just home. */
/* The injected loading page is scoped to its own id and carries everything it
   needs, because the case pages load only base.css and tw.css and the richer
   home loader lives in site.css. Self-contained beats moved: nothing of home's
   changes. */
#sm-page-loader{position:fixed;inset:0;z-index:120;background:var(--cream);
  display:grid;place-items:center;transition:clip-path 620ms cubic-bezier(.16,1,.3,1)}
#sm-page-loader.gone{clip-path:inset(0 0 100% 0);pointer-events:none}
#sm-page-loader .lgrid{display:grid;place-items:center;gap:var(--s-4)}
#sm-page-loader .lrule{width:min(42vw,280px);height:2px;background:var(--deep);
  transform:scaleX(0);transform-origin:0 50%}
#sm-page-loader .lrule.draw{animation:lrule-draw 520ms cubic-bezier(.16,1,.3,1) forwards}
@keyframes lrule-draw{to{transform:scaleX(1)}}

.lstar{display:block;width:clamp(56px,7vw,96px);height:auto;margin:0 auto var(--s-5);
  fill:var(--fluoro);opacity:0}
.lstar.spin{animation:lstar-in 520ms steps(6,end) forwards}
@keyframes lstar-in{
  from{opacity:0;transform:rotate(-70deg) scale(.5)}
  to{opacity:1;transform:rotate(0deg) scale(1)}
}
@media (prefers-reduced-motion:reduce){.lstar{opacity:1}.lstar.spin{animation:none}}

/* ---------- THE GRID FADES IN (12 Oct 2026, at his instruction) ----------
   The drawn grid was a flat texture at one strength everywhere. It now starts
   nearly absent at the top of the page and comes up as the visitor scrolls, so
   the paper asserts itself the further in they get. --paper-in is written by
   site.js from the scroll position, 0 at the top and 1 by the second screen.
   It is a custom property on :root and the only thing reading it is the ground
   layer, so the cost is one variable write per frame and no relayout. */
:root{--paper-in:0}
body::before{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  background-image:var(--paper);background-size:var(--paper-size);
  /* Dialled back 13 Oct, at his instruction: the grid was reaching opacity 1
     and reading as squared paper rather than as texture. The ceiling is .58
     now, and the floor .16. The SVG itself is untouched, so the strongest
     stroke is still .30 of this, which is the rule in CLAUDE.md.
     It also no longer runs the whole page: the flat blocks below paint over
     it, so the grid is the texture of SOME sections rather than of the site. */
  opacity:calc(.16 + var(--paper-in) * .42);transition:opacity 120ms linear}
@media (prefers-reduced-motion:reduce){body::before{opacity:1;transition:none}}

.vh{position:absolute!important;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ============================================================
   THE MOBILE NAV (20 Oct 2026). His note: "the mobile navigation is a
   failure, with a frustrating scroll experience." He is right, and the
   cause was specific rather than cosmetic.

   The island has three shapes and the OPEN one is reached by :hover. A
   touch screen has no hover, so the moment feel.js added .is-compact on the
   first scroll down, the links row was clipped to max-height 0 and there was
   no gesture left anywhere to reopen it. Below the fold, on a phone, the
   site had NO NAVIGATION AT ALL.

   The links were never hidden by opacity, which is why they read as present
   in the DOM and in a class check: they were clipped to ZERO HEIGHT inside
   an overflow:hidden pill. Found by measuring the box, not by reading the
   rules.

   Below 820px the island does not shrink. The three shapes exist to buy
   horizontal room on a wide screen; on a phone the links already wrap to
   their own row and there is nothing to save. Stable beats clever when the
   clever state cannot be exited.

   THIS BLOCK IS LAST IN THE FILE ON PURPOSE. The first attempt sat at line
   110 and lost to a rule at 239, which is the third time source order has
   silently beaten a fix in this repo.
   ============================================================ */
@media (max-width:820px){
  html body nav#nav.is-tight,
  html body nav#nav.is-compact{width:100%;padding-right:var(--s-4);cursor:auto}
  html body nav#nav.is-tight .links,
  html body nav#nav.is-compact .links,
  html body nav#nav .links{max-height:none;opacity:1;pointer-events:auto;
    transform:none;display:flex;overflow:visible;margin:0}
  html body nav#nav.is-tight .nav-end,
  html body nav#nav.is-compact .nav-end{opacity:1;pointer-events:auto;transform:none}
  /* the section read-out belongs to the wide island; on a phone it would
     push the links onto a third row to repeat what the heading says */
  html body nav#nav .island-now{display:none}
  /* the pill has to be able to grow to two rows */
  html body nav#nav{height:auto;overflow:visible}
}
