/* ===========================================================================
   THE BLOG'S STYLESHEET.

   The landing page is one self-contained file because it is one page carrying
   a canvas, a scroll instrument and 400KB of design. The blog is 46 pages of
   long-form reading; inlining a stylesheet 46 times to save one cached request
   would be the wrong trade in the other direction. This is linked.

   The token block at the top is pasted from index.html by the build, so there
   is one source of truth for the palette and the scale. Everything below it is
   written for reading: this page has no instrument on it.
   ========================================================================= */

:root{
  --canvas:#080B14;
  --surface:#101725;
  --surface-2:#16203A;
  --hairline:#1E2B44;
  --ink:#EAF0FF;
  --ink-soft:#8FA3C8;
  --ink-faint:#7C8FB4;
  --blue:#2969FF;
  --blue-hi:#4C82FF;
  --blue-deep:#094BCC;
  --fault:#FFB35C;
  --fault-ink:#FFB35C;   /* the fault colour where it has to read as ink, not as a lamp */
  --lamp-lit-ink:#A9C6FF;
  --lit-body-ink:#D3E2FF;

  /* the fixed header measures itself into this on load, so section offsets,
     scroll anchoring and the flight’s copy band all clear it by construction
     rather than by a number somebody has to remember to update */
  --bezel-h:84px;

  /* ground and panel material. every value below flips with the theme, so a
     rule written against a token needs no light-mode twin */
  --bezel-bg:rgba(8,11,20,.92);
  --bezel-fade:rgba(8,11,20,.55);
  --bezel-bg-lit:rgba(8,11,20,.9);
  /* the pane the lit window is glazed with. It has to occlude the rim behind
     it, so unlike every other glass on the surface it is nearly opaque. */
  --nav-well:rgba(13,18,32,.94);
  --nav-rim:linear-gradient(180deg, rgba(170,201,255,.60), rgba(83,140,255,.22) 46%, rgba(4,10,26,.5));
  --sheet-bg:rgba(8,11,20,.93);
  --plate-top:#131B2C;
  --plate-bot:#0E1522;
  --sunk-top:#0B111C;
  --sunk-bot:#0A0F19;
  --glass:linear-gradient(180deg, rgba(255,255,255,.11), rgba(255,255,255,.035));
  --glass-hi:linear-gradient(180deg, rgba(255,255,255,.17), rgba(255,255,255,.07));
  --glass-lip:rgba(255,255,255,.24);
  --glass-ring:rgba(83,140,255,.30);
  --word-filter:invert(1) brightness(1.9);
  --grain-op:.045;
  --dith:rgba(41,105,255,.34);
  --cable:linear-gradient(to bottom, rgba(41,105,255,.34) 0%, rgba(41,105,255,.34) 94%, rgba(41,105,255,0) 100%);
  --trace:#6E9BFF;
  --node-off:#131C30;
  --node-ring:rgba(120,160,255,.30);
  --q-rim:rgba(83,140,255,.34);
  /* the callout is glass, and glass is layers. There is no backdrop-filter on
     this surface: it sits over a canvas that repaints every frame, which this
     page’s own rules name as the worst case for a blur. */
  --q-sheen:rgba(150,186,255,.15);
  --q-body-top:rgba(20,29,50,.90);
  --q-body-bot:rgba(9,14,26,.94);
  --q-lip:rgba(205,225,255,.36);
  --q-under:rgba(72,134,255,.34);
  --q-glow:rgba(83,140,255,.55);
  --lamp-off:radial-gradient(circle at 50% 38%, #2A3A5A, #1A2338);
  /* a cast shadow, not a cloud. see the note on `.about__photo img` */
  /* a disc is a small object, so its shadow is short and tight. Same rule the
     cutout taught: grade the throw against the ground it falls on. */
  --disc-shadow:0 2px 4px rgba(0,0,0,.4), 0 20px 34px -18px rgba(0,0,0,.72);
  --disc-rim:rgba(158,192,255,.20);
  --disc-lip:rgba(255,255,255,.10);

  --edge:inset 0 1px 0 rgba(255,255,255,.07);
  --e1:0 1px 2px rgba(0,0,0,.4), 0 8px 24px -12px rgba(0,0,0,.6);
  --e2:0 2px 4px rgba(0,0,0,.45), 0 18px 48px -18px rgba(0,0,0,.7);
  --e3:0 2px 0 var(--blue-deep), 0 14px 34px -12px rgba(41,105,255,.55);

  /* The radius scale, complete, for the same reason as the type scale above.
     --r-frame and --r-screen were a concentric pair for the monitor's bezel,
     which is gone; --r-frame now dresses the film's stage on its own and
     --r-screen has no reader left. */
  --r-control:999px;    /* pills */
  --r-frame:16px;       /* the film's frame */
  --r-panel:12px;       /* cards, the query cards, the footage */
  --r-hair:2px;         /* cables, rails, the menu's bars */
  --r-port:3px;

  /* Three curves, because the page asks motion to do three different jobs.
     --ease is the entrance: strong ease-out, fast off the mark, for anything
     arriving or leaving. --ease-move accelerates as well as decelerates, for
     something already on screen travelling from one place to another. --ease-hue
     is the plain browser ease, for colour and opacity under the pointer, where
     a strong out-curve overshoots a job that has no distance to cover. */
  --ease:cubic-bezier(.23,1,.32,1);
  --ease-move:cubic-bezier(.77,0,.175,1);
  --ease-hue:ease;

  /* The rhythm is stated in rem so it answers the reader's text size. Each of
     these is the px value it has always been at a 16px root -- the page at the
     default setting is unchanged to the pixel -- but at 200% text the gaps now
     grow with the words instead of holding still while the type doubles.
     `--gutter` and `--section` below stay viewport-driven on purpose: a page
     margin is a property of the window, not of the paragraph. */
  --s1:.25rem; --s2:.5rem; --s3:.75rem; --s4:1rem; --s5:1.5rem;
  --s6:2rem; --s7:3rem; --s8:4rem; --s9:5.5rem; --s10:7.5rem; --s11:10rem;

  --gutter:max(clamp(20px, 5vw, 72px), env(safe-area-inset-left), env(safe-area-inset-right));
  /* the spine's x, read by the spine and by the three-step rail below 820 */
  --spine-x:calc(var(--gutter) / 2);
  --section:clamp(72px, 11vw, 160px);
  --measure:62ch;

  /* The type scale, complete. Anything set on this surface is one of these:
     if a new size is needed it gets a name here first, or the ladder stops
     describing the page the way the last four tokens stopped describing it. */
  --t-3xs:.625rem;      /* 10  the callout card's channel */
  --t-2xs:.6875rem;     /* 11  the mono silkscreen */
  --t-xs:.75rem;        /* 12  the chain node's sub-label */
  --t-label:.8125rem;   /* 13  the selector, the chain node's name */
  --t-sm:.875rem;       /* 14  control labels */
  --t-body:.9375rem;    /* 15  small body copy, the most used size on the page */
  --t-base:1.0625rem;   /* 17  body */
  --t-lg:1.25rem;       /* 20  the lede, item titles */
  /* and the display sizes, which are fluid rather than fixed because a headline
     has to hold a line at 320 and at 3440. --t-4xl is tightened again by three
     short-viewport steps further down; the others are not, because only the
     hero is set at a size a short frame cannot hold. */
  --t-xl:clamp(1.5rem, calc(2.4vw + (1rem - 16px) * 1.5), 2rem);
  --t-2xl:clamp(1.9rem, calc(4vw + (1rem - 16px) * 2), 2.75rem);
  --t-3xl:clamp(max(1.75rem, min(7.2vw, 2.3rem)), calc(5.4vw + (1rem - 16px) * 2), 3.6rem);
  --t-4xl:clamp(2.5rem, calc(5.4vw + (1rem - 16px) * 2), 4.5rem);
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
html:focus-within{scroll-padding-top:calc(var(--bezel-h,84px) + 24px)}
@media (prefers-reduced-motion:reduce){ html{scroll-behavior:auto} }
body{
  margin:0;background:var(--canvas);color:var(--ink);
  font-family:"Switzer",system-ui,-apple-system,"Segoe UI",sans-serif;
  font-weight:400;-webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
::selection{background:var(--blue);color:#fff}
:focus-visible{outline:2px solid var(--blue-hi);outline-offset:3px;border-radius:4px}
img{max-width:100%;height:auto}
h1,h2,h3,h4{margin:0;font-weight:700;line-height:1.06;letter-spacing:-.03em;text-wrap:balance}

.mono{
  font-family:"JetBrains Mono",ui-monospace,monospace;
  font-size:var(--t-2xs);letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink-faint);font-weight:400;font-variant-numeric:tabular-nums;
}
.wrap{max-width:1240px;margin:0 auto;padding-inline:var(--gutter)}

/* the pointer ground, the same one the landing page has */
.pxf{
  position:fixed;inset:0;z-index:0;pointer-events:none;
  width:100%;height:100%;image-rendering:pixelated;
  mask-image:radial-gradient(128% 108% at 50% 44%, #000 54%, transparent 100%);
  -webkit-mask-image:radial-gradient(128% 108% at 50% 44%, #000 54%, transparent 100%);
}
.grain{
  position:fixed;inset:0;z-index:1;pointer-events:none;opacity:var(--grain-op);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
.skip{
  position:fixed;top:0;left:50%;z-index:70;transform:translate(-50%,-140%);
  /* 45px, matching the landing page's. It is a keyboard affordance first, but
     it is a real control once focused and there is no reason for it to be the
     one thing on the page under the target size. */
  display:inline-flex;align-items:center;min-height:45px;
  padding:11px 20px;border-radius:var(--r-control);
  background:var(--blue);color:#fff;font-size:var(--t-sm);font-weight:600;
  text-decoration:none;transition:transform .22s var(--ease);
}
.skip:focus-visible{transform:translate(-50%,12px)}

/* ---- the header, the landing page's bezel with the nav pointing home ----- */
.bezel{
  position:fixed;top:0;left:0;right:0;z-index:60;
  display:flex;align-items:center;justify-content:space-between;gap:var(--s5);
  padding:calc(var(--s4) + env(safe-area-inset-top)) var(--gutter) var(--s4);
  background:linear-gradient(to bottom, var(--bezel-bg), var(--bezel-fade) 70%, transparent);
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
}
/* The mark is 26px tall, which makes the link wrapping it a 26px target. On a
   finger that is under half of what it should be, so the link gets the height
   without the image changing size. */
.brand{display:inline-flex;align-items:center;text-decoration:none;flex:none;min-height:44px}
.brand img{display:block;height:26px;width:auto;filter:var(--word-filter)}
.bezel__right{display:flex;align-items:center;gap:var(--s4)}
.bezel__back{
  color:var(--ink-soft);text-decoration:none;font-size:var(--t-sm);
  transition:color .2s var(--ease);white-space:nowrap;
  display:inline-flex;align-items:center;min-height:44px;
}
/* Below 430 the header is the mark, this link and the primary, and the three
   of them do not fit. The link goes: every page here has "All posts" at the
   top of the article and again in the footer, so nothing becomes unreachable. */
@media (max-width:430px){ .bezel__back{display:none} }
@media (max-width:380px){ .bezel .btn{padding:12px 18px;font-size:var(--t-label);min-height:44px} }
@media (hover:hover) and (pointer:fine){ .bezel__back:hover{color:var(--ink)} }

/* ---- controls, the landing page's recipe verbatim ------------------------ */
.btn{
  position:relative;isolation:isolate;
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  font:inherit;font-weight:600;font-size:var(--t-sm);letter-spacing:-.012em;
  padding:14px 26px;border-radius:var(--r-control);border:0;cursor:pointer;
  text-decoration:none;white-space:nowrap;
  transition:transform .16s var(--ease), box-shadow .16s var(--ease), background .16s var(--ease);
}
.btn::before{
  content:"";position:absolute;inset:0;border-radius:inherit;padding:1px;
  background:linear-gradient(180deg, rgba(255,255,255,.5), rgba(255,255,255,.06) 42%, rgba(0,0,0,.22));
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite:exclude;pointer-events:none;
}
.btn--primary{
  color:#fff;
  background:
    linear-gradient(180deg, rgba(255,255,255,.26), rgba(255,255,255,0) 46%),
    linear-gradient(180deg, var(--blue-hi), var(--blue) 58%, var(--blue-deep));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.42),inset 0 -1px 0 rgba(8,11,20,.62),0 10px 26px -10px rgba(4,10,26,.9);
}
.btn--lg{padding:18px 34px;font-size:var(--t-base)}
@media (hover:hover) and (pointer:fine){
  .btn:hover{transform:translateY(-1px)}
  .btn--primary:hover{box-shadow:inset 0 1px 0 rgba(255,255,255,.5),inset 0 -1px 0 rgba(8,11,20,.62),0 14px 34px -10px rgba(41,105,255,.6)}
}
.btn:active{transform:translateY(1px) scale(.97)}

/* ===========================================================================
   THE ARTICLE.

   Set for reading rather than for looking at. 19px on a 68-character measure,
   1.72 leading, and the body at full --ink rather than --ink-soft: 1,500 words
   of soft grey on a near-black ground is where a dark page stops being a
   design decision and starts being an eye test.
   ========================================================================= */
main{position:relative;z-index:2}
.post{
  max-width:calc(var(--post-w) + var(--gutter) * 2);
  margin:0 auto;padding:calc(var(--bezel-h, 84px) + var(--s8)) var(--gutter) var(--s9);
  --post-w:min(68ch, 720px);
}
.post__back{
  display:inline-flex;align-items:center;gap:8px;
  color:var(--ink-faint);text-decoration:none;
  font-family:"JetBrains Mono",ui-monospace,monospace;
  font-size:var(--t-2xs);letter-spacing:.16em;text-transform:uppercase;
  transition:color .2s var(--ease);
}
@media (hover:hover) and (pointer:fine){ .post__back:hover{color:var(--ink)} }
/* A 10px mono label is a 14px-tall link. On a finger the words are the label
   and the box is the target, so the box grows and the words do not. The same
   goes for the index's filter chips at 32px. */
@media (pointer:coarse){
  .post__back{min-height:44px}
  .chip{min-height:44px;padding-inline:20px}
}
.post__head{margin:var(--s6) 0 0;padding-bottom:var(--s7);border-bottom:1px solid var(--hairline);position:relative}
.post__head::after{content:"";position:absolute;left:0;bottom:-1px;width:clamp(28px,3.4vw,46px);height:1px;background:var(--blue)}
.post h1{
  font-size:clamp(2rem, 1.6vw + 1.45rem, 3rem);
  letter-spacing:-.032em;line-height:1.08;margin:0;
}
.post__meta{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3) var(--s5);
  margin-top:var(--s5);
}
.post__meta time,.post__meta span{
  font-family:"JetBrains Mono",ui-monospace,monospace;
  font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;color:var(--ink-faint);
}
.post__lede{
  margin:var(--s5) 0 0;color:var(--ink-soft);
  font-size:var(--t-lg);line-height:1.55;text-wrap:pretty;
}

.prose{
  margin-top:var(--s7);line-height:1.72;color:var(--ink);
  /* Sized by the measure it produces, not by taste. 19px inside a 320px frame
     is a 29-character column -- lines of two or three words, which is where
     reading turns into scanning. This lands 34ch at 320 and 66ch at 1440. */
  font-size:clamp(1rem, .55vw + .92rem, 1.1875rem);
}
/* Zero every block, then space them off each other. Both rules are (0,1,0) so
   the sibling one wins on order -- which `.prose p{margin:0}` did not, being
   (0,1,1), and it silently ran every paragraph in the corpus together. */
.prose > *{margin:0}
.prose > * + *{margin-top:1.15em}
.prose p{text-wrap:pretty}
.prose h2{
  font-size:clamp(1.375rem,1vw + 1.05rem,1.75rem);
  letter-spacing:-.026em;line-height:1.2;margin-top:2.1em;color:var(--ink);
}
.prose h3{
  font-size:clamp(1.125rem,.5vw + 1rem,1.3125rem);
  font-weight:600;letter-spacing:-.02em;line-height:1.25;margin-top:1.8em;color:var(--ink);
}
.prose h2 + *,.prose h3 + *{margin-top:.7em}
.prose strong{font-weight:600;color:#fff}
.prose u{text-decoration-color:var(--q-rim);text-underline-offset:3px}
.prose a{color:var(--blue-hi);text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px}
@media (hover:hover) and (pointer:fine){ .prose a:hover{color:#fff;text-decoration-color:#fff} }
.prose ul,.prose ol{margin-block:1.15em;padding-left:1.35em}
.prose li{margin-top:.5em}
.prose li::marker{color:var(--blue-hi)}
.prose li > p{margin:0}
.prose blockquote{
  margin:1.6em 0;padding:0 0 0 var(--s5);
  border-left:2px solid var(--blue);
  color:var(--ink);font-size:1.0625em;line-height:1.6;
}
.prose blockquote p{margin:0}
.prose hr{border:0;height:1px;background:var(--hairline);margin:2.2em 0}
/* a table on a phone scrolls inside its own box rather than widening the page */
.prose .tablewrap{overflow-x:auto;margin-block:1.6em;-webkit-overflow-scrolling:touch}
.prose table{border-collapse:collapse;width:100%;min-width:30rem;font-size:.9375rem}
.prose th,.prose td{
  text-align:left;vertical-align:top;padding:11px var(--s4) 11px 0;
  border-bottom:1px solid var(--hairline);line-height:1.5;
}
.prose thead th,.prose tr:first-child td:first-child{color:var(--ink);font-weight:600}
.prose td{color:var(--ink-soft)}
.prose td p{margin:0}

/* the closing call, the same offer the landing page makes */
.post__end{
  margin-top:var(--s9);padding-top:var(--s7);
  border-top:1px solid var(--hairline);position:relative;
}
.post__end::before{content:"";position:absolute;left:0;top:-1px;width:clamp(28px,3.4vw,46px);height:1px;background:var(--blue)}
.post__end h2{font-size:var(--t-xl);letter-spacing:-.028em;margin:0 0 var(--s3)}
.post__end p{color:var(--ink-soft);font-size:var(--t-base);line-height:1.6;margin:0 0 var(--s6)}
.post__nav{
  display:grid;grid-template-columns:1fr 1fr;gap:var(--s5);
  margin-top:var(--s8);padding-top:var(--s6);border-top:1px solid var(--hairline);
}
.post__nav a{
  text-decoration:none;color:var(--ink-soft);display:flex;flex-direction:column;gap:6px;
  transition:color .2s var(--ease);
}
.post__nav a:last-child{text-align:right}
.post__nav b{color:var(--ink);font-weight:600;font-size:var(--t-base);letter-spacing:-.014em;line-height:1.3}
@media (hover:hover) and (pointer:fine){ .post__nav a:hover b{color:var(--blue-hi)} }
@media (max-width:560px){ .post__nav{grid-template-columns:1fr} .post__nav a:last-child{text-align:left} }

/* ===========================================================================
   THE INDEX. Rows on rules, not cards -- the same answer the faults and the
   proof section arrived at on the landing page.
   ========================================================================= */
.blog{padding:calc(var(--bezel-h, 84px) + var(--s8)) 0 var(--s9)}
.blog__head{max-width:var(--measure)}
.blog__head h1{font-size:var(--t-3xl);letter-spacing:-.034em;line-height:1.02}
.blog__head p{color:var(--ink-soft);font-size:var(--t-lg);line-height:1.55;margin:var(--s5) 0 0;max-width:54ch}
.blog__filter{
  display:flex;flex-wrap:wrap;gap:var(--s3);margin-top:var(--s7);
  padding-bottom:var(--s6);border-bottom:1px solid var(--hairline);
}
.chip{
  font:inherit;font-family:"JetBrains Mono",ui-monospace,monospace;
  font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;
  padding:9px 16px;border-radius:var(--r-control);cursor:pointer;
  background:var(--surface-2);color:var(--ink-soft);
  box-shadow:inset 0 0 0 1px var(--hairline);border:0;
  transition:color .2s var(--ease), background .2s var(--ease), box-shadow .2s var(--ease);
}
.chip[aria-pressed="true"]{background:var(--blue);color:#fff;box-shadow:none}
@media (hover:hover) and (pointer:fine){
  .chip:not([aria-pressed="true"]):hover{color:var(--ink);box-shadow:inset 0 0 0 1px var(--q-rim)}
}
.posts{list-style:none;margin:0;padding:0}
.posts li[hidden]{display:none}
.pitem{
  display:grid;grid-template-columns:minmax(0,1fr) auto;
  gap:var(--s4) var(--s6);align-items:baseline;
  padding:var(--s6) 0;border-bottom:1px solid var(--hairline);
  text-decoration:none;
}
.pitem__t{
  font-size:clamp(1.125rem,.8vw + .95rem,1.4375rem);
  font-weight:600;letter-spacing:-.022em;line-height:1.25;color:var(--ink);
  transition:color .2s var(--ease);text-wrap:pretty;
}
.pitem__d{
  font-family:"JetBrains Mono",ui-monospace,monospace;
  font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-faint);white-space:nowrap;
}
.pitem__s{
  grid-column:1/-1;color:var(--ink-soft);font-size:var(--t-body);
  line-height:1.6;margin:0;max-width:72ch;
}
@media (hover:hover) and (pointer:fine){ .pitem:hover .pitem__t{color:var(--blue-hi)} }
.blog__empty{padding:var(--s8) 0;color:var(--ink-faint);font-size:var(--t-base)}
@media (max-width:600px){
  .pitem{grid-template-columns:1fr;gap:var(--s3);padding:var(--s5) 0}
  .pitem__d{order:-1}
}

/* ---- the page's own footer ---------------------------------------------- */
body > footer{
  border-top:1px solid var(--hairline);position:relative;z-index:2;
  padding:var(--s7) var(--gutter) calc(var(--s7) + env(safe-area-inset-bottom));
}
.foot{
  max-width:1240px;margin:0 auto;display:flex;flex-wrap:wrap;gap:var(--s5);
  align-items:center;justify-content:space-between;
}
.foot a{
  color:var(--ink-soft);text-decoration:none;font-size:var(--t-sm);
  text-underline-offset:4px;display:inline-flex;align-items:center;
  /* a four-letter word is a 38px-wide target, so the box carries the width the
     word does not */
  min-height:44px;min-width:44px;justify-content:center;padding-inline:6px;
}
@media (hover:hover) and (pointer:fine){ .foot a:hover{color:var(--ink);text-decoration:underline} }
.foot nav{display:flex;gap:var(--s5);flex-wrap:wrap;align-items:center}

@media (prefers-reduced-transparency:reduce){
  .bezel{-webkit-backdrop-filter:none !important;backdrop-filter:none !important;background:var(--canvas);border-bottom:1px solid var(--hairline)}
  .grain{display:none}
}
@media (prefers-contrast:more){
  :root{--hairline:#42598A;--ink-soft:#BACBEA;--ink-faint:#AFC1E0;--grain-op:0}
}
@media (prefers-reduced-motion:reduce){
  *{transition-duration:.001ms !important;animation-duration:.001ms !important}
}
