/* ═══════════════════════════════════════════════════════════════════════════
   /blog/<slug> — the article.  Concept: THE MEASURED PAGE.

   One reading column at 68ch, and a page that is allowed to break out of it —
   into a wide band, into a full-bleed band — exactly as often as the writing
   earns it and never as decoration. Everything that carries a figure is set in
   monospace, because monospace is this site's "you are looking at a
   measurement" signal and an article whose numbers can be checked is the whole
   argument of abbod.de. The rail on the left is contents and progress in one
   object: where you are, and how much is left.

   WHY THE COLUMN IS A GRID AND NOT A max-width. `.pst-body` is a seven-track
   grid whose middle track IS the measure. A figure, a section or a table opts
   into `wide` or `full` by one class, so a break-out never needs a negative
   margin, never overflows the viewport, and never fights the padding. This is
   what makes "large article moments" cheap enough that the generator can offer
   them without an author being able to break the page with one.

   FIVE HEROES, and an author picks one from a list of five. `data-hero` on the
   article root switches the masthead between:

     image           the cover carries it — type block, then a wide plate
     typographic     no image at all. The title is the artwork, Orbitron on a
                     mint slab, at the largest size this site sets
     data            the title beside three measured figures in monospace, on
                     the tone step. For anything that reports a number
     minimal-essay   a ruled label, a sentence-case title at body-adjacent
                     scale, and whitespace. The restrained one
     visual-story    the cover first and tall, the title set on paper beneath
                     it and pulled up into its lower edge

   Each is a self-contained grid over the SAME markup. A missing cover, a
   missing dek, a missing figure strip renders as nothing rather than as a hole
   — an author cannot produce a broken masthead, only a plainer one.

   MARKUP CONTRACT. Line 1 of content.html is fixed by
   BLOG-PLATFORM-ARCHITECTURE §4:

   <main data-taxi="" class="main-w"><div data-page="post" data-taxi-view=""
     class="taxi-w post"><script>document.documentElement.classList.add('pstjs')
     </script><link rel="stylesheet" href="/assets/css/post.css"/>

     a.pst-skip                                    → #pst-main, first focusable
     article.pst[data-hero][data-nav-theme-target="dark"]
       header.pst-mast
         p.pst-crumb · p.pst-kicker · h1.pst-title · p.pst-dek
         dl.pst-byline > div > (dt + dd)
         ul.pst-figures > li > (span.pst-figure-v + span.pst-figure-k)   data
         figure.pst-cover > picture + figcaption.pst-cover-cap
       div.pst-layout[data-pst-prog]
         div.pst-bar[aria-hidden="true"]           reading progress, mobile
         nav.pst-rail[aria-label="On this page"]
           div.pst-rail-line[aria-hidden="true"]
           ol.pst-toc[data-pst-toc]                built by post/init.js
             li > a.pst-toc-link[data-pst-toc-to][data-lvl="2|3"]
         div#pst-main.pst-body[tabindex="-1"]      ← render-html.js output
       footer.pst-end
     script src="/assets/js/post/init.js" defer

   EVERYTHING INSIDE .pst-body IS GENERATED by packages/content/src/render-html.js
   and every class it can emit is styled below, in that file's own order. A
   block that renders as unstyled prose is the same defect as a block that
   renders as nothing, so this sheet tracks that file and the two are read
   together.

   MOTION CONTRACT. Hidden start states live under `.pstjs`, set pre-paint by
   the inline one-liner on line 1. No class → no JS → the article renders
   settled, complete and fully visible: every figure opaque, every panel of a
   tab group shown with its own heading, the comparison side by side, the
   contents rail absent rather than empty.

   1rem is not 16px — html{font-size} is a fluid function of viewport width, so
   every minimum here is written max(<rem>, <px>). No shadows: depth is a tone
   step plus a 1px rim. Namespace `pst-*`, scoped under .taxi-w.post; nothing
   here may style the shared nav or footer.
   ═══════════════════════════════════════════════════════════════════════════ */

.taxi-w.post {
  --pst-bg: #fafcfb;            /* the paper */
  --pst-surface: #eef4f1;       /* THE tone step */
  --pst-surface-2: #e3ece7;     /* one step further — hover, and nested panels */
  --pst-ink: #040504;           /* 17.6:1 on the paper */
  --pst-ink-soft: #10120f;
  --pst-dim: #535450;           /* 7.41:1 on the paper */
  --pst-quiet: #6f716c;         /* 5.0:1 — the quietest text allowed here */
  --pst-line: rgba(4, 5, 4, .07);
  --pst-rim: #cbd3ce;
  --pst-rim-strong: #9fa9a4;
  /* ONE accent. #21f1a8 is ~1.5:1 on paper, so it fills shapes and never
     carries a word: rules, the hero slab, a button ground, a chart key.
     --pst-mint-ink is the same hue taken down until it clears 4.5:1 on the
     paper and is the only mint allowed to be text. Page-local derivation; the
     global token is untouched. */
  --pst-mint: var(--color--lime, #21f1a8);
  --pst-mint-ink: #0c7a55;
  --pst-on-mint: #10120f;
  --pst-mono: ui-monospace, SFMono-Regular, Menlo, 'Courier New', monospace;
  --pst-ease: cubic-bezier(.215, .61, .355, 1);   /* the signature curve */
  --pst-rhythm: 6.5rem;
  --pst-measure: 68ch;          /* inside the 62–72ch reading band */
  --pst-gap: 1.5rem;            /* the prose rhythm, one line of body text */

  /* what the shared footer band paints itself with — see css-main.html */
  --page-bg: var(--pst-bg);

  background-color: var(--pst-bg);
  color: var(--pst-ink);
  display: block;
  position: relative;
}

/* ---------------------------------------------------------------- resets

   :where() so the margin reset weighs nothing and every component below can
   set its own margin with one class. `.taxi-w.post p { margin: 0 }` would
   score 0,1,1 and silently zero them all — the bug documented at
   whatido.css:.wid-index, where a heading's gap measured one pixel. */
.taxi-w.post :is(ul, ol) { padding: 0; list-style: none; }
.taxi-w.post li { list-style: none; }
.taxi-w.post li::marker { content: none; }
:where(.taxi-w.post) :where(p, h1, h2, h3, h4, ul, ol, dl, dd, figure, blockquote, pre, table) { margin: 0; }
.taxi-w.post img, .taxi-w.post picture { display: block; max-width: 100%; }
.taxi-w.post picture img { width: 100%; height: auto; }

/* One focus treatment for everything focusable, and never `outline: none` on
   `:focus` — that out-specifies the global :focus-visible ring and deletes it,
   already fixed twice in this repository. Ink, because mint on paper is
   ~1.5:1 and a focus indicator has to be visible. */
.taxi-w.post :where(a, button, summary, input, video, audio, [tabindex]):focus-visible {
  outline: 2px solid var(--pst-ink);
  outline-offset: 3px;
  border-radius: .25rem;
}
/* :focus-within, not :focus-visible, and only for media. Tabbing into
   <video controls> puts focus on a button inside the element's SHADOW tree, so
   the host is document.activeElement while matching neither :focus nor
   :focus-visible — measured, not assumed, and the reason a 587x367 player read
   as a stop with no indicator. :focus-within crosses that boundary. Chromium
   also paints its own ring on the inner control; this draws one round the whole
   player so the indicator does not depend on a user-agent detail. It ADDS a
   ring and never removes one. */
.taxi-w.post :is(video, audio):focus-within { outline: 2px solid var(--pst-ink); outline-offset: 3px; }

/* ------------------------------------------------------------------ shell */

.pst-skip {
  position: fixed;
  top: 1rem;
  left: 50%;
  z-index: 30;
  transform: translate(-50%, -150%);
  opacity: 0;
  pointer-events: none;
  background-color: var(--pst-ink);
  color: #edf5f3;
  font-family: var(--pst-mono);
  font-size: max(.6875rem, 11px);
  letter-spacing: .06em;
  text-transform: uppercase;
  text-decoration: none;
  padding: .625rem 1rem;
  border-radius: .375rem;
  transition: transform .25s var(--pst-ease), opacity .25s linear;
}
/* Page-scoped so it beats the catch-all ring (0,3,1 vs 0,2,0): an ink ring on
   an ink pill is a focus indicator you cannot see. */
.taxi-w.post .pst-skip:focus-visible {
  transform: translate(-50%, 0);
  opacity: 1;
  pointer-events: auto;
  outline: 2px solid var(--pst-mint);
  outline-offset: 2px;
}

.pst-wrap {
  width: 100%;
  max-width: var(--fluid-container);
  margin-inline: auto;
  padding-inline: var(--padding--container);
}

/* ═══════════════════════════════════════════════════ THE MASTHEAD (5 heroes)

   Shared atoms first; the five variants below only re-arrange and re-scale
   them. Nothing in a variant block introduces a new element, so a hero cannot
   be half-built. */

.pst-mast {
  position: relative;
  padding-top: calc(var(--on-t-nav-spacer, 5.375rem) + 2.5rem);
  padding-bottom: 3.5rem;
  width: 100%;
  max-width: var(--fluid-container);
  margin-inline: auto;
  padding-inline: var(--padding--container);
}

.pst-crumb {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .625rem;
  font-family: var(--pst-mono);
  font-size: max(.625rem, 10px);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--pst-dim);
}
/* A crumb link is a flex item, so it is NOT an inline target and the 24 px
   floor applies to it in full. Measured at 320px it was 48x11 before this. */
.pst-crumb a {
  display: inline-flex;
  align-items: center;
  min-height: max(1.5rem, 24px);
  color: var(--pst-dim);
  text-decoration: none;
  transition: color .3s linear;
}
.pst-crumb a:hover, .pst-crumb a:focus-visible { color: var(--pst-ink); }

.pst-kicker {
  margin-top: 2rem;
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.75rem, 12px);
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--pst-mint-ink);
}

/* The default title. Quicksand, sentence case, because an article title is a
   sentence and an uppercase sentence reads as a sign rather than as a claim.
   `typographic` and `visual-story` promote it to Orbitron; the other three do
   not, which is what keeps Orbitron sparing across a growing archive. */
.pst-title {
  margin-top: 1rem;
  max-width: 22ch;
  font-family: Quicksand, Arial, sans-serif;
  font-size: clamp(2rem, 4.4vw, 3.5rem);
  line-height: 1.04;
  letter-spacing: -.03em;
  font-weight: 600;
  color: var(--pst-ink);
}

.pst-dek {
  margin-top: 1.5rem;
  max-width: 46ch;
  font-family: Quicksand, Arial, sans-serif;
  font-size: 1.0625rem;
  line-height: 1.55;
  color: var(--pst-dim);
}

/* Refined metadata: a definition list, ruled, in monospace, with the labels
   set smaller than the values rather than the other way round. Dates and
   reading times are figures and they take the measured face. */
.pst-byline {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 2.5rem;
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--pst-line);
}
.pst-byline > div { display: grid; gap: .35rem; }
.pst-byline dt {
  font-family: var(--pst-mono);
  font-size: max(.5625rem, 9px);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pst-quiet);
}
.pst-byline dd {
  font-family: var(--pst-mono);
  font-size: max(.8125rem, 13px);
  letter-spacing: .01em;
  color: var(--pst-ink);
}
.pst-byline a { color: inherit; text-underline-offset: .25em; }

/* The measured-figures strip. Never animated — a figure that counts up is a
   figure the reader briefly cannot read, and this site asks to be checked. */
.pst-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
  gap: 1.5rem 2rem;
  margin-top: 2.5rem;
}
.pst-figures li { display: grid; gap: .4rem; }
.pst-figure-v {
  font-family: var(--pst-mono);
  font-size: clamp(1.5rem, 2.6vw, 2.25rem);
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--pst-ink);
}
.pst-figure-k {
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.75rem, 12px);
  line-height: 1.35;
  color: var(--pst-dim);
}

.pst-cover {
  margin-top: 3rem;
  position: relative;
  border-radius: 1rem;
  overflow: hidden;
  border: 1px solid var(--pst-line);
  background-color: var(--pst-surface);
}
.pst-cover img { width: 100%; height: auto; }
.pst-cover-cap {
  padding: .875rem 1.125rem;
  background-color: var(--pst-surface);
  border-top: 1px solid var(--pst-line);
  font-family: var(--pst-mono);
  font-size: max(.6875rem, 11px);
  line-height: 1.5;
  color: var(--pst-dim);
}

/* ---- hero: image ------------------------------------------------------ */

/* The default, and the reason it is the default: it works with any cover of
   any shape, and the type block above it is complete on its own if the cover
   is missing. */
.pst[data-hero="image"] .pst-figures { display: none; }
/* The cover keeps its own proportion — that is the variant's whole point — but
   it is capped against the viewport and cropped rather than allowed to set the
   page's height. A 16:10 asset at full container width measured 875px tall,
   which put the first sentence of the article a whole screen below the fold. */
.pst[data-hero="image"] .pst-cover { aspect-ratio: auto; }
.pst[data-hero="image"] .pst-cover img { max-height: 68vh; object-fit: cover; }

/* ---- hero: typographic ------------------------------------------------ */

/* No image, by definition — the title IS the artwork. Orbitron uppercase at
   the largest size this site sets, on a mint slab, which is the one legitimate
   use of #21f1a8 at scale: a filled shape with ink on it. An alternate is
   `display: none`, never `opacity: 0` — a parked element reads as a stuck
   reveal to every harness that counts them, and rightly so. */
.pst[data-hero="typographic"] .pst-cover,
.pst[data-hero="typographic"] .pst-figures { display: none; }
.pst[data-hero="typographic"] .pst-mast { padding-bottom: 5rem; }
.pst[data-hero="typographic"] .pst-title {
  margin-top: 2rem;
  max-width: 16ch;
  font-family: Orbitron, Arial, sans-serif;
  font-size: clamp(2.5rem, 7.4vw, 6rem);
  line-height: .9;
  letter-spacing: -.06em;
  text-transform: uppercase;
  font-weight: 700;
  font-variation-settings: normal;
}
.pst[data-hero="typographic"] .pst-title em {
  font-style: normal;
  background-image: linear-gradient(var(--pst-mint), var(--pst-mint));
  background-size: 100% .16em;
  background-position: 0 90%;
  background-repeat: no-repeat;
}
.pst[data-hero="typographic"] .pst-dek { margin-top: 2.25rem; max-width: 40ch; font-size: 1.1875rem; }

/* ---- hero: data ------------------------------------------------------- */

/* For an article that reports a number. No new element: the figures strip
   becomes a panel on the tone step with a rim — depth without a shadow — and on
   a wide screen the masthead's own grid puts it beside the title. Three figures
   is the design; two or four still compose, because the strip is auto-fit. */
.pst[data-hero="data"] .pst-cover { display: none; }
.pst[data-hero="data"] .pst-figures {
  padding: 2rem 1.75rem;
  background-color: var(--pst-surface);
  border: 1px solid var(--pst-rim);
  border-radius: 1rem;
}
.pst[data-hero="data"] .pst-title { font-size: clamp(1.875rem, 3.6vw, 2.875rem); }
.pst[data-hero="data"] .pst-figure-k { color: var(--pst-ink-soft); }

/* ---- hero: minimal-essay ---------------------------------------------- */

/* The restrained one: a ruled label, a title only a step above the prose, and
   whitespace doing the rest. This is the variant for a short argument, and it
   must never grow an image — a photograph would make it a different article. */
.pst[data-hero="minimal-essay"] .pst-cover,
.pst[data-hero="minimal-essay"] .pst-figures { display: none; }
.pst[data-hero="minimal-essay"] .pst-mast {
  padding-top: calc(var(--on-t-nav-spacer, 5.375rem) + 4.5rem);
  padding-bottom: 4.5rem;
}
.pst[data-hero="minimal-essay"] .pst-kicker {
  display: inline-block;
  padding-bottom: .5rem;
  border-bottom: 2px solid var(--pst-mint);
}
.pst[data-hero="minimal-essay"] .pst-title {
  max-width: 26ch;
  font-size: clamp(1.75rem, 2.9vw, 2.375rem);
  font-weight: 500;
  letter-spacing: -.02em;
}
.pst[data-hero="minimal-essay"] .pst-dek { max-width: 52ch; font-size: 1rem; }

/* ---- hero: visual-story ----------------------------------------------- */

/* The cover comes first and tall, and the type is set on paper pulled up into
   its lower edge — the one masthead on the site where a plate and a headline
   overlap. `order` moves the cover only; the DOM order (title first) is what
   a screen reader and the document outline read, and that stays correct. */
.pst[data-hero="visual-story"] .pst-figures { display: none; }
.pst[data-hero="visual-story"] .pst-mast {
  display: flex;
  flex-direction: column;
  padding-top: calc(var(--on-t-nav-spacer, 5.375rem) + 1.25rem);
  padding-bottom: 3rem;
}
.pst[data-hero="visual-story"] .pst-crumb { order: 1; }
/* 21:9 and capped against the viewport. At 16:9 and full container width the
   plate measured 790px tall on a 900px screen — the masthead WAS the page, and
   the title it is supposed to introduce was below the fold. */
.pst[data-hero="visual-story"] .pst-cover {
  order: 2;
  margin-top: 1.75rem;
  aspect-ratio: 21 / 9;
  max-height: 58vh;
}
.pst[data-hero="visual-story"] .pst-cover img { height: 100%; object-fit: cover; }
/* Bottom RIGHT, not full width: the title cuts into the lower-left corner of
   the same plate at ≥992px and two objects cannot own one edge. */
.pst[data-hero="visual-story"] .pst-cover-cap {
  position: absolute;
  inset: auto 0 0 auto;
  max-width: min(100%, 34ch);
  border-top: 0;
  border-top-left-radius: .5rem;
  background-color: rgba(250, 252, 251, .94);
}
.pst[data-hero="visual-story"] .pst-kicker { order: 3; }
.pst[data-hero="visual-story"] .pst-title {
  order: 4;
  font-family: Orbitron, Arial, sans-serif;
  font-size: clamp(1.75rem, 4vw, 3.25rem);
  line-height: 1;
  letter-spacing: -.05em;
  text-transform: uppercase;
  font-weight: 700;
  font-variation-settings: normal;
  max-width: 18ch;
}
.pst[data-hero="visual-story"] .pst-dek { order: 5; }
.pst[data-hero="visual-story"] .pst-byline { order: 6; }

/* ═══════════════════════════════════════════════════════ RAIL AND PROGRESS

   Contents and progress in one object. It is one <nav> whose links track the
   section on screen, plus a hairline whose fill is scaleY(--pst-p). Both read
   the same custom property, which post/init.js writes on `.pst-layout` — one
   variable, one rAF loop, and the rail and the mobile bar inherit it. */

.pst-layout { padding-bottom: var(--pst-rhythm); }

/* Mobile progress. A 2px ink line at the very top of the viewport, and it is
   the ONLY place on this page a fixed element is allowed. It is aria-hidden:
   it duplicates nothing and announces nothing. */
.pst-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 6;
  background-color: var(--pst-ink);
  transform: scaleX(var(--pst-p, 0));
  transform-origin: 0 50%;
  pointer-events: none;
  display: none;
}
.pstjs .pst-bar { display: block; }

/* Up to 991px the contents are a horizontal strip under the masthead: a
   sticky sidebar on a phone is a sidebar covering the article. The rail owns
   the page padding here, because .pst-layout only takes it at ≥992px where it
   becomes the two-column wrapper. */
.pst-rail { margin: 0; padding-inline: var(--padding--container); }
/* No contents at all — fewer than three headings, so post/init.js built
   nothing and said so. The rail is removed rather than left as a bare rule,
   and the article takes the whole column. */
.pst-layout.is-norail .pst-rail { display: none; }
.pst-toc {
  display: flex;
  gap: 1.25rem;
  padding: 1rem 0;
  overflow-x: auto;
  border-block: 1px solid var(--pst-line);
}
/* Empty until post/init.js fills it, and an empty rail must not draw two
   rules across the page. :empty is exact here — the script writes <li>s or
   nothing at all. */
.pst-toc:empty { display: none; }
.pst-toc-link {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  min-height: max(1.75rem, 24px);
  line-height: 1.45;
  white-space: nowrap;
  font-family: var(--pst-mono);
  font-size: max(.625rem, 10px);
  letter-spacing: .07em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--pst-dim);
  transition: color .3s linear;
}
.pst-toc-link[data-lvl="3"] { padding-left: .875rem; text-transform: none; letter-spacing: .02em; }
/* The current section is marked by a colour promotion and a rule, never by
   fading its neighbours: `opacity: .3` on real text drops under 3:1 on every
   ground here. Every entry stays at a passing ratio whether it is current or
   not. */
.pst-toc-link:hover,
.pst-toc-link:focus-visible,
.pst-toc-link.is-here { color: var(--pst-ink); }
/* The mark is a mint bar and a colour promotion, and it is aligned to the
   FIRST line of a wrapped entry — centred, it drifts to the middle of a
   three-line heading and stops reading as a pointer at all. */
.pst-toc-link.is-here::before {
  content: "";
  width: .75rem;
  height: 2px;
  flex: none;
  margin-top: .62em;
  align-self: flex-start;
  background-color: var(--pst-mint);
}
.pst-rail-line { display: none; }

/* ══════════════════════════════════════════════════════ THE READING COLUMN

   Seven tracks. The middle one is the measure; `wide` and `full` are the two
   sanctioned break-outs. Everything a block needs to become a large moment is
   one class, and no rule below ever uses a negative margin. */

.pst-body {
  display: grid;
  grid-template-columns:
    [full-start] minmax(var(--padding--container), 1fr)
    [wide-start] minmax(0, 7rem)
    [content-start] min(100% - 2 * var(--padding--container), var(--pst-measure)) [content-end]
    minmax(0, 7rem) [wide-end]
    minmax(var(--padding--container), 1fr) [full-end];
  padding-top: var(--pst-gap);
}
/* 0,0,0 on purpose: this is the default placement and the default rhythm, and
   any single-class rule below must be able to beat it without :is() gymnastics
   or an. */
:where(.pst-body) > * { grid-column: content; margin-top: var(--pst-gap); }
:where(.pst-body) > :first-child { margin-top: 0; }
.pst-body > .pst-fig--wide,
.pst-body > .pst-section--wide,
.pst-body > .pst-cols,
.pst-body > .pst-tabs,
.pst-body > .pst-stats,
.pst-body > .pst-tl--horizontal { grid-column: wide; }
.pst-body > .pst-fig--full,
.pst-body > .pst-section--full { grid-column: full; }
/* The two defaults, written out rather than left implicit: `content` is what
   the grid already does, and a rule that says so is what stops the next person
   adding a negative margin to get it. */
.pst-body > .pst-fig--content,
.pst-body > .pst-section--content { grid-column: content; }
.pst-body > .pst-fig--inset { grid-column: content; }

/* The skip link's destination. tabindex="-1" is not a tab stop — it lets the
   skip link move focus rather than merely scroll. */
.pst-body:focus-visible { outline: 2px solid var(--pst-ink); outline-offset: .75rem; }

/* Nested containers get the same rhythm so a callout, a column or an accordion
   body reads with the same spacing as the article around it.

   The gap rule is :where() (0,0,0) so any component can override it. The
   first-child reset is :is() (0,2,0) BECAUSE IT MUST BEAT `h3.pst-h` at 0,1,1 —
   written with :where() it lost, and a raised band opened with 2.75rem of dead
   space above its own heading. Measured, in a screenshot, before this line. */
:where(.pst-callout, .pst-col, .pst-acc-body, .pst-faq-a, .pst-step-body, .pst-tab, .pst-note, .pst-raw, .pst-section) > * { margin-top: 1.25rem; }
:is(.pst-body, .pst-callout, .pst-col, .pst-acc-body, .pst-faq-a, .pst-step-body, .pst-tab, .pst-note, .pst-raw, .pst-section) > :first-child { margin-top: 0; }
/* A label and the sentence it introduces are one object, not two blocks. */
:is(.pst-callout-title, .pst-note-label, .pst-audio-title) + * { margin-top: .5rem; }

/* ------------------------------------------------------------------ prose */

/* :where() so this weighs 0,0,1 and NOT 0,1,1. Written the obvious way,
   `.pst-body p { font-size }` out-specifies every single-class rule below it —
   .pst-lead, .pst-stat-value, .pst-cta-h, .pst-tl-when and a dozen more are all
   <p> elements inside .pst-body — and the sheet ends up carrying fifty
   `!important`s to undo one selector. Same lesson as the margin reset in
   PAGE-KIT §2.3, arrived at from the type side. Container overrides below
   (:where(.pst-quote) p and friends) are the same weight and win on order. */
:where(.pst-body) p {
  font-family: Quicksand, Arial, sans-serif;
  font-size: 1.0625rem;
  line-height: 1.72;
  color: var(--pst-ink-soft);
  hanging-punctuation: first;
}

/* The lead paragraph. One per article, set a size up and in the dim ink, so it
   reads as the bridge between the dek and the body rather than as a second
   dek. */
.pst-lead {
  font-size: 1.25rem;
  line-height: 1.55;
  color: var(--pst-dim);
  margin-top: 2rem;
}

/* Headings. `.pst-h` is emitted on h2/h3/h4 alike, so the element carries the
   size — element+class is 0,1,1 and beats the bare class cleanly. */
:where(.pst-body) .pst-h { font-family: Quicksand, Arial, sans-serif; color: var(--pst-ink); }
h2.pst-h {
  margin-top: 3.5rem;
  max-width: 26ch;
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  line-height: 1.14;
  letter-spacing: -.025em;
  font-weight: 600;
}
h3.pst-h {
  margin-top: 2.75rem;
  max-width: 34ch;
  font-size: 1.25rem;
  line-height: 1.25;
  letter-spacing: -.015em;
  font-weight: 600;
}
h4.pst-h {
  margin-top: 2.25rem;
  font-size: 1.0625rem;
  line-height: 1.35;
  font-weight: 600;
}
/* Anchor targets: a heading jumped to from the contents must not land under
   the nav. */
:where(.pst-body) [id] { scroll-margin-top: calc(var(--on-t-nav-spacer, 5.375rem) + 1.5rem); }

/* Links in prose read as links without a colour that fails on paper: ink, with
   a rule under it that thickens on hover. Mint would be ~1.5:1. */
:where(.pst-body) :where(p, li, dd) a,
:where(.pst-raw) a {
  color: var(--pst-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--pst-rim-strong);
  text-underline-offset: .22em;
  transition: text-decoration-color .3s var(--pst-ease);
}
:where(.pst-body) :where(p, li, dd) a:hover,
:where(.pst-body) :where(p, li, dd) a:focus-visible { text-decoration-color: var(--pst-ink); }

.pst-body strong { font-weight: 600; color: var(--pst-ink); }
.pst-body em { font-style: italic; }
.pst-body s { text-decoration-thickness: 1px; color: var(--pst-dim); }
.pst-body sub, .pst-body sup { font-size: .72em; line-height: 0; }
/* Mint as a highlight is a filled shape with ink on it — the one place it is
   allowed to sit behind words. */
.pst-body mark {
  background-color: var(--pst-mint);
  color: var(--pst-on-mint);
  padding: .08em .2em;
  border-radius: .15em;
}

.pst-code-inline {
  font-family: var(--pst-mono);
  font-size: .875em;
  padding: .12em .35em;
  border-radius: .3em;
  background-color: var(--pst-surface);
  border: 1px solid var(--pst-line);
  color: var(--pst-ink);
  word-break: break-word;
}

/* Citations and footnote references. Small, but never small mint: ink on
   paper, with a target that still clears 24 px because the <sup> is given a
   real inline-block box. */
.pst-cite a, .pst-fn a {
  display: inline-block;
  min-width: max(1.25rem, 20px);
  padding: 0 .15rem;
  font-family: var(--pst-mono);
  font-size: max(.625rem, 10px);
  text-align: center;
  text-decoration: none;
  color: var(--pst-mint-ink);
}
.pst-cite a:hover, .pst-fn a:hover { text-decoration: underline; }

/* ------------------------------------------------------------------ lists */

.pst-ul, .pst-ol, .pst-tasks {
  display: grid;
  gap: .625rem;
  font-family: Quicksand, Arial, sans-serif;
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--pst-ink-soft);
}
.pst-ul > li { position: relative; padding-left: 1.75rem; }
/* A grid row with align-items:baseline, so the marker's baseline sits on the
   text's baseline exactly — a positioned marker at a smaller font-size needs a
   guessed line-height to line up, and the guess was visibly wrong. */
.pst-ol > li { display: grid; grid-template-columns: 1.75rem minmax(0, 1fr); align-items: baseline; }
/* The marker is drawn rather than inherited, because the reset above removes
   ::marker outright — that reset exists so the shared sheet's element rules
   cannot reintroduce a bullet inside a component that does not want one. */
.pst-ul > li::before {
  content: "";
  position: absolute;
  left: .35rem;
  top: .72em;
  width: .375rem;
  height: .375rem;
  border-radius: 50%;
  background-color: var(--pst-mint-ink);
}
.pst-ol { counter-reset: pst-ol; }
.pst-ol > li { counter-increment: pst-ol; }
.pst-ol > li::before {
  content: counter(pst-ol) ".";
  font-family: var(--pst-mono);
  font-size: .8125em;
  color: var(--pst-dim);
}
/* A nested list is a continuation, not a new block. */
.pst-ul .pst-ul, .pst-ul .pst-ol, .pst-ol .pst-ul, .pst-ol .pst-ol { margin-top: .625rem; }

/* Task list. The box is a real element carrying role="img" and a label from
   the renderer — a disabled checkbox in published prose is a control that
   cannot be used, and an emoji is a state assistive technology has to guess. */
.pst-task { display: grid; grid-template-columns: 1.375rem minmax(0, 1fr); gap: .75rem; align-items: start; }
.pst-task-box {
  width: 1.125rem;
  height: 1.125rem;
  margin-top: .35em;
  border: 1px solid var(--pst-rim-strong);
  border-radius: .25rem;
  background-color: var(--pst-bg);
  position: relative;
}
.pst-task-box[data-checked="true"] { background-color: var(--pst-mint); border-color: var(--pst-ink); }
/* A tick, drawn, so the state survives a greyscale print and forced colours —
   a colour-only "done" is not a state anyone can read. */
.pst-task-box[data-checked="true"]::after {
  content: "";
  position: absolute;
  left: 32%;
  top: 12%;
  width: .3rem;
  height: .6rem;
  border: solid var(--pst-on-mint);
  border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}
.pst-task-body { min-width: 0; }

/* ------------------------------------------------------------------- rule */

/* A rule between two passages is a rule, not a decoration: short, ink, left
   aligned to the measure. */
.pst-rule {
  margin-top: 3rem;
  width: 4rem;
  height: 1px;
  border: 0;
  background-color: var(--pst-rim-strong);
}

/* ----------------------------------------------------------------- quotes */

.pst-quote { margin-top: 2.25rem; padding-left: 1.5rem; border-left: 2px solid var(--pst-mint); }
:where(.pst-quote) p { font-size: 1.125rem; line-height: 1.6; color: var(--pst-ink); }
.pst-quote-by {
  margin-top: .875rem;
  font-family: var(--pst-mono);
  font-size: max(.6875rem, 11px);
  letter-spacing: .04em;
  color: var(--pst-dim);
}
.pst-quote-by cite { font-style: normal; }
/* Same floor, same reason — this one measured 78x13. */
.pst-quote-by a { display: inline-flex; align-items: center; min-height: max(1.5rem, 24px); }

/* THE PULL QUOTE is a large article moment, so it is allowed out of the
   measure. It is type and a rule and nothing else: no quotation-mark glyph the
   size of a hand, which is the single most template-looking object a blog can
   own. */
.pst-pull { margin-top: 3rem; }
.pst-pull blockquote p,
.pst-pull blockquote {
  font-family: Quicksand, Arial, sans-serif;
  font-size: clamp(1.375rem, 2.6vw, 2rem);
  line-height: 1.22;
  letter-spacing: -.02em;
  font-weight: 500;
  color: var(--pst-ink);
}
.pst-pull-by {
  margin-top: 1rem;
  padding-top: .875rem;
  border-top: 1px solid var(--pst-line);
  font-family: var(--pst-mono);
  font-size: max(.6875rem, 11px);
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--pst-dim);
}
.pst-body > .pst-pull--wide { grid-column: wide; }
/* left/right pull out of the column on a wide screen only — see the ≥992px
   block. Up to there they are simply full-measure, which is the readable
   answer at a phone width. */

/* --------------------------------------------------------------- callouts */

/* Four tones, and every one of them is the same object: the tone step, a 1px
   rim, and a 3px edge in the tone's own ink. There is no amber-on-cream and no
   red-on-pink here — this site has one accent, and greys between two neutrals.
   The tone is carried by the edge colour AND by the title, so a reader who
   cannot separate the four hues still gets four labelled boxes. */
.pst-callout {
  margin-top: 2.25rem;
  padding: 1.5rem 1.625rem;
  background-color: var(--pst-surface);
  border: 1px solid var(--pst-rim);
  border-left: 3px solid var(--pst-callout-edge, var(--pst-rim-strong));
  border-radius: .75rem;
}
:where(.pst-callout) p { font-size: 1rem; line-height: 1.65; color: var(--pst-ink); }
.pst-callout-title {
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.75rem, 12px);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--pst-callout-edge, var(--pst-ink));
  margin-bottom: .25rem;
}
.pst-callout--note     { --pst-callout-edge: #3b3c38; }   /* 8.9:1 on the step */
.pst-callout--success  { --pst-callout-edge: #0c7a55; }   /* the derived mint  */
.pst-callout--warning  { --pst-callout-edge: #7a4c05; }   /* 5.6:1 on the step */
.pst-callout--caution  { --pst-callout-edge: #8a2010; }   /* 7.2:1 on the step */

/* ------------------------------------------------------------------- code */

.pst-code {
  margin-top: 2.25rem;
  border: 1px solid var(--pst-rim);
  border-radius: .75rem;
  overflow: hidden;
  background-color: var(--pst-surface);
}
.pst-code-file, .pst-code-lang {
  padding: .625rem 1rem;
  border-bottom: 1px solid var(--pst-rim);
  font-family: var(--pst-mono);
  font-size: max(.6875rem, 11px);
  letter-spacing: .04em;
  color: var(--pst-dim);
  line-height: 1.4;
  background-color: var(--pst-surface-2);
}
.pst-code-lang { text-transform: uppercase; }

/* THE PRE SCROLLS, THE PAGE DOES NOT. The renderer hard-codes tabindex="0"
   here, which is correct for a pre that can overflow at any width: it is the
   one scroll container on this page whose overflow is unconditional at some
   width, and a keyboard user must be able to reach the rest of the line. */
.pst-code-pre {
  margin: 0;
  padding: 1.25rem 1.25rem;
  overflow-x: auto;
  font-family: var(--pst-mono);
  font-size: max(.8125rem, 13px);
  line-height: 1.7;
  color: var(--pst-ink);
  tab-size: 2;
}
.pst-code-pre code { font-family: inherit; font-size: inherit; background: none; border: 0; padding: 0; }
/* Line numbers, if the highlighter emits per-line elements. When it does not,
   the gutter is simply a quiet left rule and nothing is broken or missing —
   the numbers are an enhancement, not the content. */
.pst-code-pre--nums { counter-reset: pst-line; padding-left: 3.25rem; position: relative; }
.pst-code-pre--nums::before {
  content: "";
  position: absolute;
  left: 2.5rem;
  top: 0;
  bottom: 0;
  width: 1px;
  background-color: var(--pst-rim);
}
.pst-code-pre--nums code > .line,
.pst-code-pre--nums code > [data-line] { counter-increment: pst-line; display: block; position: relative; }
.pst-code-pre--nums code > .line::before,
.pst-code-pre--nums code > [data-line]::before {
  content: counter(pst-line);
  position: absolute;
  left: -2.25rem;
  width: 1.5rem;
  text-align: right;
  color: var(--pst-quiet);
}

/* ---------------------------------------------------------------- figures */

.pst-fig { margin-top: 2.5rem; }
.pst-fig picture, .pst-fig > a { display: block; }
.pst-fig img, .pst-fig video {
  width: 100%;
  height: auto;
  border-radius: .75rem;
  border: 1px solid var(--pst-line);
  background-color: var(--pst-surface);
}
.pst-fig--full img { border-radius: 0; border-inline: 0; }
.pst-fig--content { max-width: none; }
.pst-fig--inset { max-width: 26rem; }

.pst-cap {
  margin-top: .875rem;
  display: block;
  font-family: var(--pst-mono);
  font-size: max(.6875rem, 11px);
  line-height: 1.6;
  color: var(--pst-dim);
}
.pst-fig--full .pst-cap,
.pst-fig--wide .pst-cap { max-width: var(--pst-measure); }
/* The credit is a second sentence, not a second style — it takes the same face
   one tone quieter, so an unattributed and an attributed caption look like the
   same object. */
.pst-credit { display: block; margin-top: .25rem; color: var(--pst-quiet); }

/* ---------------------------------------------------------------- gallery */

.pst-gal { display: grid; gap: 1rem; }
.pst-gal-item { display: grid; gap: .5rem; min-width: 0; }
.pst-gal-item img { width: 100%; height: 100%; object-fit: cover; border-radius: .625rem; border: 1px solid var(--pst-line); }
.pst-gal-cap {
  font-family: var(--pst-mono);
  font-size: max(.625rem, 10px);
  line-height: 1.5;
  color: var(--pst-dim);
}
.pst-gal--grid[data-cols="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pst-gal--grid[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pst-gal--grid[data-cols="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.pst-gal--grid .pst-gal-item img { aspect-ratio: 4 / 3; }
/* Masonry without masonry: CSS columns, which reflow with no JS and no
   library. The reading order is column-by-column, which is correct for a set
   of images with no narrative order — a gallery is not a sequence. */
.pst-gal--masonry { display: block; columns: 3; column-gap: 1rem; }
.pst-gal--masonry[data-cols="2"] { columns: 2; }
.pst-gal--masonry[data-cols="4"] { columns: 4; }
.pst-gal--masonry .pst-gal-item { break-inside: avoid; margin-bottom: 1rem; }
/* A row scrolls sideways rather than shrinking to thumbnails. The wrapper's
   focusability is set at runtime by post/init.js, only when it overflows. */
.pst-gal--row {
  grid-auto-flow: column;
  grid-auto-columns: minmax(14rem, 1fr);
  overflow-x: auto;
  padding-bottom: .75rem;
}
.pst-gal--stack { grid-template-columns: minmax(0, 1fr); }

/* ------------------------------------------------------------- comparison */

/* The no-JS state and the JS-off state are the same state: two labelled
   images side by side. `data-pst-compare="slider"` only asks post/init.js to
   upgrade it; if that never happens nothing is lost but the wipe. */
.pst-compare { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
.pst-compare-side { position: relative; display: grid; gap: .5rem; min-width: 0; }
.pst-compare-label {
  font-family: var(--pst-mono);
  font-size: max(.625rem, 10px);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--pst-dim);
}
.pst-compare img { width: 100%; border-radius: .625rem; border: 1px solid var(--pst-line); }

/* Upgraded: the two sides stack and the second is clipped to --pst-cmp. The
   control is a real <input type="range"> that post/init.js inserts, so it is
   keyboard-operable, labelled and announced without a line of ARIA. */
.pst-compare.is-slider {
  display: block;
  position: relative;
  border-radius: .625rem;
  overflow: hidden;
  border: 1px solid var(--pst-line);
}
.pst-compare.is-slider .pst-compare-side { position: absolute; inset: 0; gap: 0; }
.pst-compare.is-slider .pst-compare-side:first-child { position: relative; }
.pst-compare.is-slider .pst-compare-side:last-child { clip-path: inset(0 0 0 calc(var(--pst-cmp, 50) * 1%)); }
.pst-compare.is-slider img { border: 0; border-radius: 0; height: 100%; object-fit: cover; }
.pst-compare.is-slider .pst-compare-label {
  position: absolute;
  top: .625rem;
  z-index: 2;
  padding: .25rem .5rem;
  border-radius: .25rem;
  background-color: rgba(250, 252, 251, .92);
  color: var(--pst-ink);
}
.pst-compare.is-slider .pst-compare-side:first-child .pst-compare-label { left: .625rem; }
.pst-compare.is-slider .pst-compare-side:last-child .pst-compare-label { right: .625rem; }
/* The seam. A filled mint line — a shape, so the accent is legitimate — with
   an ink hairline beside it so it is visible over a light photograph too. */
.pst-compare.is-slider::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--pst-cmp, 50) * 1%);
  width: 3px;
  margin-left: -1px;
  background-color: var(--pst-mint);
  border-inline: 1px solid var(--pst-ink);
  pointer-events: none;
  z-index: 3;
}
.pst-cmp-ctl { margin-top: .875rem; display: grid; gap: .4rem; }
.pst-cmp-lab {
  font-family: var(--pst-mono);
  font-size: max(.625rem, 10px);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--pst-dim);
}
.pst-cmp-range { width: 100%; accent-color: var(--pst-ink); min-height: max(1.5rem, 24px); }

/* --------------------------------------------------------- video and embed */

.pst-video { width: 100%; border-radius: .75rem; }
.pst-video--facade {
  position: relative;
  aspect-ratio: var(--pst-aspect, 16 / 9);
  overflow: hidden;
  border: 1px solid var(--pst-line);
  background-color: var(--pst-surface);
}
.pst-video--facade picture, .pst-video--facade img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border: 0; border-radius: 0; }
.pst-video--facade iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* Facade buttons only exist when JS does. Without a runtime they are controls
   that cannot do their one job, and an inert control in the tab order is a
   worse defect than its absence — with JS off, the poster and the caption
   remain, and the <noscript> anchor the renderer emits inside .pst-embed is
   what carries the reader to the source. */
.pst-video-play, .pst-embed-load { display: none; }
.pstjs .pst-video-play, .pstjs .pst-embed-load {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  min-height: max(2.75rem, 44px);
  padding: .875rem 1.375rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background-color: var(--pst-mint);
  color: var(--pst-on-mint);
  font-family: Quicksand, Arial, sans-serif;
  font-size: .9375rem;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  transition: background-color .35s var(--pst-ease);
}
.pstjs .pst-video-play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
}
.pst-video-play:hover, .pst-embed-load:hover { background-color: #19d894; }
/* The consequence of pressing it, said before it is pressed. Third-party
   frames are the one thing on this site that loads because a reader asked.
   It is quieter than the label by SIZE and by face, never by opacity: a fade
   on real text is the rule PAGE-KIT §5 names, and it measured 0.85 here, which
   is a contrast reduction on a mint ground for no gain at all. */
.pst-video-note, .pst-embed-note {
  font-family: var(--pst-mono);
  font-size: max(.625rem, 10px);
  letter-spacing: .04em;
  font-weight: 400;
}

.pst-embed {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: var(--pst-aspect, 16 / 9);
  border: 1px solid var(--pst-rim);
  border-radius: .75rem;
  background-color: var(--pst-surface);
  overflow: hidden;
}
.pst-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.pst-embed noscript a { color: var(--pst-ink); font-family: Quicksand, Arial, sans-serif; }

/* ------------------------------------------------------------------ audio */

.pst-audio {
  display: grid;
  gap: .875rem;
  padding: 1.375rem 1.5rem;
  background-color: var(--pst-surface);
  border: 1px solid var(--pst-rim);
  border-radius: .75rem;
}
.pst-audio audio { width: 100%; }
.pst-audio-title {
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.75rem, 12px);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--pst-ink);
}
.pst-audio-tr { font-size: .9375rem; }
.pst-audio-tr a { color: var(--pst-ink); text-underline-offset: .22em; }

/* ------------------------------------------------------------ file download */

.pst-file { margin-top: 2rem; display: grid; gap: .5rem; }
.pst-file > a {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .75rem;
  min-height: max(3.25rem, 44px);
  padding: 1rem 1.25rem;
  background-color: var(--pst-surface);
  border: 1px solid var(--pst-rim);
  border-radius: .625rem;
  text-decoration: none;
  transition: background-color .3s var(--pst-ease), border-color .3s var(--pst-ease);
}
.pst-file > a:hover { background-color: var(--pst-surface-2); border-color: var(--pst-rim-strong); }
.pst-file-label { font-family: Quicksand, Arial, sans-serif; font-size: 1rem; font-weight: 600; color: var(--pst-ink); }
.pst-file-meta { font-family: var(--pst-mono); font-size: max(.6875rem, 11px); letter-spacing: .04em; color: var(--pst-dim); }
.pst-file-desc { font-family: Quicksand, Arial, sans-serif; font-size: .9375rem; line-height: 1.6; color: var(--pst-dim); }

/* ----------------------------------------------------------------- tables */

/* THE WRAPPER SCROLLS, NEVER THE PAGE. `tabindex` and the accessible name are
   set at runtime by post/init.js and only while the wrapper actually
   overflows: a hard-coded tab stop is wrong at every width where it does not,
   which is the anti-pattern PAGE-KIT §9 names. */
/* [data-pst-scroll] is the renderer's own marker for "this box scrolls, the
   page does not". It rides on .pst-table-wrap today; styling the attribute too
   means a future block that carries it inherits the same box and the same
   runtime tab-stop sync without a second rule. */
.pst-table-wrap,
[data-pst-scroll] {
  margin-top: 2.25rem;
  overflow-x: auto;
  border: 1px solid var(--pst-rim);
  border-radius: .75rem;
}
.pst-table-wrap:focus-visible { outline: 2px solid var(--pst-ink); outline-offset: 2px; }
.pst-table {
  width: 100%;
  border-collapse: collapse;
  font-family: Quicksand, Arial, sans-serif;
  font-size: .9375rem;
  line-height: 1.5;
}
.pst-table caption {
  caption-side: top;
  padding: .875rem 1.125rem;
  text-align: left;
  background-color: var(--pst-surface-2);
  border-bottom: 1px solid var(--pst-rim);
  font-family: var(--pst-mono);
  font-size: max(.6875rem, 11px);
  letter-spacing: .05em;
  color: var(--pst-dim);
}
.pst-table th, .pst-table td { padding: .75rem 1.125rem; text-align: left; vertical-align: top; }
.pst-table thead th {
  background-color: var(--pst-surface);
  border-bottom: 1px solid var(--pst-rim);
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.6875rem, 11px);
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--pst-ink);
  white-space: nowrap;
}
.pst-table tbody tr + tr th, .pst-table tbody tr + tr td { border-top: 1px solid var(--pst-line); }
.pst-table tbody th { font-weight: 600; color: var(--pst-ink); }
.pst-table td { color: var(--pst-ink-soft); }
/* The data twin under a chart: figures, so monospace and right-aligned, which
   is the only way a column of numbers can be compared by eye. */
.pst-table--data td { font-family: var(--pst-mono); font-size: max(.8125rem, 13px); text-align: right; }
.pst-table--data th[scope="row"] { white-space: nowrap; }
.pst-table--data thead th + th { text-align: right; }

/* ------------------------------------------------------ charts + diagrams */

.pst-chart, .pst-diagram {
  padding: 1.5rem;
  background-color: var(--pst-surface);
  border: 1px solid var(--pst-rim);
  border-radius: .75rem;
}
.pst-chart svg, .pst-diagram svg, .pst-diagram img { width: 100%; height: auto; display: block; }

/* The text alternative is a <details>, open to everyone rather than hidden
   behind aria — the two-part alternative W3C WAI asks for on a complex image.
   It is not a "more info" flourish: for a chart it is the data. */
.pst-chart-alt, .pst-diagram-alt {
  margin-top: .75rem;
  border: 1px solid var(--pst-line);
  border-radius: .625rem;
  background-color: var(--pst-bg);
}
.pst-chart-alt > summary, .pst-diagram-alt > summary,
.pst-acc-sum, .pst-faq-q {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: .75rem;
  min-height: max(2.75rem, 44px);
  padding: .875rem 1.125rem;
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.8125rem, 13px);
  letter-spacing: .02em;
  color: var(--pst-ink);
}
.pst-chart-alt > summary::-webkit-details-marker,
.pst-diagram-alt > summary::-webkit-details-marker,
.pst-acc-sum::-webkit-details-marker,
.pst-faq-q::-webkit-details-marker { display: none; }
/* The disclosure triangle, drawn, so its rotation is the open/closed signal in
   every colour mode. <details> gives keyboard, screen-reader and find-in-page
   behaviour for free, which is exactly why this site does not build a JS
   accordion. */
.pst-chart-alt > summary::before,
.pst-diagram-alt > summary::before,
.pst-acc-sum::before,
.pst-faq-q::before {
  content: "";
  flex: none;
  width: .5rem;
  height: .5rem;
  border: solid var(--pst-mint-ink);
  border-width: 0 2px 2px 0;
  transform: rotate(-45deg);
  transition: transform .3s var(--pst-ease);
}
details[open] > .pst-acc-sum::before,
details[open] > .pst-faq-q::before,
.pst-chart-alt[open] > summary::before,
.pst-diagram-alt[open] > summary::before { transform: rotate(45deg); }
:where(.pst-chart-alt, .pst-diagram-alt) > p {
  padding: 0 1.125rem 1.125rem;
  font-size: .9375rem;
  line-height: 1.6;
  color: var(--pst-dim);
}
.pst-chart-alt .pst-table-wrap, .pst-diagram-alt .pst-table-wrap { margin: 0 1.125rem 1.125rem; }

/* ------------------------------------------------------------------ stats */

/* Figures, never animated. A number that counts up is a number the reader
   briefly cannot read, and every one of these carries its source beside it. */
.pst-stats {
  display: grid;
  gap: 2rem;
  padding: 2rem 0;
  border-block: 1px solid var(--pst-line);
}
.pst-stats[data-cols="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pst-stats[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pst-stats[data-cols="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.pst-stat { display: grid; gap: .5rem; align-content: start; min-width: 0; }
.pst-stat-value {
  font-family: var(--pst-mono);
  font-size: clamp(1.75rem, 3.2vw, 2.75rem);
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--pst-ink);
}
.pst-stat-label {
  font-family: Quicksand, Arial, sans-serif;
  font-size: 1rem;
  line-height: 1.4;
  font-weight: 600;
  color: var(--pst-ink);
}
.pst-stat-note { font-size: .875rem; line-height: 1.55; color: var(--pst-dim); }
/* The source is a link to the list at the foot of the article, because a
   number without a method and a date is the tell this site is written
   against. */
.pst-stat-src { font-family: var(--pst-mono); font-size: max(.625rem, 10px); letter-spacing: .04em; }
.pst-stat-src a { color: var(--pst-mint-ink); text-underline-offset: .22em; }

/* ---------------------------------------------------------------- metrics */

.pst-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr)); gap: 1rem; }
.pst-metric {
  display: grid;
  gap: .4rem;
  align-content: start;
  padding: 1.25rem 1.375rem;
  background-color: var(--pst-surface);
  border: 1px solid var(--pst-rim);
  border-radius: .75rem;
  min-width: 0;
}
.pst-metric-label {
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.6875rem, 11px);
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--pst-dim);
}
.pst-metric-value {
  font-family: var(--pst-mono);
  font-size: 1.5rem;
  line-height: 1.1;
  color: var(--pst-ink);
}
/* Direction is an arrow AND a colour, never a colour alone. `flat` gets a
   dash, so all three states are readable in greyscale. */
.pst-metric-delta {
  display: flex;
  align-items: baseline;
  gap: .35rem;
  font-family: var(--pst-mono);
  font-size: max(.75rem, 12px);
  color: var(--pst-dim);
}
.pst-metric-delta::before { content: "—"; }
.pst-metric-delta[data-dir="up"]::before { content: "▲"; }
.pst-metric-delta[data-dir="down"]::before { content: "▼"; }
.pst-metric-delta[data-dir="up"] { color: #0c7a55; }
.pst-metric-delta[data-dir="down"] { color: #8a2010; }

/* --------------------------------------------------------------- timeline */

.pst-tl, .pst-tl--vertical { display: grid; gap: 0; counter-reset: pst-tl; }
.pst-tl-item {
  position: relative;
  display: grid;
  gap: .35rem;
  padding: 1.5rem 0 1.5rem 2.25rem;
  border-left: 1px solid var(--pst-line);
}
.pst-tl-item::before {
  content: "";
  position: absolute;
  left: -.3125rem;
  top: 1.9rem;
  width: .625rem;
  height: .625rem;
  border-radius: 50%;
  background-color: var(--pst-mint);
  border: 1px solid var(--pst-ink);
}
.pst-tl-when {
  font-family: var(--pst-mono);
  font-size: max(.6875rem, 11px);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--pst-dim);
}
.pst-tl-title { font-size: 1.0625rem; line-height: 1.3; font-weight: 600; color: var(--pst-ink); }
.pst-tl-body { font-size: .9375rem; line-height: 1.6; color: var(--pst-dim); }
/* Horizontal is a scrolling track; post/init.js decides its focusability by
   measuring the overflow, never by hard-coding a tab stop. */
.pst-tl--horizontal {
  grid-auto-flow: column;
  grid-auto-columns: minmax(13rem, 1fr);
  overflow-x: auto;
  padding-bottom: .75rem;
}
.pst-tl--horizontal .pst-tl-item { border-left: 0; border-top: 1px solid var(--pst-line); padding: 1.75rem 1.25rem 0 0; }
.pst-tl--horizontal .pst-tl-item::before { left: 0; top: -.3125rem; }

/* ------------------------------------------------------------- layout kit */

.pst-cols { display: grid; gap: 2rem; }
.pst-col { min-width: 0; }
.pst-cols--1-1 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pst-cols--1-2 { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
.pst-cols--2-1 { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
.pst-cols--1-1-1 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.pst-section { display: block; }
.pst-section--raised,
.pst-section--accent,
.pst-section--inverted { padding: 2.5rem 2rem; border-radius: 1rem; border: 1px solid var(--pst-rim); }
.pst-section--raised { background-color: var(--pst-surface); }
/* Accent is the mint at a strength that is a ground, not a wash: the full
   accent as a left edge on the tone step. Mint is never the whole ground of a
   band of prose — 1.5:1 against ink is a ground you cannot read on. */
.pst-section--accent { background-color: var(--pst-surface); border-left: 4px solid var(--pst-mint); }
/* The one dark object in a light article. Every colour inside it is re-pointed
   at the page tokens rather than overridden rule by rule, so a component
   dropped in here inverts with it and nothing has to know it is inside. */
.pst-section--inverted {
  --pst-bg: #10120f;
  --pst-surface: #1c1f1b;
  --pst-surface-2: #262a25;
  --pst-ink: #edf5f3;
  --pst-ink-soft: #edf5f3;
  --pst-dim: #b4b8a5;         /* 9.6:1 on #10120f */
  --pst-quiet: #9fa39a;
  --pst-line: rgba(237, 245, 243, .12);
  --pst-rim: rgba(237, 245, 243, .22);
  --pst-rim-strong: rgba(237, 245, 243, .45);
  --pst-mint-ink: #21f1a8;    /* on ink, the full mint measures 11.9:1 */
  background-color: #10120f;
  color: #edf5f3;
  border-color: rgba(237, 245, 243, .22);
}
.pst-section--inverted :where(a, button, summary):focus-visible { outline-color: var(--pst-mint); }
/* A full-bleed band is a full-bleed GROUND, never a full-bleed line of text.
   It carries the same three tracks the article does, so prose inside it lands
   on the reading measure — at 1440px the dark band was setting 1150px lines
   16px from the viewport edge before this. */
.pst-section--full {
  border-radius: 0;
  border-inline: 0;
  padding-inline: 0;
  display: grid;
  grid-template-columns:
    minmax(var(--padding--container), 1fr)
    min(100% - 2 * var(--padding--container), var(--pst-measure))
    minmax(var(--padding--container), 1fr);
}
.pst-section--full > * { grid-column: 2; }
.pst-section--plain { padding: 0; border: 0; background: none; }

.pst-spacer { display: block; }
.pst-spacer--sm { height: 1.5rem; }
.pst-spacer--md { height: 3rem; }
.pst-spacer--lg { height: 5.5rem; }

/* ---------------------------------------------- accordion, FAQ and steps */

/* All <details>. No JS disclosure, no role="tab", no roving tabindex and no
   focus trap — PAGE-KIT §5.5, and the reason is that the element already does
   all four correctly and find-in-page works into a closed one. */
.pst-acc, .pst-faq { display: grid; gap: .5rem; }
.pst-acc-item, .pst-faq-item {
  border: 1px solid var(--pst-rim);
  border-radius: .625rem;
  background-color: var(--pst-bg);
  overflow: hidden;
}
.pst-acc-item[open], .pst-faq-item[open] { background-color: var(--pst-surface); }
.pst-acc-sum:hover, .pst-faq-q:hover { color: var(--pst-mint-ink); }
/* Aligned to the SUMMARY TEXT, not to the box: the chevron is .5rem wide with
   a .75rem gap, so the body indents past both and the answer reads as an answer
   rather than as a new paragraph. */
.pst-acc-body, .pst-faq-a { padding: 0 1.125rem 1.25rem 2.375rem; }
.pst-faq-q { font-size: max(.9375rem, 15px); }

.pst-steps { counter-reset: pst-step; display: grid; gap: 0; }
.pst-step {
  counter-increment: pst-step;
  position: relative;
  display: grid;
  gap: .5rem;
  padding: 1.75rem 0 1.75rem 3.5rem;
  border-top: 1px solid var(--pst-line);
}
.pst-step:last-child { border-bottom: 1px solid var(--pst-line); }
.pst-step::before {
  content: counter(pst-step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 1.85rem;
  font-family: var(--pst-mono);
  font-size: max(.75rem, 12px);
  letter-spacing: .04em;
  color: var(--pst-mint-ink);
}
/* `numbered: false` means the order is not the argument, so the counter is
   replaced by a mark rather than dropped — a step list with nothing in the
   gutter reads as a paragraph that lost its indent. */
.pst-steps--plain .pst-step::before { content: ""; width: .5rem; height: .5rem; top: 2.1rem; background-color: var(--pst-mint); }
.pst-step-title { font-size: 1.0625rem; line-height: 1.3; font-weight: 600; color: var(--pst-ink); }
.pst-step-body { min-width: 0; }

/* ------------------------------------------------------------------ tabs */

/* The renderer emits headed sections and nothing else, so with JS off the
   reader gets every panel, each under its own <h3> — which is the whole point:
   a tab interface that hides its panels without a runtime is content nobody
   can reach.

   post/init.js upgrades it to a BUTTON GROUP, not an ARIA tablist: PAGE-KIT
   §5.5 rules out role="tab" and roving tabindex here, so every button stays in
   the tab order and each one is a plain <button aria-expanded aria-controls>.
   Tab and Enter operate it; there is nothing to learn and nothing to trap. */
.pst-tabs { display: grid; gap: 1.5rem; }
.pst-tablist { display: flex; flex-wrap: wrap; gap: .5rem; border-bottom: 1px solid var(--pst-line); padding-bottom: .75rem; }
.pst-tabbtn {
  min-height: max(2.25rem, 32px);
  padding: .4rem .9rem;
  border: 1px solid var(--pst-rim);
  border-radius: 999px;
  background-color: transparent;
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.75rem, 12px);
  line-height: 1;
  color: var(--pst-dim);
  cursor: pointer;
  transition: background-color .3s var(--pst-ease), color .3s linear, border-color .3s var(--pst-ease);
}
.pst-tabbtn:hover { border-color: var(--pst-ink); color: var(--pst-ink); }
.pst-tabbtn[aria-expanded="true"] { background-color: var(--pst-mint); border-color: var(--pst-ink); color: var(--pst-on-mint); }
.pst-tab { min-width: 0; }
.pst-tab[hidden] { display: none; }
/* The panel heading stays in the DOM once the group is tabbed up — it is the
   panel's accessible name via aria-labelledby — but it is not drawn twice. */
.pst-tabs.is-tabbed .pst-tab-h {
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
}
.pst-tab-h {
  font-family: Quicksand, Arial, sans-serif;
  font-size: 1.25rem;
  line-height: 1.25;
  font-weight: 600;
  color: var(--pst-ink);
  margin-bottom: .75rem;
}

/* ------------------------------------------------------------- marketing */

.pst-cta {
  margin-top: 3rem;
  display: grid;
  gap: .75rem;
  padding: 2.25rem 2rem;
  border-radius: 1rem;
  border: 1px solid var(--pst-rim);
  background-color: var(--pst-surface);
}
.pst-cta--accent { border-left: 4px solid var(--pst-mint); }
.pst-cta-h {
  font-family: Quicksand, Arial, sans-serif;
  font-size: 1.375rem;
  line-height: 1.2;
  font-weight: 600;
  color: var(--pst-ink);
  max-width: 24ch;
}
.pst-cta-b { font-size: 1rem; line-height: 1.6; color: var(--pst-dim); max-width: 52ch; }
/* One solid button, mint ground, ink type — the site-wide CTA hierarchy. */
.pst-cta-btn {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  min-height: max(2.75rem, 44px);
  margin-top: .5rem;
  padding: .875rem 1.5rem;
  border-radius: 999px;
  background-color: var(--pst-mint);
  color: var(--pst-on-mint);
  font-family: Quicksand, Arial, sans-serif;
  font-size: .9375rem;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  transition: background-color .35s var(--pst-ease);
}
.pst-cta-btn:hover { background-color: #19d894; }
/* The plain tone is a text link, not a second pill: an outlined pill would
   need a boundary carrying 3:1 on this paper, and the hairline rim does not. */
.pst-cta--plain { background: none; border: 0; border-top: 1px solid var(--pst-line); border-radius: 0; padding-inline: 0; }
.pst-cta--plain .pst-cta-btn { background: none; padding: 0; color: var(--pst-ink); text-decoration: underline; text-underline-offset: .3em; }

.pst-linkcard { margin-top: 2rem; }
.pst-linkcard > a {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  gap: 1.25rem;
  align-items: center;
  padding: 1rem;
  border: 1px solid var(--pst-rim);
  border-radius: .75rem;
  background-color: var(--pst-bg);
  text-decoration: none;
  transition: background-color .3s var(--pst-ease), border-color .3s var(--pst-ease);
}
.pst-linkcard > a:hover { background-color: var(--pst-surface); border-color: var(--pst-rim-strong); }
.pst-linkcard-img img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: .5rem; }
.pst-linkcard-body { display: grid; gap: .3rem; min-width: 0; }
.pst-linkcard-site { font-family: var(--pst-mono); font-size: max(.625rem, 10px); letter-spacing: .07em; text-transform: uppercase; color: var(--pst-dim); }
.pst-linkcard-title { font-family: Quicksand, Arial, sans-serif; font-size: 1.0625rem; line-height: 1.3; font-weight: 600; color: var(--pst-ink); }
.pst-linkcard-desc { font-family: Quicksand, Arial, sans-serif; font-size: .9375rem; line-height: 1.55; color: var(--pst-dim); }
/* No image supplied: the grid collapses to one column rather than leaving a
   7rem hole. */
.pst-linkcard > a:not(:has(.pst-linkcard-img)) { grid-template-columns: minmax(0, 1fr); }

.pst-rel { margin-top: var(--pst-rhythm); padding-top: 2rem; border-top: 1px solid var(--pst-line); }
.pst-rel-h {
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.75rem, 12px);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--pst-dim);
}
.pst-rel ul { display: grid; gap: 0; margin-top: 1rem; }
.pst-rel-item { border-top: 1px solid var(--pst-line); }
.pst-rel-item:last-child { border-bottom: 1px solid var(--pst-line); }
.pst-rel-item > a {
  display: grid;
  gap: .35rem;
  padding: 1.25rem 0;
  min-height: max(3rem, 44px);
  text-decoration: none;
}
.pst-rel-title {
  font-family: Quicksand, Arial, sans-serif;
  font-size: 1.125rem;
  line-height: 1.25;
  font-weight: 600;
  color: var(--pst-ink);
  background-image: linear-gradient(var(--pst-ink), var(--pst-ink));
  background-size: 0 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size .45s var(--pst-ease);
}
.pst-rel-item > a:hover .pst-rel-title,
.pst-rel-item > a:focus-visible .pst-rel-title { background-size: 100% 1px; }
.pst-rel-ex { font-family: Quicksand, Arial, sans-serif; font-size: .9375rem; line-height: 1.55; color: var(--pst-dim); max-width: var(--pst-measure); }

/* The author note. First person singular is the site's whole voice, so this is
   a quiet ruled aside rather than an avatar card — there is one person here
   and a photograph of him beside every article would be a claim, not a fact. */
.pst-note { margin-top: 2.5rem; padding: 1.5rem 0 0; border-top: 2px solid var(--pst-ink); }
:where(.pst-note) p { font-size: 1rem; line-height: 1.65; color: var(--pst-dim); }
.pst-note-label {
  font-family: var(--pst-mono);
  font-size: max(.625rem, 10px);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--pst-ink);
  margin-bottom: .75rem;
}

/* ------------------------------------------------------------- references */

.pst-sources, .pst-footnotes { margin-top: var(--pst-rhythm); padding-top: 2rem; border-top: 1px solid var(--pst-line); }
.pst-sources-h, .pst-footnotes-h {
  font-family: 'Quicksand Label', Arial, sans-serif;
  font-size: max(.75rem, 12px);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--pst-dim);
}
.pst-sources ol, .pst-footnotes ol { display: grid; gap: 1rem; margin-top: 1.25rem; }
.pst-src { display: grid; grid-template-columns: 2rem minmax(0, 1fr); gap: .75rem; scroll-margin-top: calc(var(--on-t-nav-spacer, 5.375rem) + 1.5rem); }
.pst-src-n { font-family: var(--pst-mono); font-size: max(.6875rem, 11px); color: var(--pst-quiet); padding-top: .15em; }
.pst-src-body { display: grid; gap: .25rem; font-family: Quicksand, Arial, sans-serif; font-size: .9375rem; line-height: 1.5; }
.pst-src-body a {
  display: inline-flex;
  align-items: center;
  min-height: max(1.5rem, 24px);
  color: var(--pst-ink);
  text-underline-offset: .22em;
}
.pst-src-meta, .pst-src-acc { font-family: var(--pst-mono); font-size: max(.625rem, 10px); letter-spacing: .03em; color: var(--pst-dim); }
.pst-fn-item {
  display: block;
  font-family: Quicksand, Arial, sans-serif;
  font-size: .9375rem;
  line-height: 1.6;
  color: var(--pst-dim);
  scroll-margin-top: calc(var(--on-t-nav-spacer, 5.375rem) + 1.5rem);
}
/* The back-link is a real target, not a 10px arrow: 24 px minimum at every
   width, which is the rule that decides its size rather than its glyph. */
.pst-fn-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: max(1.5rem, 24px);
  min-height: max(1.5rem, 24px);
  text-decoration: none;
  color: var(--pst-mint-ink);
}

/* ------------------------------------------------------------- raw HTML */

/* Admin-only, sanitised, and deliberately given no styling of its own beyond
   the prose rhythm — anything more would be a second, undeclared design
   system reachable from a text field. */
.pst-raw { font-family: Quicksand, Arial, sans-serif; font-size: 1.0625rem; line-height: 1.7; color: var(--pst-ink-soft); }
.pst-raw img { border-radius: .625rem; }

/* ---------------------------------------------------------------- the end */

.pst-end {
  margin-top: var(--pst-rhythm);
  width: 100%;
  max-width: var(--fluid-container);
  margin-inline: auto;
  padding-inline: var(--padding--container);
  padding-bottom: var(--pst-rhythm);
}

/* ------------------------------------------------------------------ motion

   Classified, per PAGE-KIT §4.3, rather than blanket-disabled:

     reveals         STOP        — the settled state is the start state
     cover settle    STOP
     progress        STOP        — the loop never starts, the bar is not drawn
     TOC .is-here    KEEP        — a state change, not an animation
     hovers          INSTANT     — the transition-duration override handles it
     numbers         never animate anywhere, under any preference

   The hidden start state exists only under `.pstjs`. No class → no JS → the
   article renders settled and complete. */
.pstjs .pst-fig,
.pstjs .pst-pull,
.pstjs .pst-cover { opacity: 0; transform: translateY(1rem); }
.pstjs .pst-fig.is-in,
.pstjs .pst-pull.is-in,
.pstjs .pst-cover.is-in {
  opacity: 1;
  transform: none;
  transition: opacity .8s var(--pst-ease), transform .8s var(--pst-ease);
}
/* Prose is never revealed. A paragraph that fades in is a paragraph the reader
   waits for, and the whole page is prose. */

@media (prefers-reduced-motion: reduce) {
  .taxi-w.post *,
  .taxi-w.post *::before,
  .taxi-w.post *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  /* Everything reaches its settled, visible state whether or not the observer
     ever fires. Reading progress is off entirely: the bar is not drawn and the
     rail's fill is not a fill — post/init.js also never starts the loop and
     clears any value it already wrote. */
  .pstjs .pst-fig,
  .pstjs .pst-pull,
  .pstjs .pst-cover { opacity: 1; transform: none; }
  .pstjs .pst-fig.is-in,
  .pstjs .pst-pull.is-in,
  .pstjs .pst-cover.is-in { transition: none; }
  .pstjs .pst-bar { display: none; }
  .pst-rail-line::after { transform: none; }
}

/* ------------------------------------------------------------- ≥ 992px */

@media screen and (min-width: 992px) {
  /* Two heroes take a real two-column masthead; the other three stay one
     column, which is what makes them read as different articles rather than
     as the same article with a different picture.

     The `data` masthead is a grid over the SAME children — no wrapper, so a
     missing dek or a missing figure strip leaves an empty area rather than a
     broken box, and the other four heroes are unaffected because the grid only
     exists under this attribute. */
  .pst[data-hero="data"] .pst-mast {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
    column-gap: 3.5rem;
    align-items: start;
    grid-template-areas:
      "crumb  crumb"
      "kicker figs"
      "title  figs"
      "dek    figs"
      "byline byline";
  }
  .pst[data-hero="data"] .pst-crumb { grid-area: crumb; }
  .pst[data-hero="data"] .pst-kicker { grid-area: kicker; }
  .pst[data-hero="data"] .pst-title { grid-area: title; }
  .pst[data-hero="data"] .pst-dek { grid-area: dek; }
  .pst[data-hero="data"] .pst-byline { grid-area: byline; }
  .pst[data-hero="data"] .pst-figures { grid-area: figs; margin-top: 2rem; align-self: stretch; }

  /* THE CUT-IN. The kicker and the title share one paper plate that is pulled
     up into the lower-left corner of the cover — the only place on this site
     where type sits over an image, and it does it by cutting a hole in the
     image rather than by floating on top of one. Contrast is therefore a
     property of the layout: the words are always on paper, whatever the
     photograph underneath happens to be. */
  .pst[data-hero="visual-story"] .pst-kicker,
  .pst[data-hero="visual-story"] .pst-title {
    align-self: flex-start;
    position: relative;
    z-index: 2;
    background-color: var(--pst-bg);
    padding-right: 2.25rem;
    border-top-right-radius: .75rem;
  }
  .pst[data-hero="visual-story"] .pst-kicker { margin-top: -4.75rem; padding-top: 1.5rem; }
  .pst[data-hero="visual-story"] .pst-title { margin-top: .75rem; padding-bottom: .25rem; }

  /* The rail becomes what it is for: a contents column and a progress line,
     sticky beside the article. `top` clears the nav so a heading jumped to is
     never behind it. */
  .pst-layout {
    display: grid;
    grid-template-columns: 13rem minmax(0, 1fr);
    column-gap: 3.5rem;
    align-items: start;
    width: 100%;
    max-width: var(--fluid-container);
    margin-inline: auto;
    padding-inline: var(--padding--container);
  }
  /* No contents: one column, and the mobile bar comes back, because otherwise
     this article would be the only one with no progress indicator at all. */
  .pst-layout.is-norail { grid-template-columns: minmax(0, 1fr); }
  .pstjs .pst-layout.is-norail .pst-bar { display: block; }

  .pst-rail {
    position: sticky;
    padding-inline: 0;
    top: calc(var(--on-t-nav-spacer, 5.375rem) + 2.5rem);
    /* max-height + overflow so a forty-heading article's contents scrolls
       inside the rail rather than running off the bottom of the viewport —
       the "sticky behaviour that is correct, not annoying" half of the job. */
    max-height: calc(100vh - var(--on-t-nav-spacer, 5.375rem) - 5rem);
    display: flex;
    gap: 1.125rem;
    margin-top: var(--pst-gap);
  }
  .pst-rail-line {
    display: block;
    position: relative;
    width: 1px;
    align-self: stretch;
    flex: none;
    background-color: var(--pst-line);
    overflow: hidden;
  }
  /* Ink, not mint: a progress rail is a graphical object the reader is meant
     to be able to see, and mint on paper is ~1.5:1. */
  .pst-rail-line::after {
    content: "";
    position: absolute;
    inset: 0;
    background-color: var(--pst-ink);
    transform: scaleY(var(--pst-p, 0));
    transform-origin: top center;
  }
  .pst-toc {
    flex-direction: column;
    align-items: flex-start;
    gap: .75rem;
    padding: 0;
    overflow-x: visible;
    overflow-y: auto;
    border: 0;
  }
    .pst-toc-link { white-space: normal; align-items: flex-start; }
  /* The mobile bar is redundant once the rail is drawing the same number. */
  .pstjs .pst-bar { display: none; }

  /* The body's own padding is now the layout's, so the column tracks stop
     carrying it and the measure sits where the grid puts it. */
  .pst-body {
    grid-template-columns:
      [full-start] minmax(0, 1fr)
      [wide-start] minmax(0, 5rem)
      [content-start] min(100%, var(--pst-measure)) [content-end]
      minmax(0, 5rem) [wide-end]
      minmax(0, 1fr) [full-end];
    padding-top: 0;
  }

  /* Side pull quotes leave the column. This is the page's only asymmetry and
     it is reserved for the sentence the article is actually about. */
  .pst-body > .pst-pull--left { grid-column: full-start / content-end; padding-right: 3rem; }
  .pst-body > .pst-pull--right { grid-column: content-start / full-end; padding-left: 3rem; }
}

/* ------------------------------------------------------------- ≤ 991px */

@media screen and (max-width: 991px) {
  .taxi-w.post { --pst-rhythm: 4.5rem; --pst-measure: 64ch; }
  .pst-mast { padding-top: calc(var(--on-t-nav-spacer, 5.375rem) + 1.5rem); padding-bottom: 2.5rem; }
  .pst[data-hero="minimal-essay"] .pst-mast { padding-top: calc(var(--on-t-nav-spacer, 5.375rem) + 2.5rem); }
  .pst-cols--1-2, .pst-cols--2-1 { grid-template-columns: minmax(0, 1fr); }
  .pst-cols--1-1-1 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pst-stats[data-cols="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pst-gal--grid[data-cols="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pst-gal--masonry, .pst-gal--masonry[data-cols="4"] { columns: 2; }
}

@media screen and (max-width: 767px) {
  .pst-cols--1-1, .pst-cols--1-1-1 { grid-template-columns: minmax(0, 1fr); }
  .pst-stats, .pst-stats[data-cols="2"], .pst-stats[data-cols="3"], .pst-stats[data-cols="4"] { grid-template-columns: minmax(0, 1fr); gap: 1.75rem; }
  .pst-gal--grid, .pst-gal--grid[data-cols="3"], .pst-gal--grid[data-cols="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pst-gal--masonry { columns: 1; }
  /* Two images side by side on a phone are two thumbnails. The comparison
     stacks, and both labels stay. */
  .pst-compare { grid-template-columns: minmax(0, 1fr); }
  .pst-section--raised, .pst-section--accent, .pst-section--inverted { padding: 1.75rem 1.25rem; }
  .pst-cta { padding: 1.75rem 1.375rem; }
  .pst-cta-btn { width: 100%; justify-content: center; }
  .pst-linkcard > a { grid-template-columns: 4.5rem minmax(0, 1fr); gap: .875rem; }
  .pst-callout { padding: 1.25rem 1.125rem; }
  .pst-code-pre { padding: 1rem; }
  .pst-byline { gap: 1.25rem 1.75rem; }
  .pst-step { padding-left: 2.75rem; }
  .pst-table th, .pst-table td { padding: .625rem .875rem; }
}
