/* ============================================================================
   gaffer.css — THE GAFFER: the broadsheet

   PRESENTATION ONLY. This file styles a page; it decides nothing. Every string,
   every ordering and every selection arrives from /api/gaffer already made.

   THE IDEA
   The Gaffer is not a dashboard with a serif font bolted on. It is a printed
   page: a nameplate, a dateline, a leader, one lead story, an editorial rack
   set in real columns, a spike of what was killed, and a colophon of what the
   paper was reading from. Almost nothing here is a box: structure is carried by
   RULES (hairlines) and by TYPE, because that is how a page carries structure
   and because a drop-shadowed card in a column of newsprint reads as an error.

   INHERITED, NOT REINVENTED — every value below comes from style.css:
     --font-display (Cormorant Garamond)  every headline, every pull-quote
     --font-mono    (JetBrains Mono)      every kicker, dateline, footnote
     --color-*      the editorial palette green is still the ONLY accent
     --space-*      the 4px scale         no ad-hoc pixel margins
     .briefing-item-implication           the 2px primary "this is the
                                          reasoning" rail, reused verbatim
     .ce-conf / .ce-degraded              the honesty chips
   No new font, no new dependency, no @import: everything renders with what
   index.html already loads.

   PAGE STRUCTURE (as emitted by gaffer_tab.js — Builder A, this gate)
     header.gf-masthead            nameplate · rule · dateline · standfirst
     section.gf-leader             brief.lead — the paper's own voice
     div.gf-broadsheet
       article.gf-lead-story       cards[0], always
       div.gf-rack                 cards[1..] in 3 / 2 / 1 columns
       aside.gf-spike              suppressed[] — the Editor's Spike
     footer.gf-canons              _meta.canons — the colophon

   HOUSE CONVENTION: night-mode and narrow-viewport blocks live at the BOTTOM.
   The `.gf-` prefix is reserved for this tab; the only unprefixed selectors are
   scoped under `#tab-gaffer`, so none of them can move a non-Gaffer surface.
   ========================================================================= */


/* ============================================================================
   ===  THE SHEET  ============================================================
   ========================================================================= */

/* Local tokens are scoped to the panel. Nothing declared here exists outside
   the Gaffer, so no other surface can inherit a single value from this file. */
#tab-gaffer {
  --gf-rule:     var(--color-border);     /* section and masthead rules       */
  --gf-hairline: var(--color-divider);    /* between rack items, in gutters   */
  --gf-gutter:   var(--space-8);          /* the column gutter                */
  --gf-measure:  68ch;                    /* longest comfortable line         */

  max-width: 1360px;
  margin-inline: auto;
}

/* Generous margins (§6) — but they may not cost a column. At a 1200px viewport
   the panel still has ~930px inside the 220px sidebar, and at 1500px ~1130px;
   48px of gutter each side leaves three ~340px columns, which is a real
   newspaper measure. Below 1200 every pixel goes to the text. */
@media (min-width: 1200px) {
  #tab-gaffer { padding-inline: var(--space-6); }
}

/* Section labels ("The Editor's Spike", "READING FROM") are style.css's
   `.briefing-section-label` and are deliberately NOT restyled here — lead
   ruling this gate. They already print as tracked mono small-caps, which is
   exactly the furniture this page wants, and forking them in gaffer.css would
   give the app two definitions of the same label free to drift apart.

   `.gf-broadsheet` is the wrapper around lead story + rack + spike. It carries
   no box of its own: it exists so the three body regions share one flow. */
.gf-broadsheet { display: block; }

/* GATE — MAGAZINE PASS, revised (2026-09-02, real bug found in production):
   an earlier version of this put lead+rack side by side in ONE grid row
   (1.7fr/1fr). CSS Grid sizes a shared row to its TALLEST cell — real data
   is one lead card next to MANY full rack cards (each a headline+body+
   pullquote+dodont, not a short blurb), so the rack routinely runs 3-4x
   taller than the lead. The row still had to be that tall, leaving the
   lead's own column full of empty page background below its short
   content — on this page's solid near-black ground, with nothing else
   nearby to break it up, that read as a broken/blank void, not intentional
   whitespace. Alignment properties (align-items:start etc.) do NOT fix
   this — they change which box contains the dead space, not whether it's
   there, since neither column has a background to reveal its true bounds.

   Fixed by NOT sharing a row at all: lead runs full width on its own,
   rack follows as an independent self-wrapping card grid — closer to the
   New Yorker "Today's Mix" reference (one row of comparably-sized cards)
   than a hero+rail split this content shape can't support. Scoped to the
   .gf-front-grid modifier, not bare .gf-broadsheet — Price Watch also
   stamps .gf-broadsheet on an unrelated section below. */
.gf-broadsheet.gf-front-grid {
  display: block;
}
.gf-front-grid > .gf-lead-story,
.gf-front-grid > .gf-rack,
.gf-front-grid > .gf-spike,
.gf-front-grid > .gf-empty {
  margin-bottom: var(--space-8);
}
.gf-front-grid > *:last-child { margin-bottom: 0; }


/* ============================================================================
   ===  MASTHEAD  =============================================================
   Nameplate, double hairline, dateline, standfirst. CONTRACT §4. The heavy top
   rule is the one piece of colour above the fold and it is keyed to POSTURE —
   you know your stance before you have read a word.
   ========================================================================= */

.gf-masthead {
  border-top: 6px solid var(--color-text);
  border-bottom: 1px solid var(--gf-rule);
  padding: var(--space-6) 0 var(--space-5);
  margin-bottom: var(--space-8);
}

.gf-nameplate {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  text-align: center;
}

.gf-nameplate-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.6rem, 1.4rem + 4.4vw, 5rem);
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: var(--color-text);
}

.gf-nameplate-sub {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.34em;       /* wide tracking = the strapline under a plate */
  text-transform: uppercase;
  color: var(--color-text-faint);
  padding-left: 0.34em;         /* optical: cancel the trailing letter-space   */
}

/* The plate's closing rule. `3px double` is one declaration that paints two
   hairlines with a gap — the masthead double-rule with no extra markup. */
.gf-masthead-rule {
  height: 0;
  border-top: 3px double var(--color-text);
  margin: var(--space-5) 0 var(--space-3);
}

/* Posture-keyed nameplate rule. `unknown` is NOT neutral: it is the state
   where the posture fetch failed and the paper does not know your stance (§5).
   It prints in the faint ink rather than in full-strength black, so an edition
   that cannot state a stance never looks as certain as one that can. */
.gf-masthead--protect { border-top-color: var(--color-success); }
.gf-masthead--climb   { border-top-color: var(--color-warning); }
.gf-masthead--neutral { border-top-color: var(--color-text); }
.gf-masthead--unknown { border-top-color: var(--color-text-faint); }


/* ============================================================================
   ===  DATELINE  =============================================================
   `PROTECT · overall rank 8,000 · 4 GWs remain`. Mono, tracked, uppercase,
   ruled underneath — the strip that dates the edition. §4.
   ========================================================================= */

.gf-dateline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--gf-rule);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.gf-dateline-qual,
.gf-dateline-bits {
  font-family: var(--font-mono);
  font-size: inherit;
  letter-spacing: inherit;
  color: var(--color-text-muted);
}

/* The mid-dot between segments is furniture, not content — it is aria-hidden
   in the markup and it prints faint here for the same reason. */
.gf-kicker-sep { color: var(--color-text-faint); opacity: 0.7; }

/* The "pre-season default" flag rides under the dateline as its own line so
   the chip is not mistaken for another dateline segment. */
.gf-masthead-flag { margin: 0 0 var(--space-4); }

/* ---- posture chip ------------------------------------------------------- */

.gf-posture {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  padding: 1px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);   /* squarer than a pill — this is print */
}

.gf-posture--sm { font-size: 0.6rem; letter-spacing: 0.08em; padding: 0 var(--space-1); }

.gf-posture-protect { color: var(--color-success); border-color: var(--color-success);
                      background: rgba(0, 255, 135, 0.10); }
.gf-posture-climb   { color: var(--color-warning); border-color: var(--color-warning);
                      background: rgba(184, 111, 28, 0.10); }
.gf-posture-neutral { color: var(--color-text-muted); border-color: var(--color-border);
                      background: var(--color-surface-2); }
/* Dashed, unfilled: "we could not read your stance" must not look like a
   stance. Same dashed-means-not-a-fact convention as .gf-card--modelled. */
.gf-posture-unknown { color: var(--color-text-faint); border-style: dashed;
                      border-color: var(--color-border); background: none; }

/* ---- the stance line: why this posture ---------------------------------- */

/* Serif italic at standfirst size. This is the paper's stance stated in its own
   voice, so it is set in ink, not in the muted grey of an annotation. */
.gf-stance-line {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 0.95rem + 0.65vw, 1.45rem);
  font-style: italic;
  line-height: 1.42;
  color: var(--color-text);
  max-width: var(--gf-measure);
  margin: 0 0 var(--space-4);
}

/* ---- the quiet edition (§5) --------------------------------------------- */

/* Two italic serif blocks in a row would mush together, so the quiet-edition
   note is deliberately NOT another standfirst: it is an editor's note, set
   smaller, on the warning rail the rest of the app already uses for "this is
   thinner than usual". It has to read as caution, never as an error. */
.gf-standfirst {
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--color-text-muted);
  max-width: var(--gf-measure);
  margin: 0 0 var(--space-4);
  padding-left: var(--space-4);
  border-left: 2px solid var(--color-warning);
}

/* ---- filing stamp + the standing note on "the field" -------------------- */

.gf-filed {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  margin: 0 0 var(--space-3);
}

.gf-field-note {
  margin: 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--gf-hairline);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.6;
  letter-spacing: 0.04em;
  color: var(--color-text-faint);
  max-width: var(--gf-measure);
}
.gf-field-note strong { color: var(--color-text-muted); font-weight: 500; }


/* ============================================================================
   ===  THE LEADER  ===========================================================
   brief.lead — the paper's own voice about the whole edition, above the fold
   and above the lead story. Banner headline, drop cap, two columns of body.
   ========================================================================= */

.gf-leader {
  padding: 0 0 var(--space-8);
  margin-bottom: var(--space-8);
  border-bottom: 3px double var(--color-text);
}

.gf-leader .briefing-headline {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.8rem, 1.15rem + 2.4vw, 3.1rem);
  line-height: 1.04;
  letter-spacing: -0.024em;
  color: var(--color-text);
  margin: 0 0 var(--space-5);
  text-wrap: balance;          /* no one-word last line on a banner headline */
}

/* max-width is dropped because the leader body is COLUMNED at broadsheet
   width; a 60ch cap would collapse two columns back into one narrow one. */
.gf-leader .briefing-summary {
  font-size: 1.02rem;
  line-height: 1.72;
  color: var(--color-text);
  max-width: none;
  margin: 0;
}

/* The drop cap is the single loudest "this is a newspaper" signal available and
   it costs nothing. Exactly ONE per page — the leader gets it, the lead story
   does not — and none below 900px, where a 3-line cap in a phone column eats
   the first paragraph. */
@media (min-width: 900px) {
  .gf-leader .briefing-summary::first-letter {
    float: left;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 3.4em;
    line-height: 0.8;
    padding: 0.04em 0.08em 0 0;
    color: var(--color-text);
  }
}

/* Every live payload carries an ~880-character leader. Set in one column at
   1130px that is a 110-character line, which nobody can read; two columns puts
   it back at a newspaper measure. */
@media (min-width: 1200px) {
  .gf-leader .briefing-summary {
    columns: 2;
    column-gap: var(--gf-gutter);
    column-rule: 1px solid var(--gf-hairline);
  }
}


/* ============================================================================
   ===  THE LEAD STORY  =======================================================
   cards[0], always (§4, harold ruling). Kicker, headline, the live trigger as
   the dek, the cited principle as a display pull-quote, the byline naming the
   bytes, and the do/don't set as a pair.
   ========================================================================= */

.gf-lead-story {
  display: block;
  padding: 0 0 var(--space-8);
  margin: 0 0 var(--space-8);
  border-bottom: 3px double var(--color-text);
}

.gf-kicker--lead { margin-bottom: var(--space-3); }

.gf-lead-headline {
  font-family: var(--font-display);
  font-weight: 600;
  /* GATE — MAGAZINE PASS: --text-2xl, not --text-3xl — real lead headlines
     run up to ~160 chars (full sentences, not NYT-length captions) and need
     2-3 line wrap room at this size without looking absurd. */
  font-size: var(--text-2xl);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--color-text);
  margin: 0 0 var(--space-3);
  text-wrap: balance;
}

/* ---- provenance (demoted honesty/confidence/posture) --------------------
   GATE — MAGAZINE PASS: same three segments the kicker used to carry, now
   at byline weight UNDER the headline instead of competing with it above.
   Reuses .gf-honesty/.ce-conf/.gf-posture--sm and the .gf-kicker-sep
   middot verbatim — a re-layout of existing chips, not a new vocabulary. */
.gf-provenance {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  margin: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  color: var(--color-text-faint);
}

.gf-lead-story .gf-provenance { margin: 0 0 var(--space-4); }

.gf-dek {
  font-size: 0.98rem;
  line-height: 1.7;
  color: var(--color-text);
  max-width: none;
  margin: 0 0 var(--space-4);
}

/* GATE — MAGAZINE PASS: the 2-column split above was written for the old
   full-page-width lead story. The lead now sits in a narrower 1.7fr grid
   column (see .gf-front-grid), so splitting into two columns there just
   orphans the tail of the paragraph over on the right — normal single-
   column flow reads correctly at the new width. */

/* ---- the pull-quote ----------------------------------------------------- */

/* The cited principle, at display size, on the app's established 2px primary
   rail (.briefing-item-implication). Same rail, same meaning — "this is the
   reasoning" — just set as a quote rather than as a note, because on the lead
   story the principle IS the story's licence to exist.

   The tint and the rounded corner are dropped and the rail is taken back to
   full --color-primary. What .briefing-item-implication actually paints in the
   live cascade is a DIM rail over a 4% green wash with a rounded right edge —
   which reads as a highlighted callout, i.e. as a UI chip sitting on top of
   the page. On newsprint the rail has to be a rule, not a panel. */
.gf-pullquote {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 1rem + 0.7vw, 1.6rem);
  font-style: italic;
  line-height: 1.38;
  color: var(--color-text);
  background: none;
  border-left: 2px solid var(--color-primary);
  border-radius: 0;
  max-width: 46ch;              /* a quote is short by design; do not run it
                                   out to the full measure or it stops
                                   reading as a quote */
  margin: 0 0 var(--space-3);
  padding: 0 0 0 var(--space-5);
}

/* The byline is the pull-quote's attribution, so it hangs off the same rail
   rather than starting a new left edge. */
.gf-byline {
  margin: 0 0 var(--space-6);
  padding-left: var(--space-5);
  border-left: 2px solid var(--color-primary);
}


/* ============================================================================
   ===  THE RACK  =============================================================
   GATE — MAGAZINE PASS, revised: cards[1..], now full width under the lead
   (see .gf-front-grid above — lead+rack no longer share a grid row, which
   is what caused the dead-space bug). Self-wrapping card grid: each card
   is a genuine distinct box, cards wrap to as many per row as fit at
   22rem minimum, and the number of rows is however many cards the server
   sent — no fixed column count, no shared-height dependency on anything
   else on the page. Rules still do the visual separation between items,
   see .gf-rack-item below (was: CSS multi-column text-flow before the
   first magazine pass, which is why items used to read as a wall of
   paragraphs).
   ========================================================================= */

.gf-rack {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: var(--space-5) var(--space-8);
}


/* ============================================================================
   ===  RACK ITEM  ============================================================
   mono kicker → serif headline → body → do/don't → source footnote, separated
   from its neighbour by a hairline. No border, no radius, no shadow: an item
   is a piece of a page, not a card floating above one.
   ========================================================================= */

.gf-rack-item {
  display: block;
  background: none;
  border: 0;
  /* --gf-rule, not --gf-hairline: the item separator is the only thing telling
     a reader where one story stops in a column, and at rgba(0,0,0,0.06) it was
     invisible on screen next to the vertical column rules. */
  border-top: 1px solid var(--gf-rule);
  border-radius: 0;
  /* Padding, never margin: in a multi-column context a top margin collapses
     away at the head of each column and the hairlines stop lining up. */
  padding: var(--space-5) 0 var(--space-6);
}

/* Honesty. HARD and DERIVED carry no rail — the kicker already names them, and
   a rail on every item turns the page into a fence. MODELLED gets a dashed one
   because "this rests on a fitted threshold" is the single thing a skimming
   reader must not miss. */
.gf-lead-story.gf-card--modelled,
.gf-rack-item.gf-card--modelled {
  border-left: 2px dashed var(--color-warning);
  padding-left: var(--space-4);
}

/* ---- checklist mark (done) -----------------------------------------------
   The manager's own "I handled this" toggle, same interaction as Action
   Queue's `.aq-check`/`.aq-check--on` (action_queue.css) but under Gaffer's
   own `.gf-` prefix rather than reusing that file's classes: this tab's
   cards already carry their own broadsheet vocabulary top to bottom (kicker,
   honesty rail, dodont, byline — none borrowed from Action Queue), so a
   checkbox pulled from a different tab's stylesheet would be the one piece
   of the card not speaking this file's visual language. gaffer.css already
   owns everything else the card needs, including the --color-primary /
   --color-warning tokens this reuses. De-emphasized, not hidden — a done
   card stays on the page (it is never removed or re-ranked out of view),
   it just dims and its headline strikes through. */
.gf-card-head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
}
.gf-card-head .gf-kicker,
.gf-card-head .gf-kicker--lead { margin: 0; }
.gf-lead-story .gf-card-head { margin-bottom: var(--space-3); }

.gf-check {
  flex: none;
  width: 1.4em;
  height: 1.4em;
  margin-top: 1px;
  border-radius: 50%;
  border: 1.5px solid var(--gf-rule, var(--color-border));
  background: none;
  color: transparent;
  font-size: 0.68rem;
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.gf-check:hover { border-color: var(--color-primary); }
.gf-check--on {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #05140d;
}

.gf-lead-story.gf-card--done,
.gf-rack-item.gf-card--done { opacity: 0.55; }
.gf-lead-story.gf-card--done .gf-lead-headline,
.gf-rack-item.gf-card--done .gf-card-headline { text-decoration: line-through; }

/* ---- kicker ------------------------------------------------------------- */

.gf-kicker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  margin: 0 0 var(--space-2);
  font-family: var(--font-mono);
  /* GATE — MAGAZINE PASS: bold — this is now a single-word section label
     (card.theme only), not a multi-chip stat strip, so it needs to read
     with real weight on its own. */
  font-weight: 700;
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

.gf-card-theme { color: var(--color-text-muted); }

/* The honesty tag carries a title= tooltip, so it gets the printed convention
   for "there is more behind this word": a dotted underline. */
.gf-honesty {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  cursor: help;
  border-bottom: 1px dotted currentColor;
}

.gf-honesty-hard     { color: var(--color-success); }
.gf-honesty-derived  { color: #7aa2f7; }
.gf-honesty-modelled { color: var(--color-warning); }

/* ---- headline + body ---------------------------------------------------- */

.gf-card-headline {
  font-family: var(--font-display);
  font-weight: 600;
  /* GATE — MAGAZINE PASS: --text-lg, matching the digest-rack/brief-item
     tier used across the rest of the magazine pass. */
  font-size: var(--text-lg);
  line-height: 1.22;
  letter-spacing: -0.012em;
  color: var(--color-text);
  margin: 0 0 var(--space-2);
  text-wrap: pretty;
}

.gf-trigger {
  font-size: 0.86rem;
  line-height: 1.62;
  color: var(--color-text);
  margin: 0 0 var(--space-3);
}

/* The API field names behind the trigger, set as a run of mono text rather
   than as chips — a row of grey pills is dashboard furniture, and in a 340px
   column it wraps into a rubble of boxes. */
.gf-fields {
  display: block;
  margin: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  line-height: 1.7;
  color: var(--color-text-faint);
  overflow-wrap: anywhere;      /* field names are long and unbreakable */
}

.gf-field {
  font-family: var(--font-mono);
  font-size: inherit;
  background: none;
  padding: 0;
}
.gf-field + .gf-field::before { content: "· "; color: var(--color-text-faint); }

/* The cited principle at item scale, on the app's 2px reasoning rail — same
   de-panelling as the lead story's pull-quote, see .gf-pullquote for why. */
.gf-principle {
  font-family: var(--font-display);
  font-size: 1rem;
  font-style: italic;
  line-height: 1.5;
  color: var(--color-text);
  background: none;
  border-left: 2px solid var(--color-primary);
  border-radius: 0;
  padding: 0 0 0 var(--space-4);
  max-width: none;                           /* it is already in a column */
  margin-bottom: var(--space-3) !important;  /* beats .briefing-item-implication */
}

/* ---- do / don't --------------------------------------------------------- */

.gf-dodont {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--gf-hairline);
}

.gf-do,
.gf-dont {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2);
  align-items: baseline;
  font-size: 0.86rem;
  line-height: 1.55;
}

/* Underlined mono labels rather than filled pills. The colour still separates
   the instruction from its opposite; two solid badges per item would print
   fourteen coloured blocks down a page that is meant to be ink on paper. */
.gf-dodont-label {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0 0 1px;
  border-bottom: 2px solid currentColor;
  white-space: nowrap;
}

.gf-do   .gf-dodont-label { color: var(--color-success); }
.gf-dont .gf-dodont-label { color: var(--color-danger); }

.gf-dodont-text { color: var(--color-text); }

/* On the lead story the do and the don't are the point of the page, so at
   tablet width and up they run as a pair of columns instead of a stack. */
@media (min-width: 900px) {
  .gf-lead-story .gf-dodont {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-6);
  }
}

/* ---- source footnote ---------------------------------------------------- */

/* Dotted underline + short hash (§4). The dotted rule is the printed
   convention for "there is a source behind this"; the hash is the proof — it
   names the exact bytes of the canon the advice was reasoned from. */
.gf-cites {
  display: block;
  margin: var(--space-3) 0 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.7;
  letter-spacing: 0.04em;
  color: var(--color-text-faint);
  /* .briefing-sources fades itself to 0.7; stacked on --color-text-faint that
     puts the one string proving where the advice came from below legibility.
     The colour already says "footnote" — the alpha does not need to as well. */
  opacity: 1;
  overflow-wrap: anywhere;
}

.gf-cite {
  font-family: var(--font-mono);
  font-size: inherit;
  color: var(--color-text-faint);
  text-decoration: underline dotted;
  text-decoration-color: var(--color-border);
  text-underline-offset: 3px;
}
.gf-cite + .gf-cite { margin-left: var(--space-3); }

/* `.gf-footnote` is the rack item's citation and `.gf-byline` is the lead
   story's; both also carry `.gf-cites`, which is where the type lives. The
   footnote gets a hairline over it because at the foot of a column it needs a
   visible break from the advice it is sourcing. */
.gf-footnote {
  padding-top: var(--space-3);
  border-top: 1px solid var(--gf-hairline);
}

.gf-cite-file { color: var(--color-text-muted); }
.gf-cite-sect { color: var(--color-text-faint); }
.gf-cite-hash { opacity: 0.75; }

.gf-card-degraded { display: inline-block; margin: var(--space-3) 0 0; }

.gf-card-foot {
  margin-top: var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}


/* ============================================================================
   ===  THE EDITOR'S SPIKE  ===================================================
   suppressed[] — what the paper declined to run, and why. Set on tinted stock
   under a heavy rule so it reads as an aside to the edition rather than as an
   error log. §4.

   WHY IT IS NOT A RIGHT-HAND RAIL. "Sidebar" is the idiom, not the geometry.
   The live payloads carry FIFTEEN to SIXTEEN spiked rows whose reasons run to
   ~350 characters; in a 280px rail that is a ~3000px column standing beside a
   ~1400px rack, i.e. a page two-thirds of which is a list of things we did not
   print. It runs full width under the rack instead, set in its own columns.
   ========================================================================= */

.gf-spike {
  display: block;
  background: var(--color-surface-2);
  border: 0;
  border-top: 3px solid var(--color-text);
  border-radius: 0;
  padding: var(--space-5) var(--space-6) var(--space-4);
  margin: 0 0 var(--space-10);
}

.gf-spike-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-2);
  border-bottom: 1px solid var(--gf-rule);
}

/* margin-left:auto rather than flex:1 on the label — the label is style.css's
   and is not touched by this file (see THE SHEET). */
.gf-spike-count {
  margin-left: auto;
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--color-text-faint);
}

.gf-spike-dek {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-style: italic;
  color: var(--color-text-muted);
  margin: 0 0 var(--space-3);
}

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

/* Two columns, not three: the spike's rows are two to three times longer than
   a rack item's, and a third column would leave them at a ~30-character
   measure. Same primitive as the rack, one column fewer, for the same reason
   the rack has three — the measure has to suit the copy. */
@media (min-width: 1200px) {
  .gf-withheld-list {
    column-count: 2;
    column-gap: var(--gf-gutter);
    column-rule: 1px solid var(--gf-rule);
  }
}

.gf-withheld-item {
  display: block;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  break-inside: avoid;
  padding: var(--space-3) 0;
  border-bottom: 1px dotted var(--color-border);
  font-size: 0.84rem;
}
.gf-withheld-item:last-child { border-bottom: 0; }

/* The withheld card's name is an identifier, so it is set in mono on its own
   line and the reason gets the full width beneath it. Side by side, the reason
   would be reading at a 20-character measure inside a column. */
.gf-withheld-name {
  display: block;
  margin-bottom: var(--space-1);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text);
  overflow-wrap: anywhere;      /* ids are long, hyphenated and unbreakable */
}

.gf-withheld-reason {
  display: block;
  color: var(--color-text-muted);
  line-height: 1.6;
}


/* ============================================================================
   ===  COLOPHON — canon provenance  ==========================================
   _meta.canons: what the paper was reading from, with the hash of exactly the
   bytes that were sent. Printed small, at the foot, like a colophon. §4.
   ========================================================================= */

.gf-canons {
  display: block;
  margin-top: var(--space-10);
  padding-top: var(--space-4);
  border-top: 3px double var(--color-text);
}

.gf-canon-list { list-style: none; margin: var(--space-3) 0 0; padding: 0; }

.gf-canon-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--gf-hairline);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
}
.gf-canon-list li:last-child { border-bottom: 0; }

.gf-canon-file { color: var(--color-text); overflow-wrap: anywhere; }

.gf-canon-hash {
  color: var(--color-text-faint);
  text-decoration: underline dotted;
  text-decoration-color: var(--color-border);
  text-underline-offset: 3px;
  overflow-wrap: anywhere;
}

.gf-canon-chars { color: var(--color-text-faint); margin-left: auto; }

.gf-canon-note {
  margin: var(--space-3) 0 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--color-text-faint);
  max-width: var(--gf-measure);
}


/* ============================================================================
   ===  SHORT EDITIONS — pending, failure, nothing cleared the bar  ===========
   A degrade state is still a printed page. It gets the same measure and the
   same type as a full edition, because "we have nothing for you, and here is
   why" is an editorial statement, not an error dialog. §5.
   ========================================================================= */

.gf-empty {
  padding: var(--space-8) 0;
  margin-bottom: var(--space-8);
  border-top: 1px solid var(--gf-rule);
  border-bottom: 1px solid var(--gf-rule);
  text-align: left;
}

.gf-empty .briefing-headline {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.5rem, 1.1rem + 1.4vw, 2.25rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 0 0 var(--space-4);
}

.gf-empty .briefing-summary {
  font-size: 1rem;
  line-height: 1.7;
  max-width: var(--gf-measure);
}

/* A failed run is the one state that gets a colour rail: it must never be
   read as "no advice was warranted". */
.gf-empty--error {
  border-left: 3px solid var(--color-danger);
  padding-left: var(--space-5);
}
.gf-empty--error .ce-degraded { display: inline-block; margin-top: var(--space-4); }


/* ============================================================================
   ===  NIGHT  ================================================================
   Night is the same page printed on dark stock. Every colour comes from the
   body.night token block in style.css — including the rules, since they are
   drawn in --color-text, which inverts. The only rules below are the handful
   of places that hard-code a light-paper tint.
   ========================================================================= */

body.night .gf-masthead--neutral { border-top-color: var(--color-text); }

/* Cards were never boxed, but kill any inherited paper white anyway. */
body.night .gf-lead-story,
body.night .gf-rack-item { background: none; }

/* Tinted stock: on dark paper a "tint" is a lift, not a wash. */
body.night .gf-spike { background: rgba(255, 255, 255, 0.04); }

/* style.css re-tints .briefing-item-implication under `body.night` — a two-
   class selector, so it outranks the single-class de-panelling in THE LEAD
   STORY above and the pull-quotes came back as green panels on dark stock.
   Matched here at the same weight rather than reached for with !important. */
body.night .gf-principle,
body.night .gf-pullquote { background: none; }

body.night .gf-posture-neutral { background: rgba(255, 255, 255, 0.06); }
body.night .gf-posture-protect { background: rgba(0, 255, 135, 0.08); }
body.night .gf-posture-climb   { background: rgba(227, 160, 100, 0.10); }

/* Dotted source rules vanish against dark stock at --color-border, so they are
   lifted to the faint ink. */
body.night .gf-cite,
body.night .gf-canon-hash { text-decoration-color: var(--color-text-faint); }

body.night .gf-withheld-item { border-bottom-color: rgba(255, 255, 255, 0.12); }


/* ============================================================================
   ===  NARROW  ===============================================================
   Below 900px the paper is a single column — §6, and the only honest layout
   for a phone. Nothing below may introduce horizontal overflow: no fixed
   widths, no min-widths, and every unbreakable mono run made breakable.
   ========================================================================= */

@media (max-width: 899px) {
  /* Column counts are already 1 by default. What has to be undone here is the
     lead story's paired do/don't, which is handled by its own min-width query,
     and the leader's drop cap, likewise. This block exists for the spacing:
     a phone page wants tighter rules between its sections. */
  .gf-masthead { margin-bottom: var(--space-6); }
  .gf-leader,
  .gf-lead-story { padding-bottom: var(--space-6); margin-bottom: var(--space-6); }
  .gf-pullquote { max-width: none; }
}

@media (max-width: 480px) {
  #tab-gaffer { padding-inline: 0; }

  .gf-masthead {
    border-top-width: 4px;
    padding: var(--space-4) 0 var(--space-4);
  }
  .gf-masthead-rule { margin: var(--space-4) 0 var(--space-3); }

  .gf-nameplate-title { letter-spacing: -0.02em; }

  /* The dateline wraps to two lines on a phone, which leaves the mid-dot
     stranded at the end of line one. The chip and the run below it read as
     separate segments without it. */
  .gf-dateline .gf-kicker-sep { display: none; }

  .gf-spike { padding: var(--space-4); }

  .gf-canon-chars { margin-left: 0; }
}

@media (max-width: 420px) {
  /* At 420px the content box is ~388px. The only things that can push past it
     are unbreakable strings — canon filenames, sha prefixes, API field names,
     and a hostile payload's one-token headline — so they are all forced to
     break anywhere rather than to widen the page. */
  .gf-nameplate-sub { letter-spacing: 0.22em; padding-left: 0.22em; }

  .gf-dateline { letter-spacing: 0.1em; }

  .gf-canon-list li { gap: var(--space-1) var(--space-2); }

  .gf-leader .briefing-headline,
  .gf-lead-headline,
  .gf-card-headline,
  .gf-empty .briefing-headline { overflow-wrap: anywhere; }

  .gf-stance-line,
  .gf-standfirst,
  .gf-dek,
  .gf-trigger,
  .gf-principle,
  .gf-pullquote,
  .gf-dodont-text,
  .gf-withheld-reason { overflow-wrap: anywhere; }

  /* The do/don't label stops holding a column of its own on a 388px page. */
  .gf-do,
  .gf-dont { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
  .gf-dodont-label { justify-self: start; }

  .gf-lead-story.gf-card--modelled,
  .gf-rack-item.gf-card--modelled { padding-left: var(--space-3); }

  .gf-pullquote,
  .gf-byline { padding-left: var(--space-3); }

  .gf-standfirst { padding-left: var(--space-3); }

  .gf-empty--error { padding-left: var(--space-3); }
}

/* --- Price Watch (Tier 1) --- desktop-native, not reused from pulse.css:
   see the comment above gfrPriceWatchSectionHtml in gaffer_tab.js for why. */
.gf-price-watch-title {
  margin-bottom: var(--space-3);
}

/* GATE — MAGAZINE PASS: ticker-card grid. Each card stays chromeless like
   .gf-rack-item (no border, no radius, no shadow) — the only signal is a
   colored LEFT rail, the same rail vocabulary the rest of the page uses
   for honesty markers. Real crests (already wired here pre-pass) get real
   size: 30px, matching season_record.css's .sr-card-kit card-header
   register, not the old 22px inline-glyph scale. */
.gf-pw-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-3);
}

.gf-pw-row {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-left: 2px solid var(--gf-hairline);
}

.gf-pw-row--rise { border-left-color: var(--color-success); }
.gf-pw-row--fall { border-left-color: var(--color-danger); }
.gf-pw-row--rise .gf-pw-dir { color: var(--color-success); }
.gf-pw-row--fall .gf-pw-dir { color: var(--color-danger); }

.gf-pw-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-right: 44px; /* reserves the corner the MODELLED tag sits in */
}

.gf-pw-row .gaf-crest {
  width: 30px;
  height: 30px;
  display: inline-block;
  flex-shrink: 0;
}

.gf-pw-id {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.gf-pw-name { font-weight: 600; color: var(--color-text); overflow-wrap: anywhere; }

.gf-pw-meta {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

.gf-pw-stats {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.gf-pw-dir,
.gf-pw-band,
.gf-pw-net {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
}

.gf-pw-dir { font-weight: 600; }

.gf-pw-tag {
  position: absolute;
  top: var(--space-1);
  right: var(--space-1);
  font-size: 7px;
  opacity: 0.75;
}

.gf-price-watch-alt {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--gf-hairline);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
