/* ============================================================================
   blog.css — THE BLOG'S OWN LAYER, ON TOP OF site.css
   ============================================================================

   ⚠️ A SECOND FILE AND NOT AN APPENDIX TO site.css. site.css is 87 KB and is
   loaded by `/`, the three legal pages and the fifteen marketing pages — none
   of which needs a single rule below. The blog's prose, its four callouts and
   its syntax theme are the blog's cost and are paid only by a blog reader.

   ⚠️ IT DEFINES NO NEW COLOUR. Every value here is a site.css token, and that
   is not tidiness: site.css's contrast figures were MEASURED with a calculator
   that reproduces all twelve ratios the file states about itself, including the
   four it states inside `.plate` that disagree with the design table. A new hex
   here would be a colour nobody measured, on the one surface a stranger reads
   first. If a rule below needs an ink, it names a `-text` token; if it needs a
   fill, it names a `-fill` or a `-tint`. ⚠️ The split is load-bearing: white ink
   on `--success-fill` is 4.35:1 and FAILS AA, which is why a status chip here is
   a TINT plus the `-text` ink plus a glyph.

   ⚠️ NO TAILWIND CLASS APPEARS IN ANY BLOG TEMPLATE. Invariant 31. The blog is
   on the site.css side of the product; the authenticated app is on Tailwind's.
   A Tailwind utility written in a blog template would work today (Tailwind
   scans app/pages/**) and would be the first step toward two design systems on
   one page, with specificity decided by <link> order.

   ⚠️ EVERY CLASS IS PREFIXED `cb-`, AND THE FOUR CALLOUT CLASSES AND THE
   `hljs-*` SET ARE THE ONLY ONES PRODUCED AT RUNTIME. They are emitted by
   server/services/blog-render.ts from a closed list — never assembled from
   author input and never assembled from a variable. `text-${tone}-500` emits no
   CSS at all, which is how the admin type scale shipped invisible for weeks.

   ⚠️ LIGHT ONLY. No prefers-color-scheme block, no [data-theme] block, no
   .dark selector — invariant 32, and site.css carries the owner's recorded
   decision and its reasons verbatim. `.plate` is the single dark band and the
   blog does not use it.
   ============================================================================ */

/* ── THE ARTICLE SHELL ───────────────────────────────────────────────────────
   ⚠️ IT ADDS NO GRID. site.css's `.page` is already a named-line grid with a
   190px rail on each side of the text column, so the table of contents goes on
   the LEFT rail and the author/share column on the RIGHT one with two
   grid-column declarations and nothing else. A second grid here would be a
   second thing to keep in step with the gutters, which is how the
   415-in-375 horizontal-scroll defect happened. */
.cb-rail-l{ grid-column:wide-start / text-start; }
.cb-rail-r{ grid-column:text-end / wide-end; }
.cb-rail-l > *, .cb-rail-r > *{ position:sticky; top:96px; }

/* ⚠️ THE COPY COMES FIRST AT EVERY WIDTH. Below the rails' breakpoint they
   collapse INTO the text column, above the prose for the ToC and below it for
   the author — never beside it in a squeezed 120px column. The hero defect that
   put an h1 at top:813px is the same mistake a sticky ToC eating the first
   screen on a phone would repeat. */
@media (max-width:1279px){
  .cb-rail-l, .cb-rail-r{ grid-column:text; }
  .cb-rail-l > *, .cb-rail-r > *{ position:static; top:auto; }
}

/* ── READING PROGRESS ────────────────────────────────────────────────────────
   G0's three-colour rule, 3px, fixed to the top edge. Driven by ONE passive
   scroll listener with requestAnimationFrame coalescing.
   ⚠️ A `scroll` LISTENER ONLY, NEVER `resize`. Blog pages are not framed, so
   BX24.fitWindow() is not in the path — but the habit is the invariant, and the
   endless-blink defect it prevents cost this product real customer time. */
.cb-progress{
  position:fixed; inset-block-start:0; inset-inline-start:0;
  height:var(--bw-brand); width:100%; z-index:60;
  transform-origin:left center;
  background:linear-gradient(90deg,
    var(--brand-magenta-deep) 0%, var(--brand-navy) 50%, var(--brand-cyan) 100%);
  will-change:transform;
}
[dir="rtl"] .cb-progress{ transform-origin:right center; }

/* ── THE TABLE OF CONTENTS ───────────────────────────────────────────────────
   ⚠️ IT IS RENDERED FROM body_toc (JSON) AS REAL ANCHORS THAT WORK WITH JS
   OFF. Scroll-spy is a sub-1 KB enhancement that sets aria-current="true", and
   THAT is what this stylesheet selects on — so what a screen reader announces
   and what a sighted reader sees can never disagree. */
.cb-toc{ font-size:var(--t-small); }
.cb-toc__h{
  font-family:var(--font-mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.160em; text-transform:uppercase; color:var(--ink-quiet);
  margin:0 0 var(--s-3);
}
.cb-toc ol{ list-style:none; margin:0; padding:0; }
.cb-toc li{ margin:0; }
.cb-toc a{
  display:block; padding:6px 0 6px var(--s-3);
  border-inline-start:var(--bw-rule) solid var(--edge);
  color:var(--ink-quiet); text-decoration:none; line-height:1.4;
  transition:color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.cb-toc a:hover{ color:var(--ink); border-inline-start-color:var(--inbound-text); }
.cb-toc a[aria-current="true"]{
  color:var(--display); font-weight:600;
  border-inline-start-color:var(--outbound);
}
.cb-toc li[data-level="3"] a{ padding-inline-start:var(--s-6); font-size:var(--t-micro); }

.cb-toc--inline{ margin:0 0 var(--s-8); }
.cb-toc--inline summary{
  cursor:pointer; font-family:var(--font-mono); font-size:var(--t-label);
  font-weight:600; letter-spacing:.160em; text-transform:uppercase;
  color:var(--ink-quiet); padding:var(--s-3) 0;
}
.cb-toc--inline[open] summary{ color:var(--display); }

/* ── PROSE ───────────────────────────────────────────────────────────────────
   ⚠️ SCOPED UNDER .cb-prose AND NOT UNDER .doc. site.css's `.doc article ul`
   sets list-style:none for the legal documents, which is right for them and
   wrong for an article; scoping here means the two cannot fight. */
.cb-prose{
  font-size:var(--t-body-long); line-height:1.78; color:var(--ink);
}
.cb-prose > * + *{ margin-block-start:var(--s-6); }
.cb-prose h2{
  font-family:var(--font-display); font-size:var(--t-h2); font-weight:600;
  line-height:1.18; letter-spacing:-.018em; color:var(--display);
  margin-block-start:var(--s-16); margin-block-end:var(--s-4);
  /* ⚠️ scroll-margin, NOT a padding hack. A ToC anchor lands a heading under
     the sticky header without it, and the reader sees the paragraph BEFORE the
     one they clicked. */
  scroll-margin-block-start:96px;
}
.cb-prose h3{
  font-family:var(--font-display); font-size:var(--t-h3); font-weight:600;
  line-height:1.30; letter-spacing:-.010em; color:var(--display);
  margin-block-start:var(--s-12); margin-block-end:var(--s-3);
  scroll-margin-block-start:96px;
}
.cb-prose h4{
  font-family:var(--font-display); font-size:var(--t-h4); font-weight:600;
  line-height:1.40; letter-spacing:-.006em; color:var(--display);
  margin-block-start:var(--s-10); margin-block-end:var(--s-2);
}
.cb-prose p{ margin:0; }
.cb-prose strong{ font-weight:600; color:var(--display); }
.cb-prose a{
  color:var(--inbound-text); text-decoration:underline;
  text-decoration-thickness:1px; text-underline-offset:.18em;
}
.cb-prose a:hover{ color:var(--outbound); }
.cb-prose ul, .cb-prose ol{ margin:0; padding-inline-start:var(--s-6); }
.cb-prose ul{ list-style:disc; }
.cb-prose ol{ list-style:decimal; }
.cb-prose li + li{ margin-block-start:var(--s-2); }
.cb-prose li::marker{ color:var(--inbound-text); }
.cb-prose blockquote{
  margin:0; padding:var(--s-4) var(--s-6);
  border-inline-start:var(--bw-brand) solid var(--outbound-fill);
  background:var(--bloom-out); border-radius:0 var(--r-md) var(--r-md) 0;
  color:var(--ink); font-size:var(--t-lead); line-height:1.55;
}
.cb-prose blockquote p + p{ margin-block-start:var(--s-3); }
.cb-prose hr{
  border:0; height:var(--bw-hair); background:var(--edge);
  margin-block:var(--s-12);
}
.cb-prose figure{ margin:0; }
.cb-prose figcaption{
  margin-block-start:var(--s-2); font-size:var(--t-micro);
  color:var(--ink-quiet); letter-spacing:.010em;
}

/* ⚠️ WIDTH AND HEIGHT ARE ALWAYS ON THE ELEMENT; `height:auto` HERE IS WHAT
   KEEPS THE ASPECT RATIO WHILE THE ATTRIBUTES RESERVE THE BOX. An image with no
   intrinsic size is layout shift on every article, and CLS is a ranking signal
   we would be paying for. */
.cb-prose img{
  display:block; max-width:100%; height:auto;
  border-radius:var(--r-md); border:var(--bw-hair) solid var(--edge);
  background:var(--surface);
}

/* ── TABLES ──────────────────────────────────────────────────────────────────
   ⚠️ THE WRAPPER SCROLLS, NOT THE PAGE. site.css's own net is
   overflow-x:clip on html+body, which MASKS a horizontal overflow rather than
   fixing it — so a wide table inside prose would be silently truncated. The
   article page wraps every table in .cb-scroll. */
.cb-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch; }
.cb-prose table{
  width:100%; border-collapse:collapse; font-size:var(--t-micro);
  min-width:min(640px,100%);
}
.cb-prose th, .cb-prose td{
  text-align:start; padding:var(--s-3) var(--s-4);
  border-block-end:var(--bw-hair) solid var(--edge); vertical-align:top;
}
.cb-prose thead th{
  font-family:var(--font-display); font-weight:600; color:var(--display);
  background:var(--surface); white-space:nowrap;
}

/* ── CODE ────────────────────────────────────────────────────────────────────
   ⚠️ THE BLOCKS ARE PRE-HIGHLIGHTED ON THE SERVER AND THIS IS ONLY THE THEME.
   highlight.js runs in the publish path, never per request (invariant 34), and
   the only JavaScript on a code block is the copy button. */
.cb-prose code{
  font-family:var(--font-mono); font-size:var(--t-code);
  background:var(--surface); border:var(--bw-hair) solid var(--edge);
  border-radius:var(--r-xs); padding:.1em .34em;
}
.cb-prose pre{
  margin:0; padding:var(--s-5) var(--s-6);
  background:var(--surface); border:var(--bw-hair) solid var(--edge);
  border-radius:var(--r-md); overflow-x:auto;
  -webkit-overflow-scrolling:touch;
}
.cb-prose pre code{
  background:none; border:0; padding:0; font-size:var(--t-code);
  line-height:1.62; color:var(--ink); white-space:pre;
}
.cb-code{ position:relative; }
.cb-code__copy{
  position:absolute; inset-block-start:var(--s-2); inset-inline-end:var(--s-2);
  font-family:var(--font-display); font-size:var(--t-micro); font-weight:600;
  color:var(--ink-quiet); background:var(--ground);
  border:var(--bw-hair) solid var(--edge); border-radius:var(--r-sm);
  padding:4px 10px; cursor:pointer; opacity:0;
  transition:opacity var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.cb-code:hover .cb-code__copy, .cb-code__copy:focus-visible{ opacity:1; }
.cb-code__copy:hover{ color:var(--display); }

/* ── THE SYNTAX THEME ────────────────────────────────────────────────────────
   ⚠️ EVERY INK IS A site.css `-text` TOKEN, MEASURED AGAINST A NEAR-WHITE
   GROUND. The block's ground is --surface (#F3F5F8), and site.css states each of
   these against white and against #EDF1F7 — a DARKER ground than --surface — so
   the figures it states are a floor here rather than a sample:
     keyword  --outbound      7.51 white
     string   --success-text  6.26 white / 5.52 on #EDF1F7
     number   --inbound-text  6.39 white
     comment  --ink-quiet     7.12 worst case
     literal  --warn-text     7.27 white / 6.41 on #EDF1F7
     title    --display      19.30 white
   ⚠️ NONE OF THEM IS A `-fill` TOKEN. --success-fill is 3.84 on #EDF1F7 and
   --warn-fill 3.52: both fail AA for text, and both fail INVISIBLY — which is
   the defect shape site.css records twice.
   ⚠️ AND NO ITALIC ON A COMMENT. Cairo's italic is synthesised, and a slanted
   synthetic face in a monospace block is unreadable at --t-code. */
.hljs-comment, .hljs-quote{ color:var(--ink-quiet); }
.hljs-keyword, .hljs-selector-tag, .hljs-subst, .hljs-doctag, .hljs-section{
  color:var(--outbound); font-weight:600;
}
.hljs-string, .hljs-regexp, .hljs-addition, .hljs-meta-string{ color:var(--success-text); }
.hljs-number, .hljs-literal, .hljs-symbol, .hljs-bullet{ color:var(--inbound-text); }
.hljs-title, .hljs-name, .hljs-selector-id, .hljs-selector-class, .hljs-type, .hljs-class{
  color:var(--display); font-weight:600;
}
.hljs-attr, .hljs-attribute, .hljs-variable, .hljs-template-variable, .hljs-property{
  color:var(--warn-text);
}
.hljs-built_in, .hljs-builtin-name, .hljs-meta, .hljs-tag{ color:var(--warn-text); }
.hljs-deletion{ color:var(--danger-text); }
.hljs-emphasis{ font-style:normal; text-decoration:underline; }
.hljs-strong{ font-weight:600; }
.hljs-link{ color:var(--inbound-text); text-decoration:underline; }

/* ── THE FOUR CALLOUTS ───────────────────────────────────────────────────────
   ⚠️ EACH CLASS IS A COMPLETE LITERAL STRING, chosen by our own code from the
   container name in blog-render.ts and NEVER from author input. That is the
   whole reason markdown-it-attrs is not a dependency: it lets an author write
   {.anything}, which means the sanitiser must allow arbitrary class values,
   which means an author typo can break the layout and an author can style a
   block to impersonate a form.

   ⚠️ A HUE IS NEVER THE ONLY SIGNAL. Each carries a glyph (an inline SVG in a
   ::before mask) and a bold label word, so the four are distinguishable in
   monochrome, in forced-colours mode and to a reader who cannot see hue. The
   accessibility floor this site holds itself to is not satisfied by colour. */
.cb-callout{
  margin:0; padding:var(--s-5) var(--s-6) var(--s-5) var(--s-12);
  border-radius:var(--r-md); position:relative;
  font-size:var(--t-body); line-height:1.70; color:var(--ink);
  border:var(--bw-hair) solid var(--edge);
}
.cb-callout > * + *{ margin-block-start:var(--s-3); }
.cb-callout::before{
  content:""; position:absolute;
  inset-block-start:calc(var(--s-5) + 3px); inset-inline-start:var(--s-5);
  width:20px; height:20px;
  background-color:currentColor;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-size:contain; mask-size:contain;
}
.cb-callout::after{
  content:""; position:absolute;
  inset-block:0; inset-inline-start:0;
  width:var(--bw-brand); border-radius:var(--r-md) 0 0 var(--r-md);
  background:currentColor;
}

/* tip — the text-safe cyan */
.cb-callout--tip{ background:var(--bloom-in); color:var(--inbound-text); }
.cb-callout--tip::before{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 2a6 6 0 0 0-3 11.2V15a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-1.8A6 6 0 0 0 10 2Zm-2 16h4v1H8v-1Z'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 2a6 6 0 0 0-3 11.2V15a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-1.8A6 6 0 0 0 10 2Zm-2 16h4v1H8v-1Z'/%3E%3C/svg%3E");
}
/* warning — the warn ink, 7.27 on white */
.cb-callout--warning{ background:var(--warn-tint); color:var(--warn-text); }
.cb-callout--warning::before{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 1.6 19 18H1L10 1.6Zm-1 5.6v5.3h2V7.2H9Zm0 6.8v2h2v-2H9Z'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 1.6 19 18H1L10 1.6Zm-1 5.6v5.3h2V7.2H9Zm0 6.8v2h2v-2H9Z'/%3E%3C/svg%3E");
}
/* callout — the navy bridge, for a neutral aside */
.cb-callout--callout{ background:var(--bloom-bridge); color:var(--display); }
.cb-callout--callout::before{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 1a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm-1 4h2v2H9V5Zm0 4h2v6H9V9Z'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 1a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm-1 4h2v2H9V5Zm0 4h2v6H9V9Z'/%3E%3C/svg%3E");
}
/* cta — the text-safe magenta */
.cb-callout--cta{ background:var(--bloom-out); color:var(--outbound); }
.cb-callout--cta::before{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M3 4h14v9H8l-5 4V4Z'/%3E%3C/svg%3E");
  mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M3 4h14v9H8l-5 4V4Z'/%3E%3C/svg%3E");
}
/* ⚠️ THE BODY TEXT INSIDE A CALLOUT IS --ink AND NOT THE ACCENT. The accent is
   the rule, the glyph and strong text only; a whole paragraph in --warn-text is
   a paragraph a reader has to work at. */
.cb-callout p, .cb-callout li{ color:var(--ink); }
.cb-callout strong{ color:inherit; font-weight:600; }

/* ⚠️ FORCED COLOURS: a mask-painted glyph and a tint ground both disappear, so
   the rule becomes a real border and the glyph is hidden rather than left as an
   invisible 20px hole the label then sits beside. */
@media (forced-colors:active){
  .cb-callout{ border:1px solid CanvasText; background:Canvas; color:CanvasText; }
  .cb-callout::before{ display:none; }
  .cb-callout::after{ background:CanvasText; }
  .cb-callout{ padding-inline-start:var(--s-6); }
}

/* ── ARTICLE HEAD AND META ───────────────────────────────────────────────── */
.cb-head{ margin-block-end:var(--s-10); }
.cb-head h1{
  font-family:var(--font-display); font-size:var(--t-h1); font-weight:600;
  line-height:1.08; letter-spacing:-.024em; color:var(--display);
  margin:0 0 var(--s-4);
}
.cb-head__lead{
  font-size:var(--t-lead); line-height:1.55; color:var(--ink-quiet); margin:0;
  max-width:60ch;
}
.cb-meta{
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2) var(--s-4);
  font-size:var(--t-micro); color:var(--ink-quiet); letter-spacing:.010em;
  margin-block-start:var(--s-5);
}
.cb-meta a{ color:var(--inbound-text); }
.cb-meta__dot{ color:var(--edge); }
.cb-eyebrow{
  font-family:var(--font-mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.160em; text-transform:uppercase; color:var(--outbound);
  margin:0 0 var(--s-3); display:block; text-decoration:none;
}
.cb-eyebrow:hover{ color:var(--display); }

.cb-hero{ margin-block-end:var(--s-10); }
.cb-hero img{
  display:block; width:100%; height:auto;
  border-radius:var(--r-lg); border:var(--bw-hair) solid var(--edge);
  background:var(--surface);
}
.cb-hero figcaption{
  margin-block-start:var(--s-2); font-size:var(--t-micro); color:var(--ink-quiet);
}

/* ── CARDS ───────────────────────────────────────────────────────────────── */
.cb-grid{
  display:grid; gap:var(--gap-deck);
  grid-template-columns:repeat(3,minmax(0,1fr));
}
@media (max-width:1000px){ .cb-grid{ grid-template-columns:repeat(2,minmax(0,1fr)) } }
@media (max-width:660px){ .cb-grid{ grid-template-columns:1fr } }

.cb-card{
  display:flex; flex-direction:column; gap:var(--s-3);
  padding:var(--s-6); border-radius:var(--r-lg);
  background:var(--ground); border:var(--bw-hair) solid var(--edge);
  box-shadow:var(--shadow-card);
  text-decoration:none; color:inherit; position:relative;
  transition:box-shadow var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.cb-card:hover{ box-shadow:var(--shadow-card-hover); transform:translateY(-2px); }
/* ⚠️ A 2px RULE AND NOT A SHADOW ALONE. WCAG 1.4.11 wants a non-text contrast
   indicator, and a drop shadow is not one. site.css's .panel::after is the
   same device, which is why this reads as part of the same system. */
.cb-card::after{
  content:""; position:absolute; inset-block-end:0; inset-inline:0;
  height:var(--bw-rule); border-radius:0 0 var(--r-lg) var(--r-lg);
  background:linear-gradient(90deg,var(--brand-magenta-deep),var(--brand-cyan));
  opacity:0; transition:opacity var(--dur-2) var(--ease-out);
}
.cb-card:hover::after, .cb-card:focus-visible::after{ opacity:1; }
.cb-card__cat{
  font-family:var(--font-mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.160em; text-transform:uppercase; color:var(--outbound);
}
.cb-card__h{
  font-family:var(--font-display); font-size:var(--t-h4); font-weight:600;
  line-height:1.35; letter-spacing:-.006em; color:var(--display); margin:0;
}
.cb-card__p{ font-size:var(--t-small); line-height:1.60; color:var(--ink); margin:0; }
.cb-card__meta{
  margin-block-start:auto; font-size:var(--t-micro); color:var(--ink-quiet);
  letter-spacing:.010em; display:flex; gap:var(--s-2); align-items:center;
}
.cb-card__img{
  display:block; width:100%; height:auto; aspect-ratio:16/9; object-fit:cover;
  border-radius:var(--r-md); border:var(--bw-hair) solid var(--edge);
  background:var(--surface);
}
.cb-card--lead{ grid-column:1 / -1; }
@media (min-width:1001px){
  .cb-card--lead{ flex-direction:row; align-items:center; gap:var(--s-8); }
  .cb-card--lead .cb-card__img{ width:46%; flex:0 0 46%; }
  .cb-card--lead .cb-card__body{ display:flex; flex-direction:column; gap:var(--s-3); }
  .cb-card--lead .cb-card__h{ font-size:var(--t-h2); line-height:1.18; letter-spacing:-.018em; }
}

/* ── CATEGORY RAIL, PAGINATION, SEARCH ───────────────────────────────────── */
.cb-cats{
  display:flex; flex-wrap:wrap; gap:var(--s-2);
  margin-block-end:var(--s-8); padding:0; list-style:none;
}
.cb-cats a{
  display:inline-block; padding:6px 14px; border-radius:var(--r-full);
  font-family:var(--font-display); font-size:var(--t-small); font-weight:600;
  color:var(--ink); background:var(--surface);
  border:var(--bw-hair) solid var(--edge); text-decoration:none;
  transition:color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.cb-cats a:hover{ color:var(--display); border-color:var(--inbound-text); }
.cb-cats a[aria-current="page"]{
  color:var(--accent-ink); background:var(--outbound-fill);
  border-color:var(--outbound-fill);
}

/* ⚠️ REAL <a href> PREVIOUS/NEXT LINKS, NEVER A JS-ONLY "LOAD MORE". Infinite
   scroll alone is a discovery failure: every page of the sequence must exist at
   a real URL with real links, or the posts past the first screen are
   undiscoverable. */
.cb-pager{
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--s-4); margin-block-start:var(--s-16);
  padding-block-start:var(--s-6); border-block-start:var(--bw-hair) solid var(--edge);
  font-family:var(--font-display); font-size:var(--t-ui);
}
.cb-pager a{ color:var(--inbound-text); text-decoration:none; font-weight:600; }
.cb-pager a:hover{ color:var(--outbound); }
.cb-pager__at{ color:var(--ink-quiet); font-size:var(--t-small); }

.cb-search{ display:flex; gap:var(--s-2); margin-block-end:var(--s-8); }
.cb-search input{
  flex:1 1 auto; min-width:0; font:inherit; font-size:var(--t-ui);
  padding:10px var(--s-4); color:var(--ink); background:var(--ground);
  border:var(--bw-hair) solid var(--edge); border-radius:var(--r-sm);
}
.cb-search input:focus-visible{
  outline:var(--bw-focus) solid var(--focus); outline-offset:2px; border-radius:2px;
}

/* ── SERIES, RELATED, AUTHOR, SHARE, TAGS, FAQ ───────────────────────────── */
/* ⚠️ SERIES NAVIGATION IS THE SINGLE BIGGEST INTERNAL-LINKING WIN AVAILABLE
   HERE AND NO MEASURED COMPETITOR HAS IT. It is given a real band rather than a
   footnote for that reason. */
.cb-series{
  margin-block:var(--s-10); padding:var(--s-6);
  border-radius:var(--r-lg); background:var(--bloom-bridge);
  border:var(--bw-hair) solid var(--edge);
}
.cb-series__h{
  font-family:var(--font-mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.160em; text-transform:uppercase; color:var(--ink-quiet);
  margin:0 0 var(--s-3);
}
.cb-series ol{ margin:0; padding-inline-start:var(--s-6); font-size:var(--t-small); }
.cb-series li + li{ margin-block-start:var(--s-2); }
.cb-series a{ color:var(--inbound-text); }
.cb-series li[aria-current="page"]{ color:var(--display); font-weight:600; }
.cb-series li[aria-current="page"] span{ color:var(--display); }
.cb-series__nav{
  display:flex; justify-content:space-between; gap:var(--s-4);
  margin-block-start:var(--s-4); font-size:var(--t-small);
}

.cb-related{ margin-block-start:var(--s-20); }
.cb-related > h2{
  font-family:var(--font-display); font-size:var(--t-h3); font-weight:600;
  color:var(--display); margin:0 0 var(--s-6);
}

.cb-author{
  display:flex; gap:var(--s-4); align-items:flex-start;
  padding:var(--s-6); border-radius:var(--r-lg);
  background:var(--surface); border:var(--bw-hair) solid var(--edge);
  margin-block-start:var(--s-12);
}
.cb-author img{
  width:56px; height:56px; border-radius:var(--r-full); flex:0 0 56px;
  background:var(--ground); border:var(--bw-hair) solid var(--edge);
}
.cb-author__n{
  font-family:var(--font-display); font-size:var(--t-h4); font-weight:600;
  color:var(--display); margin:0;
}
.cb-author__r{ font-size:var(--t-micro); color:var(--ink-quiet); margin:2px 0 var(--s-2); }
.cb-author__b{ font-size:var(--t-small); line-height:1.60; color:var(--ink); margin:0; }
.cb-author__b a{ color:var(--inbound-text); }

/* ⚠️ PLAIN <a> LINKS AND A COPY BUTTON. No third-party share widget, no SDK, no
   pixel — the blog is the surface most likely to erode the zero-third-party
   invariant, and a share button is the usual way it happens. */
.cb-share{ display:flex; flex-wrap:wrap; gap:var(--s-2); font-size:var(--t-micro); }
.cb-share__h{
  font-family:var(--font-mono); font-size:var(--t-label); font-weight:600;
  letter-spacing:.160em; text-transform:uppercase; color:var(--ink-quiet);
  margin:0 0 var(--s-3); width:100%;
}
.cb-share a, .cb-share button{
  font-family:var(--font-display); font-size:var(--t-micro); font-weight:600;
  color:var(--ink); background:var(--ground);
  border:var(--bw-hair) solid var(--edge); border-radius:var(--r-sm);
  padding:6px 12px; text-decoration:none; cursor:pointer;
}
.cb-share a:hover, .cb-share button:hover{ color:var(--display); border-color:var(--inbound-text); }

/* ⚠️ TAGS RENDER AS PLAIN TEXT AND NOT AS LINKS. There is no tag route, by
   construction: tags generate an unbounded set of near-duplicate list pages
   holding one to three posts each, and both measured competitors fight exactly
   that after the fact with robots.txt lines. */
.cb-tags{
  display:flex; flex-wrap:wrap; gap:var(--s-2); margin-block-start:var(--s-8);
  padding:0; list-style:none; font-size:var(--t-micro); color:var(--ink-quiet);
}
.cb-tags li{
  padding:3px 10px; border-radius:var(--r-full);
  background:var(--surface); border:var(--bw-hair) solid var(--edge);
}

/* ⚠️ VISIBLE <details> PROSE AND NO FAQPage MARKUP. FAQ rich results stopped
   appearing in Google Search on 7 May 2026; the Q&A still earns
   People-Also-Ask and AI-overview surfacing AS TEXT, at zero markup cost. */
.cb-faq{ margin-block-start:var(--s-16); }
.cb-faq > h2{
  font-family:var(--font-display); font-size:var(--t-h3); font-weight:600;
  color:var(--display); margin:0 0 var(--s-5);
}
.cb-faq details{
  border-block-end:var(--bw-hair) solid var(--edge); padding:var(--s-4) 0;
}
.cb-faq summary{
  cursor:pointer; font-family:var(--font-display); font-size:var(--t-ui);
  font-weight:600; color:var(--display); list-style:none;
}
.cb-faq summary::-webkit-details-marker{ display:none; }
.cb-faq summary::before{ content:"+ "; color:var(--outbound); }
.cb-faq details[open] summary::before{ content:"\2212 "; }
.cb-faq p{ margin:var(--s-3) 0 0; font-size:var(--t-body); line-height:1.70; color:var(--ink); }

/* ── STATES ──────────────────────────────────────────────────────────────── */
.cb-empty{
  padding:var(--s-16) var(--s-6); text-align:center;
  color:var(--ink-quiet); font-size:var(--t-lead);
}
.cb-notice{
  padding:var(--s-4) var(--s-6); border-radius:var(--r-md);
  background:var(--warn-tint); color:var(--warn-text);
  border:var(--bw-hair) solid var(--edge);
  font-family:var(--font-display); font-size:var(--t-small); font-weight:600;
  margin-block-end:var(--s-8);
}
.cb-updated{
  font-size:var(--t-micro); color:var(--ink-quiet);
  margin-block-start:var(--s-12); padding-block-start:var(--s-4);
  border-block-start:var(--bw-hair) solid var(--edge);
}

/* ── MOTION ──────────────────────────────────────────────────────────────────
   ⚠️ .001ms AND NOT `none`. site.css's own banner records why: an element
   parked mid-reveal with animation:none keeps its "from" state and becomes
   invisible. */
@media (prefers-reduced-motion:reduce){
  .cb-card, .cb-card::after, .cb-toc a, .cb-code__copy, .cb-progress{
    transition-duration:.001ms !important;
    animation-duration:.001ms !important;
  }
  .cb-card:hover{ transform:none; }
}

/* ── PRINT ───────────────────────────────────────────────────────────────────
   An article is a thing people print and send. The rails, the progress bar and
   every interactive affordance go; the prose and the code stay. */
@media print{
  .cb-progress, .cb-rail-l, .cb-rail-r, .cb-share, .cb-code__copy,
  .cb-cats, .cb-pager, .cb-search, .cb-related{ display:none !important; }
  .cb-prose pre, .cb-prose code{ background:none; border:1px solid #999; }
  .cb-callout{ border:1px solid #999; background:none; }
  .cb-callout::before{ display:none; }
  .cb-prose a::after{ content:" (" attr(href) ")"; font-size:.85em; color:#555; }
}
