/* delist.ai — the reading system.
   Everything an article, guide, hub, comparison or utility page needs. The
   landing has display type; this file is what a 2,000-word page is set in.

   THE ONE RULE: prose is set on --read (30px), furniture on --leading (28px).
   Every vertical value below is a multiple or half of --read, so a paragraph, a
   list, a table and a callout all sit on the same baseline no matter the order
   an author puts them in.

   CONTRAST (measured on --color-surface #fbfaf3, the ivory sheet):
     body / headings   --color-text            11.2:1   AAA
     links             --color-accent-700       8.6:1   AAA
     muted, meta, capt --color-text @ 72%       4.9:1   AA  ← never go lighter
     accent-2 numerals --color-accent-2-800     8.1:1   AAA
   No text style on any content page may resolve below 4.5:1. 72% is the floor and
   the only muted value: 68% measures 4.37:1 and fails, and a second passing step
   would differ from this one by 0.3:1 — invisible, and one more number to police.
   The steps at 55%, 58%, 62%, 68% and 70% no longer appear in this package. */

/* — the measure, and how to escape it —
   One grid, three widths. Body copy sits in `text`; tables and wide figures take
   `wide`; only a full-bleed band takes `full`. An author who needs a wide table
   adds one class, and the paragraph after it returns to the measure by itself. */
/* The text track is min(100%, --measure), NOT minmax(0, --measure). A minmax
   track with a 0 min is the track the sizing algorithm gives away first, so the
   two gutters grew to their 120px limit and the measure was squeezed to ~55ch
   between 900px and 1250px — most laptop traffic, and the one number this whole
   file is built around. A fixed-size track is sized before the flexible ones, so
   the gutters now collapse to protect the measure instead of eating it. */
.prose {
  display: grid;
  grid-template-columns:
    [full-start] minmax(0, 1fr)
    [wide-start] minmax(0, 120px)
    [text-start] min(100%, var(--measure))
    [text-end] minmax(0, 120px)
    [wide-end] minmax(0, 1fr)
    [full-end];
}
.prose > * { grid-column: text; }
.prose > .wide { grid-column: wide; }
.prose > .full { grid-column: full; }

/* — headings —
   h1 and h2 are Playfair. h3 and h4 are DM Sans semibold, and that is a
   deliberate break: at reading sizes the serif stops being display type and
   starts competing with the body copy it is meant to organise. The switch from
   serif to sans is a cleaner level break than another two points of size, and it
   holds up in a 12-minute page with forty headings in it. */
.prose h1, .prose h2 { font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  color: var(--color-text); text-wrap: balance; }
.prose h1 { font-size: clamp(34px, 4.6vw, 54px); line-height: 1.12; margin: 0 0 var(--read); }
.prose h2 { font-size: clamp(26px, 2.9vw, 34px); line-height: 1.26;
  margin: calc(2 * var(--read)) 0 calc(0.5 * var(--read)); }
.prose h3 { font-family: var(--font-body); font-weight: 600; font-size: 20px; line-height: 28px;
  margin: calc(1.5 * var(--read)) 0 calc(0.4 * var(--read)); }
.prose h4 { font-family: var(--font-body); font-weight: 700; font-size: 16px; line-height: 26px;
  margin: var(--read) 0 calc(0.25 * var(--read)); }
/* the h1→h2→h3 outline is load-bearing for search; do not restyle a heading into
   looking like a different level, and do not skip a level to get a size */

/* — body — */
.prose p, .prose li { font-size: 18px; line-height: var(--read); }
.prose p { margin: 0 0 var(--read); }
.prose > p.lead { font-size: 21px; line-height: 33px;
  color: color-mix(in srgb, var(--color-text) 88%, transparent);
  margin-bottom: calc(1.25 * var(--read)); }
.prose strong { font-weight: 600; }
.prose em { font-style: italic; }

/* — lists —
   Markers in accent-2 so a long procedural page has a colour that means "step"
   rather than "link". Nested lists step down a size; three levels is the limit. */
.prose ul, .prose ol { margin: 0 0 var(--read); padding-left: 0; list-style: none; }
.prose li { position: relative; padding-left: 30px; margin-bottom: calc(0.4 * var(--read)); }
.prose ul > li::before { content: ""; position: absolute; left: 8px; top: calc((var(--read) - 7px) / 2);
  width: 7px; height: 7px; border-radius: 999px; background: var(--color-accent-2-500); }
.prose ol { counter-reset: step; }
.prose ol > li { counter-increment: step; }
.prose ol > li::before { content: counter(step) "."; position: absolute; left: 0; top: 0;
  width: 22px; text-align: right; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--color-accent-2-700); }
.prose li > ul, .prose li > ol { margin: calc(0.4 * var(--read)) 0 0; }
.prose li li { font-size: 17px; }

/* — quote — */
.prose blockquote { margin: calc(1.5 * var(--read)) 0; padding: 0 0 0 24px;
  border-left: 3px solid var(--color-accent-2-300);
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 23px; line-height: calc(1.2 * var(--read)); }
.prose blockquote p { font: inherit; margin: 0; }

/* — table —
   Takes `wide` by default because the brief is right: these tables do not want to
   be narrow. Below 720px it scrolls horizontally inside its own frame rather than
   shrinking type — a 12px table is not a readable table. */
.prose .tablewrap { margin: calc(1.5 * var(--read)) 0; overflow-x: auto;
  border: 1px solid var(--color-divider); border-radius: 18px; background: var(--color-neutral-100); }
.prose table { width: 100%; border-collapse: collapse; font-size: 15px; line-height: 24px; }
.prose caption { caption-side: top; text-align: left; padding: 16px 20px 0;
  font-size: 14px; color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.prose th, .prose td { padding: 12px 20px; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--color-divider); }
.prose thead th { font-size: 12px; letter-spacing: 0.07em; text-transform: uppercase; font-weight: 700;
  color: color-mix(in srgb, var(--color-text) 72%, transparent);
  background: var(--color-neutral-200); white-space: nowrap; }
.prose tbody tr:last-child th, .prose tbody tr:last-child td { border-bottom: 0; }
.prose tbody th { font-weight: 600; }

/* — inline code and legal snippets —

   neutral-300, not neutral-200. #f2f1e7 against the page ground #efeee3 is a 1%
   difference: on a card it works, because the card is #fbfaf3, but .article prose
   sits directly on the page, so the chip was invisible on every article that has
   ever rendered one. #e2e1d4 is 4% darker than the ground, reads on both grounds,
   and holds accent-800 at 8.9:1.

   Found by the --ds-paper-2 census asking what inline code is made of. */
.prose code { font: 500 15px/1.4 ui-monospace, "SF Mono", Menlo, monospace;
  background: var(--color-neutral-300); border-radius: 6px; padding: 2px 6px;
  color: var(--color-accent-800); word-break: break-word; }
.prose pre { margin: calc(1.25 * var(--read)) 0; padding: 20px 24px; overflow-x: auto;
  background: var(--color-neutral-200); border-radius: 18px; }
.prose pre code { background: none; padding: 0; font-size: 14.5px; line-height: 24px; display: block; }

.prose hr { border: 0; border-top: 1px solid var(--color-divider); margin: calc(2 * var(--read)) 0; }
.prose figure { margin: calc(1.5 * var(--read)) 0; }
.prose figure img { width: 100%; height: auto; display: block; border-radius: 18px; }
.prose figcaption { font-size: 14px; line-height: 22px; margin-top: 10px;
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }

/* ─────────────────────────────────────────────────────────────────────
   Content furniture — the components an article is actually made of
   ───────────────────────────────────────────────────────────────────── */

/* ── CMS article meta ─────────────────────────────────────────────────────────
   The server renderer (src/content-render.js) used inline styles for these
   three, which is the one thing a page on this system may not do -- and no test
   could see it, because the conformance spec reads files and these pages render
   from the database at request time. --ink-3 was also a token from the previous
   design that this sheet does not define, so the colour was resolving to
   nothing. */

/* — breadcrumbs — real links, always in the initial HTML — */
.crumbs { font-size: 14px; line-height: 22px; margin: 0 0 var(--read);
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: 6px 10px; margin: 0; padding: 0; }
.prose .crumbs li { position: relative; padding: 0; margin: 0; font-size: 14px; }
.prose .crumbs li::before { display: none; content: none; }
.prose .crumbs li + li::before { content: "›"; display: inline; position: static;
  width: auto; margin-right: 10px; background: none; color: inherit; opacity: 0.6; }
.crumbs a { color: inherit; }
.crumbs [aria-current="page"] { color: var(--color-text); }

/* — the article head: title, byline, the two dates people actually check — */
.arthead { margin-bottom: calc(1.5 * var(--read)); }
.arthead .meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px;
  font-size: 14.5px; line-height: 22px; margin: 0;
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.arthead .meta .dot { width: 4px; height: 4px; border-radius: 999px;
  background: currentColor; opacity: 0.5; }

/* — At a glance —
   The single most valuable block on a search-entry page: the answer, before the
   article. Sage ground so it reads as the page's own summary rather than an ad. */
.glance { background: var(--color-accent-2-100); border-radius: 24px;
  padding: calc(0.9 * var(--read)) clamp(20px, 3vw, 32px);
  margin: 0 0 calc(1.5 * var(--read)); }
.glance h2 { font-family: var(--font-body); font-weight: 700; font-size: 12px; line-height: 18px;
  letter-spacing: 0.08em; text-transform: uppercase; margin: 0 0 calc(0.5 * var(--read));
  color: var(--color-accent-2-800); }
.glance ul { margin: 0; }

.prose .glance li { font-size: 16.5px; line-height: 26px; margin-bottom: 10px; padding-left: 26px; }
.prose .glance li:last-child { margin-bottom: 0; }
.prose .glance li::before { background: var(--color-accent-2-600); top: 9.5px; }

/* — Contents —
   In the flow, in the initial HTML, never behind a disclosure. Every entry is a
   real anchor to a real h2, so it is also an internal-linking structure. */
.contents { border: 1px solid var(--color-divider); border-radius: 24px;
  padding: calc(0.8 * var(--read)) clamp(18px, 2.5vw, 28px);
  margin: 0 0 calc(1.5 * var(--read)); }
.contents h2 { font-family: var(--font-body); font-weight: 700; font-size: 12px; line-height: 18px;
  letter-spacing: 0.08em; text-transform: uppercase; margin: 0 0 12px;
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }
/* NOTE the .prose prefix on every furniture marker below. The generic
   .prose ul/ol > li::before rules are (0,1,3) and reach into any list inside the
   prose grid, including these blocks — an unprefixed .contents li::before is
   (0,1,2) and loses silently, which is how the Contents index spent a build
   rendering as an ordinary body list. Prefixing with .prose makes it (0,2,2) and
   the class count wins. Any new furniture with its own list markers must do the
   same, and must restate counter-increment for the same reason. */
.prose .contents ol { margin: 0; display: grid; gap: 8px; counter-reset: toc; }
.prose .contents li { padding-left: 26px; margin: 0; font-size: 16px; line-height: 25px;
  counter-increment: toc; }
.prose .contents li::before { content: counter(toc); left: 0; top: 0; width: 18px; text-align: right;
  font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; background: none;
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }
/* The entry, not the text, is the target. .prose .contents li is 16px/25px, so a
   colour-only anchor gave a 30px row on 151 pages — nine of them per page, in the
   one component every guide reader uses first.

   Padding on the <a>, not the <li>, and the <a> goes block: the counter marker is
   an ::before on the li and stays where it is. 25px line + 10px + 10px = 45px, and
   the negative inline margin keeps the text optically flush with the marker column
   while the hit area extends past it on both sides.

   Rows sit flush against each other, so the ol gap drops to 0 — 8px of gap between
   45px targets is a stack of near-touching hit areas with no visible seam. The
   hover tint is what separates them now, and it is the affordance the plain link
   never had. */
.prose .contents ol { gap: 0; }
.contents a { display: block; box-sizing: border-box; min-height: 44px;
  padding: 10px 12px; margin: 0 -12px; border-radius: 10px;
  color: var(--color-text); text-decoration: none; }
.contents a:hover { color: var(--color-accent-700);
  background: color-mix(in srgb, var(--color-accent) 9%, transparent); }
.contents a:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }

/* — callout — advice, warnings, and the one thing not to do — */
.callout { border-radius: 24px; padding: calc(0.8 * var(--read)) clamp(18px, 2.5vw, 28px);
  margin: calc(1.25 * var(--read)) 0; background: var(--color-neutral-200);
  border-left: 4px solid var(--color-neutral-400); }
.callout > :last-child { margin-bottom: 0; }
.callout p, .callout li { font-size: 17px; line-height: 27px; }
.callout .clabel { display: block; font-family: var(--font-body); font-weight: 700;
  font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 8px;
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.callout-tip { background: var(--color-accent-100); border-left-color: var(--color-accent-400); }
.callout-tip .clabel { color: var(--color-accent-800); }
.callout-warn { background: #f7ece4; border-left-color: #c8763f; }
.callout-warn .clabel { color: #8a4a1c; }

/* — inline CTA —
   Mid-article, and it is a link, not a form. A capture field inside an article
   competes with the reading; the scan lives one click away and the click is the
   qualifying step. */
.ctablock { background: var(--color-accent-2-100); border-radius: 24px;
  padding: calc(0.9 * var(--read)) clamp(20px, 3vw, 32px);
  margin: calc(1.5 * var(--read)) 0; display: grid; gap: 12px; justify-items: start; }
.ctablock p { margin: 0; font-size: 17px; line-height: 27px; max-width: 52ch; }
.ctablock .ctatitle { font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 24px; line-height: 32px; margin: 0; }
.ctablock .btn { text-decoration: none; }

/* — FAQ —
   Plain headings and paragraphs in the initial HTML. Not an accordion: the
   answers are the page's most-quoted text and hiding them behind a click removes
   them from the initial render. If a future build wants collapse, it must ship
   open by default and collapse only after hydration. */
.faq { margin-top: calc(1.5 * var(--read)); }
.faq h3 { margin-top: calc(1.25 * var(--read)); }
.faq h3:first-of-type { margin-top: 0; }

/* — related reading — */
.related { margin-top: calc(2 * var(--read)); padding-top: calc(1.5 * var(--read));
  border-top: 1px solid var(--color-divider); }
.related h2 { font-family: var(--font-body); font-weight: 700; font-size: 12px; line-height: 18px;
  letter-spacing: 0.08em; text-transform: uppercase; margin: 0 0 var(--read);
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.related ul { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 14px; margin: 0; }
.prose .related li { padding: 0; margin: 0; }
.prose .related li::before { display: none; content: none; }

/* Same treatment for .linkgrid, and for the same reason the .contents note
   above gives. Design round 24 supplied these and corrected our reading of the
   mechanism, which is worth keeping because it decides WHERE the rule lives:

     .prose li              { padding-left: 30px }    (0,1,1)  line 75
     .linkgrid li           { margin: 0; padding: 0 } (0,1,1)  content-blocks.css:109
     .prose ul > li::before { absolute; left: 8px }   (0,1,3)  line 76

   The first two are EQUAL specificity, so blocks-loads-after-prose decides it and
   .linkgrid wins the indent. Nothing .linkgrid can say outbids (0,1,3), so it
   loses the marker. Winning the indent and losing the dot is precisely why the
   dot lands on the first character instead of sitting in an indent. A fix
   written beside .linkgrid in content-blocks.css would be (0,1,2) and lose
   silently -- the failure this file already documents for .contents. */
.prose .linkgrid li { padding: 0; margin: 0; }
.prose .linkgrid li::before { display: none; content: none; }

/* .dirgrid closes a pass early. The round-9 rule at line 147 kills list-style,
   margin and padding on the UL and never touches the marker, which is why every
   hub card carries a 7px dot at its top-left corner today (design round 20
   §4.1). Goes inert -- not wrong -- when Pass B un-nests the hub from .prose,
   and it covers any future card grid that lands inside a prose page. */
.prose .dirgrid li::before { display: none; content: none; }
/* Round 8 §4 — scoped to the list form it was written for.
   This was `.related a`, which is 0-1-1 and therefore out-specifies `.pathcard`
   (0-1-0) wherever a path card sits in a related block — not a source-order
   coin flip, which is why reordering the sheets would not have fixed it. A
   component painted by its ancestor is the container-collapse failure again.
   `.related` now owns only the section chrome; the card form it contains is the
   card component's business, so `.pathcard`, `.dircard` and the plain list form
   can all sit inside it and each keeps its own treatment. */
.related li > a { display: block; height: 100%; box-sizing: border-box;
  background: var(--color-neutral-100); border: 1px solid var(--color-divider);
  border-radius: 20px; padding: 18px 20px; text-decoration: none; color: var(--color-text);
  font-size: 16.5px; line-height: 25px; transition: border-color 0.2s ease, background 0.2s ease; }
.related li > a:hover { border-color: var(--color-accent-300); background: var(--color-accent-100); }

/* — mobile —
   The measure is already fluid; what changes is the furniture. Type does NOT
   shrink: 18px body is the reading size on a phone too, and a search visitor on a
   phone is the majority case, not the edge case. */
@media (max-width: 900px) {
  .prose { grid-template-columns: [full-start wide-start text-start] minmax(0, 1fr) [text-end wide-end full-end]; }
  .prose h2 { margin-top: calc(1.5 * var(--read)); }
  .glance, .contents, .ctablock, .callout { border-radius: 20px; }
  .prose .tablewrap { border-radius: 14px; }
  .prose th, .prose td { padding: 11px 16px; }
  .prose thead th { font-size: 13px; }
  .prose .ctablock .btn { width: 100%; box-sizing: border-box; text-align: center; }
  .related ul { grid-template-columns: 1fr; }
}
@media (max-width: 560px) {
  .prose blockquote { padding-left: 18px; font-size: 21px; }
  .prose li { padding-left: 26px; }
}

/* ─────────────────────────────────────────────────────────────────────
   CMS-SAFE TIER — the vocabulary that survives the sanitizer
   ─────────────────────────────────────────────────────────────────────

   There are two content systems, and this is the constraint that matters most.

   Static pages (405 of them) can express any markup an author types. CMS pages
   (/news, /breaches, /blog and all NEW /learn guides) go through
   marked → sanitize-html with an allowlist that permits:

     h2 h3 h4 · p · a · ul ol li · blockquote · strong em b i · code pre
     hr br · table thead tbody tr th td · img · figure figcaption

   and NO div, NO span, NO class, NO data-*. That is a deliberate trust boundary
   — bodies arrive from an agent and render to unauthenticated GETs — so it is a
   security decision, not a styling one, and the design works within it.

   Consequence: every component with a wrapper (.glance, .contents, .callout,
   .ctablock, .specs, .related, .tablewrap) is STATIC-ONLY. On CMS pages the
   author has bare elements and nothing else.

   The wrapper the server renders around the body IS ours, so it can carry a
   class. Everything below is reached through that wrapper by element selector,
   with no class on any authored element. Apply as: .prose.prose-cms  */

/* The first paragraph is the standfirst — an author cannot type class="lead". */
.prose-cms > p:first-of-type { font-size: 21px; line-height: 33px;
  color: color-mix(in srgb, var(--color-text) 88%, transparent);
  margin-bottom: calc(1.25 * var(--read)); }

/* A bare table has no .tablewrap to scroll inside, so it has to scroll itself.
   display:block is what makes overflow-x apply to a table element at all; the
   cost is that the table no longer fills its container, hence width:max-content
   with a 100% floor. */
.prose-cms > table { display: block; width: max-content; min-width: 100%; max-width: 100%;
  overflow-x: auto; border: 1px solid var(--color-divider); border-radius: 18px;
  background: var(--color-neutral-100); border-collapse: separate; border-spacing: 0;
  margin: calc(1.5 * var(--read)) 0; }
.prose-cms > table th, .prose-cms > table td { border-bottom: 1px solid var(--color-divider); }
.prose-cms > table tr:last-child th, .prose-cms > table tr:last-child td { border-bottom: 0; }

/* An author can type a blockquote but not a callout. Blockquote therefore does
   double duty on CMS pages and its styling has to survive being used for advice
   as well as for quotation — hence sage rather than a display face. */
.prose-cms > blockquote { font-family: var(--font-body); font-weight: 400;
  font-size: 17px; line-height: 27px; background: var(--color-accent-2-100);
  border-left: 4px solid var(--color-accent-2-400); border-radius: 0 20px 20px 0;
  padding: calc(0.75 * var(--read)) clamp(18px, 2.5vw, 26px); }
.prose-cms > blockquote p { font: inherit; }

/* Images arrive raw, with no wrapper and no intrinsic sizing. */
.prose-cms img { max-width: 100%; height: auto; border-radius: 18px; display: block; }
.prose-cms figure { margin: calc(1.5 * var(--read)) 0; }

/* The last link of a CMS body is usually the CTA. It cannot be a button, so it
   is given weight as a link rather than dressed up as one. */
.prose-cms > p > a:only-child { font-weight: 600; }

/* ── <details> on the 151 static guides ──
   Every static guide ships its FAQ as hand-typed <details>. A correction to an
   earlier draft of this spec, which said collapsing "removes content from the
   initial render": that is true of JS-hidden content and FALSE of <details>,
   whose content is in the initial HTML and is crawlable. The objection to it is
   therefore a UX one, not an SEO one — these are the most-quoted answers on the
   page and they are one tap away from being read.

   Both are supported. Open is recommended for FAQ; <details> is styled properly
   here rather than left to the browser, because 151 pages already use it and
   converting them is a migration nobody needs. */
.prose details { border-bottom: 1px solid var(--color-divider); padding: 4px 0; }
.prose details > summary { list-style: none; cursor: pointer; position: relative;
  padding: calc(0.5 * var(--read)) 34px calc(0.5 * var(--read)) 0;
  font-family: var(--font-body); font-weight: 600; font-size: 18px; line-height: 27px; }
.prose details > summary::-webkit-details-marker { display: none; }
.prose details > summary::after { content: ""; position: absolute; right: 8px; top: 50%;
  width: 9px; height: 9px; margin-top: -6px; border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor; transform: rotate(45deg);
  transition: transform 0.2s ease; opacity: 0.55; }
.prose details[open] > summary::after { transform: rotate(-135deg); margin-top: -2px; }
.prose details > summary:hover { color: var(--color-accent-700); }
.prose details > *:not(summary) { margin-bottom: calc(0.5 * var(--read)); }

/* The `at-a-glance` hook keeps its class — the TOC script inserts after it and
   structured data is derived from it — and `.glance` is what styles it. Both
   live on the one element: class="at-a-glance glance". Its rows are ul/li, not
   a dl; the matrix said dl and was wrong.
   `glance-meta` (173 uses) had no rule. */
/* display + a separator. The rule already set size, leading and colour, but not
   display -- so the two spans it holds (read time, last updated) were inline
   text with only source whitespace between them and rendered as
   "8 min read Last updated July 2026": two separate facts reading as one
   sentence, on 28 pages. Both sibling instances (.pagehead .meta,
   .dircard .meta) are flex with a gap; this one was the odd one out. The middot
   is the same device .pagehead uses, as a ::before so no markup changes. */
.glance .meta { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 12px;
  font-size: 13px; line-height: 20px;
  margin: calc(0.5 * var(--read)) 0 0;
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.glance .meta > span + span::before { content: "\00B7"; margin-right: 12px; opacity: 0.55; }

/* ─────────────────────────────────────────────────────────────────
   .prose-flush — a .prose block used as a SECTION inside a structured page.

   The README says the two rhythms "sit next to each other" and never said what
   happens to their alignment when they do. This is the answer.

   Default .prose centres the measure between two gutters, which is right for a
   whole article: the reading column sits in the middle of the page. Used as one
   section among .band siblings it is wrong — at 924px the gutters resolve to
   ~53px each, so a "Common questions" h2 sat at x=60 and the FAQ items it
   introduced started at x=113. A heading that does not share an edge with its
   own content reads as an accidental indent.

   All the slack moves to the trailing side: text starts at the page's left edge
   and keeps the measure, while the wide and full tracks still span the full
   width, so .wide tables and .full bands keep working. Every .prose child rule —
   details, lists, tables, callouts, the markers — is untouched, which matters
   because the matrix maps six FAQ vocabularies onto .faq and every one of them is
   styled through .prose.
   ───────────────────────────────────────────────────────────────── */
/* Scoped above the collapse: below 900px .prose is already one column, so the
   documented single-column rule stays the authoritative one at phone widths
   rather than being shadowed by an equal-specificity override. */
@media (min-width: 901px) {
  .prose-flush {
    grid-template-columns:
      [full-start] 0
      [wide-start] 0
      [text-start] min(100%, var(--measure))
      [text-end] minmax(0, 1fr)
      [wide-end] 0
      [full-end];
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   CMS TIER — element-only rules.   /news  /breaches  /blog  /state  /remove

   The sanitizer strips div, span and class from authored content. The WRAPPER is
   template-controlled and outside the sanitized region, so it can carry
   class="article prose" — everything above still applies. What cannot exist is any
   component whose definition lives INSIDE the content: a class on a paragraph, a
   wrapper div around a table, a class on a details element.

   These rules give the element-only tier the same reading experience by keying off
   position and element type instead. Everything here is additive; nothing overrides
   a class-based rule, so a static page that has both keeps the class behaviour.
   ═══════════════════════════════════════════════════════════════════════════ */

/* LEAD. .prose > p.lead cannot exist here, so the first paragraph after the h1 is
   the lead by position. Guarded with :first-of-type rather than :first-child so an
   editor adding a figure above it does not silently demote the sentence. */
.prose-cms > h1 + p { font-size: 21px; line-height: 33px;
  color: color-mix(in srgb, var(--color-text) 88%, transparent);
  margin-bottom: calc(1.25 * var(--read)); }

/* TABLES. .tablewrap is a div AROUND the table, so it cannot be authored here — and
   without it a wide table either overflows the page or squeezes the measure. The
   table becomes its own scroll container instead.

   COST, stated because it is real: display: block on a table drops its row/column
   semantics for assistive tech. That is why this is scoped to .prose-cms and not
   applied everywhere — a static page uses .tablewrap and keeps the semantics. The
   right long-term fix is the CMS template wrapping tables server-side, at which
   point this rule is deleted. */

/* CALLOUT. .callout is a classed div. blockquote is the closest authorable element
   and already carries the sage rule, so a CMS author writes a blockquote where a
   static page would use a callout. Documented so nobody "fixes" the difference. */

/* STAMP. The verification date is a classed p on static guides. Here the template
   appends it as the last paragraph, so it is addressed by position — but guarded on
   the hr the template emits with it, so a post that merely ENDS on an emphasised
   phrase is not restyled as a verification stamp. Same rule as the sources block:
   match something the pattern has, not where it sits. */
.prose-cms > hr + p:last-child em:only-child { display: inline-block;
  font-style: normal; font-size: 13px; line-height: 20px;
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }

/* SOURCE LINKS. .cite is a classed anchor. sup > a is authorable and needs no class. */
.prose-cms sup a { font-size: 11px; font-weight: 700; text-decoration: none;
  padding: 0 2px; color: var(--color-accent-700); }
.prose-cms sup a:hover { text-decoration: underline; }

/* SOURCES. .sources is a classed section, so it is stripped here like .callout and
   .tablewrap — which means a citation is TWO components, not one: the marker is
   authorable and its target is not. An earlier draft specified only the marker and
   would have shipped a dangling superscript into all five families.

   MATCHED BY STRUCTURE, NOT POSITION. An earlier version keyed off
   h2:last-of-type, which is only the Sources heading in a post that HAS sources —
   every post ending in an ordinary section had its final heading demoted to a 12px
   uppercase label with a rule above it. A sources list is identifiable by the one
   thing that makes it a sources list: its items carry ids, because the citation
   markers target them. :has(+ ol > li[id]) matches that and nothing else, so a post
   with no citations is untouched.

   The rule for a positional selector in this tier: it must describe something the
   pattern HAS, not where the pattern sits. h1 + p is safe because a lead genuinely
   is the paragraph after the title. "Last h2" was a guess about document order. */
.prose-cms > h2:last-of-type:has(+ ol > li[id]) { font-family: var(--font-body); font-weight: 700;
  font-size: 12px; line-height: 18px; letter-spacing: 0.08em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 72%, transparent);
  margin: calc(2 * var(--read)) 0 var(--half);
  padding-top: calc(1.25 * var(--read)); border-top: 1px solid var(--color-divider); }
.prose-cms > h2:has(+ ol > li[id]) + ol > li { font-size: 15px; line-height: 24px;
  margin-bottom: calc(0.3 * var(--read));
  color: color-mix(in srgb, var(--color-text) 82%, transparent); }
.prose-cms > h2:has(+ ol > li[id]) + ol > li:target { background: var(--color-accent-2-100);
  border-radius: 10px; box-shadow: 0 0 0 8px var(--color-accent-2-100); }

/* ── .pagenote — the end-of-page small print ──────────────────────────────────
   The home for `disclaimer` (173 pages) and `aa-note` (1). §8 of the matrix
   specified this in prose and the prose never became a rule; renaming into it
   would have manufactured the .cta-box failure on 173 pages at once.

   Legal or scope small print at the END of a page — not advice in the flow,
   which is what a callout is for. A callout's ground and label would hand this
   emphasis it must not have. So: no background, no icon, no uppercase label.
   A hairline rule and --read above it, and the lightest style in the system.

   §8 said 68% ink. That figure predates the contrast sweep and measures 4.37:1.
   72%, like every other muted value here. */
.pagenote { margin: var(--read) 0 0; padding-top: var(--half);
  border-top: 1px solid var(--color-divider);
  font-size: 14px; line-height: 22px;
  color: color-mix(in srgb, var(--color-text) 72%, transparent); }
.pagenote p { font-size: inherit; line-height: inherit; color: inherit;
  margin: 0 0 var(--half); max-width: 74ch; }
.pagenote p:last-child { margin-bottom: 0; }
/* links go to the accent, not down to the muted ink: a link that fails to look
   like a link in order to stay quiet is a link nobody uses. 8.6:1. */
.pagenote a { color: var(--color-accent-700); text-decoration: underline;
  text-underline-offset: 2px; }

/* ── unbreakable tokens ───────────────────────────────────────────────────────
   Broker opt-out URLs are printed in full, and a URL carries no break
   opportunity, so 34 /learn pages ran past the right edge at 390px.

   REVISED twice, and both corrections came from the branch.

   1. `anywhere`, not `break-word`. I swapped to break-word reasoning that it
      "leaves ordinary prose measuring normally" — which is true, and is exactly
      the problem. The two are identical in where they break a line; they differ
      only in whether the break counts toward min-content width, and ONLY
      `anywhere` reduces it. A flex item sizes to min-content before it shrinks,
      so under break-word the unbreakable token still sets the box width and the
      overflow survives the fix. Measured in .safetybar at 390px: 348px with
      break-word, 258px with anywhere.
      Since there is no rendering difference and only one of them survives being
      a flex item, `anywhere` is this package's default. Do not "tidy" it back.

   2. On the container, not the anchor. I scoped it to `a`, and the token that
      overflowed was a bare URL in PLAIN TEXT — a resource listed, not linked. A
      URL is legitimate content in these three components whether or not anyone
      made it a link.

   Three components, named, not a blanket rule: prose and .glance because a bare
   URL is legitimate copy there, and .safetybar because its hotline list is the
   case that found it. It deliberately does not go on body — nav labels, table
   headers and chips must keep failing loudly if they ever contain something
   unbreakable, because for them that is a content bug, not a wrap problem. */
.prose, .glance, .safetybar { overflow-wrap: anywhere; }

/* .article's only page-level rule, moved out of learn-article.html so the
   archetype carries no CSS at all. */
.article { padding-top: calc(0.5 * var(--leading)); }

/* ── Round 18: two shapes only the CMS produces ───────────────────────────────
   Cherry-picked from the round-18 sheets rather than adopting them: their copies
   predate our round-10 `.go` unscoping, the /fonts.css import and the tightened
   72% contrast floor, so taking the files wholesale would have reverted three
   real fixes. These two rules are the only additions in them. */

/* The CMS article opens on an author-written h2 with nothing between it and the
   meta line. .prose h2's 2x--read top margin is a rule for the SECOND heading
   down a page, and against .arthead it read as 90px of empty page. */
.prose .arthead + h2 { margin-top: calc(0.75 * var(--read)); }

/* The last crumb on a CMS article is the headline, and a headline long enough to
   wrap put two lines of duplicate text directly above the h1 on a phone. One
   line, ellipsised: the h1 is immediately below it, so nothing is lost. */
@media (max-width: 900px) {
  .crumbs ol { flex-wrap: nowrap; }
  .crumbs li { min-width: 0; }
  .crumbs [aria-current="page"] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
