/* ── the blog ─────────────────────────────────────────────────────────────
   THE FOURTH PAGE ON THE SAME SITE.

   /, /app and /pricing are one design: Schibsted Grotesk, the night ground,
   an announcement bar over a nav with the mark at the left, bands separated
   by dotted rules, and a close band over a four-column footer. The blog used
   to be the other one — Inter, a white sheet, its own header and its own
   footer — which read as a different product the moment you arrived from the
   front page. This file is the blog brought across.

   LOAD ORDER IS THE WHOLE CONTRACT, and it is two halves:

     docs.css              the PROSE ENGINE. A post is made of the same
                           callouts, tables and drawn diagrams the docs
                           render, out of the same renderer, and there is no
                           reason to own a second copy of them.
     landing.css, night.css   the DESIGN. Tokens, type, the chrome, the night
                           ground. Loaded after, so it wins on everything the
                           two sheets both have an opinion about.
     blog.css              this file, last, doing three jobs:
                             1 · re-point the prose engine's token names at
                                 the night palette, so a page written for a
                                 white sheet reads correctly on this one;
                             2 · carry the few rules docs.css expected from
                                 app.css, which this page does not load;
                             3 · lay out the parts only the blog has — the
                                 listing, the covers, the reading column.

   APP.CSS IS DELIBERATELY NOT LOADED. docs.css was written on top of it, and
   it would be the obvious fifth sheet — but it carries the old site's chrome
   under the same class names landing.css uses: .navwrap, .nav, .nav-burger,
   .btn. Both on one page and the nav is a white pill floating on the night
   ground, because app.css sets a background landing.css never thought to
   clear. The only thing the prose actually wanted from those hundred
   kilobytes was the code card, and the code card is twelve rules.

   Nothing below invents a colour. Everything is landing.css's scale, so a
   change there moves the blog with the rest of the site, which is the entire
   point of the exercise.
   ───────────────────────────────────────────────────────────────────────── */

/* ── 1 · the bridge ───────────────────────────────────────────────────────
   app.css and docs.css were written against the clerk-derived palette, and
   they read it through tokens rather than literals — which is the only reason
   this is a block of aliases rather than a rewrite of nine hundred rules.
   night.css has already redefined --ink and --line; these are the names it
   does not know about.

   Each one is the nearest thing in the new scale, not a new colour. */
:root {
  --sheet:       var(--night);
  --ground:      var(--night);
  --ink-2:       var(--ink2);
  --muted:       var(--ink2);
  --faint:       var(--ink3);
  --line-2:      #343946;
  --accent:      var(--ink);
  --accent-2:    #DDE0EA;
  /* the ground behind inline `code`. A wash rather than a step: a mark inside
     a sentence has to be told apart from the sentence, which is a different
     job from a panel pretending to float above the page. */
  --accent-soft: rgba(255, 255, 255, .07);
  --r-btn:       8px;
  --r-card:      14px;

  /* a status colour has to clear the night ground, and the light scale's
     forest green and umber do not */
  --green: #6FD3A6;
  --amber: #E5B567;

  /* shadows do not read on night; the hairline does the work instead */
  --shadow-card: 0 1px 2px rgba(0, 0, 0, .4);
  --shadow-pop:  0 24px 56px -20px rgba(0, 0, 0, .7);

  /* The blog's accent was --brand-soba, a brown drawn for the old sheet.
     Periwinkle is this design's one hue — it marks the thing that is new and
     nothing else — and on the blog that is the topic of the post you are
     looking at and the live link inside it. The husk is its quiet half. */
  --brand-soba:   var(--periwinkle);
  --brand-husk:   #D9DDEA;
  /* the ground docs.css puts behind a blockquote and inside a tinted diagram
     box. Nothing on this page has a ground of its own any more — the rule on
     the left of a quote and the stroke around a box do that work. */
  --brand-ground: transparent;
  --brand-black:  var(--night);

  /* the reading measure, and the only width this file invents. .in supplies
     the page's own 1200px column; this is how much of it an essay takes. */
  --measure: 700px;
}

/* ── 2 · the code card ────────────────────────────────────────────────────
   app.css's, ported, because docs.css only ever widened it and gave the copy
   button a home — the card itself lived in the sheet this page no longer
   loads. Same geometry, drawn by its hairlines rather than by a fill: see
   the note at the head of section 7. The bar across the top is the ground
   plus one line under it, which is all a bar has ever been. */
.codecard {
  background: none; border: 1px solid var(--line);
  border-radius: var(--r-card); overflow: hidden;
}
.codecard + .codecard { margin-top: 12px; }
.codebar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 7px 8px 7px 14px;
  background: none; border-bottom: 1px solid var(--line);
  font-size: 12px; color: var(--ink2);
}
.codebar b { color: var(--ink); font-weight: 600; }
.codebar .copy { background: none; }
.codecard pre {
  margin: 0; padding: 16px; overflow-x: auto; background: none;
  font-family: "JetBrains Mono", monospace; font-size: 12.5px; line-height: 1.75;
  color: var(--ink2);
}

/* The four syntax hues, lifted until they clear the ground they are now on.
   A forest green and an umber were drawn for a white card and go to mud on
   this one. */
.k  { color: var(--ink); }
.s  { color: #7FD6B2; }
.n  { color: #E5B567; }
.at { color: #C4B2F5; }
.cm { color: var(--ink3); }
.kw { color: var(--ink); font-weight: 500; }

/* ── 3 · what the bridge cannot reach ─────────────────────────────────────
   The handful of places where docs.css wrote a literal rather than a token.
   Every one of them is a white that was the sheet's own colour, and on night
   it is a hole punched through the page. */
/* A table head is a row set apart, and on this ground the rule under it does
   that without a band of colour behind it. The diagram boxes are the covers'
   story one floor down: the stroke draws the box, so the fill is the page.
   `.bar-t` keeps its fill — it is a TRACK, "what could have been" against
   what the money bought, and a track you cannot see is not a track. The
   callout tints stay too: those carry the callout's meaning, not its edge. */
.prose thead th { background: none; }

/* THE CALLOUT BASE IS A WHITE SHEET in docs.css, and only two of its four
   kinds override it — so a note or a tip would arrive on this page as a white
   block. All four are the ground now: the colour of the left edge and of the
   label is what says which kind it is, which is all it ever was. */
.cal { background: none; }
.prose .dgm .b { fill: var(--night); }
.prose .dgm .bar-t { fill: #1E2230; }
.cal-warn   { background: none; }
.cal-danger { background: none; }

/* the diagrams name Inter, which is not the font this page is set in */
.prose .dgm text { font-family: "Schibsted Grotesk", ui-sans-serif, system-ui, sans-serif; }

/* TWO SHEETS, ONE CLASS NAME. landing.css owns a `.dh` of its own — a centred
   section head, flex column, everything middle-aligned — and the markdown
   renderer has been writing `class="dh dh2"` on every <h2> in a post since
   long before that existed. landing.css loads second, so every heading in
   every essay was being centred over a left-aligned column. Put the heading
   back to being a heading; `.bt` is the same story one floor down, where
   landing.css's card title meets a rect inside a drawn diagram. */
.prose .dh {
  display: block; text-align: left;
  width: auto; padding: 0;
}
.prose .dgm .bt { width: auto; padding: 0; }

/* ── 4 · the nav, and the reading indicator on its edge ───────────────────
   The nav is landing.css's, with one addition: it sticks, and the blog draws
   how far through a post the reader is on its own bottom border. A second bar
   under the header would be a second horizontal line, and the page already
   has one exactly there. */
.navwrap { position: sticky; top: 0; border-bottom: 1px solid var(--night3); }
.nav-prog {
  position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: var(--periwinkle);
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform .09s linear;
}
.nav .links a[aria-current="page"] { color: #fff; }

/* ── 5 · the claim band ───────────────────────────────────────────────────
   The same opening /pricing has — eyebrow, headline, the line under it.

   NOT the same rhythm, though, and that is deliberate. /pricing's claim band
   is followed by four tall tier cards, so 104px under it and 112px over them
   is two bands breathing. Here the band is three short lines and what follows
   is a row of pills, and the identical padding leaves a quarter of a screen of
   empty ground with a dotted rule stranded in the middle of it. The rule is
   meant to be a seam between two things, not a thing on its own. */
.blog-claim { padding-block: 56px 30px; }
.blog-list > .in { padding-block: 30px 88px; }
/* The measure goes on the type, NOT on the column. .in is what puts the page
   on its grid — 1200px, centred, with the nav's mark standing at its left
   edge — and narrowing it here re-centres the whole band, so the headline
   drifts into the middle of the window with nothing under it. Cap the line
   lengths instead and the column stays where every other band's is. */
/* THE PAGE TITLE IS A LABEL, NOT THE CLAIM. On /pricing the h1 is the whole
   argument of the page and gets all 68px of landing.css's scale. Here the
   argument is the post underneath it, which arrives with a headline of its
   own — and two 68px lines, one above the other with a rule between them,
   read as two pages stacked rather than a blog with something to read on it.
   So the title steps down below the lead post's headline and lets it be the
   biggest thing on the page, which is what it is.

   Every headline on the blog is clamped by vw with a floor small enough for a
   400px screen: these are sentences rather than the three-word claims the
   scale was drawn for, and "Let your users bring their own Claude or ChatGPT
   plan" at a 42px floor runs straight off the side of a phone. */
.blog-head .h1 { max-width: 16ch; font-size: clamp(30px, 3.4vw, 42px); }
/* wide enough that the shortened lede is one line on a desktop rather than a
   line and an orphan; it wraps where it has to below that */
.blog-lede { margin-top: 16px; max-width: 760px; font-size: 16.5px; }
.blog-lede a {
  color: var(--ink); font-weight: 500;
  box-shadow: inset 0 -1px 0 var(--line-2);
}
.blog-lede a:hover { box-shadow: inset 0 -1px 0 var(--ink); }

/* ── 6 · the topic rail ───────────────────────────────────────────────────
   Real links, not a filter widget: every pill is a page that exists, can be
   crawled and can be linked to, which is the entire reason to keep tag pages
   rather than filtering a list in the browser.

   Drawn as the rail the front page uses for a choice between states — a soft
   trough with the chosen one lifted out of it — rather than as a row of
   outlined chips, which is a control this site does not otherwise have. */
.filters {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 14px;
}
.f-row {
  display: flex; flex-wrap: wrap; gap: 4px;
  padding: 4px; background: var(--soft); border-radius: 999px;
  width: max-content; max-width: 100%;
}
.f {
  display: inline-flex; align-items: center;
  padding: 10px 17px 11px; border-radius: 999px;
  font-size: 14.5px; font-weight: 600; letter-spacing: -.015em;
  color: var(--ink2); white-space: nowrap;
  transition: color .2s ease, background .25s ease;
}
.f:hover { color: var(--ink); }
.f.on { background: var(--ink); color: var(--night); }
.f:focus-visible { outline: 2px solid var(--periwinkle); outline-offset: 3px; }

/* Outside the trough: it is the one control in this row that does not filter
   anything, so it should not sit among the things that do. */
.f-rss {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: "JetBrains Mono", monospace; font-size: 12px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink3);
}
.f-rss:hover { color: var(--ink); }

/* ── 7 · the covers ───────────────────────────────────────────────────────
   Each cover is an SVG written by scripts/lib/covers.mjs and coloured here,
   so one palette change moves every drawing on the blog at once. They were
   drawn in the old brand's browns; this is the same four motifs on the night
   scale, with periwinkle doing what periwinkle does everywhere else — marking
   the one element the drawing is about. */
/* ── ONE GROUND, AND HAIRLINES ───────────────────────────────────────
   The rule the whole sheet follows, written down here because the covers
   are where it was first obvious.

   landing.css lifts a card onto --night2, a shade above the --night ground,
   and on a tier card that is right: it is a small object and the half-step
   says so. A cover is 640x400 of that half-step, and at that size it stops
   reading as a lift and starts reading as a pale rectangle floating on the
   page. The same was true of every other box the blog had — the code cards,
   the pager, the related cards, the callouts, the table heads.

   So: NOTHING ON THIS PAGE HAS A GROUND OF ITS OWN. Every container is the
   page's ground with a hairline around it, and the hairline is what says
   where it ends. Two things are exempt, and only two:

     · a MARK, which has to be told apart from the sentence or the drawing
       it sits in — inline `code`, the dim boxes in a cover, the track a bar
       is drawn against. Those get a wash or a fill, because without one
       they are not there at all.
     · a CONTROL, where the fill is the affordance — the trough under the
       topic rail, and the one pill lifted out of it.

   A hover is an edge brightening and a 2.5% wash, never a step to the next
   ground: that is how a hover stays a hover instead of becoming a second
   surface. */
/* A RECESS, NOT A LIFT. The cover needs an edge — without one the drawing
   has no extent and two of them side by side in the grid run into each
   other — but every version of that edge that went UP from the page read as
   a pale panel floating on it, which is the thing this sheet spent three
   rounds getting rid of.

   So it goes down instead. The page is #07080C and the cover is black: a
   real, visible step, in the one direction that cannot be mistaken for a
   card hovering above the page. The hairline around it is the page's own
   line colour, and together they read as a plate set INTO the ground. */
.shot {
  overflow: hidden;
  border: 1px solid var(--line); border-radius: 18px;
  background: #000;
  transition: border-color .18s;
}
.shot svg { display: block; width: 100%; height: auto; }

.cv-bg { fill: #000; }
.cv-a  { fill: #D5D9E6; }
/* The dim half of every motif, and it has to stay legible: these are the
   shapes the lit one is being contrasted AGAINST, so a dim that reads as the
   ground turns a drawing of two things into a drawing of one. They were a
   step above --night2 when the card had a ground of its own; with the card
   flat on the page they take that step back from the page instead. */
.cv-d  { fill: #2C3246; }
.cv-o  { fill: none; stroke: var(--periwinkle); stroke-width: 1.5; }
.cv-l  { fill: none; stroke: #434A60; stroke-width: 2; }
.cv-l.cv-lit { stroke: var(--periwinkle); }
.cv-c, .cv-lt {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px; letter-spacing: .14em;
}
.cv-c  { fill: #6E748A; }
.cv-lb { fill: none; stroke: var(--periwinkle); stroke-width: 1; }
.cv-lt { fill: var(--periwinkle); letter-spacing: .1em; }

/* ── 8 · the lead post ────────────────────────────────────────────────────
   The whole width, split in two. A front page that is a uniform grid reads as
   a directory; something has to be the thing you are meant to read first. */
.feat {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1.06fr) minmax(0, 1fr);
  gap: 52px; align-items: center;
  margin-top: 48px;
}
/* The hit area is a stretched overlay rather than a wrapper: an <a> containing
   a heading and a paragraph is one enormous link to a screen reader, and the
   heading stops being a heading. */
.hit { position: absolute; inset: 0; z-index: 1; border-radius: 18px; }
.hit:focus-visible { outline: 2px solid var(--periwinkle); outline-offset: 6px; }

.feat-t {
  margin-top: 16px; max-width: 18ch;
  font-size: clamp(27px, 3.6vw, 46px); line-height: 1.05; letter-spacing: -.04em;
  font-weight: 600; color: var(--ink);
}
.feat-d {
  margin-top: 16px; max-width: 48ch;
  font-size: 16.5px; line-height: 1.6; color: var(--ink2);
}
.more {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 26px;
  font-size: 14.5px; font-weight: 600; color: var(--ink);
}
.more i { font-style: normal; transition: transform .16s ease; }
.feat:hover .more i { transform: translateX(4px); }
.feat:hover .feat-t, .card:hover .card-t { color: var(--periwinkle); }
.feat:hover .shot, .card:hover .shot { border-color: var(--line-2); }

/* ── 9 · the label above a title ──────────────────────────────────────────
   The same mono label the rest of the site sets over a heading — .eb, from
   landing.css — written as a line of parts rather than one string, because
   the date in it is a <time> and the topic is a link. */
.meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
  font-family: "JetBrains Mono", monospace; font-size: 11.5px; font-weight: 400;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink3);
}
.meta-tag { color: var(--periwinkle); }
a.meta-tag { position: relative; z-index: 2; }
a.meta-tag:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ── 10 · the seam ─────────────────────────────────────────────────────────
   A rule with its ends marked. The crosshairs say where the measure starts
   and stops, which is the only thing on the page that draws the grid it is
   on. */
.rule { position: relative; height: 1px; background: var(--line); margin-top: 72px; }
.rule::before, .rule::after {
  content: "+";
  position: absolute; top: 50%; transform: translateY(-50%);
  font-size: 11px; line-height: 1; color: var(--line-2);
}
.rule::before { left: -4px; }
.rule::after { right: -4px; }

.bsec {
  margin-top: 38px;
  font-family: "JetBrains Mono", monospace; font-size: 12px; font-weight: 400;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink3);
}

/* ── 11 · the grid ──────────────────────────────────────────────────────── */
.grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 46px 28px;
  margin-top: 28px;
}
.card { position: relative; }
.card .meta { margin-top: 18px; }
.card-t {
  margin-top: 11px;
  font-size: 19px; line-height: 1.24; letter-spacing: -.03em; font-weight: 600;
  color: var(--ink);
}
.card-d {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  margin-top: 10px;
  font-size: 14.5px; line-height: 1.55; color: var(--ink2);
}

.empty { margin-top: 48px; color: var(--ink2); }

/* ── 12 · a post ──────────────────────────────────────────────────────────
   No sidebar competing with the argument, a measure set for continuous
   reading rather than for scanning, and the table of contents kept to a thin
   column at the right that disappears the moment there is not room for it. */
/* The reading column at its own width and the contents beside it, rather than
   1fr and a hole: a table of contents that drifts two hundred pixels off the
   text it indexes has stopped being attached to it. */
.post-shell {
  display: grid;
  grid-template-columns: minmax(0, var(--measure)) minmax(0, 1fr);
  gap: 64px;
  align-items: start;
}
/* Left edge, not centred in its column: the mark in the nav, the archive
   headline and the first word of every essay all stand on the same vertical,
   which is the only thing holding a page with an asymmetric sidebar
   together. */
.post { min-width: 0; max-width: var(--measure); padding: 56px 0 24px; }

.post-back {
  display: inline-block; margin-bottom: 30px;
  font-size: 13.5px; color: var(--ink3);
}
.post-back:hover { color: var(--ink); }

/* The shared .meta label, given a little more room: on a post it is the first
   thing under the header rather than one line in a dense grid. */
.post-meta time::before, .post-meta .meta-min::before {
  content: "·"; margin-right: 12px; color: var(--line-2);
}

/* The card's drawing, kept on the post it belongs to. A cover that appears in
   the grid and then nowhere in the piece reads as decoration bolted on for
   the listing — which is exactly what it would be. */
.post-shot { margin-top: 34px; }

.post-h1 {
  max-width: none; margin: 16px 0 0;
  font-size: clamp(27px, 4.4vw, 50px); line-height: 1.03; letter-spacing: -.04em;
  font-weight: 600; color: var(--ink);
}
.post-lede {
  margin-top: 18px;
  font-size: 18.5px; line-height: 1.55; color: var(--ink2);
}

.post-by {
  display: flex; align-items: center; gap: 12px;
  margin: 30px 0 0; padding: 20px 0 0;
  border-top: 1px solid var(--line);
}
.by-av {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 999px;
  background: none; border: 1px solid var(--line-2);
  font-family: "JetBrains Mono", monospace;
  font-size: 11px; font-weight: 500; letter-spacing: .02em; color: var(--periwinkle);
}
.by-n { font-size: 14px; font-weight: 600; color: var(--ink); letter-spacing: -.015em; }
.by-r { display: block; font-size: 12.5px; font-weight: 400; color: var(--ink3); letter-spacing: 0; }

/* An essay is read, not scanned, so the column is narrower and the line
   taller than the docs' — everything else about the prose is inherited,
   which is the whole reason the two builders share one renderer. */
.post .prose {
  max-width: none;
  margin-top: 42px;
  font-size: 17px; line-height: 1.75; color: var(--ink2);
}
.post .prose > * + * { margin-top: 22px; }
.post .prose .dh { color: var(--ink); letter-spacing: -.03em; }
.post .prose .dh2 { margin-top: 56px; font-size: 29px; line-height: 1.12; }
.post .prose .dh3 { margin-top: 40px; font-size: 20px; }
/* The docs float their small diagrams beside a paragraph; in a reading column
   there is nothing to float beside, so it drops into the flow. It keeps its
   own drawn size rather than stretching: a 316px card blown up to the full
   measure is a card with 30px type in it, which reads as an advertisement. */
.post .prose .dgm-side {
  float: none; width: 316px; max-width: 100%;
  margin: 30px auto 36px;
}
/* Only the small card is allowed to shrink to its box. The wide diagrams keep
   docs.css's 620px floor and scroll inside their figure on a narrow screen:
   scaled down to a phone's width their labels land at about five pixels,
   which is a picture of a diagram rather than a diagram. */
.post .prose .dgm-side svg { min-width: 0; }

/* ── 13 · on this page ──────────────────────────────────────────────────── */
.post-toc { position: sticky; top: 96px; max-width: 264px; padding: 66px 0 40px; }

/* THERE IS ONE RAIL, AND IT IS MADE OF THE ITEMS. docs.css draws the rule on
   each .tl rather than around the list, so that the entry you are reading can
   light its own segment of it — which is a better "on" state than a colour
   change, and is the vocabulary the docs already use.

   This sheet used to put a second rule on .dt-inner as well. The result was
   two rails: mine, out at the margin, and docs' one pressed flat against the
   text, because the padding that was meant to hold it off (12px, in docs.css)
   had been overwritten here. Hence a line that touched the type. Do not add a
   border to .dt-inner; the rail already exists one level down. */
.post-toc .dt-inner { border-left: 0; padding-left: 0; }
.post-toc h4 {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px; font-weight: 400; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink3); margin-bottom: 16px; padding-left: 19.5px;
}
/* THE SPACE BETWEEN TWO ENTRIES HAS TO BEAT THE SPACE INSIDE ONE, and by
   enough to see. At 5px of padding and 1.45 leading the two lines of
   "Reaching a machine behind a home router" sat FURTHER apart than that entry
   sat from the next one, so a list of seven headings read as a paragraph of
   fourteen lines.

   The arithmetic, since it is easy to break again: a line inside an entry is
   13 x 1.3 = 16.9px from the next. Between two entries it is 10px of padding
   either side plus the half-leading each line carries, about 24px. Roughly
   half as much again, which is what makes the eye group them. Any change to
   the leading has to move the padding with it. */
.post-toc .tl {
  display: block; padding: 10px 0 10px 18px;
  border-left: 1.5px solid var(--line);
  font-size: 13px; line-height: 1.3; color: var(--ink2);
  transition: color .12s, border-color .12s;
}
.post-toc .tl3 { padding-left: 34px; font-size: 12.5px; }
.post-toc .tl:hover { color: var(--ink); }
/* the entry you are in lights its own length of the rail */
.post-toc .tl.on { color: var(--periwinkle); border-left-color: var(--periwinkle); font-weight: 500; }

/* ── 14 · the way out ─────────────────────────────────────────────────────
   A post with no way out of it is a leaf, and a reader who finishes one essay
   is the likeliest person in the world to read a second. */
.post-pager {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
  margin-top: 60px; padding-top: 30px; border-top: 1px solid var(--line);
}
.post-pager .pg {
  display: block; padding: 16px 18px; background: none;
  border: 1px solid var(--line); border-radius: var(--r-card);
  box-shadow: none;
  transition: border-color .14s, background .14s;
}
/* hover brightens the edge and lays the faintest wash inside it — enough to
   answer the cursor, too little to become a second ground */
.post-pager .pg:hover { border-color: var(--line-2); background: rgba(255, 255, 255, .025); box-shadow: none; }
.post-pager .pg span {
  display: block; font-family: "JetBrains Mono", monospace;
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink3);
}
.post-pager .pg b {
  display: block; margin-top: 7px;
  font-size: 14.5px; font-weight: 600; line-height: 1.35; letter-spacing: -.02em; color: var(--ink);
}
.post-pager .pg-next { text-align: right; }

.post-rel { margin-top: 56px; }
.post-rel h2 {
  font-family: "JetBrains Mono", monospace;
  font-size: 12px; font-weight: 400; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink3);
}
/* auto-fit rather than a fixed three: the earliest posts have only one or two
   siblings, and two cards stranded at the left of a three-column grid look
   like a third failed to load. */
.rel-grid {
  display: grid; gap: 14px; margin-top: 18px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.rel {
  display: block; padding: 18px; background: none;
  border: 1px solid var(--line); border-radius: var(--r-card);
  transition: border-color .14s, background .14s;
}
.rel:hover { border-color: var(--line-2); background: rgba(255, 255, 255, .025); }
.rel-tag {
  display: block; font-family: "JetBrains Mono", monospace;
  font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--periwinkle);
}
.rel-t {
  display: block; margin-top: 10px;
  font-size: 15px; font-weight: 600; line-height: 1.3; letter-spacing: -.025em; color: var(--ink);
}
.rel-d {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  margin-top: 8px; font-size: 13px; line-height: 1.5; color: var(--ink2);
}

/* ── 15 · the close ───────────────────────────────────────────────────────
   landing.css's close band, with two corrections. The headline there is four
   words at 88px; the blog's is eight, and at that size it is a wall. And
   .close-k lives in soba-app.css, which this page does not load — the same
   copy, the same debt, and it goes the moment that sheet is folded in. */
.close .h1 { font-size: clamp(29px, 5.4vw, 64px); }
.close-k {
  margin: 20px auto 0; max-width: 620px;
  font-size: 16.5px; line-height: 1.6; color: var(--dink2);
}

/* ── 16 · narrow ──────────────────────────────────────────────────────────
   The same rule at every width the rest of the site uses: columns become
   rows, and anything that was only ever decoration stops being drawn. */

@media (max-width: 1080px) {
  .post-shell { grid-template-columns: minmax(0, 1fr); gap: 0; }
  /* `width: 100%` and not `margin-inline: auto` alone. An auto inline margin
     on a grid item overrides the stretch and sizes it to MAX-content, so the
     reading column stopped being 360px on a phone and became the 686px its
     longest line wanted — the whole page scrolling sideways under it. With an
     explicit width the auto margins have nothing left to distribute, and they
     still centre it in the room above this breakpoint. */
  .post { width: 100%; margin-inline: auto; }
  /* Sticky is meaningless once it is in the flow, and a table of contents in
     front of the first paragraph is a wall between the reader and the piece. */
  .post-toc { display: none; }
}

@media (max-width: 1000px) {
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 860px) {
  /* Below this the text half of the split is narrower than its own headline. */
  .feat { grid-template-columns: minmax(0, 1fr); gap: 24px; }
  .feat-t, .feat-d { max-width: none; }
}

@media (max-width: 720px) {
  .grid { grid-template-columns: minmax(0, 1fr); gap: 36px; }
  .filters { justify-content: flex-start; }
  .post { padding-top: 36px; }
  .post .prose { font-size: 16.5px; }
  .rel-grid { grid-template-columns: 1fr; }
  .post-pager { grid-template-columns: 1fr; }
  .post-pager .pg-next { text-align: left; }
}
