/* ============================================================
   PRINT — "an impression on paper" (specs/2026-08-pdf-export.md).
   Linked media="print" from default.hbs AFTER the screen sheets,
   so the whole screen cascade still applies on paper and this
   layer only has to say what changes. Prime directive: printable
   in black-and-white, optimized for reading. True black on white,
   no shadows, hairlines that survive a 600 dpi mono laser.

   The geometry is the single source of truth for every renderer:
   the reader's Cmd+P and scripts/print-pdf.mjs (which restates
   the same margins so Chromium's header/footer boxes line up)
   both inherit it from here.
   ============================================================ */

/* ---------- the page: a text block, not a sheet-filling column ----------
   Margins fixed in inches; the block absorbs the paper difference:
   Letter 8.5 − 1.25 − 2.25 = 5.00in, A4 8.27 − 3.5 = 4.77in — both a
   literary measure at ~10.5pt Spectral. The deep right margin is the
   future marginalia rail (specs/2026-08-sidenotes.md); today it simply
   keeps the measure honest. NB literals, not var(): Chromium does not
   resolve custom properties inside @page. Exporter margins must match. */
@page {
  margin: 0.9in 2.25in 1in 1.25in;
}

/* ---------- pigments: ink on white, schemes ignored ----------
   Print palette is FIXED. The scheme blocks in tokens/colors.css win on
   specificity (:root.scheme-*), so these carry !important — in this later
   sheet that beats every normal declaration, whatever the scheme. Everything
   downstream (ramp, semantic aliases, casts) derives via color-mix, so
   remapping the six base pigments collapses the whole system to printable
   grays. The two tones under strong black are #666 and #999 — anything
   lighter vanishes on paper — and since 2026-08-14 they are PINNED here
   rather than derived. That day the screen ramp moved (--text-muted ink-60 →
   ink-80, --text-faint ink-40 → ink-70) to clear the 4.5:1 small-text floor
   on a backlit sheet; toner on white is not that problem, and off #000/#fff
   those steps land on #333 and #4d4d4d — three-quarters of the way to the
   body's solid black, with the register that separates a dek from its title
   collapsed into it. Print keeps the tones it was tuned for; a later move of
   the screen ramp does not reach the press.
   The accent cannot be a hue: sanguine goes to ink, and its jobs remap to
   weight and style below. */
:root {
  --ink:      #000 !important;
  --paper:    #fff !important;
  --paper-hi: #fff !important;   /* no tinted grounds anywhere — toner + dither */
  --paper-lo: #fff !important;
  --sanguine: #000 !important;
  --foxing:   #000 !important;

  /* body prose prints FULL ink, not the screen's 90% dilution: #1a1a1a on a
     600 dpi laser is a 90% halftone that softens glyph edges — the muted and
     faint tones keep their grays, the running text is solid black */
  --text-body: var(--ink) !important;
  --text-muted: #666 !important;
  --text-faint: #999 !important;

  /* rule weights: 0.5px (0.375pt) is below what a 600 dpi engine holds —
     hairlines print at ≥0.75pt, structural lines at 1pt. And a hairline must
     be INK: --rule is --ink-30 on screen, but a 30%-tint sub-1pt rule
     halftones into a broken dotted line on a mono laser — the one place the
     ramp may not be obeyed. */
  --hair: 0.75pt !important;
  --line: 1pt !important;
  --rule: #000 !important;

  /* no shadows. Every box-shadow in the theme (including koenig.css's
     !important casts) says var(--cast*) — `none` is a valid box-shadow
     value, so the tokens themselves retire the shadow everywhere. */
  --cast:      none !important;
  --cast-sm:   none !important;
  --cast-deep: none !important;

  /* the fluid clamp() scale leans on vw, which a page box makes strange —
     restate the steps as points. Body 10.5pt/1.5 ≈ 68 characters on the
     5in block. */
  --step--2: 8pt;
  --step--1: 9pt;
  --step-0:  10.5pt;
  --step-1:  12.5pt;
  --step-2:  15pt;
  --step-3:  18pt;
  --step-4:  22pt;
  --step-5:  26pt;
  --leading-body: 1.5;

  /* code re-inks for mono printing: comments and strings share the one
     muted gray; differentiation is weight/style (below), never color */
  --code-comment: #666 !important;
  --code-string:  #666 !important;

  /* the @page margins ARE the geometry — the screen's gutters, measures
     and indent all stand down (koenig's width escapes calc() off the
     indent, so it must be a length, not 0) */
  --gutter: 0rem;
  --note-indent: 0rem;
  --measure-prose: none;
  --measure-wide: none;
  --content-max: none;
}

* { text-shadow: none !important; }

/* ---------- what never prints ----------
   Print shows the note alone. Site chrome, members machinery, comments,
   adjacent-note nav, search, Portal, the announcement bar — and the print
   link itself — all stand down. */
.skip,
.site-nav,
.site-footer,
.subscribe,
.note-foot,
.note-comments,
.note-nav,
.pagination,
.print-link,
#sodo-search-root,
#announcement-bar-root,
#ghost-portal-root,
iframe[title^="portal"],
.gh-portal-triggerbtn { display: none !important; }

/* A members-gated note served to a signed-out reader carries only its public
   preview. The paywall plate itself is chrome and never prints — but a sheet
   that just STOPS would read as complete, which is worse than gated. The
   plate collapses to one honest line in the register's voice; the colophon's
   URL and QR below it are the way to the rest. */
.gh-post-upgrade-cta { display: block !important; }
.gh-post-upgrade-cta * { display: none !important; }
.gh-post-upgrade-cta::before {
  content: "§ The remainder of this note is reserved for members — it reads in full at the address in the colophon.";
  display: block;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  color: var(--text-strong);
  border-top: var(--hair) solid var(--rule);
  border-bottom: var(--hair) solid var(--rule);
  padding: var(--space-2xs) 0;
  margin-top: var(--space-md);
}

/* continuous media cannot land on paper: embeds, video, audio and file
   cards print as their caption line (figcaption survives), like --no-images
   does for photos */
.kg-prose .kg-embed-card iframe,
.kg-prose .kg-video-card > :not(figcaption),
.kg-prose .kg-audio-card > :not(figcaption),
.kg-prose .kg-file-card > :not(figcaption) { display: none !important; }

/* ---------- the block fills the page area ---------- */
.gh-main > * { max-width: none; padding-inline: 0; margin-inline: 0; }
.wrap { max-width: none; padding-inline: 0; }
.note { padding-block: 0; }
.note-head, .note-title, .note-head .dek { max-width: none; }
.note-body, .note-body.kg-prose { margin: var(--space-md) 0 0; max-width: none; }
.kg-prose .kg-width-wide,
.kg-prose .kg-width-full { width: 100%; margin-inline: 0; }

/* title block: the screen's fold, compressed for the sheet. The print-only
   byline (post.hbs .k5) sets in the same faint tone as the date it follows. */
.note-head .kick { gap: 1rem; }
.note-head .k5 { color: var(--text-faint); }
.note-title { margin: 0.5rem 0 0.4rem; }
.note-plate { margin-top: var(--space-md); }

/* ---------- links: hairline black underline ----------
   The accent is already ink via the pigment remap; thickness rides the
   hairline floor. Refs and markers stay undecorated. Portal triggers
   (href="#/portal/…") are UI, not destinations — an underline on paper
   would promise a link that leads nowhere, so they print as plain text. */
.kg-prose a { text-decoration-color: var(--ink); }
.kg-prose a[href*="#/portal"] { text-decoration: none; }

/* ---------- break hygiene ----------
   NB Chromium honors `orphans` but not `widows` (verified against a minimal
   case: a 3-line paragraph with room for two still splits 2/1, stranding
   the last line at a page top). Both stay declared — they cost nothing and
   engage the day the engine grows up; forcing break-inside:avoid on every
   paragraph would ragged the page bottoms far worse than the odd widow. */
body { orphans: 2; widows: 2; }
h1, h2, h3, h4, h5, h6 { break-after: avoid; }
figure, pre, blockquote, table,
.kg-card, .kg-prose .footnote-item,
.colophon, .plate-field { break-inside: avoid; }

/* ---------- photos: grayscale, hairline-framed, shadowless ---------- */
.kg-prose img, .note-plate img {
  filter: grayscale(1);
  border: var(--hair) solid var(--ink);
}
/* the plate wrapper keeps its frame but sheds the screen's deep padding —
   paper is dearer than pixels. At that tight mat the inset hairline ring
   would sit ~2px off the photo's own frame and read as misregistration (a
   ghost rule), so the ring stands down; frame + mat + image edge carry the
   plate grammar alone. */
.plate { padding: var(--space-2xs); }
.plate::before { content: none; }

/* Feature-caption credit links (outside the note body) are attribution, not
   citations: they take no register letter, so a bare underline on paper would
   be a dead affordance pointing nowhere. They print as plain caption text. */
figcaption a { text-decoration: none; }

/* --no-images (exporter flag, toner-frugal runs): each photo swaps for its
   caption line. The img carries the frame, so hiding it empties the plate
   cleanly; the feature plate's own box is shed too. Generated plate fields
   stay — they are line art, the printer-native mark. */
html[data-no-images] .kg-prose img,
html[data-no-images] .note-plate img { display: none; }
html[data-no-images] .note-plate.plate { border: 0; padding: 0; }
html[data-no-images] .note-plate.plate::before { content: none; }

/* ---------- plates print as line work ----------
   The WebGL fields and mezzotint grain are continuous tone — exactly what
   B&W laser reproduction is worst at. The live canvas never prints; every
   treatment field renders the engraved hatch instead: pure line art, the
   most on-brand pixel in the system (intaglio came FROM this). The hatch is
   a background-image, which Chromium drops when backgrounds are off
   (printBackground:false / the Cmd+P default) — print-color-adjust:exact
   is the sanctioned exception that keeps exactly this mark on paper.
   Line weights sit at the hairline floor; pitch opens so tone reads as
   hatching, not gray mush. */
canvas { display: none !important; }
.plate-field, .plate-field.is-live,
.tr-engrave, .tr-woodcut, .tr-mezzo {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
  background-color: var(--paper) !important;
  background-image:
    repeating-linear-gradient(24deg,  var(--ink) 0 0.75pt, transparent 0.75pt 3.5pt),
    repeating-linear-gradient(-64deg, var(--ink) 0 0.6pt,  transparent 0.6pt 5pt) !important;
  background-size: auto !important;
  background-blend-mode: normal !important;
  filter: none !important;
}

/* ---------- code: differentiation by weight and style, never color ----------
   Shiki paints tokens inline as color:var(--code-*), so the token NAME is
   legible to a selector — the print re-tokenization hooks onto it. Machine
   tokens (sanguine's one job inside code) and keywords go bold; comments
   italic; comment/string grays come from the pigment block above. */
pre {
  background: none !important;
  border: var(--line) solid var(--ink);
  font-size: 8.5pt;
  line-height: 1.45;
  padding: var(--space-2xs) var(--space-xs);
  /* paper cannot scroll — long lines wrap at the plate edge */
  white-space: pre-wrap;
  overflow-wrap: break-word;
  overflow-x: visible;
}
pre [style*="--code-keyword"],
pre [style*="--code-machine"] { font-weight: var(--wt-bold); }
pre [style*="--code-comment"] { font-style: italic; }

/* A wrapped code line must never read as a new top-level statement: each
   Shiki .line prints as its own full-width box with a 2ch hanging indent, so
   a continuation sets visibly subordinate. The literal newlines BETWEEN the
   spans collapse (white-space: normal on the pre) or the leading would
   double — the print-side inverse of koenig.css's "leave .line inline"
   screen rule. Blank source lines are empty spans whose line box would
   collapse to nothing; the ::before no-break space holds them open.
   Unhighlighted <pre> (unknown languages) keeps the flush pre-wrap above. */
pre.shiki { white-space: normal; }
pre.shiki .line {
  display: inline-block;
  width: 100%;
  white-space: pre-wrap;
  /* 3ch, deliberately odd: house languages indent in 2-space steps, so a
     continuation can never sit flush on a structural indent column */
  padding-left: 3ch;
  text-indent: -3ch;
}
pre.shiki .line:empty::before { content: "\00a0"; }
.kg-prose :not(pre) > code { background: none; padding: 0; }
.kg-prose mark { background: none; }

/* hr lives in background paint on screen, which print drops — redraw the
   rule as a border. The short footnote rule keeps its 8rem cut. */
hr {
  height: 0;
  background: none;
  border: 0;
  border-top: var(--hair) solid var(--rule);
}

/* ---------- the apparatus on paper (v1: endnotes) ----------
   The unified register (theme.js unifyFootnotes) prints where it lives, at
   the foot of the note. Backref arrows are navigation, not text — dead on
   paper. References print full ink: an 8pt gray superscript is the first
   thing a 600 dpi engine loses. */
.kg-prose .footnote-ref a { color: var(--ink); }
.kg-prose .footnote-backref { display: none; }

/* ---------- the link register ----------
   Prose links gain a small superscript mono LETTER — deliberately letters,
   never confusable with the footnote numerals' bracketed digits — and the
   register lists each letter's full URL. Built print-only by theme.js
   (@media screen hides it in base.css); here it just gets set. */
.kg-prose .link-mark {
  position: relative;
  top: -0.5em;
  vertical-align: baseline;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  line-height: 0;
  color: var(--ink);
  margin-left: 0.08em;
}
.link-register { margin-top: var(--space-lg); }
/* .kg-prose-scoped: the register lives inside the note body, where prose.css
   already claims h2 and ol at higher specificity than a bare class */
.kg-prose .link-register__head {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: var(--wt-med);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  color: var(--text-strong);
  margin: 0 0 var(--space-2xs);
}
.kg-prose .link-register__list { list-style: none; padding: 0; margin: 0; }
.kg-prose .link-register__list li {
  display: flex;
  gap: 0.75em;
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  line-height: 1.6;
  break-inside: avoid;
}
.link-register__key { color: var(--text-strong); flex: none; min-width: 1.4em; }
.link-register__url {
  color: var(--text-muted);
  overflow-wrap: anywhere;   /* URLs are not length-bound */
}

/* ---------- the colophon: provenance ----------
   Ends the document (after the registers): hairline rule, site title in
   the mono register, the canonical URL set READABLE — typed-in-able, not
   just scannable — the date of printing, and the QR plate: ~0.9in of pure
   black modules in a hairline frame. B&W line art, exactly what QR was
   born as. Inline SVG (theme.js + vendored qr.js), so its ink is fill,
   not background — it prints regardless of background settings. */
.colophon {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--space-md);
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: var(--hair) solid var(--rule);
}
.colophon__site {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: var(--wt-med);
  text-transform: uppercase;
  letter-spacing: var(--track-eyebrow);
  color: var(--text-strong);
}
.colophon__url {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--text-strong);
  overflow-wrap: anywhere;
  margin-top: var(--space-2xs);
}
.colophon__printed {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  color: var(--text-faint);
  margin-top: var(--space-2xs);
}
.colophon__qr {
  flex: none;
  width: 0.9in;
  height: 0.9in;
  margin: 0;
  border: var(--hair) solid var(--ink);
}
.colophon__qr svg { display: block; width: 100%; height: 100%; }
