/* chaosh.at
   Design notes and rationale: vault, 90_Reference/91_Documentation/
   "chaosh.at Design System". Keep this file terse; the why lives there. */

@font-face {
  font-family: "Silkscreen";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/silkscreen-400.woff2") format("woff2");
}
@font-face {
  font-family: "Silkscreen";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/silkscreen-700.woff2") format("woff2");
}

/* Alegreya Sans, self-hosted on the same terms as Silkscreen: one origin, no
   third-party DNS on first paint, and the site's own voice not dependent on
   someone else's CDN. Three static files — the family is not variable, so 400,
   400 italic and 700 are three faces and three loads. Nothing else ships; the
   CSS asks for no other weight, and a synthesised bold on a humanist sans is
   worse than not offering one. Subset and its reasoning: src/fonts/SUBSET.md. */
@font-face {
  font-family: "Alegreya Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/alegreya-sans-400.woff2") format("woff2");
}
/* A TRUE italic — different letterforms, not a slant — and the reason this
   family won. The site carries 29 <em>; a synthesised oblique on a humanist
   skeleton is the one place the whole choice would have shown its seams. */
@font-face {
  font-family: "Alegreya Sans";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/alegreya-sans-400-italic.woff2") format("woff2");
}
@font-face {
  font-family: "Alegreya Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/alegreya-sans-700.woff2") format("woff2");
}

:root {
  --pixel: "Silkscreen", ui-monospace, monospace;
  /* Named for its ROLE, not its classification — it held `ui-serif, Georgia,
     "Times New Roman", serif` until 2026-09-04, which meant the body copy was
     never one thing: iPhone Safari resolved it to New York, Windows Chrome to
     Georgia, Linux to Noto Serif. Readers were seeing different sites. Why the
     prose face is a humanist sans and not a serif is argued in the design doc's
     Type section; the short version is that Silkscreen is itself a bitmap sans,
     so a NEUTRAL sans collapses the two-voice split — a humanist one, with a
     written skeleton and a true italic, does not. */
  --prose: "Alegreya Sans", system-ui, sans-serif;

  /* Black night. All color on the site is aurora; the chrome stays out of
     the argument. One theme only, by decision. */
  --night: #0a0c18;
  --header: #07080f;
  --deep: #14162c;
  --edge: #2b3050;
  --ink: #eceafa;
  --muted: #a9a6ce;
  --accent: #8ce8cc;

  --st-active: #8ce8cc;
  --st-dabbling: #b9e8a0;
  /* dabbling desaturated: sampled IS dabbling, faded out and stopped. */
  --st-sampled: #b6bda8;
  --st-backlog: #f0dda0;
  --st-shelved: #cab5f5;
  --st-abandoned: #f0aec2;
  --st-completed: #a9d0f6;

  /* The canon's gold. Deliberately hotter and more saturated than
     --st-backlog (#f0dda0), which is the only other warm tone on the site and
     sits one tile away on the same shelf — at pale gold the two read as the
     same mark. Matches MOON_GOLD in aurora.js, which gilds a canon subject's moon;
     if one moves, move both. */
  --gold: #f5c86b;
  /* The rung below canon. Has to read as a metal beside gold and as a
     different thing from three cool pale neighbours on the same shelf —
     --muted (#a9a6ce), --st-sampled (#b6bda8), --st-completed (#a9d0f6) —
     so it is brighter and less blue than all three. */
  --silver: #cfd3e0;

  /* Retained, unused: the per-block prose cap used until 2026-08-18. Capping
     blocks individually is what stranded headings and datelines at the shell
     edge; --read below replaced it by narrowing the whole reading column. */
  --measure: 34rem;
  /* The reading column, centred inside --wide. The furniture strips and the
     header keep the full shell so nothing changes width as you navigate; only
     the text column narrows, and it narrows on BOTH sides so it reads as
     centred under the furniture rather than as prose that ran out early. */
  --read: 47rem;
  --wide: 58rem;

  /* How tall a fragment sky may grow before it stops tracking entry length,
     and where its lower edge starts dissolving into the page. */
  --sky-cap: 20rem;
  --sky-fade: 70%;
}

* { box-sizing: border-box; }

html {
  background: var(--night) linear-gradient(var(--header), transparent) no-repeat;
  background-size: 100% 26rem;
}

body {
  position: relative;
  margin: 0 auto;
  padding: 0 1.25rem 3.5rem;
  max-width: var(--wide);
  background: transparent;
  color: var(--ink);
  font: 1.125rem/1.55 var(--prose);
  -webkit-text-size-adjust: 100%;
}

/* The masthead aurora, edge to edge across the viewport, in the day's hue.
   One 8-frame sprite sheet stepped through by background-position — sprite
   animation, one decode, no flicker. The tile is periodic and rendered at a
   fixed 960px width with repeat-x, so pixel density never changes with the
   viewport; the height is px, not rem, because the frame window must match
   the sheet's 112px frame exactly or neighbouring frames bleed through.

   960 and 8 are ribbonSheet's contract, not free numbers — the three values
   below are derived from them (960 x 8*112, steps(8), -896px) and all four
   places move together. The reasoning for both is in aurora.js. */
body::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  height: 112px;
  z-index: -1;
  pointer-events: none;
  background: url("/img/aurora/sheet.svg") repeat-x;
  background-size: 960px 896px;
  animation: aurora-roll 11.2s steps(8) infinite;
}

@keyframes aurora-roll {
  to { background-position: 0 -896px; }
}

/* The reading column. Everything that is prose lives in <main>, so centring
   main centres headings, dates and body together — capping each block instead
   left h1s and datelines stranded at the shell's left edge while the paragraph
   under them sat inset, which looked like a bug. Grid pages opt out with
   `wide: true` in their front matter and take the whole shell; the furniture
   above main never changes width either way. */
main { max-width: var(--read); margin-inline: auto; }
body.wide main { max-width: none; }

a { color: var(--accent); }

img { max-width: 100%; height: auto; }

/* Scene break: pixel dashes masking an aurora gradient, dissolving both ends.
   Spectrum runs the status palette but reads as sky, not semantics. */
hr {
  border: 0;
  height: 2px;
  margin: 2.2rem 0;
  background: linear-gradient(90deg,
    transparent,
    var(--st-active) 18%,
    var(--st-completed) 42%,
    var(--st-shelved) 62%,
    var(--st-abandoned) 82%,
    transparent);
  -webkit-mask: repeating-linear-gradient(to right, #000 0 6px, transparent 6px 8px);
  mask: repeating-linear-gradient(to right, #000 0 6px, transparent 6px 8px);
}

h1, h2, h3 { line-height: 1.25; }

/* ---------------------------------------------------------------- the sky
   Generated cover art, one SVG per subject, built nightly. The slug seeds
   the aurora's path and picks its hue; status decides how much aurora is
   left; recency decides how bright it burns. Small renders get a simplified
   chip variant — same night, the bright edge alone. */

.sky {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
  border: 1px solid var(--edge);
  background-color: var(--deep);
  background-size: 100% 100%;
}

/* dither */
.sky::before {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-conic-gradient(rgba(0, 0, 0, 0.16) 0% 25%, transparent 0% 50%);
  background-size: 4px 4px;
}

/* Holds the fragment column open for prose that belongs to no subject. */
.sky-blank { visibility: hidden; border-color: transparent; background: none; }
.sky-blank::before { content: none; }


/* ---------------------------------------------------------------- header */

.site-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
  padding: 1.75rem 0 1rem;
  margin-bottom: 1.75rem;
  border-bottom: 1px solid var(--edge);
}

.site-title {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font: 400 24px/1 var(--pixel);
  text-decoration: none;
  color: var(--ink);
}

/* The name is a pixel bitmap (see button.js), 72×8 shown at 2×: 144×16,
   within a few px of the 24px Silkscreen title it replaced, so the header's
   geometry did not move. currentColor keeps it behaving like link text. */
.site-title .wordmark { display: block; image-rendering: pixelated; }

.site-title::before {
  content: "";
  width: 18px;
  height: 18px;
  image-rendering: pixelated;
  background: linear-gradient(
    var(--st-active) 0 22%, transparent 22% 32%,
    var(--accent) 32% 54%, transparent 54% 64%,
    var(--st-abandoned) 64% 100%);
  clip-path: polygon(25% 0, 75% 0, 100% 25%, 100% 75%, 75% 100%, 25% 100%, 0 75%, 0 25%);
}

/* Five equal words at 12px. The row was tiered from 2026-09-03 to 2026-09-11
   (shelf links at 16px ink) after an outside reviewer read seven identical
   links as undifferentiated; that answer was hierarchy, not size, and it was
   right for a row of seven. The row is five now, one of them the shelf, and
   the phone wrap that tiering was managing is gone with the count — so the
   tier went with it. If the row grows past six again, tier before shrinking. */
.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  margin-left: auto;
  font: 12px/1 var(--pixel);
}

.site-nav a {
  padding: 0.5rem 0.6rem;
  border: 1px solid transparent;
  text-decoration: none;
  color: var(--muted);
}

.site-nav a:hover,
.site-nav a:focus-visible { color: var(--ink); border-color: var(--edge); }

/* On a phone five words at 12px Silkscreen run ~360px of glyphs alone, so
   the row wraps at any padding — and a wrapped row over the masthead put
   the stray word on top of the aurora. So the wordmark keeps the band to
   itself and the nav drops under it, where a second line is just a second
   line. 112px is the band's height (body::before); the title row is
   1.75rem padding + 16px wordmark, and the header's own row gap adds the
   rest. */
@media (max-width: 34rem) {
  .site-title { flex-basis: 100%; }
  .site-nav { margin-left: 0; margin-top: 2.75rem; gap: 0 0.2rem; }
  .site-nav a { padding-inline: 0.4rem; }
  .site-nav a:first-child { padding-left: 0; }
}

/* ----------------------------------------------------------------- intro
   Standing lines under the masthead saying what the auroras are. The art
   encodes status and recency and nothing on the page decoded it, so a first
   visitor read the covers as arbitrary coloured lines. Prose, not chrome:
   it is the one thing a stranger actually needs to read. Renders only when
   site.intro is set. Each line is a span; the last gets a breath of space
   above it so the punchline lands on its own beat — the sibling selector
   keeps a single-line intro unaffected. */

.site-intro {
  max-width: var(--wide);
  margin: -0.5rem 0 2rem;
  color: var(--muted);
}

.site-intro span { display: block; }
.site-intro span + span:last-child { margin-top: 0.5em; }

/* ------------------------------------------- now playing / latest essays */

.now {
  padding: 0.8rem 0.9rem;
  margin-bottom: 2.5rem;
  background: var(--deep);
  border: 1px solid var(--edge);
  position: relative;
  overflow: hidden;
}

/* Adjacent strips stack as one furniture unit; only the last one holds the
   full gap before the stream. Without :has both simply keep the full gap. */
.now:has(+ .now) { margin-bottom: 0.6rem; }

.now-head {
  display: flex;
  align-items: baseline;
  margin-bottom: 0.75rem;
}

/* Scanlines here only. Over prose they would be vandalism. */
.now::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom, rgba(255, 255, 255, 0.03) 0 1px, transparent 1px 3px);
}

.now-label {
  flex-shrink: 0;
  font: 12px/1 var(--pixel);
  color: var(--st-active);
}

.now-items { display: flex; gap: 1rem; flex-wrap: wrap; }

.now-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font: 12px/1.2 var(--pixel);
  text-decoration: none;
  color: var(--ink);
}

.now-item:hover { color: var(--accent); }

/* 32px, not smaller. Below this a chip's curtain renders under 2px per column
   and reads as coloured lines, not sky — and this strip is where a stranger
   meets the art first, so it has to survive the encounter. */
.now-item .sky { width: 32px; }

/* Essays wear the completed blue: finished, considered things beside the
   active green of what's live now. */
.essays .now-label { color: var(--st-completed); }

.essay-when { color: var(--muted); font: 12px/1 var(--pixel); }

.essays-all {
  margin-left: auto;
  flex-shrink: 0;
  font: 12px/1 var(--pixel);
  text-decoration: none;
}

/* ------------------------------------------------------- badges and tags */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.45rem;
  font: 12px/1 var(--pixel);
  color: var(--c);
  background: color-mix(in srgb, var(--c) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--c) 42%, transparent);
}

.badge::before { content: ""; width: 4px; height: 4px; background: currentColor; }

.b-active { --c: var(--st-active); }
.b-dabbling { --c: var(--st-dabbling); }
.b-sampled { --c: var(--st-sampled); }
.b-backlog { --c: var(--st-backlog); }
.b-shelved { --c: var(--st-shelved); }
.b-abandoned { --c: var(--st-abandoned); }
.b-completed { --c: var(--st-completed); }

/* The rating chip — the third axis. Told apart from a status badge by fill:
   the badge is filled in its status hue, the chip is hollow and colourless.
   Colourless on purpose, where the charms it replaced borrowed the status hue:
   the charms were categorical and a borrowed hue cost nothing, but on an
   ORDINAL scale the same word in seven colours across the shelf would read as
   seven different ratings. The word is the whole mark; the two rungs that do
   get a colour (canon gold, favorite silver) get it as a frame on the card,
   not as a hue on the word, so the metal and the word say the same thing.

   No 4px dot, no glyph: a status badge carries the dot, and its absence is the
   second discriminator at 12px. Canon's ♥ is the one exception, handled by
   .badge-g. */
.rating {
  display: inline-flex;
  align-items: center;
  padding: 0.3rem 0.45rem;
  font: 12px/1 var(--pixel);
  white-space: nowrap;
  text-decoration: none;
  color: var(--muted);
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--muted) 45%, transparent);
}

a.rating:hover,
a.rating:focus-visible { color: var(--ink); border-color: var(--muted); }

.r-favorite { color: var(--silver); border-color: color-mix(in srgb, var(--silver) 55%, transparent); }

/* Pending is a placeholder, not a fifth rung: dimmer and dashed, so it reads
   as "nothing here yet" rather than as a verdict called "pending". Rendered
   rather than omitted because a blank cell beside a filled neighbour reads as
   a field somebody forgot. */
.r-pending {
  color: color-mix(in srgb, var(--muted) 55%, transparent);
  border-style: dashed;
  border-color: color-mix(in srgb, var(--muted) 30%, transparent);
}

/* Same trick on the canon badge, where ♥ is the non-colour channel carrying
   what the gold frame says — an exception for the rare gilded ten, not a
   precedent for glyphing the statuses. */
.badge-g::before {
  content: attr(data-g);
  width: auto;
  height: auto;
  background: none;
  font-size: 11px;
  line-height: 1;
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.tag-link {
  display: inline-block;
  padding: 0.3rem 0.45rem;
  background: var(--deep);
  border: 1px solid var(--edge);
  font: 12px/1 var(--pixel);
  color: var(--muted);
  text-decoration: none;
}

.tag-link:hover,
.tag-link:focus-visible { color: var(--accent); border-color: var(--accent); }

/* --------------------------------------------------------- daily stream */

.post-list { list-style: none; margin: 0; padding: 0; }

.post-list > li + li {
  margin-top: 2.75rem;
  padding-top: 2.75rem;
  border-top: 1px solid var(--edge);
}

.entry-head { margin-bottom: 1.3rem; }

.post-list-title {
  display: block;
  margin-bottom: 0.4rem;
  font: 700 1.4rem/1.12 var(--prose);
  letter-spacing: -0.018em;
  text-decoration: none;
  color: var(--ink);
}

.post-list-title:hover { color: var(--accent); }

.post-list-date,
.post-list-subdate {
  display: block;
  margin: 0;
  font: 16px/1.35 var(--pixel);
  color: var(--muted);
  text-decoration: none;
}

a.post-list-date:hover { color: var(--accent); }

/* An essay in the home stream (2026-09-15). Its head is a titled daily's
   head, so the kicker is what says which kind of post this is — the strip's
   label, in the strip's blue, sat above the title. */
.entry-kind {
  display: block;
  margin-bottom: 0.55rem;
  font: 12px/1 var(--pixel);
  color: var(--st-completed);
}

/* Markup shape comes from linkSubjects() in eleventy.config.js. */
/* The fragment sky grows with its entry, between a floor and a ceiling.
   Stretch is the default flex behaviour and this rule keeps it deliberately:
   a definite height means aspect-ratio stops governing, and with
   preserveAspectRatio='none' the curtain elongates rather than crops — the
   rays stretch and it reads as a taller aurora, not a distorted one.

   Unbounded it was a bug: a long post got a streak running its whole length.
   The floor is the natural 3:4 tile, so a one-line entry looks exactly as it
   did before; the ceiling stops any entry becoming a stripe. Between them the
   art tracks how much was written, which is the same thing recency does with
   brightness — length is the one axis the daily stream already has and never
   used. This is the one place pixel squares are knowingly non-square. */
.frag { display: flex; gap: 1rem; align-items: stretch; }
.frag + .frag { margin-top: 1.4rem; }
/* The sky stops partway down its entry, so its lower edge is dissolved rather
   than cut: an unmasked bottom leaves a hard line and a boxed border sitting
   in the middle of the prose, which reads as a clipped image instead of a sky.
   The mask takes the border and the dither with it — everything fades, so the
   column ends in night rather than in an edge. */
.frag > .sky {
  width: 3rem;
  margin-top: 0.2rem;
  min-height: 4rem;  /* 3:4 at 3rem wide — the untouched tile */
  max-height: var(--sky-cap);
  -webkit-mask-image: linear-gradient(to bottom, #000 var(--sky-fade), transparent);
  mask-image: linear-gradient(to bottom, #000 var(--sky-fade), transparent);
}
/* A completed sky still grows with its entry, but by revealing rather than
   stretching: the scene is genuinely tall (96x320, crescent in the top third)
   and cover-crops itself top-anchored via its preserveAspectRatio, so night
   is cut off the bottom instead of the moon deforming. Capped at half the
   aurora ceiling — a finished sky doesn't burn as long as a live one. */
.frag > .sky-done { max-height: 10rem; }

/* No cap here any more — `main` is the reading column now, so a fragment's
   prose simply fills what is left after the sky. min-width:0 stays: it is what
   lets a flex child shrink below its content width. */
.frag-body { min-width: 0; }
.frag-body > :first-child { margin-top: 0; }
.frag-body > :last-child { margin-bottom: 0; }

/* Every prose container on the site, in one place. */
.post > p,
.post-body p,
.frag-body p,
.fragment > p { margin: 0 0 0.72em; }

/* Inline images. The renderer folds `![[x.jpg]]` plus the Manual's caption
   convention (an italic line in the paragraph directly under it) into a
   figure, so a post image is styled in exactly one place (2026-09-08, the
   Metroid Dread essay was the first to carry any).
   The frame is the shelf card scaled up — edge border, deep ground, the
   caption hanging under the picture as a cell — so a figure reads as one of
   the site's objects rather than a paragraph-shaped interruption. Caption is
   chrome, not prose: the same 12px pixel muted as the shelf caption and the
   archive note.
   Two styles, picked in the renderer from the Obsidian width number:
   .fig-float (default) is the magazine accent — half the column, prose
   wrapping beside it, sides alternating — and .fig-full is the whole column
   for a shot whose detail matters. The float also HANGS into the margin by
   up to half its own width (25% of the column) when the viewport affords it,
   so the prose keeps three quarters of its measure instead of half; --hang
   is what the page margin can spare minus 4rem of air, floored at 0, so at
   1100px it is a partial hang and the figure never kisses the viewport edge
   (which it did at 20px in the first mock). Full hang from ~1280px. On a
   phone (34rem, same breakpoint as the essay card) floats release and every
   figure is the column. `clear` on the full style because the essay card
   floats left through the opening paragraphs and a column-wide image landing
   beside it would be squeezed into the gutter. */
figure {
  clear: both;
  margin: 1.5rem 0;
  border: 1px solid var(--edge);
  background: var(--deep);
}
figure > img { display: block; width: 100%; }
figcaption {
  padding: 0.5rem 0.75rem;
  border-top: 1px solid var(--edge);
  font: 12px/1.5 var(--pixel);
  color: var(--muted);
  text-align: center;
}
@media (min-width: 34.01rem) {
  .fig-float {
    --hang: max(0px, min(25%, calc((100vw - var(--read)) / 2 - 4rem)));
    clear: none;
    width: 50%;
  }
  .fig-right { float: right; margin: 0.3rem calc(-1 * var(--hang)) 0.75rem 1.5rem; }
  .fig-left { float: left; margin: 0.3rem 1.5rem 0.75rem calc(-1 * var(--hang)); }
}

.frag-head {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  flex-wrap: wrap;
  margin: 0 0 0.6rem;
  font-weight: 400;
}

/* The one heading in a daily, and it shipped at chrome scale — 12px, the same
   size as the date stamp and the badge beside it, so the section title read as
   metadata rather than as the thing the section is about. 16px bold is the
   smallest step that reads as a heading and still leaves a long title
   ("Castlevania: Symphony of the Night") on one line with its badge; 18px bold
   wraps the badge. Chrome elsewhere stays 12px — this is a heading wearing the
   chrome typeface, not chrome.
   The underline is the link cue and was --edge, which is 1.3:1 on night and
   effectively invisible, so the site's most important link was the only one
   with no affordance at all. --muted is visible without pulling accent green
   into every heading, where it would collide with the active badge. */
.frag-subject {
  padding-bottom: 2px;
  font: 700 16px/1.3 var(--pixel);
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--muted);
}

.frag-subject:hover,
.frag-subject:focus-visible { color: var(--accent); border-color: var(--accent); }

.more,
.all-link a {
  display: inline-block;
  margin-top: 1.1rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--edge);
  font: 12px/1 var(--pixel);
  text-decoration: none;
}

.more:hover,
.all-link a:hover { border-color: var(--accent); background: var(--deep); }

.more-cut { color: var(--muted); }

/* ------------------------------------------------------------ post page */

.post-head { margin-bottom: 2rem; }
.post-title { margin: 0 0 0.5rem; font: 700 1.75rem/1.03 var(--prose); letter-spacing: -0.021em; }
.post-date { margin: 0; font: 400 1.4rem/1.2 var(--prose); }
.post-subdate { margin: 0; font: 16px/1.35 var(--pixel); color: var(--muted); }

.post-tags {
  margin-top: 2.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--edge);
}

/* The essay's shelf card, floated as the opening paragraph's indent. Shelf
   tile width, so the cover lands at the size it was fetched to be read at;
   the cells hang under it exactly as on the shelf. The float releases on a
   phone — at 34rem the column is too narrow to wrap prose beside a 8.5rem
   card, and a card above the text is a cleaner start than three-word lines. */
.essay-card {
  float: left;
  width: 8.5rem;
  margin: 0.35rem 1.5rem 0.75rem 0;
  text-decoration: none;
  color: inherit;
}
.essay-card .shelf-cover,
.essay-card .sky { aspect-ratio: 2 / 3; }
.essay-card .cell {
  display: flex;
  justify-content: center;
  width: 100%;
  border-top: 0;
}
@media (max-width: 34rem) {
  .essay-card { float: none; margin: 0 0 1.25rem; }
}
/* The list essay (2026-09-09, the canon; any ten-things post after it). A
   ## heading that names a cited subject makes its section a row: the card in
   a fixed left column, heading and prose in the right, top-aligned. A grid
   rather than a float so a two-line entry can never let the next card ride
   up beside it. The heading loses its top margin to sit level with the card;
   the row carries the spacing instead. The card HANGS into the left margin
   by the same --hang rule the figures use — the whole card plus gutter from
   ~1280px, so the heading and prose keep the reading edge the intro and
   outro use; less as the margin shrinks. (Alternating sides and a right-hand
   column were both mocked and rejected: the text edge zigzagged, 2026-09-09.)
   On a phone there is no margin, so the card column narrows beside the text
   rather than stacking — ten covers above ten paragraphs is a long scroll,
   and a 6rem cover beside 17rem of prose still reads. The footer clears the
   floated top cards of an ordinary essay. */
.essay-row {
  --hang: max(0px, min(10rem, calc((100vw - var(--read)) / 2 - 4rem)));
  display: grid;
  grid-template-columns: 8.5rem 1fr;
  /* auto 1fr: the card spans both rows, and without this a card taller than
     a short entry has its excess shared across BOTH rows, dropping the prose
     away from its heading. The heading row is its content; the rest is the
     body row's. */
  grid-template-rows: auto 1fr;
  gap: 0 1.5rem;
  align-items: start;
  margin: 1.75rem 0 0 calc(-1 * var(--hang));
}
.essay-row > h2 { grid-column: 2; grid-row: 1; margin-top: 0; }
.essay-row .essay-card { float: none; width: auto; margin: 0.3rem 0 0; grid-column: 1; grid-row: 1 / 3; }
.essay-row-body { grid-column: 2; grid-row: 2; }
.essay-row-body > :first-child { margin-top: 0; }
.essay-row-body > :last-child { margin-bottom: 0; }
.essay-row + :not(.essay-row) { margin-top: 1.75rem; } /* the outro after a `---` */
.essay-page .post-tags { clear: both; }
@media (max-width: 34rem) {
  .essay-row { grid-template-columns: 6rem 1fr; gap: 0 1rem; }
  .essay-row .essay-card { margin: 0.2rem 0 0; }
}

/* --------------------------------------------------- archive / best of */

.archive-list { list-style: none; margin: 0; padding: 0; }

.archive-list > li {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--edge);
}

.archive-list a { font: 12px/1.5 var(--pixel); text-decoration: none; }

/* The archive row's kicker, same word and blue as the home stream's. */
.archive-kind { font: 12px/1.5 var(--pixel); color: var(--st-completed); }

.archive-note {
  margin-left: auto;
  font: 12px/1.5 var(--pixel);
  color: var(--muted);
}

/* The essays index — the archive list wearing chips, since every essay has a
   subject to show its sky. */
.essay-list { list-style: none; margin: 0; padding: 0; }

.essay-list > li {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--edge);
}

.essay-list .sky { width: 24px; }
.essay-list a { font: 12px/1.5 var(--pixel); text-decoration: none; }

.pagination {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  margin-top: 2rem;
  font: 10px/1 var(--pixel);
}

.pagination a { text-decoration: none; }
.pagination span { margin-left: auto; color: var(--muted); }

/* ----------------------------------------------------------- shelf page */

.shelf-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: 1.75rem 1.1rem;
}

.shelf-item a { display: block; text-decoration: none; color: inherit; }

/* The card is cover + two cells, and the lift takes the whole card: they are
   one object, and lifting the art alone would peel it off the label under it. */
.shelf-card { display: block; transition: transform 0.1s steps(3); }
.shelf-item a:hover .shelf-card { transform: translateY(-4px); }
.shelf-art { display: block; }

/* 2:3 is SteamGridDB's portrait grid size (600x900), so a fetched cover lands
   unscaled. cover-sizing catches the other dimensions the API serves. No
   dither over this one: the texture belongs to the generated art, and laid
   over real box art it only reads as a dirty screen. */
.shelf-cover {
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  border: 1px solid var(--edge);
  /* Night showing through until a lazy cover arrives, so the grid never
     flashes a white or empty box against the dark page. */
  background: var(--deep);
  object-fit: cover;
}

/* Covered and coverless tiles are held to the same height so a partly-covered
   shelf lays out as one clean grid rather than a ragged one: the fallback sky
   stretches to the cover's 2:3. Stretching is what these SVGs are for —
   pAR='none' just lengthens the rays, and a completed scene cover-crops from
   its tall 3:10 canvas, so the crescent never deforms. */
.shelf-item .sky { aspect-ratio: 2 / 3; }

/* The two cells, hung off the cover. Each takes the card's full width, so the
   longest word in either vocabulary fits at 12px with room; a side-by-side
   pair never did at six columns. Edges are SHARED, not doubled: the cover
   keeps its bottom edge, the status cell keeps its bottom edge, and each cell
   drops its top — one line between every layer, so the stack reads as a
   single card with a two-line label rather than three boxes in a pile. */
.shelf-card .cell {
  display: flex;
  justify-content: center;
  width: 100%;
  border-top: 0;
}

.shelf-caption { display: block; margin: 0.55rem 0 0; font: 12px/1.5 var(--pixel); }

/* ------------------------------------------------- shelf sort (no script)

   Each tile carries a precomputed rank per axis (--o-az and friends, stamped in
   shelf.njk); the checked radio selects which one `order` reads. Status is the
   authored DOM order and so needs no rule — which also means the shelf is
   correctly sorted before any of this applies, including for a reader whose
   browser lacks :has(). Adding an axis is a rank in shelfRanks, a radio, and one
   line here. */
.shelf-sorter:has(#sort-az:checked) .shelf-item { order: var(--o-az); }
.shelf-sorter:has(#sort-recent:checked) .shelf-item { order: var(--o-recent); }
.shelf-sorter:has(#sort-canon:checked) .shelf-item { order: var(--o-canon); }
.shelf-sorter:has(#sort-rating:checked) .shelf-item { order: var(--o-rating); }

/* The category filter, same trick with display instead of order. "all" is
   the checked default and needs no rule. One line per category in
   SHELF_LABELS (eleventy.config.js); a category added there without a line
   here gets a chip that hides nothing, which is visible at once. */
.shelf-sorter:has(#show-games:checked) .shelf-item:not(.cat-games) { display: none; }
.shelf-sorter:has(#show-boardgames:checked) .shelf-item:not(.cat-boardgames) { display: none; }
.shelf-sorter:has(#show-books:checked) .shelf-item:not(.cat-books) { display: none; }
.shelf-sorter:has(#show-shows:checked) .shelf-item:not(.cat-shows) { display: none; }
.shelf-sorter:has(#show-life:checked) .shelf-item:not(.cat-life) { display: none; }

.shelf-sort {
  border: 0;
  margin: 0 0 1.4rem;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.55rem;
}

.shelf-sort legend {
  float: left; /* keeps the legend on the flex row rather than above it */
  padding: 0 0.6rem 0 0;
  font: 12px/1.5 var(--pixel);
  color: var(--muted);
}

/* The input is the state; the label is the control. Clipped rather than
   display:none so it keeps its place in the tab order and stays operable by
   keyboard — arrow keys move through the group as a radio group should. */
.shelf-sort input {
  position: absolute;
  width: 1px;
  height: 1px;
  clip-path: inset(50%);
  overflow: hidden;
}

.shelf-sort label {
  font: 12px/1.5 var(--pixel);
  color: var(--muted);
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--edge);
  cursor: pointer;
}

.shelf-sort label:hover { color: var(--ink); }
.shelf-sort input:checked + label { color: var(--night); background: var(--accent); border-color: var(--accent); }
/* Focus has to live on the label, since the input it belongs to is clipped. */
.shelf-sort input:focus-visible + label { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------------------------------------------------- shelf canon + silver

   The frame is the channel that always fires. A canon subject's moon is gilded
   in aurora.js too, but only `completed` subjects have a moon — an active
   replay would otherwise show nothing, so the gold cannot live in the art
   alone. The whole card takes the colour: cover and cells are drawn as one
   object with shared edges, and gilding half of it would split it in two.

   Silver is the same grammar one step down. It exists because "favorite" and
   "loved" cannot be ordered by English alone, and a reader orders gold over
   silver without being told — so the top two rungs of the ladder are ranked by
   metal, and the three verbs below rank themselves. Quieter glow than gold:
   it is the runner-up's frame, and it should read that way beside the ten. */
.is-canon .sky,
.is-canon .shelf-cover { border-color: var(--gold); }

.is-canon .shelf-card {
  box-shadow: 0 0 0 1px var(--gold), 0 0 14px -4px var(--gold);
}

.is-favorite .sky,
.is-favorite .shelf-cover { border-color: var(--silver); }

.is-favorite .shelf-card {
  box-shadow: 0 0 0 1px var(--silver), 0 0 10px -5px var(--silver);
}

.b-canon { --c: var(--gold); }

/* The link to the printed scale, beside the sort control it explains. */
.shelf-key {
  display: inline-block;
  margin: 0 0 1.5rem;
  font: 12px/1 var(--pixel);
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--muted) 40%, transparent);
}
.shelf-key:hover,
.shelf-key:focus-visible { color: var(--ink); border-color: var(--ink); }

/* --------------------------------------------------------- subject page */

.subject-head {
  display: flex;
  gap: 1.4rem;
  align-items: flex-start;
  padding-bottom: 1.75rem;
  margin-bottom: 2.25rem;
  border-bottom: 1px solid var(--edge);
}

.subject-head > .sky { width: 8rem; }
.subject-head h1 { margin: 0 0 0.7rem; font: 700 1.75rem/1.03 var(--prose); letter-spacing: -0.021em; }

.subject-meta {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex-wrap: wrap;
  margin: 0;
}

/* The standing blurb: the subject's one revisable paragraph, in the header's
   text column beside the cover — it just flows there, no layout of its own. */
.subject-blurb { margin-top: 0.9rem; }
.subject-blurb p { margin: 0 0 0.6em; }
.subject-blurb > :last-child { margin-bottom: 0; }

.essay { margin-bottom: 3rem; }

/* Was .timeline-head, when the timeline was the only labelled section. Both
   halves of a subject page carry one now — "Deep thoughts" over the pinned
   essays, "Along the way" over the fragments — so the name generalised. */
.section-head {
  margin: 0 0 1.75rem;
  padding-bottom: 0.8rem;
  border-bottom: 1px solid var(--edge);
  font: 400 12px/1 var(--pixel);
  color: var(--muted);
}

/* Every label sits ABOVE its rule, so the line reads as the top of the section
   it opens rather than the bottom of whatever came before. That means when an
   essay section follows, "Deep thoughts" carries the rule that closes the
   header and the header drops its own — two rules 2rem apart is one too many.
   A subject with no essay keeps the header rule: nothing below it to take the
   job over, since "Along the way" only appears when there are essays above. */
.subject-head.no-rule {
  padding-bottom: 0;
  margin-bottom: 2.5rem;
  border-bottom: 0;
}

.fragment { max-width: none; }

.fragment + .fragment { margin-top: 2rem; }
.fragment > :last-child { margin-bottom: 0; }

.fragment-date {
  display: inline-block;
  margin-bottom: 0.45rem;
  font: 12px/1.4 var(--pixel);
  color: var(--muted);
  text-decoration: none;
}

.fragment-date:hover { color: var(--accent); }

/* ------------------------------------------------- tag page, and /f/ views */
/* Both take the same shape and share src/_includes/view.njk, so the .tag-*
   classes here read as "view page" — a curated tagview or a rating rung. */

.tag-head { margin-bottom: 2.25rem; }

/* The charm's own glyph at the head of its /f/ view — the one place the
   pictogram appears without its words beside it, because the words are the
   <h1> it sits in. */
.view-glyph { font-size: 0.8em; margin-right: 0.15em; }
.tag-head h1 { margin: 0 0 0.5rem; font: 700 1.75rem/1.03 var(--prose); letter-spacing: -0.021em; }
.tag-desc { margin: 0 0 0.6rem; color: var(--muted); }
.tag-members { margin: 0; font: 12px/1.8 var(--pixel); color: var(--muted); }
.tag-members a { text-decoration: none; }
.tag-index { margin: 0 0 0.6rem; font: 12px/1 var(--pixel); }
.tag-index a { color: var(--muted); text-decoration: none; }
.tag-index a:hover,
.tag-index a:focus-visible { color: var(--ink); }

/* /r/ — the ladder, printed. One rung per row, top to bottom, the chip
   exactly as it renders on a tile so the page doubles as the legend. Rows are
   ordered, never numbered: the order IS the information, and printing 1–5
   would re-mint the score this page exists to replace. */
.ladder {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.1rem;
}
.rung {
  display: grid;
  grid-template-columns: 7rem 1fr;
  gap: 0.2rem 1rem;
  align-items: start;
  padding-top: 1.1rem;
  border-top: 1px solid var(--edge);
}
.rung > .badge,
.rung > .rating { grid-row: 1 / span 2; justify-self: start; }
.rung-desc { margin: 0; }
.rung-count { margin: 0; font: 12px/1.5 var(--pixel); color: var(--muted); }
@media (max-width: 34rem) {
  .rung { grid-template-columns: 1fr; }
  .rung > .badge,
  .rung > .rating { grid-row: auto; }
}

/* This timeline mixes subjects, so each entry carries its own sky. */
.fragment-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.4rem;
}

.fragment-meta .fragment-date { margin-bottom: 0; }
.fragment-meta .sky { width: 24px; }

.fragment-subject {
  font: 12px/1.3 var(--pixel);
  color: var(--muted);
  text-decoration: none;
}

.fragment-subject:hover { color: var(--accent); }

/* A pinned essay's title, on a subject or a tag page. It shipped wearing
   .fragment-subject — 12px muted, identical to the dateline sat next to it —
   so the title of the most considered thing on the page read as a second
   timestamp. It takes the shape a titled post already has in the feed and on
   its own /e/ page instead: serif title, dateline beneath it as a subdate. An
   essay looks the same everywhere it is listed. */
.essay-title {
  display: block;
  margin-bottom: 0.35rem;
  font: 700 1.4rem/1.12 var(--prose);
  letter-spacing: -0.018em;
  color: var(--ink);
  text-decoration: none;
}

.essay-title:hover,
.essay-title:focus-visible { color: var(--accent); }

.tag-essay { margin: 0; }

/* .more is a padded box; sat inline at the end of the lede it grew the last
   line box by its own margin and padding, so the blurb's final line spaced
   itself out to make room for a button. It gets its own line instead — which
   is also what puts air between the pinned essays and "Along the way". */
.tag-essay + .more { margin-bottom: 0.4rem; }
.subject .essays { margin-bottom: 2.75rem; }

/* Members' standing blurbs, between the tag head and the timeline. */
.blurbs { margin-bottom: 2.5rem; }

/* ------------------------------------------------------------- spoilers */

/* ||inline spoilers||. Focus is the reveal, which keeps the site at zero JS.
   Blurred rather than blacked out: anyone who squints past a blur could have
   just clicked it. */
.spoiler {
  background: var(--edge);
  filter: blur(0.3em);
  cursor: pointer;
  transition: filter 0.12s ease, background-color 0.12s ease;
  -webkit-tap-highlight-color: transparent;
}

.spoiler:focus { background: none; filter: none; outline: none; }

/* Paper has no focus state, so a blurred spoiler would print unreadable. */
@media print {
  .spoiler { background: none; filter: none; }
  body::before { content: none; }
}

.empty { color: var(--muted); }

/* --------------------------------------------------------------- footer */

.site-footer {
  margin-top: 4rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--edge);
  display: flex;
  gap: 1.25rem;
  flex-wrap: wrap;
  font: 10px/1.6 var(--pixel);
  color: var(--muted);
}

.site-footer p { margin: 0; }
.site-footer a { color: var(--muted); text-decoration: none; }
.site-footer a:hover { color: var(--accent); }

/* ----------------------------------------------------------------- links
   The site button and the people who link back. Buttons are 88×31 pixel
   objects: image-rendering pixelated so a 2× screen doubles the pixels
   instead of smearing them. The friend's button is hotlinked from links.yaml;
   an entry without one keeps the 88px column so names stay aligned. */

.links-lede { max-width: var(--read); }

.button-row { display: flex; gap: 1rem; align-items: center; margin: 1rem 0; }
.button-row img,
.links-button img { display: block; image-rendering: pixelated; }
.button-row a { display: block; line-height: 0; }

.snippet {
  margin: 0 0 2.5rem;
  padding: 0.7rem 0.9rem;
  overflow-x: auto;
  background: var(--deep);
  border: 1px solid var(--edge);
  font: 12px/1.6 ui-monospace, monospace;
  color: var(--muted);
}

.links-head { font: 12px/1 var(--pixel); color: var(--muted); margin: 0 0 1rem; }

.links-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.75rem; }
.links-list > li { display: flex; align-items: center; gap: 1rem; }
.links-button { flex: none; width: 88px; height: 31px; line-height: 0; }
.links-button-none { border: 1px dashed var(--edge); box-sizing: border-box; }
.links-body a { color: var(--ink); text-decoration: none; }
.links-body a:hover { color: var(--accent); }
.links-note { color: var(--muted); }

/* ----------------------------------------------------------------- guestbook
   The form is plain HTML posting to sign.chaosh.at; the entries are baked in
   at build. Fields share the snippet's surface so the page reads as one
   object. The honeypot is positioned off-canvas, not display:none — some
   bots skip hidden fields and fill the rest. */

.links-lede span { display: block; }
.gb-note { font: 12px/1.4 var(--pixel); color: var(--muted); max-width: var(--read); margin: -0.5rem 0 1.5rem; }
.guestbook-form { display: grid; gap: 1rem; max-width: var(--read); margin: 0 0 2.5rem; }
.guestbook-form label { display: grid; gap: 0.35rem; font: 12px/1 var(--pixel); color: var(--muted); }
.gb-opt { opacity: 0.7; }
.guestbook-form input,
.guestbook-form textarea {
  font: 1rem/1.5 var(--prose);
  color: var(--ink); background: var(--deep);
  border: 1px solid var(--edge); border-radius: 0; padding: 0.5rem 0.7rem;
  width: 100%; box-sizing: border-box;
}
.guestbook-form textarea { resize: vertical; min-height: 6rem; }
.guestbook-form input:focus-visible,
.guestbook-form textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
.guestbook-form button {
  justify-self: start; cursor: pointer;
  font: 12px/1 var(--pixel); color: var(--night); background: var(--accent);
  border: 1px solid var(--accent); border-radius: 0; padding: 0.7rem 1.2rem;
}
.guestbook-form button:hover { background: var(--ink); border-color: var(--ink); }
.guestbook-form button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gb-trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }

.gb-contact { color: var(--muted); max-width: var(--read); margin: -1.5rem 0 2.5rem; }
.guestbook-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1.5rem; max-width: var(--read); }
.gb-meta { margin: 0 0 0.3rem; display: flex; gap: 0.8rem; align-items: baseline; flex-wrap: wrap; }
.gb-name { color: var(--ink); font-weight: 700; }
.gb-name a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--edge); }
.gb-name a:hover { color: var(--accent); border-color: var(--accent); }
.gb-meta time { font: 12px/1 var(--pixel); color: var(--muted); }
.gb-message { margin: 0; white-space: pre-line; overflow-wrap: anywhere; }
.gb-empty { color: var(--muted); }

/* Silkscreen is set in px, never rem, so it never lands on a fractional size.
   Its x-height is 0.5em — half the em, against roughly 0.48 for the body
   serif — so a nominally matching px size reads far smaller beside prose.
   Chrome is 12px; 10px is reserved for the footer and pagination counts,
   which are the only genuinely tertiary text on the site.
   Below 34rem only the art shrinks. */
@media (max-width: 34rem) {
  .frag > .sky { width: 2.25rem; min-height: 3rem; }
  .subject-head { gap: 1rem; }
  .subject-head > .sky { width: 5.5rem; }
}

@media (prefers-reduced-motion: reduce) {
  .shelf-art, .spoiler { transition: none; }
  body::before { animation: none; }
}


/* The fragment art column. 7rem because that is the smallest width at which a
   cover is recognisable; the aurora fallback matches it so a subject without
   one does not shift the heading and prose left of everything around it. On
   the shelf a missing cover was cosmetic — here it would break the reading
   column, so the two forms must agree. */
.frag > .sky {
  width: 6rem;
  /* aspect-ratio: auto is load-bearing. .sky sets 3/4, which was invisible at
     3rem wide (4rem tall, the same as min-height) but at 7rem forces a 149px
     box — so a short entry, and every loose-prose column, got a tall empty
     gap. Here the height must come from the entry, never from the art. */
  aspect-ratio: auto;
}

/* Sourced cover in the fragment column. It opts OUT of three things the sky
   does, because all three are behaviours that read as sky and misread as
   photograph:

   - the length stretch. Cropping a cover to the entry's height showed the top
     third of the box art and nothing else, which reads as a cut-off image, not
     a cover. Fixed 2:3, whole art, always.
   - the bottom mask. A sky dissolving into the page reads as sky running out;
     box art doing it reads as broken.
   - the dither. The 4px checker belongs to generated art; over a photograph it
     is just a dirty screen.

   The cost is the one the stretch existed to avoid: beside a short entry the
   cover is taller than the text. Accepted — a whole cover with space under it
   beats a legible-length sliver of one. */
/* Cover behaviour shared by every surface that shows one: 2:3 like the source
   grid, cropped not stretched, and no dither — the 4px checker belongs to
   generated art and over a photograph it is just a dirty screen. */
.sky-cover {
  aspect-ratio: 2 / 3;
  background-size: cover;
  background-position: center;
}
.sky-cover::before { content: none; }

/* As a link it must not look like one: no underline, and the border warms on
   hover instead of the whole tile moving, which in a column of running prose
   would be a twitch rather than an affordance. */
a.sky-cover { text-decoration: none; transition: border-color 0.1s steps(2); }
a.sky-cover:hover { border-color: var(--accent); }

.frag > .sky-cover {
  aspect-ratio: 2 / 3;
  align-self: flex-start;
  min-height: 0;
  max-height: none;
  -webkit-mask-image: none;
  mask-image: none;
}

/* ------------------------------------------------------- the day band
   The sky the night a post was written, seeded on the post's date. Drawn on a
   pseudo-element rather than the header itself for one reason: the band needs a
   horizontal fade at both ends, and a mask on the header would eat the title
   with it. The art arrives as a custom property so the pseudo-element can read
   it — background-image does not inherit usefully here.

   Both ends dissolve rather than stopping square, the same argument the scene
   break already makes: a hard edge mid-page reads as a clipped image. It also
   runs wider than the reading column, so the sky is behind the text rather
   than boxed around it. */
.entry-head, .post-head { position: relative; }
.entry-head > *, .post-head > * { position: relative; }

.entry-head::before, .post-head::before {
  content: "";
  position: absolute;
  z-index: -1;
  /* Reaches the viewport edge on a phone (body padding is 1.25rem) and bleeds
     out of the reading column on a desktop, where main leaves 5.5rem of slack
     inside the shell — so it never causes a horizontal scrollbar. */
  inset: 0 -1.25rem;
  background-image: var(--day-sky);
  background-repeat: repeat-x;
  background-position: left bottom;
  /* 480px fixed tile, same rule as the masthead: pixel density never varies
     with the viewport. */
  background-size: 480px 96px;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent);
}

@media (min-width: 48rem) {
  .entry-head::before, .post-head::before { inset: 0 -3.5rem; }
}

.post-head { margin-bottom: 2rem; padding: 1.1rem 0 1.3rem; }
.entry-head { margin-bottom: 1.3rem; padding: 1.1rem 0 1.3rem; }

/* Everything that sits on the aurora gets a hard 1px outline rather than a
   blur — a bitmap face wants bitmap treatment, and this is how SNES-era UI put
   small type over a busy field. It lets the curtain come up behind the type
   instead of being pushed out of the way to stay readable.

   The headings were exempt until 2026-09-07, on the reasoning that they are
   large and bright already and that an outline on a serif reads as a cheap
   effect. Two things retired that: the prose face changed to Alegreya Sans
   (2026-09-04), and the band stopped being one drawing. A pale drapery fold
   can now land directly behind a heading — in testing that made a title
   vanish — and dimming the band far enough to fix it costs more than the
   outline does. The wordmark has carried a one-pixel night halo all along, so
   this is the house treatment, not an exception to it. */
.entry-head .post-list-subdate,
.entry-head .post-list-date,
.post-head .post-title,
.post-head .post-date,
.post-head .post-subdate {
  color: var(--ink);
  text-shadow:
     1px 1px 0 var(--night), -1px  1px 0 var(--night),
     1px -1px 0 var(--night), -1px -1px 0 var(--night),
     0 2px 0 var(--night);
}
