/*
   THE BLOG'S OWN STYLESHEET — the page chrome every blog surface shares, the entry
   list the index and the tag pages are built from, and the article body, install panel
   and draft banner that only a post page renders.

   CONTENTS, in source order, WITH THE SURFACE EACH BLOCK SERVES. ⚠ A BLOCK THAT SHIPS
   AHEAD OF THE PAGE THAT CONSUMES IT must be marked NO CONSUMER YET in this list, and
   the mark removed by whoever writes that page. This list is also the answer to "where
   do my rules go" for whichever blog surface you are adding: here, beside the block
   they belong with.

     the html/body overrides ..... every blog page
     .site, .col ................. every blog page
     .kicker, .h-display ......... index and tag pages
     .h-title .................... post pages — the post's own title, one step down
     .standfirst ................. index and tag pages
     .rule3 ...................... every page with a header — all three close it
     .back, .back:hover,
       .back.after ............... post pages, above and below the article; tag pages
                                   emit the first of the two, above the list
     .chips, .chip ............... index, tag and post pages
     .chip.active ................ tag pages only, via _BlogEntryListPartial — the one
                                   tag the reader is already on, frozen
     .chip.draft ................. index and tag pages, via _BlogEntryListPartial; a
                                   post page's own draft state is the .draftbar below
     .entry, .entry-meta,
       .entry-title, .entry-desc . index and tag pages, via _BlogEntryListPartial
     .post-body <element> ........ post pages — ELEMENT selectors throughout, which is
                                   the design and not a shortcut; see that block
     .filed, .filed .lbl ......... post pages — the "Filed under" row under the article
     .cta, .cta-kicker, .cta-h,
       .cta-p, .badges, .badge,
       .badge-solo ............... post pages — the install panel, emitted by
                                   Pages/Blog/Post.cshtml as <section class="cta"> holding
                                   the kicker, the heading, the supporting line and one
                                   <a class="badge badge-solo"> inside .badges. ⚠ .badge
                                   HAS A SECOND CONSUMER, WITHOUT .badge-solo: the
                                   not-found panel's single action
     .panel-msg .................. the index's empty state, and the not-found page
     .err-chip, .nf-actions ...... THE NOT-FOUND PAGE ONLY, via _BlogNotFoundPartial
     .draftbar ................... post pages — a post's OWN draft state
     .draftnote .................. THE INDEX ONLY — a post page's draft state is the
                                   .draftbar above and is not styled by this rule
     …:focus-visible ............. every blog page — ONE grouped rule carrying every
                                   focus ring this sheet owns; it names five selectors
                                   and the block lists which markup emits each
     @media (max-width: 820px) ... every blog page

   ⚠ LOAD ORDER IS styles.css → site-chrome.css → THIS FILE, and the last position is
   what the html/body overrides below depend on. The first two are linked by
   _SiteHeadPartial; THIS ONE IS LINKED BY THE PAGE, on its own <link> line
   immediately after it renders that partial. That split is deliberate and is
   explained at the <link> pair in _SiteHeadPartial: a page stylesheet hardcoded into
   the shared head is what would stop the first non-blog page adopting it.

   NO CUSTOM PROPERTY IS DECLARED HERE — no colour token and no type token. The
   palette is declared once, in styles.css's two :root blocks, and the type scale once,
   in site-chrome.css; this file reads both through var() and declares neither. A second
   declaration site is free to drift from the first, which is the failure
   Copilot/memories/site-v2-milestone-resync-workflow.md exists to prevent.
   BlogCssTests.DeclaresNoDesignTokenOfItsOwn pins exactly that — no `--*` declaration.

   ⚠ IT IS NOT LITERAL-FREE, AND EVERY EXCEPTION IS THE SAME ONE: `color: #fff` on a
   filled element — .chip.active, .chip.draft, .cta-kicker, .badge, .err-chip, .draftbar
   and .post-body pre. The palette declares no white, so there is no token to read and
   these are the mockup's literal carried across rather than a token someone forgot to
   use. Nothing else in this file is a literal colour: if you are about to write a hex
   that is not #fff, the value belongs in styles.css's palette or does not belong here.

   NO !important ANYWHERE IN THIS FILE, for the same reason site-chrome.css states at
   length: every declaration this sheet competes with is a NORMAL declaration, so
   source order and specificity settle it and an !important would hide the question
   rather than answer it.

   LIFTED FROM THE MOCKUP, WITH FOUR NAMED CHANGES. Copilot/projects/website_blog's
   mocks file is the design of record and every colour, size, shadow and border below
   is copied from it. What changed, and nothing else did:
     1. Its .mob class — which fakes a 390px column inside a desktop page — became
        real @media (max-width: 820px) rules, in the block at the foot of the file.
     2. Its .body-* classes become .post-body descendant selectors, because Markdig
        emits classless HTML. That block carries the three consequences the swap has
        beyond the rename — the ul rule reaching <ol>, the inline-code rule reaching
        into <pre>, and the image placeholder becoming a real replaced element — each
        written up where it sits.
     3. Its two state-SIMULATOR classes became real pseudo-classes: .entry.hov is
        .entry:hover here, widened with :focus-within so a keyboard reader gets the
        same row treatment a pointer does. (The other one, .ronav-cta.foc, belongs to
        the nav bar and was converted in site-chrome.css.) ⚠ .chip.active below is
        NOT one of these — it is a real class, set on the chip for the tag page the
        reader is already on, and it ships as the mockup wrote it.
     4. Its harness classes are dropped: the dashed token annotations, the share-card
        specimens, the legend, the contents list and the section headings. None is
        part of the design; the share-card rules exist to illustrate an off-site
        surface this site never renders. ⚠ ONE RULE IS HALF HARNESS: .figure .img is a
        div standing in for an image, in a file that loads none. Its border and shadow
        are the design and are on .post-body img; its fixed height, hatched fill and
        centred label are the stand-in and are gone, along with the narrow-viewport
        height that only the stand-in needed.
*/

/* ─────────────────────────────────────────────────────────────────────────────
   THE OVERRIDES: FOUR DECLARATIONS, IN TWO RULESETS HERE, ANSWERING THREE RULESETS IN
   styles.css. A blog page loads styles.css for its colour tokens and gets the comic's
   page treatment along with them. Count DECLARATIONS rather than rules — the mapping is
   not one-to-one, and one declaration below answers two rules above:

     styles.css html { background: var(--ink) }  → html { background: var(--paper) }

     styles.css body { background: var(--ink) }  ┐ ONE declaration answers BOTH. The
     styles.css body { background:               │ second body rule is declared far later
       repeating-linear-gradient(…), var(--ink) }┘ in the file and is the one actually in
                                                   effect: a 1px pinstripe over --ink.
                                                 → body { background: var(--paper) }

     styles.css body { color: var(--ink) }       → body { color: var(--ink) }
     styles.css body { font-family: Inter, … }   → body { font-family: var(--font-body) }
       a stack that always falls through, because nothing loads Inter

   Without the two backgrounds the blog is near-black text on a near-black page; without
   the font-family it is the system sans. `color` is the odd one out — it restates
   styles.css's own value rather than changing it, so it does nothing TODAY. It is kept
   because a re-sync that changed the marketing body colour would otherwise change the
   blog's, silently.

   🚨 THE BACKGROUND MUST STAY THE `background` SHORTHAND. The layered rule sets a
   background-IMAGE, and only the shorthand resets it; a tidier-looking
   `background-color: var(--paper)` leaves the gradient painting a dark pinstripe over
   the blog's paper.

   ⚠ THIS IS AN ACCOUNT OF WHAT IS OVERRIDDEN, NOT OF EVERYTHING styles.css DOES TO A
   BLOG PAGE. Four more of its declarations reach one and are DELIBERATELY KEPT, because
   each is wanted here or harmless: `html { scroll-behavior: smooth }` and
   `body { margin: 0; overflow-x: hidden }` from the two rulesets above, plus
   `* { box-sizing: border-box }` from a universal rule of its own. THE FIFTH IS NOW LIVE
   AND IS ANSWERED ELSEWHERE IN THIS FILE: styles.css styles bare `blockquote` — a clamp
   up to 25px at weight 900, for the comic's pull-quotes — and a Markdig-rendered quote
   in a post body is a bare blockquote. It is overridden at .post-body blockquote, in the
   post-body block, rather than here, because it is a post-body rule and not a page-level
   one; the counter-declarations are marked as such where they sit.

   Those five are the whole list, and it was MEASURED rather than reasoned about: bare
   element selectors in styles.css are exactly html, body and blockquote, plus the
   universal `*`. Everything else in that file is .comic / .movement / .panel / .p##
   scoped and reaches nothing a blog page emits. site-chrome.css, the sheet between the
   two, has no bare element selector at all.

   Losing either background, or the font-family, yields a page that is unreadable rather
   than subtly wrong, and the likeliest way to lose one is a future re-sync of styles.css
   from the marketing milestone. BlogCssTests asserts all four declarations, and
   separately asserts the styles.css side still exists to be overridden.
   ───────────────────────────────────────────────────────────────────────────── */
html {
  background: var(--paper);
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE DOCUMENT WRAPPER. Every blog page wraps its whole document in
   <div class="site">, and that wrapper is load-bearing beyond this rule: it is what
   opts the page into site-chrome.css's re-stated `.site .footer`, instead of the
   overlay footer styles.css draws for the comic page. Drop the wrapper and the
   footer silently pulls itself 54px up over the end of the article.
   ───────────────────────────────────────────────────────────────────────────── */
.site {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--type-body);
  line-height: 1.7;
}

/* The reading column. --measure is 68ch, declared in site-chrome.css: styles.css's
   --max is the comic grid and is far too wide for prose to be read in. */
.col {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 40px 28px 46px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE HEADER BLOCK: kicker, display heading, post title, standfirst, and the 3px rule
   that closes it. The index and the tag pages open with .h-display; a post page opens
   with .h-title instead, one step down the scale.
   ───────────────────────────────────────────────────────────────────────────── */
.kicker {
  margin: 0 0 8px;
  color: var(--night);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .14em;
  line-height: 1;
  text-transform: uppercase;
}

.h-display {
  margin: 0 0 12px;
  font-family: var(--font-heading);
  font-size: var(--type-display);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -.015em;
}

/* The POST's title, as the document's h1. One step below .h-display — --type-title
   rather than --type-display — because a post page's heading sits above an article
   rather than above a catalog, and 2px more bottom margin because what follows it is a
   chip row rather than a standfirst. Wraps freely: there is no clamp, no ellipsis and no
   shrink-to-fit, and the tightened line-height is the only concession a very long title
   gets. An h1 with NO rule is not a small regression — it is the UA's 2em bold heading
   in the body face, which is why this ships with the block rather than after it. */
.h-title {
  margin: 0 0 14px;
  font-family: var(--font-heading);
  font-size: var(--type-title);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -.015em;
}

.standfirst {
  margin: 0 0 22px;
  color: var(--slate);
  font-size: var(--type-body);
}

/* An <hr>, stripped to one line. The UA draws it as an inset groove with a margin of
   its own, so height, margin and border are all zeroed and a single border-top is put
   back — that is what makes it one flat 3px ink rule rather than a bevelled one. */
.rule3 {
  height: 0;
  margin: 0 0 4px;
  border: 0;
  border-top: 3px solid var(--ink);
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE BACK LINK — "← All posts", above and below the article on a post page and
   above the list on a tag page. The `.after` variant is the second one, which needs
   a top margin the first does not.

   The post page emits both — one above the title, one after the install panel — and the
   tag page emits the first, above its list. ⚠ EMPTYING ONE OF THESE THREE RULES SHOWS UP
   NOWHERE BUT ON SCREEN: the markup emits .back and .back.after whatever these rules
   declare, so a rule left empty still matches its element and still renders — as a link
   in default UA blue, mid-page, on one blog surface and not the others.
   (The only near miss on the name elsewhere is the marketing page's unrelated
   .back-cover-title, which is a different class and reaches nothing here.)
   ───────────────────────────────────────────────────────────────────────────── */
.back {
  display: inline-block;
  margin: 0 0 20px;
  color: var(--night);
  font-size: var(--type-caption);
  font-weight: 600;
  text-decoration: none;
}

.back:hover {
  text-decoration: underline;
}

.back.after {
  margin: 30px 0 0;
}

/* ─────────────────────────────────────────────────────────────────────────────
   TAG CHIPS. The base chip is a link; both modifiers below are compounded onto it
   and never written alone, so each keeps the pill shape, the 2px ink border and the
   uppercase treatment and changes only its fill.
   ───────────────────────────────────────────────────────────────────────────── */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  display: inline-block;
  padding: 4px 11px 5px;
  border: 2px solid var(--ink);
  border-radius: 999px;
  background: var(--cloud);
  color: var(--night);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  line-height: 1.4;
  text-decoration: none;
  text-transform: uppercase;
}

/* The tag the reader is already on, on every row of a tag page's entry list: filled
   rather than outlined, and rendered as a span rather than a link, since it would
   link to the page it is on. A real class, NOT a mockup state simulator — the entry
   row's hover fake two blocks down is the thing that had to become a pseudo-class, and
   BlogCssTests.KeepsTheFrozenActiveChipAsAClass pins the difference.

   _BlogEntryListPartial emits it when its model names an active tag, which only a tag
   page does; the index passes none, so no chip on the index can take this fill. */
.chip.active {
  background: var(--night);
  color: #fff;
}

/* The DRAFT marker, first in an entry's chip row. It can only ever render where the
   catalog is showing drafts at all, which is never in production. */
.chip.draft {
  background: var(--pink);
  color: #fff;
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE ENTRY LIST — the index and the tag pages, through _BlogEntryListPartial.

   HAIRLINE-SEPARATED ROWS, NOT CARDS. A twenty-item stack of heavy bordered panels
   is unreadable; the calm list is the register this audience reads in, and the
   family resemblance to the marketing page is carried by the type, the ink rules and
   the chips instead.

   The negative margin plus matching padding is what lets the hover band bleed 12px
   wider than the column without the text moving: the row's text starts where the
   column's does, and only the band extends past it.
   ───────────────────────────────────────────────────────────────────────────── */
.entry {
  padding: 24px 12px 26px;
  margin: 0 -12px;
  border-bottom: 2px solid var(--ink-soft);
}

/* :focus-within alongside :hover, so tabbing to the title lights the whole row the
   same way pointing at it does — the mockup could only draw the pointer half.
   Written as two selectors rather than one :is(), which buys nothing here and is the
   sort of tidy that quietly changes specificity. */
.entry:hover,
.entry:focus-within {
  background: var(--cloud);
}

.entry-meta {
  margin: 0 0 9px;
  color: var(--slate);
  font-size: var(--type-caption);
  font-weight: 600;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
}

/* display:block so the whole line is the link target, not just the glyphs — the row
   is the tap target, and on a narrow screen a two-line title with an inline box
   leaves dead space beside the shorter line. */
.entry-title {
  display: block;
  margin: 0 0 9px;
  color: var(--ink);
  font-family: var(--font-heading);
  font-size: var(--type-subtitle);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -.01em;
  text-decoration: none;
}

.entry:hover .entry-title,
.entry:focus-within .entry-title {
  text-decoration: underline;
  text-decoration-color: var(--blue);
}

.entry-desc {
  margin: 0;
  color: var(--slate);
}


/* ─────────────────────────────────────────────────────────────────────────────
   THE POST BODY — ELEMENT SELECTORS THROUGHOUT, AND THAT IS THE DESIGN.

   Markdig renders a post's Markdown to classless HTML: bare <p>, <h2>, <a>, <ul>,
   <blockquote>, <figure>, <pre>, <code>, <hr>. The mockup hand-authored .body-p,
   .body-h2, .body-h3, .body-a, .body-ul, .body-q, .body-pre, .code-i and .body-hr
   because a static file has no Markdown renderer. Each becomes a descendant of the
   <div class="post-body"> wrapper here with its declarations lifted unchanged; that is
   adaptation 2 of the four, and the three places where the swap needs MORE than a
   rename are marked below.

   🚨 DO NOT ADD A CLASS SELECTOR TO THIS BLOCK. A post body cannot carry a class at all:
   Services/Blog/BlogHtml.cs allows exactly href, src, alt, title, colspan, rowspan and
   style on any element, deliberately, so that a post cannot borrow .draftbar or
   .chip.active and impersonate a state of the page. A class selector here would match
   nothing a post can emit and would be silently dead rule.

   THE ELEMENTS AND THE SANITISER'S ALLOW-LIST ARE WRITTEN AGAINST EACH OTHER, which
   BlogHtml says at the allow-list itself: an element is allowed if Markdig emits it OR
   this sheet styles it. It is an OR, so some allowed elements have no rule here on
   purpose:
     em, strong, del, br — the UA already renders each as the design asks.
     h4, h5, h6 ........ NO RULE. They take the body size and weight. The design of
                         record draws h2 and h3 only, and inventing two more heading
                         steps is design this file is not allowed to do.
     table and its five
       child elements .. NO RULE, and this one is worth knowing before you meet it: a
                         pipe table in a post renders with the browser's default table
                         styling, unbordered and cramped beside everything around it.
                         Neither the mockup nor UXD §4.4's element table draws one, so
                         there is nothing to lift. That is a gap in the DESIGN, not an
                         omission here, and it wants a design answer rather than a
                         plausible-looking invention.
   ───────────────────────────────────────────────────────────────────────────── */
.post-body p {
  margin: 0 0 1.15em;
}

.post-body h2 {
  margin: 2em 0 .5em;
  font-family: var(--font-heading);
  font-size: var(--type-subtitle);
  font-weight: 600;
  line-height: 1.15;
}

.post-body h3 {
  margin: 1.6em 0 .4em;
  font-family: var(--font-heading);
  font-size: var(--type-body);
  font-weight: 600;
  letter-spacing: .01em;
}

/* --night, not --blue, and ALWAYS underlined: colour alone is not a link affordance, and
   --blue does not carry body text at this size (UXD §8.1's contrast finding). */
.post-body a {
  color: var(--night);
  text-decoration: underline;
}

/* ⚠ ONE RULE FOR BOTH LIST TYPES, which the rename alone would not have given. The
   mockup wrote .body-ul and put it on a <ul> because that is what its specimen held;
   UXD §4.4 gives bullets and numbers one treatment in one row. Markdig emits <ol> from
   "1." and an ordered list would otherwise lose the indent and the marker colour. */
.post-body ul,
.post-body ol {
  margin: 0 0 1.15em;
  padding-left: 1.25em;
}

.post-body li {
  margin: 0 0 .4em;
}

/* The marker only — ::marker accepts colour and font properties, nothing else. */
.post-body li::marker {
  color: var(--slate);
}

/* ─────────────────────────────────────────────────────────────────────────────
   🚨 THE BLOCKQUOTE IS THE ONE POST-BODY ELEMENT styles.css ALREADY STYLES — the fifth
   unscoped rule the overrides banner at the top of this file tracks, and the only one
   answered down here rather than up there.

   WHAT IT DOES, read off that file rather than inferred: `blockquote` at styles.css:428
   sets margin 7px 0 0, font-size clamp(15px, 1.42vw, 25px), font-weight 900 and
   line-height 1.04 — the comic's panel pull-quote — and its own 820px query re-sets the
   font-size at :977. Every blog page loads that sheet, so a Markdig-rendered quote
   arrives already wearing it: a 25px, weight-900, line-height-1.04 slab in the middle of
   the prose.

   FOUR declarations below are the mockup's .body-q, verbatim. THREE ARE COUNTER-
   DECLARATIONS and are marked. The mockup has no blockquote rule to fight, so its quote
   simply inherits the column's size, weight and leading — `inherit` and `normal` restore
   exactly that, which is why not one of the three is a number of its own. A number here
   would be a second opinion about the body type.

   SPECIFICITY, ONCE: 0,1,1 against 0,0,1 at both of its sites, and a media query adds
   none, so this single unmediaed rule beats the 820px one too. No !important, for the
   reason the file header gives.
   ───────────────────────────────────────────────────────────────────────────── */
.post-body blockquote {
  margin: 1.4em 0;
  padding: 2px 0 2px 1.25em;
  border-left: 4px solid var(--blue-bright);
  color: var(--slate);
  font-size: inherit;     /* counter-declaration — styles.css's bare blockquote */
  font-weight: normal;    /* counter-declaration — styles.css's bare blockquote */
  line-height: inherit;   /* counter-declaration — styles.css's bare blockquote */
}

.post-body blockquote p {
  margin: 0;
}

/* IMAGES, full reading-column width with the panel treatment and no tilt (UXD §4.4).
   ⚠ THE MOCKUP'S RULE IS HALF STAND-IN: .figure .img is a div posing as an image in a
   file that loads none, so its 190px height, hatched fill and centred label are harness
   and only the border and shadow are design. The three layout declarations are what make
   "full column" true of a REPLACED element — a div is block and full-width already, an
   <img> is inline and intrinsically sized — and height:auto is not decoration: width
   alone holds the intrinsic height and renders the picture squashed. */
.post-body img {
  display: block;
  width: 100%;
  height: auto;
  border: 3px solid var(--ink);
  box-shadow: 5px 5px 0 var(--ink);
}

/* <figure>/<figcaption> are the raw-HTML affordance the sanitiser keeps for a captioned
   image. Markdig's own ![alt](src) emits a bare <img> inside a <p> and takes the rule
   above on its own, with no figure involved. */
.post-body figure {
  margin: 1.6em 0;
}

.post-body figcaption {
  margin: 12px 0 0;
  color: var(--slate);
  font-size: var(--type-caption);
}

.post-body pre {
  margin: 1.5em 0;
  padding: 14px 16px;
  border: 3px solid var(--ink);
  background: var(--slate-dark);
  color: #fff;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.6;
  overflow-x: auto;
}

/* INLINE code — the mockup's .code-i. */
.post-body code {
  padding: 1px 5px;
  background: var(--cloud);
  color: var(--ink);
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: .88em;
}

/* 🚨 AND THE RESET THAT MAKES THE RULE ABOVE SAFE — the second thing adaptation 2 costs
   beyond a rename. The mockup keeps the two treatments apart BY HAND: .code-i goes on
   inline code, and its <pre class="body-pre"> holds raw text with no <code> inside.
   Markdig cannot do that — a fenced block is emitted as <pre><code>…</code></pre> — so
   without this the inline treatment lands INSIDE the dark block and paints a pale cloud
   box with ink-coloured text over it, at .88em of an already-13px font. Every value here
   is `inherit` or zero because this restores what the mockup renders; nothing in it is a
   new number. Specificity 0,1,2 beats the 0,1,1 above, so source order is not what
   settles it. */
.post-body pre code {
  padding: 0;
  background: none;
  color: inherit;
  font-size: inherit;
}

.post-body hr {
  height: 0;
  margin: 2em 0;
  border: 0;
  border-top: 2px solid var(--ink-soft);
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE "FILED UNDER" ROW — label and chips, between the article and the install panel.
   The chips are the shared .chip rules above; only the label and the block's top margin
   are new. On a post with no tags the whole row is absent rather than empty, which is a
   page decision and not one this sheet can express.
   ───────────────────────────────────────────────────────────────────────────── */
.filed {
  margin: 34px 0 0;
}

.filed .lbl {
  margin: 0 0 9px;
  color: var(--slate);
  font-size: var(--type-caption);
  font-weight: 600;
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE INSTALL PANEL — POST PAGES ONLY, and mandatory on every one of them. The index and
   the tag pages ask through the nav bar's "Get the app" instead: the panel is the ask a
   reader earns by finishing an article.

   ONE BUTTON, NOT A BADGE ROW, and .badges is still a flex row. The device-detecting
   store redirect picks the store AFTER the click, so there is a single
   <a class="badge badge-solo"> inside it; the row survives because the row is what the
   design centres the button with, not because a second button is expected.
   ───────────────────────────────────────────────────────────────────────────── */
.cta {
  margin: 40px 0 0;
  padding: 28px 24px 30px;
  border: 4px solid var(--ink);
  background: var(--cloud);
  box-shadow: 5px 5px 0 var(--ink);
  text-align: center;
}

.cta-kicker {
  display: inline-block;
  margin: 0 0 14px;
  padding: 4px 9px 5px;
  background: var(--night);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .14em;
  line-height: 1;
  text-transform: uppercase;
}

.cta-h {
  margin: 0 0 8px;
  color: var(--night);
  font-family: var(--font-heading);
  font-size: var(--type-subtitle);
  font-weight: 600;
  line-height: 1.15;
}

.cta-p {
  margin: 0 0 20px;
  color: var(--slate);
  font-size: var(--type-caption);
}

.badges {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   🚨 .badge IS DECLARED HERE ON PURPOSE, AND styles.css's .store-badge WAS THE REAL
   ALTERNATIVE. Written down because the two look interchangeable, because a blog page
   loads that sheet so reuse would have cost nothing, and because getting it wrong later
   is a silent restyle of the one button on the site that exists to be clicked.

   WHAT IS ACTUALLY IN THE OTHER FILE, read off it rather than assumed — .store-badge is
   declared FOUR times: :867 (padding 12px 17px, white fill, ink text, 3px ink border,
   3px 3px ink shadow, a transition), :878–880 (hover and :focus-visible translate it 2px
   into a 1px 1px INK shadow, plus a 3px gold focus ring), :1376 (transition:none under a
   reduced-motion query) and :2007 (padding 14px 21px, --night fill, white text,
   clamp(12px,1vw,16px), 5px 5px 0 --gold). Reusing the name would have brought the hover
   and the gold focus ring with it, which UXD §4.1 does call part of the treatment the
   blog reuses. That is the case FOR it, and it is a real one.

   IT IS STILL THE WRONG TRADE HERE, for three reasons that outlast it:
     1. THE BUTTON WOULD BE HALF IN EACH FILE. The blog's install button is
        `badge badge-solo`, and no .store-badge-solo exists anywhere — the solo padding,
        size and tracking would have to live here regardless. Splitting one button across
        a sheet this project owns and a sheet it must not edit is worse than either whole
        answer.
     2. styles.css IS RE-SYNCED WHOLESALE from the marketing milestone, and every
        correction of ours goes to port-fixes.css, which blog pages do not load. Adopting
        a COMPONENT rule from it makes the blog's call to action restyleable with no blog
        file edited and nothing here going red. Adopting its TOKENS is the opposite trade
        and is deliberate — one palette, one declaration site. That is the line: this
        sheet reads VALUES from that file, never behaviour.
     3. THE VALUES ARE NOT THE SAME ONES. The mockup's button is 13px/800 with .04em
        tracking and nowrap; .store-badge computes to clamp(12px,1vw,16px)/950 with none
        of those. And its resting shadow comes from the back-cover art-direction block at
        :2007 while its hover shadow comes from :878, so hovering it swaps the shadow from
        gold to ink — a cascade artefact of that page's history, not a design the blog
        asked for.

   THE COST OF KEEPING THE NAME: `badge` is also Bootstrap's. Inert here, and measured
   rather than assumed — wwwroot/scripts/bootstrap/ does exist and Pages/Shared/_Layout.cshtml
   links it on line 13 — but no blog surface goes anywhere near it: each is a complete
   document that renders with no layout (the pages set Layout = null, and a partial runs no
   _ViewStart to set one), linking exactly styles.css, site-chrome.css and this file. A blog
   document that ever renders inside _Layout would collide; that is the condition to re-open
   this under, and nothing short of it is.

   ⚠ NO HOVER RULE, and that is still not an oversight. The mockup draws none for this
   button, and UXD §4.1's hover is a description of .store-badge's, which is the artefact
   above. Adding one would be a fifth adaptation, so it is raised as a question rather
   than answered here.

   ⚠ THE FOCUS RING IS A SEPARATE QUESTION AND IS ANSWERED. UXD §4.1 asks for one on every
   focusable element on the blog, which is a stated requirement rather than a lift from the
   mockup — so .badge takes the shared gold ring at the foot of this file, alongside .back,
   .chip, .entry-title and .post-body a, none of which had one either until it was written.
   ───────────────────────────────────────────────────────────────────────────── */
.badge {
  padding: 13px 20px;
  border: 3px solid var(--ink);
  background: var(--night);
  box-shadow: 5px 5px 0 var(--gold);
  color: #fff;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .04em;
  text-decoration: none;
  white-space: nowrap;
}

/* The solo variant, compounded onto .badge and never written alone: same shape, more of
   it, because it is the only button in the panel rather than one of a row. ⚠ THE
   NOT-FOUND PANEL'S ACTION IS THE BASE .badge WITHOUT IT — one button there too, but the
   smaller one, since a dead link is not a conversion moment. */
.badge-solo {
  padding: 16px 34px;
  font-size: 15px;
  letter-spacing: .06em;
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE MESSAGE PANEL. The index's empty state on launch day, and the blog's
   not-found surface. Bordered and shadowed so that an index with no posts reads as a
   deliberate page rather than as a blank area or a broken layout — the empty state
   and a failure must never look alike.
   ───────────────────────────────────────────────────────────────────────────── */
.panel-msg {
  margin: 26px 0 0;
  padding: 34px 26px;
  border: 4px solid var(--ink);
  background: var(--paper);
  box-shadow: 5px 5px 0 var(--ink);
}

.panel-msg .h {
  margin: 0 0 10px;
  font-family: var(--font-heading);
  font-size: var(--type-title);
  font-weight: 600;
  line-height: 1.15;
}

.panel-msg .p {
  margin: 0;
  color: var(--slate);
}

/* The 404 stamp above the panel's heading — one of only two pink things on the blog, the
   other being the draft treatments below. Pink is the "this is not an ordinary page"
   colour and is spent nowhere else.

   ⚠ THE PANEL AND ITS TWO PIECES ARE THE WHOLE NOT-FOUND SURFACE, and the miss must look
   the same whatever caused it: a typo, an unknown tag, or a draft post's real URL in
   production all render this markup. Nothing here may vary by cause. */
.err-chip {
  display: inline-block;
  margin: 0 0 14px;
  padding: 4px 10px 5px;
  background: var(--pink);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .14em;
  line-height: 1;
}

/* The single action under the panel's copy — one .badge, no search box and no suggested
   posts. The spacing is the whole rule; the button is the .badge above. */
.nf-actions {
  margin: 24px 0 0;
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE TWO DRAFT TREATMENTS, WHICH ARE NOT THE SAME ELEMENT AND ARE EASY TO CONFLATE.
   .draftbar is a POST's own banner, loud and pink, spanning the reading column above the
   article; .draftnote is a quiet line under the INDEX's header explaining why unpublished
   posts are in the list at all. Different surface, different register, on purpose: a
   reader of a draft post needs to know the page they are reading is not public, and a
   reader of the index needs an orientation note rather than a warning.

   Both are unreachable in production: BlogCatalog gates drafts on !IsProduction(), which
   fails closed — an unset ASPNETCORE_ENVIRONMENT defaults to Production and hides them.
   So this is author-facing styling, on a host that is not the live one.
   ───────────────────────────────────────────────────────────────────────────── */
.draftbar {
  margin: 0 0 26px;
  padding: 11px 14px;
  border: 3px solid var(--ink);
  background: var(--pink);
  color: #fff;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .1em;
  line-height: 1.4;
  text-transform: uppercase;
}

/* The second line, which says where else the post is invisible. Lower case and lighter,
   because the banner above it has already done the shouting. */
.draftbar small {
  display: block;
  margin-top: 5px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: none;
}

/* The one line under the header that explains why unpublished posts are on screen.
   Deliberately quiet: it is an author's orientation note, not a warning. THE INDEX
   ONLY — a post page's own draft state is a different element and is not this rule. */
.draftnote {
  margin: 0 0 20px;
  color: var(--slate);
  font-size: var(--type-caption);
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE FOCUS RINGS — ONE RULE, EVERY FOCUSABLE THING THIS SHEET STYLES.

   UXD §4.1 lists "gold focus rings — --gold outline, 3px, offset" among the treatments
   the blog reuses from the marketing page, and says where they appear: EVERY focusable
   element on the blog. That is a stated requirement rather than something lifted from the
   mockup — a static file cannot draw focus, which is why the mockup shows none of this
   and why nothing in the blocks above carried one.

   WHAT IS FOCUSABLE HERE, and where the markup that emits it lives. Everything on the
   list is an <a href>; the blog emits no button and no tabindex of its own.
     .back .......... the "← All posts" link: above and below a post (Post.cshtml), and
                      above the list on a tag page (Tag.cshtml). The .after variant is the
                      post page's second anchor, lower down, and needs no ring of its own
                      — it is .back too, so this rule already reaches it.
     .chip .......... a tag link, in an entry's meta row and in a post's two chip rows.
                      ⚠ NOT .chip.active or .chip.draft: both are <span>, not links, and a
                      span with no tabindex cannot be focused.
     .entry-title ... the index row's link, and the one that matters most — the index IS a
                      list of these, so a keyboard reader with no ring has no position on
                      the page at all.
     .post-body a ... a link the AUTHOR wrote, rendered by Markdig. An element selector,
                      like everything else in the post-body block and for the same reason
                      stated there: a post body can carry no class.
     .badge ......... two buttons, both live: the install panel's, as `badge badge-solo`
                      (Post.cshtml), and the not-found panel's single action, as the base
                      .badge. One ring reaches both, because coverage is per ELEMENT and an
                      element is ringed by any of its classes — which is why .badge-solo
                      needs no row of its own. The banner at .badge says why this is a ring
                      of ours rather than .store-badge's.

   THE TREATMENT: 3px solid --gold at 3px offset, which is UXD §4.1's row verbatim and the
   same shape site-chrome.css writes for .ronav-cta — ⚠ THE SHAPE, NOT THE COLOUR. That
   ring is 3px WHITE (site-chrome.css, .ronav-cta:focus-visible) and is UXD §4.2's
   deliberate exception, because that button is itself gold and gold on gold is not a state
   change. Nothing this file styles is gold-filled, so nothing here takes the exception.
   ⚠ .skip, the other focusable thing in the chrome, has NO outline at all and is not a
   third shape to copy: it is off-screen until focused and its whole focus treatment is
   arriving on the page (site-chrome.css, .skip:focus).

   ⚠ :focus-visible, NEVER :focus — the same adaptation the entry row's :focus-within is,
   and made for the sharper reason. A pointer click on a link matches :focus in the major
   engines, so a bare :focus here would leave a gold ring sitting on whatever the reader
   last clicked, on a page that is mostly links. :focus-visible is the keyboard's half of
   :focus, which is exactly the audience the requirement is about.

   ⚠ .skip IS THE ONE DELIBERATE EXCEPTION TO THAT, AND IT TAKES BOTH. site-chrome.css
   writes it as .skip:focus, .skip:focus-visible — not an oversight and not a leftover.
   A skip link has to become visible whenever focus reaches it, INCLUDING when focus was
   moved programmatically rather than by the Tab key; :focus-visible is a heuristic and
   the engines do not agree on that case, so a skip link on :focus-visible alone can be
   focused and still off-screen, which is the one failure the element exists to prevent.
   It costs nothing here because .skip is invisible until focused, so the "ring parked on
   whatever was last clicked" problem above cannot arise for it.

   ⚠ THE FOOTER IS THE OTHER HALF OF THE REQUIREMENT AND IS NOT ON THE LIST ABOVE. A blog
   page's footer comes from _SiteFooterPartial, whose Privacy / TOS / Contact anchors carry
   no class and whose "Resources" placeholder is a <span tabindex="0">; its rules are
   site-chrome.css's scoped `.site .footer` re-statement, so its rings are written there —
   same 3px --gold at 3px offset, stated once in each file and compared across the two so
   neither can drift alone. THIS FILE OWNS THE FIVE SELECTORS BELOW; site-chrome.css OWNS
   EVERYTHING INSIDE THE FOOTER. The nav bar and the skip link are site-chrome.css's too.

   NO SPECIFICITY FIGHT, AND THAT WAS MEASURED RATHER THAN ASSUMED. Nothing in the two
   sheets loaded before this one declares `outline` on any selector this file also styles:
   styles.css declares it on .prologue-cta, .store-badge, .footer-coming-soon, .companion-cta
   and .panel.action, site-chrome.css on .ronav-cta and on two footer-scoped selectors
   (`.site .footer a`, `.site .footer .footer-coming-soon`), and blog.css declares not one
   of those class names. ⚠ THE FOOTER ANCHOR RULE IS THE ONE TO CHECK rather than wave
   past, because it ends in a TAG: it is scoped inside `.site .footer` and nothing this
   sheet styles renders there, so it reaches no blog link. A bare element selector is the
   only other way a rule in either sheet could reach a blog page: styles.css has exactly
   html, body, blockquote and the universal *, and site-chrome.css has none at all. So a
   pseudo-class alone settles it: 0,2,0 on the four class rows and 0,2,1 on the post-body
   row, against nothing. No !important, for the reason the file header gives.

   🚨 AND IT IS PINNED, because arithmetic in a comment is exactly what a wholesale
   styles.css re-sync from the marketing milestone falsifies without anything in this
   project being edited — a new .chip or .back rule arriving there with an outline would
   make the paragraph above quietly wrong and cost a focus ring nobody would miss.
   BlogCssTests.NoSheetLoadedBeforeThisOneDeclaresAnOutlineOnAnythingBlogCssStyles
   recomputes the intersection from all three files, and checks the footer-anchor
   exception rather than waving past it: an outline rule whose rightmost compound names
   no class must be scoped inside `.site .footer`.

   BlogCssTests.EveryFocusableElementTheBlogEmitsHasAFocusRing derives the list above from
   the blog's own markup rather than trusting it, so a sixth focusable element added without
   a ring goes red;
   SiteChromeCssChromeRuleTests.EveryFocusableElementTheSharedFooterEmitsHasAFocusRing does
   the same job for the footer, off the partial's markup.
   ───────────────────────────────────────────────────────────────────────────── */
.back:focus-visible,
.chip:focus-visible,
.entry-title:focus-visible,
.post-body a:focus-visible,
.badge:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   NARROW VIEWPORTS. One breakpoint, 820px — the same one styles.css, port-fixes.css
   and site-chrome.css's --nav-h step all use. Nothing collapses, nothing reorders,
   nothing wraps: the column tightens and the type steps down.

   These are the mockup's .mob rules, which faked a 390px column with a class so both
   widths could sit in one static file. Each one is reproduced here against the real
   viewport instead.

   ⚠ ONLY FIVE OF THE TWELVE font-size SELECTORS BELOW CHANGE ANYTHING, and they come in
   two kinds. TEN restate the FLOOR of a clamp in site-chrome.css's type scale, and a
   clamp already sitting on its floor cannot be stepped down — so whether one of those
   does work is decided by the width at which its vw term passes that floor.

   ⚠ COUNT SELECTORS, NOT TABLE ROWS: the clamp-backed table below has NINE rows for TEN
   selectors, because `.site, .standfirst` is one declaration written on two of them (‡).
   Add the two plain-literal badge rows and it is twelve selectors from eleven
   declarations. BlogCssTests counts selectors, which is the finer of the two and the one
   the arithmetic runs over.

     .h-display        30px  over clamp(30px, 5vw,   40px)   5vw   passes 30px at  600px
     .h-title          26px  over clamp(26px, 4vw,   32px)   4vw   passes 26px at  650px
     .panel-msg .h     26px  over clamp(26px, 4vw,   32px)   4vw   passes 26px at  650px
  ‡  .site,.standfirst 17px  over clamp(17px, 1.4vw, 18px)   1.4vw passes 17px at ~1214px
     .back             13px  over clamp(13px, 1vw,   14px)   1vw   passes 13px at  1300px
     .entry-meta       13px  over clamp(13px, 1vw,   14px)   1vw   passes 13px at  1300px
     .entry-title      22px  over clamp(22px, 2.6vw, 26px)   2.6vw passes 22px at  ~846px
     .post-body h2     22px  over clamp(22px, 2.6vw, 26px)   2.6vw passes 22px at  ~846px
     .cta-h            22px  over clamp(22px, 2.6vw, 26px)   2.6vw passes 22px at  ~846px

   The other TWO step a plain PIXEL LITERAL down to a smaller literal, so there is no
   clamp and no arithmetic: each differs at every width the query matches.

     .badge            12px  over 13px
     .badge-solo       14px  over 15px

   This query matches at 820px and below, so seven of the ten clamp-backed selectors never differ
   from their clamp at any width they can apply to: exact no-ops, kept because the mockup
   wrote them, not because they do anything. THE FIVE THAT WORK are .h-display above
   600px, .h-title and .panel-msg .h above 650px, and the two badge rows at every width —
   delete the first three and a tablet gets a headline of up to 40px and a title or panel
   heading of up to 32px. ⚠ THE CLAMP-BACKED THREE DO NOTHING ON A PHONE: below 600px
   every clamp here is already on its floor, so a narrow screen is not the width that
   justifies any of them. The badge rows are the opposite and are the only two that a
   phone actually sees.

   🚨 DO NOT HAND-DERIVE A WIDTH FROM THIS TABLE, and do not use "differs from the floor"
   as the test — every clamp-backed row restates its floor, so that separates nothing.
   The property is whether the vw term can climb above the floor at a width the query
   still matches, and
   BlogCssTests.OnlyFiveNarrowFontSizesAreLoadBearingAndAllOfThemAreComputed recomputes
   every row of this from the two files, in both kinds, and prints what it found.
   ───────────────────────────────────────────────────────────────────────────── */
@media (max-width: 820px) {
  .col {
    padding: 26px 20px 34px;
  }

  .h-display {
    font-size: 30px;
  }

  .h-title {
    font-size: 26px;
  }

  .site,
  .standfirst {
    font-size: 17px;
  }

  .back {
    font-size: 13px;
  }

  .entry-meta {
    font-size: 13px;
  }

  .entry-title {
    font-size: 22px;
  }

  .post-body h2 {
    font-size: 22px;
  }

  /* ⚠ NO NARROW RULE FOR .post-body img. The mockup carries one — .mob .figure .img
     drops the stand-in's height from 190px to 140px — and it went with the stand-in:
     a real image is sized by its own aspect ratio at every width, so there is no
     height here to step down. */

  .cta {
    padding: 22px 16px 24px;
    box-shadow: 4px 4px 0 var(--ink);
  }

  .cta-h {
    font-size: 22px;
  }

  .badge {
    padding: 11px 15px;
    font-size: 12px;
    box-shadow: 4px 4px 0 var(--gold);
  }

  .badge-solo {
    padding: 14px 26px;
    font-size: 14px;
  }

  .panel-msg {
    padding: 24px 18px;
    box-shadow: 4px 4px 0 var(--ink);
  }

  .panel-msg .h {
    font-size: 26px;
  }
}
