@charset "UTF-8";
/* =============================================================================
   NEON & SMOKE — system.css
   The only stylesheet. No page carries a <style> block. No second sheet.
   If a component is not in this file, it does not exist.

   Governing idea: SMOKE DOMINATES, NEON PUNCTUATES.
   Warm, grounded, candlelit, print-magazine as the base. The neon is the sign
   in the window of an otherwise dark room — rare, sharp, electric.

   Contents
   ---------------------------------------------------------------------------
   00  FONT FACES (self-hosted)
   01  TOKENS — colour, type scale, space, measure, radii, z, motion
   02  GROUND CONTEXTS — the token flip that makes text safe on both grounds
   03  RESET & BASE
   04  TYPOGRAPHY — Frank Ruhl Libre axes, headings, prose
   05  POETRY
   05b AUTHORIAL FORMATTING HOOKS — the scraper's data-* attributes (SPEC §9.1)
   06  GROUNDS & SECTION TRANSITIONS
   07  ATMOSPHERE — smoke, grain, neon bloom
   08  PRINT-MAGAZINE VOCABULARY — dropcap, pullquote, asterism, folio, rule,
                                   colophon, issue number
   09  THE "STAY LIT." SIGN
   10  LINKS & BUTTONS
   11  NAVIGATION — nav, breadcrumb, prev/next
   12  CARDS — issue, piece, contributor, archive record
   13  MODULES — submission call, newsletter
   14  FOOTER
   15  FOCUS, SKIP LINK, UTILITIES
   16  RESPONSIVE
   17  REDUCED MOTION
   18  STYLEGUIDE SUPPORT (documentation-only — not for page templates)
   19  PRINT
   20  PAGE COMPONENTS (merged from the page-template agents' staged additions)
   ============================================================================= */


/* =============================================================================
   00  FONT FACES
   Two families. That is the entire system. No third family. No monospace —
   it was tried, read as techy, and was cut (SPEC §2.2).
   Files fetched by tools/fetch-fonts.mjs; SIL OFL licences sit beside them in
   assets/fonts/. Zero runtime calls to Google Fonts.
   ========================================================================== */

/* --- Frank Ruhl Libre (variable: wght 300–900, no opsz, no italic) --- */
@font-face {
  font-family: "Frank Ruhl Libre";
  font-style: normal;
  font-weight: 300 900;
  font-stretch: normal;
  font-display: swap;
  src: url("../fonts/frankruhllibre-variable-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Frank Ruhl Libre";
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url("../fonts/frankruhllibre-variable-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --- EB Garamond (variable: wght 400–800) --- */
@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/ebgaramond-variable-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/ebgaramond-variable-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "EB Garamond";
  font-style: italic;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/ebgaramond-variable-latin-italic.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "EB Garamond";
  font-style: italic;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/ebgaramond-variable-latin-ext-italic.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}


/* =============================================================================
   01  TOKENS
   ========================================================================== */

:root {
  /* --- Ground & accent (SPEC §2.1 — these six are canonical, do not add hues) */
  --void:       #08050b;  /* near-black, warm-shifted — the dark ground        */
  --paper:      #f4eadb;  /* aged cream — the editorial ground                 */
  --paper-text: #1f1726;  /* warm near-black ink on paper                      */
  --pink:       #ff2e88;  /* hot neon magenta — primary signal                 */
  --amber:      #ffa54d;  /* ember/brass — secondary warmth                    */
  --cyan:       #5ad4e8;  /* cold neon — RAREST. 1–2 uses per page maximum.    */

  /* --- Derived legibility variants -----------------------------------------
     Not new hues. Each is the canonical accent darkened along its own hue so it
     can carry body text on --paper, where the raw neon fails WCAG AA badly
     (pink 2.94:1, amber 1.64:1, cyan 1.47:1). Measured values in qa/CONTRAST.md.
     Rule: raw --pink/--amber/--cyan are for the VOID ground and for fills only.
     Text on paper uses the -ink variant. The ground contexts in §02 wire this
     up automatically, so you rarely reference these by name.                  */
  --pink-ink:  #b01a63;   /* 5.56:1 on paper */
  --amber-ink: #8a4a12;   /* 5.75:1 on paper */
  --cyan-ink:  #166170;   /* 5.92:1 on paper */

  /* --- The smoke ramp ------------------------------------------------------
     Recovered from Jonathan's own earlier design for /issues/
     (docs/reference/original-issues.html). The handoff document that seeded this
     stylesheet recorded only void/paper/pink/amber/cyan, so the system shipped
     flatter than the palette he was actually working in: with one dark value
     there is no way to sit a dark object ON the dark ground and have it read as
     an object rather than as a hole.

     THIS INTRODUCES NO HUE, and that is checkable rather than asserted. --void
     is #08050b — blue 11, red 8, green 5. Every step below preserves that exact
     b > r > g ordering, so the ramp is --void continued toward the light, with
     the same warm-violet cast, and not a new colour smuggled in past SPEC §2.1:

        --void        08 05 0b      b>r>g
        --smoke-deep  11 0a 16      b>r>g
        --smoke       1a 12 20      b>r>g
        --smoke-lift  25 1a 2d      b>r>g
        --smoke-hi    34 25 3e      b>r>g
        --ash         48 3a 52      b>r>g

     Literal values, not color-mix(): these are Jonathan's measured steps and the
     point of recovering them is to carry them across exactly. A mix of --void and
     --paper lands neutral-warm and loses the violet; a mix of --void and --pink
     lands magenta. Neither reproduces the ramp.

     NOT ADDED, deliberately. The original also carried --paper-dim (#c9bcae) and
     --paper-mute (#8a7f74). Those are within a couple of points of --haze and
     --haze-dim, which already exist above and are already wired into the ground
     contexts — adding them would have created two names for one colour, which is
     how a palette starts drifting. Use --haze / --haze-dim. Its --gold,
     --pink-soft and --amber-soft are not here either: nothing in this build uses
     them, and an unused token is dead CSS (regression #7).                     */
  --smoke-deep: #110a16;  /* plate ground — an object laid on the void          */
  --smoke:      #1a1220;
  --smoke-lift: #251a2d;
  --smoke-hi:   #34253e;
  --ash:        #483a52;  /* the darkest step that still reads as a mark        */
  --pink-deep:  #c91a6e;  /* pink at plate depth, for the no-cover gradient     */

  /* --- Derived neutrals ----------------------------------------------------
     Paper dimmed by the room (on void) and ink lifted off the page (on paper).
     Same two colours as the grounds, mixed — no grey is introduced.           */
  --haze:      color-mix(in srgb, var(--paper) 68%, var(--void));  /* 7.5:1 on void */
  --haze-dim:  color-mix(in srgb, var(--paper) 55%, var(--void));  /* 5.4:1 on void */
  --ink-muted: color-mix(in srgb, var(--paper-text) 72%, var(--paper)); /* 6.2:1 on paper */

  /* --- Type families ------------------------------------------------------- */
  --font-display: "Frank Ruhl Libre", "Times New Roman", Georgia,
    "Noto Serif", serif;
  --font-body: "EB Garamond", "Iowan Old Style", Garamond, Georgia,
    "Times New Roman", serif;

  /* --- Fluid type scale ----------------------------------------------------
     Two fluid ranges on purpose:
       • DISPLAY steps grow 380px → 1440px. Display type should keep scaling
         with the window because it is not constrained by a measure.
       • PROSE steps grow 380px → 1024px and then LOCK. The measure caps at
         1024px, so growing prose past that point only reduces the number of
         characters per line — it makes reading worse, not better. Prose lands
         on exactly 19px at 1024px and stays there (SPEC §3).                  */

  /* display / metadata — Frank Ruhl Libre */
  --t-micro:   clamp(0.6875rem, 0.6651rem + 0.0943vw, 0.75rem);    /* 11→12  */
  --t-meta:    clamp(0.8125rem, 0.7789rem + 0.1415vw, 0.9063rem);  /* 13→14.5*/
  --t-h4:      clamp(1.125rem, 1.0578rem + 0.283vw, 1.3125rem);    /* 18→21  */
  --t-h3:      clamp(1.3125rem, 1.1557rem + 0.6604vw, 1.75rem);    /* 21→28  */
  --t-h2:      clamp(1.625rem, 1.3113rem + 1.3208vw, 2.5rem);      /* 26→40  */
  --t-h1:      clamp(2.0625rem, 1.5024rem + 2.3585vw, 3.625rem);   /* 33→58  */
  --t-display: clamp(2.75rem, 1.6745rem + 4.5283vw, 5.75rem);      /* 44→92  */
  --t-sign:    clamp(3.25rem, 1.4575rem + 7.5472vw, 8.25rem);      /* 52→132 */
  --t-pull:    clamp(1.5rem, 1.2759rem + 0.9434vw, 2.125rem);      /* 24→34  */

  /* prose — EB Garamond */
  --t-small:   clamp(0.9375rem, 0.9006rem + 0.1553vw, 1rem);       /* 15→16  */
  --t-base:    clamp(1.125rem, 1.0881rem + 0.1553vw, 1.1875rem);   /* 18→19  */
  --t-lede:    clamp(1.25rem, 1.1762rem + 0.3106vw, 1.375rem);     /* 20→22  */
  --t-poem:    clamp(1.0625rem, 0.9887rem + 0.3106vw, 1.1875rem);  /* 17→19  */
  --t-dropcap: clamp(3.25rem, 2.7337rem + 2.1739vw, 4.125rem);     /* 52→66  */

  /* --- Line heights -------------------------------------------------------- */
  --lh-prose:   1.65;   /* SPEC §3 */
  --lh-poem:    1.5;
  --lh-display: 1.06;
  --lh-tight:   1.2;
  --lh-ui:      1.35;

  /* --- Tracking ------------------------------------------------------------ */
  --tr-micro: 0.24em;   /* folios, tags, catalogue numbers */
  --tr-meta:  0.09em;
  --tr-btn:   0.14em;

  /* --- Space scale (1.5-ish, rounded to sane pixel values) ----------------- */
  --sp-3xs: 0.25rem;
  --sp-2xs: 0.5rem;
  --sp-xs:  0.75rem;
  --sp-s:   1rem;
  --sp-m:   1.5rem;
  --sp-l:   2.25rem;
  --sp-xl:  3.5rem;
  --sp-2xl: 5rem;
  --sp-3xl: 7.5rem;
  --sp-section: clamp(3.5rem, 2.066rem + 6.0377vw, 7.5rem);  /* 56→120 */
  --sp-gutter:  clamp(1.25rem, 0.8019rem + 1.8868vw, 2.5rem); /* 20→40 */

  /* --- Measure & container widths -----------------------------------------
     MEASURED, NOT ASSUMED. The spec's suggested 34rem was checked by laying out
     ~6,000 characters of English prose in the real EB Garamond webfont at the
     locked 19px size and counting characters per rendered line:

        29rem → 63.6    31rem → 66.4    32rem → 68.6    34rem → 72.7

     EB Garamond sets narrow (mean advance 0.377em, ~7.16px at 19px), so 34rem
     would have delivered 73 characters, not 66. --measure is 31rem.
     Re-verify with the "Measure check" panel on dist/styleguide.html after any
     change to the prose size or the body family.                              */
  --measure:      31rem;    /* 496px = 66.4 characters of EB Garamond at 19px */
  --measure-poem: 26rem;    /* ~56 characters — poem lines must survive unwrapped */
  --w-wide:       46rem;    /* pull quotes, figures — breaks the measure */
  --w-page:       72rem;    /* standard grid container */
  --w-full:       90rem;    /* widest permitted container */

  /* --- Radii ---------------------------------------------------------------
     This is print, not product. Near-square by default; the one round thing on
     the site is the card-catalog punch hole.                                  */
  --r-0:    0;
  --r-hair: 1px;
  --r-card: 2px;
  --r-full: 999px;

  /* --- Hairlines ----------------------------------------------------------- */
  --hair:      color-mix(in srgb, currentColor 22%, transparent);
  --hair-soft: color-mix(in srgb, currentColor 12%, transparent);

  /* --- Z-index ------------------------------------------------------------- */
  --z-smoke:   0;
  --z-grain:   1;
  --z-content: 2;
  --z-sticky:  30;
  --z-nav:     50;
  --z-overlay: 100;
  --z-skip:    200;

  /* --- Motion -------------------------------------------------------------- */
  --dur-fast:   120ms;
  --dur:        220ms;
  --dur-slow:   480ms;
  --dur-flicker: 7.3s;   /* whole-sign irregular dropouts */
  --dur-buzz:    4.1s;   /* the failing period — coprime-ish with the above so */
                         /* the two never resolve into a repeating pattern     */
  --dur-drift:   48s;    /* opt-in smoke drift */
  --ease:      cubic-bezier(0.2, 0.7, 0.3, 1);
  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
}


/* =============================================================================
   02  GROUND CONTEXTS

   THE MOST IMPORTANT TWENTY LINES IN THIS FILE.

   Regression #3 on this project was cream text rendered on the cream paper
   ground — invisible, and invisible in the code diff too. It happened because
   components hard-coded their colours and then got moved between grounds.

   Fix: components never name a colour. They use --fg, --fg-muted, --accent,
   --accent-2, --rule, --focus. Those semantic tokens are redefined by whichever
   ground the component sits in, so a card is correct on void and on paper
   without a single component-level override.

   Note what is deliberately absent: --cyan is NOT in this cascade. It cannot be
   reached through --accent / --accent-2 at all. See §15 "Cyan discipline".
   ========================================================================== */

.on-void,
.ground-void,
.nav,
.footer {
  --fg:        var(--paper);
  --fg-muted:  var(--haze);
  --fg-faint:  var(--haze-dim);
  --bg:        var(--void);
  --accent:    var(--pink);
  --accent-2:  var(--amber);
  --focus:     var(--amber);        /* 10.4:1 on void */
  --glow:      var(--pink);
  color: var(--fg);
  background-color: var(--bg);
}

.on-paper,
.ground-paper {
  --fg:        var(--paper-text);
  --fg-muted:  var(--ink-muted);
  --fg-faint:  color-mix(in srgb, var(--paper-text) 66%, var(--paper)); /* 5.1:1 */
  --bg:        var(--paper);
  --accent:    var(--pink-ink);     /* 5.6:1 — the raw pink is 2.9:1 and unusable */
  --accent-2:  var(--amber-ink);    /* 5.8:1 — the raw amber is 1.6:1 and unusable */
  --focus:     var(--pink-ink);
  --glow:      var(--pink-ink);     /* bloom is suppressed on paper anyway */
  color: var(--fg);
  background-color: var(--bg);
}


/* =============================================================================
   03  RESET & BASE
   ========================================================================== */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The nav is sticky (~76px at desktop, 64 at mobile), so an anchor jump would
     otherwise land with its heading tucked under the bar. Offset every in-page
     scroll target by the nav's height plus a little air — this covers the awards
     deep link (/contributors/#up-for-awards) and the existing A–Z / contents
     jumps alike. */
  scroll-padding-top: 5.5rem;
}

body {
  margin: 0;
  /* clip, not hidden — `hidden` on body silently breaks position:sticky nav */
  overflow-x: clip;
  background-color: var(--void);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: var(--t-base);
  line-height: var(--lh-prose);
  font-variant-numeric: oldstyle-num proportional-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, ol, ul, pre {
  margin: 0;
}

ul[class], ol[class] { list-style: none; padding: 0; }

img, svg, video, canvas, picture {
  display: block;
  max-width: 100%;
  height: auto;
}

/* Prevent long titles/URLs from forcing horizontal overflow at 380px. The label
   classes are included because eyebrows and meta lines are often <span>s (not in
   the block list above), and a long single-word label could otherwise push width. */
h1, h2, h3, h4, p, li, dd, dt, figcaption, blockquote, td, th,
.eyebrow, .meta {
  overflow-wrap: break-word;
}

button, input, select, textarea {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
}

table { border-collapse: collapse; width: 100%; }

hr {
  border: 0;
  block-size: 1px;
  background: var(--hair);
  margin-block: var(--sp-l);
}

::selection {
  background: var(--pink);
  color: var(--void);
}

/* Layout containers ------------------------------------------------------- */
.wrap        { width: min(100% - (2 * var(--sp-gutter)), var(--w-page)); margin-inline: auto; }
.wrap--full  { width: min(100% - (2 * var(--sp-gutter)), var(--w-full)); margin-inline: auto; }
.wrap--wide  { width: min(100% - (2 * var(--sp-gutter)), var(--w-wide)); margin-inline: auto; }
.wrap--measure { width: min(100% - (2 * var(--sp-gutter)), var(--measure)); margin-inline: auto; }
.wrap--flush { padding-inline: var(--sp-gutter); }

.stack > * + * { margin-top: var(--sp-m); }
.stack--tight > * + * { margin-top: var(--sp-s); }
.stack--loose > * + * { margin-top: var(--sp-xl); }


/* =============================================================================
   04  TYPOGRAPHY

   Frank Ruhl Libre is driven through ONE custom property rather than font-weight,
   so every component can dial the axis independently. font-variation-settings
   overrides font-weight, so --dsp-wght IS the weight for display type.

     wght  300–900

   There is no optical-size axis on this face and no italic cut. Both existed on
   Frank Ruhl Libre and both are gone; --dsp-opsz was removed rather than left in
   place, because an unsupported axis is ignored silently and would have read as
   working. Small display type therefore carries its own weight literally — the
   metadata and eyebrow rules sit high in the range on purpose.

   Third display face on this project, replacing Frank Ruhl Libre on 2026-08-14 at
   Jonathan's direction, chosen off /type-specimens. Frank Ruhl Libre is a 1908
   Hebrew face extended to Latin: high contrast like the Frank Ruhl Libre, but with blunt
   serifs instead of hairlines, so it holds together at small sizes and as light
   type on the dark ground — the two places the display face was fragile.
   ========================================================================== */

.display,
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4 {
  --dsp-wght: 500;
  font-family: var(--font-display);
  font-variation-settings: "wght" var(--dsp-wght);
  font-weight: normal;         /* weight comes from the axis, not this */
  line-height: var(--lh-tight);
  text-wrap: balance;
  color: var(--fg, inherit);
}

h1, .h1 {
  --dsp-wght: 460;
  font-size: var(--t-h1);
  line-height: var(--lh-display);
  letter-spacing: -0.012em;
}

h2, .h2 {
  --dsp-wght: 500;
  font-size: var(--t-h2);
  line-height: 1.14;
  letter-spacing: -0.008em;
}

h3, .h3 {
  --dsp-wght: 590;
  font-size: var(--t-h3);
  line-height: 1.2;
}

h4, .h4 {
  --dsp-wght: 660;
  font-size: var(--t-h4);
  line-height: 1.3;
}

/* Hero display — the biggest display type on the site short of the sign */
.display {
  --dsp-wght: 420;
  font-size: var(--t-display);
  line-height: var(--lh-display);
  letter-spacing: -0.02em;
}

/* Metadata voice. This replaces the monospace that was cut: display type at low
   optical size, WONK off, heavily tracked, uppercase. It reads as a caption in
   a printed magazine rather than as a terminal. */
.meta,
.eyebrow,
.tag,
.folio,
.issue-bibmeta__label,
.issue-status,
.btn,
.nav__link,
.breadcrumb,
.pieceflow__label,
.callcard__status,
.colophon__label {
  --dsp-wght: 620;
  font-family: var(--font-display);
  font-variation-settings: "wght" var(--dsp-wght);
  font-weight: normal;
  font-variant-numeric: lining-nums tabular-nums;
}

.eyebrow {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-muted);
}
.eyebrow--signal { color: var(--accent); }

.meta {
  font-size: var(--t-meta);
  letter-spacing: var(--tr-meta);
  line-height: var(--lh-ui);
  color: var(--fg-muted);
}

/* --- Prose ---------------------------------------------------------------
   19px EB Garamond, 66-character measure, 1.65 leading. This is the single
   highest-impact fix in the rebuild: the live site sets prose in Montserrat at
   16px across ~105 characters.

   .prose is a three-track BLEED GRID, not a max-width block. The centre track
   carries the measure; the two side tracks are the only space anything is
   allowed to break out into, and they are `minmax(0, 1fr)` so they collapse to
   nothing rather than pushing the page sideways.

   The first version of this used `margin-inline: calc(-1 * clamp(…))` on the
   pull quote, computed against 100vw. That is wrong whenever the column is not
   centred in the viewport — at 768px the quote hung 69px off the left edge and
   was clipped. The grid cannot get that wrong, because a track can only give
   away space it actually has.                                                */
.prose {
  --bleed: 5rem;
  display: grid;
  grid-template-columns:
    [wide-start] minmax(0, 1fr)
    [text-start] min(100%, var(--measure)) [text-end]
    minmax(0, 1fr) [wide-end];
  max-width: calc(var(--measure) + (2 * var(--bleed)));
  margin-inline: auto;
  font-family: var(--font-body);
  font-size: var(--t-base);
  line-height: var(--lh-prose);
  font-variant-numeric: oldstyle-num proportional-nums;
  hanging-punctuation: first last;
}
.prose > * { grid-column: text; min-width: 0; }
.prose > .pullquote,
.prose > .bleed { grid-column: wide; }

/* .prose centres itself; the class is kept so existing markup does not break. */
.prose--centred { margin-inline: auto; }

.prose > * + * { margin-top: 1.15em; }

.prose p { text-wrap: pretty; }

.prose h2 { margin-top: 2em; margin-bottom: 0.6em; }
.prose h3 { margin-top: 1.8em; margin-bottom: 0.5em; }

.prose em { font-style: italic; }
.prose strong { font-weight: 640; }
.prose small { font-size: var(--t-small); color: var(--fg-muted); }

/* EMPHASIS INSIDE DISPLAY TYPE.
   Frank Ruhl Libre has no italic cut. Left alone, every one of these renders as
   a browser-synthesised oblique — the roman sheared by a fixed angle — and a
   high-contrast serif is where that shows worst, because shearing skews the
   thick/thin axis along with the shape. The alternatives were to fake it, to
   drop the italic, or to borrow a real one. The italic is carrying meaning in
   all four of these (a cited artwork title, an issue subtitle, a contributor's
   name in a run of metadata), so dropping it loses information, and it is
   borrowed from EB Garamond, which ships a drawn italic.

   NO SIZE COMPENSATION, and that is measured rather than assumed: rendered at
   100px, both faces cap at exactly 66px, so title-case emphasis — which is what
   nearly all of this is — aligns with the display type around it without help.
   The x-heights do differ (47 vs 42, 11%), so lowercase emphasis sits slightly
   smaller. That is two typefaces looking like two typefaces, which is what an
   italic in a different face is for; scaling to match would throw out the cap
   alignment to fix the less visible of the two. */
.figcaption em, .figcaption i, .figcaption cite,
.meta em, .meta i, .meta cite,
.coverplate__credit em, .coverplate__credit i, .coverplate__credit cite,
.issue-headline em, .issue-headline i, .issue-headline cite {
  font-family: var(--font-body);
  font-variation-settings: normal;   /* --dsp-wght names an axis EB Garamond lacks */
  font-style: italic;
  font-weight: 500;
}

.prose ul, .prose ol { padding-left: 1.4em; }
.prose li + li { margin-top: 0.45em; }
.prose li::marker { color: var(--accent-2); }

.prose blockquote {
  margin-inline: 0;
  padding-left: 1.15em;
  border-left: 2px solid var(--accent-2);
  font-style: italic;
  color: var(--fg-muted);
}

.prose figure { margin-block: var(--sp-l); }
.prose figcaption,
.figcaption {
  margin-top: var(--sp-2xs);
  font-family: var(--font-display);
  font-variation-settings: "wght" 560;
  font-size: var(--t-meta);
  letter-spacing: var(--tr-meta);
  line-height: var(--lh-ui);
  color: var(--fg-muted);
}

/* Standfirst / intro paragraph */
.lede {
  max-width: var(--measure);
  font-size: var(--t-lede);
  line-height: 1.5;
  color: var(--fg);
  text-wrap: pretty;
}
.lede--muted { color: var(--fg-muted); }


/* =============================================================================
   05  POETRY

   30 of 113 pieces are poems, and mobile currently destroys their lineation.
   Breaking a poem's line breaks changes what the published work says, so this
   is an editorial obligation rather than a nicety (SPEC §3.1).

   REQUIRED MARKUP — one element per authored line:

     <div class="poem">
       <div class="stanza">
         <span class="poem__line">First authored line</span>
         <span class="poem__line">Second authored line</span>
       </div>
       <div class="stanza"> … </div>
     </div>

   Only that shape gives a correct hanging indent on EVERY wrapped line, so a
   forced wrap is visually distinguishable from an authored break. The
   `<p>` + `<br>` shape is supported below as a degraded fallback for scraped
   bodies that cannot be split, but `text-indent: … each-line` is not yet
   universally supported, so prefer .poem__line.
   ========================================================================== */

.poem {
  --poem-hang: 1.5em;
  max-width: var(--measure-poem);
  font-family: var(--font-body);
  font-size: var(--t-poem);
  line-height: var(--lh-poem);
  text-align: left;             /* never justify */
  hyphens: none;                /* never hyphenate */
  -webkit-hyphens: none;
  font-variant-numeric: oldstyle-num;
}
.poem--centred { margin-inline: auto; }

.poem .stanza + .stanza { margin-top: 1.5em; }  /* real spacing, not empty <p> */

.poem__line {
  display: block;
  text-indent: calc(-1 * var(--poem-hang));
  padding-left: var(--poem-hang);
  text-align: left;
  hyphens: none;
  -webkit-hyphens: none;
  text-wrap: nowrap;            /* try to hold the authored line … */
  white-space: normal;          /* … but wrap rather than overflow (see below) */
}
/* text-wrap:nowrap on a block still wraps when it must, because white-space is
   normal; it only biases the line-breaker. Overflow is impossible. */

.poem__line--blank { min-height: 1em; }

/* Indented / stepped lines an author set deliberately */
.poem__line--indent-1 { padding-left: calc(var(--poem-hang) + 2em); }
.poem__line--indent-2 { padding-left: calc(var(--poem-hang) + 4em); }

/* Fallback for scraped stanzas that are one <p> with <br> between lines */
.poem p {
  text-indent: calc(-1 * var(--poem-hang));
  padding-left: var(--poem-hang);
  margin: 0;
}
.poem p + p { margin-top: 1.5em; }
@supports (text-indent: 1em each-line) {
  .poem p { text-indent: calc(-1 * var(--poem-hang)) each-line; }
}

/* Poem title sits closer to its body than a prose heading does */
.poem-title {
  --dsp-wght: 460;
  font-size: var(--t-h2);
  margin-bottom: var(--sp-s);
}


/* =============================================================================
   05b  AUTHORIAL FORMATTING HOOKS  (SPEC §9.1)

   "Stay true to the original format of the piece. Artists are sensitive about
   that." The scraper preserves the author's formatting as data-* attributes on
   the body markup instead of as inline styles. Without the rules below the
   formatting survives the scrape and is then thrown away at render, which is
   the worst of both worlds: it looks like it was never captured.

   Counts verified against the built content/pieces.json, not assumed:

     data-preserve-space="true"   1,214 blocks across 47 pieces
                                  — 42 fiction, 4 poetry, 1 untyped
     data-stanza-break="true"       920 blocks across 97 pieces
     data-indent="N"                 56 blocks across  8 pieces, N ∈ {1, 4}
     data-source-align="justify"    223 blocks across  8 pieces — NEVER applied
     style="text-align:center"       62 pieces — inline, and nothing here fights it

   The single most load-bearing fact: preserve-space is overwhelmingly FICTION,
   not poetry. 1,121 of those 1,214 blocks open with a literal tab character —
   it is a first-line paragraph indent. So these rules live outside .poem and
   apply wherever the body is rendered.
   ========================================================================== */

/* --- Preserved leading whitespace ---------------------------------------
   Zero of the 1,214 blocks contain a newline, so pre-wrap cannot introduce a
   spurious hard break; it only restores the tabs and space runs the author
   typed. Verified before enabling.                                          */
[data-preserve-space="true"] {
  white-space: pre-wrap;
  tab-size: 3;
  -moz-tab-size: 3;
}

/* The verse renderer rebuilds each line as a `.poem__line` span and does not carry
   the attribute across, so the rule above never reached a poem — tabs computed at
   the browser default of 8 and over-indented every tabbed line. Set on the element
   that actually ships. 3 because these are line-internal placement tabs, and 8
   pushes a placed word off a narrow column. */
.poem__line {
  tab-size: 3;
  -moz-tab-size: 3;
}

/* pre-wrap composes with the hanging indent rather than fighting it:
   padding-left holds every wrapped line at the hang, text-indent pulls only
   the FIRST line back to the margin, and the author's tab then pushes that
   first line in. Authored indent preserved, wrapped lines still distinct. */
.poem__line[data-preserve-space="true"],
.poem p[data-preserve-space="true"] {
  text-indent: calc(-1 * var(--poem-hang));
  padding-left: var(--poem-hang);
}

/* A body that indents its paragraphs with a tab should NOT also get an
   inter-paragraph margin — that is the same structural signal twice, and it
   double-spaces a piece that the author set solid. The author's own blank
   lines are already present as data-stanza-break elements, so structure is not
   lost. Scoped with :has() so it only affects bodies that actually use tabs. */
.prose:has(p[data-preserve-space="true"]) > p + p { margin-top: 0; }

/* --- Stanza / blank-line breaks -----------------------------------------
   Emitted as <p data-stanza-break="true"></p>, one per blank line the author
   left, and they legitimately appear in runs. Each is given exactly one line
   of height so a double blank line stays a double blank line — the shape of
   the poem is the work.

   TEMPLATE CONTRACT: emit these with aria-hidden="true". An empty <p> is
   already skipped by screen readers, but say it out loud.                    */
p[data-stanza-break="true"] {
  height: 1.5em;   /* fallback for browsers without the lh unit */
  height: 1lh;
  margin: 0;
  padding: 0;
}

/* A run of stanza breaks at the very start or very end of a body would open a
   hole above the first line or below the last. These two selectors collapse
   the whole leading run and the whole trailing run, not merely the first and
   last element of each. */
p[data-stanza-break="true"]:not(:not([data-stanza-break="true"]) ~ *),
p[data-stanza-break="true"]:not(:has(~ :not([data-stanza-break="true"]))) {
  height: 0;
}

/* --- Preserved indentation levels ---------------------------------------
   Only levels 1 and 4 exist in the current content; 2 and 3 are defined so a
   future scrape cannot silently render an unstyled level. Measured in em of
   the block's own type, never in fixed pixels, so indentation scales with the
   fluid prose and poem steps.                                                */
[data-indent="1"] { padding-left: 2em; }
[data-indent="2"] { padding-left: 4em; }
[data-indent="3"] { padding-left: 6em; }
[data-indent="4"] { padding-left: 8em; }

/* Combined with the poem hang, the indent stacks on top of the hang so a
   wrapped line inside an indented line still reads as a wrap. */
.poem [data-indent="1"] { padding-left: calc(var(--poem-hang) + 2em); }
.poem [data-indent="2"] { padding-left: calc(var(--poem-hang) + 4em); }
.poem [data-indent="3"] { padding-left: calc(var(--poem-hang) + 6em); }
.poem [data-indent="4"] { padding-left: calc(var(--poem-hang) + 8em); }

/* When a line carries BOTH an indent level and authored leading whitespace, the
   whitespace wins and the CSS indent yields.

   These are two records of one intention, and applying both doubles it — on
   "Portrait in White" line 24 began 241px in against line 23's 95px, so two lines
   the poet set level landed a quarter of the column apart.

   The authored characters are the primary record: they are what the poet typed.
   The indent level is metadata derived alongside them. Note this was invisible on
   Wix, where the default `white-space: normal` collapsed leading whitespace
   entirely, so only the indent level ever showed — restoring the characters is
   what surfaced the conflict. Logged in qa/OPEN-FACTS.md; if the level turns out to
   be authoritative, delete this block and strip the whitespace instead. */
[data-indent][data-preserve-space="true"] { padding-left: 0; }
.poem [data-indent][data-preserve-space="true"] { padding-left: var(--poem-hang); }

/* At 380px an 8em indent is most of the column. Halve the deep levels rather
   than let them push text off the line. */
@media (max-width: 479px) {
  [data-indent="3"] { padding-left: 3.5em; }
  [data-indent="4"] { padding-left: 4.5em; }
  .poem [data-indent="3"] { padding-left: calc(var(--poem-hang) + 3.5em); }
  .poem [data-indent="4"] { padding-left: calc(var(--poem-hang) + 4.5em); }
}

/* --- Justification: retained as data, never applied ---------------------
   223 blocks across 8 pieces carry data-source-align="justify" because the Wix
   source justified them. SPEC §3.1 forbids justifying poetry, and justified
   ragged-right prose at a 66-character measure produces rivers. The attribute
   is a record of the source, not an instruction.

   Nothing in this stylesheet sets `text-align: justify` — grep it and you will
   find no hits. This rule is a standing guard so that a future edit cannot
   quietly turn it back on.                                                   */
[data-source-align="justify"] { text-align: start; }

/* Deliberate centring the author set survives as an inline style, which beats
   every rule in this file. Confirmed: no selector here sets text-align on a
   body-level block other than the guard above and .poem/.poem__line, both of
   which lose to an inline style. */

/* --- Superscript / subscript ---------------------------------------------
   Two pieces use <sup> for ordinals. line-height: 0 stops it from opening the
   line it sits in — the default vertical-align: super otherwise increases the
   line box and breaks the prose rhythm for that one line.                    */
sup, sub {
  font-size: 0.72em;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}
sup { top: -0.45em; }
sub { bottom: -0.22em; }

/* --- Concrete / visual poetry -------------------------------------------
   Pieces that use spatial arrangement as meaning must not be squeezed into the
   standard measure (SPEC §9.1). This opts a single piece out of the cap while
   keeping the page itself from ever scrolling sideways: the geometry is
   preserved and the OVERFLOW IS CONTAINED, scrolling inside its own box.

   Apply to the .poem container: class .poem--concrete, or data-concrete="true"
   from the content record.                                                   */
.poem--concrete,
.poem[data-concrete="true"] {
  max-width: none;
  width: 100%;
  overflow-x: auto;
  white-space: pre-wrap;
  overscroll-behavior-x: contain;
}
/* No hang and no indent normalisation: on a concrete piece every space is
   authored, and a hanging indent would be the stylesheet editing the poem. */
.poem--concrete .poem__line,
.poem[data-concrete="true"] .poem__line,
.poem--concrete p,
.poem[data-concrete="true"] p {
  text-indent: 0;
  padding-left: 0;
  white-space: pre-wrap;
}


/* =============================================================================
   06  GROUNDS & SECTION TRANSITIONS

   Full-bleed alternating grounds. Dark carries nav, hero, footer and
   atmospheric interstitials. Paper carries anything meant to be READ AT LENGTH.
   An earlier iteration read too dark overall — when in doubt, give editorial
   content more paper (SPEC §2.1 balance note).

   THE FADE — SPEC §6, a real regression:
   A negative top margin of about 120px on the paper section pulled the opening
   paragraph up into the gradient and made it unreadable. The fade is painted
   INSIDE the top 100px of the paper section as a pseudo-element, and the
   section carries `padding-top: 100px` so content clears it entirely.

   NEVER a negative margin here. tools/build.mjs greps the built output for that
   exact declaration and fails the build on it, which is why this comment
   describes the pattern in prose rather than quoting it — a quoted example
   trips the very guard that exists to catch the real thing.
   ========================================================================== */

.ground-void,
.ground-paper {
  position: relative;
  isolation: isolate;
  padding-block: var(--sp-section);
}

/* These are SECTION classes. `_layout.mjs` also puts one on <body> so the page's
   opening ground sets colour tokens for anything sitting outside a section — but
   a body does not want section padding, and inheriting it pushed ~110px of dead
   space above the sticky nav on every page. Tokens and background yes, box no.
   The grain/smoke pseudo-elements are suppressed here as well: the layout ships a
   dedicated `.grain` overlay, and running both stacked the texture twice. */
body.ground-void,
body.ground-paper {
  padding-block: 0;
  isolation: auto;
}
body.ground-void::before,
body.ground-void::after {
  content: none;
}

.ground-void > *,
.ground-paper > * {
  position: relative;
  z-index: var(--z-content);
}

.ground-void  { background-color: var(--void); }
.ground-paper { background-color: var(--paper); }

/* --- void → paper ------------------------------------------------------- */
.ground-paper {
  --fade: 160px;
  padding-top: var(--fade);   /* content clears the fade. Never a negative margin */
}

/* THE OUTGOING HALF. Everything below fades INTO the arriving section; this
   fades the leaving one out, so a ground change is ~320px of blend instead of
   200px that all happens on one side of a hard line. It is painted as the
   section's own background-image rather than a pseudo-element because both
   pseudos on a ground section are already spoken for — ::before is the incoming
   fade or the grain, ::after is the smoke — and a third effect had nowhere to
   go. background-image sits above background-color and below content, which is
   exactly the layer this wants.

   Only in the cream → black direction. That is the one that reads as an edge:
   the eye is light-adapted on the cream, so the arrival of black lands hard,
   while going the other way it is already dark-adapted and the cream arrives
   gently. Symmetry here would be decoration. */
.ground-paper:has(+ .ground-void) {
  --fade-out: 120px;
  /* Text clears the darkening band. Dark ink on a cream ground that is on its
     way to black is the one place this could quietly fail contrast, and no
     audit would catch it because both tokens are still the ones that passed. */
  padding-bottom: 140px;
  background-image: linear-gradient(
    to bottom,
    transparent calc(100% - var(--fade-out)),
    color-mix(in srgb, var(--void) 6%, transparent)  calc(100% - (var(--fade-out) * 0.72)),
    color-mix(in srgb, var(--void) 18%, transparent) calc(100% - (var(--fade-out) * 0.46)),
    color-mix(in srgb, var(--void) 32%, transparent) calc(100% - (var(--fade-out) * 0.24)),
    color-mix(in srgb, var(--void) 50%, transparent) 100%   /* meets the 50% above */
  );
  background-repeat: no-repeat;
}

.ground-void + .ground-paper::before,
.ground-paper.has-fade-top::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: var(--fade);
  z-index: var(--z-grain);
  pointer-events: none;
  /* eased stops so the smoke dissipates rather than banding */
  background: linear-gradient(
    to bottom,
    var(--void) 0%,
    color-mix(in srgb, var(--void) 88%, transparent) 12%,
    color-mix(in srgb, var(--void) 72%, transparent) 26%,
    color-mix(in srgb, var(--void) 52%, transparent) 42%,
    color-mix(in srgb, var(--void) 33%, transparent) 58%,
    color-mix(in srgb, var(--void) 18%, transparent) 72%,
    color-mix(in srgb, var(--void) 7%, transparent) 86%,
    transparent 100%
  );
}

/* --- paper → void -------------------------------------------------------
   This is the edge that read as harsh, and the cause was not the gradient.
   `.ground-void::before` carries the grain as well as this fade, and the grain
   rule sets `opacity: 0.055`. This selector is more specific, so its gradient
   won — but nothing here contested the opacity, so the whole cream ramp was
   being drawn at five and a half percent and the eye saw a hard edge with a
   faint smudge on it. Hence the explicit `opacity: 1` below: it is not
   redundant, it is the fix.

   Deeper than the downward fade (140px against 100px) on purpose. Going from
   cream INTO black is the harder direction — the eye is adapted to the light
   ground and reads the arrival of black as an edge — so it gets more room and
   more stops. Both use eased stops rather than the default two, because a
   two-stop linear ramp between two flat colours bands visibly on a wide
   viewport. */
.ground-paper + .ground-void,
.ground-void.has-fade-top {
  --fade-up: 200px;
  /* Content clears the fade, same contract as `.ground-paper` above. Without
     this the first line of a void section can sit in the cream haze, where it
     is light type on a half-lit ground and the measured contrast is not the
     contrast anyone checked. Never a negative margin (SPEC §6).

     Flat, not `max(var(--sp-section), …)`: --sp-section tops out at 7.5rem =
     120px, so the fade is always the taller of the two and the max() only ever
     returned the same number. It also silently failed to parse here, which is
     the more useful reason to have dropped it. */
  padding-top: var(--fade-up);
}

.ground-paper + .ground-void::before,
.ground-void.has-fade-top::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  bottom: auto;   /* the grain rule sets inset:0; this pseudo is a band, not a fill */
  height: var(--fade-up);
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: 1;
  /* THE HANDOFF IS AT 50%, and this is the whole trick. The section above ends
     its own ramp at 50% void over cream; this one begins at 50% cream over void.
     Those are the same colour, so the two grounds meet with no step at all and
     the seam stops being a place. Do not "fix" this to 100% — a full-opacity
     first stop only made sense when the section above was flat cream, and with
     the outgoing ramp in place it puts a hard cream line at the join.

     Stops are bunched toward the top and spread toward the bottom rather than
     spaced evenly. An even ramp spends equal distance on equal ALPHA, but the
     eye reads equal steps of LIGHTNESS, and cream over black loses most of its
     lightness in the first third of its alpha. Front-loading the stops puts the
     distance where the lightness is actually moving. */
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--paper) 50%, transparent) 0%,
    color-mix(in srgb, var(--paper) 42%, transparent) 10%,
    color-mix(in srgb, var(--paper) 34%, transparent) 21%,
    color-mix(in srgb, var(--paper) 26%, transparent) 33%,
    color-mix(in srgb, var(--paper) 19%, transparent) 45%,
    color-mix(in srgb, var(--paper) 13%, transparent) 57%,
    color-mix(in srgb, var(--paper) 8%, transparent) 69%,
    color-mix(in srgb, var(--paper) 4%, transparent) 81%,
    color-mix(in srgb, var(--paper) 1.5%, transparent) 91%,
    transparent 100%
  );
}
/* The clearance for the fade above is declared once, on the same rule that sets
   --fade-up. A second `.ground-paper + .ground-void { padding-top: 100px }` used
   to sit here at identical specificity, so it silently won on source order and
   pinned the clearance to the old 100px while the fade grew to 140 — the first
   line of every void section would have sat 40px inside the cream haze. */

/* Reading pane: a lit page floating on the dark ground. This was a deliberate
   editorial choice on the old site and is kept on purpose (SPEC §3). */
.readingpane {
  --fg:        var(--paper-text);
  --fg-muted:  var(--ink-muted);
  --fg-faint:  color-mix(in srgb, var(--paper-text) 66%, var(--paper));
  --bg:        var(--paper);
  --accent:    var(--pink-ink);
  --accent-2:  var(--amber-ink);
  --focus:     var(--pink-ink);
  --glow:      var(--pink-ink);
  /* Content box lands on exactly --measure + 2×--bleed, so a .prose inside a
     reading pane gets its full bleed track and nothing is clipped.
     Padding is small at 380px on purpose: at that width the pane's padding is
     competing with the poem's lineation, and lineation wins. */
  --pad: var(--sp-s);
  position: relative;
  z-index: var(--z-content);
  width: min(100%, calc(var(--measure) + 10rem + (2 * var(--pad))));
  margin-inline: auto;
  padding: var(--sp-xl) var(--pad) var(--sp-2xl);
  color: var(--fg);
  background-color: var(--bg);
  border-radius: var(--r-card);
  box-shadow:
    0 1px 0 color-mix(in srgb, var(--paper) 60%, transparent),
    0 24px 60px -20px rgb(0 0 0 / 0.75),
    0 2px 8px rgb(0 0 0 / 0.4);
}
/* When the pane is a direct child of a ground section it supplies its own page
   gutter; nested inside a .wrap the wrap has already done that, and taking it
   twice squeezed the reading column to 228px at 380px. */
.ground-void > .readingpane,
.ground-paper > .readingpane {
  width: min(100% - (2 * var(--sp-gutter)), calc(var(--measure) + 10rem + (2 * var(--pad))));
}


/* =============================================================================
   07  ATMOSPHERE

   Smoke = room atmosphere, not a graphic. Radial gradients in `screen` blend
   mode over the void. Deliberately warm only — pink and amber and a warm
   near-white. No cold light in the room; cyan stays a signal, never air.

   Grain = SVG feTurbulence at very low opacity, so the dark ground never reads
   as flat digital black.
   ========================================================================== */

.ground-void::after,
.atmos::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-smoke);
  pointer-events: none;
  mix-blend-mode: screen;
  background:
    radial-gradient(62% 46% at 16% 8%,  rgb(255 165 77 / 0.11), transparent 70%),
    radial-gradient(54% 42% at 86% 26%, rgb(255 46 136 / 0.08), transparent 68%),
    radial-gradient(78% 58% at 48% 104%, rgb(255 165 77 / 0.07), transparent 72%),
    radial-gradient(96% 74% at 50% 46%, rgb(255 228 198 / 0.035), transparent 76%);
}

/* Grain overlay. Applied to both grounds — on paper it reads as stock texture,
   on void it keeps the black from going digital-flat.

   Two elements carry it and they cannot double, which is worth spelling out
   because it looks like they should. Sections paint their own. The page-level
   `.grain` div `_layout.mjs` emits sits at --z-grain (1) while `main` sits at
   --z-content (2) with an opaque background-color on every section, so the div
   is painted over wherever a section exists and only shows through on pages
   that have none. That is 125 piece pages, which is exactly the set that was
   coming out as flat digital black: `.ground-void > *` catches the div and
   makes it `position: relative`, an empty div is 0px tall, and `inset: 0` on
   the pseudo below resolved against a zero-height box. It needs its own
   geometry. `body.ground-void` is already `position: relative`, so absolute
   sizes it to the whole document rather than the viewport — the grain scrolls
   with the page instead of sitting still behind it like a screen door. */
.grain {
  position: absolute;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
}

.ground-void::before,
.grain::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: 0.055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}
/* .ground-void + .ground-paper::before is the fade, so paper grain is opt-in
   through .grain-paper to avoid fighting over the same pseudo-element. */
.ground-paper::after,
.grain-paper::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

/* Opt-in only. The homepage's ONE animated element is the sign; do not add
   drift to a page that already has it. */
.ground-void--drift::after {
  animation: smoke-drift var(--dur-drift) var(--ease) infinite alternate;
}
@keyframes smoke-drift {
  from { transform: translate3d(-1.5%, 0.8%, 0) scale(1.04); }
  to   { transform: translate3d(1.8%, -1.2%, 0) scale(1.09); }
}

/* --- Neon bloom ---------------------------------------------------------
   A real multi-layer bloom: a white-hot core (the excited gas), then four
   falloff layers of increasing radius and decreasing alpha (the halo in the
   room's haze). Never a single text-shadow — one shadow reads as a drop
   shadow, not as light.
   Drive it with --glow. Only meaningful on the void ground.                */
.glow,
.neon {
  color: var(--glow);
  text-shadow:
    0 0 1px  color-mix(in srgb, #fff 62%, var(--glow)),
    0 0 5px  color-mix(in srgb, var(--glow) 92%, transparent),
    0 0 14px color-mix(in srgb, var(--glow) 62%, transparent),
    0 0 38px color-mix(in srgb, var(--glow) 34%, transparent),
    0 0 86px color-mix(in srgb, var(--glow) 16%, transparent);
}
.glow--amber { --glow: var(--amber); }
.glow--soft {
  text-shadow:
    0 0 1px  color-mix(in srgb, #fff 45%, var(--glow)),
    0 0 8px  color-mix(in srgb, var(--glow) 55%, transparent),
    0 0 26px color-mix(in srgb, var(--glow) 26%, transparent);
}
/* Bloom is a dark-room effect. On paper it looks like a printing fault. */
.ground-paper .glow,
.ground-paper .neon,
.readingpane .glow,
.readingpane .neon { text-shadow: none; color: var(--accent); }

/* Box bloom, for the rare lit surface (status dot, active tab underline) */
.glow-box {
  box-shadow:
    0 0 4px  color-mix(in srgb, var(--glow) 85%, transparent),
    0 0 12px color-mix(in srgb, var(--glow) 50%, transparent),
    0 0 30px color-mix(in srgb, var(--glow) 22%, transparent);
}


/* =============================================================================
   08  PRINT-MAGAZINE VOCABULARY
   Use deliberately, not all at once (SPEC §5).
   ========================================================================== */

/* --- Hand-drawn rule -----------------------------------------------------
   Not a 1px line. An SVG path with a deliberate wobble, drawn twice at a
   slight offset for an inked-twice feel. Delivered as a MASK with
   `background-color: currentColor`, so the rule inherits whichever ground it
   sits in — no colour is hard-coded and it can never go invisible.          */
.rule {
  --rule-h: 9px;
  display: block;
  width: 100%;
  height: var(--rule-h);
  border: 0;
  margin: 0;
  background-color: currentColor;
  opacity: 0.45;
  -webkit-mask-image: var(--rule-svg);
  mask-image: var(--rule-svg);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  --rule-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 9' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round'%3E%3Cpath d='M2 4.6C64 3.1 118 6.2 182 4.5s118-2.6 184 0.7 132 3.1 232 2.2' stroke-width='1.5'/%3E%3Cpath d='M6 5.4C72 4.2 126 7 190 5.4s112-2.1 178 1.1 138 2.4 224 1.6' stroke-width='0.7' opacity='0.55'/%3E%3C/g%3E%3C/svg%3E");
}
.rule--short { width: 5.5rem; }
.rule--signal { color: var(--accent); opacity: 0.9; }
.rule--faint { opacity: 0.22; }

/* --- Asterism ------------------------------------------------------------
   Section break inside prose. Markup:
   <div class="asterism" role="separator" aria-label="Section break"></div>  */
/* The section / scene break. Was a tracked ⁂ glyph; now a hand-drawn wisp of
   smoke rising between two neon embers — a nod to both halves of the name, and
   kin to the hand-drawn rule under the nav (§ .nav::after), which is drawn the
   same way. Painted as a MASK over --accent-2 so it takes the divider's colour
   per ground automatically: amber on the void, amber-ink on paper. The mark is
   decorative; the element keeps role="separator" + aria-label in the templates,
   so assistive tech still announces the break. */
.asterism {
  --smoke-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 44'%3E%3Cpath d='M30 42C25 34 35 31 30 23C25 16 35 12 30 4' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round'/%3E%3Ccircle cx='18' cy='25' r='1.8' fill='%23000'/%3E%3Ccircle cx='42' cy='19' r='1.8' fill='%23000'/%3E%3C/svg%3E");
  margin-block: var(--sp-xl);
  height: 44px;
  background-color: var(--accent-2);
  -webkit-mask-image: var(--smoke-mark);
  mask-image: var(--smoke-mark);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: 60px 44px;
  mask-size: 60px 44px;
}

/* --- Drop cap ------------------------------------------------------------
   Three lines at full optical size, where the display face contrast is widest. Apply
   .prose--dropcap to the prose block, or .dropcap to a single paragraph.

   Implemented with `float`, deliberately, and NOT with `initial-letter`.
   Chrome reports `CSS.supports('initial-letter','3')` as true, but the letter
   measured at the inherited 18px rather than spanning three lines — an
   @supports branch on it silently deletes the drop cap on the browsers most
   readers use. This project has a history of visual bugs that are invisible in
   the diff; a progressive-enhancement branch nobody can screenshot is exactly
   that shape of risk. Float is universally supported and predictable.        */
.prose--dropcap > p:first-of-type::first-letter,
.dropcap::first-letter {
  float: left;
  font-family: var(--font-display);
  font-variation-settings: "wght" 520;
  font-size: var(--t-dropcap);
  line-height: 0.78;
  padding-top: 0.06em;
  padding-right: 0.07em;
  margin-bottom: -0.04em;
  color: var(--accent);
}

/* --- Pull quote ----------------------------------------------------------
   Display face, breaking the measure. Inside .prose it is placed in the `wide`
   track (see §04) and takes whatever the side tracks can spare — which at
   narrow widths is nothing, so it simply sits inside the measure. Outside
   .prose it falls back to the width of its own container, capped at --w-wide.
   No negative margins anywhere: they were the cause of the clipped-quote bug
   and of the void→paper fade regression, and this file uses none.

   Add .bleed to a <figure> to give it the same treatment.                    */
.pullquote,
.prose > .bleed {
  width: min(100%, var(--w-wide));
  margin-inline: auto;
}
.pullquote {
  --dsp-wght: 420;
  margin-block: var(--sp-xl);
  padding-block: var(--sp-m);
  font-family: var(--font-display);
  font-variation-settings: "wght" var(--dsp-wght);
  font-size: var(--t-pull);
  line-height: 1.24;
  letter-spacing: -0.008em;
  text-wrap: balance;
  color: var(--fg);
  border: 0;
}
.pullquote p { margin: 0; text-indent: -0.42em; }  /* hang the opening quote */
.pullquote::before,
.pullquote::after {
  content: "";
  display: block;
  height: 9px;
  background-color: currentColor;
  opacity: 0.35;
  -webkit-mask-image: var(--rule-svg);
  mask-image: var(--rule-svg);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  --rule-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 9' preserveAspectRatio='none'%3E%3Cpath d='M2 4.6C64 3.1 118 6.2 182 4.5s118-2.6 184 0.7 132 3.1 232 2.2' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
}
.pullquote::before { margin-bottom: var(--sp-m); }
.pullquote::after  { margin-top: var(--sp-m); }

/* A quotation that is NOT a display device. Same attribution furniture, prose
   size, one hairline on the left instead of two rules across the measure. It
   exists so a page can quote something without spending a second pull quote on
   it — the homepage ran two blockquotes at 34px plus the neon sign, and §1.7 is
   explicit that three competing devices is noise. */
.quiet-quote {
  width: min(100%, var(--measure));
  margin-block: var(--sp-l);
  padding-left: var(--sp-m);
  border-left: 1px solid var(--hair);
  font-size: var(--t-base);
  line-height: var(--lh-prose);
  color: var(--fg-muted);
}
.quiet-quote p { margin: 0; }

.pullquote__attrib {
  display: block;
  margin-top: var(--sp-s);
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-muted);
}

/* --- Folio ---------------------------------------------------------------
   The running foot of a printed magazine. Makes the site feel paginated.
   <p class="folio"><span class="folio__num">04</span> Neon &amp; Smoke
     <span class="folio__sep">·</span> July Smoke</p>                        */
.folio {
  display: flex;
  flex-wrap: wrap;
  /* MEASURED at 380px on /april-anthology-women-on-fire/: six flex children on a
     `nowrap` line came to 367px inside a 340px wrap, so "Released April 2026" hung
     7px past the viewport and the whole page scrolled sideways. The issue hero
     folio is the longest instance of this component on the site — count, census,
     contributor count and release date — and it is the one that has to wrap.
     Measured on all twelve at 380px: `nowrap` overflowed on ELEVEN of them, by 5
     to 27px; the twelfth is August, which fits only because it has no release
     date to print. With `wrap`, all twelve are 0. Row gap is tighter than the
     column gap so a wrapped folio reads as one continued line, not as two. */
  align-items: baseline;
  gap: 0.35em 0.7em;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  line-height: var(--lh-ui);
  color: var(--fg-faint);
}
.folio__num {
  --dsp-wght: 700;
  font-variation-settings: "wght" 760;
  color: var(--accent-2);
}
.folio__sep { opacity: 0.5; }
.folio--centre { justify-content: center; }
.folio--between { justify-content: space-between; width: 100%; }

/* --- Issue number ------------------------------------------------------- */
.issue-number {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1em;
  padding: 0.35em 0.6em 0.25em;
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  line-height: 1;
}
.issue-number__label {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  padding-left: var(--tr-micro);
  color: var(--fg-faint);
}
.issue-number__value {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-variant-numeric: lining-nums;
  font-size: var(--t-h2);
  color: var(--accent-2);
}

/* --- Colophon ----------------------------------------------------------
   A real credit block at the end of a piece, not a byline chip.            */
.colophon {
  max-width: var(--measure);
  margin-top: var(--sp-2xl);
  padding-top: var(--sp-m);
  display: grid;
  gap: var(--sp-xs);
  border-top: 1px solid var(--hair);
}
.colophon__label {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}
.colophon__body {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-m);
  align-items: start;
}
.colophon__portrait {
  width: 84px;
  height: 108px;
  object-fit: cover;
  border-radius: var(--r-card);
  filter: saturate(0.85) contrast(1.03);
}
.colophon__name {
  --dsp-wght: 590;
  font-family: var(--font-display);
  font-variation-settings: "wght" var(--dsp-wght);
  font-size: var(--t-h4);
  line-height: 1.25;
}
.colophon__bio {
  margin-top: var(--sp-2xs);
  font-size: var(--t-small);
  line-height: 1.55;
  color: var(--fg-muted);
}
.colophon__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-s);
  margin-top: var(--sp-xs);
}
.colophon__credit {
  font-family: var(--font-display);
  font-variation-settings: "wght" 560;
  font-size: var(--t-meta);
  letter-spacing: var(--tr-meta);
  color: var(--fg-faint);
}


/* =============================================================================
   09  THE "STAY LIT." SIGN
   The homepage's ONE animated element (SPEC §5.2).

   A real sign does not pulse. It drops out, catches, hesitates, catches again.
   Two mechanisms:
     • the whole sign runs an irregular 7.3s dropout cycle
     • the period runs a separate 4.1s erratic cycle (the failing tube)
   The two durations never resolve into an obvious repeat.

   `animation-timing-function: step-end` is doing the real work — every keyframe
   HOLDS until the next one, so states snap instead of easing. That is the
   difference between a flicker and a pulse.

   Markup:
     <p class="sign" role="img" aria-label="Stay lit.">
       <span class="sign__text" aria-hidden="true">STAY LIT<span
         class="sign__dot">.</span></span>
     </p>
   ========================================================================== */

.sign {
  --glow: var(--pink);
  margin: 0;
  text-align: center;
  line-height: 0.95;
}

.sign__text {
  display: inline-block;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-weight: normal;
  font-size: var(--t-sign);
  letter-spacing: 0.02em;
  color: var(--glow);
  text-shadow:
    0 0 1px  color-mix(in srgb, #fff 70%, var(--glow)),
    0 0 6px  color-mix(in srgb, var(--glow) 95%, transparent),
    0 0 18px color-mix(in srgb, var(--glow) 65%, transparent),
    0 0 46px color-mix(in srgb, var(--glow) 38%, transparent),
    0 0 110px color-mix(in srgb, var(--glow) 18%, transparent);
  animation: sign-flicker var(--dur-flicker) step-end infinite;
  will-change: opacity, text-shadow;
}

.sign__dot {
  color: var(--amber);
  animation: sign-buzz var(--dur-buzz) step-end infinite;
}

/* Unlit state: the tube is grey glass, not absent. Never animate to
   transparent — the words must stay legible through every dropout. */
@keyframes sign-flicker {
  0%,  1.6%   { opacity: 1;    filter: none; }
  1.7%, 2.1%  { opacity: 0.34; filter: saturate(0.25) brightness(0.7); }
  2.2%, 16.8% { opacity: 1;    filter: none; }
  16.9%, 17.3% { opacity: 0.28; filter: saturate(0.2) brightness(0.62); }
  17.4%, 17.9% { opacity: 1;   filter: none; }
  18%,  18.4% { opacity: 0.4;  filter: saturate(0.3) brightness(0.75); }
  18.5%, 40.6% { opacity: 1;   filter: none; }
  40.7%, 41%  { opacity: 0.55; filter: saturate(0.5) brightness(0.85); }
  41.1%, 67.4% { opacity: 1;   filter: none; }
  67.5%, 67.8% { opacity: 0.24; filter: saturate(0.18) brightness(0.58); }
  67.9%, 68.3% { opacity: 1;   filter: none; }
  68.4%, 68.6% { opacity: 0.3; filter: saturate(0.22) brightness(0.64); }
  68.7%, 88.2% { opacity: 1;   filter: none; }
  88.3%, 88.7% { opacity: 0.46; filter: saturate(0.4) brightness(0.8); }
  88.8%, 100% { opacity: 1;    filter: none; }
}

@keyframes sign-buzz {
  0%, 8.4%     { opacity: 1; }
  8.5%, 9.1%   { opacity: 0.22; }
  9.2%, 33.7%  { opacity: 1; }
  33.8%, 34.1% { opacity: 0.3; }
  34.2%, 34.6% { opacity: 1; }
  34.7%, 35.2% { opacity: 0.18; }
  35.3%, 71.4% { opacity: 1; }
  71.5%, 72.3% { opacity: 0.26; }
  72.4%, 100%  { opacity: 1; }
}

/* Sub-line under the sign — the tagline, doing actual work on the page */
.sign__tagline {
  margin-top: var(--sp-m);
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-meta);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  padding-left: var(--tr-micro);
  color: var(--fg-muted);
}


/* =============================================================================
   10  LINKS & BUTTONS
   ========================================================================== */

a {
  color: inherit;
  text-decoration-color: color-mix(in srgb, var(--accent) 70%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: color var(--dur) var(--ease),
              text-decoration-color var(--dur) var(--ease);
}
a:hover { color: var(--accent); text-decoration-color: var(--accent); }

.prose a {
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
}
.prose a:hover { text-decoration-thickness: 2px; }

/* Quiet link — for nav, cards, lists where an underline would be noise */
.link-quiet { text-decoration: none; }
.link-quiet:hover { text-decoration: underline; text-decoration-color: var(--accent); }

/* Signal link — the one you want people to actually click in a block of text */
.link-signal {
  color: var(--accent);
  text-decoration-line: underline;
  text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.link-signal:hover { text-decoration-color: var(--accent); }

/* --- Buttons -------------------------------------------------------------
   Tap targets are ≥ 44px tall by construction (SPEC §11).                   */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  min-height: 44px;
  padding: 0.7em 1.4em;
  font-size: var(--t-meta);
  text-transform: uppercase;
  letter-spacing: var(--tr-btn);
  line-height: 1;
  text-align: center;
  text-decoration: none;
  color: var(--fg);
  background: transparent;
  border: 1px solid color-mix(in srgb, currentColor 40%, transparent);
  border-radius: var(--r-card);
  cursor: pointer;
  transition: color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* The loudest control on the page. MAXIMUM ONE PER PAGE — an accent filling a
   large area is wrong, but a single small filled button is the sign in the
   window. void-on-pink measures 5.79:1, void-on-amber 10.36:1. */
.btn--signal {
  color: var(--void);
  background-color: var(--pink);
  border-color: var(--pink);
}
.btn--signal:hover {
  color: var(--void);
  background-color: color-mix(in srgb, var(--pink) 88%, #fff);
  border-color: color-mix(in srgb, var(--pink) 88%, #fff);
  box-shadow:
    0 0 10px color-mix(in srgb, var(--pink) 55%, transparent),
    0 0 34px color-mix(in srgb, var(--pink) 24%, transparent);
}
.ground-paper .btn--signal:hover,
.readingpane .btn--signal:hover { box-shadow: none; }

.btn--brass {
  color: var(--void);
  background-color: var(--amber);
  border-color: var(--amber);
}
.btn--brass:hover {
  color: var(--void);
  background-color: color-mix(in srgb, var(--amber) 88%, #fff);
}

.btn--ghost { border-color: color-mix(in srgb, currentColor 22%, transparent); }
.btn--small { min-height: 44px; padding: 0.5em 1em; font-size: var(--t-micro); }
.btn--block { display: flex; width: 100%; }

.btn[aria-disabled="true"],
.btn:disabled {
  opacity: 0.42;
  cursor: not-allowed;
  pointer-events: none;
}


/* =============================================================================
   11  NAVIGATION
   ========================================================================== */

.nav {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  /* Solid, not blurred. Glassmorphism is explicitly out (SPEC §2.4), and a
     translucent nav over paper sections turns the logo into mud. */
  background-color: var(--void);
}
.nav__inner {
  width: min(100% - (2 * var(--sp-gutter)), var(--w-full));
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-m);
  min-height: 64px;
  padding-block: var(--sp-2xs);
}
/* The hand-drawn rule under the nav, instead of a border-bottom */
.nav::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: -8px;
  height: 9px;
  background-color: var(--paper);
  opacity: 0.28;
  pointer-events: none;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 9' preserveAspectRatio='none'%3E%3Cpath d='M2 4.6C64 3.1 118 6.2 182 4.5s118-2.6 184 0.7 132 3.1 232 2.2' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 9' preserveAspectRatio='none'%3E%3Cpath d='M2 4.6C64 3.1 118 6.2 182 4.5s118-2.6 184 0.7 132 3.1 232 2.2' fill='none' stroke='%23000' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E");
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

/* Brand. ONE element. Regression #1 was an orphaned <span>Neon &amp; Smoke</span>
   left under the logo <img> from a superseded nav, rendering duplicate
   branding. The logo image alone carries the brand; its alt text carries the
   name. Do not add a text span beside it. */
.nav__brand {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  text-decoration: none;
}
/* A TEXT wordmark, not an image. The two logo files this project inherited were
   JPGs with a baked-in black background rather than true alpha, which is what
   made the logo show as a black box on the cream ground (regression #2). Setting
   the brand in the display face sidesteps that entirely and costs no request.

   This rule previously carried `height: 30px`, sized for an <img>. Against text
   with a 31.35px line-height that clipped the descender box by a pixel. Type is
   sized by its own type scale, never by a fixed box. */
/* The marketing mark. Square canvas with generous transparent padding around the
   artwork, so the box is sized well above the visual weight it carries — the mark
   itself reads at roughly 40% of this height. Sized rather than cropped: clipping
   would need overflow:hidden on the link, which would also clip its focus ring. */
.nav__mark {
  height: 52px;
  width: 52px;
  flex: 0 0 auto;
  margin-inline-start: -6px;   /* optical: the transparent margin is not the mark */
  margin-inline-end: 2px;
  /* The mark is a neon doorway drawn in cyan and magenta, and a neon sign that
     casts no light is a photograph of a neon sign. These two shadows are the
     values from the original site's own stylesheet, not new ones: pink at 14px
     and cyan at 28px, the near light warm and the far light cold. It is also the
     only place --cyan earns its keep in the chrome — the token is declared the
     rarest in the system (§02) and the mark is where the colour comes from in
     the first place, so the glow spends it on its own source. */
  filter:
    drop-shadow(0 0 14px rgb(255 46 136 / 0.25))
    drop-shadow(0 0 28px rgb(90 212 232 / 0.12));
  transition: filter var(--dur) var(--ease);
}
.nav__brand:hover .nav__mark,
.nav__brand:focus-visible .nav__mark {
  filter:
    drop-shadow(0 0 20px rgb(255 46 136 / 0.5))
    drop-shadow(0 0 40px rgb(90 212 232 / 0.25));
}
/* On the cream ground the sign is not lit: a glow needs dark around it to read
   as light, and on paper the same filter prints as a coloured smudge. */
.ground-paper .nav__mark,
body.ground-paper .nav__mark {
  filter: none;
}
@media (prefers-reduced-motion: reduce) {
  .nav__mark { transition: none; }
}

@media (max-width: 479px) {
  .nav__mark { height: 44px; width: 44px; }
}

.nav__logo {
  font-family: var(--font-display);
  font-variation-settings: "wght" 660;
  font-size: var(--t-h4);
  line-height: 1.1;
  letter-spacing: 0.005em;
  color: var(--fg);
  white-space: nowrap;   /* "Neon & Smoke" never breaks across two lines */
}

/* The disclosure is a real <button> with aria-expanded, not a checkbox styled as
   one: a checkbox is announced as a checkbox, and primary navigation is the last
   place to spend that. The cost is that it needs JS — so the default state here is
   OPEN, and the collapse is applied only once `assets/js/nav.js` has confirmed it
   is running by setting `.has-js` on <html>. A blocked or failed script therefore
   degrades to "every link visible, stacked", never to "no navigation at all". */
.nav__toggle {
  display: none;            /* revealed below, only under .has-js at small widths */
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  min-width: 44px;
  min-height: 44px;
  padding: 0 0.75em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-muted);
  background: none;
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  cursor: pointer;
}
.nav__toggle:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}

.nav__panel {
  display: flex;
  flex-direction: column;
  gap: 0;
  width: 100%;
  padding-bottom: var(--sp-s);
}
.nav__list {
  display: flex;
  flex-direction: column;
  gap: 0;
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Scoped to a max-width query on purpose. Writing this as `.has-js .nav__panel`
   at base width would outrank the plain `.nav__panel` rule in the ≥880 block and
   keep the desktop nav collapsed.

   The threshold is 880, not the 768 layout breakpoint. The brand wordmark plus
   six uppercase links need ~870px on one line, so between 768 and 869 the
   horizontal bar overflowed the viewport by up to 95px — iPad portrait, which
   SPEC §12 names. Letting the bar wrap instead would change the masthead's
   proportions at one width only, which reads as a bug rather than as a layout.
   This query and the `min-width: 920px` nav block in §16 are a matched pair:
   move one and you open a band where neither layout applies. */
@media (max-width: 919.98px) {
  .has-js .nav__toggle { display: inline-flex; }
  .has-js .nav__panel { display: none; }
  /* The open menu is a full-width dropdown anchored below the bar, NOT an inline
     panel. Since search was added, the panel lives inside the `.nav__end` flex
     row (panel · search · toggle); left in flow there it collapsed to a ~118px
     transparent column and stretched the sticky bar to ~340px tall. Taking it out
     of flow (absolute, anchored to the sticky `.nav`) restores a proper dropdown:
     full width, solid void ground so it reads over the page, and the bar keeps
     its height. */
  .has-js .nav__panel.is-open {
    display: flex;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    width: 100%;
    padding: var(--sp-2xs) var(--sp-gutter) var(--sp-m);
    background-color: var(--void);
    border-bottom: 1px solid var(--hair-soft);
    box-shadow: 0 28px 48px -24px color-mix(in srgb, var(--void) 82%, transparent);
  }
}

.nav__link {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.2em;
  font-size: var(--t-meta);
  text-transform: uppercase;
  letter-spacing: var(--tr-meta);
  text-decoration: none;
  color: var(--fg-muted);
  transition: color var(--dur) var(--ease);
}
.nav__link:hover { color: var(--fg); }

/* Current page. Colour alone is never the only signal — the marker rule under
   the label carries it too. */
.nav__link[aria-current="page"] {
  color: var(--fg);
  position: relative;
}
.nav__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0.2em;
  right: 0.2em;
  bottom: 0.55em;
  height: 2px;
  background-color: var(--pink);
  box-shadow:
    0 0 5px color-mix(in srgb, var(--pink) 80%, transparent),
    0 0 16px color-mix(in srgb, var(--pink) 40%, transparent);
}

/* --- Breadcrumb ---------------------------------------------------------
   Every piece page needs a real route back to its issue (SPEC §8.1).        */
.breadcrumb { font-size: var(--t-micro); letter-spacing: var(--tr-meta); }
.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5em;
  list-style: none;
  margin: 0;
  padding: 0;
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}
.breadcrumb li { display: flex; align-items: center; gap: 0.5em; }
.breadcrumb li + li::before {
  content: "⁄";
  opacity: 0.5;
  transform: translateY(-0.03em);
}
.breadcrumb a {
  color: var(--fg-muted);
  text-decoration: none;
  padding-block: 0.6em;   /* keeps the touch target usable without a box */
}
.breadcrumb a:hover { color: var(--accent); }
.breadcrumb [aria-current="page"] { color: var(--fg); }

/* The trail's uppercase is a UI treatment for route labels — but the last crumb is
   often a WORK'S TITLE or a PERSON'S NAME, and uppercasing those rewrites them.
   `L paternoster` rendered as L PATERNOSTER and `deanna Davidson` as DEANNA
   DAVIDSON: two names SPEC §8.2 protects by hand because the lowercase initial may
   be deliberate stylisation, destroyed by a text-transform. The contributors index
   already guarded against this; the breadcrumb did not. */
.breadcrumb [aria-current="page"],
.breadcrumb li:last-child {
  text-transform: none;
  letter-spacing: normal;
}

/* --- Previous / next within the issue -----------------------------------
   Must be real <a href> anchors carrying the ACTUAL adjacent piece titles.
   The live site ships <button disabled> here and has zero crawlable links
   between its 113 pieces (SPEC §8.1).                                       */
.pieceflow {
  display: grid;
  gap: var(--sp-s);
  margin-top: var(--sp-2xl);
  padding-top: var(--sp-m);
  border-top: 1px solid var(--hair);
}
.pieceflow__item {
  display: grid;
  gap: 0.35em;
  align-content: start;
  padding: var(--sp-s) var(--sp-m);
  text-decoration: none;
  border: 1px solid var(--hair-soft);
  border-radius: var(--r-card);
  transition: border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
.pieceflow__item:hover {
  border-color: var(--accent);
  background-color: color-mix(in srgb, var(--accent) 7%, transparent);
}
.pieceflow__item--next { text-align: right; }
.pieceflow__label {
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}
.pieceflow__title {
  font-family: var(--font-display);
  font-variation-settings: "wght" 570;
  font-size: var(--t-h4);
  line-height: 1.25;
  color: var(--fg);
}
.pieceflow__item:hover .pieceflow__title { color: var(--accent); }

/* The edge of the run — "This is the first one." / "Nothing after this yet."
   These are a <span>, not a link, and need to read as quieter than the live
   side. That was done with `opacity: 0.45` on the whole item, which took the
   label to 1.92:1 and its own sentence to 3.92:1 against the void — both under
   WCAG AA on text a reader is expected to read. Opacity dims the ground out
   from under the text as well, so it can never be reasoned about per ground.
   The quieting now comes from the same ground-aware tokens everything else
   uses, which are rebound on `.ground-paper`, so this holds on both grounds. */
.pieceflow__empty { border-style: dashed; }
.pieceflow__empty .pieceflow__title { color: var(--fg-muted); }


/* =============================================================================
   12  CARDS
   ========================================================================== */

.card {
  position: relative;
  display: grid;
  gap: var(--sp-xs);
  align-content: start;
  padding: var(--sp-m);
  color: var(--fg);
  background-color: transparent;
  border: 1px solid var(--hair-soft);
  border-radius: var(--r-card);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease);
}
a.card:hover,
.card:hover {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background-color: color-mix(in srgb, var(--accent) 5%, transparent);
}
.card__media {
  aspect-ratio: 4 / 5;
  width: 100%;
  object-fit: cover;
  border-radius: var(--r-card);
  filter: saturate(0.9) contrast(1.02);
}
.card__title {
  font-family: var(--font-display);
  font-variation-settings: "wght" 570;
  font-size: var(--t-h3);
  line-height: 1.16;
  color: var(--fg);
}
a.card:hover .card__title { color: var(--accent); }
.card__byline {
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-style: italic;
  color: var(--fg-muted);
}
.card__excerpt {
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg-muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2xs) var(--sp-s);
  margin-top: var(--sp-2xs);
}

/* --- Issue card --------------------------------------------------------- */
.card--issue { padding: 0; border: 0; gap: var(--sp-s); }
.card--issue .card__media { aspect-ratio: 3 / 4; }
.card--issue .card__body { display: grid; gap: var(--sp-3xs); }
.card--issue .card__theme {
  font-size: var(--t-small);
  font-style: italic;
  color: var(--fg-muted);
}
.card--issue:hover { background-color: transparent; }

/* --- Piece card --------------------------------------------------------- */
.card--piece .card__title { font-size: var(--t-h4); }

/* --- Contributor card --------------------------------------------------- */
.card--contributor {
  grid-template-columns: 72px minmax(0, 1fr);
  gap: var(--sp-s) var(--sp-m);
  align-items: start;
}
.card--contributor .card__portrait {
  grid-row: span 3;
  width: 72px;
  height: 92px;
  object-fit: cover;
  border-radius: var(--r-card);
  filter: saturate(0.85);
}
.card--contributor .card__title { font-size: var(--t-h4); }

/* The cross-issue publication record — the contributor page's memorable move.
   Seven people have published here more than once and today's site cannot show
   that anyone has ever published twice (SPEC §8.2). */
.record {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}
.record__count {
  font-variation-settings: "wght" 760;
  color: var(--accent-2);
}
.record__issues {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35em;
  list-style: none;
  margin: 0;
  padding: 0;
}
.record__issues li::after { content: "·"; padding-left: 0.35em; opacity: 0.5; }
.record__issues li:last-child::after { content: none; }

/* --- Tags: the closed type vocabulary (SPEC §8.4) ----------------------- */
.tag {
  display: inline-flex;
  align-items: center;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  padding: 0.35em 0.65em 0.3em;
  padding-right: calc(0.65em + var(--tr-micro));
  line-height: 1;
  color: var(--fg-muted);
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
}
.tag--fiction    { color: var(--accent);   border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.tag--poetry     { color: var(--accent-2); border-color: color-mix(in srgb, var(--accent-2) 45%, transparent); }
.tag--visual-art { color: var(--fg-muted); }
.tag--cover-art  { color: var(--fg-muted); }

/* --- The archive record: the archive's memorable move --------------------
   A shelf list of catalogued editions. Cover plate in a fixed column, the
   bibliographic record hanging in the column beside it, a hairline between
   entries, and every piece in the issue printed by name underneath.

   This replaces the card-catalog grid that used to live here. Same intent —
   SPEC §5.2 asks the archive for "a library card-catalog index" — but a
   catalogue index is a list of records, and it was being built as a grid of
   cards. The grid was the problem: twelve cards of identical height carrying a
   title and two counts each, which is exactly the uniform grid NOT-AI-MADE §1.1
   names as the strongest tell of a generated site, and which SITE-ASSESSMENT
   §3.10 recorded as "the archive does not make the publication feel established".

   The record fixes it structurally rather than decoratively. An entry's height
   is set by how many pieces the issue holds, so Issue One (fourteen) is visibly
   a larger object than November Smoke (six) without any rule saying so, and the
   list cannot be reordered at random without loss.

   Recovered from Jonathan's own earlier design for this page,
   docs/reference/original-issues.html. Two things changed in the crossing:
   the record sits on PAPER, not on the original's void — 125 linked titles and
   125 names is the densest reading on the site after a piece page, and SPEC §2.1
   puts anything read at length on paper (regression #5 is "the site read too
   dark"). The cover plate keeps its own dark ground locally via `.on-void`,
   which turns out to be better than the original: a dark plate on cream reads as
   an object laid on a page rather than dissolving into the background.

   Markup:
     <article class="issue-entry">
       <a class="issue-cover on-void" href="…">
         <span class="issue-cover__plate">…</span>
         <img class="issue-cover__img" …>
         <span class="issue-cover__folio">MAY 26</span>
       </a>
       <div class="issue-details">
         <p class="issue-status status-current"><span class="status-dot"></span>…</p>
         <h3 class="issue-headline">…</h3>
         <dl class="issue-bibmeta">…</dl>
         <p class="issue-blurb">…</p>
         <p class="inside-title">Inside this issue</p>
         <ul class="inside-list">…</ul>
       </div>
     </article>                                                              */

.issue-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xl);
}
.issue-entry {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-l);
  align-items: start;
  padding-bottom: var(--sp-2xl);
  border-bottom: 1px solid var(--hair-soft);
}
/* The last record closes the list; a rule under it would be a rule under
   nothing, which is the detail that makes a ruled list look machine-made. */
.issue-entry:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

/* The current issue. The original wrapped it in a tinted panel with a NOW
   READING tab; here it gets an accent rule above it and a larger headline
   instead. Rationale: the panel was carrying the same message as the status
   line directly beneath it, and on the paper ground a 4% pink wash is either
   invisible or dirty. A rule and a size step are what a magazine actually uses
   to say "this one first". */
.issue-entry--current {
  padding-top: var(--sp-l);
  border-top: 2px solid var(--accent);
}

/* --- The cover plate ---------------------------------------------------- */

/* `.on-void` in the markup flips --fg/--accent to their dark-ground values, so
   everything inside the plate is legible against it with no local colour
   overrides — the §02 mechanism, used as intended. Only the background is
   overridden here, because a plate is a step up from the void, not the void. */
.issue-cover {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  padding: 16px;
  text-decoration: none;
  border: 1px solid color-mix(in srgb, var(--pink) 30%, transparent);
  border-radius: var(--r-card);
  background-color: var(--smoke-deep);
  /* THE MISSING-COVER FALLBACK. Painted always, not on error: the <img> is only
     emitted once the file is on disk, so an unmigrated cover falls through to
     this and the issue title sits on it. The original did the same thing with an
     inline onerror handler, which cannot ship here (no inline JS, SPEC §14) and
     would not have fired for an image that was never emitted in the first
     place. NOT-AI-MADE §1.6: an absent image gets a designed treatment.

     The pink is mixed down to 62% rather than used neat, for two reasons and
     both were measured. SPEC §2.1: an accent filling a large area is wrong, and
     the hot corner of a 300×400 plate is a large area. And the plate's title is
     bottom-aligned, which puts it exactly on the hottest corner — against neat
     --pink-deep the title measured 2.10:1, under the 3:1 large-text floor. At
     62% it is 7.0:1. */
  background-image: linear-gradient(
    160deg,
    var(--smoke) 0%,
    var(--smoke-lift) 30%,
    var(--smoke-hi) 58%,
    color-mix(in srgb, var(--pink-deep) 62%, var(--smoke-hi)) 100%
  );
  transition: border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.issue-cover:hover {
  border-color: color-mix(in srgb, var(--pink) 62%, transparent);
  box-shadow: 0 0 32px -6px color-mix(in srgb, var(--pink) 40%, transparent);
}
.issue-cover__plate {
  position: absolute;
  inset: 16px;
  display: flex;
  align-items: flex-end;
  font-family: var(--font-display);
  font-variation-settings: "wght" 460;
  font-size: var(--t-h3);
  line-height: 1.1;
  /* --fg, not --fg-muted: with no cover image this title is the only thing on
     the plate and the only accessible name the link has, so it is the content
     rather than a caption. It also has to clear the gradient's warm corner,
     where the muted value measured 3.2:1 against paper's 7.0:1. */
  color: var(--fg);
}
.issue-cover__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* An image that ships but fails to decode lands on the same gradient rather
     than on a broken-image glyph. This is the case the original's onerror
     handler existed for. */
  background-image: inherit;
}
.issue-cover__folio {
  position: absolute;
  right: 16px;
  bottom: 16px;
  padding: 0.3em 0.7em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  padding-right: calc(0.7em + var(--tr-micro));
  color: var(--paper);
  background-color: color-mix(in srgb, var(--void) 85%, transparent);
  border: 1px solid color-mix(in srgb, var(--pink) 45%, transparent);
  border-radius: var(--r-card);
}

/* --- The record --------------------------------------------------------- */

.issue-status {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}
.status-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--r-full);
  background-color: currentColor;
  flex: none;
}
/* Three states, each earned from the record rather than assigned. Colour comes
   from --accent / --accent-2, so all three are correct on either ground and
   neither can print raw neon on paper (regression #3, SPEC §11). */
.status-current   { color: var(--accent); }
.status-anthology { color: var(--accent-2); }
.status-back      { color: var(--fg-faint); }
.status-current .status-dot {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

/* Set at h1 size even though it is an <h3>: the original ran these at up to
   84px, and the size is what makes a record read as the head of an entry rather
   than as a card title. Semantic level and optical size are separate decisions
   and this is the case that needs them separated — the archive has one <h1>
   ("Issues") and twelve records under it, all of which want to be big. */
.issue-headline {
  margin-top: var(--sp-s);
  font-family: var(--font-display);
  font-variation-settings: "wght" var(--dsp-wght, 420);
  font-size: var(--t-h1);
  line-height: var(--lh-display);
  letter-spacing: -0.015em;
}
.issue-entry--current .issue-headline { font-size: var(--t-display); }
.issue-headline .link-quiet { color: inherit; }
.issue-headline .link-quiet:hover { color: var(--accent); }
.issue-headline__sep { color: var(--fg-faint); font-style: normal; }
.issue-headline__sub { font-style: italic; }

.issue-bibmeta {
  display: grid;
  /* auto-fit, not the original's fixed four columns: the cell count genuinely
     varies from three to five across the twelve issues, because a cell whose
     fact is missing is removed rather than left blank. A fixed grid would have
     forced an empty box where August has no release date. */
  grid-template-columns: repeat(auto-fit, minmax(6.5rem, max-content));
  gap: var(--sp-m) var(--sp-l);
  margin-top: var(--sp-m);
  padding-bottom: var(--sp-m);
  border-bottom: 1px solid var(--hair-soft);
}
.issue-bibmeta__label {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}
/* BODY face, not display, purely because of the italic. Frank Ruhl Libre has no
   italic cut, so display + `font-style: italic` gets a browser-synthesised slant
   — a serif sheared by algorithm, which is one of the more legible signs that
   nobody looked at the page. EB Garamond ships a real drawn italic, so an
   editorial italic moves to the body face rather than being faked or dropped.
   The italic is carrying meaning in both of these (a value set against its
   label, a blurb set against the head), so it stays; the face changes. */
.issue-bibmeta__value {
  margin: 0.35em 0 0;
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 500;
  font-size: var(--t-h4);
  line-height: 1.15;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--fg);
}

.issue-blurb {
  margin-top: var(--sp-m);
  max-width: var(--w-wide);
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 450;
  font-size: var(--t-lede);
  line-height: 1.4;
  color: var(--fg-muted);
}
.issue-note {
  margin-top: var(--sp-2xs);
  max-width: var(--w-wide);
  font-size: var(--t-small);
  color: var(--fg-muted);
}

/* --- Inside this issue --------------------------------------------------- */

.inside-title {
  margin-top: var(--sp-l);
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--accent-2);
}
/* Running text, not a stack: the contents of an issue set as one continuous
   line the way a printed contents strip runs, wrapping as many times as the
   issue is long. It is a <ul> so assistive tech can announce and skip it as the
   list it is; `display: inline` is what keeps it reading as a line. */
/* Capped at --w-wide, and set at reading size rather than metadata size. Left
   to fill the column this ran to about 120 characters a line, which is past the
   point where the eye can find its way back to the start of the next one — the
   exact defect SPEC §3 exists to stop, and it would have applied to the densest
   text on the site. This is 250 links a reader is meant to scan by name. */
.inside-list {
  margin-top: var(--sp-2xs);
  padding: 0;
  max-width: var(--w-wide);
  list-style: none;
  font-size: var(--t-base);
  line-height: 1.85;
  color: var(--fg-muted);
}
.inside-list__item {
  display: inline;
}
/* Both separators are generated, so they are seen and never spoken. */
.inside-list__item:not(:last-child)::after {
  content: " / ";
  color: var(--ash);
  padding: 0 0.15em;
}
.inside-list__title::after {
  content: " · ";
  color: var(--fg-faint);
}
.inside-list__title em {
  font-style: italic;
  color: var(--fg);
}
.inside-list__title:hover em { color: var(--accent); }
.inside-list__by { color: var(--fg-muted); }

/* --- Grids ---------------------------------------------------------------
   Every flexible track in this file is `minmax(0, 1fr)`, never bare `1fr`. A
   bare `1fr` has an automatic MINIMUM, so one wide child — an unbroken piece
   title, a cover image, a long URL in a bio — pushes its track past the
   viewport and the whole page scrolls sideways. This was reproduced at 380px
   before the fix. Do not reintroduce `1fr` on its own.                       */
.grid {
  display: grid;
  gap: var(--sp-m);
  grid-template-columns: minmax(0, 1fr);
}
.grid--2, .grid--3, .grid--4 { grid-template-columns: minmax(0, 1fr); }


/* =============================================================================
   13  MODULES
   ========================================================================== */

/* --- Submission call status card ----------------------------------------
   The submissions page's memorable move: open/closed readable at a glance.
   The live site buries open calls inside prose paragraphs.

   Status is never colour-only — the word OPEN / CLOSED is in the markup and the
   card's left edge changes weight as well as hue.                           */
.callcard {
  display: grid;
  gap: var(--sp-xs);
  padding: var(--sp-m) var(--sp-m) var(--sp-m) calc(var(--sp-m) - 3px);
  border: 1px solid var(--hair-soft);
  border-left: 4px solid var(--fg-faint);
  border-radius: var(--r-card);
}
.callcard__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2xs) var(--sp-s);
}
.callcard__title {
  font-family: var(--font-display);
  font-variation-settings: "wght" 590;
  font-size: var(--t-h4);
  line-height: 1.2;
}
.callcard__status {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
}
.callcard__dot {
  width: 9px;
  height: 9px;
  border-radius: var(--r-full);
  background-color: var(--fg-faint);
  flex: none;
}
.callcard__terms {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0.35em var(--sp-m);
  margin: 0;
  font-size: var(--t-small);
}
.callcard__terms dt {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
  padding-top: 0.25em;
}
.callcard__terms dd { margin: 0; color: var(--fg); }

.callcard--open { border-left-color: var(--accent); }
.callcard--open .callcard__status { color: var(--accent); }
.callcard--open .callcard__dot {
  background-color: var(--pink);
  box-shadow:
    0 0 5px color-mix(in srgb, var(--pink) 80%, transparent),
    0 0 14px color-mix(in srgb, var(--pink) 45%, transparent);
}
.ground-paper .callcard--open .callcard__dot,
.readingpane .callcard--open .callcard__dot {
  background-color: var(--pink-ink);
  box-shadow: none;
}

/* Upcoming — a dated call whose window has not opened yet. Amber, so it reads as
   "warming up" rather than live (pink = open now). Mirrors the --open treatment. */
.callcard--upcoming { border-left-color: var(--accent-2); }
.callcard--upcoming .callcard__status { color: var(--accent-2); }
.callcard--upcoming .callcard__dot {
  background-color: var(--amber);
  box-shadow:
    0 0 5px color-mix(in srgb, var(--amber) 80%, transparent),
    0 0 14px color-mix(in srgb, var(--amber) 45%, transparent);
}
.ground-paper .callcard--upcoming .callcard__dot,
.readingpane .callcard--upcoming .callcard__dot {
  background-color: var(--amber-ink);
  box-shadow: none;
}

.callcard--closed { border-left-style: dashed; }
.callcard--closed .callcard__title,
.callcard--closed .callcard__terms dd { color: var(--fg-muted); }
.callcard--closed .callcard__status { color: var(--fg-faint); }

/* --- Newsletter ---------------------------------------------------------
   MARKUP CONTRACT — the consent checkbox comes BEFORE the submit button, in
   the DOM. The live site puts it after, which means people press Subscribe
   before they have seen what they are consenting to.

   Do NOT "fix" a wrong DOM order with flex `order`. That would leave the tab
   order and the screen-reader order broken, which is the actual problem.

     <form class="newsletter">
       <label class="newsletter__label" for="nl-email">…</label>
       <input class="newsletter__input" id="nl-email" type="email" …>
       <label class="newsletter__consent"><input type="checkbox" …> …</label>
       <button class="btn btn--signal newsletter__submit">Subscribe</button>
     </form>                                                                 */
.newsletter {
  display: grid;
  gap: var(--sp-s);
  max-width: 28rem;
}
.newsletter__label {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-muted);
}
.newsletter__input {
  min-height: 48px;
  padding: 0.6em 0.8em;
  font-family: var(--font-body);
  font-size: var(--t-base);
  color: var(--fg);
  background-color: color-mix(in srgb, var(--fg) 6%, transparent);
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
}
.newsletter__input::placeholder { color: var(--fg-faint); opacity: 1; }
.newsletter__input:hover { border-color: color-mix(in srgb, currentColor 40%, transparent); }

.newsletter__consent {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.7em;
  align-items: start;
  font-size: var(--t-small);
  line-height: 1.45;
  color: var(--fg-muted);
  cursor: pointer;
}
/* The consent control. SPEC §11 wants a 44×44 target; a 44px checkbox on a
   line of 15px text looks like a bug, so the CONTROL stays 20px and the
   TARGET is 44. The input keeps a real 44×44 box and the visible mark is drawn
   at 20px by ::before, with the box pulled back by the 12px it gained on each
   side so the text column starts exactly where it did (grid column 1 measures
   20px either way). It remains a native <input type="checkbox"> — the
   semantics, the required validation and the announcement are untouched. */
.newsletter__consent input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  box-sizing: border-box;
  width: 44px;
  height: 44px;
  margin: calc(0.18em - 12px) -12px 0;
  display: grid;
  place-content: center;
  background: none;
  border: 0;
  cursor: pointer;
  flex: none;
}
.newsletter__consent input[type="checkbox"]::before {
  content: "";
  width: 20px;
  height: 20px;
  border: 1px solid color-mix(in srgb, currentColor 55%, transparent);
  border-radius: 2px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 13px 13px;
}
.newsletter__consent input[type="checkbox"]:checked::before {
  border-color: var(--accent);
  background-color: var(--accent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8.5l3.4 3.4L13 5' fill='none' stroke='%2308050b' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.newsletter__consent input[type="checkbox"]:hover::before {
  border-color: color-mix(in srgb, currentColor 80%, transparent);
}
/* Outline hugs the 20px mark, not the invisible 44px target. */
.newsletter__consent input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -10px;
}
/* Windows high-contrast strips backgrounds and border colours, which would
   leave the drawn box with no checked state at all. Hand it back to the UA. */
@media (forced-colors: active) {
  .newsletter__consent input[type="checkbox"] {
    appearance: auto;
    -webkit-appearance: auto;
    width: 20px;
    height: 20px;
    margin: 0.18em 0 0;
  }
  .newsletter__consent input[type="checkbox"]::before { content: none; }
}
/* Live region for the submit result. Empty until JS or a no-JS round trip
   fills it — `:empty` collapses the row so an idle form has no reserved gap
   and no stray gridline. */
.newsletter__status {
  margin: 0;
  font-size: var(--t-small);
  line-height: 1.45;
}
.newsletter__status:empty { display: none; }
/* The palette has no dedicated error hue, and inventing one would land an
   unverified colour in a system whose contrast is checked by tools/contrast.mjs.
   `--accent` is the one signal colour, already contrast-safe in both grounds
   (raw pink is 2.9:1 on paper, so the token swaps to --pink-ink there), so the
   failure state — the message that must be noticed — takes it. Success and
   progress are ordinary reading text and use the plain foreground ramp. */
.newsletter__status[data-state="error"]   { color: var(--accent); }
.newsletter__status[data-state="ok"]      { color: var(--fg); }
.newsletter__status[data-state="pending"] { color: var(--fg-muted); }

.newsletter__note {
  font-size: var(--t-micro);
  letter-spacing: var(--tr-meta);
  color: var(--fg-faint);
}

/* --- Generic form fields (contact form replacing the published phone no.) */
.field { display: grid; gap: var(--sp-2xs); }
.field__label {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-muted);
}
.field__input,
.field__textarea,
.field__select {
  min-height: 48px;
  padding: 0.6em 0.8em;
  font-family: var(--font-body);
  font-size: var(--t-base);
  color: var(--fg);
  background-color: color-mix(in srgb, var(--fg) 6%, transparent);
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
}
.field__textarea { min-height: 8rem; line-height: 1.5; resize: vertical; }
.field__hint { font-size: var(--t-small); color: var(--fg-faint); }
.field__error { font-size: var(--t-small); color: var(--accent); }


/* =============================================================================
   14  FOOTER
   ========================================================================== */

.footer {
  padding-block: var(--sp-2xl) var(--sp-xl);
}
.footer__inner {
  width: min(100% - (2 * var(--sp-gutter)), var(--w-full));
  margin-inline: auto;
  display: grid;
  gap: var(--sp-xl);
}
.footer__cols {
  display: grid;
  gap: var(--sp-xl);
  grid-template-columns: minmax(0, 1fr);
}
.footer__heading {
  font-family: var(--font-display);
  font-variation-settings: "wght" 680;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
  margin-bottom: var(--sp-s);
}
.footer__list { display: grid; gap: 0; list-style: none; margin: 0; padding: 0; }
.footer__list a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: var(--t-small);
  color: var(--fg-muted);
  text-decoration: none;
}
.footer__list a:hover { color: var(--accent); text-decoration: underline; }
.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-s);
  padding-top: var(--sp-m);
  border-top: 1px solid var(--hair-soft);
}
.footer__tagline {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-h4);
  color: var(--fg);
}


/* =============================================================================
   15  FOCUS, SKIP LINK, UTILITIES
   ========================================================================== */

/* Visible focus on everything. `outline: none` never appears in this file
   without a replacement — search it and you will find only the two rules
   below, both of which draw a ring by another means (SPEC §11). */
:focus-visible {
  outline: 2px solid var(--focus, var(--amber));
  outline-offset: 3px;
  border-radius: var(--r-hair);
}
/* Keep a ring for browsers that do not support :focus-visible */
:focus:not(:focus-visible) { outline: none; }

.issue-cover:focus-visible,
.card:focus-visible,
.pieceflow__item:focus-visible { outline-offset: 4px; }

/* --- Tap targets (SPEC §11: ≥ 44×44) -------------------------------------
   These are the standalone controls that measured short — all of them text
   links whose height was just their line box (works__title 21.6, az-entry__name
   22.5, breadcrumb 31.3, skip-link 33.5, and the list links at 18.1). The fix
   is a floor on the box, not a bigger type size: the type scale is unchanged
   and only the hit area grows.

   Scoped to links that stand alone in a row, list item or breadcrumb. A link
   running inside a sentence is deliberately NOT included — WCAG exempts inline
   links, and padding one out to 44px would either break the line box or make
   two adjacent lines fight for the same tap.

   `.inside-list__item` is excluded for exactly that reason. It is a <li> only so
   that assistive tech can announce and skip the issue's contents as a list; it
   renders as running text, and 250 links each forced to a 44px box would turn
   the archive's contents strip into a column of buttons roughly nine screens
   tall. Same rule, same reasoning as any other link inside a sentence. */
li:not(.inside-list__item) > .link-quiet,
li:not(.inside-list__item) > .link-signal,
/* A link sitting in a .flow-row NEXT TO A BUTTON is a control, not prose — the
   "What's in it" link beside "Buy the paperback" on the homepage, /support/ and
   /submissions/. Every one measured 31.3px, i.e. its own line box, while the
   button beside it was 44. Same row, same job, two different hit areas, and the
   smaller one was always the secondary action. Not covered by the rule above
   because these sit in a <p>, not an <li>. */
.flow-row > .link-quiet,
.flow-row > .link-signal,
.works__meta > .link-quiet,
.works__title,
.az-entry__name,
/* Contributor-index names are <a> inside an <h3.repeat__name>, so the
   `li > .link-quiet` rule above never reached them and they rendered a 27-34px
   hit box on mobile -- the primary route to every contributor page. Same
   treatment as the other heading links in this group. */
.repeat__name > .link-quiet,
.breadcrumb a,
.skip-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;   /* "H.G." is 37.8px wide set at h4; the box, not the type, grows */
  max-width: 100%;
}

.skip-link {
  position: absolute;
  left: var(--sp-s);
  top: var(--sp-s);
  z-index: var(--z-skip);
  transform: translateY(-160%);
  padding: 0.7em 1.1em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--void);
  background-color: var(--amber);
  border-radius: var(--r-card);
  text-decoration: none;
  transition: transform var(--dur) var(--ease-out);
}
.skip-link:focus { transform: translateY(0); }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- CYAN DISCIPLINE -----------------------------------------------------
   --cyan is the rarest token: 1–2 uses per page maximum. Three structural
   guards make it hard to overuse rather than merely discouraged:

     1. --cyan is NOT in the ground-context cascade (§02). No component can
        reach it through --accent or --accent-2. There is no path to cyan
        except the two classes below.
     2. Those classes set `color` only. There is no cyan background utility,
        so cyan can never fill an area.
     3. Cyan carries ONE semantic on this site — "the current one". The issue
        the reader is in, the newest thing. That semantic is naturally
        singular per page.

   QA grep:  rg -c 'marker|accent-cyan' dist/<page>.html   →  must be ≤ 2     */
.marker,
.accent-cyan {
  color: var(--cyan);
}
.ground-paper .marker,
.ground-paper .accent-cyan,
.readingpane .marker,
.readingpane .accent-cyan {
  color: var(--cyan-ink);      /* raw cyan on paper is 1.47:1 — unreadable */
}
.marker {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 680;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
}
.marker::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--r-full);
  background-color: currentColor;
  box-shadow: 0 0 6px currentColor, 0 0 16px color-mix(in srgb, currentColor 45%, transparent);
}
.ground-paper .marker::before,
.readingpane .marker::before { box-shadow: none; }

/* --- Small utilities ---------------------------------------------------- */
.text-centre { text-align: center; }
.text-muted  { color: var(--fg-muted); }
.text-faint  { color: var(--fg-faint); }
.text-signal { color: var(--accent); }
.text-ember  { color: var(--accent-2); }
.measure     { max-width: var(--measure); }
.measure-wide{ max-width: var(--w-wide); }
.flow-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-s);
}
.mt-0 { margin-top: 0; }
.mt-s { margin-top: var(--sp-s); }
.mt-m { margin-top: var(--sp-m); }
.mt-l { margin-top: var(--sp-l); }
.mt-xl{ margin-top: var(--sp-xl); }


/* =============================================================================
   16  RESPONSIVE
   Breakpoints: 380 (baseline) / 768 / 1024 / 1440.
   Mobile is the baseline, not an afterthought. Every print-magazine detail has
   defined behaviour at every width, and nothing overflows horizontally.
   ========================================================================== */

/* --- ≥ 380: baseline safety --------------------------------------------- */
@media (max-width: 479px) {
  /* A three-line drop cap at 380px eats a third of the column. Two lines. */
  .prose--dropcap > p:first-of-type::first-letter,
  .dropcap::first-letter { font-size: 2.9rem; line-height: 0.82; }
  /* Poetry: the hang stays, the type steps down one more notch so authored
     lines survive unwrapped on a 380px screen. Lineation beats size. */
  .poem { --poem-hang: 1.35em; font-size: 1rem; }
  .colophon__body { grid-template-columns: minmax(0, 1fr); }
  .colophon__portrait { width: 72px; height: 92px; }
  .card--contributor { grid-template-columns: minmax(0, 1fr); }
  .card--contributor .card__portrait { grid-row: auto; }
  .callcard__terms { grid-template-columns: minmax(0, 1fr); gap: 0.15em; }
  .callcard__terms dd { margin-bottom: 0.5em; }
}

/* --- ≥ 768 --------------------------------------------------------------- */
@media (min-width: 768px) {
  .grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The record turns two-column. Below this the cover stacks over the details
     at full width, which is right on a phone: a 3/4 plate shrunk into a side
     column at 380px is too small to be a cover and too big to be an icon. */
  .issue-entry { grid-template-columns: 260px minmax(0, 1fr); gap: var(--sp-xl); }

  .pieceflow { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-m); }
  .footer__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .readingpane { --pad: var(--sp-l); }
}

/* --- ≥ 880: the horizontal nav ------------------------------------------- */
/* Deliberately NOT on the 768 breakpoint. The bar is sized by its own content —
   the wordmark and six links measure ~870px — so the layout switches where the
   content stops fitting, not where the page grid happens to change. Paired with
   `@media (max-width: 919.98px)` in §11. */
@media (min-width: 920px) {
  .nav__inner { flex-wrap: nowrap; min-height: 76px; }
  .nav__toggle { display: none; }
  .nav__panel {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--sp-m);
    width: auto;
    padding-bottom: 0;
  }
  /* --sp-s, not --sp-m, and this is a bug fix rather than a preference.
     MEASURED at exactly 920px: the gutter is 30.2px a side, so .nav__inner gets
     859.6px, and the wordmark plus six links need 866.6px. The bar overflowed
     the viewport by 7px at the precise width where it switched on — on all 271
     pages, in the one band where neither the toggle nor a roomy bar applies.

     Fixed by tightening the gap rather than by moving the breakpoint, because
     the 920 threshold and the `max-width: 919.98px` query in §11 are a matched
     pair and moving one opens a band where no nav layout applies at all — the
     comment on that query says so. Five gaps at 8px less is 40px recovered,
     against 7px needed. The roomier gap returns at 1024. */
  .nav__list {
    flex-direction: row;
    align-items: center;
    gap: var(--sp-s);
    width: auto;
  }
  .nav__link { padding-inline: 0.2em; }
}

/* --- ≥ 1024 -------------------------------------------------------------- */
@media (min-width: 1024px) {
  /* The roomier nav gap now waits until 1200. The search control returns to the
     bar at 1024 (§23), and giving the links their wider gap at the same width
     pushed the bar 7px past its container. Holding the tight gap through the
     1024–1199 band leaves room for the control; the wide gap comes back at 1200,
     with the viewport width to pay for it. The 920/919.98 pair is untouched. */
  .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .footer__cols { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* 300px, not the original's 360px, and the 60px gutter is kept. MEASURED, not
     preferred: a 3/4 plate 360px wide is 480px tall, and 480px is more than a
     six-piece issue has to say. At 360 every record in the archive was floored
     at the cover's height and eleven of the twelve measured within 1px of each
     other — a uniform grid arrived at by arithmetic rather than by choice,
     which is the thing this layout exists to avoid (NOT-AI-MADE §1.1). At 300
     the plate is 400px and the record's own content sets the height, so the
     entries vary with the issues. The one number in the original that could not
     survive contact with an "Inside this issue" list on every entry. */
  .issue-entry { grid-template-columns: 300px minmax(0, 1fr); gap: 60px; }
  .readingpane { --pad: var(--sp-xl); }

  /* Marginalia: at this width a folio can sit in the outer margin of the
     reading pane the way a running head does in print. */
  .folio--margin {
    position: absolute;
    left: calc(-1 * var(--sp-2xl));
    top: var(--sp-xl);
    writing-mode: vertical-rl;
    transform: rotate(180deg);
  }
}

/* --- ≥ 1200: the nav's roomier gap, held back from 1024 to make room for the
   search control in the bar (see the note in the ≥1024 block and §23). ------ */
@media (min-width: 1200px) {
  .nav__list { gap: var(--sp-m); }
}

/* --- ≥ 1440 -------------------------------------------------------------- */
@media (min-width: 1440px) {
  /* The record does NOT get a wider cover column here. 360px is the size at
     which the plate reads as a cover; past that it starts competing with the
     record beside it, and the extra width is worth more to the contents list,
     which is the part that actually gets longer. */
  .readingpane { --pad: var(--sp-2xl); }
}

/* Dark-mode preference: the site is already a dark room. Paper sections stay
   paper — they are an editorial decision, not a theme. Nothing to switch. */


/* =============================================================================
   17  REDUCED MOTION
   Everything that moves is listed here and stops. The STAY LIT. sign becomes a
   fully static, fully lit sign — not a dimmed one (SPEC §11).
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  /* Belt and braces: kill the sign's animations by name and pin it lit. */
  .sign__text,
  .sign__dot {
    animation: none !important;
    opacity: 1 !important;
    filter: none !important;
  }
  .ground-void--drift::after {
    animation: none !important;
    transform: none !important;
  }
  /* The cover plate's hover is a border and a bloom, not a movement, so there
     is nothing here to disable — the transition durations are already zeroed by
     the blanket rule at the top of this block. */
}


/* =============================================================================
   20  PAGE COMPONENTS

   Merged from the templates/_css-additions folder, which page-template agents staged
   for the owner of this file rather than opening a second stylesheet. The source
   files are deleted; this is now the only copy.

   Audited on merge — no new hues, no new families, no monospace, no resurrected
   --neon/--ember, no @font-face, no @import, no !important, no outline:none, no
   text-align:justify, no negative margin-top, no backdrop-filter. Every colour
   resolves through the §02 ground-context tokens, so all of it is correct on
   both grounds without a component-level override.

   Changed on merge: three bare "1fr" grid tracks were rewritten to
   "minmax(0, 1fr)" per the rule in §12 — a bare 1fr has an automatic minimum,
   which lets one wide child push the whole page sideways.
   ============================================================================= */


/* ---------------------------------------------------------------------------
   MERGED FROM templates/_css-additions/static.css
   --------------------------------------------------------------------------- */

/* =============================================================================
   PROPOSED ADDITIONS to assets/css/system.css — from templates/static.mjs
   NOT LOADED BY ANY PAGE. Other agents are editing system.css in parallel, so
   this file is a patch to be merged by whoever owns that file, not a second
   stylesheet.

   Both rules below are PURE ENHANCEMENT. Every page in static.mjs renders
   correctly today without them: the markup composes existing components and
   these classes only sharpen what they already say. Nothing here is required
   for legibility, contrast or layout.

   Two additions. That is the whole patch.
   ============================================================================= */


/* -----------------------------------------------------------------------------
   1. .callcard--unresolved  —  merge into §13 MODULES, beside .callcard--open
                                and .callcard--closed

   THE THIRD STATE, AND THE REASON IT HAS TO EXIST.

   system.css ships .callcard--open (solid accent edge, lit dot) and
   .callcard--closed (dashed edge, muted). The submissions page can use
   NEITHER, because not one of the three open calls on the live site names a
   year:

       Flash Fiction   "Opens August 16th - September 20th"
       Poetry          "Open July 12th - August 16th"
       Visual Art      "August 15 - October 1st"

   Open or closed is therefore not derivable, and a status board that guesses
   sends a writer to a closed call or hides an open one. This class is how the
   board says "we do not know" in the same visual language as the other two
   states, rather than defaulting to one of them.

   Read it as: the tube is fitted, the sign is not switched on. A ring, not a
   filled dot; a dotted edge, between the open card's solid and the closed
   card's dashed.

   The base .callcard already renders acceptably without this rule (neutral
   --fg-faint edge and dot), and the words "Year not stated" are in the markup,
   so the state is never carried by colour alone (SPEC §11).

   REMOVE THIS CLASS if Jonathan supplies the years and the board can compute
   a real state. It should not outlive the gap it documents.
   -------------------------------------------------------------------------- */

.callcard--unresolved {
  border-left-style: dotted;
  border-left-color: var(--accent-2);
}

.callcard--unresolved .callcard__status {
  color: var(--accent-2);
}

/* A ring, not a disc: unlit rather than lit or dead. No glow — the bloom in
   .callcard--open means "this is on", and reusing it here would say the
   opposite of what this state means. */
.callcard--unresolved .callcard__dot {
  background-color: transparent;
  box-shadow: inset 0 0 0 2px var(--accent-2);
}


/* -----------------------------------------------------------------------------
   2. .needsfact  —  merge into §13 MODULES

   The editorial placeholder. Marks a fact the build refuses to invent: the
   missing submission-portal URL, the anthology byline discrepancy, the unwired
   contact form, every open question on the privacy page.

   Applied alongside .card, so without this rule it is already a bordered block
   with an accent eyebrow. This rule makes it unmistakably provisional: a
   dashed border reads as "not finished" everywhere in print and in software,
   which is exactly what stops one of these shipping unnoticed.

   Deliberately NOT hidden behind a build flag. A placeholder that only exists
   in a code comment is a placeholder that goes live.

   These blocks are removed as their facts arrive. When the last one goes, this
   class goes with it.
   -------------------------------------------------------------------------- */

.needsfact {
  border-style: dashed;
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background-color: color-mix(in srgb, var(--accent) 5%, transparent);
}

.needsfact:hover {
  /* .card lifts its border on hover; a placeholder is not a link and must not
     behave like one. */
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background-color: color-mix(in srgb, var(--accent) 5%, transparent);
}


/* -----------------------------------------------------------------------------
   3. THE THREE STATIC PAGES THAT MEASURED ZERO MOVES

   /about/, /masthead/ and /anthology/women-who-burn/ each declared a move in a
   template header and rendered none. Every one was a single 736px column: no
   second container width anywhere in the family, and on /about/ and /masthead/
   the "biggest thing on the page" was the h1 rather than the thing the comment
   named. These three rules are what makes the declared move true, and each one
   is verifiable with getBoundingClientRect rather than by reading a comment.
   -------------------------------------------------------------------------- */

/* --- /masthead/: the two names ARE the page ------------------------------ */
/* The staff list was `dd class="h2"` — 40px, identical to the three ordinary
   section headings below it. Now the names carry display type and the word
   "Masthead" is set down to label size, because on a real masthead the label is
   furniture and the names are the content. Two people is the whole staff, and
   the page should be unable to pretend otherwise. */
.masthead__label {
  font-size: var(--t-h4);
  letter-spacing: var(--tr-meta);
  color: var(--fg-muted);
}
.masthead__staff { margin: 0; }
.masthead__role { margin-top: var(--sp-l); }
.masthead__name {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "wght" 460;
  font-size: var(--t-display);
  line-height: var(--lh-display);
  letter-spacing: -0.02em;
  color: var(--fg);
}

/* --- /about/: the standing policy actually breaks the measure ------------- */
/* `.pullquote` caps itself at --w-wide, which is why the plate measured exactly
   736px inside a 736px column and broke nothing. `--plate` lets it run its
   container (.wrap--full) and sets it above the h1, so the sentence the page
   exists for is the largest thing on the page rather than the fourth largest.
   The policy prose underneath stays at the reading measure. */
.pullquote--plate {
  width: 100%;
  max-width: none;
  font-size: var(--t-display);
  line-height: 1.08;
}
.policyplate .prose { margin-top: var(--sp-xl); }

/* --- /anthology/: the imprint plate --------------------------------------- */
/* A printed book puts this on one page, in small type, in a column much
   narrower than its text block. So does this. 26rem against the page's 46rem
   measure — a second width on a page that had exactly one. */
.imprint {
  max-width: 26rem;
  padding: var(--sp-m);
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
}
.imprint__byline {
  margin: var(--sp-2xs) 0 0;
  font-family: var(--font-display);
  font-variation-settings: "wght" 540;
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg-muted);
}

/* --- /support/: the price column ------------------------------------------
   The rate card. Five ways to help, each with what it costs the READER printed
   in the margin beside it, ordered cheapest first so the column reads
   "Nothing · Nothing · Your time · Cover price · Up to you" and the money lands
   at the bottom.

   THE COST IS THE PAGE'S ONE MOVE, so it is the largest repeated type here and
   the only display-face element besides the h1 — --t-h2 against the rungs'
   --t-h3. It sits at --fg-faint rather than at an accent: five 40px words in
   pink or amber would be an accent filling an area, which SPEC §2.1 rules out,
   and a folio-grey numbers column is what the print object this imitates
   actually looks like. --fg-faint measures 5.4:1 on void and 5.1:1 on paper, so
   it clears AA for normal text and is nowhere near the large-text threshold.

   NO TIERS, and the structure is the reason it cannot grow any. There is no
   .ways__benefit, no price attribute, no featured modifier and no card — a rung
   is a cost, a name and one sentence. The support page that ships tier cards
   ships them because the component had a slot for them.

   Two columns only from 768. Below that the cost stacks above its rung, still
   larger than the rung's own title, which keeps the ordering legible on a phone
   where a margin column would leave ~150px for the sentence. */
.ways {
  list-style: none;
  margin: 0;
  padding: 0;
}
.ways__item {
  display: grid;
  gap: var(--sp-3xs);
  padding-block: var(--sp-m);
  border-top: 1px solid var(--hair-soft);
}
.ways__item:last-child { border-bottom: 1px solid var(--hair-soft); }
.ways__cost {
  font-family: var(--font-display);
  font-variation-settings: "wght" 440;
  font-size: var(--t-h2);
  line-height: var(--lh-tight);
  color: var(--fg-faint);
}
.ways__title {
  font-family: var(--font-display);
  font-variation-settings: "wght" 520;
  font-size: var(--t-h3);
  line-height: var(--lh-tight);
  color: var(--fg);
}
.ways__note {
  margin-top: var(--sp-2xs);
  max-width: var(--measure);
  color: var(--fg-muted);
}

@media (min-width: 768px) {
  /* 11rem holds "Cover price" on one line at 40px and lets "Up to you" break to
     two, which is what a rate card does anyway. minmax(0, 1fr) on the second
     track, not a bare 1fr — §12's rule: a bare 1fr has an automatic minimum and
     one long word in a rung would push the page sideways. */
  .ways__item {
    grid-template-columns: 11rem minmax(0, 1fr);
    column-gap: var(--sp-l);
    align-items: baseline;
  }
  .ways__cost { text-align: right; }
}


/* ---------------------------------------------------------------------------
   MERGED FROM templates/_css-additions/issues.css
   --------------------------------------------------------------------------- */

/* =============================================================================
   12b  ISSUE LANDING & ARCHIVE — from templates/issues.mjs
   (the archive at /issues/ and the twelve issue landing pages)

   Everything below is ENHANCEMENT, not scaffolding. The two pages are built from
   existing system.css components and render correctly without a single line of
   this — these rules add the hierarchy that stops the archive and the contents
   from reading as a uniform grid (NOT-AI-MADE §1.1).

   No new tokens. No new hues. Every colour is a semantic token from §02, so
   every rule is correct on the void ground and on the paper ground without an
   override — regression #3 (cream text on cream paper) cannot recur through
   anything here.
   ========================================================================== */


/* -----------------------------------------------------------------------------
   RECORDS THAT ARE NOT LIKE THE OTHERS

   The two exceptions the archive has to express are the current issue and the
   anthology, and both are now handled where they belong rather than here:

   • the current issue  →  `.issue-entry--current` in §12, which gives it an
     accent rule above and a size step on the headline.
   • the anthology      →  `.status-anthology` in §12. The April anthology is a
     printed book rather than an issue, and the status line says so in words
     ("Anthology · also in print") instead of encoding it as a doubled border
     nobody can read. SITE-ASSESSMENT §3.10 asked for the distinction to be
     visible; a legend-free border was visible but not legible.

   The `.catalog__entry--lead` and `--anthology` rules that used to sit here went
   with the card grid they modified.
   -------------------------------------------------------------------------- */


/* -----------------------------------------------------------------------------
   THE COVER PLATE
   The eleven scraped covers are still on the Wix media host and are not
   migrated yet, and August has no published cover file at all because the issue
   was never published. So the plate is the element that holds the space — the
   issue title set on a blank ground — and the cover image, when it exists, is
   laid over it. A gap in the asset migration degrades to a typographic cover
   rather than to a broken slot (NOT-AI-MADE §1.6 — a deliberately absent image
   needs a designed treatment, and an asset that has not landed is the same
   problem). templates/issues.mjs only emits the <img> once the file is on disk.
   -------------------------------------------------------------------------- */

.coverplate {
  position: relative;
  margin: 0;
  align-self: start;
}
.coverplate__plate {
  position: relative;
  display: flex;
  align-items: flex-end;
  aspect-ratio: 3 / 4;          /* the covers are portrait, not the 4/5 card crop */
  padding: var(--sp-m);
  font-family: var(--font-display);
  font-variation-settings: "wght" 460;
  font-size: var(--t-h3);
  line-height: 1.1;
  color: var(--fg-faint);
  background-color: color-mix(in srgb, var(--fg) 7%, transparent);
  border: 1px solid var(--hair-soft);
  border-radius: var(--r-card);
}
.coverplate__img {
  position: absolute;
  /* Hang from the TOP of the plate at the plate's own 3/4 ratio — NOT inset:0.
     inset:0 anchors to .coverplate, whose box also contains the caption below,
     so the image stretched down over the credit line ("Cover art by …") on all
     twelve issue heroes. Matching width + ratio makes the image cover exactly the
     plate and leaves the caption in the clear. */
  top: 0;
  left: 0;
  right: 0;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}
.coverplate__caption {
  position: relative;
  max-width: 22rem;
}
/* Credit and status are two separate statements, not one run-on sentence. The
   caption used to read "Cover art by Patty Paine — if The cover image is not on
   the site yet." on twelve pages, because the linked title ran straight into the
   next sentence and "if" reads as a conjunction. */
.coverplate__credit,
.coverplate__status { display: block; }


/* -----------------------------------------------------------------------------
   THE ISSUE HERO
   Asymmetric on purpose: the title block gets the long column, the cover hangs
   in the short one. `.grid--2` alone would centre the page on a 50/50 split,
   which is the centre-everything default NOT-AI-MADE §1.2 bans.
   -------------------------------------------------------------------------- */

@media (min-width: 768px) {
  .issuehero__grid {
    grid-template-columns: minmax(0, 1.45fr) minmax(0, 0.55fr);
    gap: var(--sp-2xl);
    align-items: start;
  }
}
@media (min-width: 1024px) {
  .issuehero__grid { gap: var(--sp-3xl); }
}


/* -----------------------------------------------------------------------------
   THE CONTENTS — the numbered table of contents on an issue page

   Jonathan's own design, recovered from docs/reference/original-index.html (`.toc`
   / `.toc-item`) and rebuilt on this system's tokens. It replaces the `.contents__*`
   family that stood here, which grouped the pieces into <h3> kind sections with one
   lead item at display size. The argument for the change is set out in full in
   templates/issues.mjs `issuePage` and is not repeated here; the short version is
   that a contents page prints the issue's running order and grouping destroyed it.

   THE ROW IS THE ANCHOR. `.toc-item` IS the <a>, wrapping number, text and arrow,
   so the tap target is the whole row rather than the eight characters of "Ritual"
   (SPEC §11). Every descendant is a <span> — an <a> may not contain <p> or <ol> —
   and `.toc-text` blockifies them as flex items.

   GROUND-AWARE, NOT COPIED. The original ran on the VOID ground and its greys were
   picked for it. Measured against this list's actual ground, --paper #f4eadb:

     title   --paper-dim  #c9bcae   1.56:1   (10.89:1 on void)
     num,
     byline  --paper-mute #8a7f74   3.29:1   ( 5.18:1 on void)
     genre   --pink       #ff2e88   2.94:1   ( 5.79:1 on void)

   Three of the four fail AA outright, and the title — the largest thing in the row
   — fails worst. Carrying the original's values across would have produced a
   contents page that is legible in the reference file and not on the site. Nothing
   below names a colour: every value is a semantic token from §02, so these rules
   are correct on paper today and would be correct on void unchanged.

   AXES. The original's settings included "SOFT" 60 / "WONK" 1, which were Fraunces
   axes. The display face is Frank Ruhl Libre now and has neither, so each rule sets
   --dsp-wght only (§04).

   WIDTH. The list is capped at --w-wide (736px), not at the section's 72rem. The
   arrow is right-aligned in the row, and at 1152px the eye would cross a hand's
   width of empty paper to get from a title to its own arrow. 736px is the folio
   column the rest of this page family already sets, so the contents sit in the
   same column as the prose above them.
   -------------------------------------------------------------------------- */

.toc {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--w-wide);
  border-top: 1px solid var(--hair);
}

/* Two columns at the narrow end and three from 560px up. At 380px a 36px number,
   a title and a 28px arrow leave the title 250px, which wraps every one of the
   longer titles to three lines; the arrow is the part that carries no information
   (it is aria-hidden — the row is a link and announces itself as one), so it is
   the part that goes. The number narrows with it. Nothing overflows and nothing
   is truncated: the text column is minmax(0, 1fr), so a long unbroken title wraps
   inside the row instead of pushing the grid wider than the page. */
.toc-item {
  display: grid;
  grid-template-columns: 1.75rem minmax(0, 1fr);
  column-gap: var(--sp-s);
  align-items: baseline;
  min-height: 44px;                 /* floor; rows measure 100px+ in practice */
  padding-block: var(--sp-s);
  border-bottom: 1px solid var(--hair-soft);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--dur) var(--ease);
}
@media (min-width: 560px) {
  .toc-item {
    grid-template-columns: 2.25rem minmax(0, 1fr) auto;
    column-gap: var(--sp-m);
  }
}

/* Hover moves the hairline to the accent and underlines the title. The underline
   matters: colour alone is not an affordance, and the row's other hover cues are
   all colour (SPEC §11). The original also slid the row 24px to the right on
   hover; that is a layout shift under the pointer on a list of fourteen links and
   is not carried over. */
.toc-item:hover {
  color: inherit;                   /* not the global a:hover accent — the row is not one word */
  border-bottom-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.toc-item:hover .toc-title {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
.toc-item:hover .toc-num,
.toc-item:hover .toc-arrow { color: var(--accent); }
.toc-item:hover .toc-arrow { transform: translateX(0.2em); }

/* Upright. The original set these numerals in an italic the display face no
   longer has, and a synthesised slant is at its most obvious on lining tabular
   figures, where every digit leans by the same machine angle down a column of
   eleven. The number does its job standing up. */
.toc-num {
  --dsp-wght: 400;
  font-family: var(--font-display);
  font-variation-settings: "wght" var(--dsp-wght);
  font-size: var(--t-h4);
  line-height: var(--lh-tight);
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--fg-muted);
  transition: color var(--dur) var(--ease);
}

.toc-text {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
  min-width: 0;
}

.toc-title {
  --dsp-wght: 400;
  font-family: var(--font-display);
  font-variation-settings: "wght" var(--dsp-wght);
  font-size: var(--t-h3);
  line-height: var(--lh-tight);
  letter-spacing: -0.008em;
  color: var(--fg);
  transition: color var(--dur) var(--ease);
}

/* Body face, italic — the original's byline voice. Text, not a link: see
   `authorLine` in templates/issues.mjs for why the row can only hold one anchor. */
.toc-author {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-small);
  line-height: var(--lh-ui);
  color: var(--fg-muted);
}

/* The genre, from the closed vocabulary. Rendered only when the piece records
   one — there is no "Other" row and no empty slot where a label would go. */
.toc-genre {
  --dsp-wght: 620;
  font-family: var(--font-display);
  font-variation-settings: "wght" var(--dsp-wght);
  font-size: var(--t-micro);
  line-height: var(--lh-ui);
  letter-spacing: var(--tr-micro);
  text-transform: uppercase;
  color: var(--accent);
}

/* The opener's own first line. Body face at reading size and capped at the reading
   measure — it is a sentence of somebody's poem, not another metadata row. */
.toc-opening {
  max-width: var(--measure);
  margin-top: var(--sp-2xs);
  font-size: var(--t-base);
  line-height: 1.45;
  color: var(--fg-muted);
}

/* A row carries these only when the PIECE carries them: a dedication, a recorded
   content warning, the other issues its author appears in. Most rows have none,
   which is the variation showing rather than a metadata chip on every entry. */
.toc-note {
  max-width: var(--measure);
  margin-top: var(--sp-3xs);
  font-size: var(--t-small);
  line-height: 1.4;
  color: var(--fg-faint);
}
.toc-note--cw { color: var(--accent-2); }

/* Decorative and aria-hidden. Present from 560px up; the row is a link with or
   without it. `align-self: center` rather than the grid's baseline, because a
   28px italic arrow sitting on a 28px title's baseline reads as a dropped glyph. */
.toc-arrow {
  --dsp-wght: 400;
  font-family: var(--font-display);
  font-variation-settings: "wght" var(--dsp-wght);
  font-size: var(--t-h3);
  line-height: 1;
  align-self: center;
  justify-self: end;
  color: var(--fg-faint);
  transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
@media (max-width: 559.98px) {
  .toc-arrow { display: none; }
}

/* §17 zeroes every transition DURATION, which turns the arrow's hover nudge from
   a glide into a 5px jump under the pointer rather than removing it. The nudge is
   decoration on a decoration, so it is dropped outright here instead. Kept local
   to this component rather than added to §17 — the colour changes are still
   wanted, and they are what tells a reduced-motion reader the row is live. */
@media (prefers-reduced-motion: reduce) {
  .toc-item:hover .toc-arrow { transform: none; }
}


/* -----------------------------------------------------------------------------
   THE ARCHIVE HEAD
   One deliberate exception to the measure: the standfirst on the archive runs
   wider than prose because it is a single statement of scale, not something read
   at length.
   -------------------------------------------------------------------------- */

.archive__head .lede { max-width: var(--w-wide); }


/* -----------------------------------------------------------------------------
   THE EDITOR'S LETTER
   Moved off the homepage onto the issue it is about (BACKLOG item 11). It reads
   at the prose measure with a drop cap, on paper.
   -------------------------------------------------------------------------- */

.letter { max-width: var(--w-wide); }
.letter .prose > p:last-child,
.letter .prose > p:nth-last-child(2) {
  font-family: var(--font-display);
  font-variation-settings: "wght" 560;
  font-size: var(--t-h4);
  line-height: 1.3;
  color: var(--fg-muted);
}
.letter .prose > p:nth-last-child(2) { margin-top: 1.8em; }


/* =============================================================================
   12c  HOMEPAGE — THE COVER HERO & THE ISSUE BREAKDOWN
   (templates/index.mjs)

   Recovered from Jonathan's own earlier homepage, docs/reference/original-index.html,
   and blended back in. Two elements, and he named both:

     `header.hero`      -> `.cover-hero`   the cover artwork IS the hero
     `section#now-lit`  -> `.breakdown`    cover plate left, the issue's own
                                           account of itself beside it

   Deliberately NOT carried over from the original: the pulsing eyebrow dot, the
   glowing wordmark on its 6s doorway-pulse, and the amber flicker on the word
   "lit" in the hero tagline. Three animated elements in the first viewport, on
   a page whose ONE move (SPEC §5.2) is the sign at the foot. The hero here is a
   still photograph with type on it, which is what a magazine cover is, and it
   leaves the sign as the only thing on the page that moves.

   No new tokens and no new hues. Every colour resolves through the §02 ground
   contexts or the §01 smoke ramp.
   ========================================================================== */


/* -----------------------------------------------------------------------------
   THE COVER HERO

   Three layers, bottom to top: the artwork, a scrim, the content.

   THE SCRIM IS A SCRIM, NOT A DIMMED IMAGE. Lowering the <img>'s own opacity
   mixes the artwork toward whatever sits behind it and collapses its blacks —
   the picture loses its tonal range so that the text can have some, and on this
   cover (a collage whose darks are most of its subject) that is most of the
   work. A separate scrim layer leaves the artwork intact and darkens only the
   region the type occupies, which is what a printed cover does with a colour
   block behind its cover lines.

   VERIFIED AGAINST COMPOSITED PIXELS, NOT AGAINST A TOKEN. Every other pair on
   this site is two flat colours out of §01 and `tools/contrast.mjs` can check
   it from the stylesheet. Here there is no hex value to check: the background of
   the headline is a photograph. So the hero is rendered to a canvas — the same
   `object-fit` crop the browser uses, then the scrim's layers parsed out of the
   element's own COMPUTED background-image so the measurement cannot drift from
   these rules — and for each piece of type the LIGHTEST pixel anywhere in its
   box is taken as the background. Worst case a letterform can land on.

   Result across 380×500, 380×720, 768, 920, 1024 and 1440: no failures, and the
   tightest pair on the page is 4.92:1. Full table, plus the measured share of
   the artwork that survives the scrim, in qa/CONTRAST.md.

   The arithmetic that sets the floor, for anyone re-tuning the stops: --paper
   (#f4eadb) has a relative luminance of 0.832, so a background must sit at or
   below 0.146 — sRGB 107 — for 4.5:1. The worst pixel this artwork can hold is
   pure white; a scrim of alpha `a` over white composites to 255 − 247a, which
   reaches 107 at a = 0.60. That is the floor UNDER THE TYPE. It is emphatically
   not the floor for the frame — the first version of this rule applied it to the
   whole frame and erased the picture. See the note on `.cover-hero__scrim`.

   The three chips — status line, issue card, cover credit — do not rely on the
   scrim at all: each carries its own near-opaque ground, because the credit sits
   at the right-hand edge of the frame, which is deliberately the weakest part of
   the scrim so that the artwork can be seen there. Solid colour, never
   backdrop-filter: glassmorphism is out (SPEC §2.4).
   -------------------------------------------------------------------------- */

.cover-hero {
  display: flex;
  flex-direction: column;
  min-height: 40rem;                    /* floor for browsers without svh */
  min-height: min(44rem, 90svh);
  padding-block: 0;                     /* the hero sets its own, inside */
  overflow: hidden;
  isolation: isolate;
  /* The missing-cover fallback, painted always and simply covered by the <img>
     when there is one. Same gradient family as the archive's `.issue-cover`, so
     a homepage whose cover has not been migrated degrades to the plate the
     archive already degrades to (NOT-AI-MADE §1.6) rather than to a black band
     with type floating on it. templates/index.mjs emits the <img> only once the
     file is on disk, so this is reachable, not decorative. */
  background-image: linear-gradient(
    158deg,
    var(--smoke) 0%,
    var(--smoke-lift) 32%,
    var(--smoke-hi) 62%,
    color-mix(in srgb, var(--pink-deep) 55%, var(--smoke-hi)) 100%
  );
}

/* Child selectors, not bare class selectors. §06 gives every direct child of a
   ground section `position: relative; z-index: var(--z-content)` at the same
   specificity as a single class, so these would win on source order alone —
   which is true today and is the kind of thing that stops being true when
   somebody reorders the file. */
.cover-hero > .cover-hero__bg,
.cover-hero > .cover-hero__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.cover-hero > .cover-hero__bg    { z-index: 0; }
.cover-hero > .cover-hero__scrim { z-index: 1; }

.cover-hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* 42%, not 50%. The scan carries a white mount top and bottom; at 42% the
     crop band is the collage itself rather than its margins. */
  object-position: 50% 42%;
  display: block;
}

/* --- the scrim ------------------------------------------------------------
   THE STOPS ARE IN PIXELS FROM THE BOTTOM, and that is the load-bearing
   decision here rather than a detail. The head carries `margin-top: auto` and
   the footer follows it, so the type block sits at a fixed 36–450px off the
   bottom edge of the frame no matter how tall the frame is. Percentage stops
   track the FRAME; the type does not. Measured at 380 × 500, a percentage ramp
   let the head rise to 22% of the frame while the ramp was still at 0.28 there,
   and the <h1> landed on the artwork's white strip at 1.67:1 against a floor of
   3. Pixels from the bottom follow the type instead, and the same stops then
   hold from a 450px frame to a 1080px one.

   TWO EARLIER VERSIONS, both recorded because both were plausible and wrong:

   1. A flat 0.66 across the whole frame, derived from the worst pixel the
      artwork could THEORETICALLY put behind a word (pure white, which this scan
      really does contain). Every contrast check passed. Measured against the
      real composited pixels it left 1–2% of the artwork's luminance standing:
      the cover was a black rectangle with a sliver of picture at one edge,
      which is not an integrated cover, it is the old problem with an image file
      attached to it. Worst-case arithmetic sets the floor. The measurement has
      to decide where the floor is actually needed.

   2. A corner — `to top right`, dark where the type is, clear at the opposite
      corner. On a 1425 × 704 frame the magic-corner angle is 64° off vertical,
      so it is very nearly a horizontal gradient: the left third held 1.7%
      transmission from top to bottom and the right third 86%. The subject of
      this cover is a face at 8–45% of a square canvas, so the corner put the
      whole of it behind the dark end.

   What is here now: the top of the frame is left almost alone across its full
   width and the picture reads there, and the darkness rises to meet the type.
   That is what a printed cover does with its cover lines.
   -------------------------------------------------------------------------- */
/* A GUARANTEED FLOOR BEHIND THE TYPE.
 *
 * The ramp below is a bottom-to-top gradient: 94% void at the foot of the frame,
 * 12% at 700px. That is right for the picture — the artwork stays visible at the
 * top — but the type does NOT all sit at the foot, and a cover is not a
 * predictable ground. Measured against Patty Paine's cover, which carries a
 * near-white passage at (225,225,213) directly behind the masthead, every piece of
 * hero text failed WCAG AA at its worst pixel: the h1 at 1.11:1 and the status
 * line at 1.00:1 — invisible.
 *
 * The fix is scoped to the content column rather than the whole frame, so the
 * artwork is still clear above and around the type instead of being flattened.
 * 62% is the measured floor: over the brightest pixel on this cover it puts paper
 * text at 4.66:1, and it composites with the ramp beneath it, so real values are
 * higher everywhere. This has to hold for covers nobody has seen yet, which is why
 * it is a floor and not a value tuned to one image.
 */
.cover-hero__inner {
  position: relative;
  isolation: isolate;
}
.cover-hero__inner::before {
  content: "";
  position: absolute;
  inset: -2rem -2rem -2rem -2rem;
  z-index: -1;
  pointer-events: none;
  background-image: linear-gradient(
    to top,
    color-mix(in srgb, var(--void) 78%, transparent) 0,
    color-mix(in srgb, var(--void) 70%, transparent) 55%,
    color-mix(in srgb, var(--void) 62%, transparent) 100%
  );
  -webkit-mask-image: linear-gradient(to top, #000 82%, transparent 100%);
  mask-image: linear-gradient(to top, #000 82%, transparent 100%);
}

/* The status line is the archive's component, and on the archive it sits on a flat
 * token ground where its accent is a measured pass. Over a PHOTOGRAPH it cannot be:
 * --pink is a mid-luminance colour, so against the bright passages of a cover it
 * measured 1.00:1 at 12px, and no survivable scrim rescues it — at a 62% floor it
 * still only reaches 1.85:1 against a 4.5 requirement.
 *
 * So over the hero the LABEL carries the meaning in paper, and the accent survives
 * as the dot beside it. The dot is aria-hidden and duplicates the text, so it is
 * decorative and not held to the 3:1 non-text floor. Colour is never the only
 * signal here either way — the word "Current issue" says it.
 */
.cover-hero .issue-status { color: var(--paper); }
.cover-hero .issue-status .status-dot { background-color: var(--pink); }

.cover-hero__scrim {
  background-image:
    /* an edge for the sticky nav to sit against, rather than whatever the crop
       happens to put under it. Absolute, so it does not move with the frame. */
    linear-gradient(
      to bottom,
      color-mix(in srgb, var(--void) 30%, transparent) 0,
      transparent 90px
    ),
    /* the ramp, under the type */
    linear-gradient(
      to top,
      color-mix(in srgb, var(--void) 94%, transparent) 0,
      color-mix(in srgb, var(--void) 90%, transparent) 200px,
      color-mix(in srgb, var(--void) 84%, transparent) 340px,
      color-mix(in srgb, var(--void) 72%, transparent) 470px,
      color-mix(in srgb, var(--void) 34%, transparent) 580px,
      color-mix(in srgb, var(--void) 12%, transparent) 700px
    );
}

.cover-hero__inner {
  z-index: 2;
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--sp-l);
  padding-block: var(--sp-xl) var(--sp-l);
}

/* The status line, and it is the third chip rather than bare type on the scrim.
   MEASURED: it is set in --accent, and 12px of hot pink over the flat scrim at
   920px came out at 1.96:1 against the artwork's pale left margin. It clears
   4.5 on its own ground at every width. The original solved the same problem
   the same way — its hero eyebrow sat on rgba(8,5,11,0.65) — but as a pill;
   this one is near-square, because the one round thing on this site is the
   card-catalog punch hole (§01 radii).

   `align-self` because .issue-status is inline-flex and a flex item is
   blockified and stretched: the box was 1152px of mostly-empty line reaching
   into the weak right-hand end of the scrim, which would also have made a
   focus ring the width of the page. */
.cover-hero__inner > .issue-status {
  align-self: flex-start;
  padding: 0.6em 1em;
  background-color: color-mix(in srgb, var(--void) 88%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  border-radius: var(--r-card);
}

/* The head is pushed to the foot of the frame rather than centred in it: the
   top of a cover belongs to the picture. `auto` here is also what keeps the
   type inside the band the scrim is measured for at short viewport heights. */
.cover-hero__head { margin-top: auto; }
.cover-hero__head .lede { max-width: var(--measure); }

.cover-hero__footer {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-m);
}

/* --- the issue card ------------------------------------------------------ */
/* The route into the issue, and the only one in the hero. The original ran a
   card here AND a button above it pointing at the same page; two links to one
   destination inside one viewport is furniture. */
.cover-hero__card {
  display: flex;
  align-items: center;
  gap: var(--sp-m);
  min-height: 44px;
  padding: var(--sp-s) var(--sp-m);
  text-decoration: none;
  color: var(--fg);
  background-color: color-mix(in srgb, var(--void) 88%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 38%, transparent);
  border-radius: var(--r-card);
  transition: border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
.cover-hero__card:hover {
  border-color: color-mix(in srgb, var(--accent) 72%, transparent);
  background-color: color-mix(in srgb, var(--void) 94%, transparent);
}
.cover-hero__card-label {
  display: block;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  /* --fg-muted, not --fg-faint. MEASURED: over this chip's own composite the
     faint value lands at 4.4:1 and this line is 11px, so it is normal text and
     4.5 is the floor. --fg-muted measures 6.4:1 in the same place. */
  color: var(--fg-muted);
}
.cover-hero__card-title {
  display: block;
  margin-top: 0.45em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 440;
  font-size: var(--t-h4);
  line-height: var(--lh-tight);
}
.cover-hero__card-arrow {
  margin-left: auto;
  color: var(--accent);
  flex: none;
  transition: transform var(--dur) var(--ease);
}
.cover-hero__card:hover .cover-hero__card-arrow { transform: translateX(4px); }

/* --- the cover credit ---------------------------------------------------- */
/* Mandatory wherever the artwork appears. It carries its own ground because it
   sits at the weakest corner of the scrim by design — that corner is weak so
   the artwork can be seen, and the credit cannot pay for it. */
.cover-hero__credit {
  max-width: 22rem;
  margin: 0;
  padding: var(--sp-xs) var(--sp-s);
  background-color: color-mix(in srgb, var(--void) 88%, transparent);
  border-left: 2px solid color-mix(in srgb, var(--accent-2) 45%, transparent);
}
.cover-hero__credit-label,
.cover-hero__credit-by {
  display: block;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-muted);
}
/* An artwork title is italicised because that is how a work is cited, so the
   italic is not decoration and cannot just be dropped. Body face for it — see
   `.issue-bibmeta__value` for why display + italic is not available on this face. */
.cover-hero__credit-title {
  display: block;
  margin-block: 0.2em;
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 500;
  font-size: var(--t-h4);
  line-height: var(--lh-tight);
  letter-spacing: 0;
  text-transform: none;
  color: var(--fg);
}

/* --- >= 768: the footer becomes a row ------------------------------------ */
@media (min-width: 768px) {
  .cover-hero__footer {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
  }
  .cover-hero__credit { text-align: right; border-left: 0; border-right: 2px solid color-mix(in srgb, var(--accent-2) 45%, transparent); }
}

/* --- >= 1024: the ramp, plus a weight toward the reading side --------------
   Once the frame is landscape the type is a block in one corner and nothing
   else is, so the left of the frame carries a little more. A WEIGHT, not a
   wall: 0.55 at the left edge falling to nothing by 70%, which gives the
   headline margin over the artwork's white strip without taking the picture
   side's detail. The ramp above is unchanged and still does most of the work.
   -------------------------------------------------------------------------- */
@media (min-width: 1024px) {
  .cover-hero__head { max-width: var(--w-wide); }

  .cover-hero__scrim {
    background-image:
      linear-gradient(
        to right,
        color-mix(in srgb, var(--void) 42%, transparent) 0%,
        color-mix(in srgb, var(--void) 30%, transparent) 22%,
        color-mix(in srgb, var(--void) 12%, transparent) 48%,
        transparent 70%
      ),
      linear-gradient(
        to bottom,
        color-mix(in srgb, var(--void) 30%, transparent) 0,
        transparent 90px
      ),
      linear-gradient(
        to top,
        color-mix(in srgb, var(--void) 94%, transparent) 0,
        color-mix(in srgb, var(--void) 90%, transparent) 200px,
        color-mix(in srgb, var(--void) 84%, transparent) 340px,
        color-mix(in srgb, var(--void) 72%, transparent) 470px,
        color-mix(in srgb, var(--void) 34%, transparent) 580px,
        color-mix(in srgb, var(--void) 12%, transparent) 700px
      );
  }
}


/* -----------------------------------------------------------------------------
   THE ISSUE BREAKDOWN

   Two columns: the cover as an object on the left, the issue's account of
   itself on the right. Asymmetric — `.grid--2` alone gives the 50/50 split that
   is the centre-everything default (NOT-AI-MADE §1.2), and the cover wants the
   short column because a 3/4 plate at half a 72rem page is a poster.

   The plate is the archive's `.issue-cover` component, unmodified, including
   its missing-cover gradient and its corner folio. The homepage and /issues/
   render the same object because it is the same object.
   -------------------------------------------------------------------------- */

.breakdown__grid {
  display: grid;
  gap: var(--sp-xl);
  align-items: start;
}
@media (min-width: 768px) {
  .breakdown__grid {
    grid-template-columns: minmax(0, 0.7fr) minmax(0, 1.3fr);
    gap: var(--sp-2xl);
  }
}
@media (min-width: 1024px) {
  .breakdown__grid { gap: var(--sp-3xl); }
}

.breakdown__cover { margin: 0; }
.breakdown__cover .figcaption { margin-top: var(--sp-s); }

/* The editors' letter slot. Rendered only when `issue.editorsNote` holds one —
   it holds none for any of the twelve today, so this styles nothing at the
   moment and is here so that the day it does, the letter lands in the layout
   already set rather than being pasted in. `.letter` and `.prose` are the same
   components the issue page uses for the July letter, so a letter reads the
   same on both pages. */
.breakdown__letter { margin-top: var(--sp-l); }


/* -----------------------------------------------------------------------------
   THE NUMBERED CONTENTS

   The original's `01 02 03` table of contents. What is NOT carried over is its
   `toc-meta` sidebar (Issue / Pieces / Genres): on this page the issue is the
   <h1> two sections up, and the piece count and the genre mix are the display
   line of the breakdown directly above. A sidebar restating all three is the
   same facts a third time in a smaller type size.

   Numbers are real elements in the markup, not a CSS counter. A counter's
   generated content is announced by some screen readers, which would put "01"
   in front of a title inside an <ol> that is already numbered.
   -------------------------------------------------------------------------- */

.toc-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.toc-list__item {
  display: grid;
  grid-template-columns: 2.75rem minmax(0, 1fr);
  gap: 0.15em var(--sp-s);
  align-items: baseline;
  padding-block: var(--sp-s);
  border-top: 1px solid var(--hair-soft);
}
.toc-list__item:last-child { border-bottom: 1px solid var(--hair-soft); }

.toc-list__num {
  grid-column: 1;
  grid-row: 1;                        /* the number hangs in the gutter, top-aligned */
  align-self: start;
  padding-top: 0.15em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-meta);
  line-height: var(--lh-ui);
  letter-spacing: var(--tr-micro);
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--fg-faint);
}
.toc-list__item > :not(.toc-list__num) { grid-column: 2; }

.toc-list__link {
  display: block;
  min-height: 44px;                   /* tap target, SPEC §11 */
  padding-block: 0.1em;
}
.toc-list__item:hover .toc-list__num { color: var(--accent); }


/* ---------------------------------------------------------------------------
   MERGED FROM templates/_css-additions/piece.css
   --------------------------------------------------------------------------- */

/* =============================================================================
   CSS ADDITIONS REQUESTED BY templates/piece.mjs

   STAGING FILE — NOT LOADED BY THE SITE. Nothing here is linked from any page.
   These rules are written to be merged into assets/css/system.css by whoever
   owns that file; the merge targets are named per block. system.css is not
   edited here because other agents are working in it in parallel.

   Until the merge lands, piece pages render with correct SEMANTICS and correct
   TEXT but without these components — the poetry measure falls back to
   system.css's fixed --measure-poem, and the four new components below fall
   back to unstyled blocks. No page breaks; no page carries a <style> block.

   Two of these are OVERRIDES of existing declarations rather than new
   components, and are called out as such. Everything else is additive.
   ========================================================================== */


/* -----------------------------------------------------------------------------
   MERGE INTO §05 POETRY

   1. PER-POEM MEASURE.
   qa/POETRY-MEASURE.md §3: measure poetry in characters, per poem, from the
   poem's own longest line — `min(longestLineChars + 1ch, 80ch)`. Nine of the
   ten August poems then fit exactly. A single fixed container cannot do this:
   at 66ch it strands Fisher's "Disco Horse" (longest line 31 characters) and
   MacNeil's "Parked" (22) in a field of white space, and it still breaks
   Livesay's 96-character line and Morales's 78.

   Delivered as a ladder of classes on 2ch steps rather than an inline custom
   property, because no page on this site carries an inline style. The template
   rounds UP to the next step, so a poem never gets less room than the exact
   figure would have given it; the cost is at most 2ch of slack. Floor 24ch,
   ceiling 80ch — the measured point at which the whole August issue is down to
   one wrapped line out of 269.

   The class goes on the READING PANE, and both the pane and the poem inside it
   read --poem-measure, so a wide-lined poem widens its page instead of being
   squeezed by a pane sized for prose.
--------------------------------------------------------------------------- */

.readingpane--verse {
  width: min(
    100% - (2 * var(--sp-gutter)),
    calc(var(--poem-measure, var(--measure-poem)) + (2 * var(--sp-2xl)))
  );
}
@media (min-width: 1440px) {
  /* .readingpane's inline padding steps up to --sp-3xl at this width */
  .readingpane--verse {
    width: min(
      100% - (2 * var(--sp-gutter)),
      calc(var(--poem-measure, var(--measure-poem)) + (2 * var(--sp-3xl)))
    );
  }
}

.poem--measured { max-width: min(var(--poem-measure, var(--measure-poem)), 100%); }

.m-poem-24 { --poem-measure: 24ch; }
.m-poem-26 { --poem-measure: 26ch; }
.m-poem-28 { --poem-measure: 28ch; }
.m-poem-30 { --poem-measure: 30ch; }
.m-poem-32 { --poem-measure: 32ch; }
.m-poem-34 { --poem-measure: 34ch; }
.m-poem-36 { --poem-measure: 36ch; }
.m-poem-38 { --poem-measure: 38ch; }
.m-poem-40 { --poem-measure: 40ch; }
.m-poem-42 { --poem-measure: 42ch; }
.m-poem-44 { --poem-measure: 44ch; }
.m-poem-46 { --poem-measure: 46ch; }
.m-poem-48 { --poem-measure: 48ch; }
.m-poem-50 { --poem-measure: 50ch; }
.m-poem-52 { --poem-measure: 52ch; }
.m-poem-54 { --poem-measure: 54ch; }
.m-poem-56 { --poem-measure: 56ch; }
.m-poem-58 { --poem-measure: 58ch; }
.m-poem-60 { --poem-measure: 60ch; }
.m-poem-62 { --poem-measure: 62ch; }
.m-poem-64 { --poem-measure: 64ch; }
.m-poem-66 { --poem-measure: 66ch; }
.m-poem-68 { --poem-measure: 68ch; }
.m-poem-70 { --poem-measure: 70ch; }
.m-poem-72 { --poem-measure: 72ch; }
.m-poem-74 { --poem-measure: 74ch; }
.m-poem-76 { --poem-measure: 76ch; }
.m-poem-78 { --poem-measure: 78ch; }
.m-poem-80 { --poem-measure: 80ch; }

/* -----------------------------------------------------------------------------
   2. OVERRIDE — .poem__line white-space.

   system.css §05 sets `white-space: normal; text-wrap: nowrap` on .poem__line.
   POETRY-MEASURE §4 requires `white-space: pre-wrap` so that leading whitespace
   inside a line survives (SPEC §9.1 — some poets indent with spaces, and one
   piece, "but saying It, I can't", uses tab-separated horizontal placement as
   its whole form). `pre-wrap` also resets text-wrap-mode to `wrap`, which is
   the behaviour the existing comment says it wants: hold the authored line
   where possible, wrap rather than overflow when it cannot.

   MERGE ACTION: replace the `white-space` / `text-wrap` pair in .poem__line
   with this single declaration rather than appending this rule after it.
--------------------------------------------------------------------------- */

.poem__line { white-space: pre-wrap; }

/* -----------------------------------------------------------------------------
   3. Authorial alignment on a single line.

   system.css has .poem--centred, which centres the poem BLOCK on the page.
   That is not the same thing as an author centring their lines. Eight of the
   thirty scraped poems carry `text-align:center` on at least one block, and
   "Love, Persevering" carries it on all 28 — that is the shape of the poem.
   A centred line drops the hanging indent, because a hang on a centred line
   is not a hang, it is a lopsided line.
--------------------------------------------------------------------------- */

.poem__line--centre {
  text-align: center;
  text-indent: 0;
  padding-left: 0;
}
.poem__line--right {
  text-align: right;
  text-indent: 0;
  padding-left: 0;
}


/* -----------------------------------------------------------------------------
   MERGE INTO §04 TYPOGRAPHY — prose additions

   `data-preserve-space` and `data-indent` are the scraper's record of authorial
   whitespace and indentation (SPEC §9.1). The template converts them to these
   classes so no inline style reaches the output.
--------------------------------------------------------------------------- */

.prose .preserve-space { white-space: pre-wrap; }
.prose .indent-1 { padding-left: 2em; }
.prose .indent-2 { padding-left: 4em; }

/* The byline under the title. EB Garamond italic, one step up from the body —
   a magazine byline, not a metadata chip. */
.piece__byline {
  font-family: var(--font-body);
  font-size: var(--t-lede);
  font-style: italic;
  line-height: 1.4;
  color: var(--fg-muted);
}
.piece__byline a { text-decoration: none; }
.piece__byline a:hover { text-decoration: underline; }

/* A manuscript dedication ("for Diane"), held out of the verse in the content
   model so it can never be read as a line of the poem. */
.dedication {
  margin-top: var(--sp-m);
  font-family: var(--font-body);
  font-style: italic;
  color: var(--fg-muted);
}


/* -----------------------------------------------------------------------------
   NEW COMPONENT — CONTENT WARNING.  Merge into §13 MODULES.

   One piece in the August issue carries one. It was lifted out of the poem body
   in the content model so that it can never render as a line of verse, and it
   belongs before the poem, stated once, in the author's own words.

   Designed to be unmissable without being lurid: it is a marked block, not a
   red alarm, and it does not use the loudest accent on the site. The rule down
   its left edge is the same device as .callcard, so it reads as apparatus the
   reader has met elsewhere. --accent-2 measures 5.75:1 on paper and 10.4:1 on
   void, so it is legible on both grounds; the colour is never the only signal,
   because the words "Content warning" are carried by the aria-label and the
   warning text itself begins "CW:".
--------------------------------------------------------------------------- */

.cw {
  display: grid;
  gap: var(--sp-3xs);
  max-width: var(--measure);
  margin-block: var(--sp-l);
  padding: var(--sp-s) var(--sp-m);
  border-left: 4px solid var(--accent-2);
  background-color: color-mix(in srgb, var(--accent-2) 7%, transparent);
  border-radius: var(--r-card);
}
.cw__text {
  font-family: var(--font-display);
  font-variation-settings: "wght" 660;
  font-weight: normal;
  font-size: var(--t-h4);
  line-height: 1.3;
  color: var(--fg);
}
.cw__note {
  font-size: var(--t-small);
  font-style: italic;
  color: var(--fg-muted);
}


/* -----------------------------------------------------------------------------
   NEW COMPONENT — ARTWORK PLATE.  Merge into §08 PRINT-MAGAZINE VOCABULARY.

   Eleven pieces ARE artwork. The image is the work, so it gets a full plate:
   large, uncropped (`object-fit` is deliberately absent — .card__media crops,
   this must not), on the dark ground where a gallery would hang it. Nothing on
   this site should crop an artist's frame to fit a grid cell.

   .plate--inset is the smaller case: artwork that ran alongside a story or a
   poem, placed after the text so the reading comes first.

   .plate--pending is the honest state for an image whose file has not been
   migrated off Wix yet. It renders a caption and NO <img> element — an empty
   image element is what reads as broken, and it is what the live site does.
--------------------------------------------------------------------------- */

.plate {
  width: min(100% - (2 * var(--sp-gutter)), var(--w-full));
  margin-inline: auto;
  margin-block: var(--sp-xl);
}
.plate__img {
  width: 100%;
  height: auto;
  border-radius: var(--r-card);
}
.plate .figcaption { text-align: right; }

.plate--inset {
  width: 100%;
  max-width: var(--w-wide);
  margin-block: var(--sp-2xl) 0;
}

.plate--pending {
  display: grid;
  align-content: center;
  min-height: 18rem;
  max-width: var(--w-wide);
  padding: var(--sp-xl) var(--sp-l);
  border: 1px dashed var(--hair);
  border-radius: var(--r-card);
}
.plate--pending .figcaption {
  max-width: var(--measure);
  margin: 0 auto;
  text-align: left;
  font-size: var(--t-small);
  letter-spacing: normal;
  font-family: var(--font-body);
  line-height: 1.55;
}


/* -----------------------------------------------------------------------------
   NEW COMPONENT — THE LISTEN BLOCK.  Merge into §08 PRINT-MAGAZINE VOCABULARY.

   One piece in 125 has a recording, so this has to be invisible everywhere else:
   `templates/piece.mjs` emits nothing at all unless a piece carries an audio
   record, so no page without audio gains a rule, a gap, or a byte of CSS work.

   The player is the BROWSER'S OWN `<audio controls>`, not a scripted one. That
   is a deliberate accessibility decision rather than a shortcut: native controls
   are keyboard operable, expose real roles and time values to a screen reader,
   honour the reader's own media and motion settings, and keep working with
   JavaScript off, which is the rule for this whole site. A custom player would
   have to earn all of that back and would still be worse. What is styled here is
   the frame around it — the browser's control surface is left alone, because
   restyling it means replacing it.

   It sits on the paper ground at the foot of the reading pane, set like a
   sidebar rather than a media widget: a small display-type label, the control,
   and one quiet line giving the length and a download link for anyone whose
   browser cannot play the file or who would rather keep it.

   Focus: the global `:focus-visible` rule at §14 already puts a 2px --focus ring
   on the element, and the outline is offset here so it clears the control's own
   rounded chrome instead of cutting into it.
--------------------------------------------------------------------------- */

.listen {
  max-width: var(--w-wide);
  margin: var(--sp-2xl) auto 0;
  padding-top: var(--sp-l);
  border-top: 1px solid var(--hair);
  display: grid;
  gap: var(--sp-s);
}

/* IN THE PIECE HEAD the same component is an offer, not a footnote, so it loses
   the rule-above-and-lots-of-air treatment that suited a page footer and gains a
   frame that says "this is a thing you can press". It is left-aligned to the
   title rather than centred, because in the head it belongs to the byline block
   above it and not to the page. */
.piecehead .listen {
  max-width: 32rem;
  margin: var(--sp-l) 0 0;
  padding: var(--sp-s) var(--sp-m) var(--sp-m);
  border-top: 0;
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  background-color: color-mix(in srgb, var(--void) 55%, transparent);
  gap: var(--sp-2xs);
}
.piecehead .listen__label { color: var(--accent); }
.piecehead .listen__caption { margin-bottom: var(--sp-2xs); }
.listen__label {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-muted);
  margin: 0;
}
.listen__player {
  width: 100%;
  max-width: 32rem;
  height: 44px;                  /* tap target ≥ 44px (SPEC §11) */
  border-radius: var(--r-card);
}
.listen__player:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}
.listen__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2xs) var(--sp-s);
  font-size: var(--t-small);
  color: var(--fg-muted);
  margin: 0;
}
.listen__detail { color: var(--fg-faint); }


/* -----------------------------------------------------------------------------
   NEW COMPONENT — THE UNTRANSCRIBED PIECE.  Merge into §08.

   Casey Clough's "Down Bad Girl" exists in the publishing record but its text
   exists in neither source document. This is the treatment for that: say so,
   print the fragments that ARE recorded, invent nothing. Built to look like a
   catalogue record rather than an error message, because it is one.
--------------------------------------------------------------------------- */

.fragments {
  display: grid;
  gap: var(--sp-s);
  max-width: var(--measure);
  padding: var(--sp-m) 0;
  border-top: 1px solid var(--hair);
  border-bottom: 1px solid var(--hair);
}
.fragments__label {
  font-family: var(--font-display);
  font-variation-settings: "wght" 680;
  font-weight: normal;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--accent);
}
.fragments__note {
  font-size: var(--t-small);
  line-height: 1.55;
  color: var(--fg-muted);
}
.fragments__quote {
  margin: 0;
  padding-left: 1.15em;
  border-left: 2px solid var(--accent-2);
  font-family: var(--font-display);
  font-variation-settings: "wght" 420;
  font-weight: normal;
  font-size: var(--t-h4);
  line-height: 1.35;
}
.fragments__list {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0.35em var(--sp-m);
  margin: 0;
  font-size: var(--t-small);
}
.fragments__list dt {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
  padding-top: 0.3em;
}
.fragments__list dd { margin: 0; font-style: italic; }
@media (max-width: 479px) {
  .fragments__list { grid-template-columns: minmax(0, 1fr); gap: 0.15em; }
  .fragments__list dd { margin-bottom: 0.5em; }
}


/* -----------------------------------------------------------------------------
   MERGE INTO §15 UTILITIES — one missing counterpart.
   system.css has .text-centre but no .text-right; the scraper records
   right-aligned authorial blocks and they have to land somewhere.
--------------------------------------------------------------------------- */

.text-right { text-align: right; }


/* ---------------------------------------------------------------------------
   MERGED FROM templates/_css-additions/contributors.css
   --------------------------------------------------------------------------- */

/* =============================================================================
   CONTRIBUTORS — components to be merged into assets/css/system.css §12 (CARDS)
   by whoever owns system.css. Nothing here is loaded at runtime; system.css is
   still the only stylesheet (SPEC §2, §14).

   Written against the existing tokens only — no new custom properties, no new
   colours, no new families. Every colour resolves through --fg / --fg-muted /
   --fg-faint / --accent / --accent-2, which the ground contexts flip, so all of
   it passes on BOTH grounds without a second definition (SPEC §11).

   Reuses, does not duplicate: .record, .record__count, .record__issues,
   .colophon, .tag, .rule, .asterism, .folio, .meta, .link-quiet.
   ========================================================================== */


/* --- The record, at full size -------------------------------------------
   The contributor page's one memorable move (SPEC §5.2). .record is metadata
   type at 11px; on the contributor page and in the repeat block it is the
   loudest thing on the page, so it steps up to display size and stops
   shouting the issue titles in caps.                                        */
.record--stamp {
  font-size: var(--t-h4);
  letter-spacing: 0.06em;
  color: var(--fg-muted);
  gap: 0.45em 0.6em;
}
.record--stamp .record__count {
  font-variation-settings: "wght" 670;
  font-size: var(--t-h3);
  text-transform: none;
  letter-spacing: -0.005em;
  color: var(--accent-2);
}
.record--stamp .record__issues {
  flex-basis: 100%;
  font-size: var(--t-micro);
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}


/* --- Repeat contributors -------------------------------------------------
   Deliberately NOT a uniform grid (NOT-AI-MADE §1.1). Three-issue contributors
   get a full-width entry with every piece listed; two-issue contributors get a
   compact one. The hierarchy comes from the archive, not from array order — if
   somebody publishes here a fourth time the block gets bigger on its own.     */
.repeats {
  display: grid;
  gap: var(--sp-l);
  grid-template-columns: minmax(0, 1fr);
}
.repeat {
  display: grid;
  gap: var(--sp-2xs);
  align-content: start;
}
.repeat__name {
  --dsp-wght: 570;
  font-size: var(--t-h3);
  line-height: 1.15;
}
.repeat--major {
  padding-top: var(--sp-m);
  border-top: 1px solid var(--hair);
}
.repeat--major .repeat__name { font-size: var(--t-h2); }
.repeat__works {
  list-style: none;
  margin: var(--sp-xs) 0 0;
  padding: 0;
  display: grid;
  gap: var(--sp-2xs);
  font-family: var(--font-body);
  font-size: var(--t-small);
  color: var(--fg-muted);
}
.repeat__works li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2em 0.7em;
}
.repeat__aside {
  max-width: var(--w-wide);
  font-family: var(--font-body);
  font-size: var(--t-small);
  color: var(--fg-muted);
}


/* --- A to Z --------------------------------------------------------------
   ~114 names. A grid of 114 identical cards would be the single clearest
   generated-site tell, so this is a ruled list instead: one line per person,
   name left, publication record right, groups of wildly uneven length left
   uneven because that IS the archive (nine names under A, one under X).      */
.az-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-meta);
  letter-spacing: var(--tr-micro);
}
.az-jump__letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;              /* tap target, SPEC §11 */
  color: var(--accent);
  text-decoration: none;
}
.az-jump__letter:hover { text-decoration: underline; }
/* Letters nobody has published under. Shown, not hidden — the gaps are a fact
   about the archive, and hiding them would make the row look tidier than the
   data is. Not interactive, so no contrast requirement applies. */
.az-jump__letter--empty { color: var(--fg-faint); opacity: 0.4; }

.az-group { margin-top: var(--sp-l); }
.az-group__letter {
  --dsp-wght: 420;
  font-size: var(--t-h2);
  line-height: 1;
  color: var(--accent-2);
}
.az-list {
  list-style: none;
  margin: var(--sp-xs) 0 0;
  padding: 0;
}
.az-entry {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.2em var(--sp-s);
  padding-block: 0.55em;
  border-bottom: 1px solid var(--hair-soft);
}
.az-entry__name {
  font-family: var(--font-display);
  font-variation-settings: "wght" 550;
  font-size: var(--t-h4);
  line-height: 1.25;
  color: var(--fg);
  text-decoration: none;
  /* No text-transform. Two contributors are stored with a lowercase initial and
     that may be deliberate stylisation (OPEN-FACTS §3.2). Never "correct" it. */
}
.az-entry__name:hover { color: var(--accent); text-decoration: underline; }
.az-entry__where {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
  text-align: right;
}


/* --- Works list on a contributor page ----------------------------------- */
.works {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--w-wide);
}
.works__item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2xs) var(--sp-s);
  padding-block: var(--sp-xs);
  border-bottom: 1px solid var(--hair-soft);
}
.works__title {
  font-family: var(--font-display);
  font-variation-settings: "wght" 570;
  font-size: var(--t-h4);
  line-height: 1.2;
  color: var(--fg);
  text-decoration: none;
}
.works__title:hover { color: var(--accent); text-decoration: underline; }
.works__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2xs) var(--sp-s);
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}


/* --- Award nominations --------------------------------------------------
   The prize is stamped on the piece (content/recognition.json) and surfaced
   where a reader looks up a person: an accolade under the name, a board on the
   index, a badge on the nominated work, and a small mark in the A–Z. Every
   placement uses var(--accent) — neon pink on the void, legible pink-ink on
   paper — so it stays on-ground without introducing a second colour. The mark
   is a small diamond, distinct from the round debut dot in §23 New Voices. */

/* Accolade under the record block on a contributor page (void ground). */
.accolades { list-style: none; margin: var(--sp-s) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2xs); }
.accolade {
  display: flex; align-items: baseline; gap: 0.7em; max-width: var(--w-wide);
  font-family: var(--font-body); font-size: var(--t-small); line-height: 1.5; color: var(--fg-muted);
}
.accolade__mark {
  flex: 0 0 auto; width: 7px; height: 7px; border-radius: 1px;
  background-color: var(--accent); transform: translateY(-0.15em) rotate(45deg);
}
.accolade__text strong {
  font-family: var(--font-display); font-variation-settings: "wght" 620;
  font-size: var(--t-meta); letter-spacing: var(--tr-meta); text-transform: uppercase; color: var(--fg);
}

/* The board of nominations on the contributors index (paper ground). */
.awards-board {
  list-style: none; margin: var(--sp-m) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: var(--sp-s);
}
.award-card {
  padding: var(--sp-m);
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  background-color: color-mix(in srgb, var(--accent) 5%, transparent);
}
.award-card__prize {
  font-family: var(--font-display); font-variation-settings: "wght" 620;
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: var(--tr-micro);
  color: var(--accent); margin: 0;
}
.award-card__name { font-size: var(--t-h3); margin: var(--sp-2xs) 0 var(--sp-3xs); }
.award-card__name a { text-decoration: none; }
.award-card__name a:hover { color: var(--accent); }
.award-card__work { font-family: var(--font-body); font-size: var(--t-small); color: var(--fg-muted); margin: 0; }

/* Badge beside a nominated work in the works list. */
.award-badge {
  display: inline-flex; align-items: center; gap: 0.4em;
  font-family: var(--font-display); font-variation-settings: "wght" 620;
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: var(--tr-micro);
  color: var(--accent);
}
.award-badge::before {
  content: ""; width: 6px; height: 6px; border-radius: 1px;
  background-color: var(--accent); transform: rotate(45deg);
}

/* Small diamond after a nominated name in the A–Z list. */
.az-entry__award {
  display: inline-block; width: 6px; height: 6px; margin-inline-start: 0.45em;
  border-radius: 1px; background-color: var(--accent); transform: rotate(45deg);
  vertical-align: middle;
}


/* --- Colophon without a portrait ----------------------------------------
   .colophon__body is a two-track grid that assumes a portrait in track one.
   No contributor portrait can be emitted today (every author-image URL in the
   content model is a wixstatic address the build guard rejects), so the bio
   blocks use .colophon directly with this modifier. When real portraits land
   in assets/img/ the standard .colophon__body comes back and this stays for
   the ones who declined.                                                     */
.colophon--flat {
  max-width: var(--w-wide);
  margin-top: 0;
}
.colophon--flat .colophon__bio {
  font-size: var(--t-base);
  line-height: var(--lh-prose);
  color: var(--fg);
}
.colophon--flat .colophon__bio > * + * { margin-top: 0.8em; }


/* --- Editorial notice ----------------------------------------------------
   Where the archive does not know something — an unresolved name spelling, a
   photograph a contributor declined — the page says so in plain language
   rather than papering over it (NOT-AI-MADE §3.1). Quiet by design: this is a
   footnote on somebody's page, not a warning banner.                         */
.notice {
  max-width: var(--measure);
  font-family: var(--font-body);
  font-size: var(--t-small);
  line-height: 1.55;
  color: var(--fg-muted);
  padding-left: var(--sp-s);
  border-left: 1px solid color-mix(in srgb, var(--accent-2) 55%, transparent);
}
.notice + .notice { margin-top: var(--sp-s); }


/* --- Responsive (merge into §16) ---------------------------------------- */
@media (min-width: 768px) {
  .repeats--minor { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .az-list { column-count: 2; column-gap: var(--sp-xl); }
  .az-entry { break-inside: avoid; }
}
@media (min-width: 1024px) {
  .repeats--minor { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .az-list { column-count: 3; }
  /* The A–Z runs in columns; the two three-issue contributors deliberately do
     not, so the top of the page breaks the grid the rest of it keeps. */
}
@media (max-width: 480px) {
  .az-entry, .works__item { justify-content: flex-start; }
  .az-entry__where { text-align: left; }
  .record--stamp .record__count { font-size: var(--t-h4); }
}


/* ---------------------------------------------------------------------------
   MERGED FROM templates/_css-additions/editors-light.css
   --------------------------------------------------------------------------- */

/* =============================================================================
   EDITOR'S LIGHT — components for templates/editors-light.mjs
   NOT LOADED BY ANY PAGE. Merge target: assets/css/system.css, as a new
   subsection of §12 CARDS (it is the last listing component) or a §12.1.
   Kept out of system.css because other agents are editing that file in
   parallel; every class is prefixed `.el-` so no two additions files collide.

   RULES OBSERVED
   • No colour is named. Everything resolves through the §02 ground-context
     tokens (--fg / --fg-muted / --fg-faint / --accent / --accent-2 / --hair),
     so every component below is correct on --void AND on --paper without a
     single component-level override. That is the fix for regression #3.
   • No new tokens. Type, space, tracking and rules all come from §01.
   • The display face is driven through --dsp-wght (wght is its only axis)
     where an element already inherits the §04 heading rule, and through
     font-variation-settings directly where it does not.
   • Nothing here animates, so §17 reduced-motion needs no additions.
   ========================================================================== */


/* -----------------------------------------------------------------------------
   INDEX — masthead
   Asymmetric: everything hangs off the left margin, nothing is centred.
   -------------------------------------------------------------------------- */

.el-masthead {
  display: grid;
  gap: var(--sp-m);
  max-width: var(--w-wide);
}
.el-masthead .rule { margin-block: var(--sp-3xs); }

/* --- the three real categories, as a filter rail ------------------------- */
.el-filedbar {
  display: grid;
  gap: var(--sp-xs);
  margin-top: var(--sp-s);
}
.el-filedbar__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2xs) var(--sp-s);
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Sits on .tag for its shape. Raised to a 44px tap target (SPEC §11). */
.el-chip {
  min-height: 44px;
  text-decoration: none;
  color: var(--fg-muted);
}
.el-chip:hover {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}
.el-chip__count {
  margin-left: 0.7em;
  font-variation-settings: "wght" 760;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--accent-2);
}


/* -----------------------------------------------------------------------------
   INDEX — the run

   NOT a uniform card grid (NOT-AI-MADE §1.1). One chronological spine, three
   weights. The lead is the newest post; two entries carry a lifted opening line
   because they are the longest letters in the archive, and one of those two is
   the OLDEST post — so the weight demonstrably follows the writing rather than
   the array index. Everything else is a ruled row.

   Each entry's own FIRST LINE does the job a cover photograph would do. All
   eight posts share one cover image (the logo), so there is no per-post art to
   show and none is invented.
   -------------------------------------------------------------------------- */

.el-runhead {
  display: grid;
  gap: var(--sp-2xs);
  margin-bottom: var(--sp-xl);
}

.el-run {
  display: grid;
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
}

.el-run__item {
  padding-block: var(--sp-l);
  border-top: 1px solid var(--hair-soft);
}
.el-run__item:first-child {
  padding-top: 0;
  border-top: 0;
}
.el-run__item--lead { padding-bottom: var(--sp-xl); }
.el-run__item--major { padding-block: var(--sp-xl); }

.el-run__entry { display: grid; gap: var(--sp-xs); }
.el-run__aside { display: grid; gap: var(--sp-2xs); align-content: start; }
.el-run__body  { display: grid; gap: var(--sp-xs); align-content: start; }

.el-run__date {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  line-height: var(--lh-ui);
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--fg-faint);
}

/* Titles are <h3>, so they inherit §04's axis rule and can be re-dialled
   through the four custom properties rather than by restating the font. */
.el-run__title { font-size: var(--t-h4); }
.el-run__item--major .el-run__title { font-size: var(--t-h3); }
.el-run__item--lead  .el-run__title {
  --dsp-wght: 460;
  font-size: var(--t-h2);
  letter-spacing: -0.01em;
}

/* The lifted opening line — the substitute for cover art. Marked by a short
   hand-drawn rule above it, not by quotation marks: it is not a quotation,
   it is where the letter starts. */
.el-run__first {
  max-width: var(--w-wide);
  font-family: var(--font-display);
  font-variation-settings: "wght" 400;
  font-weight: normal;
  font-size: var(--t-h3);
  line-height: 1.22;
  letter-spacing: -0.006em;
  text-wrap: pretty;
  color: var(--fg);
}
.el-run__first::before {
  content: "";
  display: block;
  width: 3.25rem;
  height: 9px;
  margin-bottom: var(--sp-xs);
  background-color: var(--accent-2);
  opacity: 0.8;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 9' preserveAspectRatio='none'%3E%3Cpath d='M2 4.6C64 3.1 118 6.2 182 4.5s118-2.6 184 0.7 132 3.1 232 2.2' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 9' preserveAspectRatio='none'%3E%3Cpath d='M2 4.6C64 3.1 118 6.2 182 4.5s118-2.6 184 0.7 132 3.1 232 2.2' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.el-run__item--lead .el-run__first { font-size: var(--t-pull); }

.el-run__dek { margin-top: var(--sp-3xs); }

.el-run__filed {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--sp-s);
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}
.el-run__filedlabel { align-self: center; }
.el-run__filed a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;          /* SPEC §11 */
  color: var(--fg-muted);
  text-decoration: none;
}
.el-run__filed a:hover { color: var(--accent); text-decoration: underline; }


/* -----------------------------------------------------------------------------
   INDEX — the category index at the foot
   -------------------------------------------------------------------------- */

.el-filed {
  display: grid;
  gap: var(--sp-xl);
  grid-template-columns: minmax(0, 1fr);
  margin-top: var(--sp-l);
}
.el-filed__col h3 { font-size: var(--t-h4); }
.el-filed__count {
  margin-left: 0.7em;
  font-family: var(--font-display);
  font-variation-settings: "wght" 760;
  font-variant-numeric: lining-nums tabular-nums;
  font-size: var(--t-micro);
  letter-spacing: var(--tr-micro);
  color: var(--accent-2);
}
.el-filed__list {
  display: grid;
  gap: 0;
  list-style: none;
  margin-top: var(--sp-s);
  padding: 0;
}
.el-filed__list li {
  padding-block: var(--sp-2xs);
  border-top: 1px solid var(--hair-soft);
}
.el-filed__list li:first-child { border-top: 0; }
.el-filed__list a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--fg);
}
.el-filed__when {
  display: block;
  font-family: var(--font-display);
  font-variation-settings: "wght" 560;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}

.el-close {
  display: grid;
  gap: var(--sp-s);
  margin-top: var(--sp-2xl);
}


/* -----------------------------------------------------------------------------
   POST PAGE
   -------------------------------------------------------------------------- */

.el-posthead {
  display: grid;
  gap: var(--sp-m);
  max-width: var(--w-wide);
  margin-top: var(--sp-m);
  margin-bottom: var(--sp-xl);
}

.el-dateline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--sp-2xs);
}
.el-dateline__sep { opacity: 0.5; }
.el-dateline a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--fg-muted);
  text-decoration: none;
}
.el-dateline a:hover { color: var(--accent); text-decoration: underline; }

/* The running head of the lit page. Below 1024 it is an ordinary folio line at
   the top of the page; at 1024 it stands up and runs the outer margin of the
   reading pane, the way a running head does in print.

   It sits INSIDE the pane's own padding, never outside it: outside, it would
   inherit the pane's paper-ink colours while sitting on the void ground, which
   is regression #3 exactly. .readingpane is already position:relative and its
   inline padding is --sp-2xl at ≥768 and --sp-3xl at ≥1440, so there is room. */
.el-panefolio { margin-bottom: var(--sp-l); }

@media (min-width: 1024px) {
  .el-panefolio {
    position: absolute;
    left: var(--sp-m);
    top: var(--sp-2xl);
    max-height: calc(100% - (2 * var(--sp-2xl)));
    margin: 0;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
  }
}

/* Prev/next and related reading align to the lit page above them, not to the
   wider grid — same width expression as .readingpane. */
.el-below {
  width: min(100%, calc(var(--measure) + (2 * var(--sp-xl))));
  margin-inline: auto;
}

.el-more { display: grid; gap: var(--sp-l); margin-top: var(--sp-xl); }
.el-more__group { display: grid; gap: var(--sp-2xs); }
.el-more__list {
  display: grid;
  gap: 0;
  list-style: none;
  margin: 0;
  padding: 0;
}
.el-more__list li { border-top: 1px solid var(--hair-soft); }
.el-more__list a {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2xs) var(--sp-s);
  min-height: 44px;
  padding-block: var(--sp-2xs);
  color: var(--fg-muted);
  text-decoration: none;
}
.el-more__list a:hover { color: var(--accent); }
.el-more__title {
  font-family: var(--font-display);
  font-variation-settings: "wght" 600;
  font-size: var(--t-h4);
  line-height: 1.25;
  color: var(--fg);
}
.el-more__list a:hover .el-more__title { color: var(--accent); }
.el-more__when {
  font-family: var(--font-display);
  font-variation-settings: "wght" 560;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}


/* -----------------------------------------------------------------------------
   RESPONSIVE — breakpoints 380 / 768 / 1024 / 1440 (SPEC §12)
   -------------------------------------------------------------------------- */

@media (max-width: 479px) {
  /* The lifted opening line steps down so it stays a display line rather than
     becoming a wall of large type on a 380px screen. */
  .el-run__first { font-size: var(--t-h4); }
  .el-run__item--lead .el-run__first { font-size: var(--t-h3); }
}

@media (min-width: 768px) {
  /* The date hangs in its own margin column, the way a dateline does on a
     contents page. The 0.55em top pad is an optical alignment to the first
     baseline of the title, not a grid value (NOT-AI-MADE §1.4). */
  .el-run__entry {
    grid-template-columns: 9rem minmax(0, 1fr);
    column-gap: var(--sp-l);
    align-items: start;
  }
  .el-run__date { padding-top: 0.55em; }

  /* WHERE THE PAGE BREAKS ITS OWN GRID: the lead ignores the date column that
     every other entry in the run obeys, and runs the full width instead. It is
     the only entry allowed to. */
  .el-run__item--lead .el-run__entry { grid-template-columns: minmax(0, 1fr); }
  .el-run__item--lead .el-run__aside {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-s);
  }
  .el-run__item--lead .el-run__date { padding-top: 0; }

  .el-filed { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--sp-xl); }
}

@media (min-width: 1024px) {
  /* Three real categories, three columns — but not three EQUAL columns. The
     widest lane is the one the editors have written in most. */
  .el-filed {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--sp-2xl);
  }
  .el-run__entry { grid-template-columns: 11rem minmax(0, 1fr); }
  .el-below { width: min(100%, calc(var(--measure) + (2 * var(--sp-2xl)))); }
}

@media (min-width: 1440px) {
  .el-panefolio { left: var(--sp-l); }
}


/* =============================================================================
   18  STYLEGUIDE SUPPORT

   Documentation-only. These classes exist so dist/styleguide.html can render
   swatches, scale rulers and spec panels WITHOUT carrying a <style> block of
   its own. They are not part of the site's component vocabulary — no page
   template should use them.
   ========================================================================== */

.sg { display: grid; gap: var(--sp-l); }
.sg-section__head { display: grid; gap: var(--sp-2xs); margin-bottom: var(--sp-l); }
.sg-note {
  max-width: var(--w-wide);
  font-size: var(--t-small);
  line-height: 1.55;
  color: var(--fg-muted);
}
.sg-panel {
  padding: var(--sp-m);
  border: 1px dashed var(--hair);
  border-radius: var(--r-card);
}
.sg-grid {
  display: grid;
  gap: var(--sp-m);
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.sg-swatch {
  display: grid;
  gap: 0;
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  overflow: hidden;
}
.sg-swatch__chip {
  display: flex;
  align-items: flex-end;
  min-height: 88px;
  padding: var(--sp-2xs);
  font-family: var(--font-display);
  font-variation-settings: "wght" 680;
  font-size: var(--t-micro);
  letter-spacing: var(--tr-meta);
}
.sg-swatch__chip--void       { background-color: var(--void);       color: var(--paper); }
.sg-swatch__chip--paper      { background-color: var(--paper);      color: var(--paper-text); }
.sg-swatch__chip--paper-text { background-color: var(--paper-text); color: var(--paper); }
.sg-swatch__chip--pink       { background-color: var(--pink);       color: var(--void); }
.sg-swatch__chip--amber      { background-color: var(--amber);      color: var(--void); }
.sg-swatch__chip--cyan       { background-color: var(--cyan);       color: var(--void); }
.sg-swatch__chip--pink-ink   { background-color: var(--pink-ink);   color: var(--paper); }
.sg-swatch__chip--amber-ink  { background-color: var(--amber-ink);  color: var(--paper); }
.sg-swatch__chip--cyan-ink   { background-color: var(--cyan-ink);   color: var(--paper); }
.sg-swatch__chip--haze       { background-color: var(--haze);       color: var(--void); }
.sg-swatch__chip--haze-dim   { background-color: var(--haze-dim);   color: var(--void); }
.sg-swatch__chip--ink-muted  { background-color: var(--ink-muted);  color: var(--paper); }
.sg-swatch__meta {
  display: grid;
  gap: 0.15em;
  padding: var(--sp-2xs) var(--sp-xs) var(--sp-xs);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-meta);
  color: var(--fg-muted);
}
.sg-swatch__meta strong { color: var(--fg); font-weight: 640; }

.sg-scale { display: grid; gap: var(--sp-s); }
.sg-scale__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.2em;
  padding-bottom: var(--sp-s);
  border-bottom: 1px solid var(--hair-soft);
}
.sg-scale__spec {
  font-family: var(--font-display);
  font-variation-settings: "wght" 560;
  font-size: var(--t-micro);
  letter-spacing: var(--tr-meta);
  color: var(--fg-faint);
}
.sg-size-micro   { font-size: var(--t-micro); }
.sg-size-meta    { font-size: var(--t-meta); }
.sg-size-small   { font-size: var(--t-small); }
.sg-size-base    { font-size: var(--t-base); }
.sg-size-lede    { font-size: var(--t-lede); }
.sg-size-poem    { font-size: var(--t-poem); }
.sg-size-pull    { font-size: var(--t-pull); }
.sg-size-display { font-size: var(--t-display); }

.sg-bar { background-color: var(--accent-2); height: 14px; border-radius: var(--r-hair); }
.sg-bar--3xs { width: var(--sp-3xs); }
.sg-bar--2xs { width: var(--sp-2xs); }
.sg-bar--xs  { width: var(--sp-xs); }
.sg-bar--s   { width: var(--sp-s); }
.sg-bar--m   { width: var(--sp-m); }
.sg-bar--l   { width: var(--sp-l); }
.sg-bar--xl  { width: var(--sp-xl); }
.sg-bar--2xl { width: var(--sp-2xl); }
.sg-bar--3xl { width: var(--sp-3xl); }

.sg-measure-rule {
  height: 6px;
  background-color: var(--accent);
  opacity: 0.5;
  border-radius: var(--r-hair);
}
.sg-measure-rule--measure { width: var(--measure); max-width: 100%; }
.sg-measure-rule--poem    { width: var(--measure-poem); max-width: 100%; }
.sg-measure-rule--wide    { width: var(--w-wide); max-width: 100%; }

/* Axis demonstration: the same word at four SOFT/WONK settings */
.sg-axis {
  font-family: var(--font-display);
  font-size: var(--t-h2);
  line-height: 1.1;
  font-weight: normal;
}
/* The four specimens must differ, or the panel demonstrates nothing. All are set
   at the same font-size so the only variable on show is the optical size — which
   is the whole point: one axis, so weight is the only thing to watch.
   (These were four identical rules left over from the Fraunces SOFT/WONK demo,
   which had four genuinely different axis combinations to show.) */
.sg-axis--flat  { font-variation-settings: "wght" 520; }
.sg-axis--soft  { font-variation-settings: "wght" 470; }
.sg-axis--both  { font-variation-settings: "wght" 420; }
.sg-axis--micro { font-variation-settings: "wght" 680; font-size: var(--t-h4); }

.sg-toc {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2xs) var(--sp-s);
  list-style: none;
  margin: 0;
  padding: 0;
}
.sg-toc a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-muted);
  text-decoration: none;
}
.sg-toc a:hover { color: var(--accent); }

.sg-warn {
  padding: var(--sp-s) var(--sp-m);
  border-left: 4px solid var(--accent);
  background-color: color-mix(in srgb, var(--accent) 8%, transparent);
  font-size: var(--t-small);
  line-height: 1.55;
}


/* =============================================================================
   19  PRINT
   A magazine should print like one.
   ========================================================================== */

@media print {
  .nav, .footer, .skip-link, .newsletter, .pieceflow { display: none !important; }
  body, .ground-void, .ground-paper, .readingpane {
    background: #fff !important;
    color: #000 !important;
    box-shadow: none !important;
  }
  .ground-void::before, .ground-void::after,
  .ground-paper::before, .ground-paper::after { display: none !important; }
  .prose, .poem { max-width: none; }
  .sign__text { text-shadow: none; color: #000; }
  a { text-decoration: underline; }
  .prose a::after { content: " (" attr(href) ")"; font-size: 0.8em; }
}


/* =============================================================================
   21  THE PIECE PAGE

   Jonathan's brief: keep the content true to its original formatting, take the
   two-column concept and the big intro from the original design, and make each
   piece "feel like its own world".

   The body renderers are NOT touched by anything in this section. Lineation,
   stanza breaks, preserved whitespace, hanging indents and the per-poem measure
   are exactly what they were; this is the room built around them.

   WHAT MAKES A PIECE ITS OWN WORLD. One custom property, --piece-h, set inline
   on .piecehead from a hue sampled out of that piece's own artwork
   (tools/extract-accents.ps1). It reaches exactly one thing: the wash behind the
   title. It never touches type. A hue lifted from a photograph cannot be assumed
   to pass contrast against anything, so lightness and alpha are pinned here and
   the words stay on the §02 tokens on all 124 pages.
   ========================================================================== */

/* --- the spine ----------------------------------------------------------- */
.spine {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5em;
  margin: 0 0 var(--sp-l);
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  line-height: var(--lh-ui);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
}
.spine__kind { color: var(--accent); }
.spine__sep  { color: var(--fg-faint); opacity: 0.6; }

/* --- the big intro ------------------------------------------------------- */
.piecehead {
  overflow: hidden;   /* the wash is a room, not a spill onto the next section */
  padding-bottom: var(--sp-2xl);
}

/* The accent, and the only place it is spent. Two radials rather than one so the
   light has a direction — a single centred glow is the wallpaper move
   NOT-AI-MADE §1.5 names. Hue comes from the artwork; the lightness and the
   alphas are fixed here, which is what keeps 124 hues inside one design instead
   of turning into 124 designs. */
.piecehead__wash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* LIGHTNESS 30% AND THESE ALPHAS ARE A CONTRAST FLOOR, not taste. The accent
     hue is whatever an artwork happened to contain, so the safe value has to
     hold for EVERY hue on the wheel, not for the ones currently in the archive —
     the next issue can arrive with any of them and nothing would re-check.

     Solved rather than guessed: composite both washes at peak over --void at 3-
     degree steps, take the worst small-text ratio across all 120 samples. The
     first draft (52% lightness, 0.20/0.13) bottomed out at 2.75:1 on the spine
     at hue 60, which is a fail, and 8.62:1 on the title, which is why looking at
     one page would not have caught it. These values bottom out at 4.6:1 with
     yellow, above the 4.5 floor for small text, and the deeper lightness reads
     as coloured light in a dark room rather than as a pastel tint — closer to
     what the mark does anyway. */
  background:
    radial-gradient(78% 62% at 14% 0%,
      hsl(var(--piece-h, 336) 62% 30% / 0.12), transparent 72%),
    radial-gradient(64% 58% at 92% 22%,
      hsl(var(--piece-h2, 28) 58% 30% / 0.07), transparent 70%);
}

/* The house smoke is suppressed here because the wash IS this section's
   atmosphere. Running both stacks two light sources with different intentions,
   and the smoke's `screen` blend lifts the ground further — which the contrast
   solve above would not have accounted for, since it composites against the
   section colour rather than against the smoke on top of it. One room, one
   light. */
.piecehead::after { content: none; }

.piecehead__inner { padding-block: var(--sp-l) 0; }

.piecehead__title {
  margin: var(--sp-s) 0 0;
  max-width: 18ch;              /* a title is a headline, not a paragraph */
  font-family: var(--font-display);
  font-variation-settings: "wght" 460;
  font-size: var(--t-display);
  line-height: 0.98;
  letter-spacing: -0.015em;
  color: var(--fg);
  text-wrap: balance;
}

/* Long titles step down and widen. Both together: dropping the size alone still
   wraps at 18ch, and widening alone keeps the type too big to sit under. The
   class is chosen in the template from the title's own character count. */
.piecehead__title--mid  { font-size: var(--t-h1); max-width: 24ch; }
.piecehead__title--long { font-size: var(--t-h2); max-width: 30ch; line-height: 1.05; }

/* The opening line, prose only. Set in the BODY face because it is the author's
   sentence rather than the magazine's furniture, and italic so it never reads as
   the first line of the story arriving twice. */
.piecehead__opening {
  margin: var(--sp-m) 0 0;
  max-width: 44ch;
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 450;
  font-size: var(--t-lede);
  line-height: 1.45;
  color: var(--fg-muted);
}

.piecehead__byline {
  margin: var(--sp-l) 0 0;
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-lede);
  color: var(--fg-muted);
}
.piecehead__byline a { color: var(--fg); text-decoration: none; }
.piecehead__byline a:hover { text-decoration: underline; }

/* --- the interview exchange ---------------------------------------------
   The question is the magazine's and the answer is the writer's, and the page
   has to make that legible without a label on every one. So the question is set
   in the DISPLAY face — the magazine's voice, the same face as its headings —
   and the answer in the BODY face at reading size, which is the face every other
   published answer on this site appears in. Two voices, two faces, no "Q:" and
   "A:" needed.

   The questions are real <h2>s, so an interview has an outline a screen reader
   can jump through question by question. */
.qa + .qa { margin-top: var(--sp-xl); }

.qa__q {
  margin: 0 0 var(--sp-s);
  max-width: 34ch;
  font-family: var(--font-display);
  font-variation-settings: "wght" 560;
  font-size: var(--t-h4);
  line-height: var(--lh-tight);
  color: var(--accent);
}

.qa__a {
  font-family: var(--font-body);
  font-size: var(--t-base);
  line-height: var(--lh-prose);
  max-width: var(--measure);
  color: var(--fg);
}
.qa__a > * + * { margin-top: 1.15em; }

/* A writer quoting their own published work inside an answer. Set apart, because
   read as an ordinary paragraph it sounds like they are still talking. */
.qa__quote {
  margin: 1.3em 0;
  padding-left: 1.15em;
  border-left: 2px solid var(--accent-2);
  font-style: italic;
  color: var(--fg-muted);
}
.qa__quote p { margin: 0; }

/* --- the caption on a recording -----------------------------------------
   Who is speaking, above the control rather than under it. A player with no
   caption asks the reader to press play to find out what it is, and a reader on
   a screen reader has no way to find out at all — which was the objection that
   kept these unpublished. The same string is the control's aria-label, so the
   two can never drift apart. */
.listen__caption {
  margin: var(--sp-2xs) 0 var(--sp-s);
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg-muted);
}

/* --- new and emerging writers -------------------------------------------
   A sentence, not a badge. It sits under the byline because it is a fact about
   the byline, and it is marked with a rule and a dot rather than a coloured
   chip: a chip reads as a category the magazine sorted someone into, and a line
   of type reads as the magazine saying something. --accent is spent here
   because this is the one thing on the page the eye should catch after the
   title, and on most pages the element is absent entirely. */
.spotlight {
  display: flex;
  align-items: baseline;
  gap: 0.7em;
  margin: var(--sp-m) 0 0;
  max-width: 46ch;
  font-family: var(--font-body);
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg-muted);
}

/* The whole note is a link into /new-voices/ now. It carries the flex layout so
   the mark and text still sit on one baseline row, keeps the paragraph colour
   (not link colour) so it reads as a note rather than a button, and clears the
   44px tap target for the mobile audit. The accent shows on hover/focus so it
   is discoverable as a link without shouting. */
.spotlight__link {
  display: flex;
  align-items: baseline;
  gap: 0.7em;
  min-height: 44px;
  text-decoration: none;
  color: inherit;
}
.spotlight__link:hover .spotlight__text,
.spotlight__link:focus-visible .spotlight__text { color: var(--fg); }
.spotlight__link:hover .spotlight__text strong,
.spotlight__link:focus-visible .spotlight__text strong { color: var(--accent); }
.spotlight__mark {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--accent);
  transform: translateY(-0.15em);
}
/* An award note reuses the spotlight row but marks itself with a small diamond
   rather than the debut dot, so a nomination and a first appearance stay
   distinguishable when a single piece carries both. */
.spotlight--award .spotlight__mark {
  border-radius: 1px;
  transform: translateY(-0.15em) rotate(45deg);
}
.spotlight__text strong {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-meta);
  letter-spacing: var(--tr-meta);
  text-transform: uppercase;
  color: var(--fg);
}

/* --- the genre pill ------------------------------------------------------ */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  margin: 0;
  padding: 0.4em 0.9em;
  border: 1px solid var(--hair);
  border-radius: 999px;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-muted);
}
.pill__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--accent);
}
.pill__kind { color: var(--fg); }
.pill__sep { color: var(--fg-faint); }

/* --- the two columns ----------------------------------------------------- */
.piecegrid {
  display: grid;
  gap: var(--sp-xl);
  grid-template-columns: minmax(0, 1fr);
  width: min(100% - (2 * var(--sp-gutter)), 76rem);
  margin-inline: auto;
}

/* One column until there is genuinely room for two. Below this the sidebar is
   not a sidebar, it is cards stacked under the work — which is the right order:
   the piece first, then where it sits and who wrote it. */
@media (min-width: 1024px) {
  .piecegrid {
    grid-template-columns: minmax(0, 1fr) 18.5rem;
    align-items: start;
  }
  .piece-aside {
    position: sticky;
    top: calc(var(--nav-h, 72px) + var(--sp-m));
  }
}

/* The pane no longer sets its own page width — it fills its column. What it DOES
   keep is the per-poem measure, so a 32-character poem is still 32 characters
   wide, now centred in its column instead of on the page. */
.piecegrid > .readingpane {
  width: auto;
  margin-inline: auto;
}

/* --- the sidebar cards --------------------------------------------------- */
.piece-aside {
  display: grid;
  gap: var(--sp-m);
  align-content: start;
}

.asidecard--cover {
  position: relative;
  margin: 0;
  overflow: hidden;
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  background-color: var(--smoke-deep);
}
.asidecard__art {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}
/* The 3:4 crop above is right for portrait-ish art but butchers a landscape or a
   near-square one — a 16:9 photograph loses more than half its width to it. Those
   few carry `fit: "contain"` on their record and show at their own shape instead,
   uncropped. The template pins the box to the image's own ratio inline (from the
   recorded dimensions) so the space is still reserved before it loads; the `auto`
   here is only a fallback for the case where no dimensions are on record. The
   overlay still sits on the image's bottom edge either way. */
.asidecard__art--native {
  aspect-ratio: auto;
  object-fit: contain;
}
.asidecard__overlay {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  display: grid;
  gap: 0.15em;
  padding: var(--sp-m) var(--sp-s) var(--sp-s);
  /* Its own scrim. The cover is somebody's photograph and its bottom edge could
     be any value at all, so the label brings its own ground rather than trusting
     the picture to be dark there. Same reasoning as the homepage hero, where an
     unusually light cover measured 1.11:1 before the floor went in. */
  background: linear-gradient(to top,
    color-mix(in srgb, var(--void) 88%, transparent) 0,
    color-mix(in srgb, var(--void) 62%, transparent) 55%,
    transparent 100%);
  font-family: var(--font-display);
  color: var(--paper);
}
.asidecard__issue {
  font-variation-settings: "wght" 560;
  font-size: var(--t-small);
  line-height: 1.2;
}
.asidecard__year {
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  letter-spacing: var(--tr-micro);
  color: color-mix(in srgb, var(--paper) 78%, transparent);
}

/* The colophon was written as a footer inside the reading pane. In the sidebar
   it is a card, so it gets the frame it never needed down there, and its
   portrait stops being a small inset beside text and becomes the top of the
   card — the shape the original design's author card had. */
.piece-aside .colophon {
  margin: 0;
  padding: var(--sp-m);
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  background-color: color-mix(in srgb, var(--smoke) 55%, transparent);
}
.piece-aside .colophon__body { display: grid; gap: var(--sp-2xs); }
/* The placeholder plate. Same box a portrait occupies, so a card with one does
   not sit differently from a card without — set type on the smoke ground, which
   reads as a designed absence rather than a failed image. */
.colophon__plate {
  display: grid;
  place-items: center;
  width: 84px;
  height: 108px;
  margin: 0;
  border: 1px solid var(--hair);
  border-radius: var(--r-soft);
  background-color: color-mix(in srgb, var(--smoke) 70%, transparent);
  font-family: var(--font-display);
  font-variation-settings: "wght" 420;
  font-size: var(--t-h3);
  line-height: 1;
  letter-spacing: 0.04em;
  color: var(--fg-faint);
}

/* Explicit width, not `width: 100%`. The colophon body is a grid whose column is
   sized by its content, so a percentage resolved against the two letters inside
   the plate and drew it at 36x46 instead of 96x123. A fixed box is also what the
   portrait beside it uses, which is the point — the two have to occupy the same
   slot or a card with a photograph sits differently from one without. */
.piece-aside .colophon__plate {
  width: 96px;
  height: 123px;
}

.piece-aside .colophon__portrait {
  width: 100%;
  height: auto;
  max-width: 96px;
  aspect-ratio: 7 / 9;
  object-fit: cover;
  border-radius: var(--r-soft);
}

.asidecard__return {
  display: grid;
  gap: 0.2em;
  margin: 0;
  padding-top: var(--sp-s);
  border-top: 1px solid var(--hair);
}

/* --- also in this issue -------------------------------------------------- */
.morefrom { margin-top: var(--sp-2xl); }

.moregrid {
  display: grid;
  gap: var(--sp-s);
  grid-template-columns: minmax(0, 1fr);
  margin-top: var(--sp-m);
}
@media (min-width: 600px)  { .moregrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .moregrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.morecard {
  display: grid;
  gap: 0.25em;
  align-content: start;
  min-height: 44px;
  padding: var(--sp-s) 0;
  border-top: 1px solid var(--hair);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease);
}
.morecard:hover { border-top-color: var(--accent); }
.morecard__num {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  letter-spacing: var(--tr-micro);
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--fg-faint);
}
.morecard__title {
  font-family: var(--font-display);
  font-variation-settings: "wght" 500;
  font-size: var(--t-h4);
  line-height: var(--lh-tight);
  color: var(--fg);
}
.morecard:hover .morecard__title { color: var(--accent); }
.morecard__by {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-small);
  color: var(--fg-muted);
}
.morecard__kind {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--accent-2);
}

@media (prefers-reduced-motion: reduce) {
  .morecard { transition: none; }
}

/* =============================================================================
   22  IN PRINT — the anthology, shown as a book

   Used by the homepage "In print" section and the /support/ book section. A book
   is the one thing this magazine sells, so it is shown as an object rather than
   described: the real cover on the left, the details and the buy control on the
   right. Below 720px it stacks, cover first.
   ========================================================================== */
.inprint {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-l);
  align-items: center;
}
@media (min-width: 720px) {
  .inprint {
    grid-template-columns: minmax(150px, 190px) minmax(0, 1fr);
    gap: var(--sp-2xl);
  }
}

/* The cover, lifted off the page like a book on a shelf. object-fit keeps the
   art undistorted; the ring and shadow give it edge and weight. */
.inprint__cover {
  display: block;
  width: 100%;
  max-width: 190px;
  justify-self: center;
  border-radius: var(--r-soft);
  overflow: hidden;
  box-shadow:
    0 1px 0 color-mix(in srgb, var(--void) 12%, transparent),
    0 18px 40px -12px color-mix(in srgb, var(--void) 45%, transparent);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.inprint__cover img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 800 / 1199;
  object-fit: cover;
}
.inprint__cover:hover,
.inprint__cover:focus-visible {
  transform: translateY(-3px);
  box-shadow:
    0 1px 0 color-mix(in srgb, var(--void) 12%, transparent),
    0 26px 56px -12px color-mix(in srgb, var(--void) 55%, transparent);
}

.inprint__body { min-width: 0; }
.inprint__title {
  font-size: var(--t-h2);
  line-height: var(--lh-tight);
  max-width: 20ch;
}
.inprint__title a { color: inherit; text-decoration: none; }
.inprint__title a:hover { color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .inprint__cover { transition: none; }
  .inprint__cover:hover, .inprint__cover:focus-visible { transform: none; }
}


/* =============================================================================
   23  SEARCH

   Two surfaces, one look. The /search/ PAGE is a big field over grouped results;
   the COMMAND PALETTE is that same field and those same results in an overlay,
   summoned from the header control, the "/" key, or Cmd/Ctrl-K. Both are drawn
   from the smoke ramp so the field and the rows read as objects laid on the void,
   in the site's own editorial voice — serif titles, italic subtitles, small-caps
   kind labels, a neon wash on the active row. All colours are §02 tokens.
   search.js drives both; the markup for a result row is shared between them.
   ========================================================================== */

/* --- the header trigger -------------------------------------------------- */
.nav__end { display: flex; align-items: center; gap: var(--sp-2xs); }

.search-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2xs);
  flex: 0 0 auto;
  /* 44×44 — the site's tap-target floor (SPEC §11). It was 40 and every other
     control beside it (the nav toggle) is 44. The ≥1024 rule below switches to
     width:auto with padding; height stays 44 there. */
  width: 44px;
  height: 44px;
  color: var(--fg-muted);
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--r-full);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
.search-trigger:hover, .search-trigger:focus-visible {
  color: var(--fg);
  background-color: color-mix(in srgb, var(--accent) 12%, transparent);
}
.search-trigger[aria-current="page"] { color: var(--accent); }
.search-trigger__icon { flex: 0 0 auto; width: 18px; height: 18px; }
.search-trigger__label, .search-trigger__key { display: none; }

/* The tight horizontal band. Below 920 the bar collapses to a hamburger and the
   icon sits beside it with room to spare; at 920 the seven-link bar is already
   full to the pixel (measured: adding the 40px control overflows by 47px and puts
   a horizontal scrollbar on the page). So the control is hidden in exactly this
   band — search stays reachable by "/" and Cmd/Ctrl-K — and returns as a bordered
   pill at 1024 where the roomier gap comes back, gaining its label at 1200. */
@media (min-width: 920px) and (max-width: 1023.98px) {
  .search-trigger { display: none; }
}

@media (min-width: 1024px) {
  .search-trigger {
    width: auto;
    padding-inline: 0.75rem;
    border-color: var(--hair);
    background-color: color-mix(in srgb, var(--paper) 4%, transparent);
  }
  .search-trigger:hover, .search-trigger:focus-visible {
    border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  }
}
@media (min-width: 1200px) {
  .search-trigger__label {
    display: inline-block;
    font-family: var(--font-display);
    font-size: var(--t-meta);
    letter-spacing: var(--tr-meta);
  }
  .search-trigger__key {
    display: inline-block;
    font-family: var(--font-body);
    font-size: var(--t-micro);
    line-height: 1;
    padding: 3px 6px 2px;
    color: var(--fg-faint);
    border: 1px solid var(--hair);
    border-radius: var(--r-card);
  }
}

/* --- a small key cap (used in prose and the palette footer) -------------- */
.key, .cmdk__foot kbd {
  display: inline-block;
  font-family: var(--font-body);
  font-size: 0.82em;
  line-height: 1;
  padding: 0.25em 0.45em;
  color: var(--fg-faint);
  background-color: color-mix(in srgb, var(--paper) 5%, transparent);
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  vertical-align: baseline;
}

/* --- the /search/ page --------------------------------------------------- */
.searchpage { padding-block: var(--sp-2xl) var(--sp-xl); }
.searchpage__title { max-width: 18ch; }

.searchbox {
  position: relative;
  display: flex;
  align-items: center;
  max-width: 44rem;
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  background-color: color-mix(in srgb, var(--paper) 5%, transparent);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.searchbox:focus-within {
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.searchbox__icon {
  flex: 0 0 auto;
  display: inline-flex;
  padding-inline: var(--sp-s) var(--sp-2xs);
  color: var(--fg-faint);
}
.searchbox:focus-within .searchbox__icon { color: var(--accent); }
.searchbox__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 64px;
  padding: 0 var(--sp-2xs);
  font-family: var(--font-body);
  font-size: var(--t-lede);
  color: var(--fg);
  background: transparent;
  border: 0;
  outline: none;
}
.searchbox__input::placeholder { color: var(--fg-faint); }
.searchbox__input::-webkit-search-cancel-button { display: none; }
.searchbox__clear {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  margin-inline-end: var(--sp-2xs);
  color: var(--fg-faint);
  font-size: var(--t-small);
  background: transparent;
  border: 0;
  border-radius: var(--r-full);
  cursor: pointer;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.searchbox__clear:hover, .searchbox__clear:focus-visible {
  color: var(--fg);
  background-color: color-mix(in srgb, var(--paper) 8%, transparent);
}

.search__status { color: var(--fg-muted); min-height: 1.4em; }

.search__results { list-style: none; margin: var(--sp-m) 0 0; padding: 0; max-width: 44rem; }

.search__browse { margin-top: var(--sp-l); }
.eyebrow--muted { color: var(--fg-faint); }
.search__browse-list {
  list-style: none;
  margin: var(--sp-s) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-s) var(--sp-l);
  font-family: var(--font-body);
  font-size: var(--t-base);
}

/* --- the shared result row (page + palette) ------------------------------ */
.search__group { list-style: none; margin: 0 0 var(--sp-s); }
.search__grouphead {
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
  margin: var(--sp-s) 0 var(--sp-2xs);
}
.search__list { list-style: none; margin: 0; padding: 0; }
.search__link {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "title kind" "sub kind";
  align-items: baseline;
  gap: 0.1em var(--sp-m);
  padding: var(--sp-xs) var(--sp-2xs);
  border-radius: var(--r-card);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease);
}
.search__link[data-active="true"], .search__link:hover {
  background-color: color-mix(in srgb, var(--accent) 12%, transparent);
}
.search__link[data-active="true"]::before {
  content: "";
  position: absolute;
  inset-block: 18%;
  inset-inline-start: 0;
  width: 2px;
  border-radius: 2px;
  background-color: var(--accent);
}
.search__title {
  grid-area: title;
  font-family: var(--font-display);
  font-variation-settings: "wght" 520;
  font-size: var(--t-h4);
  line-height: var(--lh-tight);
  color: var(--fg);
}
.search__link[data-active="true"] .search__title,
.search__link:hover .search__title { color: var(--accent); }
.search__sub {
  grid-area: sub;
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-small);
  color: var(--fg-muted);
  margin-top: 0.15em;
}
.search__kind {
  grid-area: kind;
  align-self: center;
  font-family: var(--font-display);
  font-variation-settings: "wght" 620;
  font-size: var(--t-micro);
  text-transform: uppercase;
  letter-spacing: var(--tr-micro);
  color: var(--fg-faint);
  white-space: nowrap;
}
.search__hit {
  background-color: color-mix(in srgb, var(--accent) 24%, transparent);
  color: inherit;
  border-radius: 2px;
  padding: 0 0.05em;
}

/* --- the command palette overlay ----------------------------------------- */
html.cmdk-open { overflow: hidden; }

.cmdk {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: max(8vh, var(--sp-xl)) var(--sp-s) var(--sp-s);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.cmdk[hidden] { display: none; }
.cmdk.is-open { opacity: 1; }

.cmdk__backdrop {
  position: absolute;
  inset: 0;
  background-color: color-mix(in srgb, var(--void) 72%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.cmdk__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(94vw, 42rem);
  max-height: min(72vh, 40rem);
  background-color: var(--smoke-deep);
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  box-shadow:
    0 42px 120px -24px color-mix(in srgb, var(--void) 78%, transparent),
    0 0 60px -18px color-mix(in srgb, var(--pink) 40%, transparent);
  transform: translateY(-10px) scale(0.985);
  transition: transform var(--dur) var(--ease-out);
  overflow: hidden;
}
.cmdk.is-open .cmdk__panel { transform: translateY(0) scale(1); }

.cmdk__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2xs);
  padding: var(--sp-2xs) var(--sp-m);
  border-bottom: 1px solid var(--hair-soft);
}
.cmdk__icon { flex: 0 0 auto; display: inline-flex; color: var(--fg-faint); }
.cmdk__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 56px;
  padding: 0 var(--sp-2xs);
  font-family: var(--font-body);
  font-size: var(--t-lede);
  color: var(--fg);
  background: transparent;
  border: 0;
  outline: none;
}
.cmdk__input::placeholder { color: var(--fg-faint); }
.cmdk__input::-webkit-search-cancel-button { display: none; }
.cmdk__esc {
  flex: 0 0 auto;
  /* On mobile the footer keyboard hints are hidden, so this is the one visible
     close control — it gets the 44×44 tap floor (SPEC §11). The label stays small;
     the box, not the type, grows. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-meta);
  text-transform: uppercase;
  color: var(--fg-faint);
  padding: 0.35em 0.6em;
  background: transparent;
  border: 1px solid var(--hair);
  border-radius: var(--r-card);
  cursor: pointer;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.cmdk__esc:hover, .cmdk__esc:focus-visible { color: var(--fg); border-color: var(--fg-faint); }

.cmdk__list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
  margin: 0;
  padding: var(--sp-2xs) var(--sp-2xs) var(--sp-s);
}
/* Give the palette rows and headings their inset; the page keeps them flush. */
.cmdk__list .search__grouphead { padding-inline: var(--sp-s); }

.cmdk__empty { padding: var(--sp-m) var(--sp-m) var(--sp-l); }
.cmdk__hint { color: var(--fg-muted); font-family: var(--font-body); font-size: var(--t-base); }
.cmdk__shortcuts {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-s) var(--sp-m);
  margin: var(--sp-m) 0 0;
  padding: 0;
  font-family: var(--font-body);
}
.cmdk__foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-s) var(--sp-m);
  padding: var(--sp-2xs) var(--sp-m);
  border-top: 1px solid var(--hair-soft);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-meta);
  color: var(--fg-faint);
}
.cmdk__foot span { display: inline-flex; align-items: center; gap: var(--sp-3xs); }

@media (max-width: 560px) {
  .cmdk { padding-top: var(--sp-s); }
  .cmdk__panel { width: 100%; max-height: 82vh; }
  .cmdk__foot { display: none; }   /* keyboard hints are moot on touch */
}

@media (prefers-reduced-motion: reduce) {
  .cmdk, .cmdk__panel { transition: none; }
  .cmdk__panel { transform: none; }
  .search__link { transition: none; }
}



/* Cover credit — a legible editorial credit, NOT a tracked label. The default
   .figcaption letter-spaces every glyph, which is right for "FIG. 1" but wrong
   for a person's name: "Jessica Edmond" came out spaced like a sign. This reads
   the name naturally in the body italic, small and muted. */
.cover-credit {
  margin-top: var(--sp-s);
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-small);
  letter-spacing: normal;
  line-height: var(--lh-ui);
  color: var(--fg-muted);
}
.cover-credit a { font-style: normal; }
