/* Prime Time 24 — reader-facing design system.
   Every value is lifted from DESIGN-GUIDELINES.md, which is itself derived from
   PT24 Website.dc.html. Where this file and the guidelines disagree, the
   guidelines win.

   August 2026: the guidelines were re-cut from a Figma redesign of the front
   page, and the one change that reaches every rule below is that the paper now
   has a serif voice. See the type block for what that means and what it does
   not. */

:root {
  /* 3. Typography — three faces, three jobs, and no overlap between them.
     Until now the whole paper was set in one grotesque and hierarchy came from
     weight and spacing alone. It still largely does *within* a role; what the
     redesign adds is that a headline and a caption are no longer the same
     letterform at two sizes.

     --serif is the news voice: every headline, from the lead down to a brief.
     Newsreader is a screen-first old-style with enough contrast to carry a
     50px lead and a low enough x-height ratio not to shout at 22px.

     --sans is everything a headline is not: standfirsts, body, meta, labels,
     nav, forms. It replaced Archivo, and the two Notos below are re-paired to
     it — they are not a fourth and fifth family in the system, they are this
     same neutral-grotesque role in the two scripts --sans cannot draw.

     --ui is for controls with a word in them: Subscribe, Read, Watch, Save,
     Explore More. It is one notch geometric against --sans, which is what
     keeps a button reading as a button next to a label at the same size.
     Nothing that is not a control takes it. */
  --serif: 'Newsreader', Georgia, 'Times New Roman', serif;
  --sans: 'Roboto', system-ui, sans-serif;
  --ui: 'Plus Jakarta Sans', 'Roboto', system-ui, sans-serif;

  /* 2. Colour */
  --ink: #14120F;
  --paper: #FBF9F5;
  /* Both stepped by the redesign. The red is a shade cooler and a shade
     brighter, which is what let it come off the masthead's black bar and sit
     on cream as the Subscribe button; the gold warmed to match it. */
  --red: #EC1F28;
  --gold: #C7862B;
  --ink-70: #3A362E;
  --ink-60: #57534A;
  --ink-45: #6B665C;
  /* The bottom two steps were set by eye and both failed WCAG AA against the
     backgrounds they are actually printed on. On --wash, the old --ink-30
     (#8A8378) measured 3.16:1 and the old --ink-18 (#A9A296) 2.13:1 — and
     between them they carried nearly every label, note and placeholder in the
     system, all of it below 18px, where 4.5:1 is the bar.

     --ink-30 is now the muted step for small text and clears 4.5:1 on both
     --wash (4.64:1) and --paper (5.24:1). That puts it close to --ink-45,
     which is the honest answer: a warm grey scale on a cream page does not
     have three distinguishable passing steps, and pretending it did is what
     produced two that failed.

     --ink-18 clears 3:1 and no more. It is for non-text furniture — carets,
     disabled fills, rules — and for type at 18px and above. Anything smaller
     takes --ink-30 or darker. */
  --ink-30: #6F695F;
  --ink-18: #857E73;

  /* The redesign's one neutral grey, and the only one on the page that is not
     warm. It carries the whole provenance idiom — "Updated 5 days ago", the
     thread card's date range, a node's date and its role — which is type that
     is deliberately not part of the paper's cream-and-ink voice.

     Drawn as #747474, printed as #717171. #747474 measures 4.45:1 on --paper
     and this text is set at 14px and 16px, where the bar is 4.5:1; one step
     darker clears it at 4.64:1 and is indistinguishable at any size. The rule
     stated at the head of the ink scale applies to grey that arrives from a
     design file too. */
  --meta: #717171;

  /* The rules. --rule and its two steps up are the warm hairlines inside a
     card or between two items of a list. The redesign draws every *structural*
     rule — the masthead, the nav, the column divider, the band between two
     rows of the river, the border of a box — as a 1px ink hairline instead,
     which is what gives the page its grid. Those take --ink directly. */
  --rule: #E2DDD3;
  --rule-strong: #D8D2C6;
  --rule-heavy: #C9C2B4;
  --wash: #EFEBE3;
  --wash-warm: #F6F2EA;
  --slot: #EAE5DB;
  --dark-inset: #241F1A;
  --verified: #1E7A46;
  --amber: #D9A227;
  --ad-wash: #F2EEE6;

  /* On ink */
  --knock-75: rgba(251, 249, 245, .75);
  --knock-68: rgba(251, 249, 245, .68);
  --knock-62: rgba(251, 249, 245, .62);
  --knock-55: rgba(251, 249, 245, .55);
  --knock-50: rgba(251, 249, 245, .50);
  --knock-45: rgba(251, 249, 245, .45);
  --knock-42: rgba(251, 249, 245, .42);
  --knock-28: rgba(251, 249, 245, .28);
  --knock-22: rgba(251, 249, 245, .22);
  --knock-18: rgba(251, 249, 245, .18);
  --knock-16: rgba(251, 249, 245, .16);
  --knock-14: rgba(251, 249, 245, .14);
  --knock-12: rgba(251, 249, 245, .12);

  /* 4. Layout
     The gutter is the biggest single change to the page's proportions. It was a
     flat 32px, which on a 1728px screen gave the paper 1664px of column and no
     margin worth the name — every rule ran nearly edge to edge and the page had
     no frame. The redesign draws about 6% a side, which is 104px at 1728, and
     that is where the ceiling comes from: past 1730px or so the margin stops
     growing and the columns take the rest.

     The floor is 20px, which is a phone. Between the two it is proportional,
     so a 1280px laptop gets 77px and a 1024px one gets 61px rather than all
     three getting the same 32. */
  --max: 1920px;
  --max-story: 1920px;
  --max-footer: 1920px;
  --gutter: clamp(20px, 6vw, 104px);
  /* 439px of rail plus the 24px that holds it off the column rule. It was
     380px flat: the vertical timeline that moved into the rail needs a ring, a
     date, a role and a two-line headline across it, and 380 was not enough
     column for that without the headline setting three words a line. */
  --rail: 463px;
  --rail-wide: 480px;

  /* 4b. Spacing — a 4pt scale, named. The masthead and the nav had their
     gaps set one number at a time, which is how 26px of row gap ended up
     next to an 8px search offset and a 10px action gap: three values that
     were each defensible alone and read as three unrelated decisions
     together. New work measures from here. */
  --space-3xs: 2px;
  --space-2xs: 4px;
  --space-xs:  8px;
  --space-sm:  12px;
  --space-md:  16px;
  --space-lg:  24px;
  --space-xl:  32px;
  --space-2xl: 48px;

  /* The masthead row's controls all stand this tall — the search field,
     Subscribe and the account door. They used to run between 27px and 33px
     depending on which font size each had been given padding for, so a row of
     controls that are doing the same job read as four sizes of control.

     48px, up from 40. The row is a good deal taller than it was because the
     dateline moved under the wordmark, and 40px controls in it read as small
     print rather than as the row's furniture. It is also the tap target the
     phone bar has always used. */
  --control-h: 48px;

  /* The nav band's own height, so the active-item rule and the two fences in
     the format axis have one number to measure from. */
  --nav-h: 72px;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-synthesis: none; /* no faux bold */
  -webkit-font-smoothing: antialiased;
}

/* Copy guard. The selection itself is turned off on the reading surfaces, so the
   long-press menu and the drag never start; the script handles the clipboard.
   Anything the reader types into is exempt, because a search field you cannot
   select inside is a broken search field. */
body {
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
input, textarea, select, [contenteditable] {
  -webkit-user-select: text; user-select: text;
}

a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--red); }

button { font-family: inherit; }

img { max-width: 100%; }

/* Never apply a CSS filter to the logo, and never place it on a coloured box. */
.pt24-logo { display: block; }

/* Clock times are tabular. */
.tnum { font-variant-numeric: tabular-nums; }

/* 6. Motion — only two animations exist. */
@keyframes pt24pulse { 0%, 100% { opacity: 1 } 50% { opacity: .25 } }
@keyframes pt24run   { from { transform: translateX(0) } to { transform: translateX(-50%) } }

.pulse { animation: pt24pulse 1.8s ease-in-out infinite; }
.pulse-fast { animation: pt24pulse 1.6s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .pulse, .pulse-fast, .runner-track { animation: none !important; }
}

.pt24scroll { scrollbar-width: thin; scrollbar-color: var(--rule-heavy) transparent; }
.pt24scroll::-webkit-scrollbar { height: 6px; }
.pt24scroll::-webkit-scrollbar-track { background: transparent; }
.pt24scroll::-webkit-scrollbar-thumb { background: var(--rule-heavy); border-radius: 3px; }

.shell { max-width: var(--max); margin: 0 auto; padding: 0 var(--gutter); }
.shell-story { max-width: var(--max-story); margin: 0 auto; padding: 0 var(--gutter); }

/* ------------------------------------------------------------------ */
/* 5.2 Masthead — logo first at top left, always                        */
/*                                                                      */
/* There is no 5.1 any more. The utility strip was a 38px band of ink   */
/* above the publisher's own name carrying nine items, none of them     */
/* news. The four a reader uses are the dateline under the wordmark;    */
/* the rest are under Explore more and in the footer. See pt24_base.    */
/*                                                                      */
/* The row is brand, then search, then the three controls: Subscribe,   */
/* the edition, the account door. Every one of them is 48px, and the    */
/* only fill on the row is the red on Subscribe — the one control that  */
/* is asking the reader for something rather than telling them where    */
/* they are.                                                            */
/* ------------------------------------------------------------------ */
.masthead { background: var(--paper); border-bottom: 1px solid var(--ink); }
.masthead-row {
  display: flex; align-items: center; gap: var(--space-xl); padding: 15px 0 26px;
}

/* The wordmark and the dateline are one block, because they are one statement:
   which paper, and which day of it. The dateline used to be in a band of its
   own two rows up, where it read as a status bar.

   It is the shrinkable item on this row, and that is the whole of how the row
   fits. The dateline's four facts run to 585px set on one line, which is more
   than the design allowed for — the design drew three facts and no clock. As
   flex:none the block sized to that 585px and refused to give any of it back,
   so the search field, the only other item that can flex, paid: at 1180px it
   had collapsed to 74px, which is a magnifier and no room to type.

   Shrinkable and floored at 0, the block gives width up instead and
   .masthead-meta's own flex-wrap takes the dateline to a second line. Four facts
   on two lines is a dateline either way; a 74px search field is not a search
   field. The floor that matters is on .searchbox below, not here. */
.masthead-brand { flex: 0 1 auto; display: flex; flex-direction: column; gap: 9px; min-width: 0; }

/* The master wordmark, not the square lockup. The desktop row is the widest
   masthead the paper gets and it had the room to spell the name out; the
   square mark was chosen when the row was thought of as a toolbar, and a
   toolbar is not what a masthead is. Never filtered, never on a coloured box
   — it carries its own.

   The negative margins are not a nudge. pt24-wordmark-light.png is a 624x212
   file with 582x76 of letterform in the middle of it — 64% of its height and
   7% of its width are transparent padding baked into the art. Sizing the *box*
   therefore sizes the padding: `height: 46px` drew PRIMETIME24 at 17px tall in
   a 122px masthead, which is a masthead whose publisher's name is smaller than
   its own nav.

   So the box is set to the height that puts the letterform at the 26px the
   design draws it at, and the art's own padding is then taken back out of the
   layout: 74 * 68/212 = 23.7px at the top and the same at the foot, 74 *
   21/624 = 7.3px at the left, which is what lands the P on the gutter. The row
   measures 27px of letterform, which is what it should have been measuring all
   along.

   If the art is ever re-exported without the padding, delete the margins and
   set the height to 27px. */
.masthead-mark { display: block; }
.masthead-mark .pt24-logo { width: auto; height: 74px; margin: -24px 0 -24px -7px; }

/* Date, clock, weather, air. Four facts, one line, no separators between them
   — they are a dateline, and a dateline is not a list. The clock keeps its
   tabular figures so the minute changing does not move the weather.

   In --ui rather than --sans: this line and the controls to its right are the
   masthead's own voice, and it is the one piece of running text in the system
   that is neither a headline nor body copy. */
.masthead-meta {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 0; font: 400 16px var(--ui); color: var(--ink);
}
.masthead-meta .dot { width: 7px; height: 7px; margin-right: 5px; }

/* 526px and no more, 240px and no less.

   The maximum is because the slack in the row belongs in the margin in front of
   the field rather than in the field: one that grows to 800px on a wide screen
   is not a better search field, it is a search field that has stopped looking
   like one.

   The minimum is the load-bearing half. It is what makes the row's priority
   explicit — the dateline yields before the field does — and it is why the
   masthead needs no width breakpoint to stay usable. 240px holds the placeholder
   to about "Search Stories, Threads..." and leaves room to type; the narrowest
   desktop lowers it once, below. */
.searchbox {
  flex: 1 1 526px; min-width: 240px; max-width: 526px; margin-left: auto;
  display: flex; align-items: center; gap: var(--space-sm);
  height: var(--control-h);
  border: 1px solid var(--ink); border-radius: 0; padding: 0 var(--space-md);
  background: none;
}
.searchbox .mag { color: var(--meta); font-size: 15px; }
.searchbox input {
  border: none; outline: none; background: none; font: 400 16px var(--sans);
  color: var(--ink); width: 100%;
}
.searchbox input::placeholder { color: var(--meta); }

/* Words and a caret, not a filled black button. Where you are reading from is a
   statement about this page rather than an action, and the black fill was
   making the loudest control on the row out of the one item on it that is
   already true. */
.edition-block { display: flex; align-items: center; flex: none; }
.edition-btn {
  display: flex; align-items: center; gap: var(--space-xs);
  height: var(--control-h); background: none; border: none; border-radius: 0;
  padding: 0; font: 600 16px var(--ui); color: var(--ink); cursor: pointer;
  white-space: nowrap;
}
.edition-btn:hover { color: var(--red); }
.edition-btn .caret { color: var(--ink); font-size: 11px; }

.btn-subscribe {
  display: inline-flex; align-items: center; flex: none; height: var(--control-h);
  background: var(--red); color: #fff; border: none; border-radius: 0;
  padding: 0 25px; font: 600 16px var(--ui); cursor: pointer; white-space: nowrap;
}
.btn-subscribe:hover { background: var(--ink); }

/* ------------------------------------------------------------------ */
/* 5.2b The account door                                               */
/*                                                                      */
/* One control where there were three. Sign in was a 13px text link and  */
/* the language switch a 44px-a-side segmented control, and between them */
/* they spent a third of the row's right end on two things a reader      */
/* touches once a session. Both are in the panel.                        */
/* ------------------------------------------------------------------ */
.account { position: relative; flex: none; display: flex; }
.account-btn {
  width: var(--control-h); height: var(--control-h); display: grid; place-items: center;
  border: 1px solid var(--ink); border-radius: 50%; background: none;
  color: var(--red); cursor: pointer; padding: 0;
}
.account-btn svg { width: 26px; height: 26px; display: block; }
.account-btn[aria-expanded="true"] { background: var(--ink); color: var(--paper); }

/* A flat panel, 1px ink, no shadow and no motion — the same object the nav's
   two dropdowns are. A dropdown here is a piece of the page that was folded,
   not a thing that arrives. */
.account-panel {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 60;
  width: 240px; display: flex; flex-direction: column; gap: 10px;
  background: var(--paper); border: 1px solid var(--ink); padding: 16px;
}
.account-panel[hidden] { display: none; }
.account-signin {
  display: flex; align-items: center; justify-content: center; height: 40px;
  background: var(--ink); color: var(--paper); font: 600 15px var(--ui);
}
.account-signin:hover { background: var(--red); color: #fff; }
/* You, arriving from the nav's format axis where it never belonged. Set below
   Sign in and above the language switcher because that is the order the panel
   already reads in — identity, then the settings that identity carries. Bolder
   than the service links under FROM THE PAPER: it is the reader's own screen,
   not one more product. */
.account-you {
  font: 500 16px var(--sans); color: var(--ink);
  padding-bottom: 10px; border-bottom: 1px solid var(--rule);
}
.account-you:hover { color: var(--red); }
.account-you.is-active { font-weight: 600; color: var(--red); }
.account-label {
  font: 700 11px var(--sans); letter-spacing: .1em; color: var(--meta);
}
.account-links { display: flex; flex-direction: column; gap: 7px; }
.account-links a { font: 400 15px var(--sans); color: var(--ink); }
.account-links a:hover { color: var(--red); }

/* A segmented control, so the segments are laid out by the control's height
   rather than by each chip's own line box. EN is Latin at one cap height,
   हिन्दी hangs a matra above it and drops a descender below, ಕನ್ನಡ sits
   somewhere else again: three scripts baseline-aligned to their own metrics
   is exactly the ragged row the chips used to draw. Centre them in a fixed
   box and the row is level whatever is set in it. */
.lang-switch {
  display: flex; align-items: stretch; width: 100%; height: 40px;
  border: 1px solid var(--ink); border-radius: 0; overflow: hidden;
}
.lang-switch button {
  display: flex; align-items: center; justify-content: center; flex: 1;
  min-width: 0; padding: 0 var(--space-xs); line-height: 1;
  color: var(--ink-45); background: none; border: none; cursor: pointer;
  font-size: 14px; font-weight: 600;
}
.lang-switch button + button { border-left: 1px solid var(--ink); }
.lang-switch button.is-active { background: var(--ink); color: var(--paper); }

/* The switcher names each language in its own script, so each script needs a
   face that can set it. These are the same neutral grotesque role as --sans,
   not a fourth and fifth family in the type system. */
.lang-deva { font-family: 'Noto Sans Devanagari', 'Roboto', sans-serif; }
.lang-knda { font-family: 'Noto Sans Kannada', 'Roboto', sans-serif; }

/* The dot. It is the air-quality swatch on the dateline and the phone service
   strip, the pulse on a LIVE chip, and the state light on a followed thread —
   one 7px circle whose colour is set by whatever is using it.

   .locator and .social went with the utility strip. The first was the ◎ in
   front of the edition name, which the edition selector now prints in words;
   the second was five glyphs in five 20px circles, and the footer and the
   Explore more panel both carry those links in words instead. */
.dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }

/* ------------------------------------------------------------------ */
/* 5.3 Navigation                                                      */
/*                                                                      */
/* A 72px band between two ink hairlines. The subjects lead at 18px and  */
/* 39px apart — this row is the paper's contents page, and it used to be */
/* set at 14.5px with 12px of padding, which made the taxonomy the       */
/* smallest type in the chrome and packed it tight enough to read as a   */
/* toolbar.                                                             */
/*                                                                      */
/* The right end holds the format axis between two short fences and,     */
/* past them, the one control in the row drawn as a button.              */
/* ------------------------------------------------------------------ */
/* `flex-wrap: wrap` at every width, not below a breakpoint.

   This row's content is the edition's own taxonomy. Bengaluru publishes eleven
   subjects; another edition publishes six. There is no viewport width at which
   "the nav fits" is true for both, so the width at which it drops to a second
   line cannot be a media query — it has to be whatever width this edition's
   subjects, the format axis and the button actually stop fitting. It used to
   wrap only under 1000px, and between about 1038px and 1300px the row simply
   ran off the right of the viewport by up to 254px.

   Wrapping and not scrolling: .nav-verticals-panel is absolutely positioned inside
   this row, and an overflow container would clip the panel on both axes. */
.nav {
  display: flex; align-items: stretch; flex-wrap: wrap;
  column-gap: 39px; row-gap: 0; min-height: var(--nav-h);
  border-top: 1px solid var(--ink);
}
.nav a {
  display: flex; align-items: center; padding: 0;
  font: 500 18px var(--sans); color: var(--ink);
}
/* A section name is a name. "Fact-check" breaking after the hyphen and taking
   the nav to two lines with it is not a narrow-screen compromise, it is a
   different word. The row steps its type down instead — see below. */
.nav a, .nav-verticals-btn, .nav-explore-btn { white-space: nowrap; }
.nav a:hover { color: var(--red); }

/* Ink at 600 with a red rule under it, flush to the band's own bottom hairline.
   The subjects do not go red on selection: red in this row is reserved for the
   format axis, where it is the only thing distinguishing two kinds of link
   that sit side by side. */
.nav a.is-active {
  font-weight: 600; color: var(--ink);
  box-shadow: inset 0 -3px 0 var(--red);
}

/* The format axis, pushed right by its own auto margin and fenced by a rule on
   each side. Read / Watch / Listen / You answers a different question from
   Civic / India / Politics, and mixing the two on one line asked the reader to
   notice the difference from the type alone.

   The fences are 32px, not the band's full height: a full-height rule reads as
   a column division, and this is a bracket around three words. */
/* The service end of the row: the format axis and Explore more, as one item.

   The auto margin belongs here rather than on either child. Two siblings each
   taking the row's slack wrap independently — the formats would drop to line
   two while the button stayed on line one, or the reverse — and neither of those
   is a row. As one item they drop together and stay right-aligned on whichever
   line they land on. */
.nav-service {
  margin-left: auto; align-self: center; flex: none;
  display: flex; align-items: center; min-height: var(--nav-h);
}

.nav-formats {
  flex: none; display: flex; align-items: center; gap: 20px; height: 32px;
  padding: 0 22px;
  border-left: 1px solid var(--ink); border-right: 1px solid var(--ink);
}
.nav-formats a.format { font: 500 18px var(--sans); color: var(--ink); }
.nav-formats a.format.is-active {
  color: var(--red); font-weight: 600; box-shadow: none;
}

/* Verticals — the paper's own segments — and the products panel beside them.
   Flat panels, 1px ink, no shadow and no motion: a dropdown is a piece of the
   page that was folded, not a thing that arrives.

   Set at the row's own weight and size rather than styled as a control. It is
   the seventh item on a row of six subjects and it reads as one of them,
   because that is what it is — the difference is what it opens, not what it
   ranks. Only the mark and the caret say it opens at all. */
.nav-verticals { position: relative; display: flex; }
.nav-verticals-btn {
  display: flex; align-items: center; gap: 7px; padding: 0;
  font: 500 18px var(--sans); color: var(--ink);
  background: none; border: none; cursor: pointer;
}
.nav-verticals-btn:hover { color: var(--red); }
.nav-verticals-btn .caret { font-size: 11px; color: var(--ink); }
.nav-verticals-btn.is-active { font-weight: 600; box-shadow: inset 0 -3px 0 var(--red); }

/* The flag, clipped to the glyph. The source is a square with the mark inset
   in a wide margin, so `object-fit` cannot help — cropping transparent padding
   is not a fit mode. This is the design's own window onto that square, in the
   design's own percentages: an oversized image inside a fixed box that shows
   the part of it the mark occupies.

   Sized in em so it tracks the row when the type steps down at 1180px rather
   than sitting at 17px next to 16px text. */
.nav-verticals-mark {
  position: relative; flex: none; overflow: hidden;
  width: .95em; height: 1.22em;
}
.nav-verticals-mark img {
  position: absolute; max-width: none;
  width: 226.89%; height: 181.34%; left: -63.12%; top: -36.12%;
}

.nav-verticals-panel {
  position: absolute; top: 100%; left: 0; z-index: 40;
  display: flex; flex-direction: column; gap: 10px;
  background: var(--paper); border: 1px solid var(--ink); border-radius: 0;
  padding: 8px; margin-top: 0;
}
.nav-verticals-panel[hidden] { display: none; }
.verticals-links { display: grid; grid-template-columns: repeat(2, minmax(158px, 1fr)); }
.nav-verticals-panel a {
  padding: 10px var(--space-sm); font: 400 16px var(--sans); color: var(--ink);
  white-space: nowrap;
}
.nav-verticals-panel a:hover { background: var(--wash); color: var(--red); }
.nav-verticals-panel a.is-active { font-weight: 600; background: var(--wash); box-shadow: none; }

/* Explore more: the network's other products, and the two service links the
   utility strip used to print in 12px above the masthead. It is the one item in
   this row drawn as a button because it is the one that is not a section —
   everything else here filters today's file. */
.nav-explore { position: relative; flex: none; margin-left: 22px; }
.nav-explore-btn {
  display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 14px;
  font: 600 16px var(--ui); color: var(--ink);
  background: none; border: 1px solid var(--ink); border-radius: 0; cursor: pointer;
}
.nav-explore-btn:hover { background: var(--ink); color: var(--paper); }
.nav-explore-btn .caret { font-size: 11px; }
.nav-explore-panel {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 40;
  width: 250px; display: flex; flex-direction: column; gap: 10px;
  background: var(--paper); border: 1px solid var(--ink); padding: 16px;
}
.nav-explore-panel[hidden] { display: none; }
.explore-label { font: 700 11px var(--sans); letter-spacing: .1em; color: var(--meta); }
.explore-links { display: flex; flex-direction: column; gap: 7px; }
.explore-links a { font: 400 15px var(--sans); color: var(--ink); }
.explore-links a:hover { color: var(--red); }

/* ------------------------------------------------------------------ */
/* The narrow desktop, 821px to 1440px.                                 */
/*                                                                      */
/* Everything above this point is sized for the width the masthead is   */
/* actually drawn at, which is now about 1730px — the gutter grew to 6% */
/* a side and the nav's subjects went from 14.5px to 18px, so the row    */
/* that used to run out of width at 1180px runs out of it a good deal    */
/* sooner.                                                              */
/*                                                                      */
/* The order is deliberate: duplicates first, then decoration, then     */
/* size, and never the taxonomy or the edition. Nothing here is hidden  */
/* that is not printed somewhere else on the same screen.               */
/* ------------------------------------------------------------------ */

/* First to give is the space between things, then the type. Eleven subjects at
   18px with 39px between them want a little over 1700px on their own, which is
   why the ceiling is 1700 and not the 1440 it started at: between the two the
   row was back at full size, did not fit, and wrapped — so 1440 drew one line,
   1600 drew two and 1728 drew one again. Stepping type and gaps back holds one
   line down to roughly 1300. Below that it wraps, which needs no rule — the
   wrap is on .nav at every width. */
@media (min-width: 821px) and (max-width: 1700px) {
  .nav { column-gap: 26px; }
  .nav a, .nav-verticals-btn, .nav-formats a.format { font-size: 16px; }
  .nav-formats { gap: 16px; padding: 0 16px; }
  .nav-explore { margin-left: 16px; }
  .masthead-row { gap: var(--space-lg); }
  .masthead-meta { font-size: 14px; gap: 11px; }
}

@media (min-width: 821px) and (max-width: 1180px) {
  :root { --control-h: 40px; }

  /* The wordmark steps down before the search field does. It is 289px of a row
     that has 1140px for it and four controls, and the field was paying for it
     — at 900px the search had shrunk to its own magnifier with no room left to
     type in. */
  .masthead-row { padding: 13px 0 18px; }
  .masthead-mark .pt24-logo { height: 34px; }
  .searchbox { flex-basis: 300px; max-width: 300px; }
  .btn-subscribe, .edition-btn, .account-signin, .nav-explore-btn { font-size: 15px; }
  .btn-subscribe { padding: 0 16px; }

  /* Below 1000px the row does not have 240px of search to give after the
     wordmark, Subscribe, the edition and the account door have taken theirs, and
     a floor that cannot be met is an overflow rather than a floor. It comes down
     to 150px here, which is the last width at which the field is still a field.

     There is no cap on .masthead-brand. It had one — 300px, to force the
     dateline to wrap — and the cap living in a max-width block was itself the
     bug: at 1181px it lifted, the block sprang back to its full 510px and took
     the search field down to 74px, so the field got *smaller* as the viewport got
     larger. The floor on .searchbox does the same job at every width and has no
     edge to fall off. */
}

/* The narrowest desktop the paper draws. */
@media (min-width: 821px) and (max-width: 1000px) {
  .searchbox { min-width: 150px; }
}

/* ------------------------------------------------------------------ */
/* 5.4 The area picker and the edition picker                          */
/*                                                                      */
/* Both are dialogs at every width. Above 820px they centre over a      */
/* scrim; at 820px and below they dock as bottom sheets. The shared     */
/* rules are here, the geometry is in the two media blocks — the phone  */
/* sheet near the rest of the phone chrome, the centred modal in the    */
/* min-width block below it.                                            */
/*                                                                      */
/* The desktop half used to be an inline band in the document flow:     */
/* --wash under a 1px rule, which read as another section of the front  */
/* page rather than as a question, and which pushed the river down by   */
/* its own height every time it opened.                                 */
/* ------------------------------------------------------------------ */
.cities { background: var(--wash); }
.cities[hidden] { display: none; }
.cities-inner { padding: 20px 0 22px; }
.cities-head {
  display: flex; align-items: center; gap: 16px;
  padding-bottom: 16px; border-bottom: 1px solid var(--rule-strong); margin-bottom: 18px;
}
.cities-head .copy { display: flex; flex-direction: column; gap: 3px; }
/* The ask, at heading weight. It carries a verb now, so it is allowed to look
   like the thing the dialog is for. */
.cities-head h3 { font: 600 21px/1.25 var(--sans); margin: 0; letter-spacing: -.005em; }
.cities-head p { font-size: 12.5px; color: var(--ink-30); margin: 0; }

.area-search-row { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.area-search {
  flex: 1; display: flex; align-items: center; gap: 9px;
  border: 1px solid var(--rule-heavy); background: #fff; border-radius: 2px;
  padding: 10px 14px; max-width: 460px;
}
.area-search input {
  border: none; outline: none; background: none; font: 400 13px var(--sans);
  color: var(--ink); width: 100%;
}
.area-search input::placeholder { color: var(--ink-45); }
.area-search .slash {
  margin-left: auto; font: 600 10px var(--sans); color: var(--ink-45);
  border: 1px solid var(--rule); border-radius: 2px; padding: 2px 6px;
}
.area-search-note { font-size: 12px; color: var(--ink-30); }

.chip-rows { display: flex; flex-direction: column; gap: 11px; margin-bottom: 18px; }
.chip-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.chip-label { font: 700 11px var(--sans); color: var(--ink-30); letter-spacing: .08em; flex: none; }
/* Pills are the one radius exception, at 100px. */
.chip {
  display: inline-flex; align-items: center; gap: 7px; font: 500 12.5px var(--sans);
  background: #fff; border: 1px solid var(--rule-strong); border-radius: 100px;
  padding: 6px 15px; cursor: pointer; color: var(--ink);
}
.chip:hover { border-color: var(--ink); color: var(--ink); }
.chip.is-selected {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
  font-weight: 600;
}
.chip.is-selected .x { color: var(--knock-55); font-size: 11px; }
.chip-free { font-size: 11.5px; color: var(--ink-45); }
.chip-more {
  display: inline-flex; align-items: center; gap: 6px; font: 600 12px var(--sans);
  color: var(--red); padding: 6px 4px; background: none; border: none; cursor: pointer;
}

/* Pinned to the bottom of the dialog at every width, not the end of a scroll.
   An edition with 250 areas otherwise puts the only way to commit a selection
   several screens below the fold. */
.cities-foot {
  flex: none; display: flex; align-items: center; gap: 14px;
  padding: 13px 26px calc(13px + var(--safe-bottom, 0px));
  border-top: 1px solid var(--rule-strong); background: var(--wash);
}
.cities-foot p { font-size: 12px; color: var(--ink-30); margin: 0; }
.btn-save {
  background: var(--red); color: #fff; border: none; border-radius: 2px;
  padding: 10px 18px; font: 700 12.5px var(--sans); cursor: pointer; flex: none;
  font-variant-numeric: tabular-nums;
}
.btn-save:disabled { background: var(--rule-strong); color: var(--ink-45); cursor: default; }

/* ------------------------------------------------------------------ */
/* The edition picker: city, and which day of it.                      */
/* ------------------------------------------------------------------ */
.archive-head {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  margin: 20px 0 10px; padding-top: 16px; border-top: 1px solid var(--rule-strong);
}
.archive-note { font-size: 12px; color: var(--ink-30); }

/* A row of days, scrolled horizontally rather than wrapped: ten dates in a
   line read as a run of time, and the same ten in three ragged rows do not. */
.archive-days {
  display: flex; gap: 8px; overflow-x: auto; overscroll-behavior-x: contain;
  padding-bottom: 8px; scrollbar-width: thin;
  scrollbar-color: var(--rule-heavy) transparent;
}
.archive-empty { font-size: 12.5px; color: var(--ink-30); margin: 4px 0; }

.archive-day {
  flex: none; width: 118px; display: flex; flex-direction: column; gap: 1px;
  background: #fff; border: 1px solid var(--rule-strong); border-radius: 2px;
  padding: 10px 12px 11px; color: var(--ink); text-decoration: none;
}
a.archive-day:hover { border-color: var(--ink); }
.archive-day .d { font: 700 22px/1 var(--sans); letter-spacing: -.02em; }
.archive-day .m { font: 700 10px var(--sans); letter-spacing: .1em; color: var(--ink-45); }
.archive-day .w { font: 600 11.5px var(--sans); color: var(--ink-45); margin-top: 4px; }
.archive-day .n { font: 500 11px var(--sans); color: var(--ink-30); }
/* The lead headline, clamped to two lines. It is what tells a reader which day
   they are actually looking for; a date alone rarely does. */
.archive-day .h {
  font: 500 11.5px/1.35 var(--sans); color: var(--ink-60); margin-top: 6px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.archive-day.is-today { border-color: var(--ink); border-width: 2px; padding: 9px 11px 10px; }
.archive-day.is-today .d { color: var(--red); }
/* Shown, not offered. Hiding a day with nothing in it would leave a gap in a
   run of dates, which reads as a bug in the list rather than as a quiet day. */
.archive-day.is-empty { background: none; border-style: dashed; opacity: .6; }
.archive-day.is-empty .d, .archive-day.is-empty .m { color: var(--ink-45); }

.edition-areas {
  margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--rule-strong);
}

/* The back-edition bar. A five-day-old front page read as today's is the one
   way an archive can be worse than no archive, so it says so in the page and
   not only in the masthead button. */
.archive-bar { background: var(--ink); color: var(--paper); }
.archive-bar .shell {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding-top: 9px; padding-bottom: 9px;
}
.archive-bar .flag {
  font: 700 10px var(--sans); letter-spacing: .12em; color: var(--paper);
  border: 1px solid var(--knock-45); border-radius: 2px; padding: 3px 8px;
}
.archive-bar .when { font: 600 12.5px var(--sans); color: var(--knock-75); }
.archive-bar a {
  margin-left: auto; font: 600 12.5px var(--sans); color: var(--paper);
  border-bottom: 1px solid var(--knock-45); padding-bottom: 1px;
}

.zone-groups { display: none; flex-direction: column; gap: 11px; margin-bottom: 18px; }
.zone-groups.is-open { display: flex; }

/* ------------------------------------------------------------------ */
/* 5.5 Prime Alert runner — never "Breaking"                           */
/* ------------------------------------------------------------------ */
.runner {
  background: var(--ink); color: var(--paper); overflow: hidden;
  border-bottom: 2px solid var(--red);
}
.runner .shell { height: 48px; display: flex; align-items: center; gap: var(--space-lg); }
/* The flag is the pulse and the red block it sits on, and no longer the words
   PRIME ALERT. The words were set in 11.5px caps at .1em on a red ground at
   the very start of the reading order — the loudest thing on the page, spent
   on a label for the band rather than on the alert running through it. The
   band still says what it is: red ground, a pulse, and the vocabulary intact
   for anything that cannot see it. */
.runner-flag {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--red); width: 34px; align-self: stretch; flex: none;
}
.runner-flag .dot { width: 8px; height: 8px; background: #fff; }
.runner-window { flex: 1; overflow: hidden; position: relative; height: 48px; }
.runner-track {
  position: absolute; inset: 0; display: flex; align-items: center; gap: 34px;
  width: max-content; animation: pt24run 38s linear infinite;
  font-size: 16px; white-space: nowrap;
}
.runner-track a { color: var(--paper); }
.runner-track a:hover { color: var(--red); }
/* Every timestamp is red, not just the lead's. The band carries three or four
   alerts and the reader is scanning for how recent they are; one red figure
   among three knocked-back ones said "this one is the lead", which is a
   distinction nothing else on the band draws and nobody was reading. */
.runner-time { font-weight: 700; color: var(--red); }
.runner-diamond { color: var(--knock-28); font-size: 11px; }
.runner-all {
  flex: none; font: 400 16px var(--sans); color: var(--paper);
  border-left: 1px solid var(--knock-28); padding-left: var(--space-lg);
}
.runner-all:hover { color: var(--red); }

/* ------------------------------------------------------------------ */
/* Front page                                                          */
/* ------------------------------------------------------------------ */
.front { padding-bottom: 80px; }

/* Main column plus a 439px rail, divided by an ink hairline, not a gap. The
   divider used to be --rule, a warm near-white; it is ink now, and so is every
   other structural rule on the page. That single change is most of what makes
   the redesign read as a grid rather than as a stack.

   minmax(0, 1fr), not 1fr — a grid item's automatic minimum size is its
   content's min-content, and anything in the main column with a wide
   min-content pushes the column out and shoves the rail past the right edge. */
.split { display: grid; grid-template-columns: minmax(0, 1fr) var(--rail); gap: 0; }
.split > .main { padding-right: 34px; border-right: 1px solid var(--ink); }
.split > .rail { padding-left: 24px; display: flex; flex-direction: column; gap: 26px; }

/* The lead's own band, closed off by a rule the width of the page.

   No column divider in this one. The thread card sitting at the top of the
   rail draws its own left border in the same ink at the same weight in the same
   place, and two of them a pixel apart is a 2px rule with a seam in it. */
.hero-section { border-bottom: 1px solid var(--ink); }
.hero-section > .main { padding: 34px 34px 32px 0; border-right: none; }
.hero-section > .rail { padding: 34px 0 32px 24px; gap: 22px; }

/* The lead reads top to bottom in the order a reader needs it: the signal, the
   provenance, the name of the story, what it is about, the picture.

   LIVE and the update line are above the headline again, which is where the
   design puts them, but they are two rows now rather than one — a red chip on
   its own line, then 14px of grey. What used to be wrong with them up here was
   that four pieces of metadata at badge scale were competing with each other
   before the story had been named; one chip and one line do not compete. */
.hero-flags { margin: 0 0 12px; display: flex; align-items: center; gap: 12px; }

/* On a wide column a balanced wrap would leave a ragged hole beside the
   headline, so let it set greedily and scale with the viewport instead. 2.9vw
   is 50px at the width the design was drawn at. */
@media (min-width: 1280px) {
  .hero-section h1.hero {
    font-size: clamp(40px, 2.9vw, 50px);
    line-height: 1.12;
    text-wrap: wrap; /* not `normal` — invalid for this property, silently dropped */
  }
  .hero-section .standfirst { font-size: 22px; max-width: 80ch; }
}

/* The plate keeps the 16:9 frame the picture desk crops to and caps how much
   of the fold it can claim; `cover` takes the difference. Without the cap a
   16:9 hero on a 1022px column is 575px tall by arithmetic alone.

   `width: 100%` is load-bearing, not belt-and-braces. A max-height on a box
   with an aspect-ratio and an auto width makes the *height* the definite axis,
   and the width is then solved back from the ratio — the plate shrank inside
   the column and left a third of the lead empty. Pinning the width puts the
   ratio back on the height, where the cap belongs.

   The cap is what draws the design's crop: 44vh of a 1022px column is about
   2:1, which is the letterbox the lead is drawn at, and a shorter screen gets
   a shorter picture rather than a scroll. */
.hero-section .media-16x9 { width: 100%; max-height: 44vh; margin-bottom: 26px; }

/* 90 by 26, and the one rounded rectangle in the chrome. Radius in this system
   is 0 or 100px and nothing between; the chip is the exception the design
   draws, and it is drawn small enough that 4px reads as a chip corner rather
   than as a style. */
.badge-live {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-width: 90px; height: 26px; background: var(--red);
  color: #fff; font: 600 13px var(--sans); letter-spacing: .04em;
  padding: 0 12px; border-radius: 4px;
}
.badge-live .dot { width: 7px; height: 7px; background: #fff; }
.badge-outline {
  display: inline-flex; align-items: center; gap: 6px; font: 500 13px var(--sans);
  letter-spacing: .04em; border: 1px solid var(--ink); border-radius: 0; padding: 3px 9px;
}

/* Provenance, in the redesign's one neutral grey. Two facts and a link: when
   this last moved, and how many times it has moved today. Nothing here is a
   badge and nothing here is bold — it is a line of small print under a
   headline, and it took four styles of chip to say that before. */
.hero-meta {
  display: flex; align-items: center; gap: 14px; margin: 0 0 var(--space-sm);
  flex-wrap: wrap; font: 400 14px var(--sans); color: var(--meta);
}
.hero-meta .muted { font-size: 14px; color: var(--meta); }
/* No underline. The whole line is 14px grey and this is the only red thing in
   it, which is already two signals more than a link needs. */
.hero-meta .updates { font: 400 14px var(--sans); color: var(--red); }
.hero-meta .updates:hover { text-decoration: underline; }
.hero-meta .byline { margin-left: auto; font-size: 14px; color: var(--meta); }
.hero-meta .badge-live { font-size: 11px; min-width: 0; height: 22px; padding: 0 9px; }
.hero-meta .badge-outline { font-size: 12px; padding: 2px 7px; color: var(--ink-70); }

/* 3. Typography — the serif carries the hierarchy now.

   Every headline in the paper is set in --serif and nothing else is. That is
   the whole rule, and it is why the sizes below can be as close together as
   they are: a 22px headline and a 22px standfirst no longer need four points
   between them to be told apart, because they are not the same letterform.

   Weight 600 at lead size, 500 everywhere below it. Newsreader's optical size
   axis is on by default, so the 50px cut is drawn with the fine hairlines a
   display size wants and the 22px cut is not. */
h1.hero {
  font: 600 44px/1.12 var(--serif); letter-spacing: -.005em; margin: 0 0 14px;
  text-wrap: wrap;
}
.standfirst {
  font: 400 20px/1.35 var(--sans); color: var(--ink); margin: 0 0 22px;
  max-width: 80ch; text-wrap: pretty;
}
.media {
  position: relative; background: var(--slot); border: 1px solid var(--rule);
  margin-bottom: 14px; display: grid; place-items: center; overflow: hidden;
}
.media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-16x9 { aspect-ratio: 16/9; }
.media-4x3 { aspect-ratio: 4/3; }
.media-3x2 { aspect-ratio: 3/2; }
.media-1x1 { aspect-ratio: 1; }
.media-label { font-size: 12px; color: var(--ink-45); letter-spacing: .08em; }
.media-caption {
  position: absolute; left: 0; bottom: 0; background: rgba(20,18,15,.86);
  color: var(--paper); font-size: 12px; padding: 7px 12px; letter-spacing: .01em;
}

/* One story, three formats, and the row moved into the rail beside the
   headline. It used to sit under the picture, which put the choice of format
   after the picture had already been looked at and the standfirst read — the
   rail puts it level with the headline, where it is a choice about the story
   rather than a footnote to it.

   auto-fit rather than two fixed columns: a filing with Read and Watch gets
   two buttons across the rail, one with Read, Watch and Listen gets three, and
   neither of them needs a rule of its own. */
.format-row {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 16px; margin: 0;
}
/* The note explained the synchronisation to a reader who had not clicked
   anything yet, in the rail's narrowest column, under three buttons. The
   player itself says it. */
.format-row .format-note { display: none; }

/* 48px, square, and set in --ui. These are the two buttons in the system and
   everything else that looks like a button is one of them: the ink one is the
   thing to do, the outlined one is the alternative to it. */
.btn-ink {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--control-h); background: var(--ink); color: var(--paper);
  border: 1px solid var(--ink); border-radius: 0; padding: 11px 22px;
  font: 600 16px var(--ui); line-height: 1.25; cursor: pointer; text-align: center;
}
.btn-ink:hover { background: var(--red); border-color: var(--red); color: #fff; }
/* min-height, not height. These are 48px controls, but "Read the 2-min
   catch-up" in the story page's 185px left rail is two lines of 16px --ui, and
   a fixed height made the second one overflow the border it was drawn inside.
   The padding is what holds the one-line case at 48. */
.btn-line {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--control-h); background: none; color: var(--ink);
  border: 1px solid var(--ink); border-radius: 0; padding: 11px 22px;
  font: 600 16px var(--ui); line-height: 1.25; cursor: pointer; text-align: center;
}
.btn-line:hover { background: var(--ink); color: var(--paper); }
.format-note { font-size: 13px; color: var(--meta); }

/* ------------------------------------------------------------------ */
/* 5.6 Story timeline — "How this story got here"                      */
/*                                                                      */
/* It runs down the rail now, not across the column.                    */
/*                                                                      */
/* It used to be a 940px horizontal track with `overflow-x: auto` under  */
/* the lead, and a caption that had to say "scroll for the start ⟵"      */
/* because the start of the story was off the right-hand edge of a       */
/* left-to-right page. Vertical, in the rail, the same eleven turning    */
/* points read in the order they happened with nothing to scroll and     */
/* nothing to explain, and the column they need is the one column on the */
/* page that had spare height.                                          */
/* ------------------------------------------------------------------ */
.thread-rail { border: 1px solid var(--ink); background: var(--paper); }

/* A two-row grid: the heading and its one control on the first, the date range
   under both. The design draws no control at all here — this one earns its
   place because the card is 700px tall in a rail and a reader who does not want
   eleven turning points should be able to put them away. It is 13px grey in the
   corner rather than 12px red in the middle of the row, which is where it was
   when the card was a full-width panel with room to spare. */
.thread-head {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  column-gap: 12px; row-gap: 7px;
  padding: 22px 24px 18px; background: none; cursor: pointer; width: 100%;
  border: none; text-align: left;
}
.thread-head .meta { grid-column: 1 / -1; }
/* Red, at dek size, in the sans. It is the card saying what it is, and it is
   the only red heading in the system — the card is the one thing on the front
   page that is neither a story nor a product, and it earns the colour by being
   the only one. */
.thread-head h3 {
  font: 600 22px var(--sans); letter-spacing: 0; color: var(--red);
  text-transform: none; margin: 0;
}
.thread-head .meta { font: 400 12px/1.5 var(--sans); color: var(--meta); }
.thread-head .toggle {
  display: flex; align-items: center; gap: 7px;
  font: 500 13px var(--sans); color: var(--meta);
}
.thread-head .toggle:hover { color: var(--red); }

.thread-body { padding: 4px 24px 0; }
.thread-body[hidden] { display: none; }

/* A 27px gutter for the ring, and the connector runs down the middle of it. The
   grid is one column of nodes; the horizontal track and the two absolutely
   positioned progress rules it needed are gone with it. */
.thread-track {
  position: relative; display: flex; flex-direction: column; min-width: 0;
}

.thread-node {
  position: relative; padding: 0 0 0 27px;
  padding-bottom: 20px;
}
/* The connector, drawn by the node rather than by the track, so it stops of its
   own accord at the last one. It starts below the ring and ends at the next
   ring's top edge — a line that ran under the ring would show through it. */
.thread-node::before {
  content: ""; position: absolute; left: 6px; top: 20px; bottom: 0;
  width: 1px; background: var(--ink);
}
.thread-node:last-child { padding-bottom: 0; }
.thread-node:last-child::before { display: none; }

.thread-dot {
  position: absolute; left: 0; top: 3px;
  display: block; width: 13px; height: 13px; border-radius: 50%;
  background: var(--paper); border: 2px solid var(--ink); margin: 0;
}
.thread-node.is-current .thread-dot {
  background: var(--red); border-color: var(--red);
  box-shadow: 0 0 0 4px rgba(236, 31, 40, .16);
}

/* Date, role, headline: three lines, 16 / 16 / 18, and the weight is what
   separates the first two rather than the size. The role used to be coloured —
   red for a start, gold for a document obtained — which put three accent
   colours in a card whose heading is already red. The date carries the colour
   now, and only when the node is the one the story is standing at. */
.thread-date { font: 700 16px var(--sans); letter-spacing: 0; color: var(--meta); margin-bottom: 4px; }
.thread-role {
  font: 400 16px var(--sans); letter-spacing: .01em; color: var(--meta);
  text-transform: uppercase; margin-bottom: 4px;
}
.thread-role.role-start, .thread-role.role-now { font-weight: 700; color: var(--meta); }
.thread-role.role-obtained { font-weight: 400; color: var(--meta); }
.thread-node.is-current .thread-date { color: var(--red); }
.thread-headline { font: 700 18px/1.3 var(--sans); margin-bottom: 5px; }
.thread-source { font: 400 13px var(--sans); color: var(--meta); }

/* The foot stacks, because it is 24px of rail wide rather than a column. The
   note went the way of the format row's: "every turning point links to the
   reporting it came from" is a sentence about the card, printed inside the
   card, under the links it is describing. */
.thread-foot {
  display: flex; flex-direction: column; gap: 10px; margin: 0 24px;
  padding: 20px 0 22px; border-top: 1px solid var(--rule);
}
.thread-foot .note { display: none; }

/* 5.6 Room for disagreement — gold left rule, on every hero */
.disagreement { border-left: 3px solid var(--gold); padding: 0 0 0 21px; max-width: 92ch; }
.disagreement .label {
  font: 700 16px var(--sans); letter-spacing: .02em; color: var(--gold); margin-bottom: 8px;
}
.disagreement p { font: 400 18px/1.45 var(--sans); color: var(--ink); margin: 0; }

/* Rails */
.rail-card { border: 1px solid var(--ink); padding: 20px; }
.rail-head {
  font: 700 14px var(--sans); letter-spacing: .07em;
  padding-bottom: 10px; border-bottom: 1px solid var(--ink); margin-bottom: 14px;
}
.rail-head-logo { padding-bottom: 10px; border-bottom: 1px solid var(--ink); margin-bottom: 14px; }
.rail-list { display: flex; flex-direction: column; gap: 13px; }
.hairline { height: 1px; background: var(--rule); }
/* `place-items: center` centres the box; `text-align: center` centres the words
   inside it, and without the second one a label that wraps — which
   "ADVERTISEMENT · 300x600 MINIMUM" does in any column under about 420px — sets
   ragged against the left edge of a box it is supposed to be in the middle of. */
.ad {
  background: #fff; border: 1px solid var(--rule-strong); width: 100%;
  display: grid; place-items: center; padding: 0 16px;
  font: 400 22px var(--sans); color: #D1D1D1; letter-spacing: 0; text-align: center;
}

/* A rail always runs out of content before the main column does. The last ad
   in a rail grows into whatever is left, so the tail is never an empty strip.
   The slot's own aspect-ratio box is the flex base size, which makes the
   declared size (300x250 unless the slot says otherwise) the floor and not
   the ceiling — a lone ad in a long rail simply runs taller.

   Growth stops at one screen: past that the box is taller than any creative
   sold for it, so a contained image would float in empty bands. Whatever
   height is left over is travelled by sticking the unit instead. */
.split > .rail > .ad-unit:last-child,
.split > .rail > .ad:last-child,
.story-rail > .ad-unit:last-child,
.story-rail > .ad:last-child { flex: 1 0 auto; position: sticky; }

.split > .rail > .ad-unit:last-child,
.split > .rail > .ad:last-child { max-height: calc(100vh - 48px); top: 24px; }

.story-rail > .ad-unit:last-child,
.story-rail > .ad:last-child { max-height: calc(100vh - 116px); top: 92px; }

.ad-unit > .ad-link { flex: 1 0 auto; min-height: 0; }

/* Stacked rails have no leftover height to fill — hold the declared size. */
@media (max-width: 1120px) {
  .split > .rail > .ad-unit:last-child,
  .split > .rail > .ad:last-child,
  .story-rail > .ad-unit:last-child,
  .story-rail > .ad:last-child { flex: 0 0 auto; position: static; max-height: none; }
}

/* PT24 Daily. The one rail card that is a product rather than a list, and the
   only one carrying an audio transport — so it is the one card allowed to be
   drawn in the house red rather than in ink. The accent stays a signal: a
   single bordered card and a single 38px button on a page whose every other
   rule is --ink. */
.rail-card.is-daily { border-color: var(--red); }
.daily-play {
  width: 38px; height: 38px; flex: none; display: grid; place-items: center;
  background: var(--red); color: #fff; border: none; border-radius: 50%;
  font-size: 11px; cursor: pointer;
}
.progress { flex: 1; height: 2px; background: var(--rule); display: block; }
.progress > span { display: block; height: 2px; background: var(--gold); }

.follow-item { display: flex; align-items: baseline; gap: 10px; }
.follow-item .dot { width: 7px; height: 7px; flex: none; transform: translateY(-2px); background: var(--rule-heavy); }
.follow-item.is-live .dot { background: var(--red); }
.follow-item .title { flex: 1; font: 500 16px/1.35 var(--sans); }
.follow-item .count { font: 400 13px var(--sans); color: var(--meta); }
.follow-item.is-live .count { color: var(--red); }

/* 5.7 Day timeline — 64px | 1px | 1fr, nodes pinned at left:-25px */
.section-head { display: flex; align-items: center; gap: 14px; margin-bottom: 24px; }
.section-head h2 { font: 700 14px var(--sans); letter-spacing: .1em; margin: 0; }
.section-head .note { font: 400 14px var(--sans); color: var(--meta); }
.section-head .fill { flex: 1; height: 1px; background: var(--rule-strong); }
.filter-chips { display: flex; gap: 6px; }
.filter-chip {
  font: 400 14px var(--sans); border: 1px solid var(--rule-strong); border-radius: 0;
  padding: 4px 11px; color: var(--meta); background: none; cursor: pointer;
}
.filter-chip.is-active { background: var(--ink); color: var(--paper); border-color: var(--ink); font-weight: 600; }

.spine { display: grid; grid-template-columns: 58px 1px 1fr; column-gap: 20px; }
.spine-story { grid-template-columns: 64px 1px 1fr; }
.spine-time {
  grid-column: 1; font: 500 12px var(--sans); color: var(--ink-30);
  font-variant-numeric: tabular-nums; padding-top: 2px;
}
.spine-time.is-live { color: var(--red); font-weight: 600; }
.spine-line { grid-column: 2; background: var(--rule); }
.spine-entry { grid-column: 3; padding-bottom: 26px; position: relative; }

/* The date rule in the All river. It sits in the entry column so it lines up
   with the headlines rather than with the times, and the rule runs to the
   right edge — a midnight is a break in the river, not an entry in it. */
.spine-daybreak {
  grid-column: 3; display: flex; align-items: center; gap: 14px;
  padding-bottom: 18px;
}
.spine-daybreak span {
  font: 700 11px var(--sans); letter-spacing: .1em; color: var(--ink-45);
  text-transform: uppercase; flex: none;
}
.spine-daybreak::after {
  content: ""; flex: 1; height: 1px; background: var(--rule);
}

/* The All river's pager. A link, not a fetch-and-append: the river has an
   address at every depth this way. */
.all-more {
  display: flex; align-items: center; gap: 14px;
  padding-top: 20px; border-top: 1px solid var(--rule); margin-top: 6px;
}
.all-more .note { font-size: 12px; color: var(--ink-30); }
.spine-node {
  position: absolute; left: -25px; top: 6px; width: 9px; height: 9px;
  border-radius: 50%; border: 2px solid var(--paper);
}
.entry-row { display: flex; gap: 22px; }
.entry-row .body { flex: 1; }
.entry-thumb { width: 212px; aspect-ratio: 4/3; background: var(--slot); border: 1px solid var(--rule); flex: none; overflow: hidden; }
.entry-thumb-sm { width: 160px; }
.entry-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

h3.entry-lead { font: 500 28px/1.14 var(--serif); margin: 0 0 9px; letter-spacing: 0; }
h3.entry { font: 500 22px/1.18 var(--serif); margin: 0 0 8px; }
.entry-standfirst { font: 400 13.5px/1.55 var(--sans); color: var(--ink-70); margin: 0 0 9px; max-width: 84ch; }
.entry-standfirst.lead { font-size: 14px; max-width: 84ch; margin-bottom: 10px; }
.entry-meta { display: flex; align-items: center; gap: 10px; font-size: 11.5px; color: var(--ink-30); flex-wrap: wrap; }
.entry-meta .thread-link { color: var(--red); font-weight: 600; }
.tag-gold {
  border: 1px solid var(--gold); color: var(--gold); font-weight: 600;
  border-radius: 2px; padding: 1px 6px; font-size: 10px; letter-spacing: .06em;
}

/* Nukkad — ink band with 9:14 swipe cards */
.nukkad { background: var(--ink); color: var(--paper); padding: 18px 20px; }
.nukkad-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.nukkad-head .blurb { font-size: 11.5px; color: var(--knock-55); }
.nukkad-head .swipe { margin-left: auto; font-size: 11.5px; color: var(--knock-55); }
.nukkad-cards { display: flex; gap: 12px; overflow-x: auto; }
.nukkad-card {
  width: 150px; aspect-ratio: 9/14; background: var(--dark-inset);
  border: 1px solid var(--knock-14); padding: 12px;
  display: flex; flex-direction: column; justify-content: flex-end; flex: none;
  color: var(--paper);
}
.nukkad-card .num { font: 600 10px var(--sans); color: var(--red); letter-spacing: .08em; margin-bottom: 6px; }
.nukkad-card .title { font: 500 17px/1.25 var(--serif); }
.nukkad-card.is-ghost { opacity: .45; }
.nukkad-more { flex: 1; display: grid; place-items: center; font-size: 11.5px; color: var(--knock-50); min-width: 80px; }

/* The Sawaal page is .split with its own top padding; everything else about the
   main-plus-rail behaviour, including stacking below 1120px, it inherits. */
.sawaal-split { padding-top: 34px; }

/* 5.8 Sawaal */
.sawaal-block {
  border: 1px solid var(--rule); background: #fff; padding: 18px 20px;
  display: flex; gap: 20px; align-items: center;
}
.sawaal-block .body { flex: 1; }
.sawaal-head { display: flex; align-items: center; gap: 12px; margin-bottom: 9px; }
.sawaal-kicker { font: 700 11px var(--sans); letter-spacing: .09em; color: var(--red); }
.sawaal-block h3 { font: 500 26px/1.16 var(--serif); margin: 0 0 10px; }
.sawaal-block p { font: 400 13.5px/1.55 var(--sans); color: var(--ink-70); margin: 0 0 12px; max-width: 80ch; }
.sawaal-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.btn-red {
  background: var(--red); color: #fff; border: none; border-radius: 2px;
  padding: 8px 15px; font: 600 12px var(--sans); cursor: pointer;
}
.btn-red:disabled { background: var(--ink-18); cursor: default; }
.sawaal-count { font-size: 11.5px; color: var(--ink-30); }
.sawaal-thumb { width: 170px; aspect-ratio: 1; background: var(--slot); border: 1px solid var(--rule); flex: none; overflow: hidden; }
.sawaal-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ==================================================================== */
/* 5.7 The day mosaic                                                    */
/*                                                                       */
/* The front page's river. It used to be the spine above — a time gutter,*/
/* a hairline and one recipe repeated: headline left, 212px thumbnail    */
/* parked at the right margin. The pictures were the last thing in every */
/* row, which is the last place the eye goes, and a headline-only entry  */
/* was shorter than its own thumbnail, so the day ran as a column of     */
/* half-empty bands.                                                     */
/*                                                                       */
/* Now: the same stories in the same order, packed by presentation.py    */
/* into rows of four shapes, picture first. Hierarchy is carried by how  */
/* much room a story gets rather than by four points of type, and the    */
/* hairline that used to run vertically down the gutter now runs         */
/* horizontally between rows, which is where a broadsheet puts it.       */
/*                                                                       */
/* The tile is one piece of markup at every size. Only the grid changes. */
/* ==================================================================== */
.day-mosaic {
  display: flex; flex-direction: column;
  /* Queried against the main column, not the viewport. Between 1121px and
     1280px the rail still takes its 380px, so the column is narrower at
     1200px of viewport than it is at 1100px where the rail has stacked —
     a viewport media query would lay the mosaic out wider exactly where
     there is less room for it. The column knows its own width. */
  container-type: inline-size;
}

.mosaic-row { display: grid; gap: 26px; align-items: start; }
/* Ink, like every other structural rule on the page. A warm near-white
   hairline between two rows of the river separated them the way a fold in a
   sheet of paper does; the design rules the day off between one row and the
   next, and that rule is what carries the horizontal beat. */
.mosaic-row + .mosaic-row {
  margin-top: 32px; padding-top: 32px; border-top: 1px solid var(--ink);
}

/* Two hero shapes, mirrored, and two fillers. The mirror is why the day does
   not read as a template: the same three-story row lands picture-left on one
   turn and picture-right on the next. */
.mosaic-row.is-feature      { grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr); }
.mosaic-row.is-feature-flip { grid-template-columns: minmax(0, 1fr) minmax(0, 1.9fr); }
.mosaic-row.is-triptych     { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.mosaic-row.is-duo          { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.mosaic-row.is-wide,
.mosaic-row.is-band         { grid-template-columns: minmax(0, 1fr); }

.mosaic-stack { display: flex; flex-direction: column; gap: 22px; min-width: 0; }

.mosaic-foot { margin-top: 30px; padding-top: 26px; border-top: 1px solid var(--rule); padding-bottom: 34px; }

.mosaic-daybreak {
  display: flex; align-items: center; gap: 14px;
  margin-top: 34px; padding-bottom: 4px;
}
.mosaic-daybreak:first-child { margin-top: 0; }
.mosaic-daybreak span {
  font: 700 13px var(--sans); letter-spacing: .1em; color: var(--meta);
  text-transform: uppercase; flex: none;
}
.mosaic-daybreak::after { content: ""; flex: 1; height: 1px; background: var(--ink); }
/* A date rule is its own separator; it does not also need the row hairline. */
.mosaic-daybreak + .mosaic-row { border-top: none; margin-top: 18px; padding-top: 0; }

.mosaic-none {
  padding: 40px 0; text-align: center; font: 400 16px var(--sans); color: var(--meta);
}

/* --- the tile ------------------------------------------------------- */
.day-tile { display: flex; flex-direction: column; min-width: 0; }
.day-link { display: block; min-width: 0; color: inherit; }
.day-frame { position: relative; }

.day-shot {
  position: relative; margin: 0 0 14px; aspect-ratio: 16/9;
  background: var(--slot); border: none; overflow: hidden;
}
.day-shot img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: opacity 140ms ease-out;
}
@media (prefers-reduced-motion: reduce) { .day-shot img { transition: none; } }

/* A lead filed without a crop gets the desk's name set on a hatched plate,
   not an empty grey rectangle. The difference matters: a blank box in a grid
   of photographs reads as an image that failed to load, and a reader who
   thinks the page is broken stops scrolling it. */
.day-shot-blank {
  display: grid; place-items: center; width: 100%; height: 100%;
  background:
    repeating-linear-gradient(135deg, transparent 0 9px, rgba(20, 18, 15, .035) 9px 10px),
    var(--wash);
  font: 700 11px var(--sans); letter-spacing: .12em; text-transform: uppercase;
  /* --ink-30 and not --ink-18: this is a word a reader is meant to read, and
     --ink-18 is documented up at the top of this file as furniture — it holds
     3:1 and no more. --ink-30 clears 4.5:1 on --wash. */
  color: var(--ink-30); text-align: center; padding: 0 12px;
}
/* Same plate at lead size. It is forty times the area of a tile's, so the
   hatch stays put and only the type steps up. */
.media > .day-shot-blank { font-size: 13.5px; letter-spacing: .18em; }

/* LIVE keeps its red ground and its pulse here for the same reason it keeps
   them in the lead: it is the one thing on a tile that is a signal rather
   than a fact. On the picture, because that is what a reader sees first. */
.day-flag {
  position: absolute; left: 0; top: 0; display: inline-flex; align-items: center;
  gap: 5px; background: var(--red); color: #fff;
  font: 600 9.5px var(--sans); letter-spacing: .1em; padding: 4px 8px;
}
.day-flag .dot { width: 5px; height: 5px; background: #fff; }

/* Clock and desk, above the headline. This is what is left of the spine: the
   times still run down the page newest first, they are simply attached to the
   story they belong to rather than to a gutter. The tick carries the entry's
   node colour, which is the one the spine dot used to carry. */
/* Desk and clock, over the headline, in the same 14px neutral grey the lead's
   provenance line uses. It used to be 11px caps at .07em, which made a tile's
   kicker the one piece of type on the page set like a label rather than like
   small print — and the day is a river of small print with headlines in it. */
.day-kicker {
  display: flex; align-items: center; gap: 9px; margin-bottom: 10px;
  font: 400 14px var(--sans); letter-spacing: 0; text-transform: none;
  color: var(--meta);
}
.day-kicker .tick { width: 14px; height: 2px; flex: none; }
.day-kicker .desk { font-weight: 500; color: var(--meta); text-transform: uppercase; letter-spacing: .05em; font-size: 12px; }
.day-kicker time { font-weight: 400; letter-spacing: 0; color: var(--meta); }
.day-kicker .desk + time::before { content: "\00b7"; margin-right: 9px; color: var(--meta); }

/* No tracking. The negative letter-spacing was there to tighten a grotesque
   set large; Newsreader is drawn with its own fit and closing it up muddies
   the joins on the serifs. */
.day-headline { margin: 0; letter-spacing: 0; }
.day-standfirst {
  font: 400 16px/1.5 var(--sans); color: var(--ink-70); margin: 10px 0 0; max-width: 64ch;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}

.day-meta {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  margin-top: 11px; font: 400 14px var(--sans); color: var(--meta);
}
/* The middot is drawn by the row, not typed into the markup — but never into
   a .tag-gold, whose ::before would land inside its border and read as part
   of the label. The pill already separates itself. */
.day-meta > * + *:not(.tag-gold)::before { content: "\00b7"; margin-right: 11px; color: var(--meta); }
.day-meta .thread-link { color: var(--red); font-weight: 400; }

/* Quiet, and only one thing added. `a:hover` already turns every link on this
   site red, and the headline inherits it — the tile only has to say that the
   picture is part of the same target, which four points of light does. An
   underline on top of the red was two answers to one question. */
.day-link:hover .day-shot img { opacity: .86; }

/* --- sizes ---------------------------------------------------------- */
/* 35px in the design, at the width the design was drawn at — which is a tile
   about 490px wide, so 3.4cqi lands on it and steps down with the column
   rather than with the viewport. The floor is 22px, which is where Newsreader
   at 500 stops being a headline and starts being bold body copy.

   A brief is the same face at a fixed 22px. The mosaic used to carry four
   headline sizes between 16.5px and 27px and lean on weight to tell a feature
   from a brief; the serif does that work, so the sizes only have to say how
   much room the story was given. */
.day-tile.is-feature .day-headline { font: 500 clamp(22px, 3.4cqi + 6px, 35px)/1.12 var(--serif); text-wrap: balance; }
.day-tile.is-wide    .day-headline { font: 500 clamp(22px, 3.4cqi + 6px, 35px)/1.12 var(--serif); text-wrap: balance; }
.day-tile.is-brief   .day-headline { font: 500 22px/1.18 var(--serif); text-wrap: pretty; letter-spacing: 0; }
.day-tile.is-brief   .day-kicker   { font-size: 13px; margin-bottom: 8px; }
.day-tile.is-brief   .day-meta     { margin-top: 9px; font-size: 13px; }

/* The odd story out, handed the whole row. A 16:9 crop at full column width is
   half a metre of photograph for one headline; the panorama keeps a row a row
   and gives the day a horizontal beat it otherwise never gets. */
.day-tile.is-wide .day-shot { aspect-ratio: 2.6 / 1; }
.day-tile.is-wide .day-standfirst { -webkit-line-clamp: 2; max-width: 90ch; }

.day-band > .day-kicker { margin-bottom: 9px; }

/* --- the words, under the picture ------------------------------------ */
/* They used to be *in* it: a three-stop scrim across the bottom third of every
   crop with the headline knocked out of it in paper white.
   
   The redesign takes them out again, and the reason is the serif. A 35px
   Newsreader headline is a high-contrast letterform with hairlines in it, and a
   hairline over a photograph is the first thing to go — on a bright sky it
   thinned to nothing, and the scrim that fixed that had to be dark enough to
   throw away the bottom third of the picture. Ink on cream, under the frame,
   and the picture keeps all of itself.

   Nothing here is conditional on `has-shot` any more. The tile with a crop and
   the tile without one now set their words the same way, which is one recipe
   instead of two and one fewer rule that has to be suspended for an
   exception. */
.day-tile .day-cap { position: static; padding: 0; background: none; color: inherit; }
.day-tile .day-cap .day-headline { color: var(--ink); }
.day-tile .day-cap .day-kicker { color: var(--meta); }
.day-tile .day-standfirst { margin-top: 12px; }

/* --- filtered ---------------------------------------------------------- */
/* A desk filter cannot leave the rows standing: hide the feature out of a
   feature row and what is left is two briefs and a hole. `display: contents`
   dissolves the rows and the stacks, every surviving tile becomes a direct
   child of one flat grid, and the shapes stop mattering. Sizes flatten with
   them — a filtered river is a gallery of one desk, not a front page. */
.day-mosaic.is-filtered {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 26px; row-gap: 30px;
}
.day-mosaic.is-filtered .mosaic-row,
.day-mosaic.is-filtered .mosaic-stack { display: contents; }
.day-mosaic.is-filtered .mosaic-daybreak { grid-column: 1 / -1; margin-top: 4px; }
.day-mosaic.is-filtered .mosaic-none { grid-column: 1 / -1; }
.day-mosaic.is-filtered .day-tile.is-feature .day-headline,
.day-mosaic.is-filtered .day-tile.is-wide .day-headline { font: 500 22px/1.18 var(--serif); letter-spacing: 0; }
.day-mosaic.is-filtered .day-tile.is-wide > .day-link { display: block; }
.day-mosaic.is-filtered .day-standfirst { display: none; }
.day-mosaic.is-filtered .day-kicker { font-size: 13px; }
[data-desk-hidden] { display: none !important; }

.filter-chip .n {
  margin-left: 6px; font-weight: 500; font-variant-numeric: tabular-nums;
  color: var(--meta);
}
.filter-chip.is-active .n { color: var(--knock-62); }

/* --- the column narrows --------------------------------------------- */
/* Three columns of picture stop being pictures somewhere around 200px wide.
   Below that the row halves, and whichever tile is left over runs sideways. */
@container (max-width: 880px) {
  .mosaic-row.is-feature,
  .mosaic-row.is-feature-flip { grid-template-columns: minmax(0, 1fr); }
  .mosaic-stack { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px; }
  .mosaic-row.is-triptych { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Three into two leaves one over. It takes the whole row and the panorama
     crop with it, the same answer the wide tile gets at every width. */
  .mosaic-row.is-triptych > .day-tile:nth-child(3) { grid-column: 1 / -1; }
  .mosaic-row.is-triptych > .day-tile:nth-child(3) .day-shot,
  .day-tile.is-wide .day-shot { aspect-ratio: 2.2 / 1; }

  .day-mosaic.is-filtered { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* One column, picture on top, every tile the same shape. Below this width
   there is no mosaic to draw — there is a phone, and a phone reads a river. */
@container (max-width: 560px) {
  .mosaic-row,
  .mosaic-row.is-triptych,
  .mosaic-row.is-duo { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .mosaic-stack { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .mosaic-row + .mosaic-row { margin-top: 22px; padding-top: 22px; }
  /* No panorama on a phone — 358px of 2.2:1 is a letterbox, not a picture. */
  .day-tile.is-wide .day-shot,
  .mosaic-row.is-triptych > .day-tile:nth-child(3) .day-shot { aspect-ratio: 16/9; }
  .day-tile.is-feature .day-headline,
  .day-tile.is-wide .day-headline { font-size: 26px; }
  .day-tile.is-brief .day-headline { font-size: 21px; }
  .day-standfirst { -webkit-line-clamp: 3; }
  .day-mosaic.is-filtered { grid-template-columns: minmax(0, 1fr); row-gap: 22px; }
}

.btn-wide {
  width: 100%; background: none; border: 1px solid var(--ink); border-radius: 0;
  padding: 15px; font: 600 16px var(--ui); cursor: pointer; color: var(--ink);
}
.btn-wide:hover { background: var(--ink); color: var(--paper); }

.ward-head { display: flex; align-items: center; gap: 8px; padding-bottom: 10px; border-bottom: 1px solid var(--ink); margin-bottom: 14px; }
.ward-head .label { font: 700 14px var(--sans); letter-spacing: .07em; }

.edition-item .kicker { font-size: 12px; font-weight: 500; color: var(--red); letter-spacing: .07em; }
.edition-item .title { font: 500 19px/1.2 var(--serif); margin-top: 4px; }

/* Other news — wire copy, below the spine.
   Set deliberately quieter than the mosaic above it: smaller headlines, no
   pictures, ink rather than red on the kicker. This is the day's other
   reporting, not this desk's, and the page should read that way at a glance
   without anyone having to be told. Three columns of headlines occupy the
   width the mosaic uses for four tiles, so the block reads as a footer to the
   page rather than a second front page under the first. */
.other-news { margin-top: 34px; padding-top: 4px; border-top: 2px solid var(--ink); }
.other-news-note {
  font-size: 13px; color: #6C665C; margin: 0 0 18px; max-width: 52ch;
}
.other-news-list {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 28px; row-gap: 0;
}
.other-news-item { padding: 14px 0; border-top: 1px solid var(--rule); }
/* The first item of each column sits under the block's own rule, so its own
   top border would double it. `nth-child(-n+3)` matches the first row at three
   columns; the media queries below re-state it for the other counts. */
.other-news-list > .other-news-item:nth-child(-n + 3) { border-top: 0; padding-top: 4px; }
.other-news-link { display: block; text-decoration: none; color: inherit; }
.other-news-link:hover .title { text-decoration: underline; text-underline-offset: 2px; }
.other-news-item .kicker {
  font-size: 11px; font-weight: 500; color: #6C665C; letter-spacing: .07em;
}
.other-news-item .title {
  display: block; font: 500 17px/1.25 var(--serif); margin-top: 3px;
}
/* The credit is the licence this copy is published under, so it never wraps
   away to nothing and never drops below 12px. */
.other-news-credit {
  display: block; margin-top: 5px; font-size: 12px; color: #6C665C;
}

@media (max-width: 900px) {
  .other-news-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .other-news-list > .other-news-item:nth-child(-n + 3) { border-top: 1px solid var(--rule); padding-top: 14px; }
  .other-news-list > .other-news-item:nth-child(-n + 2) { border-top: 0; padding-top: 4px; }
}
@media (max-width: 600px) {
  .other-news-list { grid-template-columns: minmax(0, 1fr); }
  .other-news-list > .other-news-item:nth-child(-n + 2) { border-top: 1px solid var(--rule); padding-top: 14px; }
  .other-news-list > .other-news-item:first-child { border-top: 0; padding-top: 4px; }
}

/* ------------------------------------------------------------------ */
/* Screen 02 — story page                                              */
/* ------------------------------------------------------------------ */
.story-bar { position: sticky; top: 0; z-index: 50; background: var(--ink); color: var(--paper); }
.story-bar .shell-story { height: 52px; display: flex; align-items: center; gap: 16px; }
.story-bar .vr { width: 1px; height: 20px; background: rgba(251,249,245,.2); }
.story-bar .title {
  font-size: 13px; font-weight: 500; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis;
}
.story-bar .actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.btn-knock {
  background: none; border: 1px solid var(--knock-28); color: var(--paper);
  border-radius: 2px; padding: 6px 12px; font: 500 12px var(--sans); cursor: pointer;
}
.btn-follow {
  background: var(--red); border: none; color: #fff; border-radius: 2px;
  padding: 7px 13px; font: 600 12px var(--sans); cursor: pointer;
}
.btn-follow.is-off { background: none; border: 1px solid var(--knock-28); color: var(--paper); }
/* Reading progress: the one place red is allowed to move. */
.read-progress { height: 2px; background: var(--knock-12); }
.read-progress > span { display: block; height: 2px; width: 0; background: var(--red); }

.story-grid { display: grid; grid-template-columns: 260px minmax(0, 1fr) var(--rail-wide); gap: 0; padding-bottom: 56px; }
.thread-nav {
  padding: 34px 26px 0 0; border-right: 1px solid var(--rule);
  position: sticky; top: 78px; align-self: start; height: fit-content;
}
.thread-nav .head { font: 700 11px var(--sans); letter-spacing: .09em; color: var(--ink-30); margin-bottom: 14px; }
.thread-nav .items { display: flex; flex-direction: column; font-size: 12.5px; }
.thread-nav a {
  display: flex; gap: 10px; padding: 7px 0 7px 11px; margin-left: -13px;
  border-left: 2px solid transparent; color: var(--ink-45);
}
.thread-nav a.is-active { border-left-color: var(--red); font-weight: 600; color: var(--ink); }
.thread-nav a.is-active .t { color: var(--red); }
.thread-nav .t { font-variant-numeric: tabular-nums; }
.thread-nav .more { padding: 7px 0; color: var(--ink-45); font-size: 11.5px; background: none; border: none; cursor: pointer; text-align: left; }
.thread-started { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--rule); }
.thread-started .head { font: 700 11px var(--sans); letter-spacing: .09em; color: var(--ink-30); margin-bottom: 10px; }
.thread-started .body { font-size: 12.5px; color: var(--ink-70); line-height: 1.5; }
.thread-started .muted { color: var(--ink-30); }

.story-main { padding: 34px 40px 0 28px; border-right: 1px solid var(--ink); }
.crumbs { display: flex; align-items: center; gap: 10px; font-size: 14px; color: var(--meta); margin-bottom: 14px; flex-wrap: wrap; }
.crumbs a { color: var(--meta); }
/* The story page's own headline, in the same face and at the same weight as the
   lead it was clicked from. A reader who opens a serif headline and lands on a
   grotesque one has arrived somewhere else. */
h1.story { font: 600 44px/1.12 var(--serif); letter-spacing: -.005em; margin: 0 0 16px; text-wrap: balance; }
.story-byline {
  display: flex; align-items: center; gap: 12px;
  padding-bottom: 16px; border-bottom: 1px solid var(--rule); margin-bottom: 22px; flex-wrap: wrap;
}
.avatar { width: 34px; height: 34px; border-radius: 50%; background: var(--slot); border: 1px solid var(--rule); flex: none; overflow: hidden; }
.story-byline .who { font-size: 14px; line-height: 1.4; }
.story-byline .who .name { font-weight: 600; }
.story-byline .who .desk { color: var(--ink-30); }
.story-tags { margin-left: auto; display: flex; gap: 7px; flex-wrap: wrap; }
.tag { font: 500 12px var(--sans); letter-spacing: .06em; border: 1px solid var(--ink); border-radius: 0; padding: 3px 8px; }
.tag.gold { border-color: var(--gold); color: var(--gold); }
.tag.quiet { border-color: var(--rule-strong); color: var(--ink-45); }

/* The story's lead photograph. The frame comes from `lead_aspect` inline, so
   the crop the picture desk chose is the crop that draws; everything here is
   the room it takes in the column.

   `width: 100%` is load-bearing for the same reason it is on the front page
   hero: with an aspect-ratio and a max-height, the height becomes the definite
   axis and the width is solved back from the ratio, which leaves a tall crop
   sitting narrow in the middle of the column. Pinning the width puts the cap
   back on the height where it belongs. */
.story-lead { width: 100%; max-height: 52vh; margin-bottom: 24px; }
.story-lead .media-caption { max-width: 100%; }

/* Synchronised read-and-watch player */
.player { background: var(--ink); color: var(--paper); padding: 20px 22px; margin-bottom: 26px; }
.player-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.player-head .label { font: 700 12px var(--sans); letter-spacing: .08em; }
.player-head .note { font-size: 11.5px; color: var(--knock-50); margin-left: auto; }
.player-grid { display: grid; grid-template-columns: 1.25fr 1fr; gap: 18px; }
.player-well {
  aspect-ratio: 16/9; background: var(--dark-inset); border: 1px solid var(--knock-14);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px; margin-bottom: 10px;
}
.player-well .chapter { font-size: 11px; color: var(--knock-42); letter-spacing: .08em; }
.scrubber { display: flex; gap: 3px; margin-bottom: 7px; }
.scrubber span { height: 4px; background: var(--knock-22); cursor: pointer; }
.scrubber span.is-active { background: var(--red); }
.player-times { display: flex; justify-content: space-between; font-size: 11px; color: var(--knock-50); }
.segments { display: flex; flex-direction: column; }
.segment { border-left: 2px solid var(--knock-16); padding: 2px 0 12px 13px; cursor: pointer; }
.segment:last-child { padding-bottom: 0; }
.segment .label { font: 600 9.5px var(--sans); letter-spacing: .1em; color: var(--knock-42); margin-bottom: 4px; }
.segment .text { font: 400 13px/1.5 var(--sans); color: var(--knock-68); }
.segment.is-active { border-left-color: var(--red); }
.segment.is-active .label { color: var(--red); }
.segment.is-active .text { color: var(--paper); }

.live-head { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; }
.live-head h2 { font: 700 14px var(--sans); letter-spacing: .1em; margin: 0; }
.live-head .fill { flex: 1; height: 1px; background: var(--rule); }
.live-head .auto { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ink-30); }
.live-head .auto .dot { width: 6px; height: 6px; background: var(--red); }

.update-badge {
  font: 700 10px var(--sans); letter-spacing: .09em; background: var(--red);
  color: #fff; padding: 2px 7px; border-radius: 2px;
}
.update-badge.analysis { background: none; color: var(--ink); border: 1px solid var(--ink); font-weight: 600; letter-spacing: .08em; }
.update-head { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; flex-wrap: wrap; }
.update-head .author { font-size: 11.5px; color: var(--ink-30); }
h3.update-lead { font: 500 27px/1.16 var(--serif); margin: 0 0 10px; }
/* The reading size. 17px, up from 14 — the whole page moved up a step and the
   one place a reader spends minutes rather than seconds cannot be the place
   that did not. `--read` is the reader's own 100/120/140% setting and it
   multiplies this, so the two rules at the foot of this file that restate the
   size have to restate 17 as well. */
.update-body { font: 400 17px/1.6 var(--sans); color: var(--ink); margin: 0 0 16px; max-width: 88ch; }
.update-body p { margin: 0 0 16px; }
.update-links { display: flex; gap: 14px; font-size: 11.5px; color: var(--ink-30); flex-wrap: wrap; }
.update-links a { color: var(--gold); font-weight: 600; }
.update-links button { background: none; border: none; padding: 0; font: inherit; color: var(--ink-30); cursor: pointer; }

/* 5.9 Fact-check — gold left rule, verdict chips */
.factcheck { border-left: 2px solid var(--gold); padding-left: 16px; }
.factcheck .desk { font: 600 10.5px var(--sans); letter-spacing: .1em; color: var(--gold); margin-bottom: 7px; }
.claims { display: flex; flex-direction: column; gap: 9px; max-width: 88ch; }
.claim { display: flex; gap: 10px; }
.verdict {
  font: 600 10px var(--sans); border-radius: 2px; padding: 2px 6px;
  height: fit-content; flex: none;
}
.verdict.v-true { color: var(--verified); border: 1px solid var(--verified); }
.verdict.v-context { color: var(--gold); border: 1px solid var(--gold); }
.verdict.v-false { color: var(--red); border: 1px solid var(--red); }
.claim .text { font: 400 13.5px/1.55 var(--sans); color: var(--ink-70); }

.ward-check { border: 1px solid var(--rule); background: #fff; padding: 14px 16px; display: flex; gap: 16px; align-items: center; }
.ward-check .thumb { width: 120px; aspect-ratio: 1; background: var(--slot); flex: none; }
.ward-check .title { font: 600 12px var(--sans); margin-bottom: 5px; }
.ward-check .copy { font-size: 12px; color: var(--ink-45); line-height: 1.5; margin-bottom: 9px; }
.ward-check .form { display: flex; gap: 7px; }
.ward-check input {
  border: 1px solid var(--rule-strong); border-radius: 2px; padding: 6px 12px;
  font-size: 12px; min-width: 150px; outline: none; font-family: inherit;
}
.ward-check input::placeholder { color: var(--ink-45); }
.btn-check { background: var(--ink); color: var(--paper); border: none; border-radius: 2px; padding: 6px 14px; font: 600 12px var(--sans); cursor: pointer; }

.corrections {
  margin-top: 30px; padding: 20px 22px; border: 1px solid var(--rule); background: #fff;
  display: flex; gap: 18px; align-items: center;
}
.corrections .label { font: 600 12px var(--sans); letter-spacing: .06em; color: var(--ink-30); margin-bottom: 6px; }
.corrections p { font: 400 13.5px/1.55 var(--sans); margin: 0 0 4px; color: var(--ink-70); }
.corrections a { font-size: 12px; font-weight: 600; color: var(--gold); white-space: nowrap; }

.story-rail { padding: 34px 0 0 28px; display: flex; flex-direction: column; gap: 26px; }
.doc-link { display: flex; gap: 9px; font-size: 15px; }
.doc-link .arrow { color: var(--gold); }

/* ------------------------------------------------------------------ */
/* Screen 03 — local hub                                               */
/* ------------------------------------------------------------------ */
.local { padding-bottom: 56px; }
.local-head { padding: 34px 0 26px; border-bottom: 1px solid var(--ink); }
.local-head-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 32px; margin-bottom: 22px; flex-wrap: wrap; }
.local-kicker { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.local-kicker .label { font: 700 13px var(--sans); letter-spacing: .1em; color: var(--red); }
h1.local { font: 600 44px/1.12 var(--serif); letter-spacing: -.005em; margin: 0 0 10px; }
.local-head p { font: 400 17px/1.5 var(--sans); color: var(--ink-70); margin: 0; max-width: 80ch; }
.local-actions { display: flex; gap: 8px; flex: none; }

.area-switcher { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }
.area-switcher .label { font-size: 11.5px; color: var(--ink-30); margin-right: 5px; }
.area-pill {
  font: 500 11.5px var(--sans); border: 1px solid var(--rule-strong); border-radius: 2px;
  padding: 4px 10px; color: var(--ink-70);
}
.area-pill:hover { border-color: var(--ink); }
.area-pill.is-active { background: var(--red); color: #fff; border-color: var(--red); font-weight: 600; }
.area-pill.edition { border-color: var(--ink); color: var(--ink); }
.area-switcher .vr { width: 1px; height: 18px; background: var(--rule); margin: 0 6px; }

/* Local hub: main column plus a 380px rail with a 36px gap. */
.local-grid { display: grid; grid-template-columns: 1fr var(--rail-wide); gap: 36px; padding-top: 28px; }
.local-lead-grid {
  display: grid; grid-template-columns: 1.4fr 1fr; gap: 24px;
  padding-bottom: 26px; border-bottom: 1px solid var(--rule); margin-bottom: 26px;
}
.area-tag {
  font: 600 10px var(--sans); letter-spacing: .08em; background: var(--red);
  color: #fff; padding: 2px 7px; border-radius: 2px;
}
.area-kicker { font: 600 10px var(--sans); letter-spacing: .08em; color: var(--red); }
h2.local-lead { font: 500 32px/1.14 var(--serif); margin: 0 0 10px; letter-spacing: 0; }
.local-lead-meta { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.local-lead-meta .when { font: 400 14px var(--sans); color: var(--meta); }
.local-secondary { display: flex; flex-direction: column; gap: 18px; }
.local-secondary h3 { font: 500 21px/1.2 var(--serif); margin: 6px 0 0; }
.local-secondary .when { font: 400 13px var(--sans); color: var(--meta); }
.desk-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.desk-grid h3 { font: 500 20px/1.22 var(--serif); margin: 0 0 7px; }
.desk-grid .when { font-size: 11.5px; color: var(--ink-30); }

.sawaal-board { background: var(--ink); color: var(--paper); padding: 22px 24px; }
.sawaal-board-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.sawaal-board-head .area { font: 700 13px var(--sans); letter-spacing: .07em; }
.sawaal-board-head .note { font-size: 11.5px; color: var(--knock-55); }
.sawaal-board-head .ask { margin-left: auto; font-size: 12px; font-weight: 600; color: var(--red); }
.sawaal-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.sawaal-card { border: 1px solid var(--knock-16); padding: 14px; color: var(--paper); }
.sawaal-card .status { font: 600 10px var(--sans); letter-spacing: .08em; margin-bottom: 7px; }
.sawaal-card .q { font: 600 13.5px/1.35 var(--sans); margin-bottom: 9px; }
.sawaal-card .foot { font-size: 11px; color: var(--knock-50); }

.map-card { border: 1px solid var(--rule); background: #fff; }
.map-card .well { aspect-ratio: 1; background: var(--slot); display: grid; place-items: center; border-bottom: 1px solid var(--rule); overflow: hidden; }
.map-card .well img { width: 100%; height: 100%; object-fit: cover; }
.map-card .label { font-size: 11px; color: var(--ink-45); letter-spacing: .08em; }
.map-card .copy { padding: 14px 16px; }
.map-card .copy .title { font: 600 12.5px var(--sans); margin-bottom: 5px; }
.map-card .copy .note { font-size: 12px; color: var(--ink-45); line-height: 1.5; }

.week-list { display: flex; flex-direction: column; gap: 12px; font-size: 12.5px; }
.week-row { display: flex; gap: 12px; }
.week-row .day { font-weight: 600; flex: none; width: 44px; color: var(--ink-30); }
.week-row.is-notable .day { color: var(--red); }
.week-row .what { line-height: 1.45; }

.nearby-row { display: flex; justify-content: space-between; align-items: baseline; }
.nearby-row .name { font: 500 15px var(--sans); }
.nearby-row .count { font: 400 13px var(--sans); color: var(--meta); }
.nearby-row.is-busy .count { color: var(--red); font-weight: 600; }

/* ------------------------------------------------------------------ */
/* Footer                                                              */
/* ------------------------------------------------------------------ */
.footer { background: var(--ink); color: var(--knock-75); padding: 44px 0 32px; }
.footer .shell { max-width: var(--max-footer); }
.footer-grid {
  display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr 1fr; gap: 36px;
  padding-bottom: 32px; border-bottom: 1px solid var(--knock-14);
}
.footer p { font: 400 15px/1.55 var(--sans); color: var(--knock-62); margin: 0 0 16px; max-width: 34ch; }
.footer .charter { color: var(--gold); font-size: 14px; font-weight: 500; }
.footer h4 { font: 700 12px var(--sans); letter-spacing: .1em; color: var(--paper); margin: 0 0 14px; }
.footer-links { display: flex; flex-direction: column; gap: 9px; font-size: 15px; }

/* The second home of the language switcher. The account panel in the masthead is
   where a reader changes language; this is where a reader who has scrolled to
   the bottom of a page finds out they can. Knocked out of the ink ground rather
   than drawn in it. */
.footer .lang-switch { border-color: var(--knock-28); width: max-content; margin-top: 4px; }
.footer .lang-switch button { color: var(--knock-62); min-width: 52px; flex: none; }
.footer .lang-switch button + button { border-left-color: var(--knock-28); }
.footer .lang-switch button.is-active { background: var(--paper); color: var(--ink); }
.footer-links a { color: var(--knock-75); }
.footer-links a:hover { color: var(--paper); }
/* Install is an action, so it is a button; it has to read as one of the links. */
.footer-install {
  background: none; border: none; padding: 0; cursor: pointer; text-align: left;
  font: inherit; font-size: 15px; color: var(--knock-75);
}
.footer-install:hover { color: var(--paper); }
.footer-install[hidden] { display: none; }
.footer-base { padding-top: 22px; display: flex; gap: 24px; font-size: 13px; color: var(--knock-45); flex-wrap: wrap; }
.footer-base a { color: var(--knock-45); }
.footer-base .promise { margin-left: auto; }

/* ------------------------------------------------------------------ */
/* Empty / offline / error states — each names what failed             */
/* ------------------------------------------------------------------ */
.empty {
  border: 1px solid var(--rule); background: var(--wash-warm); padding: 22px 24px;
}
.empty .label { font: 700 12px var(--sans); letter-spacing: .1em; color: var(--meta); margin-bottom: 9px; }
.empty p { font: 400 16px/1.5 var(--sans); color: var(--ink-70); margin: 0; max-width: 56ch; }
.empty-rail { padding: 14px 0; font: 400 14px/1.5 var(--sans); color: var(--meta); }
.empty-rail p { margin: 0 0 13px; }

.toast {
  position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
  background: var(--ink); color: var(--paper); padding: 12px 20px;
  font: 500 15px var(--sans); border-radius: 0; z-index: 90;
  box-shadow: 0 2px 12px rgba(20,18,15,.2);
}
.toast[hidden] { display: none; }

/* Inline banner when the live thread moves while the reader is on the page. */
.new-updates {
  background: var(--ink); color: var(--paper); padding: 11px 16px;
  font: 500 12.5px var(--sans); margin-bottom: 16px;
}
.new-updates[hidden] { display: none; }

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

:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }

/* ------------------------------------------------------------------ */
/* Narrower desktops. The design is desktop-first at 1360px; below the  */
/* rail breakpoint the rails stack rather than compress.               */
/* ------------------------------------------------------------------ */
@media (max-width: 1120px) {
  /* minmax(0, 1fr), not 1fr: a bare 1fr floors at min-content, so a long
     headline or a 16:9 media box pushes the column wider than the screen. */
  .split, .local-grid { grid-template-columns: minmax(0, 1fr); }
  .split > .main, .split > .rail { min-width: 0; }
  .split > .main { padding-right: 0; border-right: none; }
  .split > .rail { padding-left: 0; border-top: 1px solid var(--rule); padding-top: 24px; }
  .story-grid { grid-template-columns: minmax(0, 1fr); }
  .story-grid > * { min-width: 0; }
  .entry-row .body { min-width: 0; }
  .thread-nav { position: static; border-right: none; padding: 24px 0 0; }
  .story-main { padding: 24px 0 0; border-right: none; }
  .story-rail { padding-left: 0; }
  .footer-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 820px) {
  h1.hero { font-size: 34px; }
  h1.story, h1.local { font-size: 30px; }
  .desk-grid, .sawaal-cards, .local-lead-grid { grid-template-columns: 1fr; }
  .player-grid { grid-template-columns: 1fr; }

  /* The Sawaal block was a row with a 170px thumb pinned beside a flex:1 body.
     Below about 380px that left the body under 100px, and a bare flex:1 floors
     at min-content, so one long word in the question pushed the whole document
     sideways. It stacks like every other media row on the phone. */
  .sawaal-block {
    flex-direction: column-reverse; align-items: stretch; gap: 14px; padding: 16px;
  }
  .sawaal-block .body { min-width: 0; }
  .sawaal-thumb { width: 100%; aspect-ratio: 3/2; }

  /* Two buttons cannot sit side by side on a 320px screen, and flex:none meant
     the row kept its max-content width rather than wrapping. It takes the whole
     row and the buttons share it. */
  .local-actions { flex: 1 1 100%; flex-wrap: wrap; }
  .local-actions .btn-line, .local-actions .btn-ink {
    flex: 1 1 auto; justify-content: center;
  }
  .entry-row { flex-direction: column-reverse; }
  .entry-thumb, .entry-thumb-sm { width: 100%; }
  .footer-grid { grid-template-columns: 1fr; }
}

/* ================================================================== */
/* 10. Mobile PWA                                                      */
/*                                                                     */
/* Reference device iPhone 17 Pro, 402x874 logical points. The phone    */
/* does not get a squeezed desktop masthead: it gets the app chrome     */
/* from the design package. Chrome budget is 48 + 56 + 34 = 138px,      */
/* sixteen percent of the viewport, and never more.                     */
/*                                                                     */
/* Base grid 4px. Screen gutters 18px. Card padding 12-14px.            */
/* ================================================================== */

/* The shell is desktop-only furniture. */
.mtop, .mservice, .msections, .tabbar { display: none; }

@media (max-width: 820px) {
  :root {
    --gutter: 18px;
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
    --mtop-h: 48px;
    --tabbar-h: 56px;
  }

  /* ---------------------------------------------------------------- */
  /* Nothing may exceed the viewport. The desktop masthead is a single */
  /* non-wrapping flex row roughly 970px wide, which is what pushed    */
  /* the whole document sideways before the shell existed.             */
  /*                                                                   */
  /* `clip`, never `hidden`: hidden makes body a scroll container and  */
  /* silently kills position:sticky on the top bar and the runner.     */
  /* This is a backstop for stray desk content, not the fix — the fix  */
  /* is minmax(0, 1fr) on the collapsing grids.                        */
  /* ---------------------------------------------------------------- */
  body { overflow-x: clip; }

  .masthead-row,
  .masthead .nav { display: none; }

  /* The masthead element stays: it is the containing block for the
     cities panel, which the phone edition chip still opens. */
  .masthead { border-bottom: none; }

  .shell, .shell-story {
    padding-left: calc(var(--gutter) + var(--safe-left));
    padding-right: calc(var(--gutter) + var(--safe-right));
  }

  /* Room for the fixed tab bar and the home indicator. */
  body { padding-bottom: calc(var(--tabbar-h) + var(--safe-bottom)); }

  /* ---------------------------------------------------------------- */
  /* Top bar — 48px                                                    */
  /* ---------------------------------------------------------------- */
  .mtop {
    display: flex; align-items: center; gap: 10px;
    position: sticky; top: 0; z-index: 40;
    height: calc(var(--mtop-h) + var(--safe-top));
    padding: var(--safe-top) calc(var(--gutter) + var(--safe-right)) 0 calc(var(--gutter) + var(--safe-left));
    background: var(--paper); border-bottom: 1px solid var(--ink);
  }
  /* The compact PT24 lockup. The master wordmark spent 120px of a 390px bar
     spelling PRIMETIME24 out, and the edition chip — the one control up here
     a reader actually changes — was left ellipsing "BENGALURU" to fit in
     what was left. The compact mark is the same letterforms and the same
     fist at a third of the width, and it is the mark the sub-brand lockups
     have always used at this size.

     Height drives it and the width follows the file's own ratio. The box is
     the full 48px so the target covers the whole bar. */
  .mtop-mark {
    flex: none; height: 48px; margin-left: -2px;
    display: grid; place-items: center start;
  }
  .mtop-mark .pt24-logo { width: auto; height: 26px; }

  /* Sized to its own text, not to the bar. `flex:1` made the chip a banner
     across the whole row for the sake of one city name; `0 1 auto` lets it ask
     for what it needs and give it back as the name grows.

     An auto margin on BOTH sides, so what it gives back is split evenly rather
     than pooling into one hole between the chip and the icons. Mark left, icons
     right, chip riding the space between them. */
  .mtop-edition {
    flex: 0 1 auto; min-width: 0; margin-left: auto; margin-right: auto;
    display: flex; align-items: center; gap: 5px;
    height: 30px; padding: 0 9px;
    background: none; border: 1px solid var(--rule-strong); border-radius: 100px;
    font: 600 10.5px var(--sans); letter-spacing: .07em; color: var(--ink);
    cursor: pointer; overflow: hidden;
  }
  .mtop-edition .loc { color: var(--red); flex: none; font-size: 9px; }
  .mtop-edition .txt {
    flex: 0 1 auto; min-width: 0; text-align: left; text-transform: uppercase;
    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  }
  .mtop-edition .caret { flex: none; font-size: 8px; color: var(--ink-30); }

  /* 44px minimum target, drawn on a 24px grid at 1.8px stroke. */
  .mtop-icon {
    flex: none; width: 34px; height: 34px; margin: 0 -5px;
    display: grid; place-items: center; color: var(--ink-70);
  }
  .mtop-icon svg { width: 20px; height: 20px; display: block; }

  /* ---------------------------------------------------------------- */
  /* Prime Alert runner. Collapses to a 4px red hairline after 120px    */
  /* of scroll and restores after 40px of scroll up.                    */
  /* ---------------------------------------------------------------- */
  .runner { position: sticky; top: calc(var(--mtop-h) + var(--safe-top)); z-index: 30; }
  .runner .shell { padding-left: 0; padding-right: 0; }
  /* The block bleeds to the edge because the runner's shell drops its gutter
     here — so the gutter is added to the flag's width rather than to its
     padding, which would have pushed the pulse off the centre of its own
     block by half the gutter. */
  .runner-flag { width: calc(34px + var(--gutter) + var(--safe-left)); }
  .runner-all { display: none; }

  .runner.is-collapsed {
    height: 4px; overflow: hidden; background: var(--red);
  }
  .runner.is-collapsed > * { visibility: hidden; }

  /* ---------------------------------------------------------------- */
  /* Service strip — what the utility strip carries on the desktop.     */
  /* Scrolls rather than wraps or truncates.                            */
  /* ---------------------------------------------------------------- */
  .mservice {
    display: flex; align-items: center; gap: 14px;
    padding: 7px calc(var(--gutter) + var(--safe-right)) 7px calc(var(--gutter) + var(--safe-left));
    border-bottom: 1px solid var(--rule);
    font: 500 11px var(--sans); color: var(--ink-45);
    overflow-x: auto; overflow-y: hidden; white-space: nowrap;
    -webkit-overflow-scrolling: touch;
  }
  .mservice > span { flex: none; display: flex; align-items: center; gap: 5px; }

  /* ---------------------------------------------------------------- */
  /* Sections sheet — the nav, docked. Same shape as the localities     */
  /* sheet, but it sizes to its content instead of filling the screen:  */
  /* twenty section names are not two hundred and fifty areas.          */
  /* ---------------------------------------------------------------- */
  .msections {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 75;
    display: flex; flex-direction: column;
    background: var(--paper); border-top: 1px solid var(--ink);
    box-shadow: 0 -12px 40px rgba(20, 18, 15, .18);
    max-height: calc(100dvh - var(--mtop-h) - var(--safe-top) - 8px);
  }
  /* The class rule above outranks the hidden attribute's UA display:none, so
     the closed state has to be restated. Same reason .cities[hidden] exists. */
  .msections[hidden] { display: none; }
  .msections-inner {
    flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
    padding: 2px var(--gutter) calc(18px + var(--safe-bottom));
  }
  .msections-label {
    display: block; padding: 14px 0 8px;
    font: 700 10.5px var(--sans); letter-spacing: .09em; color: var(--ink-45);
  }
  /* Two columns of 48px rows on a hairline grid. Borders carry the structure,
     so the rows are separated by rules rather than by gaps. */
  .msections-grid {
    display: grid; grid-template-columns: 1fr 1fr;
    border-top: 1px solid var(--rule); border-left: 1px solid var(--rule);
  }
  /* Three formats in three columns. At two they leave a cell short and the
     grid's bottom-right corner hangs open, which reads as a missing row. */
  .msections-grid.is-formats { grid-template-columns: repeat(3, 1fr); }
  .msections-grid a {
    display: flex; align-items: center; min-height: 48px; padding: 0 12px;
    border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
    font: 500 13px var(--sans); color: var(--ink-70);
  }
  .msections-grid a.is-active {
    font-weight: 700; color: var(--ink); background: var(--wash);
    box-shadow: inset 2px 0 0 var(--red);
  }

  /* ---------------------------------------------------------------- */
  /* Tab bar — 56px, five tabs, never more.                            */
  /* ---------------------------------------------------------------- */
  .tabbar {
    display: grid; grid-template-columns: repeat(5, 1fr);
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    height: calc(var(--tabbar-h) + var(--safe-bottom));
    padding-bottom: var(--safe-bottom);
    background: var(--paper); border-top: 1px solid var(--rule-strong);
  }
  .tabbar .tab {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    font: 600 9.5px var(--sans); letter-spacing: .07em;
    color: var(--ink-30); border-top: 2px solid transparent; margin-top: -1px;
  }
  .tabbar .tab svg { width: 22px; height: 22px; display: block; }
  .tabbar .tab.is-active { color: var(--ink); border-top-color: var(--ink); }
  .tabbar .tab:hover { color: var(--ink); }

  .tab-mark { position: relative; display: block; }
  .tab-pulse {
    position: absolute; top: -1px; right: -3px;
    width: 6px; height: 6px; border-radius: 50%; background: var(--red);
  }

  /* ---------------------------------------------------------------- */
  /* Cities and areas — the same search-first pattern, presented as a   */
  /* bottom sheet. One pattern on both platforms.                       */
  /* ---------------------------------------------------------------- */
  /* ---------------------------------------------------------------- */
  /* The area picker is a three-part sheet, not one long scroll: a grip  */
  /* bar, a body that scrolls, and a Save row pinned to the bottom.      */
  /*                                                                    */
  /* It used to be a single scrolling column with Save at the end, which */
  /* meant an edition with 250 areas put the only way to commit a        */
  /* selection several screens below the fold. Search went with it. Both */
  /* are now fixed in place, so the reader scrolls to browse and never   */
  /* to act.                                                            */
  /* ---------------------------------------------------------------- */
  /* z-index sits above the scrim (70), below the install and alert sheets. */
  .cities {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 75;
    display: flex; flex-direction: column;
    /* The sheet itself no longer scrolls; its body does. */
    overflow: hidden; border-top: 1px solid var(--ink);
    box-shadow: 0 -12px 40px rgba(20, 18, 15, .18);
    /* Never tall enough to swallow the top bar: the edition chip that opened
       the sheet has to stay reachable, and it is the second way out. dvh so a
       mobile browser's collapsing toolbar cannot clip the pinned footer. */
    height: calc(100dvh - var(--mtop-h) - var(--safe-top) - 8px);
    max-height: calc(100dvh - var(--mtop-h) - var(--safe-top) - 8px);
  }
  .cities-sheet-bar {
    flex: none;
    display: flex; align-items: center; justify-content: center;
    padding: 8px 8px 4px; background: var(--wash);
  }
  .cities-sheet-bar .grip {
    width: 38px; height: 4px; border-radius: 100px; background: var(--rule-heavy);
  }
  .cities-close {
    position: absolute; right: 6px; top: 2px;
    width: 44px; height: 44px; display: grid; place-items: center;
    background: none; border: none; cursor: pointer; color: var(--ink-45);
  }
  .cities-close svg { width: 20px; height: 20px; display: block; }

  /* The body is the only scroller in the sheet. It carries its own gutter now:
     .shell came off .cities-inner when the desktop half became a modal, and a
     modal is not the width of the page. */
  .cities-inner {
    flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
    display: flex; flex-direction: column;
    padding: 0 var(--gutter);
  }

  /* Search first, and it stays. It is the fast way into 250 areas, so a picker
     whose search scrolls away is a picker you have to scroll to use. */
  .area-search-row {
    order: 1; position: sticky; top: 0; z-index: 2;
    flex: none; flex-direction: column; align-items: stretch; gap: 6px;
    background: var(--wash); padding: 12px 0 10px; margin-bottom: 0;
    border-bottom: 1px solid var(--rule);
  }
  .area-search { max-width: none; }
  /* The affordance is a keyboard one; a phone has no "/" key to print. */
  .area-search .slash { display: none; }

  /* What is already chosen, directly under the search. */
  .chip-rows { order: 2; flex: none; gap: 10px; margin: 12px 0 14px; }

  /* 28px is inside the 24px AA minimum but well under a comfortable thumb, and
     these are the primary control of the whole sheet. */
  .chip { padding: 10px 16px; min-height: 40px; }

  /* Every area, grouped by zone, open. "Browse all" was a tap that stood
     between the reader and the only thing this sheet is for. */
  .zone-groups { order: 3; display: flex; margin-bottom: 16px; }
  .chip-more { display: none; }

  /* The heading comes last in the area picker: on a phone the reader tapped a
     button that already named this dialog, and the ask can wait until after
     the thing that answers it. In the edition picker it stays on top, because
     the days below it need the sentence that frames them. */
  .cities-head {
    order: 4; flex: none; flex-direction: column; align-items: flex-start; gap: 10px;
    padding-top: 14px; border-top: 1px solid var(--rule); margin-bottom: 14px;
    padding-bottom: 0; border-bottom: none;
  }
  .cities-head h3 { font-size: 16px; }
  /* The lockup is 40px of decoration in a sheet that needs its height. */
  .cities-head .pt24-logo { display: none; }

  .edition-modal .cities-head {
    order: 0; padding-top: 16px; border-top: none;
    padding-bottom: 14px; border-bottom: 1px solid var(--rule-strong);
  }
  .edition-modal .chip-rows { order: 1; }
  .edition-modal .archive-head { order: 2; }
  .edition-modal .archive-days { order: 3; }
  .edition-modal .edition-areas { order: 4; margin-bottom: 18px; }
  /* "Browse all areas by zone" is a desktop disclosure; on the phone every
     zone is already open. The link into the area picker is not that button and
     must survive. */
  .edition-modal .chip-more { display: inline-flex; }

  /* Save never scrolls away. */
  .cities-foot {
    flex: none; display: flex; align-items: center; gap: 12px;
    background: var(--wash);
    padding: 10px var(--gutter) calc(10px + var(--safe-bottom));
    border-top: 1px solid var(--rule-strong);
  }
  /* The promise is one line of reassurance and the phone needs the height for
     areas. The button carries the count, which is the part being checked. */
  .cities-foot p { display: none; }

  /* ---------------------------------------------------------------- */
  /* Type — mobile scale. Nothing below 10px.                          */
  /* ---------------------------------------------------------------- */
  h1.hero { font-size: 28px; line-height: 1.14; font-weight: 600; letter-spacing: 0; }
  h1.story, h1.local { font-size: 27px; line-height: 1.14; }
  h2.local-lead, h3.entry-lead { font-size: 23px; line-height: 1.18; }
  .section-head h2 { font-size: 13px; }
  h3.entry, h3.update-lead, .sawaal-block h3 { font-size: 18px; line-height: 1.28; font-weight: 600; }
  .standfirst { font-size: 17px; line-height: 1.5; }
  .entry-standfirst, .entry-standfirst.lead { font-size: 16px; line-height: 1.5; }
  .entry-meta, .update-links, .crumbs { font-size: 13px; }

  /* Reading type is not river type: the article body is set to read. */
  .update-body, .update-body p, .factcheck .claim, .disagreement p {
    font-size: 17px; line-height: 1.65;
  }

  /* Every measure cap is a desktop concern; the phone column is the cap. */
  .standfirst, .entry-standfirst, .entry-standfirst.lead, .update-body,
  .claims, .disagreement, .sawaal-block p, .local-head p, .empty p, .footer p {
    max-width: none;
  }

  /* ---------------------------------------------------------------- */
  /* Layout — one column, no nested vertical scrollers.                */
  /* ---------------------------------------------------------------- */
  .hero-section > .main { padding: 18px 0 20px; }
  .hero-section > .rail { padding: 20px 0 24px; }
  .split > .main { padding-right: 0; }
  .front { padding-bottom: 32px; }

  .story-bar .title { display: none; }
  .story-main { padding: 18px 0 0; }
  .story-rail { padding: 24px 0 0; }
  .thread-nav { padding-top: 18px; }

  /* Label row, hairline, content — the rhythm survives, the row wraps.

     The hairline is the divider, and it used to be display:none here, which is
     why the phone lost every section rule: a wrapping flex row gave a `flex:1`
     spacer nothing to fill, so it collapsed to nothing and was hidden rather
     than fixed. Given its own full-width row it reads as the divider it is, and
     it aligns to the same gutters as the content above and below it because
     .shell owns both edges. */
  .section-head { flex-wrap: wrap; gap: 8px 12px; margin-bottom: 16px; }
  .section-head .fill {
    order: 99; flex: 0 0 100%; width: 100%; height: 1px;
    background: var(--rule-strong); margin-top: 2px;
  }
  .section-head .note { font-size: 11.5px; }
  /* Six desks do not fit across a 390px screen, and letting them wrap puts
     three rows of chips above the river. They scroll instead, which is what a
     tab row does everywhere else a reader has met one. Ordered below the
     heading and above the hairline, so the divider still closes the block. */
  .section-head .filter-chips {
    order: 98; flex: 0 0 100%; width: 100%;
    flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
    padding-bottom: 3px; scrollbar-width: none;
  }
  .section-head .filter-chips::-webkit-scrollbar { display: none; }
  .section-head .filter-chip { flex: none; }
  /* Row gap first, column gap second: the provenance line wraps to two or
     three rows on a 414px screen, and at a flat 12px the wrapped rows sat as
     far apart as the words within them. */
  /* Row gap first, column gap second: the provenance line wraps on a 414px
     screen, and at a flat 12px the wrapped rows sat as far apart as the words
     within them.

     The byline shares whatever row it lands on. It used to be given
     flex-basis:100%, which was harmless while this row sat *under* the headline
     and the standfirst — it is above them now, and three rows of 14px grey
     between the LIVE chip and the name of the story is a lead that opens with
     its own filing metadata. Two rows is what the same four items take when the
     last of them is allowed to follow the third. */
  .hero-meta { gap: 6px 10px; }
  .hero-meta .byline { margin-left: 0; }

  /* 22px is a desktop rail's label. In a full-width column it is the largest
     type on a screen with no story on it. */
  .ad { font-size: 16px; }

  /* The timeline spine narrows; the time column stays tabular. */
  .spine { grid-template-columns: 44px 1px minmax(0, 1fr); column-gap: 12px; }
  .spine-story { grid-template-columns: 44px 1px minmax(0, 1fr); }
  .spine-node { left: -17px; }
  .spine-entry { padding-bottom: 20px; }
  .entry-row { gap: 12px; }
  .entry-thumb, .entry-thumb-sm { aspect-ratio: 3/2; }

  /* The timeline needs nothing here any more.

     It used to need a good deal: the desktop drew it as a 940px horizontal
     track and the phone had to turn that into a swipeable strip of cards at 88%
     of the viewport, fighting an inline grid-template-columns with !important
     to do it. The track is a vertical flex column at every width now, which is
     the reading order the phone wanted in the first place — so all of that is
     gone and what is left is padding.

     Nothing below sets grid properties on .thread-track. If a rule reappears
     that does, it is a rule written against the old horizontal track. */
  .thread-body { padding: 4px 16px 0; }
  .thread-head { padding: 18px 16px 14px; }
  .thread-head h3 { font-size: 19px; }
  .thread-head .meta { font-size: 11.5px; }
  .thread-node { padding-left: 24px; padding-bottom: 18px; }
  .thread-date, .thread-role { font-size: 14px; }
  .thread-headline { font-size: 16px; }
  .thread-foot { margin: 0 16px; padding: 16px 0 18px; }

  .player { padding: 14px; }
  .factcheck, .disagreement { padding-left: 12px; }

  /* Touch — 44px minimum, chips 36px with 8px gaps. */
  .btn-ink, .btn-line, .btn-red, .btn-save, .btn-subscribe, .btn-follow, .btn-knock {
    min-height: 44px; padding-left: 16px; padding-right: 16px;
  }
  .format-row { gap: 8px; }
  .chip, .filter-chip, .area-chip { min-height: 36px; }
  .chip-row { gap: 8px; }
  .filter-chips { gap: 8px; }

  /* Footer sits above the tab bar and stops pretending to be a grid. */
  .footer { padding: 28px 0 24px; }
  .footer-grid { gap: 24px; }
  .footer-base { flex-direction: column; align-items: flex-start; gap: 8px; }
  .footer-base .promise { margin-left: 0; }
}

/* The 34px home indicator area is the tab bar's, not the content's. */
@supports (padding: env(safe-area-inset-bottom)) {
  @media (max-width: 820px) {
    .tabbar { padding-bottom: max(env(safe-area-inset-bottom), 0px); }
  }
}

/* ------------------------------------------------------------------ */
/* Advertising. Disclosure is never optional: a commercial unit always  */
/* carries its label, and the label reads before the creative does.     */
/* ------------------------------------------------------------------ */
.ad-unit { display: flex; flex-direction: column; gap: 6px; }

.ad-disclosure {
  font: 600 10px var(--sans); letter-spacing: .12em; color: var(--ink-30);
  text-transform: uppercase;
  padding-bottom: 5px; border-bottom: 1px solid var(--rule);
}

.ad-link {
  display: grid; place-items: center; overflow: hidden; width: 100%;
  background: var(--ad-wash); border: 1px solid var(--rule);
}
.ad-link:hover { border-color: var(--rule-strong); }
/* `contain`, not `cover`: a stretched slot must not crop the creative down to
   a vertical slice. Identical to cover when the creative matches the slot. */
.ad-link img { width: 100%; height: 100%; object-fit: contain; display: block; }

.ad-fallback {
  font: 600 12px var(--sans); color: var(--ink-45); letter-spacing: .04em;
  padding: 0 16px; text-align: center;
}

/* ------------------------------------------------------------------ */
/* 10. PWA surfaces                                                     */
/*                                                                      */
/* First run, install, the alert contract, the offline banner and pull   */
/* to refresh. All of it is inert markup until the script decides it is  */
/* needed, so a reader who never installs never meets any of it.         */
/* ------------------------------------------------------------------ */

/* The centred modal. Scoped to the desktop side rather than left unqualified:
   this block is appended after the phone block, so a bare rule would win
   everywhere and the bottom sheet would lose its docking. */
@media (min-width: 821px) {
  /* Above the scrim (70), below the install and alert sheets — the same
     stacking order the phone sheet uses. */
  .cities {
    position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
    z-index: 75; width: min(760px, calc(100vw - 80px));
    max-height: min(680px, calc(100vh - 80px));
    display: flex; flex-direction: column; overflow: hidden;
    border: 1px solid var(--ink); border-radius: 2px;
    box-shadow: 0 24px 60px rgba(20, 18, 15, .22);
  }
  .edition-modal { width: min(680px, calc(100vw - 80px)); }

  /* The body is the only scroller. A fixed height with the head and the Save
     row outside it is what stops "Browse all areas by zone" from growing the
     dialog past the viewport — the zone list scrolls in place now instead of
     pushing the page down a second time. */
  .cities-inner {
    flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
    padding: 22px 26px 24px;
  }

  /* The grip is a phone gesture and says nothing with a mouse. The close
     button is not: this is where the dialog's only visible exit lives. */
  .cities-sheet-bar {
    flex: none; position: relative; height: 0; padding: 0; background: none;
  }
  .cities-sheet-bar .grip { display: none; }
  .cities-close {
    position: absolute; right: 10px; top: 10px; z-index: 3;
    width: 34px; height: 34px; display: grid; place-items: center;
    background: var(--wash); border: 1px solid var(--rule-strong); border-radius: 2px;
    cursor: pointer; color: var(--ink-45);
  }
  .cities-close:hover { border-color: var(--ink); color: var(--ink); }
  .cities-close svg { width: 17px; height: 17px; display: block; }

  /* Room for the close button, so a long heading cannot run under it. */
  .cities-head { padding-right: 40px; }

  /* Search stays put while the zone list scrolls under it. It is the fast way
     into 250 areas, and a search field you have to scroll back up to reach is
     not one. */
  .area-search-row {
    position: sticky; top: -22px; z-index: 2; background: var(--wash);
    padding: 8px 0 12px; margin: -8px 0 8px;
  }
  .area-search { max-width: none; }
}

.pwa-locked { overflow: hidden; }

.pwa-scrim {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(20, 18, 15, .48);
}
.pwa-scrim[hidden] { display: none; }

/* Offline banner ---------------------------------------------------- */
.pwa-offline {
  position: fixed; left: 0; right: 0; z-index: 45;
  bottom: calc(var(--tabbar-h, 0px) + env(safe-area-inset-bottom, 0px));
  display: flex; align-items: center; gap: 8px;
  padding: 9px calc(var(--gutter) + env(safe-area-inset-right, 0px))
           9px calc(var(--gutter) + env(safe-area-inset-left, 0px));
  background: var(--dark-inset); color: var(--knock-75);
  font: 500 12px var(--sans); border-top: 1px solid var(--knock-14);
}
.pwa-offline[hidden] { display: none; }
.pwa-offline .dot { width: 6px; height: 6px; flex: none; background: var(--amber); }

/* Pull to refresh --------------------------------------------------- */
.pwa-ptr {
  position: fixed; top: 0; left: 0; right: 0; z-index: 44;
  padding: 10px 16px; text-align: center;
  background: var(--wash); border-bottom: 1px solid var(--rule);
  font: 600 11.5px var(--sans); letter-spacing: .04em; color: var(--ink-45);
}
.pwa-ptr[hidden] { display: none; }

/* Sheets ------------------------------------------------------------ */
.pwa-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
  background: var(--paper); border-top: 1px solid var(--ink);
  box-shadow: 0 -14px 44px rgba(20, 18, 15, .22);
  transform: translateY(100%); transition: transform .22s ease;
  max-height: 88vh; overflow-y: auto;
}
.pwa-sheet[hidden] { display: none; }
.pwa-sheet.is-open { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .pwa-sheet { transition: none; }
}

.pwa-sheet-inner {
  max-width: 520px; margin: 0 auto;
  padding: 22px calc(18px + env(safe-area-inset-right, 0px))
           calc(22px + env(safe-area-inset-bottom, 0px))
           calc(18px + env(safe-area-inset-left, 0px));
}
.pwa-sheet-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 16px; }
.pwa-sheet-head h2 { font: 600 21px/1.2 var(--sans); margin: 0; letter-spacing: -.005em; }
.pwa-sheet-flag {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  background: var(--red); color: #fff; font: 600 10px var(--sans); letter-spacing: .1em;
  padding: 4px 7px; border-radius: 2px;
}
.pwa-sheet-flag .dot { width: 5px; height: 5px; background: #fff; }

/* Cost and benefit, one line each, and the label reads before the line. */
.pwa-terms { margin: 0 0 18px; }
.pwa-terms dt {
  font: 700 10.5px var(--sans); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-30); padding-bottom: 5px; border-bottom: 1px solid var(--rule);
}
.pwa-terms dd {
  margin: 8px 0 14px; font: 400 14px/1.55 var(--sans); color: var(--ink-70);
}
.pwa-terms dd:last-child { margin-bottom: 0; }

/* The two taps Safari needs, numbered, because "use the share sheet" is not an
   instruction to somebody who has never found it. */
.pwa-steps {
  margin: 0 0 18px; padding-left: 20px;
  font: 400 14px/1.7 var(--sans); color: var(--ink-70);
}
.pwa-steps li { margin-bottom: 6px; }
.pwa-steps b { font-weight: 700; color: var(--ink); }

.pwa-sheet-actions { display: flex; gap: 10px; }
.pwa-sheet-actions .btn-ink,
.pwa-sheet-actions .btn-line { flex: 1; min-height: 44px; justify-content: center; }

.pwa-note { font: 400 11.5px/1.5 var(--sans); color: var(--ink-30); margin: 8px 0 0; }
.pwa-label {
  font: 700 10.5px var(--sans); letter-spacing: .08em; color: var(--ink-30);
  text-transform: uppercase; margin: 22px 0 10px;
  display: flex; align-items: center; gap: 8px;
}
.pwa-label .count { margin-left: auto; color: var(--ink-45); letter-spacing: .04em; }

/* First run --------------------------------------------------------- */
.pwa-firstrun {
  position: fixed; inset: 0; z-index: 90;
  background: var(--paper); display: flex; flex-direction: column;
}
.pwa-firstrun[hidden] { display: none; }

/* The takeover is raised by the head script, before the body parses, so the
   front page never flashes behind it. The screen itself is the last element in
   the document, so hiding its siblings is what makes it first: without this the
   masthead paints while the parser is still working towards the takeover.

   Every path out of initFirstRun removes the class, including the paths that
   decide first run is not needed — the script knows things the head script
   cannot, such as a reader who already has areas saved on the server. */
html.pt24-firstrun body > *:not(.pwa-firstrun):not(script) { display: none !important; }
html.pt24-firstrun .pwa-firstrun[hidden] { display: flex; }
html.pt24-firstrun body { overflow: hidden; }
.pwa-firstrun-inner {
  flex: 1; overflow-y: auto;
  padding: calc(28px + env(safe-area-inset-top, 0px)) 22px 12px;
  max-width: 520px; width: 100%; margin: 0 auto;
}
/* --sans, not --serif. This is the same question the area picker asks in
   .cities-head, and the rule at the head of this file is that the serif is the
   news voice — a headline, and nothing that is not one. A cover asking the
   reader which city they are in is not a headline, however much it looks like
   one, and setting it in Newsreader put an exception in the rule on its first
   outing. */
.pwa-firstrun h1 { font: 600 28px/1.18 var(--sans); margin: 0 0 12px; letter-spacing: -.005em; }
.pwa-firstrun .lede { font: 400 14.5px/1.55 var(--sans); color: var(--ink-60); margin: 0; }

.pwa-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.pwa-chips .chip { min-height: 36px; }

/* The city, as a choice rather than as a line with a Change link on it.
   First run asks for exactly one thing and this is it, so it gets the room:
   56px rows, the guess already selected, and one tap to disagree.

   This replaces the old read-only .pwa-city row plus the seven-row area
   checklist that followed it. The checklist asked a reader who had not seen a
   story yet to name five neighbourhoods, using a control that looked nothing
   like the pill picker doing the same job a minute later. */
.pwa-cities { display: flex; flex-direction: column; }
.pwa-city-option {
  display: flex; align-items: center; gap: 12px; min-height: 56px;
  padding: 0 2px; background: none; border: none; border-bottom: 1px solid var(--rule);
  cursor: pointer; text-align: left; font: 500 15px var(--sans); color: var(--ink);
  width: 100%;
}
.pwa-city-option .loc {
  width: 18px; height: 18px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--rule-heavy); font-size: 0;
}
.pwa-city-option.is-selected .loc {
  border-color: var(--red);
  /* The filled centre is drawn, not a glyph — the same trick the old checkbox
     tick used, and for the same reason. */
  background: radial-gradient(circle at center, var(--red) 0 5px, transparent 5px);
}
.pwa-city-option.is-selected { font-weight: 700; }
.pwa-city-option .nm { flex: 1; }
.pwa-city-option .zn {
  font: 500 11.5px var(--sans); color: var(--ink-30); text-align: right;
  max-width: 45%;
}

.pwa-firstrun-foot {
  flex: none; border-top: 1px solid var(--rule); background: var(--wash-warm);
  padding: 14px 22px calc(16px + env(safe-area-inset-bottom, 0px));
}
.pwa-firstrun-foot .pwa-cta {
  width: 100%; min-height: 48px; justify-content: center; font-size: 14px;
}
.pwa-firstrun-foot .pwa-note { text-align: center; }

/* Above the phone breakpoint the sheets centre rather than dock. */
@media (min-width: 821px) {
  .pwa-sheet {
    left: 50%; bottom: 50%; right: auto; width: min(520px, calc(100vw - 64px));
    border: 1px solid var(--ink);
    transform: translate(-50%, 50%) scale(.98); opacity: 0;
    transition: opacity .18s ease, transform .18s ease;
  }
  .pwa-sheet.is-open { transform: translate(-50%, 50%) scale(1); opacity: 1; }
  .pwa-offline { bottom: 0; }
}

/* ================================================================== */
/* Screen 04 · You. The only settings screen.                          */
/*                                                                     */
/* One shape all the way down: a 11px label, a white card ruled at 1px, */
/* and 56px rows carrying a 46px switch. Following, alerts and offline  */
/* are the same row with the same switch, so nothing that can be turned */
/* on is harder to turn off.                                           */
/* ================================================================== */

.you {
  /* A settings column, not a river: it does not want the full 1920px. */
  max-width: 560px; margin: 0 auto;
  padding: 0 var(--gutter) 40px;
}

/* The screen names itself where the shell would otherwise repeat the
   masthead. 48px, ink rule under it, matching the app top bar it replaces. */
.you-head {
  display: flex; align-items: center; gap: 12px;
  min-height: 48px; margin: 0 calc(var(--gutter) * -1);
  padding: 0 var(--gutter); border-bottom: 1px solid var(--ink);
}
.you-head .ttl { font: 700 13px var(--sans); letter-spacing: .09em; }
.you-head .since { margin-left: auto; font: 500 11px var(--sans); color: var(--ink-30); }

.you-label {
  display: flex; align-items: baseline; gap: 10px; padding: 20px 0 8px;
  font: 700 11px var(--sans); letter-spacing: .09em;
}
.you-label .count {
  margin-left: auto; font: 600 10.5px var(--sans); color: var(--ink-30);
  letter-spacing: .04em;
}

.you-card { border: 1px solid var(--rule); background: #fff; }
.you-card.is-padded { padding: 14px; }
.you-card.is-spaced { margin-top: 22px; }

/* 56px rows. A row is a div, a button or a link depending on what it does; all
   three have to look identical, so every reset lives here. */
.you-row {
  display: flex; align-items: center; gap: 14px; width: 100%;
  min-height: 56px; padding: 13px 14px; text-align: left;
  background: none; border: none; border-bottom: 1px solid var(--rule);
  font-family: inherit; color: var(--ink);
}
.you-row:last-child { border-bottom: none; }
.you-row.is-tappable { cursor: pointer; }
.you-row.is-tappable:hover { background: var(--wash-warm); }

.you-row .txt { flex: 1; min-width: 0; display: block; color: var(--ink); }
.you-row .nm { display: block; font: 600 13.5px var(--sans); }
.you-row .sub {
  display: block; font: 500 11px/1.45 var(--sans); color: var(--ink-30); margin-top: 3px;
}
a.txt:hover .nm { color: var(--red); }

.you-change {
  flex: none; background: none; border: none; cursor: pointer;
  font: 600 11.5px var(--sans); color: var(--red);
  /* The row is 56px; the target inside it has to be 44px. */
  min-height: 44px; padding: 0 4px;
}
.you-chev { flex: none; font-size: 12px; color: var(--ink-30); }
.you-fixed {
  flex: none; font: 600 12px var(--sans); color: var(--ink-45);
}
.you-check { flex: none; color: var(--verified); }
.you-check svg { width: 20px; height: 20px; display: block; }
.you-check[hidden] { display: none; }

.you-cta {
  flex: none; background: var(--ink); color: var(--paper);
  font: 600 11.5px var(--sans); padding: 9px 15px; border-radius: 2px;
}

/* The switch. 46 x 28, knob 22, and the off state is as plainly reachable as
   the on state — same row, same target, no confirmation step. */
.you-toggle {
  flex: none; position: relative; width: 46px; height: 28px; padding: 0;
  border: none; border-radius: 100px; background: var(--rule-strong);
  cursor: pointer;
}
.you-toggle .knob {
  position: absolute; top: 3px; left: 3px; width: 22px; height: 22px;
  border-radius: 50%; background: #fff; transition: transform .16s ease;
}
.you-toggle.is-on { background: var(--ink); }
.you-toggle.is-on .knob { background: var(--paper); transform: translateX(18px); }
.you-toggle:disabled { opacity: .5; cursor: default; }
@media (prefers-reduced-motion: reduce) {
  .you-toggle .knob { transition: none; }
}

/* Content and language tags. The same chip as the cities panel, because it is
   the same kind of choice: a complete set, all of it on one screen. */
.you-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.you-tags .chip { min-height: 36px; }

.you-note { font: 400 11.5px/1.55 var(--sans); color: var(--ink-30); margin: 9px 2px 0; }
.you-note.is-outside { margin: 9px 2px 0; }

.you-empty { padding: 18px 14px; }
.you-empty .label {
  font: 700 11px var(--sans); letter-spacing: .09em; color: var(--ink-30); margin-bottom: 7px;
}
.you-empty p { font: 400 12.5px/1.55 var(--sans); color: var(--ink-60); margin: 0; }

/* ---------------------------------------------------------------------------
   My Threads. The reader's own file, grouped by the thread they followed.

   Not the day mosaic. The mosaic packs tiles by picture weight to make a page
   look like a page, and it is right for a river the desk assembled. This river
   the reader assembled, and its structure is the grouping — four headlines
   under the thread they belong to, in a list, with the dates legible down the
   left edge. Pictures would break the grouping into a wall and hide the one
   thing this screen has to show, which is which of your threads moved.
   --------------------------------------------------------------------------- */
.thread-block { padding: 22px 0; border-top: 1px solid var(--rule); }
.thread-block:first-of-type { border-top: 1px solid var(--ink); }
.thread-block-head {
  display: flex; align-items: baseline; gap: 14px; margin-bottom: 10px;
}
.thread-block-head .txt { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.thread-block-head h3 { font: 600 22px/1.25 var(--serif); margin: 0; }
.thread-block-head h3 a { color: var(--ink); }
.thread-block-head h3 a:hover { color: var(--red); }
.thread-block-head .state {
  font: 400 13px var(--sans); color: var(--meta);
}
/* The unread mark is the reason to open this screen, so it is the one thing on
   the row allowed to be red — and only when there is something unread. "Up to
   date" is information too, and it is set in meta because it asks for nothing. */
.thread-block-head .mark {
  margin-left: auto; flex: none;
  font: 500 13px var(--sans); color: var(--meta); white-space: nowrap;
}
.thread-block-head .mark.is-new { color: var(--red); font-weight: 600; }
.thread-block.is-live .thread-block-head h3::before {
  content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 9px;
  border-radius: 50%; background: var(--red); vertical-align: middle;
}
.thread-block-summary {
  font: 400 15px/1.6 var(--sans); color: var(--ink-60);
  margin: 0 0 12px; max-width: 62ch;
}

.thread-block-list { display: flex; flex-direction: column; }
.thread-turn {
  display: flex; align-items: baseline; gap: 14px;
  padding: 9px 0; border-top: 1px solid var(--rule);
}
.thread-turn:hover .headline { color: var(--red); }
/* A fixed column so the dates stack into a readable edge instead of ragging
   against however long each headline happens to be. */
.thread-turn .when {
  flex: none; width: 104px;
  font: 400 13px var(--sans); color: var(--meta);
}
.thread-turn .headline { font: 500 17px/1.4 var(--sans); color: var(--ink); }
.thread-turn .live-dot {
  flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--red);
}

.threads-empty {
  padding: 48px 0; border-top: 1px solid var(--ink); max-width: 56ch;
}
.threads-empty .label {
  font: 700 12px var(--sans); letter-spacing: .1em; color: var(--ink-30);
  margin-bottom: 10px;
}
.threads-empty p { font: 400 17px/1.6 var(--sans); color: var(--ink-60); margin: 0 0 20px; }

@media (max-width: 700px) {
  .thread-block-head h3 { font-size: 19px; }
  .thread-block-head .mark { margin-left: 0; }
  /* The date column stops earning its width once the headline has to wrap
     under it anyway. */
  .thread-turn { flex-wrap: wrap; gap: 2px 12px; }
  .thread-turn .when { width: auto; order: 2; }
  .thread-turn .headline { flex: 1 0 100%; font-size: 16px; }
}

/* Text size. The slider says where it starts, and where it starts is whatever
   the device already asked for. */
.you-slider-head { display: flex; align-items: center; gap: 8px; margin-bottom: 11px; }
.you-slider-head .nm { font: 600 13.5px var(--sans); }
.you-slider-head .sub { margin-left: auto; font: 500 11px var(--sans); color: var(--ink-30); }

.you-slider { display: flex; align-items: center; gap: 12px; }
.you-slider .a-small { font: 600 13px var(--sans); color: var(--ink-30); }
.you-slider .a-large { font: 600 21px var(--sans); color: var(--ink); }

.you-slider input[type="range"] {
  flex: 1; min-width: 0; height: 26px; margin: 0;
  -webkit-appearance: none; appearance: none; background: none; cursor: pointer;
}
.you-slider input[type="range"]::-webkit-slider-runnable-track {
  height: 3px; border-radius: 2px; background: var(--rule-strong);
}
.you-slider input[type="range"]::-moz-range-track {
  height: 3px; border-radius: 2px; background: var(--rule-strong);
}
.you-slider input[type="range"]::-moz-range-progress {
  height: 3px; border-radius: 2px; background: var(--ink);
}
.you-slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 26px; height: 26px; margin-top: -11.5px;
  border-radius: 50%; background: #fff; border: 1px solid var(--rule-strong);
  box-shadow: 0 2px 6px -2px rgba(20, 18, 15, .35);
}
.you-slider input[type="range"]::-moz-range-thumb {
  width: 26px; height: 26px; box-sizing: border-box;
  border-radius: 50%; background: #fff; border: 1px solid var(--rule-strong);
  box-shadow: 0 2px 6px -2px rgba(20, 18, 15, .35);
}
.you-slider input[type="range"]:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* The long wordmark belongs here, at the foot of the settings screen, and on
   the splash. The chrome carries the square mark instead. */
.you-foot { padding: 24px 0 0; text-align: center; }
.you-foot p {
  font: 500 10.5px/1.6 var(--sans); color: var(--ink-45); margin: 10px 0 0;
}

@media (max-width: 820px) {
  /* The screen names itself, so the shell's masthead row would be a second
     title. The design gives You its own 48px bar and no search. */
  body[data-screen="you"] .mtop,
  body[data-screen="you"] .mservice { display: none; }

  .you { padding-bottom: 28px; }
  .you-head {
    position: sticky; top: 0; z-index: 40; background: var(--paper);
    padding-top: var(--safe-top);
    min-height: calc(48px + var(--safe-top));
  }
}

/* ------------------------------------------------------------------ */
/* Reading size. The scale set on You multiplies reading type and only  */
/* reading type: furniture, labels, the tab bar and the time columns    */
/* keep their sizes. That is what stops a reader at 140 percent from    */
/* losing the chrome, and it is why the slider is honest about starting  */
/* from whatever the device already asked for.                          */
/* ------------------------------------------------------------------ */
:root { --read: 1; }

.standfirst { font-size: calc(20px * var(--read)); }
.entry-standfirst { font-size: calc(16px * var(--read)); }
.entry-standfirst.lead { font-size: calc(17px * var(--read)); }
.update-body, .update-body p { font-size: calc(17px * var(--read)); }
.disagreement p { font-size: calc(18px * var(--read)); }
/* The claim row is a flex container; the reading type inside it is .text, which
   is what has to scale. */
.claim .text { font-size: calc(16px * var(--read)); }

@media (max-width: 820px) {
  .standfirst { font-size: calc(17px * var(--read)); }
  .entry-standfirst, .entry-standfirst.lead { font-size: calc(16px * var(--read)); }
  .update-body, .update-body p, .disagreement p { font-size: calc(17px * var(--read)); }
}
