/* ═══════════════════════════════════════════════════════════════════════════
   INTERNETCOVERPAGE — structure only.
   Not one colour, font or effect is decided here: everything visual comes from
   a skin file as a custom property, so a new skin is a new stylesheet and
   nothing else. Load order is base.css, then one skin.
   ═══════════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font-family: var(--sans);
  /* the ground is painted by .desk, which sits behind everything — if body
     painted too it would cover the pattern and the branding */
  background: var(--desktop-bg);
  color: var(--fg);
  overflow: hidden;                      /* the story sequence owns the screen */
  -webkit-text-size-adjust: 100%;
}
body.scrolling { overflow: hidden; }
img, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

/* ── the magazine: one portrait frame, centred ───────────────────────────── */
.mag {
  position: fixed; inset: 0; margin: auto; z-index: 2; overflow: hidden;
  width: 100vw; height: 100dvh;
  background: var(--paper-bg); overflow: hidden;
  display: flex; flex-direction: column;
}
@media (min-width: 720px) {
  .mag {
    height: 100dvh; width: min(100vw, calc(100dvh / 1.618));
    box-shadow: var(--mag-shadow);
  }
}


/* ── progress rail: one bar per screen, filling over its 15 seconds ──────── */
/* The rail counts the screens, so it belongs on top of everything the screen
   itself draws — the picture, and the grid over it. It gets its own ground as
   well, because a translucent bar over a dot grid reads as though the grid were
   painted on top of it. */
.rail { position: absolute; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; align-items: center; gap: 3px; padding: 7px 8px; height: var(--rail-h);
  background: var(--rail-ground); }

/* THE RAIL READS ITSELF OFF WHATEVER IS BEHIND IT.
   
   A fixed white bar over a fixed dark scrim is fine until the picture behind it
   is black, or white, or the video cuts between the two — and then the segments
   either vanish or sit in a grey band that has nothing to do with the artwork.
   `difference` inverts against the backdrop: white over black stays white, and
   over white it goes black. It cannot be invisible, on any frame of any video,
   without the scrim that was there to guarantee it.
   
   Guarded, because a browser without blend modes has to keep the scrim. */
@supports (mix-blend-mode: difference) {
  .rail { background: none; mix-blend-mode: difference; }
  .rail .bar { background: rgba(255,255,255,.34); }
  .rail .bar i { background: #fff; }
  .rail .bar.done { background: #fff; opacity: .62; }
}
@media (max-width: 560px) {
  .rail { gap: 2px; padding: 8px 7px; height: calc(var(--rail-h) + 4px); }
  .rail .bar { height: calc(var(--bar-h) + 1px); }
}
/* Fifteen segments across a narrow frame is about two centimetres each, and a
   fill that takes fifteen seconds to cross one is invisible. The live segment
   takes four times the width of the others so the movement can actually be
   seen, and settles back when it is done. */
.rail .bar { flex: 1; height: var(--bar-h); background: var(--bar-track); overflow: hidden;
  transition: flex-grow .3s ease; }
/* INSTAGRAM'S BAR, and it has to be seen moving. Four was not enough on a
   phone: sixty-nine pixels crossed over fifteen seconds is four pixels a
   second, which is movement you can measure and not movement you can see. */
/* Thirty seconds against fifteen, drawn as twice the segment — but only while
   it is waiting its turn. This has to sit ABOVE the live rule: same specificity,
   so whichever comes last wins, and a live video segment growing by two instead
   of by eight made the one screen that holds longest the shortest bar on the
   rail. At fourteen it did the opposite and squeezed the other seventeen down
   to eleven pixels each. Live is live; the width says the rest. */
.rail .bar.long { flex-grow: 2; }
.rail .bar.live { flex-grow: 8; }
.rail .bar i { display: block; height: 100%; width: 0; background: var(--bar-fill); }
.rail .bar.done { background: var(--bar-fill); opacity: .55; }
.rail .bar.done i { width: 100%; }
.rail .bar.live i { animation: fill var(--dwell) linear forwards; }
.rail .folio { flex: none; margin-left: 4px; font-family: var(--mono); font-size: 8px;
  font-weight: 700; letter-spacing: .16em; color: var(--bar-fill); opacity: .75; }
body.paused .rail .bar.live i { animation-play-state: paused; }
@keyframes fill { to { width: 100%; } }
/* NO reduced-motion rule on the progress bar either, and for the same reason
   as the marquee: it filled the segment instantly and sat there, so on any
   phone with battery saver or Reduce Motion on the story never appeared to
   progress — which is exactly what it was reported as, working on a desktop
   and dead on a phone. A progress bar that does not progress is not a calmer
   progress bar. It is a bar that is lying about where you are. */

/* ── the stage holds the cover, with the reel closed over it on demand ───── */
.stage { position: relative; flex: 1; min-height: 0; }

/* the cover is not part of the sequence — it sits there until you pick a story */
.cover { position: absolute; inset: 0; display: flex; flex-direction: column; overflow: hidden; }
.cover > .bg { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; object-fit: cover; filter: var(--art-filter); }
.cover > .veil { position: absolute; inset: 0; z-index: 2; background: var(--overlay); }

/* ── the reel: opens on a coverline, runs the issue, closes back to the cover ─ */
.reel { position: absolute; inset: 0; z-index: 20; background: var(--paper-bg); }
.reel[hidden] { display: none; }
.reel-x {
  position: absolute; top: 20px; right: 8px; z-index: 42; width: 26px; height: 26px;
  display: grid; place-items: center; color: var(--reel-x-fg); font-size: 17px; line-height: 1;
  text-shadow: var(--shadow-s);
}
/* THE MUTE TOGGLE, under the close button. Only a screen with a video has one,
   because it is the only screen with anything to switch off. */
.sound {
  position: absolute; top: 52px; right: 8px; z-index: 42; width: 26px; height: 26px;
  display: grid; place-items: center; padding: 0; border: 0; background: none;
  color: var(--reel-x-fg); cursor: pointer;
}
.sound svg { width: 19px; height: 19px; display: block; filter: drop-shadow(0 1px 2px rgba(0,0,0,.55)); }
.sound .off-icon { display: none; }
.sound[aria-pressed="true"] .on-icon { display: none; }
.sound[aria-pressed="true"] .off-icon { display: block; }
.sound:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.deck { position: absolute; inset: 0; }
.screen { position: absolute; inset: 0; display: none; flex-direction: column; overflow: hidden; }
.screen.on { display: flex; }
/* the multi-story screens are grids, but only once they are the live screen —
   these have to out-specify .screen.on, or they would show all at once */
.screen.on.duo { display: grid; }
.screen.on.quad { display: grid; }
.screen > .bg { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; object-fit: cover; filter: var(--art-filter); }
.screen > .veil { position: absolute; inset: 0; z-index: 2; background: var(--overlay); }

/* ── what a story is doing: views and clicks, top right of every screen ────
   Set on its own chip rather than floating on the art, so it reads over a pale
   shard as well as a dark one, and clear of the progress rail on the top row. */
/* THE RIGHT EDGE IS WHERE THESE LIVE. They were all inset 42px to clear the
   close button, but the X is one button at the top of the reel — it is over the
   top row and nothing else, so every counter below it was being held off an
   edge by a button that is nowhere near it. */
.counts {
  position: absolute; top: calc(var(--rail-h) + 5px); right: 10px; z-index: 12;
  display: flex; gap: 8px; align-items: center;
  padding: 3px 7px; background: var(--counts-bg); border: var(--counts-border);
  font-family: var(--mono); font-size: 8px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--counts-fg);}
.counts b { color: var(--counts-strong); }

/* only the top row of a split screen sits under the rail */
.duo article:nth-of-type(n+2) .counts,
.quad article:nth-of-type(n+3) .counts { top: 5px; }
/* The top row is the one the X is over, so that row alone goes to the left. */
.screen.story > .counts,
.duo article:nth-of-type(1) .counts,
.quad article:nth-of-type(-n+2) .counts { left: 10px; right: auto; }
.duo .counts, .quad .counts { font-size: 7.5px; gap: 6px; padding: 2px 5px; }

/* ── the two things you can do with a story ─────────────────────────────── */
.acts { position: relative; display: flex; align-items: stretch;
  gap: 6px; flex-wrap: wrap; margin-top: 12px; }
/* every control in the row is the same box, so the link and the votes line up */
.more, .vote {
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
  border: var(--more-border); background: var(--more-bg); color: var(--more-fg);
  padding: 7px 12px; font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: var(--more-track); text-transform: uppercase; line-height: 1;}
/* One arrow and one number. There is no second arrow: a position on this page
   was paid for, and the crowd does not get to punish something somebody bought.
   A vote can be taken back, which is the only way it ever goes down. */
.score { display: inline-flex; align-items: stretch; border: var(--more-border); }
.score .vote { border: 0; padding: 7px 11px; line-height: 1; }
.score .n {
  display: inline-flex; align-items: center; justify-content: center; min-width: 46px;
  padding: 0 4px; font-family: var(--mono); font-size: 13px; font-weight: 700;
  color: var(--fg); border-left: var(--more-border);
}
.vote .caret { font-size: 12px; line-height: 1; }
.vote:hover { background: var(--more-bg-hover); color: var(--more-fg-hover); }
.vote[aria-pressed="true"] { background: var(--voted-bg); color: var(--voted-fg); }
/* Cast, but waiting on an address before anybody else sees it. */
.score.pending { border-style: dashed; }
.score.pending .vote[aria-pressed="true"] { background: var(--pending-bg); color: var(--pending-fg); }
.score.pending .n { border-left-style: dashed; opacity: .72; }
.duo .score .vote, .quad .score .vote { padding: 5px 8px; }
.quad .score .n { min-width: 34px; font-size: 11px; }
.duo .acts, .quad .acts { margin-top: 8px; }
.duo .more { padding: 5px 9px; font-size: 9px; }


/* Tap zones sit above the picture and below the text. They used to be at 30,
   over everything — and because .body carries z-index 10 it makes its own
   stacking context, so nothing inside it could ever be raised past them. Every
   control in a story was underneath a transparent div. */
.tap { position: absolute; top: 0; bottom: 0; z-index: 5; width: 34%; }
.tap.prev { left: 0; }
.tap.next { right: 0; width: 66%; }

/* ── screen 1: the cover ─────────────────────────────────────────────────── */
.mast { position: relative; z-index: 10; flex: none; padding: var(--mast-pad); }
.mast svg { width: 100%; display: block; filter: var(--mast-shadow); }
.mast svg text { font-family: var(--display); fill: var(--mast-fill); font-weight: var(--mast-weight); letter-spacing: var(--mast-track); }
/* ONE LINE. Never two.

   The old cover's strip is 17px tall with 2px of padding and 9.5px type, and it
   stays that height because nothing in it is allowed to wrap. Ours was taking a
   long dateline, wrapping it, and growing to two rows — which moved everything
   under it and made the page taller than the one it is copying.

   The middle item is the one that gives: it clips with an ellipsis rather than
   pushing the row open. */
.issue-line {
  display: flex; align-items: center; justify-content: var(--issue-justify, space-between);
  gap: var(--issue-gap, 10px); margin-top: 4px; padding: 2px 6px;
  background: var(--issue-bg); color: var(--issue-fg); border: var(--issue-border);
  font-family: var(--mono); font-size: 9.5px; font-weight: 700; line-height: 1.3;
  letter-spacing: var(--issue-track); text-transform: uppercase;
  white-space: nowrap; overflow: hidden;
}
.issue-line > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.issue-line > span:first-child, .issue-line > span:last-child { flex: none; }

/* THE COVER IS THREE QUARTERS AND ONE QUARTER.

   The eight other headlines take the top three quarters; the cover story takes
   the bottom quarter and is always in the same place. A third gave the story
   more room than it needed and left a hole under it. Fixed position, variable treatment — a reader looking for the
   day's story should never have to hunt for it, and the eight above it are the
   part that is allowed to be different every day.

   Set LEFT, because the right of that quarter belongs to the mark that opens
   the issue, and to the square above it. */
.lines {
  position: relative; z-index: 10; flex: 3; min-height: 0; overflow: hidden;
  display: flex; justify-content: space-between; gap: 12px; padding: 1.6dvh 16px 3.4dvh;
}
/* Each column keeps to its own three quarters. The last headline was hanging
   eight pixels past the bottom edge and getting cut in half there, which reads
   as a broken page rather than as a column that ends. On a short screen a
   headline gets two lines instead of three and the offset column starts higher
   — the three quarters are the fixed thing, and the type fits into them. */
.col { overflow: hidden; }
@media (max-height: 760px) {
  .line .t { -webkit-line-clamp: 2; }
  .line.m .t { -webkit-line-clamp: 2; }
  .col.offset { padding-top: 1.6dvh; padding-bottom: .6dvh; }
}
/* On the smallest screens six headlines fit and eight do not. Six whole ones
   beat seven and a half — the eighth was being sliced through the middle. */
@media (max-height: 620px) { .col .line:last-child { display: none; } }
/* The plate behind a headline is the headline's background, so it ran out to
   wherever the text ran to — which on both sides was the edge of the screen.
   The section holds it off; a plate that touches the frame reads as a mistake
   rather than as a plate. */
.col { display: flex; flex-direction: column; justify-content: space-between; gap: 1dvh; width: 47%; min-width: 0; }
.col.offset { padding-top: 3dvh; padding-bottom: 1dvh; }
.col.right { text-align: right; align-items: flex-end; }
.col.right .line .k { margin-left: auto; }

.line { display: block; max-width: 100%; }
.line .k {
  display: block; margin-bottom: 2px; width: fit-content; color: var(--kick-fg);
  background: var(--line-plate, transparent); padding: var(--line-plate-pad, 0);
  font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: var(--kick-track); text-transform: uppercase; text-shadow: var(--shadow-s);
}
.line .k::before { content: var(--kick-prefix); }
.col.right .line .k::before { content: ""; }
.col.right .line .k::after { content: var(--kick-suffix); }
.line .t {
  background: var(--line-plate, transparent); padding: var(--line-plate-pad, 0);
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  color: var(--c); font-family: var(--sans); font-weight: var(--line-weight);
  font-size: clamp(13px, 2.05dvh, 19px); line-height: 1.14; letter-spacing: -.01em;
  text-shadow: var(--shadow-m);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.line.m .t { font-size: clamp(17px, 2.7dvh, 26px); }
.line.s .t { font-size: clamp(11px, 1.65dvh, 15px); -webkit-line-clamp: 2; }
.line.s .k { font-size: 8px; }
.line:hover .t { text-decoration: underline; }
/* four editorial variants, so no two coverlines are set the same way */
.line.v0 .t { text-decoration: var(--v0-decoration); }
.line.v1 .t { font-family: var(--serif); font-style: italic; }
.line.v2 .t { font-family: var(--display); font-weight: 400; text-transform: uppercase; letter-spacing: .02em; }
.line.v3 .t { text-shadow: none; }
.line.v3 .hl {
  background: var(--hl-bg); color: var(--hl-fg); padding: 1px 4px;
  font-family: var(--hl-font); font-weight: var(--hl-weight);
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
.new { display: inline-block; background: var(--new-bg); color: var(--new-fg); padding: 0 3px;
  font-family: var(--sans); font-size: 8px; font-weight: 700; letter-spacing: var(--new-track);
  text-shadow: none; transform: var(--new-rotate); }
.blink { animation: var(--blink-anim); }
@keyframes blink { to { visibility: hidden; } }

/* Pushed to the floor. The coverlines take what they need at the top and the
   story takes the rest — it was sitting wherever the lines happened to end,
   with the whole bottom of the page empty under it. */
/* THE CORNER, and it is one number.
   
   It was 108px flat, which is right on a desktop and wrong on a phone: the
   bottom quarter of a 664-tall screen is 122px, and a 108 square with a 36
   button under it is 150 — the mark hung out of its own quarter and over the
   picture. The quarter is about 19dvh, so the square is what is left of that
   after the button and the gaps, and never more than the 108 it was drawn at. */
/* The floor is 72 and not less, because ALL 25 and the arrow have to sit on one
   line inside it. Narrower and the label wraps, which is how the foot came to
   read ALL / 25 in two rows with the arrow beside them. */
.cover { --corner: clamp(72px, calc(19dvh - 56px), 108px); }

.lead {
  position: relative; z-index: 10; flex: 1; min-height: 0;
  width: 100%; padding: 0 10px 1.4dvh; margin-top: 0;
  display: flex; flex-direction: column; justify-content: flex-start;
  text-align: left; overflow: hidden;
}
/* The mark sits in the right of this quarter, so the story keeps clear of it —
   the corner's own width plus a gap you can see rather than a hairline. It was
   a hardcoded 136 with a second guess at 380px wide, and both were wrong the
   moment the square stopped being one size. */
.lead.has-mark { padding-right: calc(var(--corner) + 28px); padding-bottom: 1.4dvh; }
.lead .k { display: block; margin-bottom: 3px; color: var(--lead-k-fg); font-family: var(--mono);
  font-size: 10px; font-weight: 700; letter-spacing: var(--lead-k-track); text-transform: uppercase;
  text-shadow: var(--shadow-s); }
/* Kept inside its quarter. TWO lines, because three at this size ran past the
   floor and the standfirst printed over the last one — a fixed slot is only a
   fixed slot if what goes in it is made to fit. The size stays large; what
   gives is the number of lines. */
.lead .t { color: var(--lead-fg); font-family: var(--display); font-weight: var(--lead-weight);
  font-size: clamp(20px, 3.5dvh, 38px); line-height: 1; text-transform: uppercase;
  letter-spacing: var(--lead-track); text-shadow: var(--shadow-l);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.lead .sub { display: block; margin-top: 5px; color: var(--sub-fg); font-family: var(--serif);
  font-style: italic; font-size: clamp(12px, 1.75dvh, 15px); line-height: 1.3;
  text-shadow: var(--shadow-s); -webkit-line-clamp: 2; min-height: 0; }

/* THE STANDFIRST IS WHAT GIVES WAY when the quarter is tight. The headline
   never does — and it was doing exactly that on a phone: the quarter is 122px
   at 664 tall, the four things in it wanted 165, and the only one that could
   shrink was the headline, so the cover printed a kicker, a standfirst and a
   price with NO STORY between them.
   
   These two rules have to sit after the declarations above them. The first one
   did not, which is why it never fired once. */
.lead .t { flex: none; }
@media (max-height: 720px) { .lead .sub { display: none; } }
/* A long one drops a step and is allowed a third line. Anything set larger runs
   past the floor of the quarter, and what gives is the size, never the words. */
.lead .t.long { font-size: clamp(17px, 2.7dvh, 29px); -webkit-line-clamp: 3; }
.cover.selling .lead .t.long { font-size: clamp(16px, 2.3dvh, 25px); }
.lead:hover .t { text-decoration: underline; }

.vid-tag { display: inline-block; margin-left: 8px; background: var(--tag-bg); color: var(--tag-fg);
  padding: 1px 6px; font-family: var(--mono); font-size: 8px; letter-spacing: .2em; }

/* ── story furniture ────────────────────────────────────────────────────────
   Shared by every screen that carries a story — one, two or six of them — so a
   headline is set the same way wherever it lands. The screen types below vary
   size and clamping only. */
.kick { color: var(--kick-fg); font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: var(--kick-track); text-transform: uppercase; text-shadow: var(--shadow-s); margin: 0 0 6px; }
.ttl { margin: 0; color: var(--lead-fg); font-family: var(--display); font-weight: var(--lead-weight);
  font-size: clamp(21px, 3.6dvh, 36px); line-height: 1.06; letter-spacing: var(--lead-track);
  text-shadow: var(--shadow-l); }
.sum { margin: 8px 0 0; color: var(--sub-fg); font-family: var(--serif); font-style: italic;
  font-size: clamp(12px, 1.8dvh, 15px); line-height: 1.35; text-shadow: var(--shadow-s); }

/* ── screens 2–9: one story, full bleed ────────────────────────────────────
   A skin whose ink is dark cannot rely on a scrim: it gets a ground under the
   words instead, the same answer the coverlines take. */
.story .body { position: relative; z-index: 10; margin-top: auto; padding: 8px 14px 4dvh;
  background: var(--body-plate, transparent); }
.duo .body, .quad .body { background: var(--body-plate, transparent); }
.more { position: relative; }
.more:hover { background: var(--more-bg-hover); color: var(--more-fg-hover); }

/* ── #10–17: two stories to a screen ─────────────────────────────────────── */
.duo { grid-template-rows: 1fr 1fr; }
.duo .half { position: relative; overflow: hidden; display: flex; flex-direction: column; }
.duo .half + .half { border-top: var(--split-rule); }
.duo .half > .bg { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; object-fit: cover; filter: var(--art-filter); }
.duo .half > .veil { position: absolute; inset: 0; z-index: 2; background: var(--overlay); }
.duo .body { position: relative; z-index: 10; margin-top: auto; padding: 0 12px 14px; }
.duo .ttl { font-size: clamp(15px, 2.4dvh, 23px); -webkit-line-clamp: 3; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
.duo .sum { font-size: clamp(11px, 1.5dvh, 13px); -webkit-line-clamp: 2; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }

/* ── #18–25: four stories to a screen ────────────────────────────────────── */
.quad { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.quad .cell { position: relative; overflow: hidden; display: flex; flex-direction: column; border: var(--split-rule); border-width: 0 0 1px 0; }
.quad .cell:nth-child(odd) { border-right-width: 1px; }
.quad .cell > .bg { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; object-fit: cover; filter: var(--art-filter); }
.quad .cell > .veil { position: absolute; inset: 0; z-index: 2; background: var(--overlay); }
.quad .body { position: relative; z-index: 10; margin-top: auto; padding: 0 9px 10px; }
.quad .kick { font-size: 7.5px; margin-bottom: 3px; }
.quad .ttl { font-size: clamp(11px, 1.65dvh, 15px); line-height: 1.14; -webkit-line-clamp: 3; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
.quad .sum { display: none; }

/* ── the marquee, across the top of the frame ────────────────────────────── */
.ticker {
  position: relative; z-index: 41; flex: none; width: 0; min-width: 100%;
  height: 20px; display: flex; align-items: center;
  overflow: hidden; background: var(--bar-bg); border-bottom: var(--bar-rule);
}
/* The run is the height of the strip and its words are centred in it. Setting
   the line-height to the strip's height instead put the type on the baseline of
   a twenty-pixel line, which is not the middle of a twenty-pixel strip — it sat
   on the floor. */
.tk { display: inline-flex; align-items: center; height: 100%; white-space: nowrap;
  animation: tk var(--tk-speed, 45s) linear infinite; }
/* The strip centres what is in it. Three goes at this went through line-height,
   which sets type on a baseline and is not the same thing as putting it in the
   middle of a box — it kept landing on the floor. */
/* SET LIKE THE ORIGINAL, because the original is right: eleven px, sentence
   case, and tracking you can barely see. It was shouting — uppercase at .22em,
   which is a headline's setting, not a wire's. A ticker is meant to be read
   past, and small letters read faster than capitals.

   Twenty tall, and line-height IS the height, so the type sits centred on the
   strip rather than padding pushing it open. */
.tk span { font-family: var(--mono); font-size: 11px; font-weight: 700; line-height: 1;
  letter-spacing: .04em; text-transform: none; color: var(--tk-fg); padding: 0 1.15em; }
.tk span.hot { color: var(--tk-hot); }
@keyframes tk { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* NO reduced-motion rule, on purpose, and this is the second thing to go wrong
   here. It first said `animation: none`, which froze the headlines dead on any
   phone with battery saver or Reduce Motion on — while the same phone ran the
   marquee on internetcover.pages.dev, which has no such rule. Slowing it down
   instead was no better: the marquee then ran at one speed on a desktop and
   another on a phone, which is the same bug wearing a different hat.

   One speed, everywhere, the same as the original. */

/* ── the chrome strips: ad slot and statistics bar, and the menu over both ── */
.chrome { position: relative; flex: none; }

/* The label sits at the left edge and the slot takes the rest of the row, with
   the creative centred inside it.

   It used to be the label and a 320px slot centred together as one group, which
   left a margin of dead chrome down both sides and pushed the AD chip into the
   middle of nowhere. The old site does it the other way and that is why its ad
   row reads as a row rather than as a floating box. */
.adbar { flex: none; display: flex; align-items: center; justify-content: flex-start; gap: 6px;
  padding: 4px; background: var(--chrome); border-top: var(--chrome-rule); }
/* THE LIST BAR. It stands where the advertisement stands on every other page,
   at exactly the same height, because the row across the foot of the frame is
   one row and its height is part of the furniture. Tomorrow does not carry an
   advertisement; it carries the way into the board. */
.listbar { text-decoration: none; gap: 8px; min-height: var(--adbar-h, 58px);
  padding-inline: 10px; }
.listbar .lb-t { font-family: var(--sans); font-size: 12px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--kick-fg); }
.listbar .lb-s { flex: 1; min-width: 0; font-family: var(--serif); font-size: 11px;
  font-style: italic; color: var(--sub-fg); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.listbar .lb-a { font-size: 15px; line-height: 1; color: var(--kick-fg);
  animation: va-nudge 2.4s ease-in-out infinite; }
.listbar:hover .lb-t { text-decoration: underline; }
.listbar:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
@media (max-width: 560px) { .listbar .lb-s { display: none; } }
@media (prefers-reduced-motion: reduce) { .listbar .lb-a { animation: none; } }

/* The list page is the panel, so the panel is not a floating window on it. */
[data-kind="list"] .allnews-x { display: none; }
[data-kind="list"] .adbar { display: flex; }

.ad-lbl { font-family: var(--sans); font-size: 8px; font-weight: 700;
  background: var(--adlbl-bg); border: var(--adlbl-border); color: var(--adlbl-fg); padding: 0 3px; }
/* THE AD, FULL WIDTH.

   The height is fixed and the width is the row. A slot pinned to 320px leaves
   dead chrome on both sides of it, and I spent too long defending that because
   the old cover does it — the old cover having the same gap is not a reason to
   keep it.

   --slot-h is the only dimension that matters now. Everything that appears in
   this row — both advertisements and the letter — is one full-width band of the
   same height, so the row never changes size and nothing in it floats. */
:root { --slot-h: 50px; }
.mag { container-type: inline-size; container-name: mag; }

.adrot { flex: 1; min-width: 0; height: var(--slot-h); overflow: hidden; }
.adslide { flex: 0 0 100%; height: 100%; }
.adslide .subscribe { width: 100%; height: 100%; }
.adtrack { display: flex; height: 100%; transition: transform .45s ease; }
@media (prefers-reduced-motion: reduce) { .adtrack { transition: none; } }

.banner { width: 100%; height: 100%; padding: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; overflow: hidden; background: var(--banner-bg); border: var(--banner-border);
  animation: var(--banner-anim); }
@keyframes bflash { to { border-color: var(--banner-flash); } }
/* the creative has to fit the slot it was sold — nothing clips, nothing bleeds */
.banner { gap: 2px; }
.banner .b1 {
  max-width: 100%; color: var(--banner-b1); font-family: var(--sans); font-weight: 700;
  font-size: clamp(8px, 2.4vw, 11px); line-height: 1.2; letter-spacing: .02em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.banner .b2 {
  max-width: 100%; margin-top: 2px; color: var(--banner-b2); font-family: var(--display);
  font-size: clamp(11px, 3.1vw, 15px); line-height: 1.15; letter-spacing: .02em;
  text-decoration: underline; text-underline-offset: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (min-width: 620px) {
  .banner .b1 { font-size: 11px; }
  .banner .b2 { font-size: 17px; }
}
@media (min-width: 900px) {

  .banner .b1 { font-size: 13px; letter-spacing: .06em; }
  .banner .b2 { margin-top: 5px; font-size: 24px; }
}
/* the newsletter fills the same slot, whatever size it is */
:is(.adslide, .nlpop, .encore-nl) .subscribe { height: 100%; padding: 6px 10px; }
@media (min-width: 900px) {
  :is(.adslide, .nlpop, .encore-nl) .subscribe { padding: 10px 14px; gap: 10px; }
  :is(.adslide, .nlpop, .encore-nl) .subscribe .lbl { font-size: 11px; max-width: 150px; }
  :is(.adslide, .nlpop, .encore-nl) .subscribe input { font-size: 13px; padding: 9px 11px;}
  :is(.adslide, .nlpop, .encore-nl) .subscribe button { font-size: 11px; padding: 10px 20px;}
}


/* ── the statistics bar — and the menu, which is exactly its height ──────── */
.statbar {
  flex: none; display: flex; gap: 3px;
  padding: 2px 3px; background: var(--chrome); border-top: var(--chrome-rule);
  font-size: 9.5px; min-height: var(--statbar-h);
}
.statbar .cell {
  display: flex; align-items: center; border: var(--cell-border); padding: 1px 7px;
  color: var(--cell-fg); font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; white-space: nowrap; overflow: hidden;}
.statbar .cell.grow { flex: 1; }
.statbar .cell b { color: var(--led); }
/* NOTHING is dropped from the bar on a phone. READING NOW carries the `wide`
   class and this rule hid it under 560px — on the one screen where it is the
   only live number on the page. The strip scrolls; that is what it is for, and
   it is why there is nothing here to hide. */

.burger {
  position: relative; z-index: 46; flex: none; width: var(--statbar-h);
  display: grid; place-items: center; border: var(--cell-border);
  background: var(--chrome); color: var(--cell-fg);}
.burger i {
  position: absolute; left: 50%; width: 14px; height: 2px; margin-left: -7px;
  background: currentColor; transition: transform .16s ease, opacity .16s ease;
}
.burger i:nth-child(1) { transform: translateY(-5px); }
.burger i:nth-child(3) { transform: translateY(5px); }
/* open: the top and bottom rules cross, the middle one goes */
.burger[aria-expanded="true"] { background: var(--menu-hover-bg); color: var(--menu-hover-fg); }
.burger[aria-expanded="true"] i:nth-child(1) { transform: rotate(45deg); }
.burger[aria-expanded="true"] i:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] i:nth-child(3) { transform: rotate(-45deg); }
@media (prefers-reduced-motion: reduce) { .burger i { transition: none; } }

/* the menu takes the statistics bar; the ad slot above it turns into the
   newsletter for as long as the menu is open. Six entries rarely fit a frame
   this narrow, so it scrolls rather than squeezing them into initials. */
.menu {
  position: absolute; z-index: 45; left: calc(var(--statbar-h) + 6px); right: 3px;
  bottom: 3px; height: calc(var(--statbar-h) - 4px);
  display: flex; gap: 3px; background: var(--chrome);
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  overscroll-behavior-x: contain; scroll-snap-type: x proximity;
  mask-image: linear-gradient(90deg, transparent 0, #000 6px,
    #000 calc(100% - 6px), transparent 100%);
}
.menu::-webkit-scrollbar { display: none; }
.menu[hidden] { display: none; }
.menu a {
  /* never shrink an entry to fit — the strip scrolls instead */
  flex: none; scroll-snap-align: start; white-space: nowrap; padding: 0 15px;
  display: flex; align-items: center; justify-content: center; text-align: center;
  border: var(--cell-border); background: var(--menu-bg); color: var(--menu-fg);
  font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: var(--menu-track); text-transform: uppercase;}
.menu a:hover, .menu a[aria-current] { background: var(--menu-hover-bg); color: var(--menu-hover-fg); }


/* ── newsletter: on the cover, and at the head of the list ───────────────── */
/* The newsletter is the same object wherever it appears; only the box it has to
   fill changes. In the ad slot it fills the slot; in the list it is a strip. */
.subscribe {
  width: 100%; display: flex; align-items: center; gap: 7px;
  border: var(--nl-border); padding: 6px 8px; background: var(--nl-bg);
}
:is(.adslide, .nlpop, .encore-nl) .subscribe { height: 100%; padding: 5px 8px; }
.allnews .subscribe { max-width: 460px; margin: 0 auto; }
.allnews .subscribe .lbl { max-width: 120px; }
.subscribe .lbl {
  flex: none; font-family: var(--mono); font-size: 9px; font-weight: 700; line-height: 1.2;
  letter-spacing: .12em; text-transform: uppercase; color: var(--nl-fg); max-width: 104px;
}
@media (max-width: 560px) { .subscribe .lbl { max-width: 68px; font-size: 8px; } }
.subscribe input {
  flex: 1; min-width: 0; font-family: var(--mono); font-size: 11px; font-weight: 700;
  background: var(--nl-field-bg); color: var(--nl-fg);
  border: 1px solid var(--nl-field-border); padding: 6px 8px;
}
.subscribe input::placeholder { color: var(--nl-placeholder); font-weight: 400; }
.subscribe input:focus-visible { outline: 2px solid var(--nl-btn-bg); outline-offset: 0; }
.subscribe button {
  flex: none; font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
  background: var(--nl-btn-bg); color: var(--nl-btn-fg); border: 0; padding: 7px 14px;
}
.subscribe button:hover { filter: brightness(1.25); }
/* the slot announces itself when it is carrying the newsletter rather than an ad */

/* ── the foot of the cover ───────────────────────────────────────────────────
   A mark in the bottom right, not a bar across the bottom. Full width made it
   compete with the headlines it was supposed to point past; at this size it
   reads as "there is more this way", which is all it ever had to say. */
/* Over the cover, not under it. As a row of its own it pushed the cover story
   up and left the whole bottom-left corner empty — the mark only occupies the
   right, so the text should have the rest.

   Absolutely positioned with no width, so it shrink-wraps to the widest thing
   in it, which is the mark. */
.cover-foot {
  position: absolute; right: 10px; bottom: 1.2dvh; z-index: 14;
  display: flex; justify-content: flex-end;
}
.viewall {
  display: inline-flex; align-items: center; justify-content: space-between;
  width: var(--corner); gap: 6px; cursor: pointer;
  padding: 8px 9px 8px 11px;
  background: var(--raise-bg, var(--kick-fg)); color: var(--raise-fg, var(--paper-bg));
  border: 0; text-decoration: none;
}
.va-t {
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; line-height: 1;
  white-space: nowrap;
}
/* At the floor width the tracking is what gives, not the line. */
@media (max-height: 760px) { .va-t { letter-spacing: .1em; } }
/* An arrow, set in the label's own colour. It was a filled square with a play
   triangle in it, which on a phone read as a video control on a page whose
   cover is sometimes a video. An arrow says "this way" and nothing else. */
.va-a {
  font-style: normal; font-size: 15px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  width: auto; height: auto; background: none;
  color: var(--raise-fg, var(--paper-bg));
  animation: va-nudge 2.4s ease-in-out infinite;
}
/* A small movement rather than a big button: the eye finds motion at the edge
   of a still page without the page having to shout. */
@keyframes va-nudge {
  0%, 72%, 100% { transform: translateX(0); }
  80%           { transform: translateX(3px); }
  88%           { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) { .va-a { animation: none; } }
.viewall:hover { filter: brightness(1.15); }
.viewall:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ── where the issue ends: the weekly and the archive ───────────────────── */
.endlinks { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 14px; }

/* ── all news: the same frame, scrolling inside it ────────────────────────
   Not another page and not another design — the marquee, the ad slot and the
   statistics bar stay exactly where they are; only this panel scrolls. */
.allnews {
  position: absolute; inset: 0; z-index: 22; overflow-y: auto; overscroll-behavior: contain;
  background: var(--paper-bg); padding: 14px 12px 30px;
}
.allnews[hidden] { display: none; }
.allnews-x {
  position: sticky; top: 0; z-index: 3; float: right; width: 26px; height: 26px;
  display: grid; place-items: center; color: var(--reel-x-fg); font-size: 17px; line-height: 1;
}
.allnews .band { display: flex; align-items: center; gap: 12px; margin: 26px 0 12px;
  font-family: var(--mono); font-size: 9px; letter-spacing: var(--kick-track);
  text-transform: uppercase; color: var(--kick-fg); }
.allnews .band::after { content: ''; flex: 1; height: 1px; background: var(--rule-color); }
.feedrow { display: grid; grid-template-columns: 26px 62px 1fr; gap: 10px; align-items: start;
  padding: 10px 0; border-bottom: 1px solid var(--rule-color); }
.feedrow .no { font-family: var(--mono); font-size: 11px; color: var(--kick-fg); }
.feedrow img { width: 62px; height: 46px; object-fit: cover; filter: var(--art-filter); }
.feedrow h3 { margin: 0; font-family: var(--display); font-weight: var(--lead-weight);
  font-size: 15px; line-height: 1.2; color: var(--fg); }
.feedrow p { margin: 4px 0 0; font-family: var(--mono); font-size: 9px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--kick-fg); }
.endmark { text-align: center; padding: 30px 0 6px; clear: both; }
.endmark .subscribe { margin: 18px auto 0; text-align: left; }
.endmark p { font-family: var(--mono); font-size: 10px; letter-spacing: var(--kick-track);
  text-transform: uppercase; color: var(--kick-fg); margin: 0 0 8px; }
.endmark b { font-family: var(--display); font-weight: var(--lead-weight); font-size: 26px; color: var(--fg); }
.backtop { display: inline-block; margin-top: 18px; border: var(--more-border);
  padding: 8px 16px; font-family: var(--mono); font-size: 10px;
  letter-spacing: var(--more-track); text-transform: uppercase; color: var(--fg); }

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

/* keyboard users must always see where they are */
a:focus-visible, button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ═══════════════════════════════════════════════════════════════════════════
   THE DESK — everything outside the magazine frame.
   Desktop only: on a phone the frame is the whole screen and none of this has
   anywhere to live. All of it is driven by data-attributes on <html>, so the
   switcher only ever sets an attribute and the stylesheet does the rest.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── desk patterns ───────────────────────────────────────────────────────── */
.desk { position: fixed; inset: 0; z-index: 0; background: var(--desk-bg); }
.desk::after { content: ''; position: absolute; inset: 0; opacity: var(--desk-ink, .5); }

[data-desk="plain"] .desk::after { background: none; }

[data-desk="dots"] .desk::after {
  background-image: radial-gradient(var(--desk-fg) 1px, transparent 1px);
  background-size: 14px 14px;
}
[data-desk="grid"] .desk::after {
  background-image: linear-gradient(var(--desk-fg) 1px, transparent 1px),
                    linear-gradient(90deg, var(--desk-fg) 1px, transparent 1px);
  background-size: 32px 32px;
}
[data-desk="pinstripe"] .desk::after {
  background-image: repeating-linear-gradient(45deg,
    var(--desk-fg) 0 1px, transparent 1px 9px);
}
[data-desk="rule"] .desk::after {
  background-image: repeating-linear-gradient(0deg,
    var(--desk-fg) 0 1px, transparent 1px 7px);
}
[data-desk="halftone"] .desk::after {
  background-image: radial-gradient(var(--desk-fg) 22%, transparent 23%),
                    radial-gradient(var(--desk-fg) 22%, transparent 23%);
  background-size: 18px 18px;
  background-position: 0 0, 9px 9px;
}
[data-desk="vignette"] .desk::after {
  background: radial-gradient(ellipse at center, transparent 40%, var(--desk-fg) 140%);
  opacity: .55;
}

/* ── branding takeover: the gutters either side, fading toward the page ──── */
.deskbrand { position: fixed; inset: 0; z-index: 1; display: none; pointer-events: none; }
[data-brand="on"] .deskbrand { display: block; }
.deskbrand i {
  position: absolute; top: 0; bottom: 0; width: 50vw;
  background-image: var(--brand-art);
  background-size: cover;
}
/* one campaign photograph, read from both edges and falling away toward the
   frame — the way a desktop takeover always worked */
.deskbrand .l { left: 0;  background-position: left center;
  -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 46%, transparent 98%);
          mask-image: linear-gradient(90deg, #000 0%, #000 46%, transparent 98%); }
.deskbrand .r { right: 0; background-position: right center;
  -webkit-mask-image: linear-gradient(270deg, #000 0%, #000 46%, transparent 98%);
          mask-image: linear-gradient(270deg, #000 0%, #000 46%, transparent 98%); }
/* only the wordmark is clickable — a takeover should not swallow every stray
   click on the desk, which is exactly what a full-width anchor was doing */
.deskbrand .word {
  position: absolute; left: 3vw; top: 50%; transform: translateY(-50%);
  max-width: min(24vw, 300px); color: var(--brand-word-fg);
  text-shadow: 0 2px 20px rgba(0,0,0,.7); pointer-events: auto;
  display: block; padding: 10px 12px; border: 1px solid transparent;
}
.deskbrand .word:hover { border-color: var(--brand-word-fg); }
.deskbrand .word b {
  display: block; font-family: var(--display); font-weight: var(--lead-weight);
  font-size: clamp(22px, 2.4vw, 40px); line-height: 1.02; letter-spacing: var(--lead-track);
}
.deskbrand .word span {
  display: block; margin-top: 8px; font-family: var(--mono); font-size: 10px;
  font-weight: 700; letter-spacing: .26em; text-transform: uppercase;
}
@media (max-width: 1200px) { .deskbrand .word { display: none; } }
.deskbrand .tag {
  position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
  font-family: var(--mono); font-size: 8px; font-weight: 700; letter-spacing: .3em;
  text-transform: uppercase; color: var(--brand-tag-fg); background: var(--brand-tag-bg);
  padding: 2px 8px;
}

/* ── reading modes ───────────────────────────────────────────────────────────
   Both modes work on the TYPE — face, size, measure, weight, position and how
   much of it there is. Neither one touches the artwork: somebody paid for that
   image or that video, and dimming it is not ours to do. What changes is the
   text sitting on it.

   focus  — less text. The headline, and nothing else competing with the picture.
   reader — more text. A solid sheet for the words to sit on, at reading size.  */

/* ── The two levels ──────────────────────────────────────────────────────────
   Normal → Focus → Reader, and they stack: Reader does everything Focus does
   and then goes further. Focus takes the furniture away; Reader also resets the
   type for reading. Neither touches the artwork.                              */

/* LEVEL 1 — Focus. Everything competing with the story steps back. */
[data-mode="focus"] .ticker, [data-mode="reader"] .ticker,
[data-mode="focus"] .chrome, [data-mode="reader"] .chrome {
  opacity: .22; transition: opacity .3s ease;
}
[data-mode="focus"] .ticker:hover, [data-mode="reader"] .ticker:hover,
[data-mode="focus"] .chrome:hover, [data-mode="reader"] .chrome:hover,
[data-mode="focus"] .chrome:focus-within, [data-mode="reader"] .chrome:focus-within {
  opacity: 1;
}
[data-mode="focus"] .rail, [data-mode="reader"] .rail,
[data-mode="focus"] .counts, [data-mode="reader"] .counts,
[data-mode="focus"] .desk::after, [data-mode="reader"] .desk::after,
[data-mode="focus"] .deskbrand, [data-mode="reader"] .deskbrand { display: none; }
[data-mode="focus"] .kick, [data-mode="reader"] .kick,
[data-mode="focus"] .line .k, [data-mode="reader"] .line .k,
[data-mode="focus"] .lead .k, [data-mode="reader"] .lead .k { opacity: .72; }
[data-mode="focus"] .mag, [data-mode="reader"] .mag { box-shadow: 0 0 120px rgba(0,0,0,.6); }

/* LEVEL 2 — Reader. On top of all of that, the words are reset to be read. */
[data-mode="reader"] .story .body,
[data-mode="reader"] .duo .body,
[data-mode="reader"] .quad .body,
[data-mode="reader"] .cover .lead {
  background: var(--reader-sheet); padding: 14px 16px;
  border-top: var(--reader-sheet-rule);
}
[data-mode="reader"] .cover .lead { width: 100%; margin: 0; }
[data-mode="reader"] .ttl,
[data-mode="reader"] .lead .t {
  font-family: var(--serif); font-weight: 400; text-transform: none;
  letter-spacing: 0; line-height: 1.22; text-shadow: none;
  font-size: clamp(17px, 2.5dvh, 25px); -webkit-line-clamp: unset; display: block; overflow: visible;
}
[data-mode="reader"] .sum {
  font-style: normal; font-family: var(--serif); color: var(--reader-body-fg);
  font-size: clamp(13px, 1.85dvh, 16px); line-height: 1.6; max-width: 62ch;
  text-shadow: none; -webkit-line-clamp: unset; display: block; overflow: visible;
}
[data-mode="reader"] .quad .sum { display: block; font-size: 11px; line-height: 1.45; }
[data-mode="reader"] .line .t {
  font-family: var(--serif); font-weight: 400; text-transform: none;
  letter-spacing: 0; line-height: 1.28; -webkit-line-clamp: 4; text-shadow: none;
  background: var(--reader-sheet); padding: 3px 6px;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
[data-mode="reader"] .lead .sub { font-style: normal; font-size: clamp(12px, 1.7dvh, 15px); line-height: 1.55; }

@media (prefers-reduced-motion: reduce) {
  [data-mode="focus"] .ticker, [data-mode="reader"] .ticker,
  [data-mode="focus"] .chrome, [data-mode="reader"] .chrome { transition: none; }
}

/* ── the switcher, parked on the desk ────────────────────────────────────── */
.switcher {
  position: fixed; left: 14px; bottom: 14px; z-index: 60;
  font-family: var(--mono); display: none;
}
@media (min-width: 1080px) { .switcher { display: block; } }

.switcher-btn {
  display: flex; align-items: center; gap: 7px;
  background: var(--sw-bg); color: var(--sw-fg); border: var(--sw-border);
  padding: 8px 12px; font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: .22em; text-transform: uppercase;}
.switcher-btn .swatch {
  width: 11px; height: 11px; border: 1px solid currentColor;
  background: linear-gradient(135deg, var(--sw-a) 50%, var(--sw-b) 50%);
}
.switcher-panel {
  position: absolute; left: 0; bottom: calc(100% + 8px); width: 232px;
  background: var(--sw-bg); border: var(--sw-border); padding: 12px;
  display: grid; gap: 12px;}
.switcher-panel[hidden] { display: none; }
.switcher h4 {
  margin: 0 0 6px; font-size: 8px; font-weight: 700; letter-spacing: .28em;
  text-transform: uppercase; color: var(--sw-dim);
}
.opts { display: flex; flex-wrap: wrap; gap: 4px; }
.opts button {
  flex: 1 1 auto; padding: 6px 8px; font-family: var(--mono); font-size: 8.5px;
  font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  background: transparent; color: var(--sw-fg); border: var(--sw-border);}
.opts button:hover { background: var(--sw-hover-bg); color: var(--sw-hover-fg); }
.opts button[aria-pressed="true"] { background: var(--sw-on-bg); color: var(--sw-on-fg); border-color: var(--sw-on-bg); }

/* ── menu pages, swapped into the shell ─────────────────────────────────────
   Same frame, same chrome. htmx replaces only what is inside this box, so the
   marquee, the ad slot and the menu are never re-rendered or re-painted. */
.pagewin {
  position: absolute; inset: 0; z-index: 24; overflow-y: auto; overscroll-behavior: contain;
  background: var(--paper-bg); padding: 14px 16px 24px;
}
.pagewin:empty { display: none; }
.page-x {
  position: sticky; top: 0; z-index: 3; float: right; width: 26px; height: 26px;
  display: grid; place-items: center; color: var(--reel-x-fg); font-size: 17px; line-height: 1;
}
.lede {
  font-family: var(--serif); font-size: 15px; line-height: 1.55;
  max-width: 58ch; color: var(--fg); margin: 0 0 12px;
}
.pagewin .band { display: flex; align-items: center; gap: 12px; margin: 18px 0 10px;
  font-family: var(--mono); font-size: 9px; letter-spacing: .32em;
  text-transform: uppercase; color: var(--kick-fg); }
.pagewin .band::after { content: ''; flex: 1; height: 1px; background: var(--rule-color); }
.pagewin .band:first-of-type { margin-top: 0; }

/* ── the auction ladder ─────────────────────────────────────────────────────
   Same furniture as the rest of the magazine: mono for every number, serif for
   every word, and the bar behind each row drawn to the size of the bid. */
.codebar { border: 1px solid var(--rule-color); padding: 10px 12px; margin-bottom: 14px;}
.findform { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; }
.findform label, .field label {
  display: block; font-family: var(--mono); font-size: 8px; font-weight: 700;
  letter-spacing: .24em; text-transform: uppercase; color: var(--kick-fg); margin-bottom: 4px;
}
.findform input, .field input {
  font-family: var(--mono); font-size: 12px; font-weight: 700; letter-spacing: .06em;
  background: var(--nl-field-bg); color: var(--nl-fg);
  border: 1px solid var(--nl-field-border); padding: 7px 9px; width: 100%;}
.findform input { min-width: 170px; }
.findform > form, .findform label + input { flex: 1; }
.field { margin-bottom: 8px; }
.two-up { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

.stats { display: flex; flex-wrap: wrap; gap: 1px; background: var(--rule-color);
  border: 1px solid var(--rule-color); margin-bottom: 12px; }
.stat { flex: 1 1 96px; background: var(--paper-bg); padding: 8px 10px;}
.stat b { display: block; font-family: var(--mono); font-size: 16px; color: var(--fg); }
.stat span { font-family: var(--mono); font-size: 7.5px; letter-spacing: .24em;
  text-transform: uppercase; color: var(--kick-fg); }

.youare { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  border: 2px solid var(--kick-fg); padding: 9px 11px; margin: 0 0 12px;
  font-family: var(--serif); font-size: 14px; color: var(--fg); }

.ladder { list-style: none; margin: 0; padding: 0; }
.tierband { display: flex; align-items: baseline; gap: 10px; padding: 14px 0 5px;
  font-family: var(--mono); font-size: 8.5px; letter-spacing: .26em; text-transform: uppercase;
  color: var(--kick-fg); border-bottom: 1px solid var(--rule-color); }
.tierband b { font-weight: 700; }
.tierband span { color: var(--counts-fg); }
.tierband.below { color: var(--counts-fg); }

.rung { position: relative; display: grid; gap: 10px; align-items: center;
  grid-template-columns: 30px 1fr 116px auto;
  padding: 9px 0; border-bottom: 1px solid var(--rule-color); }
.rung > * { position: relative; }
.rung .depth { position: absolute; left: 0; top: 0; bottom: 0;
  background: var(--depth-bg); border-right: 1px solid var(--depth-edge); }
.rung .folio { font-family: var(--mono); font-size: 11px; font-weight: 700; color: var(--kick-fg); }
.rung .what b { display: block; font-family: var(--serif); font-weight: 400;
  font-size: 14px; line-height: 1.2; color: var(--fg); }
.rung .who { display: block; margin-top: 2px; font-family: var(--mono); font-size: 8px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--counts-fg); }
.rung .price { font-family: var(--mono); font-size: 12px; font-weight: 700;
  color: var(--fg); text-align: right; }
.rung .votes { font-family: var(--mono); font-size: 9px; color: var(--counts-fg); text-align: left; }
.rung small { display: block; font-weight: 400; font-size: 7.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--counts-fg); }
.rung.mine { box-shadow: inset 3px 0 0 var(--kick-fg); }
.rung.cut { opacity: .62; }

.btn.raise { padding: 5px 9px; font-size: 8.5px; background: transparent;
  color: var(--more-fg); border: var(--more-border);}
.btn.raise:hover { background: var(--more-bg-hover); color: var(--more-fg-hover); }
.btn.go { background: var(--nl-btn-bg); color: var(--nl-btn-fg); border: 0; }

.raise-form { border: 2px solid var(--kick-fg); padding: 12px; margin-bottom: 14px;
  background: var(--paper-bg);}
.raise-form h4 { margin: 0 0 10px; font-family: var(--serif); font-weight: 400; font-size: 15px; color: var(--fg); }
.raise-form .row { display: flex; gap: 8px; margin-top: 4px; }

.flash { border: 1px solid var(--kick-fg); padding: 9px 11px; margin-bottom: 12px;
  font-family: var(--mono); font-size: 10px; letter-spacing: .08em; color: var(--kick-fg);}
.flash.bad { border-color: var(--flash-bad); color: var(--flash-bad); }
.flash.good b { font-size: 13px; }
.flash .code { display: block; margin-top: 6px; color: var(--fg); }
.flash .code b { letter-spacing: .2em; }

@media (max-width: 620px) {
  .rung { grid-template-columns: 26px 1fr auto; }
  .rung .price { grid-column: 2; text-align: left; }
  .two-up { grid-template-columns: 1fr; }
}

/* ── the archive: a run of days ─────────────────────────────────────────────── */
.daylist { list-style: none; margin: 0 0 4px; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 4px; }
.daylist a { display: flex; align-items: baseline; gap: 7px;
  border: 1px solid var(--rule-color); padding: 7px 9px;}
.daylist a:hover { border-color: var(--kick-fg); background: var(--depth-bg); }
.daylist b { font-family: var(--mono); font-size: 11px; font-weight: 700; color: var(--fg); }
.daylist span { font-family: var(--mono); font-size: 8px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--counts-fg); }

/* ── the weekly cover: the daily furniture, driven by the week's numbers ───── */
.weekcover { position: relative; aspect-ratio: 1 / 1.35; display: flex; flex-direction: column;
  overflow: hidden; margin-bottom: 16px;
  background: var(--paper-bg); border: 1px solid var(--rule-color); }
.weekcover .mast { padding: 10px 12px 0; }
.weekcover .lines { padding: 1.4dvh 12px 0; }
.weekcover .lead { width: 88%; padding: 0 12px 14px; }
.weekcover .line .t { text-shadow: var(--shadow-m); }
.weekcover .lead .t { font-size: clamp(17px, 2.6vw, 26px); }

.awards { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 8px; margin-bottom: 4px; }
.award { border: 1px solid var(--rule-color); padding: 11px;}
.award .cat { font-family: var(--mono); font-size: 8px; letter-spacing: .24em;
  text-transform: uppercase; color: var(--kick-fg); }
.award h3 { margin: 7px 0 5px; font-family: var(--serif); font-weight: 400;
  font-size: 15px; line-height: 1.2; color: var(--fg); }
/* the switcher says when something is switched on */
.switcher.changed .switcher-btn { background: var(--sw-on-bg); color: var(--sw-on-fg); }

/* ── the loader ─────────────────────────────────────────────────────────────
   CSS only, and there is nothing to dismiss: it sits *underneath* the picture.
   An image or a video is transparent until it has data, so the loader shows
   through; the moment the media paints it covers the loader completely. No
   script, no state, no class to remove, and nothing to leave stuck on screen
   if a file never arrives. */
.loading {
  position: absolute; inset: 0; z-index: 0;
  display: grid; place-content: center; justify-items: center; gap: 12px;
  background: var(--paper-bg);
}
.loading .mark {
  font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: .4em; text-transform: uppercase; color: var(--kick-fg);
}
/* The publisher's mark. It is drawn into the page rather than fetched, so it is
   a span round an svg where it used to be an img — and a span needs to be told
   it has a size. The circle is in the artwork; nothing here rounds anything. */
.mark:has(svg) { display: inline-block; flex: none; line-height: 0; letter-spacing: 0; }
.mark > svg { display: block; width: 100%; height: 100%; }
.loading .bar { width: 150px; height: var(--bar-h); background: var(--bar-track); overflow: hidden; }
.loading .bar i { display: block; width: 38%; height: 100%; background: var(--bar-fill);
  animation: sweep 1.15s cubic-bezier(.65,0,.35,1) infinite; }
@keyframes sweep {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(375%); }
}
@media (prefers-reduced-motion: reduce) {
  .loading .bar i { animation: none; width: 100%; opacity: .5; }
  .loading { transition: none; }
}

/* ── the model: four bands, and what each one costs ────────────────────────── */
.model { border: 1px solid var(--rule-color); padding: 12px 14px; margin-bottom: 14px; }
.model-head { font-family: var(--mono); font-size: 9px; letter-spacing: .26em;
  text-transform: uppercase; color: var(--kick-fg); margin-bottom: 10px; }
.bands { list-style: none; margin: 0; padding: 0; }
.bands li { display: grid; grid-template-columns: 62px 1fr 120px 150px; gap: 10px;
  align-items: baseline; padding: 7px 0; border-bottom: 1px solid var(--rule-color); }
.bands li:last-child { border-bottom: 0; }
.bands b { font-family: var(--mono); font-size: 11px; font-weight: 700; color: var(--kick-fg); }
.bands .what { font-family: var(--serif); font-size: 14px; color: var(--fg); }
.bands .cost, .bands .keep { font-family: var(--mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--counts-fg); }
.bands .keep { color: var(--kick-fg); }
/* the further down the cover, the less of the bid is actually spent */
.bands .band-video, .bands .band-full { opacity: 1; }
@media (max-width: 620px) {
  .bands li { grid-template-columns: 52px 1fr; }
  .bands .cost, .bands .keep { grid-column: 2; }
}

/* ── the auction, which is a magazine with a price on every position ────────
   Same cover, same reel, same list. The only thing a published issue does not
   have is this: what each position is holding, and a way to take it. It is the
   whole reason the page exists, so it is the loudest thing on every story. */
.bidtag { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.bidtag b {
  font-family: var(--mono); font-size: 18px; font-weight: 700;
  letter-spacing: .04em; color: var(--fg); text-shadow: var(--shadow-m);
}
.col.right .bidtag { justify-content: flex-end; }

.btn.raise {
  font-family: var(--mono); font-size: 12px; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase;
  /* The loudest thing on a story wherever it sits. Most skins want their
     accent, which is what the kicker is set in — but a skin whose kicker is a
     quiet grey needs to say so, or the one button that takes money ends up the
     palest thing on the page. */
  background: var(--raise-bg, var(--kick-fg)); color: var(--raise-fg, var(--paper-bg));
  border: 0;
  padding: 11px 22px; cursor: pointer; white-space: nowrap;
}
.btn.raise:hover { filter: brightness(1.25); }
.btn.raise:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* THE COVER STORY'S PRICE AND BUTTON.

   These were set as though the quarter were a poster: twenty-four point on the
   price and a button 127 by 41. On a 900-tall desktop that left NINE pixels of
   slack under the standfirst, which is not a gap — it is a collision waiting
   for a headline one word longer, and it was reported as one three times.

   Smaller, and pinned to the floor with margin-top: auto, so the type above it
   keeps its own room and the button can never march up into it. */
.lead .bidtag { flex: none; margin-top: auto; padding-top: 10px; }
.lead .bidtag b { font-size: 19px; }
.lead .btn.raise { font-size: 12px; padding: 10px 20px; }
/* On a phone that setting is 62px of button in a quarter 122px tall, and it
   printed over the standfirst above it. The 560px rule below could not reach
   it — `.lead .bidtag b` outranks `.bidtag b` — so the reach is here. */
@media (max-height: 760px), (max-width: 560px) {
  .lead .bidtag { padding-top: 8px; gap: 8px; }
  .lead .bidtag b { font-size: 16px; }
  .lead .btn.raise { font-size: 11px; padding: 8px 15px; }
}
/* And a step further on the smallest screens, where a three-line headline and
   a price and a button together still ran past the floor of the quarter. */
@media (max-height: 620px) {
  .lead .t, .lead .t.long { -webkit-line-clamp: 2; }
  .lead .bidtag { padding-top: 6px; }
  .lead .bidtag b { font-size: 14px; }
  .lead .btn.raise { padding: 7px 13px; }
}

/* the split screens have less room, but it still has to be a real target */
.duo .bidtag, .quad .bidtag { margin-top: 8px; gap: 8px; }
.duo .bidtag b { font-size: 15px; }
.quad .bidtag b { font-size: 13px; }
.duo .btn.raise { font-size: 11px; padding: 9px 16px; }
.quad .btn.raise { font-size: 10px; padding: 7px 12px; letter-spacing: .14em; }

@media (max-width: 560px) {
  .bidtag b { font-size: 15px; }
  .btn.raise { padding: 10px 16px; font-size: 11px; }
}

.raisewin { position: absolute; inset: 0; z-index: 26; display: grid; place-items: center;
  padding: 16px; background: rgba(0,0,0,.72); }
.raisewin:empty { display: none; }
.raisewin .raise-form { max-width: 420px; width: 100%; background: var(--paper-bg); margin: 0; }

/* an unsold position, which is still worth showing — it is the offer */
.line.empty { cursor: default; }
.line.empty .t, .ttl.empty { opacity: .55; font-style: italic; }
/* THE COVERLINE'S TAG. Small enough that eight of them in a 168px column read
   as prices under headlines rather than as eight buttons with headlines
   attached. It wraps to two rows before it ever runs out of the column. */
.line .bidtag.line-bid { display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px 6px; margin-top: 5px; max-width: 100%; }
.line .bidtag.line-bid b { font-size: 12px; }
.line .bidtag.line-bid .btn.raise { font-size: 9px; letter-spacing: .1em;
  padding: 4px 8px; width: auto; flex: 0 1 auto; min-width: 0; }
.col.right .bidtag.line-bid { justify-content: flex-end; }

/* THE MARK NEEDS AIR. It was set hard against the first letter of whatever it
   stood in front of — a kicker, a byline, a section. */
.mark.tiny, .mark.small { margin-right: 6px; }
.kick .mark, .byline .mark { margin-right: 6px; }

.bidtag.vacant b { color: var(--kick-fg); }
.bidtag.vacant .btn.raise { background: transparent; color: var(--kick-fg);
  border: 1px solid var(--kick-fg); }
.bidtag.vacant .btn.raise:hover { background: var(--kick-fg); color: var(--paper-bg); filter: none; }

/* ── the auction cover has to fit a price and a button under every coverline ─
   so the coverlines themselves give up a little room for them. */
.cover.selling .line .t { -webkit-line-clamp: 2; }
.cover.selling .line.m .t { font-size: clamp(14px, 2.1dvh, 20px); }
.cover.selling .line .t { font-size: clamp(12px, 1.75dvh, 16px); }
.cover.selling .line.s .t { font-size: clamp(11px, 1.5dvh, 14px); }
.cover.selling .col { gap: .4dvh; }
.cover.selling .line .bidtag { margin-top: 4px; gap: 7px; }
.cover.selling .line .bidtag b { font-size: 13px; }
.cover.selling .line .btn.raise { font-size: 9.5px; padding: 6px 12px; letter-spacing: .14em; }
.cover.selling .lead .t { font-size: clamp(17px, 2.9dvh, 30px); }
.cover.selling .lead .bidtag { margin-top: 10px; }

/* the masthead is the way home, on every page */
.mast-home { display: block; }
.mast-home:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }

/* ── the weekly does not take votes: it reports where a story finished ────── */
.ranktag { position: relative; display: inline-flex; align-items: baseline;
  gap: 7px; flex-wrap: wrap; border: 1px solid var(--rule-color); padding: 7px 12px; }
.ranktag b { font-family: var(--mono); font-size: 17px; font-weight: 700; color: var(--kick-fg); }
.ranktag > span { font-family: var(--mono); font-size: 8px; letter-spacing: .24em;
  text-transform: uppercase; color: var(--counts-fg); }
.ranktag small { font-family: var(--mono); font-size: 9px; letter-spacing: .08em;
  color: var(--counts-fg); }
.quad .ranktag { padding: 5px 8px; gap: 5px; }
.quad .ranktag b { font-size: 13px; }
.quad .ranktag small { display: none; }

/* ── the reveal ─────────────────────────────────────────────────────────────
   The skin is chosen in the head, before anything paints, so the page is never
   drawn in the wrong one. What is left is the ordinary first frame — fonts
   resolving, the plate arriving — and it settles in rather than snapping. */
html.booting .mag, html.booting .desk, html.booting .deskbrand, html.booting .switcher {
  filter: blur(14px); opacity: 0;
}
.mag, .desk, .deskbrand, .switcher {
  filter: blur(0); opacity: 1;
  transition: filter .5s cubic-bezier(.2,0,.2,1), opacity .4s ease;
}
@media (prefers-reduced-motion: reduce) {
  html.booting .mag, html.booting .desk, html.booting .deskbrand, html.booting .switcher {
    filter: none; opacity: 0;
  }
  .mag, .desk, .deskbrand, .switcher { transition: opacity .2s ease; }
}

/* ── picking a coverline ────────────────────────────────────────────────────
   Hovering one headline should say so. The line gets a ground to sit on and a
   rule in the skin's accent, and the others step back — the *text* steps back,
   never the picture behind it. Focus does the same, so a keyboard sees it too. */
.line .t, .lead .t, .lead .sub, .line .k, .lead .k { transition: opacity .18s ease; }

.lines:has(.line:hover) .line:not(:hover) .t,
.lines:has(.line:hover) .line:not(:hover) .k,
.lines:has(.line:focus-visible) .line:not(:focus-visible) .t,
.lines:has(.line:focus-visible) .line:not(:focus-visible) .k { opacity: .3; }

.paper:has(.lines .line:hover) .lead .t,
.paper:has(.lines .line:hover) .lead .sub,
.paper:has(.lines .line:hover) .lead .k { opacity: .3; }
.paper:has(.lead:hover) .lines .line .t,
.paper:has(.lead:hover) .lines .line .k { opacity: .3; }

.line:hover .t, .line:focus-visible .t,
.lead:hover .t, .lead:focus-visible .t {
  background: var(--hover-plate); color: var(--hover-ink);
  box-shadow: -6px 0 0 var(--hover-plate), 6px 0 0 var(--hover-plate);
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  text-shadow: none; text-decoration: none;
}
.line:hover .k, .line:focus-visible .k,
.lead:hover .k, .lead:focus-visible .k { color: var(--hover-rule); opacity: 1; }

/* a rule marking the one you are on */
.line, .lead { position: relative; }
.line::before, .lead::before {
  content: ''; position: absolute; top: 0; bottom: 0; width: 3px;
  background: var(--hover-rule); opacity: 0; transition: opacity .18s ease;
}
.line::before, .lead::before { left: -10px; }
.col.right .line::before, .lead.right::before { left: auto; right: -10px; }
.line:hover::before, .line:focus-visible::before,
.lead:hover::before, .lead:focus-visible::before { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .line .t, .lead .t, .lead .sub, .line .k, .lead .k,
  .line::before, .lead::before { transition: none; }
}
/* a touch screen has no hover, and a sticky :hover state there is worse than none */
@media (hover: none) {
  .lines:has(.line:hover) .line:not(:hover) .t,
  .lines:has(.line:hover) .line:not(:hover) .k { opacity: 1; }
  .line:hover .t, .lead:hover .t { background: none; color: var(--c); box-shadow: none; }
}

/* ── a row in the list ─────────────────────────────────────────────────────
   The same story a third time: what it did, what you can do, and a way in. */
.rowbody { min-width: 0; }
.feedrow .byline { margin: 3px 0 0; font-family: var(--mono); font-size: 8.5px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--kick-fg); }
.rowfoot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
.rowstat { font-family: var(--mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--counts-fg); }
.rowstat b { font-size: 11px; color: var(--fg); }
.rowfoot .more { padding: 5px 10px; font-size: 9px; letter-spacing: .16em; }
.rowfoot .score .vote { padding: 4px 8px; }
.rowfoot .score .n { min-width: 36px; font-size: 11px; }
.rowfoot .ranktag { padding: 4px 9px; }
.rowfoot .ranktag b { font-size: 13px; }
.rowfoot .ranktag small { display: none; }
@media (max-width: 560px) { .rowfoot { gap: 7px; } .rowstat { font-size: 8px; } }

/* ── the encore: the winner again, and where the issue leads next ───────────
   Three quarters to the story that was paid for, one quarter to the week and
   to everything ever published. */
/* Half and half. The winner's second appearance is the top half — free, and
   the same size as the position they paid for, so what they are getting is
   legible rather than a courtesy. The bottom half is where the issue leads on
   to, with the letter under it. */
.screen.on.encore { display: grid; grid-template-rows: 1fr 1fr; }
.encore .story-part { position: relative; overflow: hidden; display: flex; flex-direction: column; }
.encore .story-part > .bg { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%;
  object-fit: cover; filter: var(--art-filter); }
.encore .story-part > .veil { position: absolute; inset: 0; z-index: 2; background: var(--overlay); }
.encore .body { position: relative; z-index: 10; margin-top: auto; padding: 0 14px 3dvh; }
.encore .ttl { font-size: clamp(19px, 3.1dvh, 32px); }

/* min-width as well as min-height, and for the same reason: a grid item also
   defaults to auto, so without it the slot below pushed this — and the whole
   half-screen with it — wider than the magazine. */
.encore-foot { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.promos { flex: 1; min-height: 0;
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--split-rule-color, var(--rule-color)); border-top: var(--split-rule); }

/* The letter runs the full width under both, because it is a field and a
   button and needs the room that two columns would take away from it. */
/* min-width: 0, and it matters. A flex item defaults to min-width: auto, so
   the slot inside was allowed to push this wider than the frame — 736px inside
   a 680px magazine, bleeding out of the page and making the same newsletter a
   different width here than in the other two places it appears. */
.encore-nl { flex: none; min-width: 0; width: 100%;
  display: flex; align-items: center; justify-content: center;
  padding: 4px; background: var(--chrome); border-top: var(--split-rule); }
/* Set at the foot of the block, the way everything else on this site is set
   over a picture. Centred was right when the row was a quarter of the screen;
   in half of one it left two titles floating in the middle of nothing. */
.promo { display: flex; flex-direction: column; justify-content: flex-end; gap: 5px;
  padding: 14px; background: var(--paper-bg); }
.promo:hover { background: var(--depth-bg); }
.promo-k { font-family: var(--mono); font-size: 8px; font-weight: 700; letter-spacing: .26em;
  text-transform: uppercase; color: var(--kick-fg); }
.promo b { font-family: var(--display); font-weight: var(--lead-weight);
  font-size: clamp(17px, 2.6dvh, 26px); letter-spacing: var(--lead-track); color: var(--fg);
  line-height: 1.08; }
.promo-s { font-family: var(--serif); font-style: italic; font-size: 12px; line-height: 1.35;
  color: var(--sub-fg); }
/* It used to be dropped on a narrow screen because the row was a quarter tall
   and there was nowhere to put it. There is now. */
@media (max-height: 560px) { .promo-s { display: none; } }

/* ── the foot of the page ───────────────────────────────────────────────────
   Taller than a status strip, because it now carries the ticket count, who is
   reading, and the two things you can do with the page itself. */
.statbar { min-height: var(--statbar-h); align-items: stretch; padding: 3px; gap: 3px;
  overflow: hidden; }
/* The burger and the two actions are pinned; the figures between them scroll.
   Nothing is dropped and nothing is cut in half — if the frame is too narrow
   for every figure you push them sideways and they are all still there. */
.statbar-mid {
  flex: 1 1 auto; min-width: 0; display: flex; gap: 3px;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  overscroll-behavior-x: contain; scroll-snap-type: x proximity;
}
.statbar-mid::-webkit-scrollbar { display: none; }
.statbar-mid > * { scroll-snap-align: start; }
.statbar > .burger, .statbar > .act { flex: none; }
/* a hint that there is more, on whichever side there is more of it */
.statbar-mid { mask-image: linear-gradient(90deg, transparent 0, #000 6px,
  #000 calc(100% - 6px), transparent 100%); }
.statbar .cell { flex: none; display: flex; align-items: center; gap: 0; padding: 0 10px;
  white-space: nowrap; }
.statbar .cell b { font-size: 11px; }
/* Icons, not words — but they are the two things you can do with the page, so
   they carry the accent rather than sitting in the chrome's own grey where
   nobody finds them. */
.statbar .act {
  display: grid; place-items: center; width: 46px; padding: 0;
  border: var(--cell-border); background: var(--chrome); color: var(--cell-fg);
  cursor: pointer;
}
.statbar .act svg { width: 22px; height: 22px; display: block; }
.statbar .act + .act { margin-left: 1px; }
.statbar .act:hover { background: var(--menu-hover-bg); color: var(--menu-hover-fg); }
.statbar .act:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
@media (max-width: 560px) { .statbar .act { width: 40px; } .statbar .act svg { width: 19px; height: 19px; } }
.statbar .act:hover { background: var(--menu-hover-bg); color: var(--menu-hover-fg); }
.statbar .tickets { cursor: pointer; }
.statbar .tickets:hover { background: var(--menu-hover-bg); color: var(--menu-hover-fg); }
.statbar .tickets b { color: var(--led); }
.statbar .tickets:hover b { color: inherit; }
.statbar .live .dot {
  width: 6px; height: 6px; margin-right: 7px; background: var(--live-dot);
  animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .statbar .live .dot { animation: none; } }
/* the bar drops what it cannot fit rather than squeezing every cell until the
   words are cut in half */
/* below these the figures are dropped outright rather than left half-cut */
/* READING NOW STAYS. Two more rules hid it — one on the container, one on the
   viewport — so on a phone the only number on the page that is actually live
   was the one that never showed. The strip scrolls; nothing has to be dropped
   to make room. */
@media (max-width: 640px) {
  .statbar .cell { padding: 0 7px; font-size: 8.5px; }
}

/* ── the drawer ─────────────────────────────────────────────────────────────
   Share and comment come up from the foot rather than opening a page. */
/* HALF THE PAGE, over everything below it — the ad row, the statistics bar and
   the menu included. At 62% of the stage it stopped above the chrome, which
   made it look bolted onto the furniture instead of drawn over the page. */
.drawer {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 55;
  height: 50%; display: flex; flex-direction: column;
  background: var(--paper-bg); border-top: var(--drawer-rule);
  box-shadow: 0 -18px 40px rgba(0,0,0,.35);
  /* One property moves: the sheet comes up from the floor and goes back down.
     No fade, no scale, nothing behind it dimming. */
  /* THE SHEET STARTS ON THE FLOOR and is brought up by a class. It used to
     rely on @starting-style to animate out of display:none, which Safari only
     learned in 17.5 — so on a phone it did not slide, it appeared. One class,
     one property, and every browser can do it. */
  transform: translateY(100%); transition: transform .3s cubic-bezier(.2,0,.2,1);
  will-change: transform;
}
.drawer.open { transform: translateY(0); }
.drawer[hidden] { display: none; }
.drawer-bar {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  padding: 11px 14px; border-bottom: 1px solid var(--rule-color);
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: .26em; text-transform: uppercase; color: var(--kick-fg);
}
.drawer-x { font-size: 15px; line-height: 1; color: var(--fg); cursor: pointer; }
.drawer-body { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 14px 24px; }
.drawer-body .note { max-width: 52ch; }
/* THE SLIDE IS THE DRAWER. Killing the transition under reduced motion made it
   appear from nowhere on any phone with battery saver on — the fifth thing that
   rule was switching off, after the marquee, the story bar, the heart and the
   sense that anything was moving at all. It slides, faster. */
@media (prefers-reduced-motion: reduce) { .drawer { transition-duration: .12s; } }

/* The one way out of the encore that is not one of its four promos. */
.tolist {
  flex: none;
  position: relative; z-index: 14; display: flex; align-items: center;
  justify-content: space-between; gap: 10px;
  padding: 9px 12px; text-decoration: none; border-bottom: var(--split-rule);
  background: var(--raise-bg, var(--kick-fg)); color: var(--raise-fg, var(--paper-bg));
}
.tolist-t { font-family: var(--sans); font-size: 12px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; }
.tolist-a { font-size: 15px; line-height: 1; animation: va-nudge 2.4s ease-in-out infinite; }
.tolist:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .tolist-a { animation: none; } }

/* BELOW THE FOLD. ONE LINE EACH.

   It was the published row's layout with the pictures taken out, which stacks
   a title over a byline over a footer — three rows of furniture for a position
   that did not run. These are a waiting list: a number, who, and what they are
   holding, on one line, quiet, with no accent colour on them. The magazine is
   above; this is the queue.

   Nothing is said about what happens to the money here. The rules page is
   where that belongs, not twenty-five repetitions of it down a list. */
/* `.bt`, not `.ttl` — the third class collision in this file. A story screen's
   `.ttl` is set at clamp(…, 3dvh, …), which came out at 24px inside a row 40px
   tall. Every size here is stated, and none of it is inherited from a screen. */
.belowrow {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 10px; border-bottom: 1px solid var(--rule-faint, rgba(128,128,128,.18));
  font-family: var(--sans); font-size: 11px; line-height: 1.25; color: var(--sub-fg);
}
.belowrow .no { flex: none; width: 20px; font-family: var(--mono); font-size: 10px;
  font-weight: 700; color: var(--sub-fg); opacity: .7; }
.belowrow .mark { flex: none; width: 12px; height: 12px; }
.belowrow .bt { flex: 1 1 auto; min-width: 0; font-size: 11.5px; font-weight: 500;
  line-height: 1.25; color: var(--sub-fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* `.by`, not `.who` — the weekly's ladder already owns `.who` and its rule
   threw this one 2,779 pixels off the top of the row. */
.belowrow .by { flex: none; font-family: var(--mono); font-size: 9px;
  letter-spacing: .06em; opacity: .6; }
.belowrow .held { flex: none; font-family: var(--mono); font-size: 11px; font-weight: 700;
  color: var(--counts-strong, currentColor); }
.belowrow .btn.raise { flex: none; font-size: 8.5px; padding: 3px 8px; letter-spacing: .1em; }
@media (max-width: 480px) { .belowrow .by { display: none; } }

/* ── the heart ──────────────────────────────────────────────────────────────
   Tap it as often as you like. Each tap sends one up and out. */
/* the corner of the story, and nothing but the icon */
.heart {
  position: absolute; right: 12px; bottom: 12px; z-index: 12; cursor: pointer;
  display: grid; justify-items: center; gap: 3px;
  border: 0; background: none; color: var(--heart-fg);
  font-family: var(--mono); font-size: 10px; font-weight: 700; letter-spacing: .06em;
  filter: drop-shadow(0 1px 4px rgba(0,0,0,.75));
}
.heart svg { width: 30px; height: 30px; display: block; }
.heart:active svg { transform: scale(.88); }
.heart svg { transition: transform .12s ease; }
.rowfoot .heart { position: static; flex-direction: row; }
.pop {
  position: absolute; left: 50%; bottom: 100%; pointer-events: none;
  width: 26px; color: var(--heart-fg);
  animation: rise 1.9s cubic-bezier(.25,.6,.4,1) forwards;
}
.pop svg { width: 100%; height: auto; display: block; }
/* all the way up the screen, and out */
@keyframes rise {
  from { opacity: 1; transform: translate(-50%, 0) scale(.7); }
  15%  { opacity: 1; }
  to   { opacity: 0; transform: translate(calc(-50% + var(--drift, 0px)), -78dvh) scale(1.9); }
}
/* THE FOURTH THING prefers-reduced-motion was switching off, after the
   marquee, the story bar and — via those — the sense that the page was alive
   at all. `display: none` meant a phone with battery saver on gave no answer
   when you pressed the heart: no hearts, nothing. It is the whole feedback of
   the button. It rises faster now, and it still rises. */
@media (prefers-reduced-motion: reduce) { .pop { animation-duration: .5s; } }



/* ── the LED screen ─────────────────────────────────────────────────────────
   Every picture and every video is shown as if it were on a panel: a fine dot
   grid laid over the media and blended with `screen`, which only ever adds
   light. Nothing is darkened, which is the point — the picture is what was
   paid for. Pitch, colour and strength are skin tokens, so a skin can make it
   coarse and loud or almost invisible.

   Pseudo-elements, so it costs no markup at all. */
.cover::after,
.screen.story::after,
.encore .story-part::after,
.duo .half::after,
.quad .cell::after {
  content: ''; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  /* Two grids, one light and one dark, offset by half a step. A single light
     grid disappears the moment the picture behind it is bright; a pair reads on
     anything, and because the two cancel each other the picture is no darker
     overall than it was. That is the whole trick. */
  background-image:
    radial-gradient(var(--led-hi) 1px, transparent 1.2px),
    radial-gradient(var(--led-lo) 1px, transparent 1.2px);
  background-position: 0 0, calc(var(--led-size) / 2) calc(var(--led-size) / 2);
  background-size: var(--led-size) var(--led-size);
  opacity: calc(var(--led-opacity) * var(--led-scale, 1));
  mix-blend-mode: var(--led-blend);
}
/* a screen made of two or four panels gets a finer pitch, or it reads as noise */
.duo .half::after, .quad .cell::after {
  background-size: var(--led-size-sm) var(--led-size-sm);
  background-position: 0 0, calc(var(--led-size-sm) / 2) calc(var(--led-size-sm) / 2);
}

/* ── the publisher's mark ───────────────────────────────────────────────────
   The one round thing on the page. Everything else here is square on purpose,
   but a mark of identity is a circle everywhere on the internet and reads wrong
   as anything else. Same file at every size. */
.mark {
  display: inline-block; vertical-align: -3px; margin-right: 6px;
  width: 18px; height: 18px; border-radius: 50%;
  box-shadow: 0 0 0 1px var(--mark-ring, rgba(0,0,0,.25));
}
.mark.tiny { width: 12px; height: 12px; vertical-align: -2px; margin-right: 5px; }
.mark.small { width: 15px; height: 15px; vertical-align: -3px; }
.quad .mark { width: 13px; height: 13px; vertical-align: -2px; margin-right: 4px; }
/* #01 is the position everyone paid for, so its mark is a real one — a block
   of its own above the kicker rather than a favicon wedged into a line of type,
   and it sits on whichever edge the cover story is set against. */
.mark.lead-mark {
  display: block; width: 46px; height: 46px; margin: 0 0 10px;
  vertical-align: baseline;
  box-shadow: 0 0 0 2px var(--mark-ring, rgba(0,0,0,.25)), 0 2px 14px rgba(0,0,0,.55);
}
.lead.right .mark.lead-mark { margin-left: auto; }
@media (max-width: 560px) { .mark.lead-mark { width: 36px; height: 36px; margin-bottom: 8px; } }


/* ── the sheet ───────────────────────────────────────────────────────────────
   The one screen that asks for something back. It is raised by an upvote and
   never by a heart: an upvote is a ticket in the draw and a ticket has to have
   somewhere to go, while a heart costs nothing and wins nothing.

   It lives inside the frame, over the stage, so it never covers the chrome. */
.sheet {
  position: absolute; inset: 0; z-index: 70;
  display: flex; align-items: center; justify-content: center; padding: 20px;
  background: var(--sheet-veil, rgba(0,0,0,.72));
  animation: sheet-in .2s ease both;
}
.sheet[hidden] { display: none; }
.sheet.closing { animation: sheet-out .22s ease both; }
@keyframes sheet-in  { from { opacity: 0 } to { opacity: 1 } }
@keyframes sheet-out { to { opacity: 0 } }

.sheet-in {
  position: relative; width: min(400px, 100%); max-height: 100%; overflow-y: auto;
  padding: 26px 24px 22px;
  background: var(--paper-bg); color: var(--fg); border: var(--sheet-border, 2px solid var(--fg));
  animation: sheet-rise .26s cubic-bezier(.2,0,.2,1) both;
}
@keyframes sheet-rise { from { transform: translateY(14px) } to { transform: none } }
.sheet-x {
  position: absolute; top: 8px; right: 10px;
  font-size: 15px; line-height: 1; color: var(--fg); cursor: pointer; padding: 4px;
}
.sheet-k {
  font-family: var(--mono); font-size: 9px; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--counts-fg); margin: 0 0 10px;
}
.sheet-k b { color: var(--accent, var(--fg)); }
.sheet-h { font-family: var(--display, var(--serif)); font-size: 27px; line-height: 1.05;
  margin: 0 0 10px; letter-spacing: -.01em; }
.sheet-p { font-size: 13px; line-height: 1.5; margin: 0 0 16px; max-width: 40ch; }
.sheet-fine { font-family: var(--mono); font-size: 9px; line-height: 1.6;
  color: var(--counts-fg); margin: 12px 0 0; }

.sheet-form { display: flex; gap: 6px; flex-wrap: wrap; }
.sheet-form input {
  flex: 1 1 180px; min-width: 0; padding: 11px 12px;
  font-family: var(--mono); font-size: 12px;
  color: var(--fg); background: var(--field-bg, transparent);
  border: var(--more-border); outline-offset: -2px;
}
.sheet-form input::placeholder { color: var(--counts-fg); }
.sheet-form .btn {
  flex: none; cursor: pointer; padding: 11px 15px;
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: var(--more-track); text-transform: uppercase; line-height: 1;
  background: var(--nl-btn-bg); color: var(--nl-btn-fg); border: 0;
}
.sheet-form .btn:hover { filter: brightness(1.15); }
.sheet-form .btn:focus-visible, .sheet-form input:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px;
}
.sheet-form.busy { opacity: .55; pointer-events: none; }
.sheet-alt { font-family: var(--mono); font-size: 10px; color: var(--counts-fg); margin: 14px 0 0; }
.lnk { cursor: pointer; color: var(--fg); text-decoration: underline;
  text-underline-offset: 3px; font: inherit; }
.sheet-err {
  margin: 14px 0 0; padding: 9px 11px;
  font-family: var(--mono); font-size: 10px; line-height: 1.5;
  color: var(--pending-fg); background: var(--pending-bg);
}
.sheet-err[hidden] { display: none; }

@media (max-width: 460px) {
  .sheet { padding: 12px; }
  .sheet-in { padding: 22px 18px 18px; }
  .sheet-h { font-size: 23px; }
}

/* ── signed in, or not ───────────────────────────────────────────────────────
   Set by the head script before the first paint, so neither state ever flashes.
   The markup is identical either way — that is the whole point. */
[data-auth] [data-anon] { display: none; }
html:not([data-auth]) [data-known] { display: none; }

/* ── where the magic link lands ────────────────────────────────────────────
   A page of its own, because it is the one address that arrives from outside.
   Still a static file: the token is in the query string and the exchange runs
   in the browser, so nothing here needed a server. */
.plainpage { min-height: 100dvh; display: flex; background: var(--desk-bg, var(--bg)); }
.enter { flex: 1; display: flex; align-items: center; justify-content: center; padding: 24px; }
.enter-in {
  width: min(420px, 100%); padding: 30px 26px 24px;
  background: var(--paper-bg); color: var(--fg); border: var(--sheet-border, 2px solid var(--fg));
}
.enter-code {
  display: flex; flex-direction: column; gap: 5px; margin: 18px 0 0; padding: 12px 13px;
  border: var(--more-border);
}
.enter-code[hidden] { display: none; }
.enter-code span { font-family: var(--mono); font-size: 9px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--counts-fg); }
.enter-code b { font-family: var(--mono); font-size: 17px; letter-spacing: .08em; }

/* ── the screen stack ────────────────────────────────────────────────────────
   Screens come in over the magazine from the right and go back out the same
   way. The frame, the marquee and the statistics bar never move — what changes
   is what is on top of them. That is the difference between a stack and a site.

   Above everything (the rail is 60, the sheet 70) because a screen is the
   thing being looked at while it is up. */
.stack { position: absolute; inset: 0; z-index: 80; pointer-events: none; }
.stack[hidden] { display: none; }

.screenpanel {
  position: absolute; inset: 0; pointer-events: auto;
  display: flex; flex-direction: column;
  background: var(--paper-bg); color: var(--fg);
  transform: translateX(0); opacity: 1;
  transition: transform .3s cubic-bezier(.2,0,.2,1), opacity .3s ease;
}
.screenpanel.entering, .screenpanel.leaving { transform: translateX(100%); opacity: .4; }

.panelbar {
  flex: none; display: flex; align-items: center; gap: 8px;
  padding: 9px 10px; border-bottom: 1px solid var(--rule-color);
  background: var(--paper-bg);
}
.panelback, .panelx {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; cursor: pointer; color: var(--fg);
}
.panelback svg { width: 20px; height: 20px; }
.panelx { font-size: 14px; line-height: 1; }
.paneltitle {
  flex: 1; min-width: 0; text-align: center;
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.panelbody { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }

/* ── a screen that is a form ───────────────────────────────────────────────── */
.screenform, .screendone { padding: 18px 16px 32px; }
.screen-lead { font-size: 13px; line-height: 1.55; margin: 0 0 20px; max-width: 46ch;
  color: var(--fg); }
.screen-lead b { font-family: var(--mono); }

.fld { display: block; margin: 0 0 18px; }
.fld-k {
  display: block; margin: 0 0 6px;
  font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--counts-fg);
}
.fld-k i { font-style: normal; color: var(--accent, var(--fg)); margin-left: 4px; }
.fld-k i.tier {
  font-family: var(--mono); font-size: 8px; letter-spacing: .1em;
  color: var(--counts-fg); margin-left: 6px; text-transform: uppercase;
}
.fld input, .fld textarea, .screenform input, .screenform textarea {
  display: block; width: 100%; padding: 11px 12px;
  font-family: var(--mono); font-size: 12px; line-height: 1.45;
  color: var(--fg); background: var(--field-bg, transparent);
  border: var(--more-border); outline-offset: -2px; resize: vertical;
}
.fld input::placeholder, .fld textarea::placeholder { color: var(--counts-fg); }
.fld input:focus-visible, .fld textarea:focus-visible { outline: 2px solid var(--focus); }
.fld-h { display: block; margin: 6px 0 0; font-family: var(--mono);
  font-size: 9px; line-height: 1.6; color: var(--counts-fg); }
.fld.locked input { opacity: .45; cursor: not-allowed; }

/* Categories as squares you can see at once, rather than a wheel. */
.cats { border: 0; padding: 0; margin: 0 0 20px; }
.catgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 6px; }
.cat { position: relative; display: block; padding: 10px 11px; cursor: pointer;
  border: var(--more-border); }
.cat input { position: absolute; opacity: 0; pointer-events: none; }
.cat-l { display: block; font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; }
.cat-b { display: block; margin-top: 3px; font-family: var(--mono); font-size: 8.5px;
  line-height: 1.5; color: var(--counts-fg); }
.cat:hover { background: var(--more-bg-hover); }
.cat:has(input:checked) { background: var(--voted-bg); color: var(--voted-fg); }
.cat:has(input:checked) .cat-b { color: inherit; opacity: .75; }
.cat:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }

.screenfoot { margin-top: 24px; padding-top: 18px; border-top: 1px solid var(--rule-color); }
.screenfoot .btn.go {
  display: block; width: 100%; padding: 14px; cursor: pointer; margin-bottom: 10px;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: var(--more-track); text-transform: uppercase;
  background: var(--nl-btn-bg); color: var(--nl-btn-fg); border: 0;
}
.screenform.busy { opacity: .55; pointer-events: none; }

/* ── what you are given afterwards ─────────────────────────────────────────── */
.screendone .enter-code { margin: 18px 0; }
.screendone .enter-code b { font-size: 11px; letter-spacing: .02em; word-break: break-all;
  line-height: 1.5; }
.keyacts { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.keyacts .btn.go {
  padding: 11px 15px; cursor: pointer; border: 0;
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  letter-spacing: var(--more-track); text-transform: uppercase;
  background: var(--nl-btn-bg); color: var(--nl-btn-fg);
}
.sheet-form.keeper { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--rule-color); }


/* ── the ad row belongs to the cover ─────────────────────────────────────────
   A story is a full screen, and a banner across the foot of one is somebody
   else's magazine. So the slot is up on the cover and gone everywhere else —
   the reel, All News, a menu page, a pushed screen. `data-cover` is set on
   <html> by whatever changed what is showing.

   The row goes entirely rather than being emptied. An empty band between the
   story and the statistics bar is worse than either — it reads as something
   that failed to load. The statistics bar does move up by the height of it,
   and that is fine: the move follows a click, which is the one kind of shift
   that is a transition rather than instability. */
html:not([data-cover]) .adbar { display: none; }
/* Except on the last screen. The issue is over there — the winner shown once
   more, where to go next, the letter — so the chrome comes back and the row
   below it is an advertisement again. It is the one story screen where an ad is
   not interrupting anything. */
html:has(.encore.on) .adbar { display: flex; }
/* And on the two breaks. They are our pages, not the day's — the slot belongs
   at the foot of them the same way it belongs at the foot of the cover, and
   because the letter rides in its rotation neither the break nor the back cover
   has to carry one of its own. */
html:has(.moment.on) .adbar { display: flex; }
/* And on the list, which is a page rather than a screen — the same chrome the
   cover has. */
html:has(.allnews:not([hidden])) .adbar { display: flex; }

/* ── the newsletter, which is the menu's and nothing else's ──────────────────
   It used to be one of the ads on a rotation. It is not an ad. It comes in
   with the menu, sits in the ad row — empty on a story, worth interrupting on
   the cover — and leaves when the menu does. */
.nlpop {
  position: absolute; left: 0; right: 0; bottom: var(--statbar-h, 40px); z-index: 62;
  display: flex; align-items: center; justify-content: center;
  padding: 4px; background: var(--chrome); border-top: var(--chrome-rule);
  animation: nlpop-in .26s cubic-bezier(.2,0,.2,1) both;
}
.nlpop[hidden] { display: none; }
.nlpop.going { animation: nlpop-out .24s ease both; }
@keyframes nlpop-in  { from { transform: translateY(100%); opacity: 0 } }
@keyframes nlpop-out { to   { transform: translateY(100%); opacity: 0 } }
@media (prefers-reduced-motion: reduce) { .nlpop, .nlpop.going { animation: none; } }

/* Both of these are the ad slot's box, exactly: same width, same height, same
   type inside. Somebody who has seen it on the cover should not have to work
   out that this is the same thing. */
/* The letter is the same object in the same box — one slot, filled, so the two
   things that ever appear in that row are the same size and neither of them
   floats. */
:is(.nlpop, .encore-nl) .subscribe {
  width: 100%; height: var(--slot-h); padding: 0 12px;
}
/* Same geometry as the ad row it stands in for: a label's width of chrome on
   the left, then the slot. */

/* The letter, once it has been left somewhere. */
.subscribe .lbl.done { max-width: none; font-style: normal; }
.subscribe input[aria-invalid="true"] { outline: 2px solid var(--focus); }

/* ── /post.html ──────────────────────────────────────────────────────────────
   A page rather than a screen, because a post carries a picture and a video
   and an upload wants somewhere to navigate to. It borrows the panel's bar so
   it still reads as part of the magazine rather than a form somewhere else. */
.postpage .mag { display: flex; flex-direction: column; }
.postpage .panelbar { flex: none; }
.postpage .panelbody { flex: 1; min-height: 0; overflow-y: auto; }
.postpage .panelback, .postpage .panelx { text-decoration: none; }

/* What a coupon came back with. Green is not in any skin here, so acceptance
   reads as the skin's own accent rather than a colour borrowed from nowhere. */
.sheet-err.ok { color: var(--voted-fg); background: var(--voted-bg); }

/* Shut until a coupon opens it. */
.fld.locked input { opacity: .45; cursor: not-allowed; }
.fld:not(.locked) .fld-k i.tier { color: var(--accent, var(--fg)); }


/* ── scrollbars ──────────────────────────────────────────────────────────────
   One treatment, everywhere. There were two — All News and the menu pages —
   and everything added since had whatever the browser felt like: the drawer,
   the sheet, the post page, the page itself. A scrollbar is chrome, and chrome
   that changes between two panes of the same frame is worse than no styling at
   all, because it looks like a mistake rather than a default.

   Square, like everything else. The track shows through a two-pixel border on
   the thumb rather than a radius, which is what gives it room without rounding
   anything. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--scroll-thumb) var(--scroll-track, transparent);
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--scroll-track, transparent); }
::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border: 2px solid var(--scroll-track, transparent);
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover, var(--scroll-thumb)); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: var(--scroll-track, transparent); }

/* The two that are deliberately invisible stay that way: both are horizontal
   strips of chrome where a bar would sit on top of the content. */
.menu, .statbar-mid { scrollbar-width: none; }
.menu::-webkit-scrollbar, .statbar-mid::-webkit-scrollbar { display: none; }

/* ── the live moment ─────────────────────────────────────────────────────────
   The screen that breaks the reel after the fourth story. Not a story — every-
   thing the site is that this issue is not.

   Four layouts, one picked at random per load. The PROPORTIONS are the same in
   all four — all time takes half the screen, the week a quarter, the rest share
   what is left — so what matters never moves even though the furniture does. */
/* No `position` here. `.screen` is already absolute with inset:0 — declaring
   `relative` took that away, the screen collapsed to the height of its content,
   and its only child is absolutely positioned, so the whole thing measured
   zero and painted nothing. */
.screen.on.moment { display: block; padding: 0; }
/* Hugging the edges, and over the tiles rather than under them — the mosaic is
   inside the grid, which paints after. */
/* Wide enough for a thumb. They were twenty-six pixels — technically present
   and impossible to hit — so this screen was the one place in the reel you
   could not move forward or back by tapping. */
/* Over the mosaic, not beside it. Narrow enough that the tiles are still
   usable, wide enough for a thumb. */
.moment .tap { width: 46px; z-index: 41; }
.moment .tap.next { width: 46px; }
/* Clear the rail and the close button at the top — a tile that starts at y=0
   sits under both of them. */
/* Pinned, not sized in per cent. A percentage height needs a parent with a
   definite one, and a screen inside the deck is sized by its content — so the
   grid only filled as much as its tiles asked for and left the rest of the
   screen empty. */
/* Edge to edge. It was inset 62px on each side to leave room for the tap zones
   and it read as a card floating in the middle of a screen — the tap zones can
   sit ON it instead, which is what they do everywhere else in the reel. */
.moment .m-grid {
  position: absolute; z-index: 20;
  top: calc(var(--rail-h) + 30px); left: 0; right: 0; bottom: 0;
  height: auto; width: auto;
}

/* This screen has six things on it, not one headline, so it is given longer
   before the reel moves on. */
html:has(.screen.on.moment) .rail .bar.live i { animation-duration: var(--dwell-long, 26s); }
/* THE MOVING COVER RUNS FOR THIRTY SECONDS. Fifteen is what a picture gets. */
html:has(.screen.on[data-hold="30"]) .rail .bar.live i,
html:has(.screen.on[data-hold="30"]) .loading .bar i { animation-duration: var(--dwell-video, 30s); }
.moment .m-grid {
  display: grid; gap: 1px;
  grid-template-columns: repeat(4, 1fr);
  background: var(--split-rule-color, var(--rule-color));
}
/* space-between, so a tile is used rather than occupied: the eyebrow rides the
   top of its box and the substance sits on the floor of it. Centred or
   bottom-pinned, every tile had a band of nothing in it and the screen read as
   five small cards adrift on a large one. */
.m-tile {
  min-width: 0; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column; justify-content: space-between; gap: 5px;
  padding: 14px 16px; background: var(--paper-bg);
}
/* Only the big one is set at the foot of its block, the way a story is over a
   picture. The small tiles are centred: bottom-pinning one line inside a tall
   empty box reads as a gap rather than as a decision. */


/* Five tiles, four arrangements, and the arrangements are not free — each tile
   declares its span in story.mjs (MOMENT_SPANS) and every layout here honours
   it. The rule that costs something: a `wide` tile gets a ROW OF ITS OWN, full
   width and one row tall, in all four. The letter is one, because a field and a
   button need the run — boxed into a quarter they wrap and stop reading as a
   form at all.

/* Six boxes: one big and five small, laid out like a board rather than a page.
   Which arrangement, and which panel gets the big box, differ between the two
   breaks and again per build — the pair is never the same twice in a reading. */
/* The stat rows are sized to their contents and the big box takes what is left.
   Six equal rows gave every number a hundred and fifty pixels to sit in and
   each one used a third of it — the boxes were not too empty, the rows were too
   tall. */
.moment[data-mosaic="1"] .m-grid {
  grid-template-rows: 1fr auto auto auto;
  grid-template-areas: "h h h h" "b b l l" "v v n n" "o o o o"; }
.moment[data-mosaic="2"] .m-grid {
  grid-template-rows: auto auto auto 1fr;
  grid-template-areas: "b b l l" "v v n n" "o o o o" "h h h h"; }
.moment[data-mosaic="3"] .m-grid {
  grid-template-rows: auto auto auto 1fr;
  grid-template-areas: "h h b b" "h h l l" "h h v v" "h h n n"; }
.moment[data-mosaic="4"] .m-grid {
  grid-template-rows: auto auto auto 1fr;
  grid-template-areas: "b b h h" "l l h h" "v v h h" "n n h h"; }
/* Two of the four put the big box in a column, and there the fifth stat joins
   the stack rather than taking a band of its own. */
.moment[data-mosaic="3"] .m-open, .moment[data-mosaic="4"] .m-open { display: none; }
.m-hero { grid-area: h; } .m-buyer { grid-area: b; } .m-live { grid-area: l; }
.m-visitors { grid-area: v; } .m-letters { grid-area: n; } .m-open { grid-area: o; }

/* A stat box reads as one thing, so its three parts sit together in the middle
   of the box rather than being pushed to its corners. space-between is right for
   a tile with a headline in it and wrong for a tile with a number in it — spread
   across 180 pixels the label, the figure and the note stop looking related. */
.m-tile.stat { justify-content: center; gap: 3px; }
.m-tile.stat .m-k { opacity: .85; }
.m-tile.stat .m-s2 { line-height: 1.3; }
.m-fig { font-family: var(--mono); font-size: clamp(20px, 3.4dvh, 34px); font-weight: 700;
  line-height: 1; color: var(--fg); letter-spacing: -.02em; }
.m-tile.big { justify-content: space-between; }
.m-tile.big .m-h { font-size: clamp(19px, 3.2dvh, 30px); margin-top: auto; }

.m-tile.link a { display: flex; flex-direction: column; gap: 4px; height: 100%;
  justify-content: space-between; text-decoration: none; }
.m-link { font-family: var(--display); font-weight: var(--lead-weight);
  font-size: clamp(14px, 2.1dvh, 20px); letter-spacing: var(--lead-track); color: var(--fg); }
.m-tile.link:hover { background: var(--depth-bg); }

.m-k { font-family: var(--mono); font-size: 8.5px; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; color: var(--kick-fg); }
.m-h { font-family: var(--display); font-weight: var(--lead-weight);
  font-size: clamp(20px, 4.4dvh, 40px); line-height: 1.02;
  letter-spacing: var(--lead-track); color: var(--fg); margin: 0; }
.m-h2 { font-family: var(--display); font-weight: var(--lead-weight);
  font-size: clamp(14px, 2.2dvh, 21px); line-height: 1.08;
  letter-spacing: var(--lead-track); color: var(--fg); margin: 0; }
.m-s { font-family: var(--serif); font-style: italic; font-size: 12.5px;
  line-height: 1.35; color: var(--sub-fg); margin: 0; }
.m-s2 { font-family: var(--mono); font-size: 9px; color: var(--counts-fg); line-height: 1.4; }
.m-by { font-family: var(--mono); font-size: 9px; color: var(--counts-fg); }
.m-none { font-family: var(--serif); font-style: italic; font-size: 12px;
  line-height: 1.35; color: var(--sub-fg); }
.m-none a { color: var(--fg); }
.m-go { font-family: var(--mono); font-size: 8.5px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--fg);
  text-decoration: underline; text-underline-offset: 3px; }
.m-money { font-family: var(--mono); font-size: clamp(19px, 3.2dvh, 30px);
  font-weight: 700; line-height: 1; color: var(--fg); }
.m-live { font-family: var(--mono); font-size: clamp(17px, 2.8dvh, 26px);
  font-weight: 700; line-height: 1; color: var(--fg); }

/* the last bid is the one thing here that is happening right now, so it is the
   one thing that gets a ground of its own */
.m-tile.push { background: var(--depth-bg); }
/* A tile with nothing in it centres what little it has, rather than pinning one
   line to the floor and leaving a void above it. */

.m-figs { font-family: var(--mono); font-size: 9.5px; color: var(--counts-fg); }
.m-figs b { color: var(--fg); }

.m-btn {
  display: block; text-align: center; padding: 9px 8px; margin-top: auto;
  border: var(--more-border); background: var(--more-bg); color: var(--more-fg);
  font-family: var(--mono); font-size: 9px; font-weight: 700;
  letter-spacing: var(--more-track); text-transform: uppercase;
}
.m-btn:hover { background: var(--more-bg-hover); color: var(--more-fg-hover); }
.m-btn.ghost { border-style: dashed; }
.m-letter { justify-content: stretch; padding: 0; }
.m-letter .subscribe { flex: 1; min-width: 0; height: auto; padding: 10px 12px;
  border: 0; gap: 6px; }
.m-letter .subscribe input { min-width: 0; }
.m-letter .subscribe button { flex: none; white-space: nowrap; }
.m-letter .subscribe .lbl { max-width: 96px; font-size: 8.5px; }

/* Narrow: four columns is too many for a phone, so the same six tiles run in
   two — and the mosaics still differ, they just differ vertically. */
@media (max-width: 560px) {
  .moment .m-grid { grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(6, 1fr); }
  /* Same rule at two columns: the wide tile keeps a whole row to itself. */
  .moment .m-grid { grid-template-columns: repeat(2, 1fr); }
  .moment[data-mosaic="1"] .m-grid { grid-template-rows: 1fr auto auto auto;
    grid-template-areas: "h h" "b l" "v n" "o o"; }
  .moment[data-mosaic="2"] .m-grid { grid-template-rows: auto auto auto 1fr;
    grid-template-areas: "b l" "v n" "o o" "h h"; }
  .moment[data-mosaic="3"] .m-grid { grid-template-rows: 1fr auto auto auto;
    grid-template-areas: "h h" "b l" "o o" "v n"; }
  .moment[data-mosaic="4"] .m-grid { grid-template-rows: auto auto 1fr auto;
    grid-template-areas: "b l" "v n" "h h" "o o"; }
  .moment[data-mosaic="3"] .m-open, .moment[data-mosaic="4"] .m-open { display: flex; }
  .m-tile { padding: 8px 10px; gap: 3px; }
  .m-letter .subscribe .lbl { display: none; }
}

/* ── the encore's four ─────────────────────────────────────────────────────── */
.promos.four { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.promos.four .promo { padding: 10px 12px; gap: 3px; }
.promos.four .promo b { font-size: clamp(14px, 2dvh, 19px); }
.promos.four .promo-s { font-size: 10.5px; }
.promo.push { background: var(--depth-bg); }
@media (max-height: 620px) { .promos.four .promo-s { display: none; } }


/* An open position is quieter than a bought one, not invisible. It was pale
   enough that a board with twenty-two of them read as a grey page. */
.line.empty .t { opacity: .82; }
.line.empty .k { opacity: .9; }


/* ── the grid over the artwork, turned down or off ───────────────────────────
   Every skin sets its own --led-opacity; this scales it. A multiplier rather
   than a replacement, so a skin that wants a heavier grain than another still
   gets one at every setting — the control changes the amount, never the
   relationship between the skins. */
:root { --led-scale: 1; }
html[data-led="off"]    { --led-scale: 0; }
html[data-led="faint"]  { --led-scale: .45; }
html[data-led="normal"] { --led-scale: 1; }
html[data-led="strong"] { --led-scale: 1.9; }





/* The break screen carries an advertisement, in the same 320x50 box the ad row
   uses — one slot size on the site, wherever it appears. */
.m-tile.slot { flex-direction: row; align-items: center; justify-content: flex-start; gap: 6px;
  padding: 4px 8px; background: var(--chrome); }
.m-tile.slot .banner { flex: 1; min-width: 0; height: var(--slot-h); }
.m-tile.slot .ad-lbl { flex: none; }


/* ── the eight, scattered ────────────────────────────────────────────────────
   Two columns is the structure; where a headline sits inside its column is not.
   Each one carries its own indent, width and side, seeded off its id in
   story.mjs so a cover holds still between builds but no two covers set the
   same way. Eight headlines in two tidy columns read as a table. */
.lines .line { width: 100%; max-width: 100%; }
/* Aligned outward — the left column reads left, the right column reads right,
   which is what gives the old cover its edge-to-edge tension. */
.lines .col.right .line { text-align: right; }
.lines .col:not(.right) .line { text-align: left; }
/* Only the air varies. */
.lines .line.d1 { margin-top: 1dvh; }
.lines .line.d2 { margin-top: 2dvh; }
.lines .line.d3 { margin-top: 3dvh; }

/* ── the guide ───────────────────────────────────────────────────────────────
   Off by default. On, it draws the two-thirds line so the split can be checked
   without measuring it — and a second rule where the mark's column begins,
   because that is the other edge the cover is composed against. */
html[data-guides="on"] .cover .lines { box-shadow: inset 0 -1px 0 0 var(--focus); }
html[data-guides="on"] .cover .lead::before {
  content: ''; position: absolute; top: 0; bottom: 0; right: 112px;
  border-left: 1px dashed var(--focus); opacity: .7; pointer-events: none;
}


/* ── the square above the mark ───────────────────────────────────────────────
   The mark opens the issue and takes the bottom right. Directly above it the
   cover leaves a square the same width — a slot for a logo, sold or ours. */
/* One column in the bottom right: the square sits on the mark and is exactly
   as wide as it. `align-items: stretch` is what makes that true by construction
   — a hardcoded width was a guess, and the guess was wrong the moment the
   mark's label changed length. */
/* ONE NUMBER sizes the corner, and everything in the quarter reads it — the
   square, the button under it, and the headline that has to keep clear of both.
   It is declared on .cover, above all three; see the clamp there.

   Three attempts at making one measure the other were all circular — the square
   has an aspect-ratio and no intrinsic width, so it contributes nothing to a
   measurement and whatever stretched to fill the column became the column. It
   kept landing at 314. Declaring it once ends the argument. */
.cover-foot { flex-direction: column; align-items: flex-end;
  width: var(--corner); gap: 6px; }
/* A CIRCLE, of the same diameter the square had. The one shape in this
   magazine that is allowed a radius is a mark of identity, and that is what
   this is — a publisher's mark, in the corner of the cover. */
.cover-badge {
  display: block; width: var(--corner); height: var(--corner); overflow: hidden;
  border-radius: 50%;
  /* Its own ground, not the chrome's. The theta is a white glyph on
     transparency, so on a cream strip it was simply not there. */
  background: var(--badge-bg, #101010); border: var(--more-border);
}
.cover-badge img { width: 100%; height: 100%; object-fit: contain; padding: 12px;
  display: block; }
/* Below this the quarter cannot hold the square and the button both, and the
   button is the one that has to stay. */
@media (max-height: 620px) { .cover-badge { display: none; } }

/* ── the ribbon ──────────────────────────────────────────────────────────────
   A section says what it is here, where the cover says its issue number — one
   band under the masthead, never both. The masthead itself never changes.

   Each section has its own: the same slot, four different treatments, because
   four bands that differ only in their wording are four pages that look the
   same. */
.ribbon {
  position: relative; z-index: 11; flex: none;
  margin: 4px 0 0; padding: 2px 8px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  white-space: nowrap; overflow: hidden; line-height: 1.3;
}
.ribbon b {
  font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: .32em; text-transform: uppercase; white-space: nowrap;
}
.ribbon span {
  font-family: var(--mono); font-size: 8.5px; letter-spacing: .12em;
  text-transform: uppercase; opacity: .78; text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* TOMORROW — the one that is happening. Solid, in the colour that takes money,
   with a rule under it like a countdown line. */
.ribbon--tomorrow {
  background: var(--raise-bg, var(--kick-fg)); color: var(--raise-fg, var(--paper-bg));
  border-bottom: 2px solid var(--fg);
}

/* THE WEEKLY — editorial. Ruled above and below, ink on the page, nothing
   filled: it is a summary, not an event. */
.ribbon--theweekly {
  background: transparent; color: var(--fg);
  border-top: 1px solid var(--fg); border-bottom: 1px solid var(--fg);
  box-shadow: 0 3px 0 -1px var(--fg);
}
.ribbon--theweekly b { font-family: var(--display); letter-spacing: .2em; font-size: 11px; }

/* ALL TIME — the archive. Reversed, the heaviest of the four, because it is the
   one that covers everything. */
.ribbon--alltime {
  background: var(--fg); color: var(--paper-bg);
}
.ribbon--alltime b { letter-spacing: .42em; }
.ribbon--alltime span { opacity: .62; }

/* EXAMPLE — not a real issue, and it says so: dashed, unfilled, quiet. */
.ribbon--example {
  background: transparent; color: var(--counts-fg);
  border: 1px dashed var(--counts-fg);
}
.ribbon--example b { font-weight: 400; letter-spacing: .28em; }

