/* 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.

   How the file is ordered, because it is not obvious and it used to claim
   otherwise. The headings carried numbers: 2, 3, 4, 5.1 through 5.10, 6, 10,
   lifted from the design package's own section list, and by the time four
   passes had folded their work back in, the numbers ran 6, 5.2, 5.3, 5.4, 5.5,
   3, 5.6, 5.6, 5.10, 5.8, 5.7, 5.9, 10, 10 down the file, with two 5.6s and two
   10s. They are gone; the titles stayed.

   What orders this file is the cascade, and nothing else. Tokens, then the
   base, then the chrome a reader meets before any story, then the front page,
   the story page, the local hub, the footer, the failure states, and only then
   the width blocks and the phone, a media query that steps a rule has to come
   after the rule it steps. The last third of the file is later still on
   purpose: those sections re-open ones above them (the front page again, the
   story page again, You again) because they hold rules that have to outrank
   what is up there, and the comment at each one says which and why. Adding a
   rule near its relatives is right; moving one is a cascade change wearing a
   tidy-up's clothes. */

:root {
  /* 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.
     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. */
  /* The two Notos ride in every stack, after the face that owns it.

     They are already declared per-language further down, where a page in
     Hindi or Kannada takes the whole role in the Noto that can draw it. This
     is the other half: a string that mixes scripts without saying so. `You`
     prints "English, हिन्दी, ಕನ್ನಡ and मराठी available" as one
     line of Latin-tagged text, and font matching is per glyph, so every
     Devanagari and Kannada letterform in it fell past Roboto to whatever the
     device happened to have, which on a phone without an Indic font is
     tofu. Both faces are loaded on every page already; naming them here costs
     nothing and is the difference between a legible line and a row of boxes.
     Latin never reaches them: Newsreader, Roboto and Jakarta all draw it. */
  --serif: 'Newsreader', Georgia, 'Times New Roman', 'Noto Sans Devanagari', 'Noto Sans Kannada', serif;
  --sans: 'Roboto', 'Noto Sans Devanagari', 'Noto Sans Kannada', system-ui, sans-serif;
  --ui: 'Plus Jakarta Sans', 'Roboto', 'Noto Sans Devanagari', 'Noto Sans Kannada', system-ui, sans-serif;

  /* Tracking, as a multiplier rather than as a value.

     Nearly every label in this paper is set in caps with tracking: .04em on
     a status, .1em on a section head, .18em on the hatch over a blank crop,
     and all seventy-odd of those declarations were literals that a Devanagari
     or Kannada page then inherited unchanged. Neither script has an upper
     case for the idiom to be built on, and neither is drawn as a row of
     separate letters: the shirorekha runs across the top of a whole
     Devanagari word, and tracking saws it into pieces. `आज / नुक्कड़ / सवाल /
     आप` in the tab bar was being set at the .07em Latin caps want.

     The negative end is the same argument from the other side: -.02em on a
     38px headline is an optical correction for Latin bowls and counters, and
     the design package asks for every one of them at zero in both Indic
     scripts.

     So the value stays where it was argued for, at the rule, and the script
     multiplies it: one token, redefined beside the families in the [lang]
     blocks below, because it is the same instrument answering the same
     question. A Latin fallback marked lang="en" inside a Hindi page gets its
     tracking back exactly as it gets Newsreader back. */
  --track: 1;

  /* Colour */
  --ink: #14120F;
  --paper: #FBF9F5;
  /* --ink's own channels, for the handful of places ink is wanted at an
     alpha rather than at full strength: a caption plate over a photograph,
     the hatch on a blank crop, a scrim, and the four shadows the sheets and
     the modal draw. CSS gives no way to take a hex custom property to an
     alpha, so the channels have to be a token of their own, which means
     these two lines and the two hexes above them have to move together. The
     same goes for --red-rgb, which exists for exactly one ring. */
  --ink-rgb: 20, 18, 15;
  --red-rgb: 236, 31, 40;
  /* The two accents are the client's, and they are fixed.

     Both stepped by the redesign, the red a shade cooler and 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. The values below are the
     ones in the approved design package (September 2026): Signature Red as
     the brand guidelines draw it, and the package's gold. For a while this
     sheet ran both darker to clear WCAG AA for small type (#E8141D /
     #D1121A / #8E5F1F); the client reviewed the package at these values and
     asked for exactly this look, so the accents went back and the bar was
     waived for them, knowingly, and only for them. The ink scale and --meta
     below still hold the 4.5:1 line.

     What survives from the audit is the mechanism, because it is the right
     shape regardless of the values: an accent used as a *mark* (a button
     ground, a live dot, a chip) takes --red; an accent used as *type* takes
     the token named for the ground it sits on, --red-on-paper or
     --red-on-ink, through --red-type, which every dark ground below re-points.
     Today the two answers are the same colour. Keep the seam anyway: the day
     a ground needs its own answer, this is where it goes, and
     tests/test_accent_contrast.py still holds the usage rules.

     Measured, for the record, against the darkest ground each is printed on:

       white on --red                     4.38:1
       --red-on-paper  on --wash          3.68:1
       --red-on-ink    on --dark-inset    3.73:1
       --gold          on --wash          2.57:1
       --gold-on-ink   on --dark-inset    5.35:1

     --slot is the well an image sits in (an avatar, an embed, a day's shot)
     and no accent sets type on it. */
  --red: #EC1F28;
  --red-on-paper: #EC1F28;
  --red-on-ink: #EC1F28;
  --gold: #C7862B;
  --gold-on-ink: #C7862B;
  /* Revolution Gold, from the brand palette. The guidelines' rule is that a
     focus ring is gold "so it stays visible on red and on black", and on
     cream it measures 1.37:1, invisible. So the ring is red on the paper's
     grounds and gold on every ground that knocks its type out, through the
     same re-point the accents use. */
  --focus-gold: #FCD404;
  --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 in the Figma, printed as #717171 in the approved
     package. It clears --paper at 4.64:1; on --slot, the blank shot and the
     missing embed, it measures 3.89:1, a step darker (#656565) cleared that
     too, and was set aside with the accents when the package's values were
     restored. */
  --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;

  /* The card ground. A card in this system is a box the reader is meant to
     read as sitting *on* the page rather than as part of it, the settings
     rows on You, an area chip, a day in the archive strip, the map card in
     the Local rail, the switch's own knob. What makes it sit on the page is
     that it is not the page: cream on cream is a hairline with nothing
     inside it.

     It was #fff at every one of those, which is how it came to be the one
     ground in the system with no name. Naming it is most of the point, a
     card ground and the knockout below are two different jobs that happen
     to resolve to the same value today, and if the paper's grounds are ever
     re-warmed the type knocked out of red must not follow them. */
  --card: #FFFFFF;

  --wash: #EFEBE3;
  --wash-warm: #F6F2EA;
  --slot: #EAE5DB;
  --dark-inset: #241F1A;
  --verified: #1E7A46;
  --amber: #D9A227;
  --ad-wash: #F2EEE6;

  /* Knocked out of a dark or a coloured ground, and there are two answers
     depending on which.

     On --ink the paper's own cream *is* the knockout: the runner, the
     footer, the archive bar, the ink board and every ink button set their
     type in --paper, and the scale below is that same cream at the alphas
     the design draws its secondary knockout type, its hairlines and its
     rules at.

     On --red it is not. --red is a bright warm red, and cream laid on it
     goes a shade yellow, a Subscribe button whose word is dirtier than the
     LIVE chip beside it. Type and marks on red take the solid step instead,
     and that is now a rule and not a preference: white on --red clears 4.5:1
     (4.62:1) and the cream does not (4.39:1). The red was moved to make the
     first of those true; the second is why nothing on a red ground may be set
     in --paper or in any of the --knock steps below. */
  --knockout: #FFFFFF;

  /* The scale, and the floor under it.

     An alpha is not a contrast ratio. The step says how much of the cream
     survives; what reaches the eye is that cream mixed with the ground
     behind it, so nothing here means anything until it is composited over
     the ground the rule actually sits on. Done for both dark grounds:

                      on --ink     on --dark-inset
       --knock-75      10.21           9.24
       --knock-68       8.55           7.84
       --knock-62       7.27           6.75
       --knock-55       5.95           5.61
       --knock-50       5.11           4.88
       --knock-45       4.36           4.22
       --knock-28       2.43           2.47
       --knock-22       1.95           2.01
       --knock-16       1.57           1.64
       --knock-14       1.47           1.53

     Which puts the floor for type at --knock-50, and puts it there for a
     reason rather than by taste: .46 is the first alpha to clear 4.5:1 on
     --ink and .48 the first on --dark-inset, and .50 is the nearest step
     above both. Every step above it clears the bar on either ground; no
     step below it clears it on either. There is no third case to remember.

     The scale used to run a step lower than that and set type there. The
     footer's legal line, its Privacy / Terms / DPDP links and the names in
     its link columns that are stated rather than linked all sat at
     --knock-45 and measured 4.36:1, on every page of the paper, since the
     footer is on every page. The synchronised player's chapter mark and its
     segment labels sat lower still. They now sit at the floor.

     Below the floor the steps are lines, not letters: a card's edge, the
     rule under a row, the boundary of a control. Two bars apply there and
     neither is 4.5, a boundary that has to be seen to work the control it
     draws is held to 3:1, which --knock-45 clears and --knock-28 does not,
     and a hairline that is only there to separate two things the reader can
     already tell apart is decoration and is held to nothing.

     The scale had three steps no rule read: .42, .18 and .12, the first of
     them left behind by this audit when its two rules moved up to the floor.
     They are gone. A step nothing uses renders as nothing and so reads to the
     next person as a sanctioned answer for "quieter than --knock-50" rather
     than as a value nobody has checked, and every one of the three sat below
     the floor.

     One piece of type is exempt, and only because the markup says so: the
     diamond between the runner's passes carries aria-hidden, which makes it
     decoration in the same sense a rule is. It stays at --knock-28. The
     test for this file checks that claim against the template rather than
     taking it on trust, so if the attribute ever goes the diamond stops
     being exempt. */

  --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-28: rgba(251, 249, 245, .28);
  --knock-22: rgba(251, 249, 245, .22);
  --knock-16: rgba(251, 249, 245, .16);
  --knock-14: rgba(251, 249, 245, .14);

  /* 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);
  /* A fifth of the page, not a fixed measure. The rail carries the slot and
     Threads you follow now, the story timeline moved into the main column
     beside the lead picture, so the rail is no longer sized for a ring, a
     date, a role and a two-line headline across it, and a percentage tracks
     the column rule at every width instead of drifting off it above or below
     the one viewport a flat px was measured at. */
  --rail: 20%;
  --rail-wide: 480px;

  /* 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. One number so a row of controls doing the
     same job reads as one size of control, not four. Trimmed from 48px: the
     row was reading as loud furniture rather than a quiet strip of utilities
     under the wordmark. */
  --control-h: 38px;

  /* 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: 56px;
}

/* Which red, and which gold.

   An accent used as type has to answer to the ground under it, and the ground
   is decided by the DOM, not by the selector: `a:hover` is one rule and it
   fires over cream in the river and over ink in the runner. There is exactly
   one mechanism in CSS for "take the value my ground set", and it is a custom
   property, which is what --track already is: one token, redefined where the
   answer changes, read unchanged everywhere else.

   So --red-type and --gold-type resolve to the paper's answers here, and every
   ground that knocks its type out redefines them below. The list is not a
   judgement about which panels happen to contain an accent today; it is every
   rule in this file that sets a dark background *and* a knocked-out colour,
   because such a rule is precisely a declaration that type inside it is read
   against ink. tests/test_accent_contrast.py recomputes that set from this
   file and fails if the two lists have come apart, so an ink panel added later
   cannot quietly inherit the paper's red. */
:root {
  --red-type: var(--red-on-paper);
  --gold-type: var(--gold);
  --focus: var(--red);
}

.account-btn[aria-expanded="true"],
.lang-switch button.is-active,
.chip.is-selected,
.archive-bar,
.runner,
.media-caption,
.media-mark-note,
.btn-ink,
.btn-line:hover,
.filter-chip.is-active,
.nukkad,
.nukkad-card,
.btn-wide:hover,
.player,
.sawaal-board,
.footer,
.toast,
.new-updates,
.skip-link:focus,
.pwa-offline,
.you-cta,
.daily-day.is-current,
.event-hero,
.event-slab,
.event-updates,
.event-sponsors,
.onair-clock,
.onair-slab,
.onair-flag.is-desk,
.onair-crawl .track,
.onair-crawl .tail,
.onair-post-head,
.onair-sponsors.is-post,
.btn-line:active,
.follow-tile,
.gallery-nav,
.gallery-count,
.tile-clip,
.wa-link {
  --red-type: var(--red-on-ink);
  --gold-type: var(--gold-on-ink);
  --focus: var(--focus-gold);
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  /* `clip`, never `hidden`: hidden makes body a scroll container and silently
     kills position:sticky on the top bar and the runner. It lived in the phone
     block, which was the width it was noticed at, but `.nukkad` is `100vw`
     and a viewport unit counts the scrollbar, so on any desktop with a classic
     scrollbar every page carrying a Nukkad band scrolled sideways by its
     width. The band's bleed is the whole page's business, so the guard is. */
  overflow-x: clip;
  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. */
/* Scoped to the furniture, not the journalism.

   This was on `body`, which took the article text with it, and the cost on a
   phone was larger than the copy guard is worth. A reader could not select a
   sentence, so iOS Look Up and Translate, and Android's select-to-search,
   were dead on a paper published in four scripts, where looking a word up
   across scripts is the single most likely reason to select anything.

   `-webkit-touch-callout: none` is gone entirely rather than scoped: it killed
   the iOS long-press menu on links and images, which directly contradicted
   `pt24.js`, where `contextmenu` is deliberately re-permitted on `a, img`
   because "saving a photograph or copying a link to send to somebody is not
   what this is about". The CSS was defeating the script's own stated intent. */
.masthead, .mtop, .mservice, .tabbar, .msections, .runner, .footer,
.cities, .pwa-sheet, .pwa-firstrun, .toast {
  -webkit-user-select: none; user-select: none;
}
input, textarea, select, [contenteditable] {
  -webkit-user-select: text; user-select: text;
}

a { color: var(--ink); text-decoration: none; }
a:hover { color: var(--red-type); text-decoration: underline; text-underline-offset: 2px; }

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; }

/* 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); }

/* ------------------------------------------------------------------ */
/* Masthead: logo first at top left, always                             */
/*                                                                      */
/* There is no utility strip any more. It was a 38px band of ink      */
/* above the publisher's own name carrying nine items, none of them   */
/* news. Date and weather are the dateline under the wordmark now;    */
/* the clock and AQI moved to the phone's own strip. The rest are     */
/* under the account door 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 stands at           */
/* --control-h, 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); }

/* Lifted out of the page's own paint order, because two of the masthead's
   controls open downwards into it: the account panel and the verticals panel
   both hang below the row they belong to and overlap whatever <main> is
   showing. Without this they are painted first and the front-page photograph
   covers them.

   `view-transition-name` on this element (see the transitions block at the end
   of this file) already makes it "form a stacking context" per the View
   Transitions spec, so the numbers inside the masthead are a private scale and
   never meet these. This one is read against the document: above everything in
   <main> (the highest is .you-head, sticky at 40) and below the scrim at 70,
   so an open modal still dims the masthead as it should.

   Desktop only. Below 821px the row and the nav are `display: none` and there
   is no dropdown left to rescue, while the masthead still carries .runner,
   which is deliberately stacked *under* the phone's top bar, and lifting the
   whole element would invert that. */
@media (min-width: 821px) {
  .masthead { position: relative; z-index: 50; }
}
.masthead-row {
  display: flex; align-items: center; gap: var(--space-lg); padding: 16px 0;
}

/* 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 ran to 585px on one line when it still carried four facts:
   date, clock, weather, air, which was more than the design allowed for;
   the design drew three 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. It is two facts now, but the shrink is what
   holds the row together whatever the dateline grows back to.

   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: 5px; 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, and
   74 * 21/212 = 7.3px at the left, the box is sized by its height, so the
   left inset is that height against the *height* of the canvas too, not
   against its width. That is what lands the P on the gutter. The row measures
   26.5px 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; }

/* Day and weather. Two facts, one line, no separator between them; they are
   a dateline, and a dateline is not a list. The clock and the AQI figure used
   to run alongside; both are gone, not hidden, a dateline reads as a quiet
   caption under the wordmark only when it says as little as it needs to.

   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: 10px; flex-wrap: wrap;
  margin: 0; font: 400 13px var(--ui); color: var(--ink-60);
}
.masthead-meta .dot { width: 6px; height: 6px; margin-right: 4px; }

/* 460px and no more, 220px 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. 220px holds the placeholder
   to about "Search Stories, Threads..." and leaves room to type; the narrowest
   desktop lowers it once, below. */
.searchbox {
  flex: 1 1 460px; min-width: 220px; max-width: 460px; margin-left: auto;
  display: flex; align-items: center; gap: var(--space-xs);
  height: var(--control-h);
  border: 1px solid var(--ink); border-radius: 0; padding: 0 var(--space-sm);
  background: none;
}
.searchbox .mag { color: var(--meta); font-size: 13px; }
.searchbox input {
  border: none; outline: none; background: none; font: 400 13px var(--sans);
  color: var(--ink); width: 100%;
}
/* `.searchbox input` is (0,1,1) and the global `:focus-visible` is (0,1,0), so
   `outline: none` above wins wherever the rules sit and keyboard focus produced
   no visual change at all. The wrapper carries the border, so the ring goes
   there rather than on the field. */
.searchbox:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }
.searchbox input::placeholder { color: var(--meta); }

/* The search page's own field is phone-only: above 820px the masthead's is a
   few pixels away. Hidden here and shown in the `max-width: 820px` block far
   below, same specificity, later rule wins, so no `!important` is needed and
   the file keeps its four. */
.searchbox-phone { display: none; }

/* 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-2xs);
  height: var(--control-h); background: none; border: none; border-radius: 0;
  padding: 0; font: 600 13px var(--ui); color: var(--ink); cursor: pointer;
  white-space: nowrap;
}
.edition-btn:hover { color: var(--red-type); }
.edition-btn .caret { color: var(--ink); font-size: 9px; }

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

/* ------------------------------------------------------------------ */
/* 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-type); cursor: pointer; padding: 0;
}
.account-btn svg { width: 18px; height: 18px; 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; }
/* You, arriving from the nav's format axis where it never belonged. It leads
   the panel now that Sign in has gone, and sits above the language switcher
   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-type); }
.account-you.is-active { font-weight: 600; color: var(--red-type); }
.account-label {
  font: 700 10px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--meta);
}
.account-links { display: flex; flex-direction: column; gap: 7px; }
.account-links a { font: 400 13px var(--sans); color: var(--ink); }
.account-links a:hover { color: var(--red-type); }

/* 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;
}
/* `font-family: var(--sans)` and not the inherited one. Every chip in this
   control carries its own `lang` (the template writes it from
   `language.name`), so the two blocks below already redefine --sans on the
   button itself. What was stopping that reaching the glyphs is that nothing
   on the button asked for --sans: `button { font-family: inherit }` takes the
   parent's *computed* family, which was resolved back at `body`, in whatever
   script the page as a whole is in. Asking for the token here resolves it on
   the element, which is where the fact lives.

   That also fixes the EN chip inside a Hindi or Kannada page. It carries
   `lang="en"`, and the `[lang="en"]` block exists precisely so an English
   string in a page set in another script comes back to Roboto rather than
   being drawn in the Noto face the page inherited. */
.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-family: var(--sans); 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 two Notos, bound to the attribute that says which script it is.  */
/*                                                                     */
/* The guidelines say Devanagari and Kannada are set in the two Notos,  */
/* pt24_base loads both faces, and until now the only thing on the      */
/* page that used either of them was the language switcher's own chips: */
/* `.lang-deva` and `.lang-knda` were applied from `script_class`, and  */
/* `script_class` reaches exactly three elements in the whole codebase, */
/* all three of them a chip in the switcher. Every other Hindi and      */
/* Kannada string in the paper, a headline, a standfirst, the nav, the  */
/* body of a story, asked for Roboto and Newsreader, neither of which   */
/* has a glyph for either script, and got whatever the device happened  */
/* to have. Two readers of the same Hindi story on two phones were      */
/* reading it in two different typefaces, and neither was ours.         */
/*                                                                     */
/* Bound to `lang` rather than to a class because `lang` is the fact.   */
/* It is already on <html> for the reader's own language and already on */
/* every string the API marks as a fallback, which is what makes an     */
/* untranslated English headline inside a Hindi page come back to       */
/* Newsreader on its own, the attribute changes, so the tokens change   */
/* with it.                                                            */
/*                                                                     */
/* Written as token redefinitions and not as `font-family` rules, which */
/* is the whole reason this can be four short blocks instead of a fight */
/* with every `font:` shorthand in the stylesheet. A custom property is */
/* resolved where it is used and inherits from wherever it was last     */
/* set, so `h1.story { font: 600 38px/1.12 var(--serif) }` picks up     */
/* Noto inside a Hindi story and Newsreader outside it without either   */
/* rule knowing the other exists.                                       */
/*                                                                     */
/* There is no serif for either script. That is a gap in what is        */
/* available, not a decision made here, and the guidelines name it as   */
/* one, a Devanagari headline sets in the Noto face rather than in a    */
/* Latin serif that would draw it as tofu.                              */
/*                                                                     */
/* These four blocks are also the only place in the stylesheet where a  */
/* family name is written out. `.lang-deva` and `.lang-knda` used to be */
/* a fifth and sixth, two rules naming the two Notos directly, doing    */
/* by hand what the attribute now does by inheritance, and they are     */
/* gone; the chips carry `lang` and ask for `var(--sans)` instead. The  */
/* one family outside this block is the stack trace on the API-down     */
/* page, which is monospaced, only ever draws under DEBUG, and is       */
/* argued for at the rule.                                             */
/* ------------------------------------------------------------------ */
[lang="hi"], [lang="mr"] {
  /* The package's Hindi stacks, verbatim. The Latin face leads each one and
     the Devanagari face rides behind it, so a numeral, a "PT24" or an English
     word inside a Hindi line is drawn by Newsreader, Roboto or Jakarta and
     every Devanagari letter falls through to the Noto that can draw it, a
     per-glyph fallback, which is what the browser does anyway. Headlines get
     a serif at last: Noto Serif Devanagari, paired with Newsreader, is the
     package's answer to the gap the guidelines used to record.

     Kannada rides at the end of each of these for the same reason Devanagari
     rides at the end of the Latin stacks: a Hindi page names the paper's other
     languages in their own scripts, and one of them is not this one. */
  --serif: 'Newsreader', 'Noto Serif Devanagari', 'Noto Sans Devanagari', Georgia, 'Noto Sans Kannada', serif;
  --sans:  'Roboto', 'Noto Sans Devanagari', 'Noto Sans Kannada', system-ui, sans-serif;
  --ui:    'Plus Jakarta Sans', 'Noto Sans Devanagari', 'Roboto', 'Noto Sans Kannada', system-ui, sans-serif;
  /* See --track at the top of the file: the letter-spacing every label in the
     system asks for is a Latin caps idiom, and Devanagari has neither the
     caps nor the separate letterforms it is built on. */
  --track: 0;
}
[lang="kn"] {
  --serif: 'Noto Sans Kannada', 'Roboto', 'Noto Sans Devanagari', system-ui, sans-serif;
  --sans:  'Noto Sans Kannada', 'Roboto', 'Noto Sans Devanagari', system-ui, sans-serif;
  --ui:    'Noto Sans Kannada', 'Plus Jakarta Sans', 'Noto Sans Devanagari', system-ui, sans-serif;
  --track: 0;
}
/* An English string inside a page in another script comes back to the paper's
   own three faces. This is the half that makes the marking worth anything: the
   API labels a fallback row `lang="en"` precisely so the reader can see it is
   not their language, and it should look like the paper it came from. */
[lang="en"] {
  /* The Notos ride here too, at the end, for the reason given at the top of
     the file, and this is the block that decides it, not `:root`. An English
     page carries `lang="en"` on `<html>`, so this rule is what every string on
     it resolves against, including the ones that name the other three
     languages in their own scripts. Without them here the You screen's
     "English, हिन्दी, ಕನ್ನಡ and मराठी available" was tofu on any device with
     no Indic font of its own, on the one screen whose job is to offer a reader
     their own language. */
  --serif: 'Newsreader', Georgia, 'Times New Roman', 'Noto Sans Devanagari', 'Noto Sans Kannada', serif;
  --sans:  'Roboto', 'Noto Sans Devanagari', 'Noto Sans Kannada', system-ui, sans-serif;
  --ui:    'Plus Jakarta Sans', 'Roboto', 'Noto Sans Devanagari', 'Noto Sans Kannada', system-ui, sans-serif;
  --track: 1;
}

/* The dot. It is the air-quality swatch on 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 bullseye 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
   account door both carry those links in words instead. */
.dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }

/* ------------------------------------------------------------------ */
/* Navigation                                                          */
/*                                                                      */
/* A --nav-h band between two ink hairlines. The subjects lead at 14px   */
/* and 28px 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 redesign took it to 18px and 39px; the narrow-desktop    */
/* step below then took it back down, and the step is where it actually  */
/* draws on every screen under 1700px, which is most of them.            */
/*                                                                      */
/* 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: 28px; row-gap: 0; min-height: var(--nav-h);
  border-top: 1px solid var(--ink);
}

/* On an alert day the runner closes itself with a 2px full-bleed red rule, and
   the nav's own 1px hairline is inset to the shell, stacked, they read as one
   rule that changes width at both ends. Suppress the second. No runner means no
   adjacency and the hairline returns on its own. */
.runner + .shell .nav { border-top: 0; }
.nav a {
  display: flex; align-items: center; padding: 0;
  font: 500 14px 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 { white-space: nowrap; }
.nav a:hover { color: var(--red-type); }

/* 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 service end of the row: the format axis, alone now.

   The auto margin keeps this group right-aligned on whichever line it wraps
   to. Explore more used to share this wrapper, bundled with the format axis
   so the two would not wrap to different lines independently; it has since
   moved into the account door, and Verticals is its own sibling in the row
   rather than sharing this wrapper. */
.nav-service {
  margin-left: auto; align-self: center; flex: none;
  display: flex; align-items: center; min-height: var(--nav-h);
}

/* The format axis, fenced by a rule on each side. Watch and My Threads answer
   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 24px, not the band's full height: a full-height rule reads as
   a column division, and this is a bracket around two words. */
.nav-formats {
  flex: none; display: flex; align-items: center; height: 24px;
  padding: 0 16px;
  border-left: 1px solid var(--ink); border-right: 1px solid var(--ink);
}
/* Each format its own button, so the rule between them reads the same as the
   fence around the group, Watch and My Threads are two separate places to
   go, not one label with a gap in it. */
.nav-formats a.format {
  font: 500 14px var(--sans); color: var(--ink); padding: 0 12px;
}
.nav-formats a.format:first-child { padding-left: 0; }
.nav-formats a.format:last-child { padding-right: 0; }
.nav-formats a.format + a.format { border-left: 1px solid var(--ink); }
.nav-formats a.format.is-active {
  color: var(--red-type); 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 14px var(--sans); color: var(--ink);
  background: none; border: none; cursor: pointer;
}
.nav-verticals-btn:hover { color: var(--red-type); }
.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)); }

/* Scoped to the text list, not to the panel. These three describe a name set in
   the panel's type (a padding box, a size, a ground to light on hover) and
   the panel also holds the lockup grid below, where every one of them is either
   meaningless on an `<img>` or, in the case of the ground, forbidden. */
/* Both cells again, for the reason the phone sheet's twin gives at
   `.msections-grid`: this list names a vertical it cannot link, and an
   unstyled `<span>` in a padded list is a name sitting flush against its
   neighbours in the wrong size. */
.verticals-links a,
.verticals-links span {
  padding: 10px var(--space-sm); font: 400 16px var(--sans); color: var(--ink);
  white-space: nowrap;
}
.verticals-links span { color: var(--ink-30); cursor: default; }
.verticals-links a:hover { background: var(--wash); color: var(--red-type); }
.verticals-links a.is-active { font-weight: 600; background: var(--wash); box-shadow: none; }

/* Group headings inside the panel, printed only when there is more than one
   group to tell apart. */
.explore-label { font: 700 11px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--meta); }

/* Each product's own lockup, not its name set in the panel's type, Daily,
   Live, Nukkad and Sawaal are mastheads in their own right, the same way this
   paper's is. A two-up grid of tiles, not a list, so all four read at once as
   one set of marks. The source art is 139px wide; the tile is sized so the
   mark shows at that native resolution instead of being downscaled to a
   smudge. Never filtered, never on a coloured box, same rule as every other
   mark in the system, so each tile is bordered rather than filled.

   A vertical with no lockup of its own falls back to .verticals-links above,
   which is why both survive in one panel. */
.verticals-products { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
/* The unlinked lockup takes the tile too. The template keeps a mark with
   nothing behind it in place and drops only its link: "a mark with nothing
   behind it keeps its place and loses its link", and without this the tile
   it keeps its place in was drawn for its neighbours and not for it: no
   border, no height, a lockup floating in the gap between two boxes. The
   mark itself is never dimmed; a mark is not filtered in this system, so
   what says the tile is inert is the quieter border, not the art. */
.verticals-products a,
.verticals-products > span {
  display: flex; align-items: center; justify-content: center; height: 100px;
  border: 1px solid var(--rule-strong); padding: 10px;
}
.verticals-products > span { border-color: var(--rule); cursor: default; }
.verticals-products img { max-height: 100%; max-width: 100%; width: auto; display: block; }
.verticals-products a:hover,
/* The tile the reader is inside, said the only way a bordered tile can say it.
   The text list marks its active item by filling it; a mark may not be filled,
   so the tile states it with the one property it already owns, the same ink
   border hover lifts to, held rather than lifted. */
.verticals-products a.is-active { border-color: var(--ink); }

/* ------------------------------------------------------------------ */
/* The narrow desktop, 821px to 1700px.                                 */
/*                                                                      */
/* 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: 20px; }
  .nav a, .nav-verticals-btn, .nav-formats a.format { font-size: 13px; }
  .nav-formats { padding: 0 12px; }
  .nav-formats a.format { padding: 0 9px; }
  .masthead-meta { font-size: 12px; gap: 8px; }
}

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

  /* 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: 12px 0; }
  .masthead-mark .pt24-logo { height: 30px; }
  .searchbox { flex-basis: 260px; max-width: 260px; }
  .btn-subscribe, .edition-btn { font-size: 12px; }
  .btn-subscribe { padding: 0 12px; }

  /* 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; }
}

/* ------------------------------------------------------------------ */
/* 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 18px/1.25 var(--sans); margin: 0; letter-spacing: calc(-.005em * var(--track)); }
.cities-head p { font-size: 12px; 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: var(--card); border-radius: 2px;
  padding: 10px 14px; max-width: 460px;
}
.area-search input {
  border: none; outline: none; background: none; font: 400 12px var(--sans);
  color: var(--ink); width: 100%;
}
.area-search:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }
.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;
}

.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 10.5px var(--sans); color: var(--ink-30); letter-spacing: calc(.08em * var(--track)); flex: none; }
/* Pills are the one radius exception, at 100px. */
.chip {
  display: inline-flex; align-items: center; gap: 7px; font: 500 12px var(--sans);
  background: var(--card); 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: 10.5px; }
.chip-free { font-size: 11px; color: var(--ink-45); }
.chip-more {
  display: inline-flex; align-items: center; gap: 6px; font: 600 11.5px var(--sans);
  color: var(--red-type); 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: 11.5px; color: var(--ink-30); margin: 0; }
.btn-save {
  background: var(--red); color: var(--knockout); border: none; border-radius: 2px;
  padding: 10px 18px; font: 700 12px 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: 11.5px; 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: 12px; color: var(--ink-30); margin: 4px 0; }

.archive-day {
  flex: none; width: 118px; display: flex; flex-direction: column; gap: 1px;
  background: var(--card); 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 19px/1 var(--sans); letter-spacing: calc(-.02em * var(--track)); }
.archive-day .m { font: 700 10px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--ink-45); }
.archive-day .w { font: 600 11px var(--sans); color: var(--ink-45); margin-top: 4px; }
.archive-day .n { font: 500 10.5px 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 11px/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-type); }
/* 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: calc(.12em * var(--track)); color: var(--paper);
  border: 1px solid var(--knock-45); border-radius: 2px; padding: 3px 8px;
}
.archive-bar .when { font: 600 12px var(--sans); color: var(--knock-75); }
.archive-bar a {
  margin-left: auto; font: 600 12px 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; }

/* ------------------------------------------------------------------ */
/* Prime Alert runner: never "Breaking"                                */
/* ------------------------------------------------------------------ */
.runner {
  background: var(--ink); color: var(--paper); overflow: hidden;
  border-bottom: 2px solid var(--red);
}
.runner .shell { height: 38px; 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: 7px; height: 7px; background: var(--knockout); }
.runner-window { flex: 1; overflow: hidden; position: relative; height: 38px; }
/* No gap here, and that is not a tidy-up. The track holds two passes and
   `pt24run` shifts it `translateX(-50%)`, so the shift lands on the repeat only
   if half the track is one whole pass *including the space before the next
   one*. A gap on the track sits between the two passes and is therefore counted
   once, so half the track fell 14px (half a gap) short of the repeat, and the
   band stepped sideways by that much every 38 seconds. The 28px belongs to the
   pass, below, where it is inside the width being halved. */
.runner-track {
  position: absolute; inset: 0; display: flex; align-items: center; gap: 0;
  width: max-content; animation: pt24run 38s linear infinite;
  font-size: 12px; white-space: nowrap;
}
/* One pass, and the two are identical by construction: every item carries its
   own trailing diamond (see the markup) and every pass its own trailing space.
   That is what makes half the track exactly one pass.

   `flex: none` because a pass must never be squeezed to fit a track it exists
   to overflow. */
.runner-pass {
  display: flex; align-items: center; gap: 28px; flex: none;
  padding-right: 28px;
}

/* Nothing to scroll. Set by pt24.js when one pass is already as wide as the
   window: the alerts all fit, so the band has no reason to move, and moving
   them anyway put the second pass on screen beside the first and printed the
   same headline twice. Motion here is a way of showing what does not fit, not
   decoration, so when everything fits it stops and the repeat comes off. */
.runner-track.is-static { animation: none; }
.runner-track.is-static .runner-pass + .runner-pass { display: none; }
/* The last separator has nothing after it once the repeat is gone. */
.runner-track.is-static .runner-pass > .runner-diamond:last-child { display: none; }

.runner-track a { color: var(--paper); }
.runner-track a:hover { color: var(--red-type); }
/* 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-type); }
.runner-diamond { color: var(--knock-28); font-size: 10.5px; }
.runner-all {
  flex: none; font: 400 12px var(--sans); color: var(--paper);
  border-left: 1px solid var(--knock-28); padding-left: var(--space-lg);
}
.runner-all:hover { color: var(--red-type); }

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

/* Main column plus a --rail column, a fifth of the page, not a fixed measure,
   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 under the reading column only.

   Not the width of the page any more: the rail's own widgets end at their own
   height, short of the main column's, so a full-width rule left a stretch of
   it crossing empty rail space before the next section's rail resumed, a
   line with nothing on either side of it. The column divider still separates
   the two, so the main column closing its own band on its own is enough. */
.hero-section > .main { padding: 24px 34px 32px 0; border-bottom: 1px solid var(--ink); }
.hero-section > .rail { padding: 24px 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; }

/* There was a `@media (min-width: 1280px)` block here and it is gone. It set
   the lead to 38px/1.12 with `text-wrap: wrap` and the standfirst to 16px on
   an 80ch measure, a step down from the 50px lead and the 20px standfirst
   the pre-redesign scale drew above 1280. The redesign brought the base rules
   to exactly those numbers, so three of the four declarations had become the
   base restated at a width, and the fourth was worse than redundant: a
   two-class selector beats `.standfirst`, so `font-size: 16px` outranked the
   reading-size restatement at the foot of this file and took `--read` off the
   lead's standfirst entirely. A reader at 140% got 140% of every paragraph on
   the page except the one under the biggest headline on it, and, because
   1280 is the only width the 16 applied at, the standfirst also got *smaller*
   as the screen got larger, which is the same shape of bug the masthead's
   brand cap was.

   The greedy wrap the block existed for lives on `h1.hero` itself. */

/* The picture and the story's own formats-and-timeline column, side by side,
   both are introduced by the headline above the row, so they read as one
   thing rather than as a picture followed by a rail that happens to sit next
   to it. 3:2 of the row to the picture, which is roughly what a 4:3 crop next
   to a 320-ish-px column of buttons and a timeline card draws out to; the
   rail's own fixed width (`--rail`) is a different number solving a different
   problem, the true rail, past the column divider, is unrelated content and
   keeps its own width regardless of what this row does. */
.hero-media-row {
  display: grid; grid-template-columns: minmax(0, 3fr) minmax(240px, 2fr);
  gap: 24px; align-items: stretch; margin-bottom: 26px;
}
.hero-media-row .media { margin-bottom: 0; }
/* Stretched to the row's height, which the picture sets. The format row sits
   at its own height and the timeline card takes what is left over, see
   .thread-rail, so the card ends level with the foot of the picture instead
   of running on past it or stopping short. */
.hero-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; min-height: 0; }

/* 90 by 26, and the only 4px corner in the file. The radius vocabulary is
   0 for anything structural, a panel, a button, a card, a field on the
   masthead row: 50% for a circle, 100px for a true pill, and a 2px
   softening on the small furniture the design draws that way: a verdict
   chip, a tag, a save button, an area pill, a phone sheet's flag. This is
   the one thing between 2 and 100, 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: var(--knockout); font: 600 12px var(--sans); letter-spacing: calc(.04em * var(--track));
  padding: 0 12px; border-radius: 4px;
}
.badge-live .dot { width: 7px; height: 7px; background: var(--knockout); }
.badge-outline {
  display: inline-flex; align-items: center; gap: 6px; font: 500 12px var(--sans);
  letter-spacing: calc(.04em * var(--track)); 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 13px var(--sans); color: var(--meta);
}
.hero-meta .muted { font-size: 13px; 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 13px var(--sans); color: var(--red-type); }
.hero-meta .updates:hover { text-decoration: underline; }
.hero-meta .badge-live { font-size: 10.5px; min-width: 0; height: 22px; padding: 0 9px; }
.hero-meta .badge-outline { font-size: 11.5px; padding: 2px 7px; color: var(--ink-70); }

/* Typography: the serif carries the hierarchy now.
   The scale itself, as opposed to the three faces named in the token block.

   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 38px/1.12 var(--serif); letter-spacing: calc(-.005em * var(--track)); margin: 0 0 14px;
  text-wrap: wrap;
}
.standfirst {
  font: 400 17px/1.35 var(--sans); color: var(--ink); margin: 0 0 22px;
  max-width: 80ch; text-wrap: pretty;
}
/* `margin: 0 0 14px`, not `margin-bottom` alone. `.media` is a <figure>, and the
   UA sheet gives a figure `margin: 1em 40px`, so setting only the bottom left
   40px of inline margin on every one of them. `.story-lead` then adds
   `width: 100%` on top, which puts the story's lead photograph 40px right of its
   own headline at every width and 40px past the column at narrow ones, scrolling
   the page sideways on a phone. `.day-shot` below already zeroes it, which is
   why the mosaic tiles were always fine and this was invisible for so long. */
.media {
  position: relative; background: var(--slot); border: 1px solid var(--rule);
  margin: 0 0 14px; display: grid; place-items: center; overflow: hidden;
}
.media img { width: 100%; height: 100%; object-fit: cover; object-position: center top; display: block; cursor: zoom-in; }
/* Unstyled, a <video> keeps its own intrinsic size instead of filling the
   16:9 box the way `.media img` does, so it renders small and off to one
   side with a barely visible native control bar -- indistinguishable from a
   broken image at a glance. `cursor: zoom-in` is deliberately not carried
   over: a click here plays the video, it does not open the lightbox. */
.media video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The story's own lead is shown whole, never cropped to fit its frame.
 *
 * `.media img` fills its box with `object-fit: cover` and
 * `object-position: center top`, which is right for a card, where a
 * thumbnail's job is to fill a tile. On the story lead it threw away the
 * bottom of every picture taller than the frame, and the frame is one of
 * three landscape ratios while a reporter's photograph is whatever their
 * phone took. The PT24 watermark is burned into the bottom-right corner
 * (`studio_media.watermark_options` defaults to bottom-right, a logo height
 * plus padding off the bottom edge), so the one part of the picture
 * guaranteed to be cut was the mark identifying it as ours.
 *
 * `contain` costs nothing where the picture already matches its frame, which
 * is every correctly cropped wire photograph: contain and cover are the same
 * rendering then. Where it does not match, the picture letterboxes against
 * `--slot`, which `.media` already paints and already centres into. Cards,
 * tiles and thumbnails are untouched and still fill. */
.story-lead img, .story-lead video { object-fit: contain; }

/* The story-lead gallery: plain CSS scroll-snap, the same technique the
   exclusive-band row above already uses, not a JS carousel library. Buttons
   and the dot/count readout are progressive enhancement over that native
   scrolling -- swipe works with none of `initGallery`'s JS at all. */
.story-gallery { padding: 0; }
.gallery-track {
  display: flex; width: 100%; height: 100%; overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; -ms-overflow-style: none;
}
.gallery-track::-webkit-scrollbar { display: none; }
.gallery-slide { position: relative; flex: 0 0 100%; width: 100%; height: 100%; margin: 0; scroll-snap-align: start; }
/* `contain`, for the reason the lead above gives: a gallery's slides are
   whatever shapes the desk attached, one frame holds all of them, and the
   watermark is in the corner that a crop takes first. */
.gallery-slide img, .gallery-slide video { width: 100%; height: 100%; object-fit: contain; display: block; }
.gallery-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; width: 34px; height: 34px;
  display: grid; place-items: center; border: 0; border-radius: 50%; background: rgba(var(--ink-rgb), .55);
  color: var(--paper); font: 600 15px var(--sans); cursor: pointer; line-height: 1;
}
.gallery-nav.is-prev { left: 10px; }
.gallery-nav.is-next { right: 10px; }
.gallery-foot { position: absolute; top: 10px; right: 10px; z-index: 2; display: flex; align-items: center; gap: 8px; }
.gallery-count {
  background: rgba(var(--ink-rgb), .55); color: var(--paper); font: 500 11px var(--sans);
  padding: 3px 8px; border-radius: 10px; letter-spacing: calc(.01em * var(--track));
}
.gallery-dots { display: flex; gap: 6px; }
.gallery-dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(255, 255, 255, .5); }
.gallery-dot.is-active { background: var(--paper); }
.media-16x9 { aspect-ratio: 16/9; }
.media-4x3 { aspect-ratio: 4/3; }
.media-3x2 { aspect-ratio: 3/2; }
.image-viewer-open { overflow: hidden; }
.image-viewer { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 28px; background: rgba(12, 13, 16, .94); cursor: zoom-out; }
.image-viewer img { max-width: min(96vw, 1600px); max-height: 94vh; width: auto; height: auto; object-fit: contain; box-shadow: 0 18px 60px rgba(0,0,0,.45); cursor: default; }
.image-viewer-close { position: absolute; top: 14px; right: 18px; border: 0; background: transparent; color: #fff; padding: 8px; line-height: 0; cursor: pointer; }
.media-viewer { position: fixed; inset: 0; z-index: 101; display: grid; place-items: center; padding: 28px; background: rgba(12, 13, 16, .96); cursor: zoom-out; }
.media-viewer video { max-width: 96vw; max-height: 94vh; width: auto; height: auto; object-fit: contain; cursor: default; }
.media-caption {
  position: absolute; left: 0; bottom: 0; background: rgba(var(--ink-rgb), .86);
  color: var(--paper); font-size: 11.5px; padding: 7px 12px; letter-spacing: calc(.01em * var(--track));
}

/* The story's formats, in the column beside its picture, Read first, then
   whatever else this filing was cut for.

   Read used to be struck from the row entirely, because as a lone button at
   the top of the rail it restated the click the headline already offers. It is
   not lone here: beside Watch and Listen it names one of three ways into the
   same story, and the brief above it is what a reader decides from.

   auto-fit rather than fixed columns: a filing with Read alone gets one button
   across the column, one with all three gets three, and none 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; flex: none;
}

.format-row .btn-line svg { width: 16px; height: 16px; flex: none; }

/* --control-h, 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. The
   phone raises both to a 44px touch target; see the touch block. */
.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: 9px 18px;
  font: 600 14px var(--ui); line-height: 1.25; cursor: pointer; text-align: center;
}
.btn-ink:hover { background: var(--red); border-color: var(--red); color: var(--knockout); }
/* min-height, not height. These are var(--control-h) controls, but "Read the
   2-min catch-up" in the story page's 185px left rail is two lines of 14px
   --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 the control
   height. */
.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: 9px 18px;
  font: 600 14px var(--ui); line-height: 1.25; cursor: pointer; text-align: center;
}
.btn-line:hover { background: var(--ink); color: var(--paper); }

/* ------------------------------------------------------------------ */
/* 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.                                          */
/* ------------------------------------------------------------------ */
/* Fills whatever height .hero-side has left over after the format row, so the
   card ends level with the foot of the picture beside it, see
   .hero-media-row. The body scrolls inside that fixed height rather than the
   card growing past the picture on a story with a long thread. */
.thread-rail {
  border: 1px solid var(--ink); background: var(--paper);
  display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0;
}

/* A two-row grid: the heading on the first, the date range under it. Plain
   type, not a control, the card used to carry a collapse toggle here, and
   the timeline is short enough beside the picture now that putting it away
   was a control looking for a reason to exist. */
.thread-head {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  column-gap: 12px; row-gap: 7px; flex: none;
  padding: 22px 24px 18px;
}
.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 18px var(--sans); letter-spacing: 0; color: var(--red-type);
  text-transform: none; margin: 0;
}
.thread-head .meta { font: 400 11px/1.5 var(--sans); color: var(--meta); }

/* The one place in the system a reader scrolls inside a box rather than the
   page, earned because the box is pinned to the picture's own height, not
   an arbitrary crop. */
.thread-body {
  padding: 4px 24px 20px; flex: 1 1 auto; min-height: 0; overflow-y: auto;
}
.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(var(--red-rgb), .16);
}

/* Date, role, headline: three lines, 13 / 13 / 15, 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 13px var(--sans); letter-spacing: 0; color: var(--meta); margin-bottom: 4px; }
.thread-role {
  font: 400 13px var(--sans); letter-spacing: calc(.01em * var(--track)); color: var(--meta);
  text-transform: uppercase; margin-bottom: 4px;
}
.thread-node.is-current .thread-date { color: var(--red-type); }
.thread-headline { font: 700 15px/1.3 var(--sans); margin-bottom: 5px; }
.thread-source { font: 400 12px var(--sans); color: var(--meta); }


/* 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 14px var(--sans); letter-spacing: calc(.02em * var(--track)); color: var(--gold-type); margin-bottom: 8px;
}
.disagreement p { font: 400 16px/1.45 var(--sans); color: var(--ink); margin: 0; }

/* Rails */
.rail-card { border: 1px solid var(--ink); padding: 20px; }
.rail-head {
  font: 700 13px var(--sans); letter-spacing: calc(.07em * var(--track));
  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); }
/* 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,
.story-rail > .ad-unit:last-child { flex: 1 0 auto; position: sticky; }

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

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

/* Both states, because both are the box inside the unit. `.ad-house` was added
   with the unsold state and needs the same growth: without it the unit stretches
   to fill the rail and the box inside it stays at its aspect-ratio height,
   leaving the dashed edge floating above a band of nothing. */
.ad-unit > .ad-link,
.ad-unit > .ad-house { flex: 1 0 auto; min-height: 0; }

/* An unsold slot holds its declared size and does not grow.

   The growth above exists so a *creative* can use the height a rail has left
   over: an image sized `contain` fills a taller box perfectly well. There is
   nothing to fill an available slot with except air: stretched to the tail of
   a long rail it became a 300x250 of copy centred in eight hundred pixels of
   dashed box, which reads as a rendering fault rather than as a space for
   sale. And the size is the whole point of this state: the dimensions are
   what the slot is offering.

   It keeps `position: sticky`, which costs nothing and still travels with the
   reader; only the stretch is given up. */
.split > .rail > .ad-unit.is-available:last-child,
.story-rail > .ad-unit.is-available:last-child { flex: 0 0 auto; max-height: none; }

/* Stacked rails have no leftover height to fill, hold the declared size.  */
@media (max-width: 1120px) {
  .split > .rail > .ad-unit:last-child,
  .story-rail > .ad-unit: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); padding: 15px; }
.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 14px/1.35 var(--sans); }
.follow-item .count { font: 400 12px var(--sans); color: var(--meta); }
.follow-item.is-live .count { color: var(--red-type); }

/* The spine: 58px | 1px | 1fr, nodes pinned at left:-25px. The story
   page's live blog runs down it; the front page's river is the mosaic. The
   story cut widens the time column to 64px, which is what "10 Aug, 05:13"
   would need if the two fixed time columns ever carried a date; see the
   known gaps in DESIGN-GUIDELINES. */
.section-head { display: flex; align-items: center; gap: 14px; margin-bottom: 24px; }
.section-head h2 { font: 700 13px var(--sans); letter-spacing: calc(.1em * var(--track)); margin: 0; }
.section-head .note { font: 400 13px 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 13px 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 11.5px var(--sans); color: var(--ink-30);
  font-variant-numeric: tabular-nums; padding-top: 2px;
}
.spine-time.is-live { color: var(--red-type); font-weight: 600; }
.spine-line { grid-column: 2; background: var(--rule); }
.spine-entry { grid-column: 3; padding-bottom: 26px; position: relative; }

/* 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: 11.5px; color: var(--ink-30); }
.spine-node {
  position: absolute; left: -25px; top: 6px; width: 9px; height: 9px;
  border-radius: 50%; border: 2px solid var(--paper);
}
h3.entry { font: 500 19px/1.18 var(--serif); margin: 0 0 8px; }
.tag-gold {
  border: 1px solid var(--gold); color: var(--gold-type); font-weight: 600;
  border-radius: 2px; padding: 1px 6px; font-size: 10px; letter-spacing: calc(.06em * var(--track));
}

/* Nukkad: ink band with swipe cards, edge to edge of the screen.

   A band is a wall across the *page*, not across .main: `mosaic_blocks()`
   in presentation.py cuts the mosaic into a "rows" block per stretch of
   stories and a "band" block per band, and home.html renders a "band" block
   as its own top-level section, outside any `.split`. So this is never
   beside a rail column to begin with: the rail that ran beside the stories
   above it ends there, and a new one starts beside whatever comes after.
   Full width is exactly what it says.

   That is also why this bleeds both edges again rather than stopping short
   of one. An earlier version kept the band nested inside .main and capped
   its right edge at .main's own boundary so it could not paint over the
   rail beside it, correct as far as it went, but it was patching a band
   that should never have been sharing a row with the rail in the first
   place. Splitting the layout so nothing sits beside a band removes the
   problem the cap was working around, so the bleed goes back to both sides.

   Not the textbook `calc(50% - 50vw)` break-out: that trick shifts a box to
   whatever its own containing block's *centre* is and then back by half the
   viewport, which only lands on the viewport edge when the containing block
   is itself centred on the page. This section's left edge is always exactly
   one gutter from the viewport's own left edge, at every width from the
   two-column desktop row down to the stacked phone column, because it is a
   direct child of `.shell` like every other top-level section, so pulling
   left by the gutter (plus, past --max, the extra margin .shell centres
   itself with) reaches the viewport edge on both sides regardless. `.nukkad-
   inner` puts the gutter and max-width back for its own content. */
.nukkad {
  background: var(--ink); color: var(--paper);
  width: 100vw;
  margin-left: calc(-1 * (var(--gutter) + max(0px, (100vw - var(--max)) / 2)));
}
.nukkad-inner {
  max-width: var(--max); margin: 0 auto; padding: 24px var(--gutter);
  display: flex; align-items: center; gap: 28px;
}
/* The mark at 38px, level with the blurb type, is what read as unclear, a
   139px-wide source asset that small is most of the way to a favicon. A
   dedicated left column lets it print near its own resolution.
   `align-items: flex-start` on the column matters as much as the height does:
   the default `stretch` fills a flex item's cross size (width, here, since
   the column runs vertically) even when the item's own width is `auto`, and
   a replaced element stretched that way fills the column at whatever height
   was given it: the aspect ratio does not survive. Content sized to its own
   ratio, not to the column, is what `flex-start` buys back. */
.nukkad-brand {
  display: flex; flex-direction: column; align-items: flex-start; gap: 12px;
  flex: none; width: 200px;
}
.nukkad-brand .pt24-logo { height: 80px; width: auto; }
.nukkad-brand .blurb { font-size: 12px; line-height: 1.45; color: var(--knock-55); }
.nukkad-cards-col { flex: 1; min-width: 0; }
.nukkad-cards-col .swipe { display: block; text-align: right; font-size: 11px; color: var(--knock-55); margin-bottom: 10px; }
/* An honest affordance, and one rule rather than the two identical ones the
   river's band and the product's own pages each grew for themselves.

   "swipe" is a touch verb, and above the phone breakpoint this column is wide
   enough that four or five cards sit in it without overflowing, so the word
   was instructing a reader to do something the row would not answer. Below
   820px two cards already exceed the column at every real phone width, and
   there it is true. Above it the scrollbar `.pt24scroll` draws is the
   affordance, and the browser only draws that when there is something to
   scroll.

   Both scopes are named rather than the base rule being flipped, because the
   two are the only two places a band is drawn: `.day-band` in the mosaic and
   `.nukkad-page` on the index and the set, and naming them keeps the
   affordance's default honest for a third. The template does the other half:
   a set with one card prints no affordance at any width, because one card
   never scrolls. */
.day-band .nukkad-cards-col .swipe { display: none; }
@media (max-width: 820px) {
  .day-band .nukkad-cards-col .swipe { display: block; }
}
/* `.nukkad-page` is no longer suppressed with it, because the premise above is
   not true of these two pages.

   The argument was measured on the river's band, where the brand column is the
   fixed 200px `.nukkad-brand` and five 210px cards fit the remainder at desktop
   widths. `/nukkad/` puts a *set title* in that column instead, three lines of
   serif, and the cards lose what the title takes. The row overflows at every
   desktop width on the index and from 1200px down on a set page. So the word
   was hidden in exactly the places it was true, and the fifth card sat cut off
   at the right edge of a 1440px screen with nothing to say it could be reached:
   `.pt24scroll` draws its scrollbar on hover, which is no help to a reader who
   has not discovered there is more.

   The river's band keeps the suppression. It was measured and it does fit. */
.nukkad-cards { display: flex; gap: 14px; overflow-x: auto; }
/* Width only. A card's height used to come from `aspect-ratio: 9/14` against
   its own width, so widening the card to make room for the title widened the
   height right along with it. Fixed at the original 150px card's height
   (150 x 14/9) instead, so width can move on its own. */
.nukkad-card {
  width: 210px; height: 233px; background: var(--dark-inset);
  border: 1px solid var(--knock-14); padding: 14px;
  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-type); letter-spacing: calc(.08em * var(--track)); margin-bottom: 6px; }
.nukkad-card .title { font: 500 16px/1.25 var(--serif); }
/* The card's sixty words. There was no rule for this at all: the detail
   screen printed the span and it inherited whatever it landed on, and the
   index screen did not print it, so a card there was a number and a title
   pinned to the bottom of a 233px box with nothing above them.

   Clamped rather than truncated at a character count: the copy is written to
   a length, and a clamp keeps a card that runs slightly long inside its box
   without cutting a sentence at a fixed number of letters in every language.
   Four lines is what fits under the title in the space the artwork leaves. */
.nukkad-card .body {
  font: 400 12.5px/1.45 var(--sans); color: var(--knock-62);
  margin-top: 8px;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4;
  overflow: hidden;
}
.nukkad-more { flex: 1; display: grid; place-items: center; font-size: 11px; color: var(--knock-50); min-width: 100px; }

@media (max-width: 820px) {
  .nukkad-inner { flex-direction: column; align-items: stretch; gap: 16px; }
  .nukkad-brand { width: auto; flex-direction: row; align-items: center; }
  .nukkad-brand .pt24-logo { height: 48px; }
}

/* 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; }

/* Sawaal */
.sawaal-block {
  border: 1px solid var(--rule); background: var(--card); 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 10.5px var(--sans); letter-spacing: calc(.09em * var(--track)); color: var(--red-type); }
.sawaal-block h3 { font: 500 22px/1.16 var(--serif); margin: 0 0 10px; }
.sawaal-block p { font: 400 12.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: var(--knockout); border: none; border-radius: 2px;
  padding: 8px 15px; font: 600 11.5px var(--sans); cursor: pointer;
}
.btn-red:disabled { background: var(--ink-18); cursor: default; }
.sawaal-count { font-size: 11px; 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; }

/* ==================================================================== */
/* 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. */
/* ==================================================================== */
/* One flat grid, three even columns, always, not rows of four shapes any
   more. presentation.py still labels a story feature, brief, wide or band
   (the picker for the day-tile size classes below), and `row`/`stack` still
   group entries for that labelling, but the grouping divs carry `display:
   contents` here and never render as boxes: every tile becomes a direct
   child of this grid and wraps every three, the same shape a desk filter
   already forced the mosaic into; see `[is-filtered]` in pt24.js, which
   used to be the one case this grid drew; now it is the only case. */
.day-mosaic {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 26px; row-gap: 30px;
}
/* Viewport media queries, not `container-type: inline-size` on this element,
   a size container query never matches the element that establishes the
   containment context, only its descendants, so a `@container` rule written
   here against `.day-mosaic` itself never fired at any width: the grid sat
   at three columns from 1920px down to a 360px phone. Same fix as
   `.brief-grid` below, and now the same two widths: 880 is where three
   columns of picture, kicker, serif headline and meta stop having the room,
   and the overflow grid under this one has to turn at the same place or the
   front page draws two columns of stories above three columns of briefs. */
@media (max-width: 880px) {
  .day-mosaic { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .day-mosaic { grid-template-columns: minmax(0, 1fr); row-gap: 22px; }
}

.mosaic-row, .mosaic-stack { display: contents; }

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

/* A date rule spans every column, it breaks the day, not one card in it.  */
.mosaic-daybreak {
  grid-column: 1 / -1;
  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 12px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--meta);
  text-transform: uppercase; flex: none;
}
.mosaic-daybreak::after { content: ""; flex: 1; height: 1px; background: var(--ink); }

.mosaic-none {
  grid-column: 1 / -1;
  padding: 40px 0; text-align: center; font: 400 14px 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, transform 320ms cubic-bezier(.2, 0, .2, 1);
}
@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(var(--ink-rgb), .035) 9px 10px),
    var(--wash);
  font: 700 10.5px var(--sans); letter-spacing: calc(.12em * var(--track)); 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;
}
/* The paper's own mark in an empty picture well: pt24-plate.png is the
   PRIMETIME24 wordmark centred on paper at 4:3, with margin enough that a
   16:9 or 3:2 well crops nothing but paper. Owner's instruction of
   2026-09-13, replacing the hatched plate on the lead and the collapsed
   news-in-brief tile. Not a photograph, so no zoom cursor and no viewer. */
.pt24-plate { object-fit: cover; object-position: center; cursor: default; }

/* A clip inside the story, said on its card.
 *
 * A tile of a still photograph looks identical whether or not there is a film
 * behind the headline, so a reader scanning a river could not tell which one
 * would play. Bottom-left, because the top-right of a tile is where the live
 * and developing plates go and the bottom-right is where a caption plate
 * starts. Ink ground with knocked-out type, so it re-points its accents with
 * the rest of them. */
.tile-clip {
  position: absolute; left: 8px; bottom: 8px; z-index: 2;
  display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 50%; background: rgba(var(--ink-rgb), .72); color: var(--paper);
  pointer-events: none;
}
.tile-clip svg { width: 13px; height: 13px; margin-left: 1px; display: block; }

/* 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: var(--knockout);
  font: 600 9.5px var(--sans); letter-spacing: calc(.1em * var(--track)); padding: 4px 8px;
}
.day-flag .dot { width: 5px; height: 5px; background: var(--knockout); }

/* 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 what the
   entry's node says, which is what 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 13px var(--sans); letter-spacing: 0; text-transform: none;
  color: var(--meta);
}
/* The tick, and what it is for. It is the spine's node, carried up to the
   story it belongs to, a 14px rule in the kicker where the old river had a
   dot in a gutter.

   Its colour comes off what the entry *is*, the same way `.spine-node`'s does
   further down the file: the API sends `node` as `plain`, `urgent` or
   `checked` and the template writes that straight into the class. It used to
   send a hex, which broke the first rule in the guidelines, the CMS supplies
   content and labels, not presentation, and broke it expensively, because
   the hex it sent was the pre-redesign red and gold and every alert on the
   page therefore drew its tick in last season's accent beside this season's
   everything else.

   Ink is the default because on all but a handful of filings that is what the
   node says. The mosaic tile and the overflow tile do not print the element at
   all in that case, a mark in the body colour beside every headline on the
   page is furniture rather than a signal, but a band prints its tick
   unconditionally, so `plain` has to draw something and ink is what it is. */
.day-kicker .tick { width: 14px; height: 2px; flex: none; background: var(--ink); }
/* Red is the state: an alert is the one thing in the day that is happening
   rather than reported. */
.day-kicker .tick.is-urgent { background: var(--red); }
/* Gold is a claim being weighed (the fact-check's left rule, its desk label,
   the CONTEXT verdict, the correction), so the tick takes it too and the eye
   can find the checks by running down the day. */
.day-kicker .tick.is-checked { background: var(--gold); }
/* `white-space: nowrap` so "CITY DESK" breaks as a unit, not mid-word, a
   narrow mosaic column (two or three tiles across on a phone) is exactly
   where a flex item is tight enough for the browser to wrap "CITY" onto its
   own line and leave "DESK" under it. */
.day-kicker .desk { font-weight: 500; color: var(--meta); text-transform: uppercase; letter-spacing: calc(.05em * var(--track)); font-size: 11.5px; white-space: nowrap; }
.day-kicker time { font-weight: 400; letter-spacing: 0; color: var(--meta); white-space: nowrap; }
.day-kicker .desk + time::before { content: "-"; 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-meta {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  margin-top: 11px; font: 400 13px var(--sans); color: var(--meta);
}
/* Each item stays one line: without this, a narrow mosaic column wraps
   "Sources attached" itself into two lines and leaves the dot the row draws
   stranded at the start of the second, reading as a bullet with nothing
   after it. `flex-wrap` above still moves a whole item to the next line
   when the row runs out of room; this only stops a single item breaking. */
.day-meta > * { white-space: nowrap; }
/* 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: "-"; margin-right: 11px; color: var(--meta); }
.day-meta .thread-link { color: var(--red-type); 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; transform: scale(1.03); }

/* --- sizes ---------------------------------------------------------- */
/* One size, because every tile is the same size in the grid now. Feature,
   wide and brief were three shapes handed three different amounts of room by
   the old rows; three even columns have the same room for everyone, so the
   type stops promising otherwise: 19px, the size a brief always was.  */
.day-tile .day-headline { font: 500 19px/1.18 var(--serif); text-wrap: pretty; letter-spacing: 0; }
.day-tile .day-kicker   { font-size: 12px; margin-bottom: 8px; }
.day-tile .day-meta     { margin-top: 9px; font-size: 12px; }

/* Nukkad and Sawaal were each handed a row of their own before the grid
   flattened: "a band is a wall across the page" in presentation.py. Truer
   than ever now: `mosaic_blocks()` renders a band as its own top-level
   section, not a row inside `.day-mosaic`'s grid at all, so there is no
   grid row-gap to inherit its spacing from. 30px matches that row-gap on
   both edges instead, so a band reads like a beat in the same rhythm as
   every other row rather than a section with different margins. */
.day-band { margin: 30px 0; }
.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); }

[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); }

/* "More news": the day's remaining wire copy, headline only. No picture
   because there is no crop for two hundred wire filings to carry, and a
   thumbnail with nothing under it reads as a missing image rather than as a
   deliberate choice. Same three-column rhythm as the mosaic above it.

   Viewport media queries, not a `@container`, a size container query never
   matches the element that establishes it, only that element's descendants,
   which is why `.day-mosaic`'s first attempt at this never fired at any
   width. It has viewport queries of its own now, and these are its two
   widths rather than the 820 they used to be: 820 is the phone breakpoint
   and it had no business setting a desktop column count. Between 821 and 880
   it put three columns of briefs directly under two columns of stories, on
   two grids whose whole argument is that they share a rhythm. */
.brief-head { margin: 34px 0 18px; }
.brief-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 26px; row-gap: 0;
}
/* The rule sits on every tile, first row included, rather than only between
   rows, column count changes at each breakpoint below, and an nth-child
   rule tuned to "skip the first row" at three columns misplaces itself the
   moment the grid drops to two or one. A rule above every tile is the one
   version that survives all three without a matching breakpoint of its own. */
.brief-tile {
  display: flex; flex-direction: column; min-width: 0;
  padding-top: 18px; border-top: 1px solid var(--rule);
}
.brief-tile .day-headline { font: 500 17px/1.3 var(--serif); text-wrap: pretty; }
.brief-tile .day-meta { margin-top: 8px; font-size: 12px; }
@media (max-width: 880px) {
  .brief-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .brief-grid { grid-template-columns: minmax(0, 1fr); row-gap: 18px; }
}

/* `display: flex`, the way `.btn-line` above does it, so this works on an
   anchor as well as on a `<button>`.

   It was written when all three call sites were buttons, and `width: 100%` on
   the inline box an `<a>` starts as does nothing at all: the front page's
   "Load earlier from today" collapsed to the width of its own words and
   left-aligned itself the moment it became the link it should always have
   been. The flex centring is what `.btn-line` already uses for exactly this
   reason: two of its call sites are anchors. */
.btn-wide {
  display: flex; align-items: center; justify-content: center;
  width: 100%; background: none; border: 1px solid var(--ink); border-radius: 0;
  padding: 15px; font: 600 14px var(--ui); cursor: pointer; color: var(--ink);
  text-align: center; text-decoration: none;
}
.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 13px var(--sans); letter-spacing: calc(.07em * var(--track)); }

.edition-item .kicker { font-size: 11.5px; font-weight: 500; color: var(--red-type); letter-spacing: calc(.07em * var(--track)); }
.edition-item .title { font: 500 17px/1.2 var(--serif); margin-top: 4px; }

/* ------------------------------------------------------------------ */
/* Screen 02: story page                                               */
/* ------------------------------------------------------------------ */
/* The LIVE chip above the headline, exactly as the front page sets it: the one
   item on the screen that is a state rather than a fact, on its own line. */
.story-flags { margin: 0 0 12px; display: flex; align-items: center; gap: 12px; }

/* Follow, and the thread's catch-up where there is one. Under the byline rule
   rather than in a bar above the masthead, so the controls sit with the story
   they act on. Not full width: these are two controls, not a form. `.is-off` is
   pt24.js's state hook and carries no rule of its own, a follow the reader
   has not made and one they have look the same, and the label is what says
   which. */
.story-actions { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 22px; }
.story-actions .btn-line { flex: 0 0 auto; }

.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 10.5px var(--sans); letter-spacing: calc(.09em * var(--track)); color: var(--ink-30); margin-bottom: 14px; }
.thread-nav .items { display: flex; flex-direction: column; font-size: 12px; }
.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-type); }
.thread-nav .t { font-variant-numeric: tabular-nums; }
.thread-nav .more { padding: 7px 0; color: var(--ink-45); font-size: 11px; 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 10.5px var(--sans); letter-spacing: calc(.09em * var(--track)); color: var(--ink-30); margin-bottom: 10px; }
.thread-started .body { font-size: 12px; 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); }
/* Set as the label it is. The desk a story is filed to is printed in caps
   everywhere else in the paper, the river's kicker, the section head, the
   local hub's YOUR AREA, because the store holds section names in whatever
   case the desk typed them ("civic", "business") and the caps are what make
   them read as furniture rather than as a word. The trail here was the one
   place printing them raw, so a story filed to Civic said CIVIC in the river
   and civic on its own page. */
.crumbs {
  display: flex; align-items: center; gap: 10px; font-size: 13px;
  color: var(--meta); margin-bottom: 14px; flex-wrap: wrap;
  text-transform: uppercase; letter-spacing: calc(.05em * var(--track));
}
.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. */
/* `wrap`, not `balance`. Balancing pulls words down off the first line and
   leaves a short ragged block inside a column it never reaches the edge of,
   measured at 746px: balance sets 496/503/516, greedy sets 676/670/170. The
   front-page hero has always set greedily; this is the story page catching up
   with the decision, not a new one. */
h1.story { font: 600 38px/1.12 var(--serif); letter-spacing: calc(-.005em * var(--track)); margin: 0 0 16px; text-wrap: wrap; }
.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: 13px; line-height: 1.4; }
.story-byline .who .name { font-weight: 600; }
.story-byline .who .desk { color: var(--ink-30); }
/* Who a blended story was written from. Sits under the byline in the same
   quiet grey, one notch smaller: it is a credit rather than a byline, and it
   names three or four mastheads where the line above names one. Only drawn on
   a blend, which is a small minority of stories. */
.story-byline .who .compiled-from { font-size: 12px; font-style: italic; }
.story-tags { margin-left: auto; display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
/* Share, on the byline line: one glyph in the byline's grey, red on hover,
   a 32px square so a thumb finds it. Not the outlined button it was: that
   drew a control the width of a word between the headline and the picture
   and looked like the start of a form. */
.story-share {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; margin: -6px 0; padding: 0; background: none;
  border: 0; border-radius: 0; color: var(--ink-45); cursor: pointer;
}
.story-share .pt24-icon { width: 18px; height: 18px; }
.story-share:hover, .story-share:focus-visible { color: var(--ink); }
.story-share:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.tag { font: 500 11.5px var(--sans); letter-spacing: calc(.06em * var(--track)); border: 1px solid var(--ink); border-radius: 0; padding: 3px 8px; }
.tag.gold { border-color: var(--gold); color: var(--gold-type); }
.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. */
/* No height cap on the lead picture: a cap cropped the frame top and
   bottom on short screens, and the paper's mark, burnt into the picture's
   bottom-right corner since 2026-09-13, went with the crop. The frame is
   its own 16:9 at the column's width, which on a laptop is the fold. */
.story-lead { width: 100%; margin-bottom: 24px; }
.story-lead .media-caption { max-width: 100%; }
/* The mark on a representative picture: PT24's raised fist alone, small,
   unboxed, in the top-right corner of the frame (the generated picture
   carries the paper's own mark in the bottom-right). A tap opens the note
   under it; nothing else is printed on the picture, the black caption
   band is gone. `.media` is the positioned box, so the corner is the
   frame's corner whatever the crop. The image carries its own soft halo,
   so it reads on a light photograph without a plate. */
.media-mark {
  position: absolute; top: 8px; right: 8px; width: 24px; height: 24px;
  padding: 3px; margin: 0; background: none; border: 0; border-radius: 4px;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.media-mark img { width: 18px; height: 18px; display: block; opacity: .9; }
.media-mark:hover img { opacity: 1; }
.media-mark:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.media-mark-note {
  position: absolute; top: 36px; right: 8px; max-width: min(260px, calc(100% - 20px));
  background: rgba(var(--ink-rgb), .92); color: var(--paper); font: 500 12px/1.4 var(--ui);
  padding: 7px 10px; border-radius: 4px; display: none; z-index: 2;
}
.media-mark[aria-expanded="true"] + .media-mark-note { display: block; }

/* 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 11.5px var(--sans); letter-spacing: calc(.08em * var(--track)); }
.player-head .note { font-size: 11px; 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: 10.5px; color: var(--knock-50); letter-spacing: calc(.08em * var(--track)); }
.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: 10.5px; 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: calc(.1em * var(--track)); color: var(--knock-50); margin-bottom: 4px; }
.segment .text { font: 400 12px/1.5 var(--sans); color: var(--knock-68); }
.segment.is-active { border-left-color: var(--red); }
.segment.is-active .label { color: var(--red-type); }
.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 13px var(--sans); letter-spacing: calc(.1em * var(--track)); margin: 0; }
.live-head .fill { flex: 1; height: 1px; background: var(--rule); }
.live-head .auto { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--ink-30); }
.live-head .auto .dot { width: 6px; height: 6px; background: var(--red); }

.update-badge {
  font: 700 10px var(--sans); letter-spacing: calc(.09em * var(--track)); background: var(--red);
  color: var(--knockout); padding: 2px 7px; border-radius: 2px;
}
.update-badge.analysis { background: none; color: var(--ink); border: 1px solid var(--ink); font-weight: 600; letter-spacing: calc(.08em * var(--track)); }
.update-head { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; flex-wrap: wrap; }
.update-head .author { font-size: 11px; color: var(--ink-30); }
h3.update-lead { font: 500 23px/1.16 var(--serif); margin: 0 0 10px; }
/* The reading size. 15px, up from the 14 the pre-redesign paper set its live
   updates at, 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.

   It read 17 here for a while, and this comment went on saying 17 for longer:
   the serif re-cut took it to 17 and the front-page rework that followed
   brought it back to 15, moving the rule and the restatement at the foot
   together and leaving only the prose behind. Fifteen at 1.6 on an 88ch
   measure is what the drawing holds; DESIGN-GUIDELINES said 17 too and has
   been corrected against it.

   `--read` is the reader's own 100/120/140% setting and it multiplies this,
   so the rule at the foot of this file that restates the size has to restate
   15 as well. */
.update-body { font: 400 15px/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: 11px; color: var(--ink-30); flex-wrap: wrap; }
.update-links a { color: var(--gold-type); font-weight: 600; }
.update-links button { background: none; border: none; padding: 0; font: inherit; color: var(--ink-30); cursor: pointer; }

/* 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: calc(.1em * var(--track)); color: var(--gold-type); 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-type); border: 1px solid var(--gold); }
.verdict.v-false { color: var(--red-type); border: 1px solid var(--red); }
.claim .text { font: 400 12.5px/1.55 var(--sans); color: var(--ink-70); }

/* A card, so it takes the card ground. It was #fff here and --paper eighteen
   hundred lines further down, which is one box drawn twice in two colours and
   the later one winning silently. The argument for --paper was that #fff was
   the one colour on the page with no name; --card is that name now, and it is
   what every other card in the paper is drawn on, the settings rows, the map
   card, the Sawaal block, a day in the archive strip. A corrections log that
   sinks into the page it corrects is the one card that cannot afford to. */
.corrections {
  margin-top: 30px; padding: 20px 22px; border: 1px solid var(--rule);
  background: var(--card);
  display: flex; gap: 18px; align-items: center;
}
.corrections .label { font: 600 11.5px var(--sans); letter-spacing: calc(.06em * var(--track)); color: var(--ink-30); margin-bottom: 6px; }
.corrections p { font: 400 12.5px/1.55 var(--sans); margin: 0 0 4px; color: var(--ink-70); }
.corrections a { font-size: 11.5px; font-weight: 600; color: var(--gold-type); 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: 14px; }
.doc-link .arrow { color: var(--gold-type); }

/* ------------------------------------------------------------------ */
/* Screen 03: local hub                                                */
/* ------------------------------------------------------------------ */
/* `main.local`, not `.local`.

   The class does two jobs in this screen: it names the page container
   (`<main class="shell-story local">`) and it modifies the heading
   (`<h1 class="local">`, six lines down). A bare `.local` selector matches
   both, so this page-level footer padding was also applied to the headline,
   56px of it, between "Bengaluru, Koramangala" and the standfirst directly
   beneath it, on every locality page at every width.

   It reads as a missing element rather than as spacing, which is why it
   survived a search: nothing sits in that gap, and the space is *inside* the
   h1's own box. Measured at 390px before the fix, the h1 was 82px tall for one
   23px line. */
main.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 12px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--red-type); }
h1.local { font: 600 38px/1.12 var(--serif); letter-spacing: calc(-.005em * var(--track)); margin: 0 0 10px; }
.local-head p { font: 400 15px/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: 11px; color: var(--ink-30); margin-right: 5px; }
.area-pill {
  font: 500 11px 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: var(--knockout); 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 --rail-wide rail with a 36px gap. It is the
   same rail width the story page's takes, which is the point, a reader
   crossing from one to the other is not meant to notice a new measure. */
.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: calc(.08em * var(--track)); background: var(--red);
  color: var(--knockout); padding: 2px 7px; border-radius: 2px;
}
.area-kicker { font: 600 10px var(--sans); letter-spacing: calc(.08em * var(--track)); color: var(--red-type); }
h2.local-lead { font: 500 28px/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 13px var(--sans); color: var(--meta); }
.local-secondary { display: flex; flex-direction: column; gap: 18px; }
.local-secondary h3 { font: 500 18px/1.2 var(--serif); margin: 6px 0 0; }
.local-secondary .when { font: 400 12px var(--sans); color: var(--meta); }
.desk-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.desk-grid h3 { font: 500 17px/1.22 var(--serif); margin: 0 0 7px; }
.desk-grid .when { font-size: 11px; 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 12px var(--sans); letter-spacing: calc(.07em * var(--track)); }
.sawaal-board-head .note { font-size: 11px; color: var(--knock-55); }
.sawaal-board-head .ask { margin-left: auto; font-size: 11.5px; font-weight: 600; color: var(--red-type); }
.sawaal-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
/* The card's default is the cream page, and the board's is the exception,
   which is the way round it has to be, because the card is drawn on cream on
   the Sawaal index and on ink only inside this board. It was written the other
   way when the board was the only place it appeared: ink-ground colours were
   put straight on `.sawaal-card`, the index printed paper on cream, and paper
   on cream is not faint, it is absent: a grid of blank rectangles with only
   the status line showing. That was fixed by overriding the card back to ink
   eighteen hundred lines below and re-scoping the knockout under the board, so
   both grounds were decided twice in two places. Once each, here. */
.sawaal-card { border: 1px solid var(--ink); padding: 14px; color: var(--ink); }
.sawaal-card:hover { border-color: var(--red); }
.sawaal-card .status { font: 600 10px var(--sans); letter-spacing: calc(.08em * var(--track)); margin-bottom: 7px; }
.sawaal-card .q { font: 600 12.5px/1.35 var(--sans); margin-bottom: 9px; }
.sawaal-card .foot { font-size: 10.5px; color: var(--meta); }
/* The board has an ink ground under it, so its cards knock out of it. */
.sawaal-board .sawaal-card { border-color: var(--knock-16); color: var(--paper); }
.sawaal-board .sawaal-card:hover { border-color: var(--knock-28); }
.sawaal-board .sawaal-card .foot { color: var(--knock-50); }

.map-card { border: 1px solid var(--rule); background: var(--card); }
.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: 10.5px; color: var(--ink-45); letter-spacing: calc(.08em * var(--track)); }
.map-card .copy { padding: 14px 16px; }
.map-card .copy .title { font: 600 12px var(--sans); margin-bottom: 5px; }
.map-card .copy .note { font-size: 11.5px; color: var(--ink-45); line-height: 1.5; }

.week-list { display: flex; flex-direction: column; gap: 12px; font-size: 12px; }
.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-type); }
.week-row .what { line-height: 1.45; }

.nearby-row { display: flex; justify-content: space-between; align-items: baseline; }
.nearby-row .name { font: 500 14px var(--sans); }
.nearby-row .count { font: 400 12px var(--sans); color: var(--meta); }
.nearby-row.is-busy .count { color: var(--red-type); 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 14px/1.55 var(--sans); color: var(--knock-62); margin: 0 0 16px; max-width: 34ch; }
.footer .charter { color: var(--gold-type); font-size: 13px; font-weight: 500; }
.footer h4 { font: 700 11.5px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--paper); margin: 0 0 14px; }
.footer-links { display: flex; flex-direction: column; gap: 9px; font-size: 14px; }

/* 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-45); 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-45); }
.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); }

/* The FOLLOW column, which has always been two different things and now looks
   like two.

   Above: the paper's channels as a row of their own marks, drawn by
   `newsroom/includes/follow.html` and shared with the account door. They were
   five lines of 14px cream, which is five lines a reader had to read before
   finding out the paper is on Instagram, and which put "Instagram" in Latin in
   the middle of a Devanagari footer. A mark is recognised without being read
   and reads the same in every script the paper publishes in.

   38px, which is a thumb and the smallest square all five marks survive at.
   The ground is the inset the sheet already uses for a raised panel on ink.
   The lit state is the house red rather than each channel's own colour: a
   footer that went green, then red, then magenta under the cursor would be
   four other companies' footer with this paper's name at the top of it.

   A channel with no address does not light. It dims to the floor of the
   knockout scale and its border goes dashed, so a door and a placeholder can
   be told apart without being read, and it is a span rather than a link, so
   it is not in the tab order either. That is the footer's standing rule for
   PT24 Daily, kept: being one of the paper's channels earns the tile, having
   a URL earns the link.

   Below: the two things a reader does here rather than elsewhere, installing
   the paper and having it mailed at seven. A leading glyph, the words, and the
   whole row as the target, which on a phone is the difference between a 14px
   word and something a thumb can land on. Install stays a button, because
   installing is not navigation, and stays hidden until the browser says the
   page can be installed: `initInstall` in pt24.js sets `hidden` on it, so the
   rule for that attribute has to beat the flex below. */
.follow-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 18px; }
.follow-tile {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 38px; height: 38px; border-radius: 9px; font-size: 17px; line-height: 1;
  background: var(--dark-inset); border: 1px solid var(--knock-22);
  color: var(--knock-75);
  transition: background .16s ease, border-color .16s ease, color .16s ease;
}
.follow-tile:hover, .follow-tile:focus-visible {
  background: var(--red); border-color: var(--red); color: var(--knockout);
}
.follow-tile.is-unset { color: var(--knock-50); border-style: dashed; }
.follow-actions { display: flex; flex-direction: column; gap: 2px; }
.follow-action {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 7px 0; border: 0; background: none; cursor: pointer; text-align: left;
  font: 400 14px var(--sans); color: var(--knock-75);
}
.follow-action .pt24-icon { font-size: 16px; flex: none; }
.follow-action:hover, .follow-action:focus-visible { color: var(--paper); }
.follow-action[hidden] { display: none; }

/* The same row in the account door, which is 240px of paper rather than a
   footer column of ink. Five tiles at 38 and four gaps at 8 come to 222px in
   208px of panel and wrap; four pixels off the tile and two off the gap fit
   the one row the panel was drawn for. The panel is a flex column with its own
   gap, so the row gives back the margin it takes in the footer. */
.account-panel .follow-row { gap: 6px; margin: 0; }
.account-panel .follow-tile { width: 34px; height: 34px; font-size: 15px; }
.footer-base { padding-top: 22px; display: flex; gap: 24px; font-size: 12px; color: var(--knock-50); flex-wrap: wrap; }
.footer-base a { color: var(--knock-50); }
.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;
}
/* An h1 on the two screens that have no other heading, the 503 page and a
   back edition with no lead, so the margin is set on all four sides rather
   than the bottom alone: a UA h1 brings 0.67em of its own top margin and
   would open the box up. */
.empty .label { font: 700 11.5px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--meta); margin: 0 0 9px; }
.empty p { font: 400 14px/1.5 var(--sans); color: var(--ink-70); margin: 0; max-width: 56ch; }
/* The 404 and 403 pages are the two screens where a link inside the prose is
   the entire point of the page, and the global rule sets `text-decoration:
   none` on `a`, so an ink-coloured un-underlined link in a paragraph is not
   visibly a link at all. Underline them here rather than adding a class, so
   the error pages carry no markup the rest of the site does not. */
.empty p + p { margin-top: 10px; }
.empty p a { text-decoration: underline; text-underline-offset: 2px; }
.empty-rail { padding: 14px 0; font: 400 13px/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 14px var(--sans); border-radius: 0; z-index: 96;
  box-shadow: 0 2px 12px rgba(var(--ink-rgb), .2);
}

/* On a phone the desktop placement is wrong twice over.

   `bottom: 28px` sits inside the fixed tab bar's own band, and the toast's
   z-index put it on top, so "Areas saved" and every network error painted
   over the tab icons and, on a home-indicator device, over the indicator.

   And `left: 50%` with no `right` and no width gives a fixed element a
   containing block running from the middle of the screen to its right edge, so
   shrink-to-fit caps the toast at half the viewport, about 195px on a 390px
   phone. `failed()` can produce a ninety-character sentence, which wrapped
   into a five-line black slab covering the middle two tabs.

   The z-index above also settles a tie: the toast was 90 and the first-run
   takeover is 90 too, so which one a reader saw was document order. */
@media (max-width: 820px) {
  .toast {
    left: var(--gutter); right: var(--gutter);
    transform: none; text-align: center;
    bottom: calc(var(--tabbar-h) + var(--safe-bottom) + 12px);
  }
}
.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 12px 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;
}

/* The way past the chrome, for a reader who is not using a pointer.

   There was no skip link on any page of this site, and the chrome in front of
   the content is not small: the top bar's three controls, the alert runner
   (which is a marquee of links), the service strip, the masthead's search
   field, Subscribe, the edition button, the account door and the whole nav.
   A keyboard reader tabbed through all of it on every page, and a screen
   reader user who wanted the story had no single move that got them there.

   Hidden the way `.sr-only` is hidden until it takes focus, and then drawn as
   the loudest thing on the page, because at that moment it is the only thing
   the reader is looking for. `position: fixed` on focus so it lands in the
   same corner whatever the page has scrolled to. */
.skip-link {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.skip-link:focus {
  position: fixed; top: calc(var(--safe-top, 0px) + 8px); left: 8px; z-index: 200;
  width: auto; height: auto; margin: 0; clip: auto; overflow: visible;
  padding: 10px 16px; background: var(--ink); color: var(--paper);
  font: 600 14px var(--ui); text-decoration: none;
}

:focus-visible { outline: 2px solid var(--focus); 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; }
  .split.is-river > .rail { padding-top: 24px; }
  .story-grid { grid-template-columns: minmax(0, 1fr); }
  .story-grid > * { 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; }
}

/* Layout only below 820px. The phone's *type* is decided in one place, the
   mobile scale further down, this block used to open with `h1.hero: 30px`
   and `h1.story, h1.local: 26px` and the mobile scale then set 24 and 23 over
   the top of them, so the three headlines the paper cares most about were
   each sized twice and the first number never drew. */
@media (max-width: 820px) {
  .desk-grid, .sawaal-cards, .local-lead-grid { grid-template-columns: 1fr; }
  .player-grid { grid-template-columns: 1fr; }
  .hero-media-row { grid-template-columns: minmax(0, 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;
  }
  .footer-grid { grid-template-columns: 1fr; }
}

/* ================================================================== */
/* The phone's own chrome                                              */
/*                                                                     */
/* 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.             */
  /*                                                                   */
  /* The clip itself is on the base `body` rule now; see there for why.  */
  /* ---------------------------------------------------------------- */

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

  /* The masthead is an empty box on the phone now, the row and the nav are
     both hidden above, and the two dialogs it used to hold have moved out to
     <body>. It still wraps .runner, so it stays; its rule would otherwise be
     a line under nothing. */
  .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)); }

  /* The search field, on the phone.

     `.masthead-row` above carries the site's only search input and is hidden
     here, so below 820px there was no way to search at all, the top bar's
     magnifier is a link to `/search/`, and that page then asked the reader to
     type into a field that was `display: none`. `search.html` renders its own
     field for this width; above it the masthead's is a few pixels away and two
     would be one too many.

     16px is not a style choice. iOS Safari zooms the viewport whenever a
     focused input is under 16px, and the zoom does not undo itself on blur,
     the reader is left on a magnified page with the layout pushed sideways. */
  .searchbox-phone {
    display: flex; width: 100%;
    margin: 0 0 var(--space-md);
  }
  .searchbox-phone input { font-size: 16px; }

  /* Every field on the phone, for the same reason. The area search is the
     primary control of the area picker and was set at 12px, so tapping "Type
     any area, ward or pincode" zoomed the page and pushed the sheet's own
     Save row off the bottom of the screen. */
  .searchbox input, .area-search input,
  input[type="search"], input[type="text"], textarea, select {
    font-size: 16px;
  }

  /* ---------------------------------------------------------------- */
  /* 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.

     Same control desktop's `.edition-btn` is, plain text and a caret, no
     border, no fill, sentence case, not the bordered pill this used to be.
     "Where you are reading from is a statement about this page, not an
     action," per the comment on `.edition-btn` above; that reasoning doesn't
     change because the screen got narrower. */
  .mtop-edition {
    flex: 0 1 auto; min-width: 0; margin-left: auto; margin-right: auto;
    display: flex; align-items: center; gap: var(--space-2xs);
    height: 48px; padding: 0;
    background: none; border: none;
    font: 600 13px var(--ui); color: var(--ink);
    cursor: pointer; overflow: hidden;
  }
  .mtop-edition:active { color: var(--red-type); }
  .mtop-edition .txt {
    flex: 0 1 auto; min-width: 0; text-align: left;
    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  }
  .mtop-edition .caret { flex: none; font-size: 9px; color: var(--ink); }

  /* 44px minimum target, drawn on a 24px grid at 1.8px stroke. `cursor`
     matters even though this bar only ever runs under 820px: the search icon
     is an `<a>` and gets a pointer for free, but the Sections toggle beside
     it is a `<button>`, and a bare button defaults to `cursor: default`, the
     one control on the bar that looked unclickable with a mouse. */
  /* The comment above said 44px and the rule said 34px, which is the value
     that shipped, under the floor, on the only two controls in the phone's
     top bar. Widened to the 44px it always claimed, with the negative margins
     opened to match so the *drawn* footprint is unchanged: the glyph is still
     a 20px svg and the outer box still occupies 24px, so nothing on the bar
     moves. Only the part a thumb can hit is bigger. */
  .mtop-icon {
    flex: none; width: 44px; height: 44px; margin: 0 -10px;
    display: grid; place-items: center; color: var(--ink-70);
    cursor: pointer;
    /* The two icons in this bar are different elements, search is an `<a>`,
       Sections is a `<button>`) and only one of them arrives with the user
       agent's own chrome on it. This rule set size, colour and cursor and
       reset none of that, so the Sections toggle rendered as a grey bevelled
       slab (`background: #EFEFEF`, `border: 2px outset`) beside a bare
       magnifier, in the top-right corner of every phone page. It read as a
       stuck focus ring or a broken image, and it was the loudest thing on the
       masthead.

       `appearance: none` as well as the longhands, because the bevel on a
       `<button>` is drawn by the widget style rather than by the border on
       some engines. The focus ring is deliberately not touched; it is set
       globally and a keyboard reader needs it. */
    appearance: none; -webkit-appearance: none;
    background: none; border: 0; padding: 0;
    font: inherit;
  }
  /* The two icons' negative margins otherwise cancel the bar's own 10px gap
     outright, leaving their boxes flush against each other. This adds it back
     between the pair specifically, without pulling the first icon any further
     from the edition chip on its other side. 11px preserves the gap the 34px
     rule drew, so the widening is invisible. */
  .mtop-icon + .mtop-icon { margin-left: 11px; }
  .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(var(--ink-rgb), .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: calc(.09em * var(--track)); 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);
  }
  /* The format axis fills its row exactly, whatever is in it.

     This was `repeat(3, 1fr)` and the comment above it read "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." That was written when the
     axis was Read / Watch / Listen / You. The design reference settles it at two:
     `Watch | My Threads`, and this box publishes one, so the rule it was
     written to prevent is the rule it now causes: a single cell in a
     three-column hairline grid, with two empty cells and precisely the hanging
     corner it warns about.

     One row of equal columns, however many there are: `grid-auto-flow: column`
     with `grid-auto-columns: 1fr` and no template, so the count comes from the
     items rather than from a number typed here. (`repeat(auto-fit, minmax(0,
     1fr))` looks like the same idea and is not: `1fr` gives auto-fit no
     minimum to divide the row by, so it resolves to a single column.) */
  .msections-grid.is-formats {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
  }
  /* The cell, whether or not it goes anywhere.

     A vertical with nothing filed to it is named and not linked, the rule
     this sheet shares with the footer, the panel and the nav, and the sheet
     prints that as a bare `<span>`. Only the `<a>` was styled, so those cells
     drew as 16px block text with no padding in the top-left corner of a 48px
     grid cell, overlapping the hairline and the cell beside them. Two of the
     eight on a Bengaluru front, which is the ordinary state of a young
     edition rather than an edge case.

     One rule for both, and the span then says it is inert the only way a
     flat cell can: a step back in colour, and a cursor that does not promise
     a click. It keeps its place in the grid because the grid is the paper's
     contents page and a section that exists but has nothing in it today is
     still a section. */
  .msections-grid a,
  .msections-grid span {
    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 span { color: var(--ink-30); cursor: default; }
  .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.                             */
  /* ---------------------------------------------------------------- */
  /* Five columns were hard-coded here while the template was deliberately
     dropping the tabs with nothing behind them: "the bar closes up; four
     tabs that all go somewhere beat five where one apologises". The bar did
     not close up. Four tabs left 78px of empty bar at the right on a 390px
     screen, and on the 404 page, where the chrome has no edition to ask, two
     tabs left 234px: sixty percent of the bar blank, with two icons huddled
     against the left edge.

     Flex, so the columns are whatever there are. `flex: 1 1 0` on the tab and
     not `1 1 auto`, because equal shares are the point, a tab is a
     destination under a thumb and they are all the same size whatever their
     labels measure. */
  .tabbar {
    display: flex;
    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 {
    flex: 1 1 0; min-width: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    font: 600 9.5px var(--sans); letter-spacing: calc(.07em * var(--track));
    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(var(--ink-rgb), .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, so they are the one
     chip on the phone that goes past the touch floor rather than sitting on
     it. The generic touch rule below used to name `.chip` too and, being
     later, quietly took this back to 36px; it names `.filter-chip` only now.
     Every chip a finger can reach is 44px since the client's package fixed
     that floor; the chips on You and in first run say so through their own
     two-class rules, which outrank this at any width. */
  .chip { padding: 10px 16px; min-height: 44px; }

  /* 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: 14px; }
  /* 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: 24px; line-height: 1.14; font-weight: 600; letter-spacing: 0; }
  h1.story, h1.local { font-size: 23px; line-height: 1.14; }
  h2.local-lead { font-size: 20px; line-height: 1.18; }
  .section-head h2 { font-size: 12px; }
  h3.entry, h3.update-lead, .sawaal-block h3 { font-size: 16px; line-height: 1.28; font-weight: 600; }
  .standfirst { font-size: 15px; line-height: 1.5; }
  .update-links, .crumbs { font-size: 12px; }

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

  /* Every measure cap is a desktop concern; the phone column is the cap. */
  .standfirst, .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-main { padding: 18px 0 0; }
  .story-rail { padding: 24px 0 0; }
  .thread-nav { padding-top: 18px; }

  /* The lead picture runs edge to edge on a phone, the way every news app
     draws it. It is the largest thing on the screen and the reason the reader
     tapped the headline, and the shell's 18px gutter was taking a tenth of a
     390px screen off the one element that earns its width. Text keeps the
     gutter: a line of type needs the margin to be readable, a photograph does
     not. Negative margins rather than a wider box, so the gutter stays defined
     in one place; the safe-area insets come off too, otherwise the picture
     stops short of the glass on a notched phone in landscape. */
  .story-lead {
    margin-left: calc(-1 * (var(--gutter) + var(--safe-left)));
    margin-right: calc(-1 * (var(--gutter) + var(--safe-right)));
    width: auto;
  }

  /* The story first. On the desktop the thread index is a 260px rail down the
     left of the page, read at a glance and never in the way. Stacked on a
     phone it was simply first: DOM order is nav, story, rail, nothing set an
     `order`, and on a 390x844 screen the h1 landed at y=279 and the first
     paragraph of the story at y=848, four pixels below the fold. A reader
     who tapped a headline got a table of contents and had to scroll to find
     out whether they had arrived.

     The index keeps its place in the document, which is what a screen reader
     and a keyboard read, and moves below the story it indexes. On a phone
     nobody jumps between updates from a list anyway, the thread is directly
     under the thumb and scrolling it is the same gesture, so it belongs
     with the rail's service furniture rather than in front of the news. */
  .story-grid > .story-main { order: 0; }
  .story-grid > .thread-nav { order: 1; }
  .story-grid > .story-rail { order: 2; }

  /* 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: 11px; }
  /* 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 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; }

  /* 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; }

  /* 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 16px; }
  .thread-head { padding: 18px 16px 14px; }
  .thread-head h3 { font-size: 16px; }
  .thread-head .meta { font-size: 11px; }
  .thread-node { padding-left: 24px; padding-bottom: 18px; }
  .thread-date, .thread-role { font-size: 12px; }
  .thread-headline { font-size: 14px; }

  .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-wide {
    min-height: 44px; padding-left: 16px; padding-right: 16px;
  }
  /* The fields, which this block listed the buttons of and then forgot. The
     search page's own field is the phone's only way to search (the
     masthead's is display:none at this width) and it was standing at
     --control-h, which is 38px: six pixels under the floor every button
     beside it clears, on the one control a reader has to hit before they can
     type. The area search is the same case one sheet along. 16px type inside
     them is settled further up and stays; this is the box around it. */
  .searchbox, .area-search { min-height: 44px; height: auto; }
  .format-row { gap: 8px; }
  .filter-chip { min-height: 44px; }
  .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: calc(.12em * var(--track)); 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 11.5px var(--sans); color: var(--ink-45); letter-spacing: calc(.04em * var(--track));
  padding: 0 16px; text-align: center;
}

/* The unsold slot, drawn as inventory rather than as an advertisement.

   This has been two other things. It drew "ADVERTISEMENT, 300x250 MINIMUM"
   in a grey box, the rhythm was right and the words were a rate card quoted
   to a reader who came for the news. Then it drew nothing, which stopped
   quoting the rate card and gave up the rhythm with it, and left the reference
   and `pt24_assets.pt24_ad` both describing a box that was no longer there.

   A slot with nothing booked in it is neither of those. It is a piece of this
   paper that is for sale, and saying so is the one thing that is true, useful
   to the one reader in a thousand who wants it, and harmless to the rest. So
   the box comes back with the paper's own invitation in it.

   It is an `.ad-unit`, not a class of its own. Every rule that gives the rail
   its rhythm (the last slot growing into the leftover height, sticking as the
   reader goes past, holding its declared size once the rail stacks) is
   written against `.ad-unit:last-child` up at the rail block, and reusing the
   class means the empty state inherits all of it and none of those selectors
   needs a twin. `.is-available` only changes what is inside the box. */
.ad-house {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; width: 100%; padding: 20px 18px; text-align: center;
  background: var(--ad-wash); border: 1px dashed var(--rule-strong);
}
/* Dashed, and it is the only dashed border in the stylesheet. The sold slot's
   `.ad-link` is a solid 1px rule; this is the same box in the same ground with
   the one difference that says "nothing here yet" without a word being spent
   on it. A reader who never reads the label still reads the edge. */

.ad-house-label {
  font: 700 10px var(--sans); letter-spacing: calc(.14em * var(--track));
  color: var(--ink-45); text-transform: uppercase;
}
.ad-house-note {
  font: 400 12.5px/1.5 var(--sans); color: var(--ink-45); max-width: 30ch; margin: 0;
}
.ad-house-cta {
  font: 600 12.5px var(--ui); color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--ink); padding-bottom: 2px;
}
.ad-house-cta:hover { color: var(--red-type); border-bottom-color: var(--red-type); }
/* The invitation with no address behind it. Same words, no underline and no
   hover, because it is a `<span>`; see `includes/ad.html`, and the footer's
   `.footer-nolink`, which is the same instrument for the same reason. */
.ad-house-cta.is-plain {
  color: var(--ink-45); border-bottom: 0; padding-bottom: 0; font-weight: 500;
}

/* The 300x600 slot is twice the height of the 300x250 and the copy inside it
   does not grow to match, so it centres in a tall box with a lot of air. That
   is the slot's shape rather than this state's, and a sold creative fills it
   exactly the same way. Nothing to correct, but the note wraps at 30ch above
   so the block stays a paragraph rather than one long line. */

/* Under 1120px the rail stops being a column beside the story and stacks under
   it at the full width of the page. A 300x250 ratio on an 880px-wide box is
   733px tall, and for this state that is 733px of dashed cream with three lines
   in the middle of it, so the ratio is dropped and the box becomes a band.

   `!important` because the ratio arrives as an inline `style` on the element,
   written by `includes/ad.html` from the slot's own dimensions, and an inline
   declaration cannot be overridden from the stylesheet any other way. It is the
   only `!important` here and it is answering an inline style, not another rule.

   The sold state deliberately keeps its ratio at this width: a creative is an
   image and fills the box it is given. Text does not. */
@media (max-width: 1120px) {
  .ad-house { aspect-ratio: auto !important; min-height: 180px; }
}

/* ------------------------------------------------------------------ */
/* 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(var(--ink-rgb), .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; }
}

/* The scroll lock, which did neither of its two jobs.

   `overflow: hidden` on `<body>` does not stop touch scrolling on iOS Safari,
   so the front page scrolled behind every open sheet, the edition picker, the
   area picker, the sections sheet, the first-run takeover.

   And it broke something else on the way past. The rule on `body` further up
   this file spends eight lines arguing "`clip`, never `hidden`: hidden makes
   body a scroll container and silently kills `position: sticky` on the top bar
   and the runner." `.pwa-locked` is a class selector and beats that element
   selector, and it set the shorthand on both axes, so for as long as any
   dialog was open, the sticky top bar stopped being sticky.

   `position: fixed` is the pattern that actually holds on iOS. It costs a
   scroll-position round trip, which `pt24.js` now does on lock and unlock. */
.pwa-locked {
  position: fixed; left: 0; right: 0; width: 100%;
  overflow-y: hidden;
}

.pwa-scrim {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(var(--ink-rgb), .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 11.5px 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); }

/* The measurement question ------------------------------------------ */
/* Sits where the offline banner sits, above the tab bar and clear of the
   safe area, because both are the same kind of thing: a strip the page puts
   at the reader's foot that is not the page. It is paper rather than the
   offline banner's dark inset, because this one is asking rather than
   telling, and a question set in the furniture of a warning reads as a
   warning.

   No `position: sticky` and no scroll trap. The story behind it scrolls
   normally and the bar covers about 90px of its foot, which is the cost of
   asking; it is gone the moment either answer is pressed, and it is never
   drawn again for that reader. */
.pwa-measure {
  position: fixed; left: 0; right: 0;
  bottom: calc(var(--tabbar-h, 0px) + env(safe-area-inset-bottom, 0px));
  background: var(--paper); border-top: 1px solid var(--ink);
  padding: 14px calc(var(--gutter) + env(safe-area-inset-right, 0px))
           14px calc(var(--gutter) + env(safe-area-inset-left, 0px));
  /* Above the WhatsApp button at 60, and this is not a preference about which
     of the two matters more. The button is fixed at the bottom right, which is
     exactly where the second of the two answers sits, and on a 1000px screen
     it covered "No, thank you" completely. A consent whose refusal is behind
     another control is not a consent.

     Paint order and not `display: none`. A rule that hid the button while the
     bar was up would be the only rule matching that class, which is the
     definition `tests/test_nothing_is_rendered_to_be_hidden.py` uses for
     markup rendered in order to be deleted, and it would be right: the bar is
     full-width and taller than the button's band at every width, so it
     already covers it completely. Nothing to hide.

     Still below the scrim at 70, so an open sheet dims this with everything
     else rather than leaving a lit bar on a darkened page. */
  z-index: 61;
}
.pwa-measure[hidden] { display: none; }
.pwa-measure-inner {
  max-width: var(--max); margin: 0 auto;
  display: flex; align-items: center; gap: 18px;
}
.pwa-measure .txt {
  margin: 0; font: 400 13px/1.5 var(--sans); color: var(--ink-70); max-width: 68ch;
}
.pwa-measure .txt a {
  color: var(--ink); text-decoration: underline; text-underline-offset: 2px;
  white-space: nowrap;
}
/* `flex: none` so the pair keeps its width and the sentence wraps instead:
   the two answers must not end up at different widths on a long language. */
.pwa-measure-acts { display: flex; gap: 10px; flex: none; margin-left: auto; }
.pwa-measure-acts .btn-line { min-width: 132px; }
@media (max-width: 720px) {
  /* One column, the sentence over the pair, and the pair splits the width so
     neither answer is the bigger target. */
  .pwa-measure-inner { flex-direction: column; align-items: stretch; gap: 12px; }
  .pwa-measure-acts { margin-left: 0; }
  .pwa-measure-acts .btn-line { flex: 1 1 0; min-width: 0; }
}

/* 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 11px var(--sans); letter-spacing: calc(.04em * var(--track)); 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(var(--ink-rgb), .22);
  transform: translateY(100%); transition: transform .22s ease;
  max-height: 88dvh; overflow-y: auto; overscroll-behavior: contain;
}
.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 18px/1.2 var(--sans); margin: 0; letter-spacing: calc(-.005em * var(--track)); }
.pwa-sheet-flag {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  background: var(--red); color: var(--knockout); font: 600 10px var(--sans); letter-spacing: calc(.1em * var(--track));
  padding: 4px 7px; border-radius: 2px;
}
.pwa-sheet-flag .dot { width: 5px; height: 5px; background: var(--knockout); }

/* 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: calc(.08em * var(--track)); 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 13px/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 13px/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 11px/1.5 var(--sans); color: var(--ink-30); margin: 8px 0 0; }
.pwa-label {
  font: 700 10.5px var(--sans); letter-spacing: calc(.08em * var(--track)); 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: calc(.04em * var(--track)); }

/* 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 h2 { font: 600 24px/1.18 var(--sans); margin: 0 0 12px; letter-spacing: calc(-.005em * var(--track)); }
.pwa-firstrun .lede { font: 400 13.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: 44px; }

/* 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 14px 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 11px 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: 13px;
}
.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; }
  .pwa-measure { bottom: 0; }
}

/* ================================================================== */
/* Screen 04, You. The only settings screen.                           */
/*                                                                     */
/* One shape all the way down: a 10.5px label, a 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);
}
/* An h1 now, so the UA margin has to come off it; the row sets its own.  */
.you-head .ttl { font: 700 12px var(--sans); letter-spacing: calc(.09em * var(--track)); margin: 0; }
.you-head .since { margin-left: auto; font: 500 10.5px var(--sans); color: var(--ink-30); }

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

.you-card { border: 1px solid var(--rule); background: var(--card); }
.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 12.5px var(--sans); }
.you-row .sub {
  display: block; font: 500 10.5px/1.45 var(--sans); color: var(--ink-30); margin-top: 3px;
}
a.txt:hover .nm { color: var(--red-type); }

.you-change {
  flex: none; background: none; border: none; cursor: pointer;
  font: 600 11px var(--sans); color: var(--red-type);
  /* The row is 56px; the target inside it has to be 44px. */
  min-height: 44px; padding: 0 4px;
}
.you-chev { flex: none; font-size: 11.5px; color: var(--ink-30); }
.you-fixed {
  flex: none; font: 600 11.5px 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 11px 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: var(--card); transition: transform .16s ease;
}
.you-toggle.is-on { background: var(--ink); }
/* The knob does not change colour when it moves. It was --card off and --paper
   on, which is the switch quietly repainting itself mid-travel for no reason a
   reader could name; the two grounds it crosses are --rule-strong and --ink and
   the card white reads on both. Only the position is the state. */
.you-toggle.is-on .knob { transform: translateX(18px); }
/* Disabled is a flatter ground and a knob the colour of the page, never a
   lowered opacity: the package's states card rules opacity out, and a
   half-transparent switch over a card border draws the border through it. */
.you-toggle:disabled { background: var(--rule-strong); cursor: default; }
.you-toggle:disabled .knob { background: var(--wash); }
/* The target is 44px tall; the switch drawn inside it is still 46 x 28.

   The switch *is* the button: `includes/you_toggle.html` is one `<button>`
   with a knob in it, and the 56px row around it is not a click target, so its
   28px height was the whole hit area, on the one screen in the paper that is
   nothing but switches. Every other control clears the 44px floor the phone
   block sets at `.btn-ink, .btn-line, ...`, and `.you-change` two rules above
   this one states the reasoning out loud: "The row is 56px; the target inside
   it has to be 44px."
   
   A pseudo-element rather than a taller box, because the switch art is 28px and
   growing the element would either stretch the pill or need the knob re-centred
   against a height it no longer has. The overlay is centred on the switch, is
   the full width of it, and takes the pointer, so the art is unchanged and the
   thumb gets its 44px. */
.you-toggle::after {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; height: 44px; transform: translateY(-50%);
}
@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: 44px; }

.you-note { font: 400 11px/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 10.5px var(--sans); letter-spacing: calc(.09em * var(--track)); color: var(--ink-30); margin-bottom: 7px;
}
.you-empty p { font: 400 12px/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-type); }
.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-type); 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-type); }
/* 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: calc(.1em * var(--track)); 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 12.5px var(--sans); }
.you-slider-head .sub { margin-left: auto; font: 500 10.5px var(--sans); color: var(--ink-30); }

.you-slider { display: flex; align-items: center; gap: 12px; }
.you-slider .a-small { font: 600 12px var(--sans); color: var(--ink-30); }
.you-slider .a-large { font: 600 18px 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: var(--card); border: 1px solid var(--rule-strong);
  box-shadow: 0 2px 6px -2px rgba(var(--ink-rgb), .35);
}
.you-slider input[type="range"]::-moz-range-thumb {
  width: 26px; height: 26px; box-sizing: border-box;
  border-radius: 50%; background: var(--card); border: 1px solid var(--rule-strong);
  box-shadow: 0 2px 6px -2px rgba(var(--ink-rgb), .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; }

/* ------------------------------------------------------------------ */
/* The reading-size restatement, corrected.                            */
/*                                                                     */
/* The foot of pt24.css restates four reading sizes so `--read` (the    */
/* reader's own 100/120/140% setting) can multiply them. It is a        */
/* restatement and nothing more: the size itself is decided up in the   */
/* type block, and the comment above `.update-body` says so in as many  */
/* words.                                                              */
/*                                                                     */
/* The redesign moved the type block down a step and did not move the   */
/* restatement with it, and because the restatement is the later rule   */
/* it won: every piece of *reading* type in the paper (the standfirst,  */
/* the story body, the room for disagreement, a fact-check claim) was   */
/* still being drawn at its pre-redesign size while every headline      */
/* around it had come down. That is not a small drift. A 20px           */
/* standfirst under a 38px lead is nearly the same size as the headline */
/* it is supposed to be subordinate to.                                 */
/*                                                                     */
/* It was six rules until the river dek went. `.entry-standfirst` and   */
/* `.entry-standfirst.lead` were the spine river's own dek, and the     */
/* spine has been the mosaic since the redesign, no template has        */
/* printed either class on either branch for some time, so both are     */
/* gone from the type block and from here with them.                    */
/*                                                                     */
/* So: the same four rules, restating the numbers the type block now    */
/* holds. Nothing here is a new decision. If a size below disagrees     */
/* with the type block again, the type block is right and this is       */
/* stale; that is the whole contract of a restatement.                  */
/* ------------------------------------------------------------------ */
.standfirst { font-size: calc(20px * 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. The phone block sets a size on `.factcheck .claim` and
   that has never reached the text (the child carries its own font-size), so
   the phone case is restated here too rather than left looking handled. */
.claim .text { font-size: calc(16px * var(--read)); }

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


/* ------------------------------------------------------------------ */
/* Front page: the lead's brief, the day's overflow, the marks        */
/* ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ */
/* THE BRIEF, re-set for the column it now lives in                     */
/*                                                                      */
/* The brief was drawn for the rail: a 20% column, a shade over 300px    */
/* at most desktop widths, and 15px was the size that gave it a          */
/* readable measure in it. It is not in the rail any more, the rail      */
/* holds what is *not* this story now, and the brief is nothing but      */
/* this story, so it opens `.hero-side`, the column beside the lead's    */
/* picture. That column is two fifths of the main column, comfortably    */
/* past 450px on a wide screen, and 15px there sets 70-odd characters    */
/* to the line: small type on a long measure, which is the one           */
/* combination the reading column is meant never to draw.                */
/*                                                                      */
/* 16px sits one point under the standfirst above it, and that is        */
/* deliberate: the brief is the story's own prose, the same register as  */
/* the standfirst and a step down in colour rather than in size. It was  */
/* written here as "the size the standfirst takes past 1280px", which    */
/* was true of a 1280px block that has since gone, the standfirst is     */
/* 17px at every desktop width now and the single point between them is  */
/* the whole of the step. The measure is capped so the line does not run */
/* out to the full width of the column on a wide screen, and the leading */
/* opens a little to carry three or four sentences of it.                */
/* ------------------------------------------------------------------ */
/* No bottom margin: `.hero-side` is a flex column with its own 16px gap,
   and the 18px this carried from the rail simply added to it. `flex: none`
   for the same reason the format row has it, the timeline card below is
   the one thing in this column allowed to absorb the leftover height. */
.hero-brief { margin: 0; flex: none; }
.hero-brief p {
  font: 400 16px/1.62 var(--sans); color: var(--ink-70);
  margin: 0; max-width: 62ch; text-wrap: pretty;
}
/* The credit line under a wire headline. It is not decoration and it is not
   the desk: it is the newsroom that did the reporting, and it is the whole
   basis on which the headline above it may be republished here at all, so
   it prints on every row rather than being collapsed into a heading over
   the block. Sized under the meta row it sits beside, in the same grey,
   because it is provenance and not a second standfirst. */
.brief-tile .brief-credit { display: block; margin-top: 6px; font: 400 12px var(--sans); color: var(--meta); }

/* The note over the block, which says the one thing the headlines cannot:
   that following one of them leaves this paper. Set as the section head's
   own aside rather than as a paragraph of its own, so it reads as part of
   the label instead of as the first line of the copy under it. */
.brief-head .note { font: 400 13px var(--sans); color: var(--meta); }


/* A Nukkad set whose cards have not published yet prints the brand and the
   blurb and nothing else, and `.nukkad-brand` is a fixed 200px column with
   the mark stacked over its blurb, which, alone in a full-bleed black band,
   is a logo in the top left corner of a very wide empty rectangle.
   On its own it lies down and runs the row instead, the same shape it
   already takes on a phone. Selector, not a class, because the state is
   "there is nothing beside me" and the DOM already says that. */
.nukkad-inner > .nukkad-brand:only-child {
  width: auto; flex-direction: row; align-items: center; gap: 18px;
}

/* ------------------------------------------------------------------ */
/* LIVE is red. DEVELOPING is not.                                      */
/*                                                                      */
/* One plate served both, and on /live/, where nearly every tile        */
/* carries one of the two, that put red on the whole page and so on     */
/* nothing. Red is a state in this system, spent on the one item in a   */
/* set that is happening now. A story that is still moving is not that. */
/* Ink is what everything which is not a state takes, and the flag      */
/* keeps its shape, its size and its knockout type, so the two read as  */
/* the same object in two conditions rather than as two objects.        */
/* The pulse goes with the colour, the throb is the mark of a feed      */
/* that is open, and the markup only asks for it when the story is      */
/* live, so there is no animation here to switch off. */
.day-flag.is-developing { background: var(--ink); }

/* ------------------------------------------------------------------ */
/* The network's marks, at the size they were drawn                     */
/*                                                                      */
/* All three of these slots printed a 139px-wide lockup, artwork cut    */
/* at favicon scale, with no detail in it to recover, and the Nukkad    */
/* band was rebuilt precisely to stop doing that. They print the        */
/* 1408px cut now, light-ground, because all three sit on cream or      */
/* white.                                                               */
/*                                                                      */
/* The heights are not the old numbers. The new canvas carries a good   */
/* deal more air around the mark: 65% of its height against the old     */
/* art's 81%, so at an unchanged box the mark would print a fifth       */
/* smaller than it does today. These are the heights at which it draws  */
/* exactly the size it drew before, which is the conservative half of   */
/* the fix: the resolution changes and the page's rhythm does not.      */
/*                                                                      */
/* And they are here rather than on the `img`. How big a mark prints is */
/* a design decision, and this system keeps those in the stylesheet.    */
/* `.rail-card.is-daily`, `.rail-head-logo` and `.sawaal-head` are each */
/* drawn by exactly one template, so none of them needs scoping to say  */
/* which page it means. */
.rail-card.is-daily .pt24-logo { height: 38px; width: auto; }
.rail-head-logo .pt24-logo { height: 43px; width: auto; }
.sawaal-head .pt24-logo { height: 45px; width: auto; }

/* The picture's credit, which had no rule at all and so inherited the meta
   row whole, the same size, weight and grey as "4 min read", reading as one
   more fact about the story when it is not a fact about the story at all. It
   is who owns the photograph above it.

   Set below the line it sits in, which is what this system already does with
   a credit in both the places it draws one: the wire credit under an overflow
   headline holds at 12px and is written down as never going lower, and the
   story page sets the picture's credit inside its 11.5px caption. Twelve is
   the floor of the two and the safe one here, because this credit shares a
   row rather than owning a line.

   Not 14px. DESIGN-GUIDELINES gives provenance 14px, and that governs the
   whole meta row, which this branch draws at 13. Lifting the credit alone to
   14 would make the photographer's name the largest thing in the row and the
   read time the smallest, which is the hierarchy upside down; the row's own
   size is the thing that is a point light, and that is pt24.css's to settle.

   `white-space: normal` because `.day-meta > *` forbids wrapping, which is
   right for "Sources attached" and wrong for the one item in the row that can
   be any length at all: a credit reading "Photograph: Reuters/Amit Dave" in a
   360px mosaic column has to be allowed to take two lines rather than run out
   past the tile it belongs to. */
.day-meta .credit { font-size: 12px; color: var(--meta); white-space: normal; }

/* The other credit in this row, and the one that names a newsroom rather than
   a photographer: "Also reported by 2 other papers" on a cluster's lead, and
   "Compiled by the PT24 desk from 3 papers" where the desk merged the cluster
   into one written-through story.

   The same three declarations as `.credit` above, for the same three reasons,
   twelve is the floor for a credit sharing a row, `--meta` is the row's own
   ink, and `white-space: normal` because `.day-meta > *` forbids wrapping and
   this is the other item in the row that can be any length. A count keeps it
   short, but Kannada sets the same sentence half again as wide as English and
   a 360px mosaic column has to be allowed to take two lines for it.

   Italic is the one thing it does not share, and it is not decoration: the
   story page sets `.compiled-from` italic under the byline, and this is the
   same sentence about the same story met one screen earlier. A reader who
   taps through should recognise it. */
.day-meta .compiled { font-size: 12px; font-style: italic; color: var(--meta); white-space: normal; }

/* ------------------------------------------------------------------ */
/* Story, rivers and the shared furniture                             */
/* ------------------------------------------------------------------ */

/* ------------------------------------------------------------------ */
/* The river pages name themselves                                      */
/*                                                                      */
/* Section, All, Search and My Threads are the front page's river with   */
/* something taken out of it or something added; that is the whole       */
/* argument for reusing the mosaic on all four, so the thing at the top  */
/* of them is the same rubric the front page prints over "THE DAY SO     */
/* FAR", not a headline. It had been forced to 15px inline on every one  */
/* of the four, which was a 2px shout against a 13px system before the   */
/* redesign and is a 2px shout against a smaller one now.                */
/*                                                                      */
/* It is an h1 here rather than an h2 because these four pages had no h1 */
/* at all: the rubric *is* the page's name, and a document that never    */
/* says its own name reads as a fragment of a larger one to anything     */
/* that walks the outline. Same rule, same size, same caps; the change   */
/* is what the element means, not what it looks like.                    */
/* ------------------------------------------------------------------ */
/* Scoped to `.river` on purpose. The verticals put an `h1` inside a
   `.section-head` too, but theirs is a lockup, art with the desk's own
   wordmark in it, and a rule that reached across and set a font on it would be
   this page's opinion about a page it knows nothing about. */
.river .section-head h1 {
  font: 700 13px var(--sans); letter-spacing: calc(.1em * var(--track)); margin: 0;
  /* A search for eighty characters is a legal query and sets to two or three
     lines in a flex row. Without this the rubric refuses to wrap, takes its
     max-content width and pushes the hairline and the edition note off the
     right-hand edge of the page. */
  min-width: 0; overflow-wrap: anywhere;
}

/* The river pages' own frame. `.local` and `.front` each name their page this
   way; the four that share one river share one class rather than repeating the
   same two paddings inline four times. */
/* Vertical only, in longhand. The `padding` shorthand also sets the
   horizontal sides, and .river is declared after .shell, same
   specificity, later wins, so the shorthand silently cancelled
   `.shell`'s `0 var(--gutter)` and every river page drew its mosaic
   flush to both viewport edges while the masthead and nav above it
   stayed inside the gutter. `.front` next door already uses the
   longhand, which is why the front page never showed it. */
.river { padding-top: 30px; padding-bottom: 64px; }
.river > .section-head { margin-bottom: 22px; }
/* An intro is a section's standfirst, so it is a standfirst: the measure is
   the only thing that is this page's rather than the system's. */
.river > .standfirst { max-width: 62ch; margin-bottom: 22px; }
/* Search returns three sets (stories, threads, documents) and each of them
   opens with the same rubric the page did, so a second head on the page is a
   division of it rather than the start of a new one. */
.river > .section-head:not(:first-child) { margin-top: 34px; }
/* House ads sit centred under the river, in the column the river ran down. */
.river-ad { margin-top: 34px; display: flex; justify-content: center; }

@media (max-width: 820px) {
  .river .section-head h1 { font-size: 12px; }
  /* Longhand here for the same reason as above, and it was not, this rule
     was `padding: 20px 0 40px`, the exact shorthand the comment on the desktop
     rule warns about, cancelling `.shell`'s phone gutter on every river page.
     The desktop rule was fixed; this override reintroduced it verbatim.

     Measured at 390px before the fix: `MAIN.shell` computed to zero horizontal
     padding and headlines spanned the full 393px on `/all/` (26 text nodes
     touching both edges), `/section/<slug>/` (11), `/search/` (5) and
     `/threads/` (4), including the whole empty state. `/` and `/story/` were
     unaffected because they are `.front` and `.shell-story`.

     Worse on a notched phone in landscape, where the gutter it cancels is
     `calc(var(--gutter) + var(--safe-left))`, so the copy ran under the
     notch rather than merely to the edge. */
  .river { padding-top: 20px; padding-bottom: 40px; }
}


/* ------------------------------------------------------------------ */
/* The story page again, at the foot of the cascade                     */
/* ------------------------------------------------------------------ */

/* Most filings have no thread. The story grid opens with a 260px column for
   the thread navigation and rules it off from the story with a hairline, and on
   a piece of wire copy that column was drawing empty under the heading "THE
   THREAD, 0 UPDATES", a fifth of the page spent saying that there is nothing
   to say, next to a rule that separates the nothing from the story.
   `story.html` stops printing the column at all in that case, and the grid
   closes over the gap rather than leaving a hole where it used to be. */
.story-grid.is-unthreaded { grid-template-columns: minmax(0, 1fr) var(--rail-wide); }
@media (max-width: 1120px) {
  .story-grid.is-unthreaded { grid-template-columns: minmax(0, 1fr); }
}
/* With no column to the left of it, the story's own column has no rule to sit
   away from, so the 28px that held it off one goes. */
.story-grid.is-unthreaded > .story-main { padding-left: 0; }

/* The spine node, drawn from what the update *is*.
 *
 * It used to be drawn from `update.node`, a hex the API computed and sent,
 * which broke the first rule in the guidelines twice over. The CMS does not
 * supply presentation, and the presentation it was supplying was stale: the
 * map in `api/formatting.py` held #E8232A and #B8862B, the red and the gold as
 * they were drawn *before* the redesign cooled the one and warmed the other.
 * Every prime alert and every fact-check on a story page printed its node in
 * last season's accent beside this season's everything else, and nothing about
 * the page said why the two reds did not match.
 *
 * That map holds `plain` / `urgent` / `checked` now, the vocabulary these
 * three rules were already written in, so the state crosses the API and the
 * colour stays here. `update_kind_value` is the stable key the template
 * branches on everywhere else, so the node comes off the same fact the badge
 * does. */
.spine-node { background: var(--ink); }
/* Red is the state, not the decoration: an alert and a reader's own question
   are the two kinds of update that are asking for attention rather than
   reporting. */
.spine-node.is-urgent { background: var(--red); }
/* Gold is the fact-check's colour everywhere else on this page (the left rule
   on the block, the desk label, the CONTEXT verdict), so the node takes it too
   and the eye can find the checks by running down the spine. */
.spine-node.is-checked { background: var(--gold); }

/* Furniture the story page used to declare inline, one element at a time.
   None of it is data: an avatar always fills its own circle, the live mark
   in the player well is always the same size at the same weight, so none of
   it belongs in the markup. What
   stays inline on this page is the three values that genuinely vary per story:
   the picture desk's crop, a chapter's share of the scrubber, and how many
   rows of the spine the connector has to run down. */
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.player-well .pt24-logo { height: 34px; width: auto; opacity: .9; }
.spine-entry .media { margin-bottom: 9px; }
/* The spine is a three-column grid, so the pager wants an empty cell in the
   time column to land beside the headlines rather than under the clock. */
.spine-gap { grid-column: 1; }
.spine-more { grid-column: 3; padding-bottom: 20px; }
.corrections > .log { flex: 1; }

/* The story's own body reads to a narrower measure than a live update does.
   An update is a paragraph or two under a timestamp and 88ch is comfortable
   for it; the body underneath the lead picture is the thing a reader spends
   minutes in, and 62ch is where a line stops needing the eye to travel back
   across the column to find the next one. */
.update-body.is-story { max-width: 62ch; margin-bottom: 26px; }
/* The standfirst holds the same measure as the body it introduces rather than
   the 80ch the front page's column can carry. */
.story-main > .standfirst { max-width: 62ch; }
/* The formats sit in the reading column here, not in a rail, so they need the
   air a paragraph would have had after them. */
.story-main > .format-row { margin-bottom: 24px; }

/* A rail card's action fills the card. There is only ever one, and a button
   that stops short of the rule it is drawn inside reads as an accident. */
.rail-card > .btn-ink { width: 100%; margin-bottom: 7px; }
/* The thread column's catch-up button, set off from the started-on block above
   it by the same step the block itself uses. */
.thread-nav .btn-wide { margin-top: 20px; }

/* The catch-up card's small print. It was three inline `font:` declarations in
   the rail, each restating a size the system already holds a name for. */
.rail-note { font: 400 13px/1.55 var(--sans); color: var(--ink-70); margin: 0 0 12px; }
/* Who summarised this, and who checked it. Provenance, so --meta, which is the
   grey the rest of the paper's provenance is set in. */
.rail-fine { font: 400 11px/1.45 var(--sans); color: var(--meta); }

/* A rail row that is a title with a count under it: a related thread on the
   story page, a thread in the search results. `.follow-item` is the same two
   facts on one line and is the model for the type; this is the stacked cut of
   it, for a title long enough that a count cannot share its line. */
.rail-item { display: block; }
.rail-item .title { display: block; font: 500 14px/1.35 var(--sans); }
.rail-item .count { display: block; font: 400 12px var(--sans); color: var(--meta); margin-top: 3px; }
.rail-item:hover .title { color: var(--red-type); }

/* The two-minute catch-up is the thread's own voice rather than a dissent, so
   it takes the disagreement block's geometry and the ink rule instead of the
   gold one. Gold means fact-check and room for disagreement in this system and
   spending it on a summary would make three golds on one page mean three
   different things. */
.disagreement.is-catchup { border-left-color: var(--ink); margin-bottom: 26px; }
.disagreement.is-catchup .label { color: var(--ink-30); }


/* ------------------------------------------------------------------ */
/* The streamfield: the blocks a story body is actually made of         */
/*                                                                      */
/* `includes/streamfield.html` has always emitted `.body-heading`,      */
/* `.pull-quote` and `.embed`, and pt24.css has never had a rule for any */
/* of the three. They were drawing at browser defaults: a cross-head in  */
/* Times at 1.5em, a pull quote indented 40px with no other mark on it,  */
/* an embed with no bound of any kind. Three of the six block types an   */
/* editor can reach for were undesigned, on the one screen where a       */
/* reader spends minutes.                                               */
/* ------------------------------------------------------------------ */

/* A cross-head names a passage of the story, so it is a headline and takes the
   serif. Sized between the body it interrupts and `h3.entry`, the smallest
   headline on the page, so it reads as a division of this story rather than as
   the start of another one. The space above it is most of the work: a
   cross-head with equal air on both sides belongs to neither passage. */
.body-heading {
  font: 600 20px/1.25 var(--serif); letter-spacing: calc(-.003em * var(--track));
  margin: 30px 0 10px;
}
.body-heading:first-child { margin-top: 0; }

/* A pull quote is the story's own words re-set, so: the news voice, at the
   size the river gives a lead headline, behind the same 3px left rule the room
   for disagreement uses, in ink, because gold is spoken for. No quotation
   marks drawn in CSS; the copy carries whatever punctuation it was written
   with, and a curly quote from the stylesheet lands in the wrong place in
   Devanagari and Kannada both. */
.pull-quote {
  border-left: 3px solid var(--ink); padding: 0 0 0 21px;
  margin: 26px 0; max-width: 62ch;
  font: 500 24px/1.3 var(--serif);
}
.pull-quote p { margin: 0 0 10px; }
.pull-quote p:last-of-type { margin-bottom: 0; }
.pull-quote footer {
  margin-top: 10px; font: 400 13px var(--sans); color: var(--meta);
}

/* An embed is somebody else's markup, and the only useful thing to say about
   it is where it ends. Bounded, ruled, and made to stop at the column: a
   provider iframe with a hardcoded width used to run out past the story into
   the rail. */
.embed {
  margin: 22px 0; max-width: 100%;
  background: var(--slot); border: 1px solid var(--rule);
}
.embed > * { max-width: 100%; }
.embed iframe, .embed video, .embed object {
  display: block; width: 100%; aspect-ratio: 16/9; border: 0;
}
.embed blockquote { margin: 0; padding: 16px; }
/* Audio is not a 16:9 box. It is a transport, it is about 54px tall, and giving
   it the video ratio drew a strip of controls floating in the middle of a
   letterbox. */
.embed audio { display: block; width: 100%; }
/* An embed the CMS could not expand, a dead tweet, a video pulled down, a
   provider that stopped answering oEmbed. The template prints a line saying so
   rather than leaving a bordered rectangle of nothing, because a reader can act
   on "this did not load" and cannot act on an empty box. */
.embed-missing {
  display: grid; place-items: center; min-height: 96px; padding: 20px;
  font: 400 13px/1.5 var(--sans); color: var(--meta); text-align: center;
}

/* A document reference inside the body is the same object as one in the rail,
   and it wants the air a paragraph would have had around it. */
.update-body > .doc-link { margin: 0 0 16px; }


/* ------------------------------------------------------------------ */
/* When the newsroom is not answering                                   */
/*                                                                      */
/* The one page that has to render with `chrome` set to None, so it can  */
/* lean on nothing but the shell and the empty state. It gets air        */
/* instead of a page's worth of furniture, because there is no page.     */
/* ------------------------------------------------------------------ */
.api-down { padding: 64px 0; }
/* The one monospaced string in the system, and the exception proves the rule:
   it is a stack trace, it only draws under DEBUG, and no reader ever sees it.
   Anything a reader can see takes one of the three faces. */
.api-down .trace {
  margin-top: 14px; font: 400 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--ink-70); overflow-wrap: anywhere;
}


/* ------------------------------------------------------------------ */
/* My Threads                                                           */
/* ------------------------------------------------------------------ */
/* A followed thread the desk has not filed into yet. `.empty-rail` is the
   system's note-in-a-column, which is what this is; it was a bare `.note`,
   and `.note` on its own has no rule anywhere in the stylesheet; it only ever
   means something inside a `.section-head`. It was drawing at the browser's
   default paragraph size in the middle of a ruled list. */
.thread-block-list > .empty-rail { padding: 9px 0 0; }


/* ------------------------------------------------------------------ */
/* You                                                                  */
/* ------------------------------------------------------------------ */
/* The long wordmark at the foot of the settings screen. It was sized inline;
   it is the same statement the splash makes and it belongs in the stylesheet
   with every other appearance of the marks. */
.you-foot .pt24-logo { height: 24px; width: auto; margin: 0 auto; }


/* ------------------------------------------------------------------ */
/* The phone. The mobile scale in pt24.css covers every headline the    */
/* system had a name for; these three are the ones it could not, since  */
/* none of them had a rule of any kind until now.                       */
/* ------------------------------------------------------------------ */
@media (max-width: 820px) {
  .body-heading { font-size: 17px; margin: 24px 0 8px; }
  .pull-quote { font-size: 20px; line-height: 1.3; padding-left: 14px; margin: 20px 0; max-width: none; }
  /* Every measure cap is a desktop concern; the phone column is the cap. */
  .update-body.is-story, .story-main > .standfirst { max-width: none; }
  /* Two formats will not sit side by side on a 320px screen. */
  .story-main > .format-row { margin-bottom: 18px; }
}


/* ------------------------------------------------------------------ */
/* The network's own products: Live, Nukkad, Sawaal, Local            */
/* ------------------------------------------------------------------ */

/* ==================================================================== */
/* The network's own products                                           */
/* ==================================================================== */

/* Live, Nukkad and Sawaal are mastheads, not sections. A section river names
   itself in 13px letterspaced sans because the name is a label; a product
   names itself with its own mark because the name is a brand. That is the
   whole reason the four lockups exist as art at all, and until now three of
   the four product pages set their own name as type and left the art on the
   front page.

   One head serves all three so the products read as a set. It is deliberately
   the same shape as `.local-head`, mark, a line of prose, a rule to the right
   margin, because a reader arriving from Local should not feel they have
   changed publications, only screens. */
.vertical-masthead {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--space-md);
  padding: var(--space-xl) 0 var(--space-lg);
  border-bottom: 1px solid var(--ink);
  margin-bottom: var(--space-lg);
}

/* 64px, not the 38px these pages were printing. The lockup art is 1408px
   across; at 38px high it renders 74px wide, which is a favicon of a mark
   drawn to be read, the same failure the front-page band already has a
   comment about. Anything under about 48px loses the fist inside the T, which
   is the one part of the mark that is not type. `width: auto` and `flex: none`
   keep the ratio: this sits in a flex row, and a stretched replaced element
   fills its line box at whatever height it was given. */
.vertical-masthead .pt24-logo { height: 64px; width: auto; flex: none; }

/* The mark *is* the heading on an index page, so it carries the h1 rather
   than sitting beside an invisible one. line-height:0 keeps the h1 box off
   the image's own baseline gap. */
h1.vertical-lockup { margin: 0; line-height: 0; flex: none; }

.vertical-masthead .vertical-title { min-width: 0; }
h1.vertical {
  font: 500 36px/1.15 var(--serif); letter-spacing: calc(-.02em * var(--track));
  margin: 0 0 var(--space-2xs);
}
.vertical-masthead .blurb {
  font: 400 15px/1.5 var(--sans); color: var(--ink-70);
  margin: 0; max-width: 56ch;
}
.vertical-masthead .note { font: 400 13px var(--sans); color: var(--meta); }
.vertical-masthead .fill {
  flex: 1; min-width: var(--space-lg); height: 1px; background: var(--rule-strong);
}

/* 48px is the scale's top step. The pages were each carrying their own
   inline 56 or 64, three numbers for one job. */
.vertical-screen { padding-bottom: var(--space-2xl); }

@media (max-width: 820px) {
  .vertical-masthead {
    padding: var(--space-lg) 0 var(--space-md);
    gap: var(--space-sm);
  }
  .vertical-masthead .pt24-logo { height: 44px; }
  h1.vertical { font-size: 24px; }
  .vertical-masthead .blurb { font-size: 13.5px; }
}

/* ==================================================================== */
/* PT24 Live                                                            */
/* ==================================================================== */

/* `.live-head` was drawn for exactly this row and then never used by any
   template, the page was rendering a plain `.section-head` with the product
   name set as 15px type. It is back, as a modifier on `.section-head` rather
   than instead of it, because only `.section-head` carries the phone rules
   that let this row wrap without the divider collapsing.
   The one thing it brings that `.section-head` does not is `.auto`, a quiet
   label with a red dot beside it, and that dot is the entire red budget of
   this screen. Its divider is decided once, at `.live-head .fill` up with the
   rest of the row; it used to be --rule there and --rule-strong again here,
   which is one hairline drawn in two greys. */
/* 48px, not the 64px an index masthead prints at: this row also carries the
   desk chips, and a 64px mark beside a 13px chip makes the chips look like a
   caption on the logo. 48px of a 1408px-wide source is still nine times the
   pixels it needs. */
.live-head .pt24-logo { height: 48px; width: auto; flex: none; }
.live-head .auto { flex: none; }
@media (max-width: 820px) {
  .live-head .pt24-logo { height: 36px; }
}

/* ==================================================================== */
/* Nukkad                                                               */
/* ==================================================================== */

/* A set is a band, and on the index the page is a stack of them. `.nukkad`
   bleeds to both viewport edges by pulling left through the gutter, so it has
   to be a direct child of `.shell`, which is why these two screens stopped
   nesting it inside `.shell-story` and inside a wrapper div. Vertical rhythm
   between two bands is the only thing left to say. */
.nukkad-page .nukkad + .nukkad { margin-top: var(--space-lg); }

/* The set's own name in the brand column, where the mark sits on the front
   page. The mark is not repeated per set: it has already printed once, at
   64px, at the top of the screen, and a wall of the same lockup down the page
   is how a masthead stops being a masthead. */
.nukkad-brand .set-title {
  font: 500 20px/1.2 var(--serif); color: var(--paper);
  margin: 0 0 var(--space-2xs);
}
.nukkad-brand a:hover .set-title { color: var(--red-type); }
/* The band's phone rule turns the brand column into a row, which is right
   when it holds a mark and a line of blurb and wrong when it holds a
   headline: a 20px serif title beside its own standfirst on a 360px screen
   leaves neither enough width to be read. On this screen it stays a column. */
@media (max-width: 820px) {
  .nukkad-page .nukkad-brand { flex-direction: column; align-items: flex-start; }
  .nukkad-brand .set-title { font-size: 18px; }
}

/* Sixty words is the format, so a card on the set's own page prints its body
   as well as its title. That means a card can no longer be exactly 233px
   tall, but they all still have to agree, so the height comes off and flex's
   own stretch settles it against the tallest card in the row. */
.nukkad-page .nukkad-card { height: auto; min-height: 233px; }
.nukkad-card .body {
  font: 400 12px/1.5 var(--sans); color: var(--knock-62);
  margin-top: var(--space-xs);
}

/* A set with no cards at all. `.nukkad-more` is a flex child sized to fill
   the row; on an empty row it needs a height of its own or the band closes to
   a hairline. */
.nukkad-cards > .nukkad-more:only-child { min-height: 120px; }

/* ==================================================================== */
/* Sawaal                                                               */
/* ==================================================================== */

/* Status is the whole point of this product: silence is published, not
   hidden, so it is the one thing that gets colour, and it takes it from the
   token scale. It used to arrive as a hex in the payload: the pre-redesign red
   and gold, which printed next to a `--red` button as a second, slightly wrong
   red. The API sends the bare status now and there is no `status_colour`
   beside it.

   AWAITING and OVERDUE are the live states and take red, a question the
   government has not answered is the one thing on this screen that is still
   happening. NO REPLY, CLOSED takes gold, which in this system is the colour
   of a claim being weighed: the fact-check rule, the reply block, the
   correction. ANSWERED is finished, so it takes plain ink and no signal.

   The class comes from `{{ sawaal.status }}`, so these have to be the store's
   own values: `awaiting`, `answered`, `refused`, `overdue`. `refused` was
   missing and only `no_reply_closed` was named, which is the Frappe app's word
   for the same state and a name the field never carried: the one case this
   product exists to publish drew no status colour at all. Both keys are named,
   because `api/formatting.py` answers to both for the same reason, neither
   the store nor an older payload should be the one that has to change first.

   OVERDUE is red rather than gold because it is not a refusal, it is a
   deadline that has passed with nobody saying anything. Note that
   `sawaal_status_label` has no branch for it and prints AWAITING REPLY, DAY N,
   so at the moment the colour is the only thing that separates the two; the
   label is the API's to write. */
.sawaal-card .status.status-awaiting,
.sawaal-status.status-awaiting,
.sawaal-card .status.status-overdue,
.sawaal-status.status-overdue { color: var(--red-type); }
.sawaal-card .status.status-answered,
.sawaal-status.status-answered { color: var(--ink); }
.sawaal-card .status.status-refused,
.sawaal-status.status-refused,
.sawaal-card .status.status-no_reply_closed,
.sawaal-status.status-no_reply_closed { color: var(--gold-type); }
/* Ink under it: plain ink would be invisible, so "finished" is said by the
   absence of a signal colour rather than by ink itself. */
.sawaal-board .sawaal-card .status.status-answered { color: var(--paper); }

.sawaal-status { font: 600 11px var(--sans); letter-spacing: calc(.08em * var(--track)); margin-left: auto; }

h1.sawaal {
  font: 500 40px/1.14 var(--serif); letter-spacing: calc(-.02em * var(--track));
  margin: 0 0 var(--space-sm); text-wrap: balance;
}
.sawaal-head-row {
  display: flex; align-items: center; gap: var(--space-sm);
  margin-bottom: var(--space-md); flex-wrap: wrap;
}
.sawaal-head-row .pt24-logo { height: 48px; width: auto; flex: none; }

/* Sign, count, deadline, filing: one row of facts about the question,
   fenced above and below. */
.sawaal-bar {
  display: flex; align-items: center; gap: var(--space-md);
  padding: var(--space-md) 0; flex-wrap: wrap;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  margin-bottom: var(--space-lg);
}

/* The reply, when there is one. Gold left rule, same as the fact-check and
   the correction: this is the paper showing its working. */
.sawaal-answer {
  border-left: 2px solid var(--gold); padding-left: var(--space-md);
  margin-bottom: var(--space-lg);
}
.sawaal-answer .label {
  font: 600 10.5px var(--sans); letter-spacing: calc(.11em * var(--track)); color: var(--gold-type);
  margin-bottom: var(--space-xs);
}

.signature-row { display: flex; justify-content: space-between; gap: var(--space-xs); }
.signature-row .when { font-size: 11px; color: var(--ink-30); }
.signature-list { gap: var(--space-xs); font-size: 12.5px; }

@media (max-width: 820px) {
  h1.sawaal { font-size: 26px; line-height: 1.16; }
  .sawaal-head-row .pt24-logo { height: 38px; }
}

/* ==================================================================== */
/* Local                                                                */
/* ==================================================================== */

.local-rail { display: flex; flex-direction: column; gap: 26px; }
.nearby-list { gap: 10px; }
/* The edition's own lockup, not a product's. It arrives from the CMS as a
   width-900 rendition, so 40px costs it nothing. */
.local-kicker .pt24-logo { height: 40px; width: auto; flex: none; }
/* The whole screen is this one box when the edition has no areas at all, so
   it needs the air a section would have given it. */
.empty.is-standalone { margin: var(--space-2xl) 0; }
.local-standfirst {
  font: 400 14px/1.55 var(--sans); color: var(--ink-70);
  margin: 0; max-width: 56ch;
}
.local-lead-media { margin-bottom: var(--space-sm); }
.local-secondary-media { aspect-ratio: 16/10; margin-bottom: 9px; }
.local-desk { margin-bottom: 26px; }
.local-desk .section-head { margin-bottom: var(--space-md); }
.local-desk .media { margin-bottom: 10px; }

/* The board's mark sits on ink, so it needs the knockout art, and at the
   same size the rest of the sub-brand marks print. */
.sawaal-board-head .pt24-logo { height: 40px; width: auto; flex: none; }
.sawaal-board .empty-knock {
  font: 400 12.5px/1.55 var(--sans); color: var(--knock-55);
}

@media (max-width: 820px) {
  .sawaal-board-head .pt24-logo { height: 32px; }
}

/* The way back out of a set, under the band. Meta type, meta colour: it is a
   navigation aid, not a line of the paper. */
.vertical-back {
  font: 400 12.5px var(--sans); color: var(--ink-30);
  margin: var(--space-lg) 0 0;
}


/* ------------------------------------------------------------------ */
/* Chrome                                                             */
/* ------------------------------------------------------------------ */

/* Empty on purpose, and it is worth saying why rather than leaving the next
   person to wonder whether the pass ran.

   Every class the chrome renders (the masthead row, the account door, the
   Verticals panel and both its grids, the two pickers, the runner, the phone's
   top bar, sheet, service strip and tab bar, the archive bar and the footer)
   already has its rule in pt24.css. The merge did not lose any of them, and
   the chrome's own faults were in the markup and the script, not in the
   drawing: a format axis printed twice, a focus trap asking a question with no
   answer, an empty grid holding a gap open, and a great deal of English that
   had never been through the catalogue. None of that is fixed with a
   stylesheet, so none of it was.

   Concatenating this file into pt24.css therefore adds nothing, which is the
   right outcome: UI's drawing arrived intact and did not need correcting. */


/* ------------------------------------------------------------------ */
/* Two findings from the integration sweep, and only one of them needs */
/* a rule.                                                             */
/* ------------------------------------------------------------------ */

/* `.runner-item` is emitted on every alert and has no rule anywhere. It does
   not need one, and this note is here so the next person to notice does not
   spend the same half hour working that out. It is the marquee's semantic
   unit (one alert), and the unit is what makes "this whole pass is a repeat"
   expressible on the duplicate. Its layout is entirely its parent's: the track
   is `display:flex` with a 28px gap, `white-space: nowrap` and `width:
   max-content`, so an item is at its natural width with nothing to shrink
   against and no wrapping to prevent. `flex: none` on it would be a rule that
   changes no pixel. */

/* PT24 Daily in the footer's PRODUCTS group is named without being linked,
   it is the morning audio briefing and it has never had a page. It has to read
   as a member of the list rather than as a link that failed, so it inherits
   everything the links inherit: the group sets the size and the ground sets
   the family, and neither is restated here. What it gives up is the one thing
   that says clickable, which on a knockout ground is not the ink but the
   brightness, the links sit at --knock-75 and lift to full paper on hover,
   and this sits at the weight the footer already uses for a line that is
   stated rather than offered. */
.footer-links .footer-nolink { color: var(--knock-50); }


/* ------------------------------------------------------------------ */
/* The front page's rail, out of its markup                            */
/*                                                                     */
/* Your ward, PT24 Daily and the broadcast card were built out of       */
/* inline `style` attributes, the type roles pasted onto each element   */
/* rather than named. Tokens were used throughout, so nothing was off   */
/* the scale; what was wrong is that the decisions lived in the         */
/* template, where a media query cannot reach them and where the same   */
/* widget drawn twice on one page could drift apart. These are those    */
/* decisions, unchanged, written down once.                            */
/* ------------------------------------------------------------------ */

/* The river's two columns start level, below the lead's band. */
.split.is-river > .main,
.split.is-river > .rail { padding-top: 26px; }

/* Your ward. */
.ward .hairline { margin-bottom: 11px; }
.ward-head .loc { color: var(--red-type); font-size: 14px; }
.ward-sawaal { display: block; margin-bottom: 13px; }
.ward-sawaal .q { font: 500 20px/1.22 var(--serif); margin-bottom: 6px; }
.ward-sawaal .meta { font: 400 13px var(--sans); color: var(--meta); }
.ward-story { display: block; margin-bottom: 11px; }
.ward-story .title { font: 500 19px/1.2 var(--serif); }
.ward-story .when { font: 400 13px var(--sans); color: var(--meta); margin-top: 4px; }
.ward-more { display: inline-block; margin-top: 14px; font: 400 14px var(--sans); color: var(--red-type); }

/* PT24 Daily. The gold is the audio accent, the same one the player's own
   progress uses; it is the one card on the front page about a thing you
   listen to rather than read. The card names it and says how long it runs;
   there is no transport on it, because there is nothing on this site to
   play. See the note above the card in home.html. */
.daily-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.daily-head .time { font: 500 12px var(--sans); color: var(--gold-type); letter-spacing: calc(.04em * var(--track)); }
.daily-blurb { font: 400 13px/1.45 var(--sans); color: var(--ink); margin: 0 0 11px; }
.daily-cta { font: 500 13px var(--sans); margin: 0; }

/* --- the 07:00 Daily, as a page ---------------------------------------- */
/* Everything above is the rail *card* that names the Daily on the front page.
   This is the paper itself, at /daily/.

   One column, one size, numbered, and no mosaic; argued in daily.html. The
   rule that does the most work here is the last one: the list ends, visibly,
   with a line of type rather than by simply running out of items. Every other
   river on this site is built to look like it continues past the fold, which
   is the correct shape for a river and the wrong one for a paper whose whole
   promise is that you can finish it. */
.daily-paper { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule); }
.daily-slot { display: grid; grid-template-columns: 2.4rem 1fr; gap: 0 14px; padding: 18px 0; border-bottom: 1px solid var(--rule); }
/* The slot number, set in the tabular face so 1 and 11 sit on the same left
   edge. Sized and coloured as furniture, not as a headline: it is the reader's
   position in the paper, and it must never compete with the story it labels. */
.daily-index { font: 500 15px/1.35 var(--sans); color: var(--gold-type); font-variant-numeric: tabular-nums; padding-top: 2px; }
.daily-body { min-width: 0; }
.daily-body h2 { font: 500 21px/1.24 var(--serif); margin: 0 0 6px; }
.daily-body .standfirst { font: 400 15px/1.5 var(--sans); color: var(--meta); margin: 0; }
.daily-when { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
/* The count of stories pulled since the freeze. Set apart from the list rather
   than inside it, because it is the paper talking about itself. */
.daily-short { margin: 14px 0 0; }
/* "That is the paper." The full stop at the end of a finite thing. */
.daily-end { font: 400 15px/1.5 var(--serif); font-style: italic; color: var(--meta); margin: 22px 0 0; text-align: center; }
/* Back numbers. A row of dates, not tiles: a paper that has been taken is a
   fact with nothing to preview, and a thumbnail would be inventing one. */
.daily-days { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 26px; padding-top: 18px; border-top: 1px solid var(--rule); }
.daily-day { font: 500 12px var(--sans); letter-spacing: calc(.04em * var(--track)); color: var(--meta); border: 1px solid var(--rule); padding: 6px 10px; text-decoration: none; }
.daily-day:hover { color: var(--ink); border-color: var(--ink); }
.daily-day.is-current { color: var(--paper); background: var(--ink); border-color: var(--ink); }

@media (max-width: 560px) {
  .daily-slot { grid-template-columns: 1.8rem 1fr; gap: 0 10px; padding: 15px 0; }
  .daily-body h2 { font-size: 19px; }
}

/* PT24 Live, in the rail. The still sits on ink because a broadcast frame
   that has not loaded should read as a dark screen, not as a hole. */
.broadcast-still { aspect-ratio: 16/9; background: var(--ink); display: grid; place-items: center; margin-bottom: 12px; overflow: hidden; }
.broadcast-still img { width: 100%; height: 100%; object-fit: cover; object-position: center top; cursor: zoom-in; }
.broadcast-onair { color: var(--knock-50); font: 400 13px var(--sans); letter-spacing: calc(.14em * var(--track)); }
.broadcast-title { font: 500 19px/1.2 var(--serif); margin-bottom: 5px; }
.broadcast-meta { font: 400 13px var(--sans); color: var(--meta); }

.rail-list.is-editions { gap: 10px; }

/* The lead's badge when the story is only developing, the twin of
   `.day-flag.is-developing`, and for the same reason: red says live, and a
   file that is not finished is not live. */
.badge-live.is-developing { background: var(--ink); }

/* The PWA surfaces. The first-run mark is the wordmark at reading size; the
   sheet's is the square icon, which is why one has a height and the other a
   box. Neither had a rule at all, so both drew at the art's own size. */
.pwa-firstrun .pt24-logo { height: 18px; margin-bottom: 26px; }
.pwa-sheet-head .pt24-logo { height: 40px; width: 40px; flex: none; }
.pwa-sheet-head .pwa-note { margin: 2px 0 0; }

/* ------------------------------------------------------------------ */
/* Leading, where the script decides it                                */
/*                                                                     */
/* The [lang] blocks at the top of this file swap the *families*, and   */
/* that was the whole of what this stylesheet knew about the scripts it */
/* publishes in. A Devanagari line is not a Latin line at another size: */
/* the matras above the shirorekha and the conjuncts hanging below it   */
/* need room the Latin x-height does not, and set at 1.55 they collide  */
/* with the line above and below. The design package gives the numbers: */
/* body 15/1.75 against 14.5/1.55, headlines 1.45 against 1.15, and     */
/* they are the two roles that carry running text, so they are the two  */
/* roles here.                                                          */
/*                                                                     */
/* `:lang()` and not `[lang="hi"] h1`, because the question is what     */
/* script *this element* is in: an untranslated English headline inside */
/* a Hindi story carries its own lang and comes back to Latin leading,  */
/* exactly as it comes back to Newsreader. It ties on specificity with  */
/* the rules it steps (h1.story, .update-body) and wins on order, which */
/* is why it is at the foot of the file, the same reason the last       */
/* third of it is where it is.                                          */
/* ------------------------------------------------------------------ */
/* Devanagari takes the package's own numbers (HINDI-VOICE.md): hero 1.24,
   card headline 1.28, nukkad card 1.32, thread 1.4, standfirst and body
   1.55. Kannada keeps the earlier, roomier pair until its package arrives. */
h1:lang(hi), h2:lang(hi), h3:lang(hi),
h1:lang(mr), h2:lang(mr), h3:lang(mr) { line-height: 1.28; }
h1.hero:lang(hi), h1.hero:lang(mr) { line-height: 1.24; }
.nukkad-card .title:lang(hi), .nukkad-card .title:lang(mr) { line-height: 1.32; }
.thread-headline:lang(hi), .thread-headline:lang(mr) { line-height: 1.4; }
h1:lang(kn), h2:lang(kn), h3:lang(kn) { line-height: 1.45; }

.standfirst:lang(hi), .update-body:lang(hi), .update-body p:lang(hi), .disagreement p:lang(hi),
.standfirst:lang(mr), .update-body:lang(mr), .update-body p:lang(mr), .disagreement p:lang(mr) { line-height: 1.55; }
.standfirst:lang(kn), .update-body:lang(kn), .update-body p:lang(kn) { line-height: 1.75; }


/* ==================================================================== */
/* THE CROPLESS TILE, AND THREE EDGES THAT WERE CUT RATHER THAN FADED   */
/*                                                                      */
/* Everything in this block was written against the rendered front page */
/* at 1440 and at 390, not against the file above it. Each rule says    */
/* what it was looking at.                                              */
/* ==================================================================== */


/* -------------------------------------------------------------------- */
/* 1. The cropless tile wears the paper's mark                            */
/*                                                                       */
/* This section set a tile without a crop as a news-in-brief: a 44px ink  */
/* stub, the figure collapsed, the headline up a size. Retired 2026-09-13 */
/* on the owner's instruction that the thumbnail carries the PT24 image:  */
/* day_tile.html now prints `.pt24-plate` (see the rule beside            */
/* `.day-shot-blank`) in the well, so every tile keeps its shape and the   */
/* `no-shot` class is gone from the markup.                               */
/* -------------------------------------------------------------------- */


/* -------------------------------------------------------------------- */
/* 2. The ticker's headlines leave the window instead of being guillotined */
/*                                                                        */
/* `.runner-window` is `overflow: hidden` over a marquee, so a headline is  */
/* cut mid-word at both edges, on the live page, "...seven Palestini" against */
/* the right-hand rule. That is correct clipping and it reads as damage.    */
/* A 34px fade on the entry edge and 14px on the exit is the difference     */
/* between a headline sliding out of view and one being severed.           */
/*                                                                        */
/* Two guards, and both fail towards what the page does today. `:has()` on  */
/* a track that is *not* `.is-static` means a ticker short enough to fit    */
/* (`initRunnerSeam` sets that class) is never dimmed at either end; a      */
/* browser without `:has()` simply gets no mask. And the whole thing sits   */
/* inside `no-preference`, because reduced motion kills the animation at    */
/* line ~317 without setting `.is-static`, and fading both ends of text     */
/* that is standing still would be the one case where this makes a page     */
/* worse. */
/* -------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .runner-window:has(> .runner-track:not(.is-static)) {
    -webkit-mask-image: linear-gradient(to right,
      transparent 0, #000 14px, #000 calc(100% - 34px), transparent 100%);
    mask-image: linear-gradient(to right,
      transparent 0, #000 14px, #000 calc(100% - 34px), transparent 100%);
  }
}


/* -------------------------------------------------------------------- */
/* 3. The phone's chip row says that it scrolls                          */
/*                                                                       */
/* `.section-head .filter-chips` becomes an overflow-x scroller at 820px   */
/* with `scrollbar-width: none`, which is the right call and leaves it      */
/* with no affordance whatsoever: at 390px the row measures 370 against a   */
/* 339 window, so "City desk 5" is sliced down its middle by the gutter and */
/* reads as a chip that has been cropped rather than as a row that carries  */
/* on. The fade is the affordance. The trailing padding is what keeps it    */
/* honest at the far end, the mask lands on the padding rather than on the  */
/* last chip once the row is scrolled through.                             */
/* -------------------------------------------------------------------- */
@media (max-width: 820px) {
  .section-head .filter-chips {
    padding-right: 26px;
    scroll-padding-inline-end: 26px;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 30px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 30px), transparent 100%);
  }
}


/* -------------------------------------------------------------------- */
/* 4. Nothing empty stands between the lead and the first story           */
/*                                                                       */
/* `.split` collapses at 1120px and the rail keeps DOM order, so the hero's */
/* rail lands directly under the lead, measured on the phone at y=969,      */
/* which is between the lead's Read button and the first headline of the    */
/* day. It carries the house slot and "Threads you follow", and on a first  */
/* visit the second of those is an empty state: an explanation of a feature */
/* the reader has not used yet, printed in the one position on the page     */
/* that belongs to the news.                                               */
/*                                                                        */
/* It earns its place in the desktop rail, which is a standing column a     */
/* reader's eye can decline. It does not earn a full-width interruption of  */
/* the river. `:has(.empty-rail)` is what makes this conditional on the     */
/* module being empty, a reader who *is* following threads still gets them  */
/* here, which is the whole point of the module. Without `:has()` the page  */
/* behaves exactly as it does now.                                         */
/*                                                                        */
/* The house slot stays. It is inventory, and where inventory sits is not   */
/* a decision the stylesheet gets to make.                                 */
/* -------------------------------------------------------------------- */
@media (max-width: 1120px) {
  .hero-section > .rail > div:has(.empty-rail) { display: none; }
}


/* -------------------------------------------------------------------- */
/* 5. The lead's side column closes at the foot of the picture            */
/*                                                                       */
/* `.hero-side` is stretched to the picture's height and `.thread-rail`     */
/* absorbs the difference, but only on a story that has a timeline. On one  */
/* that does not, and most do not, the column runs brief, Read, 177px of    */
/* nothing, measured: a 424px row against a 247px brief. Read is left        */
/* hanging in the middle of the column with the picture continuing past it, */
/* which reads as a block that stopped rather than one that finished.       */
/*                                                                        */
/* `margin-top: auto` sets the format row on the picture's bottom edge and  */
/* moves the slack up into the column, where it is composition instead of a */
/* remainder. It is inert wherever `.thread-rail` is present: `flex: 1 1     */
/* auto` has already taken the space, so there is none left for auto to      */
/* take. Above 820px only, where the row is two columns at all. */
/* -------------------------------------------------------------------- */
@media (min-width: 821px) {
  .hero-side > .format-row { margin-top: auto; }
}


/* ==================================================================== */
/* THE PAPER ANSWERS A FINGER                                           */
/*                                                                      */
/* This file had thirty-four hover rules and one press rule. That ratio */
/* is the whole problem: hover is the desktop's way of saying "this is  */
/* a target", a phone has no hover at all, and so on the device most of */
/* these readers actually hold, nearly nothing on the page acknowledged */
/* being touched. What they got instead was the browser's default grey  */
/* flash, which arrives late, is the wrong shape, and belongs to no     */
/* design system at all.                                               */
/*                                                                      */
/* Two halves, and they only work together. Turning the grey flash off  */
/* without putting something in its place is worse than leaving it: a   */
/* tap with no acknowledgement at all reads as a control that did not   */
/* register, and the reader taps again. So every selector that loses    */
/* the default below gains a press state here.                          */
/* ==================================================================== */

/* `manipulation` and not `none`. It drops the 300ms wait for a possible
   double-tap, which is the single largest source of "this felt slow" on a
   touch device, while leaving panning and pinch-zoom alone. Zoom is an
   accessibility affordance and nothing in this file may take it away, which
   is also why this is on the controls and not on the body. */
a, button, summary, label, [role="button"], .filter-chip, .day-link, .tab {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* Instant down, eased up.
   
   A press that fades in over 160ms feels like lag, and a press that snaps
   back feels broken; native controls light immediately and release gently.
   Zero going in, the full duration coming out, one declaration each and it
   is the whole difference between a web page and a control. */
.day-link, .follow-item, .edition-item, .nukkad-card, .sawaal-card,
.archive-day, .area-pill, .filter-chip, .btn-ink, .btn-line,
.btn-subscribe, .tabbar .tab, .ad-house-cta {
  transition: background-color 160ms ease-out, color 160ms ease-out,
              border-color 160ms ease-out, transform 160ms ease-out;
}
.day-link:active, .follow-item:active, .edition-item:active,
.nukkad-card:active, .sawaal-card:active, .archive-day:active,
.area-pill:active, .filter-chip:active, .btn-ink:active, .btn-line:active,
.btn-subscribe:active, .tabbar .tab:active,
.ad-house-cta:active { transition-duration: 0s; }

/* A row or a card presses like paper taking ink: the wash this system already
   uses for a resting surface. Nothing new is invented for the state, the
   press borrows a colour the reader has already been taught. */
.day-link:active, .follow-item:active, .edition-item:active,
.nukkad-card:active, .sawaal-card:active, .archive-day:active,
.tabbar .tab:active { background-color: var(--wash); }

/* Links already turn red under a cursor. A finger gets the same answer,
   which is one vocabulary rather than two. */
a:active { color: var(--red-type); }

/* A button presses to the state it already hovers to, so the two pointers
   agree about what the control does. */
.btn-ink:active { background: var(--red); border-color: var(--red); color: var(--knockout); }
.btn-line:active { background: var(--ink); color: var(--paper); }
.btn-subscribe:active { background: var(--ink); }
.filter-chip:active { background: var(--wash); border-color: var(--ink); }
/* The chip that is already ink cannot go darker; it steps back instead. */
.filter-chip.is-active:active { background: var(--ink); opacity: .78; }
.tabbar .tab:active { color: var(--ink); }
.area-pill:active { border-color: var(--ink); background: var(--wash); }

/* The give, and only where a finger is doing the pressing.

   A one-pixel drop, never a scale-down: the client's states card fixes the
   press as `translateY(1px)` plus a step darker, and a control that shrinks
   under the thumb reads as flinching. The drop is also what looks right
   under a mouse, where the pointer stays put and a scaled target would move
   out from under it. `hover: none` and `pointer: coarse` together are the
   honest test for "a finger", neither alone is: a laptop with a touchscreen
   reports coarse and still hovers.
   
   Kept off the big surfaces on purpose. A whole story tile shrinking is a lot
   of the screen moving for one tap; the wash above is enough there, and the
   give is spent on the things that read as controls. */
@media (hover: none) and (pointer: coarse) and (prefers-reduced-motion: no-preference) {
  .btn-ink:active, .btn-line:active, .btn-subscribe:active,
  .filter-chip:active, .area-pill:active,
  .tabbar .tab:active { transform: translateY(1px); }
}


/* -------------------------------------------------------------------- */
/* The haptics switch draws only where there is something to switch      */
/*                                                                       */
/* `navigator.vibrate` is Android's. iOS Safari has never implemented it  */
/* and the Taptic engine is not reachable from a web page, so on an       */
/* iPhone this row would be a control that silently does nothing, which   */
/* is worse than the row not being there. pt24.js adds `pt24-haptics` to  */
/* the root element where the motor exists, and this is the only thing    */
/* that draws the row.                                                    */
/*                                                                       */
/* Hidden by default and shown by a second rule rather than the reverse,  */
/* because the default has to be the safe one: a browser that never runs  */
/* the script shows no dead switch.                                       */
/* -------------------------------------------------------------------- */
.you-row.is-haptics { display: none; }
html.pt24-haptics .you-row.is-haptics { display: flex; }


/* ==================================================================== */
/* TURNING TO THE NEXT PAGE                                             */
/*                                                                      */
/* Every tap on this site is a full page load, and it looked like one:  */
/* the screen went white, the chrome was torn down and rebuilt, and the */
/* masthead the reader had been looking at flickered out and back. That */
/* flicker is the single loudest difference between a paper on the web  */
/* and a paper in an app, and it is not a speed problem, the pages are  */
/* already fast. It is that nothing on the screen was continuous.       */
/*                                                                      */
/* Cross-document view transitions fix exactly that, and they need no   */
/* JavaScript, no router and no library. The whole mechanism is the     */
/* at-rule below plus a name on each piece of furniture that should     */
/* survive the navigation. Where the browser does not support it, the   */
/* at-rule is ignored and the site behaves precisely as it does today.  */
/*                                                                      */
/* This is worth stating because the obvious alternative is not. The    */
/* usual advice for animating navigation in a server-rendered paper is  */
/* to stop doing server-rendered navigation, hijack every link click,   */
/* fetch, and swap the body. That is a large amount of machinery, a new */
/* class of bug around scroll and focus and back, and this newspaper's  */
/* whole architecture argues against it. The browser does it natively   */
/* now. */
/* ==================================================================== */

@view-transition { navigation: auto; }

/* --- the furniture that does not go anywhere ------------------------- */
/* A name lifts an element out of the page snapshot and gives it its own
   continuity, so it is the *same* masthead before and after rather than one
   picture of a masthead dissolving into another. This is the whole trick, and
   it is what an app does: the chrome stays put and the content underneath it
   changes.

   Only things that hold the same position on both pages are named. The footer
   is deliberately not: it sits wherever the story ends, so naming it would ask
   the browser to fly it from the foot of a long page to the foot of a short
   one, which is a great deal of motion in service of nothing.

   Every name has to be unique in the document or the browser abandons the
   transition entirely and silently. Each of these selectors was counted in the
   live DOM and matches exactly one element.

   A name is not free: the spec has a named element "form a stacking context",
   for the whole time the name is set and not only while a transition runs. So
   nothing that has to paint above the page may live inside one of these seven.
   That is not theoretical: the city and edition dialogs were inside the
   masthead when these names were added, and a z-index of 75 bought them
   nothing against a photograph. They are children of <body> now, and any
   overlay added later belongs there too. */
.masthead  { view-transition-name: pt24-masthead; }
.mtop      { view-transition-name: pt24-mtop; }
.mservice  { view-transition-name: pt24-mservice; }
.msections { view-transition-name: pt24-msections; }
.runner    { view-transition-name: pt24-runner; }
.nav       { view-transition-name: pt24-nav; }
.tabbar    { view-transition-name: pt24-tabbar; }

/* The chrome resolves any difference quickly and quietly, a dateline that
   ticked over, a nav item that became current. It should read as the same
   object updating, never as a transition. */
::view-transition-old(pt24-masthead),  ::view-transition-new(pt24-masthead),
::view-transition-old(pt24-mtop),      ::view-transition-new(pt24-mtop),
::view-transition-old(pt24-mservice),  ::view-transition-new(pt24-mservice),
::view-transition-old(pt24-msections), ::view-transition-new(pt24-msections),
::view-transition-old(pt24-runner),    ::view-transition-new(pt24-runner),
::view-transition-old(pt24-nav),       ::view-transition-new(pt24-nav),
::view-transition-old(pt24-tabbar),    ::view-transition-new(pt24-tabbar) {
  animation-duration: 90ms;
}

/* --- the page itself -------------------------------------------------- */
/* Short, and down-to-up by six pixels.

   The default is a 250ms cross-fade, which on a page of dense type reads as a
   blur: two sets of words on top of each other for a quarter of a second. The
   old page leaves quickly and the new one settles, which is closer to a sheet
   being laid down than to a card sliding in, and a newspaper is the former.

   Six pixels and not twenty. The movement is meant to be felt rather than
   watched; anything further and it becomes a slide, which is an app idiom this
   paper has not otherwise borrowed. */
@keyframes pt24-page-out { to   { opacity: 0; } }
@keyframes pt24-page-in  { from { opacity: 0; transform: translateY(6px); } }

::view-transition-old(root) { animation: pt24-page-out 90ms ease-in both; }
::view-transition-new(root) { animation: pt24-page-in 190ms ease-out both; }

/* --- and none of it where motion is not wanted ------------------------ */
/* Turned off at the source rather than by cancelling the animations. A reader
   who has asked for reduced motion gets the navigation they have always had,
   and the browser is never asked to snapshot anything, which is both the
   honest reading of the setting and the cheaper one. */
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}


/* ==================================================================== */
/* Live event                                                           */
/*                                                                      */
/* The Dahi Handi on launch night, and whatever the paper broadcasts    */
/* after it: `includes/event_band.html` under the masthead and           */
/* `live_event.html` for the page. Both are one row from the store, so   */
/* the desk can flip the stream open on the night without a deploy,     */
/* which is why every state here (the red flag, the gold kicker, the     */
/* plate before the feed opens) is a class the template can set from    */
/* the row and not a second template.                                   */
/*                                                                      */
/* The look is the client's on-air package, drawn in the sheet's own    */
/* tokens: the kicker as a gold chip, the flag as a red one, the slab   */
/* as --dark-inset behind a 6px gold rule, the headline in --serif,     */
/* the facts in big red tabular figures, the crawl and the sponsor      */
/* strip on ink. Type on the ink grounds re-points its accents through   */
/* the ledger under :root, as every other dark ground in this file      */
/* does; the four grounds below are listed there.                        */
/*                                                                      */
/* Last in the file on purpose, and one block: it re-opens nothing      */
/* above it and nothing above it needs to know it exists.               */
/* ==================================================================== */

/* --- the band ---------------------------------------------------------- */
/* Under the masthead's own hairline on the desktop and the top bar's on
   the phone, closing with one of its own: the two-hairline idiom the nav
   uses. On --wash so it reads as a strip laid on the page rather than as
   the first row of it. */
.event-band { background: var(--wash); border-bottom: 1px solid var(--ink); }
.event-band .shell {
  display: flex; align-items: center; gap: var(--space-md);
  min-height: 56px; padding-top: var(--space-xs); padding-bottom: var(--space-xs);
}
/* Red only while the feed is open. Before that the same chip carries the
   kicker in gold, the state and the occasion are different words and the
   band should not say "now" about tomorrow night. */
/* One chip for the band, the hero and the crawl's label: red ground,
   knocked-out word, a pulse where the feed is open. Its own class rather
   than `.event-band .flag` alone because the ground it declares is its
   own, a red chip on the band's --wash is read against red, not wash,
   and tests/test_accent_contrast.py reads the ground off the nearest
   class that sets one. */
.event-flag {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  background: var(--red); color: var(--knockout);
  font: 700 12px var(--sans); letter-spacing: calc(.06em * var(--track)); padding: 6px 10px;
}
.event-flag .dot { width: 7px; height: 7px; background: var(--knockout); }
.event-band .flag { align-self: center; }
.event-band .flag.is-kicker { background: var(--gold); color: var(--ink); letter-spacing: calc(.04em * var(--track)); }
.event-band .title {
  font: 500 22px/1.2 var(--serif); letter-spacing: 0; color: var(--ink); min-width: 0;
}
.event-band .meta { font: 400 13px/1.4 var(--sans); color: var(--meta); min-width: 0; }
/* No hour on the row yet: the desk has not announced one, and the band says so in the gold the kicker chip uses rather than printing a guess. */
.event-band .event-tba, .event-hero .event-when .event-tba { color: var(--gold-type); font-weight: 500; }
.event-band .cta { flex: none; margin-left: auto; font-size: 12px; padding: 9px 14px; }

/* --- the hero ----------------------------------------------------------- */
/* Full-bleed ink, on `.nukkad`'s recipe: the band is a wall across the page,
   not across the column, and it has to be a direct child of `.shell` for the
   pull-left to measure from the right edge. */
.event-hero {
  background: var(--ink); color: var(--paper);
  width: 100vw;
  margin: 0 0 var(--space-2xl) calc(-1 * (var(--gutter) + max(0px, (100vw - var(--max)) / 2)));
  padding: var(--space-xl) calc(var(--gutter) + max(0px, (100vw - var(--max)) / 2)) 0;
}
.event-flags { display: flex; align-items: center; gap: var(--space-sm); flex-wrap: wrap; margin-bottom: var(--space-md); }
.event-hero .event-kicker {
  display: inline-block; background: var(--gold); color: var(--ink);
  font: 700 12px var(--sans); letter-spacing: calc(.04em * var(--track)); padding: 7px 12px;
}
.event-hero .flag { gap: 7px; padding: 7px 12px; }
.event-hero .flag .dot { width: 8px; height: 8px; }
/* The feed has closed and the recording is what is embedded. Not red: red
   is the mark of a feed that is open, and this one is not. */
.event-hero .flag.is-over { background: var(--knock-16); }

/* The slab. One step lighter than the hero so it reads as a card laid on
   the ink, and a 6px gold rule down its left edge, which is the package's
   own mark for the venue line. */
.event-slab {
  background: var(--dark-inset); color: var(--paper);
  border-left: 6px solid var(--gold);
  padding: var(--space-lg) var(--space-xl) var(--space-lg) var(--space-lg);
  max-width: 62rem;
}
.event-slab .event-venue {
  font: 700 13px var(--sans); letter-spacing: calc(.06em * var(--track));
  color: var(--gold-type); margin-bottom: var(--space-xs);
}
.event-slab .event-title {
  font: 500 36px/1.2 var(--serif); letter-spacing: 0; margin: 0;
  text-wrap: wrap;
}
.event-hero .event-stand {
  font: 400 17px/1.5 var(--sans); color: var(--knock-68);
  margin: var(--space-sm) 0 0; max-width: 60ch;
}
.event-hero .event-when {
  display: flex; align-items: center; gap: var(--space-md); flex-wrap: wrap;
  font: 400 14px var(--sans); color: var(--knock-62); margin: var(--space-md) 0 0;
}
.event-hero .event-when .launch { color: var(--gold-type); font-weight: 500; }

/* --- the stream ---------------------------------------------------------- */
/* `.embed` is the story page's box, reused: the same --slot well, the same
   16:9 iframe. The poster and the plate fill it at the same ratio so the box
   is the same size before and after the feed opens and the page does not
   jump when the desk flips it. */
.event-embed { margin: var(--space-lg) 0 0; max-width: 62rem; }
.event-embed .embed { margin: 0; border-color: var(--knock-16); }
.event-embed .embed img { display: block; width: 100%; aspect-ratio: 16/9; object-fit: cover; }
.event-embed .day-shot-blank { aspect-ratio: 16/9; height: auto; font-size: 13px; }
.event-hero .event-embed .note { font: 400 14px/1.5 var(--sans); color: var(--knock-62); margin: var(--space-xs) 0 0; }
.event-hero .event-embed .note a { color: var(--paper); text-decoration: underline; text-underline-offset: 2px; }

/* --- where else to watch -------------------------------------------------- */
/* Text buttons, named for the network and nothing else. `.btn-line` is the
   paper's outline button, knocked out here and inverted on hover because
   its own hover is an ink fill, which on ink is nothing. */
.event-social { display: flex; align-items: center; gap: var(--space-xs); flex-wrap: wrap; margin-top: var(--space-lg); }
.event-social .label { font: 700 11.5px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--knock-62); margin-right: var(--space-2xs); }
.event-hero .btn-line { color: var(--paper); border-color: var(--knock-45); min-height: 36px; padding: 6px 14px; font-size: 13px; }
.event-hero .btn-line:hover { background: var(--paper); color: var(--ink); border-color: var(--paper); }

/* --- the facts ------------------------------------------------------------ */
/* Height, attempts, prize: big red tabular figures with a quiet label under
   each, divided by a hairline, as the package's next-day post sets them. */
.event-facts {
  display: flex; flex-wrap: wrap; margin-top: var(--space-xl); max-width: 62rem;
  border-top: 1px solid var(--knock-16);
}
.event-fact { flex: 1 1 140px; padding: var(--space-md) var(--space-lg) var(--space-md) 0; }
.event-fact + .event-fact { border-left: 1px solid var(--knock-16); padding-left: var(--space-lg); }
.event-hero .event-fact .n { display: block; font: 700 40px/1 var(--sans); color: var(--red-type); }
.event-hero .event-fact .l { display: block; font: 500 13px var(--sans); color: var(--knock-62); margin-top: var(--space-xs); }

/* --- the crawl ------------------------------------------------------------ */
/* The runner's mechanism again: a window, a track twice the content wide,
   and `pt24run` sliding it by half. The rules the runner learned the hard way
   hold here too, no gap on the track, every pass carrying its own trailing
   space, so half the track is exactly one pass. */
.event-updates {
  display: flex; align-items: stretch; margin-top: var(--space-xl);
  background: var(--ink); color: var(--paper);
  border-top: 1px solid var(--knock-16);
}
.event-updates .flag { display: flex; padding: 0 14px; }
.event-updates .track { flex: 1; overflow: hidden; position: relative; height: 48px; }
.event-updates .crawl {
  position: absolute; inset: 0; display: flex; align-items: center; gap: 0;
  width: max-content; animation: pt24run 30s linear infinite;
  font: 400 15px var(--sans); white-space: nowrap;
}
.event-updates .pass { display: flex; align-items: center; gap: 44px; flex: none; padding: 0 44px 0 0; }
.event-updates .pass:first-child { padding-left: var(--space-lg); }
/* A plain inline, as `.runner-item` is: an inline-flex here let the text
   inside shrink to its longest word and wrap in the middle of a crawl. */
.event-updates .item { display: inline; white-space: nowrap; }
.event-updates .item .tnum { color: var(--gold-type); font-weight: 500; }

/* --- the sponsors ----------------------------------------------------------- */
/* "प्रस्तुति" in gold, the presenting logos, "सहयोग" quieter, the co-sponsor.
   A logo is an <img> with the name as its alt, or the name in text where the
   desk has no artwork, never a dashed placeholder on a live page.  */
.event-sponsors {
  display: flex; align-items: center; gap: var(--space-lg); flex-wrap: wrap;
  padding: var(--space-md) 0 var(--space-lg);
  background: var(--ink); color: var(--knock-62);
  border-top: 1px solid var(--gold);
}
.event-sponsors .role { font: 700 12px var(--sans); letter-spacing: calc(.08em * var(--track)); color: var(--gold-type); flex: none; }
.event-sponsors .role.is-co { color: var(--knock-55); margin-left: var(--space-md); }
.event-sponsors .logo {
  display: inline-flex; align-items: center; min-height: 44px;
  font: 500 14px var(--sans); color: var(--paper);
}
.event-sponsors .logo img { display: block; height: 36px; width: auto; }

/* The Live lockup on this page is a mark, not the heading: the h1 is the
   event's own title in the slab. It takes the h1 rule's flex settings
   without the element. */
.live-head .vertical-lockup { line-height: 0; flex: none; }

@media (max-width: 820px) {
  .event-band .shell { flex-wrap: wrap; gap: var(--space-xs) var(--space-sm); padding: var(--space-sm) var(--gutter); }
  .event-band .title { font-size: 19px; flex: 1 1 100%; }
  .event-band .meta { font-size: 12.5px; flex: 1 1 auto; }
  .event-band .cta { margin-left: 0; }
  .event-hero { padding-top: var(--space-lg); margin-bottom: var(--space-xl); }
  .event-slab { padding: var(--space-md) var(--space-md) var(--space-md) var(--space-sm); border-left-width: 4px; }
  .event-slab .event-title { font-size: 24px; }
  .event-hero .event-stand { font-size: 15px; }
  .event-fact { flex-basis: 45%; }
  .event-hero .event-fact .n { font-size: 32px; }
  .event-updates .track { height: 44px; }
  .event-updates .crawl { font-size: 14px; }
  .event-sponsors { gap: var(--space-md); }
}

/* A reader who has asked for no motion gets the updates standing still,
   newest first, and the repeat comes off with the motion; it exists only
   to give the marquee something to run into. */
@media (prefers-reduced-motion: reduce) {
  .event-updates .crawl { animation: none; position: static; width: auto; white-space: normal; }
  .event-updates .pass + .pass { display: none; }
  .event-updates .track { height: auto; min-height: 48px; display: flex; align-items: center; }
}

/* ------------------------------------------------------------------ */
/* Icons that used to be glyphs                                        */
/*                                                                      */
/* The carets, the locator ring, the magnifier, the chip's cross and    */
/* the runner's diamond were characters; the golden rule (CLAUDE.md)   */
/* allows none, so each is an inline SVG at 1em in currentColor, which */
/* is exactly the size and colour the glyph took from its text. A       */
/* caret no longer swaps glyphs to point up: it turns.                  */
/* ------------------------------------------------------------------ */
.pt24-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -.125em; flex: none; }
.caret .pt24-icon { transition: transform 140ms ease-out; }
.caret.is-open .pt24-icon { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .caret .pt24-icon { transition: none; } }


/* ==================================================================== */
/* On air                                                               */
/*                                                                      */
/* The client's package ships a broadcast frame, the Dahi Handi frame   */
/* and two social creatives, all mocked with the copy typed in. These   */
/* rules draw them from the store: `newsroom/onair/*.html`, rendered    */
/* chromeless at true size for a browser source to composite over the   */
/* feed (1920 x 1080), or for a designer to capture (1080 x 1920 and    */
/* 1080 x 1080). Every size below is the package's own, in pixels,      */
/* because a frame is authored at the size it is broadcast at and       */
/* nothing here reflows: pt24.js scales the whole stage to fit a       */
/* smaller window (`initOnAir`) and the proportions hold.               */
/*                                                                      */
/* Ground and type re-point through the ledger under :root as every     */
/* other dark ground does. The frame's plates are the ink at an alpha   */
/* (`--ink-rgb`) so the feed shows through them, which is what makes    */
/* a plate read as furniture over a picture rather than a hole in it.   */
/*                                                                      */
/* Last in the file with the live event, for the same reason: it       */
/* re-opens nothing above it.                                           */
/* ==================================================================== */

body.onair { margin: 0; background: var(--ink); overflow: hidden; }
/* The window, and the stage centred in it. Without pt24.js the stage sits
   at its own size in the top-left corner, which is exactly right for a
   browser source set to the stage's size and only wrong for a laptop. */
.onair-fit { position: fixed; inset: 0; display: grid; place-items: center; overflow: hidden; }
.onair-stage {
  position: relative; flex: none; overflow: hidden;
  transform-origin: center center;
  color: var(--paper);
}
/* Transparent: the 16:9 frame is furniture over the programme feed. */
.onair-stage.is-frame { width: 1920px; height: 1080px; background: transparent; }
.onair-stage.is-social { width: 1080px; height: 1920px; background: var(--ink); display: flex; flex-direction: column; }
.onair-stage.is-post { width: 1080px; height: 1080px; background: var(--paper); color: var(--ink); display: flex; flex-direction: column; }
.onair-grow { flex: 1; }

/* --- the 16:9 frame's furniture ---------------------------------------- */
/* The scrim keeps the lower third legible over whatever the feed is
   showing, ink from the foot up to just past the strap and gone by the
   middle of the picture. */
.onair-scrim {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(var(--ink-rgb), .92) 0%, rgba(var(--ink-rgb), .35) 26%, rgba(var(--ink-rgb), 0) 52%);
}
.onair-topleft, .onair-topright { position: absolute; top: 44px; display: flex; align-items: center; gap: 14px; }
.onair-topleft { left: 52px; }
.onair-topright { right: 52px; gap: 16px; }
.onair-clock {
  background: rgba(var(--ink-rgb), .62); color: var(--paper);
  font: 400 28px var(--sans); letter-spacing: .04em; padding: 10px 18px;
}
.onair-kicker {
  background: var(--gold); color: var(--ink);
  font: 700 22px var(--sans); letter-spacing: calc(.04em * var(--track)); padding: 11px 18px;
}
.onair-live {
  display: inline-flex; align-items: center; gap: 12px;
  background: var(--red); color: var(--knockout);
  font: 700 24px var(--sans); letter-spacing: calc(.06em * var(--track)); padding: 10px 22px;
}
.onair-live .dot { width: 14px; height: 14px; background: var(--knockout); }
/* The plate behind the mark. The wordmark PNG is 624 x 212 with 582 x 76 of
   letterform in the middle of it (see `.masthead-mark`), so a 34px
   letterform is a 95px box with 30px of its own padding folded back on
   each side, and 9px at the ends. */
.onair-plate { display: flex; align-items: center; background: rgba(var(--ink-rgb), .62); padding: 12px 20px; }
.onair-plate .pt24-logo { height: 95px; width: auto; margin: -30px -9px; }

/* The strap: a flag, then the slab with the gold rule down its left edge. */
.onair-lower { position: absolute; left: 0; bottom: 238px; display: flex; max-width: 1520px; }
.onair-flag {
  display: inline-flex; align-items: center; gap: 14px; flex: none;
  background: var(--red); color: var(--knockout);
  font: 700 26px var(--sans); letter-spacing: calc(.06em * var(--track)); padding: 0 28px;
}
.onair-flag .dot { width: 14px; height: 14px; background: var(--knockout); }
/* A desk is not a state: its flag is the slab's own ground, one step
   lighter, and only the words say what the story is filed under. */
.onair-flag.is-desk { background: var(--dark-inset); color: var(--paper); }
.onair-flag.is-gold { background: var(--gold); color: var(--ink); }
.onair-slab {
  background: var(--ink); color: var(--paper);
  padding: 22px 40px 26px; border-left: 6px solid var(--gold); min-width: 0;
}
.onair-place { font: 700 22px var(--sans); letter-spacing: calc(.06em * var(--track)); color: var(--gold-type); margin-bottom: 10px; }
.onair-slab h2 { margin: 0; font: 600 58px/1.24 var(--serif); letter-spacing: 0; text-wrap: wrap; }
.onair-sub { margin-top: 12px; font: 400 26px/1.4 var(--sans); color: var(--knock-68); }
/* The verdict chip: the story page's `.verdict` recipe, a paper plate with
   the verdict's own colour as type and rule, sized for the screen it is on.
   The paper ground is what lets the three colours read on the ink slab and
   still mean what they mean two rows up the page. */
.onair-verdict {
  display: inline-block; margin-top: 18px; background: var(--paper);
  font: 700 22px var(--sans); letter-spacing: calc(.06em * var(--track)); padding: 10px 18px;
}
.onair-verdict.v-true { color: var(--verified); border: 2px solid var(--verified); }
.onair-verdict.v-context { color: var(--gold-type); border: 2px solid var(--gold); }
.onair-verdict.v-false { color: var(--red-type); border: 2px solid var(--red); }
/* A story's standfirst is a paragraph; the strap gives it two lines and no
   more, a lower third is read in the time the shot holds. */
.onair-slab .onair-sub { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }

/* The sponsor strip, above the crawl. */
.onair-sponsors.is-frame {
  position: absolute; left: 0; right: 0; bottom: 86px; height: 66px;
  background: rgba(var(--ink-rgb), .86); border-top: 1px solid rgba(199, 134, 43, .45);
  display: flex; align-items: center; gap: 22px; padding: 0 52px;
}
.onair-sponsors .role { flex: none; font: 700 20px var(--sans); letter-spacing: calc(.08em * var(--track)); color: var(--gold-type); }
.onair-sponsors .role.is-co { color: var(--knock-50); margin-left: 18px; }
.onair-sponsors .logo { flex: none; display: flex; align-items: center; justify-content: center; height: 38px; }
.onair-sponsors .logo img { display: block; height: 100%; width: auto; }
/* A sponsor with no artwork is named in a plate, the page's own rule. */
.onair-sponsors .logo.is-name {
  min-width: 190px; padding: 0 22px; border: 1px solid var(--knock-45);
  font: 500 17px var(--sans); color: var(--knock-62);
}
.onair-sponsors .also { margin-left: auto; flex: none; font: 400 19px var(--sans); color: var(--knock-55); }

/* The crawl. The runner's marquee, 86px tall, with the clock at its tail. */
.onair-crawl { position: absolute; left: 0; right: 0; bottom: 0; height: 86px; display: flex; align-items: stretch; }
.onair-crawl .onair-flag { padding: 0 30px; }
.onair-crawl .track { flex: 1; min-width: 0; background: var(--ink); color: var(--paper); overflow: hidden; display: flex; align-items: center; }
.onair-crawl .run { display: flex; white-space: nowrap; font: 400 30px var(--sans); animation: pt24run 26s linear infinite; }
.onair-crawl .pass { display: flex; gap: 70px; padding-left: 40px; padding-right: 70px; }
.onair-crawl .item .tnum { color: var(--red-type); margin-right: 8px; }
.onair-crawl .tail {
  flex: none; display: flex; align-items: center;
  background: var(--dark-inset); color: var(--paper);
  font: 400 34px var(--sans); padding: 0 34px; border-left: 4px solid var(--red);
}

/* The full-frame alert: red to every edge, the gold rule, the flag, the
   headline at the package's 104px, the hour and the city under it. */
.onair-alert {
  position: absolute; inset: 0; background: var(--red); color: var(--knockout);
  display: flex; flex-direction: column; justify-content: center; padding: 0 130px;
}
.onair-alert .rule { display: block; width: 180px; height: 8px; background: var(--focus-gold); }
.onair-alert .flag { display: block; margin: 26px 0 18px; font: 700 34px var(--sans); letter-spacing: calc(.06em * var(--track)); }
.onair-alert h2 { margin: 0; font: 700 104px/1.18 var(--serif); letter-spacing: 0; text-wrap: wrap; }
.onair-alert .sub { margin-top: 22px; font: 400 34px var(--sans); color: rgba(255, 255, 255, .82); }

/* --- the 9:16 social creative ------------------------------------------ */
.onair-social-head { flex: none; display: flex; align-items: center; justify-content: space-between; padding: 44px 56px 0; }
.onair-mark { display: flex; align-items: center; }
.onair-social-head .meta { font: 500 26px var(--sans); color: var(--knock-62); }
/* The mark at 46px of letterform is a 128px box, see `.onair-plate`. */
.onair-social-head .pt24-logo { height: 128px; width: auto; margin: -41px -13px; }
.onair-social-chips { flex: none; display: flex; align-items: center; gap: 16px; padding: 40px 56px 0; }
.onair-social-chips .onair-live, .onair-social-chips .onair-kicker { font-size: 26px; padding: 12px 22px; }
.onair-social-title { flex: none; margin: 32px 56px 0; font: 600 92px/1.18 var(--serif); letter-spacing: 0; color: var(--paper); text-wrap: wrap; }
.onair-social-stand { flex: none; margin: 26px 56px 0; font: 400 34px/1.5 var(--sans); color: var(--knock-68); }
.onair-social-video {
  flex: none; position: relative; margin: 40px 56px 0; aspect-ratio: 16 / 9;
  background: var(--dark-inset); border: 1px solid var(--knock-16); overflow: hidden;
}
.onair-social-video iframe, .onair-social-video img { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; object-fit: cover; }
.onair-social-video .empty { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; }
.onair-social-video .play { display: flex; align-items: center; justify-content: center; width: 118px; height: 82px; background: var(--red); color: var(--knockout); }
.onair-social-video .when { font: 500 28px var(--sans); color: var(--knock-62); }
.onair-rundown { flex: none; margin: 44px 56px 0; display: flex; flex-direction: column; }
.onair-rundown .row { display: flex; align-items: center; gap: 26px; padding: 22px 0; border-top: 1px solid var(--knock-16); }
.onair-rundown .row:last-child { border-bottom: 1px solid var(--knock-16); }
.onair-rundown .t { flex: none; min-width: 130px; font: 500 32px var(--sans); color: var(--gold-type); }
.onair-rundown .x { font: 500 34px var(--sans); color: var(--paper); }
.onair-social-cta {
  flex: none; display: block; margin: 0 56px 34px; padding: 30px 0; text-align: center;
  background: var(--red); color: var(--knockout); text-decoration: none;
  font: 700 38px var(--sans);
}
.onair-social-cta:hover, .onair-social-cta:focus-visible { color: var(--knockout); text-decoration: none; }
.onair-sponsors.is-social {
  flex: none; display: flex; align-items: center; gap: 22px;
  background: var(--ink); border-top: 1px solid rgba(199, 134, 43, .45); padding: 26px 56px;
}
.onair-sponsors.is-social .role { font-size: 24px; }
.onair-sponsors.is-social .logo { flex: 1; height: 56px; }
.onair-sponsors.is-social .logo.is-name { font-size: 24px; }

/* --- the 1:1 post -------------------------------------------------------- */
.onair-post-head {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  background: var(--ink); color: var(--paper); padding: 26px 44px; border-bottom: 3px solid var(--red);
}
.onair-post-head .meta { font: 500 24px var(--sans); color: var(--knock-68); }
/* 38px of letterform: a 106px box. */
.onair-post-head .pt24-logo { height: 106px; width: auto; margin: -34px -10px; }
.onair-post-photo { flex: none; position: relative; height: 432px; background: var(--slot); overflow: hidden; }
.onair-post-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.onair-post-photo .day-shot-blank { font-size: 28px; letter-spacing: calc(.04em * var(--track)); text-transform: none; }
.onair-post-photo .tag {
  position: absolute; left: 0; bottom: 0; background: var(--gold); color: var(--ink);
  font: 700 22px var(--sans); letter-spacing: calc(.04em * var(--track)); padding: 10px 18px;
}
.onair-post-title { flex: none; margin: 34px 44px 0; font: 600 62px/1.22 var(--serif); letter-spacing: 0; color: var(--ink); text-wrap: wrap; }
.onair-post-stand { flex: none; margin: 22px 44px 0; font: 400 30px/1.5 var(--sans); color: var(--ink-60); }
.onair-post-facts { flex: none; display: flex; margin: 0 44px; border-top: 1px solid var(--rule-strong); }
.onair-post-facts .cell { flex: 1; padding: 24px 0; display: flex; flex-direction: column; }
.onair-post-facts .cell + .cell { border-left: 1px solid var(--rule-strong); padding-left: 26px; }
.onair-post-facts .n { font: 700 44px var(--sans); color: var(--red-type); }
.onair-post-facts .l { margin-top: 6px; font: 500 22px var(--sans); color: var(--ink-45); }
.onair-sponsors.is-post {
  flex: none; display: flex; align-items: center; gap: 20px;
  background: var(--ink); color: var(--paper); padding: 22px 44px;
}
.onair-sponsors.is-post .role { font-size: 22px; }
.onair-sponsors.is-post .logo { flex: 1; height: 48px; }
.onair-sponsors.is-post .logo.is-name { font-size: 22px; }

@media (prefers-reduced-motion: reduce) {
  .onair-crawl .run { animation: none; }
}


/* -- feature round 2026-09. One block per branch; reuse existing tokens and
   classes first, add rules here only when nothing above fits. */

/* == topics (B) == */
/* The topic chips on the story page and the cloud on /topic/ are `.chip`
   pills in a `.chip-row`, as the cities panel draws them; only the frame
   around them and the count inside them are new. A chip that is a link
   keeps the pill's own hover and never the page's underline. */
.topic-chips { margin-top: 22px; }
.topic-chips .chip-row { margin-top: 10px; }
.topic-chips .chip, .topic-cloud .chip { text-decoration: none; }
.topic-chips .chip:hover, .topic-cloud .chip:hover { text-decoration: none; color: var(--ink); }
.topic-cloud { margin: 6px 0 26px; }
.topic-cloud .chip .n { font-weight: 500; font-variant-numeric: tabular-nums; color: var(--meta); }
.section-head .topic-feed { text-decoration: none; }
.section-head .topic-feed:hover { text-decoration: underline; text-underline-offset: 2px; }
/* == end topics == */


/* == most-read (C) == */
/* == end most-read == */


/* == newsletter (D) == */
/* The masthead's Subscribe control is a link now, to /newsletter/. The
   global `a:hover` (0,1,1) outranks `.btn-subscribe` (0,1,0) on colour and
   underline, so without this the knockout label turned red on ink on hover.
   Scoped to the masthead for the specificity, (0,3,0); the ground is the
   ink `.btn-subscribe:hover` already sets, not restated here. */
.masthead .btn-subscribe:hover, .masthead .btn-subscribe:active {
  color: var(--knockout); text-decoration: none;
}

/* The signup form on /newsletter/. One field and one button on a row that
   wraps at narrow widths; the field takes the control height the masthead's
   search box uses and the button `.btn-red` already draws. */
.newsletter-form { max-width: 560px; margin-bottom: 34px; }
.newsletter-label { display: block; font: 700 11.5px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--meta); margin: 0 0 8px; }
.newsletter-row { display: flex; gap: 10px; flex-wrap: wrap; }
.newsletter-row input[type="email"] {
  flex: 1 1 240px; min-width: 0; height: var(--control-h);
  border: 1px solid var(--rule-strong); border-radius: 0; background: var(--paper);
  padding: 0 12px; font: 400 14px var(--sans); color: var(--ink);
}
.newsletter-row input[type="email"]:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.newsletter-row .btn-red { height: var(--control-h); padding: 0 16px; font-size: 12.5px; }
.newsletter-error { margin: 8px 0 0; font: 400 13px/1.5 var(--sans); color: var(--red-type); }
.newsletter-note { margin: 12px 0 0; font: 400 13px/1.5 var(--sans); color: var(--meta); max-width: 56ch; }
.newsletter-leave { margin-top: 14px; }
.newsletter-leave .btn-ink { height: var(--control-h); }
@media (max-width: 820px) {
  .newsletter-row input[type="email"], .newsletter-row .btn-red, .newsletter-leave .btn-ink { min-height: 44px; }
}
/* == end newsletter == */


/* == push (E) == */
/* == end push == */


/* == contact (F) == */
/* The three pages a reader writes through: /contact/, /tip/, /advertise/.
   One column, the story measure, the same `.empty` box every other quiet
   screen draws for the thank-you and the refusal. The controls are drawn
   like `.area-search`: a hairline of --rule-heavy on --card, --sans inside.
   The button is the system's `.btn-ink`. */
.contact-page { padding-top: 26px; }
.contact-kicker {
  font: 700 10.5px var(--sans); letter-spacing: calc(.09em * var(--track));
  color: var(--red-type); margin: 0 0 10px;
}
.contact-page h1 {
  font: 500 32px/1.12 var(--serif); margin: 0 0 14px; text-wrap: balance; max-width: 24ch;
}
.contact-page h1:lang(hi), .contact-page h1:lang(mr) { line-height: 1.24; }
.contact-page h1:lang(kn) { line-height: 1.4; }
.contact-facts {
  display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px 22px;
  margin: 0 0 26px; padding: 18px 0; border-top: 1px solid var(--ink); border-bottom: 1px solid var(--rule);
  font: 400 14px/1.5 var(--sans); max-width: 62ch;
}
.contact-facts dt { font: 700 11.5px/1.5 var(--sans); letter-spacing: calc(.08em * var(--track)); color: var(--meta); margin: 0; padding-top: 3px; }
.contact-facts dd { margin: 0; }
.contact-facts dd a { text-decoration: underline; text-underline-offset: 2px; }
.contact-form { display: grid; gap: 18px; max-width: 62ch; margin: 0 0 var(--space-2xl); }
.contact-field { display: grid; gap: 6px; }
.contact-field label { font: 700 11.5px var(--sans); letter-spacing: calc(.08em * var(--track)); color: var(--ink-70); }
.contact-field input, .contact-field textarea, .contact-field select {
  width: 100%; box-sizing: border-box; min-height: var(--control-h);
  border: 1px solid var(--rule-heavy); background: var(--card); border-radius: 2px;
  padding: 9px 14px; font: 400 14px/1.4 var(--sans); color: var(--ink);
}
.contact-field textarea { min-height: 150px; resize: vertical; }
.contact-field input:focus, .contact-field textarea:focus, .contact-field select:focus {
  outline: 2px solid var(--focus); outline-offset: 2px;
}
.contact-field .hint { font: 400 12.5px/1.5 var(--sans); color: var(--meta); margin: 0; }
.contact-field .error { font: 400 12.5px/1.5 var(--sans); color: var(--red-type); margin: 0; }
.contact-errors {
  border: 1px solid var(--red); padding: 12px 16px; font: 400 13.5px/1.5 var(--sans);
  color: var(--ink); max-width: 62ch;
}
.contact-errors p { margin: 0; }
.contact-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.contact-actions .btn-ink { min-width: 180px; }
.contact-promise { font: 400 13px/1.55 var(--sans); color: var(--ink-70); margin: 0; max-width: 62ch; }
.contact-promise a { text-decoration: underline; text-underline-offset: 2px; }
/* The honeypot: a field a person never sees and a script fills. Moved off
   the canvas rather than `display: none`, so the field is still in the
   form a script reads and still submitted. */
.contact-honey { position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; overflow: hidden; }
@media (max-width: 820px) {
  .contact-page h1 { font-size: 24px; }
  .contact-facts { grid-template-columns: 1fr; gap: 4px 0; }
  .contact-facts dt { padding-top: 8px; }
}
/* == end contact == */


/* == the house's written pages == */
/* The eight pages of the house: the editorial charter, who we are, the
   corrections log, ownership, the grievance officer, the privacy notice, the
   DPDP consent notice and the terms of use. One template,
   `newsroom/document.html`, words in `newsroom_web/house.py`. They were
   stubs borrowing `.empty.is-standalone` and adding nothing to this file,
   because a label and one sentence is furniture the site already owns. A
   document is not: it is the only long-form prose on this site that is not a
   story, it has to be scannable by a reader looking for one clause, and it
   must not be set in the story faces or it will read as a story.

   So: the sans at the reading size, headings as the small red labels the
   contact pages established rather than as headlines, and the measure held at
   66ch, the same one `.awaaz-notice` holds for the same reason. Everything
   here is a token. */
.house-doc { padding-top: 26px; }
.house-doc h1 {
  font: 500 32px/1.12 var(--serif); margin: 0 0 14px; text-wrap: balance; max-width: 22ch;
}
.house-doc h1:lang(hi), .house-doc h1:lang(mr) { line-height: 1.24; }
.house-doc h1:lang(kn) { line-height: 1.4; }
.house-doc .standfirst { max-width: 66ch; margin: 0 0 14px; }
/* The date carries the rule under the opening, so the sections below it read
   as the document and the standfirst reads as the way in. */
.house-doc .doc-date {
  font: 400 12.5px/1.5 var(--sans); color: var(--meta); margin: 0 0 30px;
  padding-bottom: 16px; border-bottom: 1px solid var(--ink);
}
.house-doc section { max-width: 66ch; margin: 0 0 28px; }
.house-doc h2 {
  font: 700 11.5px var(--sans); letter-spacing: calc(.09em * var(--track));
  color: var(--red-type); margin: 0 0 10px;
}
.house-doc p { font: 400 14.5px/1.6 var(--sans); color: var(--ink-70); margin: 0 0 10px; }
.house-doc p:last-child { margin-bottom: 0; }
/* The scannable half: a term and what it means, drawn like `.contact-facts`
   without its borders, because here there are several of them down one page
   and a rule around each would fence the document into boxes. */
/* The term column is capped rather than `max-content`, which is where
   `.contact-facts` leaves it: there the labels are one word and the column
   sizes itself honestly, here one term runs to six words and `max-content`
   sets the column to the longest of them, leaving every short term beside a
   hand's width of nothing. 15ch lets the long ones wrap and holds the
   definitions on one measure down the page. */
.house-doc .doc-points {
  display: grid; grid-template-columns: minmax(0, 15ch) minmax(0, 1fr); gap: 14px 22px;
  margin: 14px 0 0;
}
.house-doc .doc-points dt {
  font: 700 11.5px/1.6 var(--sans); letter-spacing: calc(.06em * var(--track));
  color: var(--ink); margin: 0;
}
.house-doc .doc-points dd { font: 400 14px/1.6 var(--sans); color: var(--ink-70); margin: 0; }
.house-doc .doc-closing { margin-top: 14px; }
/* The officer, and the two siblings. Both are the document's foot rather than
   its body, which is what the wash and the rule above them say. */
.house-doc .doc-officer {
  padding: 18px 0 0; border-top: 1px solid var(--rule-heavy); margin-top: 32px;
}
.house-doc .doc-see-also {
  font: 400 13px/1.6 var(--sans); color: var(--meta); max-width: 66ch;
  margin: 0 0 var(--space-2xl);
}
.house-doc .doc-see-also a { color: var(--ink-70); text-decoration: underline; text-underline-offset: 2px; }
/* The two blocks that are fetched rather than written. Both are the paper
   telling a reader something about itself as it stands today, so both sit
   above the officer inside the body rather than below the rule with it, and
   both carry a hairline that marks where the written page stops and the
   paper's own record starts. Three weights down the page and they mean three
   things: no rule is prose, `--rule` is a fetched fact, and the heavier rule
   under them is the document's foot.

   Inside, the masthead needs nothing further: it is a `.doc-points` list
   whose definitions happen to have come off the wire. */
.house-doc .doc-masthead, .house-doc .doc-log {
  padding-top: 20px; border-top: 1px solid var(--rule);
}
.house-doc .doc-log-list { list-style: none; margin: 14px 0 0; padding: 0; }
.house-doc .doc-log-list li {
  padding: 14px 0; border-top: 1px solid var(--rule);
}
.house-doc .doc-log-list li:first-child { border-top: 0; padding-top: 0; }
/* The date is the label of the entry, set like the terms in `.doc-points` so
   a reader scanning the log down its left edge is reading one column of one
   kind of thing. */
.house-doc .doc-log-when {
  font: 700 11.5px/1.6 var(--sans); letter-spacing: calc(.06em * var(--track));
  color: var(--ink); margin: 0 0 4px;
}
.house-doc .doc-log-story { font-size: 13px; margin: 6px 0 0; }
.house-doc .doc-log-story a { color: var(--ink-70); text-decoration: underline; text-underline-offset: 2px; }
@media (max-width: 820px) {
  .house-doc h1 { font-size: 24px; }
  /* One column: a term and its text stacked, the same fall-back
     `.contact-facts` takes, because two columns at this width leave the
     definition four words wide. */
  .house-doc .doc-points { grid-template-columns: 1fr; gap: 4px 0; }
  .house-doc .doc-points dt { padding-top: 10px; }
}
/* == end the house's written pages == */


/* ==================================================================== */
/* THE PHONE, SEPTEMBER 2026: THE RIVER AS ROWS                          */
/*                                                                      */
/* Measured on the live front page at 390px on 6 September 2026: 9,739  */
/* pixels tall, eleven and a half screens. The mosaic stacked twelve     */
/* full-width photographs at 307px each (3,925px), More news stacked     */
/* sixteen rows at 133px (2,378px), the two unsold ad slots took 180px   */
/* apiece, and the footer alone was 1,034px: one column, one link per   */
/* line, five groups end to end. The subject axis, the section names,   */
/* was a hamburger away and printed nowhere on the page.                */
/*                                                                      */
/* Four answers. All phone-only, all the package's own components drawn */
/* at a phone's proportions, and none of them a new piece of markup:    */
/*                                                                      */
/*   1. The river is rows. The first tile keeps its picture, it is the  */
/*      largest thing on the screen and the LCP on a river page; every  */
/*      tile after it is a row with a 96px square at its right, which   */
/*      is the shape a phone reader already reads a feed in. A row is   */
/*      about 118px against 307, so the day's twelve cost 1,600px       */
/*      instead of 3,925. The tile is the same markup at every size.    */
/*   2. The section strip. The desktop nav was display:none below       */
/*      820px; it is a horizontal scroller under the runner now, the    */
/*      subjects in the nav's own type with the same red rule under the */
/*      current one, and it rides the pt24-nav view transition.         */
/*   3. The footer is a two-column directory rather than a column of    */
/*      lines, and the promise closes it in the serif.                  */
/*   4. Rows rise into place as the reader reaches them, six pixels and */
/*      200ms, the page transition's own gesture, once per row, off     */
/*      under reduced motion and inert until pt24.js says otherwise.    */
/* ==================================================================== */

@media (max-width: 820px) {
  /* --- 2. The section strip ------------------------------------------ */
  /* The `.masthead .nav { display: none }` in the phone block above is
     overtaken here, later in the file at the same specificity. The nav
     bleeds to both screen edges with the gutter restored inside it, so the
     first subject sits on the page's left edge and the last one can scroll
     clear of the fade. Verticals and the format axis stay hidden: the tab
     bar already carries Live, Nukkad and Sawaal, and the sheet behind the
     top bar's menu carries the formats. */
  .masthead .nav {
    display: flex; flex-wrap: nowrap; align-items: stretch;
    column-gap: 22px; min-height: 0; height: 42px;
    margin: 0 calc(-1 * (var(--gutter) + var(--safe-right))) 0 calc(-1 * (var(--gutter) + var(--safe-left)));
    padding: 0 0 0 calc(var(--gutter) + var(--safe-left));
    overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    border-top: 0; border-bottom: 1px solid var(--rule);
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent);
  }
  .masthead .nav::-webkit-scrollbar { display: none; }
  /* The trailing spacer: with the gap it is the width of the fade, so the
     last subject scrolls fully into the clear rather than stopping inside
     the gradient. An end padding on a scroller is honoured by some engines
     and not by others; a flex child is honoured by all of them. */
  .masthead .nav::after { content: ""; flex: none; width: 18px; }
  .masthead .nav a { flex: none; font-size: 14px; padding: 0 1px; }
  .masthead .nav a.is-active { box-shadow: inset 0 -3px 0 var(--red); }
  .masthead .nav .nav-verticals, .masthead .nav .nav-service { display: none; }

  /* --- the lead's brief ---------------------------------------------- */
  /* Six lines, and Read directly under them. On the desktop the brief sits
     beside the picture in a column that has the height to spare; on the
     phone it stacked under the photograph as a fourth block of prose
     before the river had started. */
  .hero-brief p {
    display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* --- the unsold slot ----------------------------------------------- */
  /* The 180px floor was set for a desktop rail stacked under a story. On a
     phone it is a band of dashed cream the height of a story row, twice per
     front page. The invitation still prints; the air around it does not. */
  .ad-house { min-height: 0; padding: 14px 16px; gap: 6px; }
  .ad-house-note { font-size: 12px; }

  /* --- 3. The footer -------------------------------------------------- */
  .footer { padding: 26px 0 22px; }
  .footer-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 18px;
    padding-bottom: 20px;
  }
  /* The brand block spans the row; the four groups pair up under it,
     Editions beside Products, the house beside Follow. */
  .footer-grid > div:first-child { grid-column: 1 / -1; }
  .footer p { font-size: 13.5px; margin-bottom: 12px; }
  .footer h4 { font-size: 11px; margin-bottom: 11px; }
  .footer-links { gap: 9px; font-size: 14px; }
  /* FOLLOW is half a phone wide here, about 150px, and five 38px tiles need
     222. Shrinking them to fit would put the row under the 40px a thumb wants,
     so they get bigger rather than smaller and wrap: three, then two, left
     aligned under each other, which reads as a block and not as a row that ran
     out of room. The two service rows grow the same way, for the same thumb. */
  .follow-tile { width: 40px; height: 40px; font-size: 18px; }
  .follow-action { padding: 9px 0; }
  /* The switcher fills its column: three equal cells instead of three
     52px cells that only reach two thirds of the way across. */
  .footer .lang-switch { width: 100%; display: flex; margin-top: 6px; }
  .footer .lang-switch button { flex: 1 1 0; min-width: 0; }
  /* The base is one wrapping row again. The promise leads it in the serif,
     the paper's one sentence about itself, and the legal line follows at
     its own size. It used to be the last line of a column of six and ran
     off the right edge of the screen. */
  .footer-base {
    padding-top: 18px; flex-direction: row; flex-wrap: wrap;
    align-items: baseline; gap: 8px 14px;
  }
  .footer-base .promise {
    order: -1; flex: 1 1 100%; margin: 0 0 8px;
    font: 500 15px/1.5 var(--serif); color: var(--knock-75);
  }
}

/* --- 1. The river as rows ----------------------------------------------- */
/* 560px and not 820: it is the width at which `.day-mosaic` and
   `.brief-grid` already go to one column, and a row is what a one-column
   tile becomes. Between 561 and 820 the two-column grid stands. */
@media (max-width: 560px) {
  .day-mosaic { row-gap: 0; }
  /* A hairline over every row, which is the rule `.brief-tile` already
     uses for the same reason: it survives the lead tile being filtered
     out, where an `nth-child` would not. */
  .day-mosaic .day-tile { padding: 14px 0; border-top: 1px solid var(--rule); }
  .day-mosaic .day-tile .day-frame {
    display: grid; grid-template-columns: minmax(0, 1fr) 96px;
    column-gap: 14px; align-items: start;
  }
  .day-mosaic .day-tile .day-shot { order: 2; aspect-ratio: 1 / 1; margin: 0; }
  .day-mosaic .day-tile .day-cap { order: 1; min-width: 0; }
  .day-mosaic .day-tile .day-headline { font-size: 17px; line-height: 1.25; }
  .day-mosaic .day-tile .day-kicker { margin-bottom: 6px; font-size: 11.5px; }
  .day-mosaic .day-tile .day-meta { margin-top: 7px; font-size: 11.5px; }
  /* The flag on a 96px square: same words, same red, less padding. 9.5px is
     what it already is; nothing on the phone goes under 10px but this, and
     it was already the one exception. */
  .day-mosaic .day-tile .day-flag { padding: 3px 6px; }

  /* The lead tile keeps its picture. `.mosaic-row` and `.mosaic-stack` are
     `display: contents`, so the first tile is reached through them rather
     than as the grid's own first child. */
  .day-mosaic > .mosaic-row:first-child > .day-tile:first-child,
  .day-mosaic > .mosaic-row:first-child > .mosaic-stack:first-child > .day-tile:first-child {
    padding-top: 0; border-top: 0;
  }
  .day-mosaic > .mosaic-row:first-child > .day-tile:first-child .day-frame,
  .day-mosaic > .mosaic-row:first-child > .mosaic-stack:first-child > .day-tile:first-child .day-frame {
    display: block;
  }
  .day-mosaic > .mosaic-row:first-child > .day-tile:first-child .day-shot,
  .day-mosaic > .mosaic-row:first-child > .mosaic-stack:first-child > .day-tile:first-child .day-shot {
    aspect-ratio: 16 / 9; margin: 0 0 12px;
  }
  .day-mosaic > .mosaic-row:first-child > .day-tile:first-child .day-headline,
  .day-mosaic > .mosaic-row:first-child > .mosaic-stack:first-child > .day-tile:first-child .day-headline {
    font-size: 21px; line-height: 1.2;
  }
  /* A date rule already closes the row above; the hairline under it would
     be a second rule saying the same thing. */
  .mosaic-daybreak + .mosaic-row > .day-tile:first-child,
  .mosaic-daybreak + .mosaic-row > .mosaic-stack:first-child > .day-tile:first-child { border-top: 0; }

  /* More news: the kicker, the headline, then one line of provenance. The
     read time and the credit used to be two lines under every headline,
     sixteen times, and together they were a third of the block. */
  .brief-grid { row-gap: 0; }
  .brief-tile {
    padding: 13px 0 14px; flex-direction: row; flex-wrap: wrap;
    align-items: baseline; column-gap: 11px;
  }
  .brief-tile .day-link { flex: 1 1 100%; }
  .brief-tile .day-kicker { margin-bottom: 5px; font-size: 11.5px; }
  .brief-tile .day-headline { font-size: 16px; line-height: 1.3; }
  .brief-tile .day-meta { margin-top: 6px; font-size: 11.5px; }
  .brief-tile .brief-credit { display: inline; margin-top: 6px; font-size: 11.5px; }
  .brief-tile .day-meta + .brief-credit::before { content: "-"; margin-right: 11px; color: var(--meta); }
  .brief-head { margin: 26px 0 4px; }
  .mosaic-foot { margin-top: 18px; padding-top: 18px; padding-bottom: 8px; }
}

/* --- 4. Rows rise into place ---------------------------------------------- */
/* `html.pt24-reveal` is written by pt24.js, and only after it has an
   IntersectionObserver to hand and the reader has not asked for reduced
   motion. A page whose script never arrives, the ordinary 4G case this paper
   plans for, draws every row in place. Inside the phone query, so a window
   widened past 820px shows everything at once. The gesture is the page
   transition's, six pixels up and in, and the 200ms is the package's "fast". */
@media (max-width: 820px) and (prefers-reduced-motion: no-preference) {
  html.pt24-reveal .day-mosaic .day-tile,
  html.pt24-reveal .brief-tile {
    opacity: 0; transform: translateY(6px);
    transition: opacity 200ms ease-out, transform 200ms ease-out;
  }
  html.pt24-reveal .day-mosaic .day-tile.is-in,
  html.pt24-reveal .brief-tile.is-in { opacity: 1; transform: none; }
}

/* == widgets == */
/* ================================================================== */
/* The weather under the wordmark, and the WhatsApp button.            */
/*                                                                      */
/* Two pieces of furniture the ERP switches on and a reader can switch  */
/* off (You, ON THE PAGE). The weather is not a widget in the usual    */
/* sense and that is the design: it is the dateline's own second fact, */
/* the sky, made the reader's. It sits where the sky already sat, in   */
/* --ui at the dateline's size, and the only things that mark it as a  */
/* control are the cursor, the caret and the place name warming to ink */
/* under the pointer. Nothing floats, nothing is added to the page.    */
/*                                                                      */
/* The WhatsApp button is the first fixed control this site has had    */
/* and it is disciplined for it: a pill at the top of a page, a circle  */
/* once the reader is reading, off the edge while they scroll down,    */
/* back when they stop. Ink on paper like the toast and the selected   */
/* chip, not the network's green: the glyph says WhatsApp, the paper's  */
/* palette says whose page it is.                                       */
/* ================================================================== */

/* The dateline control. Inherits the dateline's font and colour; the
   current reading is in ink and 600 so the number reads first. */
.wx {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  margin: 0; padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; color: inherit; text-align: left; white-space: nowrap;
}
.wx-now {
  display: inline-flex; align-items: center; gap: 5px;
  color: var(--ink); font-weight: 600;
  transition: opacity .22s ease;
}
.wx-now svg { width: 15px; height: 15px; flex: none; color: var(--ink-60); }
.wx-place { color: inherit; transition: opacity .22s ease, color .15s ease; }
.wx-place:empty { display: none; }
.wx .caret { display: inline-flex; font-size: 9px; color: var(--ink-30); }
.wx:hover .wx-place, .wx:focus-visible .wx-place { color: var(--ink); }
/* One place fades out and the next fades in: a swap, not a slide, because
   a dateline does not move. */
.wx.is-swapping .wx-now, .wx.is-swapping .wx-place { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .wx-now, .wx-place { transition: none; }
}
/* In the phone's strip the reading takes the strip's own voice and the
   place steps back, as the AQI label does beside its number. */
@media (max-width: 820px) {
  .mservice > .wx { flex: none; font: 600 11.5px var(--sans); color: var(--ink-45); }
  .mservice > .wx .wx-now { color: var(--ink); }
  .mservice > .wx svg { width: 13px; height: 13px; }
}

/* The sheet. Everything structural is the edition picker's; these are the
   three things it has that the picker does not: a locate row, a results
   list and a foot with a sentence in it. The panel's own class names the
   dialog for these rules and tightens the head, which has no lockup here. */
.wx-panel .cities-head { margin-bottom: 16px; }
.wx-locate-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 14px; margin-bottom: 14px;
}
.wx-locate-row .chip-more svg { font-size: 12px; }
.wx-note { font-size: 11.5px; color: var(--ink-45); }
.wx-note.is-error { color: var(--red-type); }
.wx-results {
  list-style: none; margin: -6px 0 16px; padding: 0;
  border: 1px solid var(--rule); background: var(--card);
}
.wx-results[hidden] { display: none; }
.wx-results li + li { border-top: 1px solid var(--rule); }
.wx-results button {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  width: 100%; padding: 10px 12px; margin: 0; border: 0; background: none;
  font: 500 13px var(--sans); color: var(--ink); text-align: left; cursor: pointer;
}
.wx-results button:hover { background: var(--wash-warm); }
.wx-results .sub { font-size: 11.5px; color: var(--ink-45); white-space: nowrap; }
.wx-results .wx-empty { padding: 10px 12px; margin: 0; font-size: 12px; color: var(--ink-45); }
.wx-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; min-height: 34px; }
.wx-chips .wx-empty { font-size: 12px; color: var(--ink-45); margin: 4px 0 0; }
.wx-chip { padding-right: 8px; cursor: default; }
.wx-chip .tnum { color: var(--ink-45); }
.wx-chip.is-here { border-style: dashed; }
.wx-remove {
  display: grid; place-items: center; width: 20px; height: 20px; margin-left: 2px;
  padding: 0; border: 0; border-radius: 100px; background: none;
  color: var(--ink-45); cursor: pointer; font-size: 11px;
}
.wx-remove:hover { background: var(--wash); color: var(--ink); }
.wx-foot { justify-content: space-between; }
.wx-foot .chip-more { flex: none; }

/* The WhatsApp button. Above the page and the sticky chrome (the phone's top
   bar is 44, the masthead 50), below the scrim at 70, so an open sheet dims
   it with everything else. */
.wa {
  position: fixed; right: 22px; bottom: 22px; z-index: 60;
  transition: transform .28s ease, opacity .28s ease;
}
.wa[hidden] { display: none; }
.wa-link {
  display: inline-flex; align-items: center; gap: 10px; height: 48px;
  padding: 0 18px 0 13px; border-radius: 100px;
  background: var(--ink); color: var(--paper); text-decoration: none;
  /* A hairline of paper under the shadow: invisible on the page's cream,
     and the edge the circle needs once it is over the footer's ink. */
  box-shadow: 0 0 0 1.5px var(--paper), 0 6px 22px rgba(var(--ink-rgb), .22);
  transition: padding .28s ease;
}
.wa-link:hover { background: #000; }
.wa-link:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.wa-glyph { width: 22px; height: 22px; flex: none; }
.wa-label {
  font: 600 13px var(--ui); white-space: nowrap; overflow: hidden;
  max-width: 260px; transition: max-width .28s ease, opacity .2s ease;
}
/* Reading: the label folds away and the pill is a 48px circle. */
.wa.is-compact .wa-link { padding: 0 13px; }
.wa.is-compact .wa-label { max-width: 0; opacity: 0; margin-left: -10px; }
/* Scrolling down: gone past the edge, and not tappable while it is. */
.wa.is-away { transform: translateX(calc(100% + 26px)); opacity: 0; pointer-events: none; }
/* The cross that hides it. On a pointer device it shows when the pointer is
   near; on a touch device there is no near, so it is always drawn. */
.wa-hide {
  position: absolute; top: -7px; left: -7px; z-index: 1;
  display: grid; place-items: center; width: 22px; height: 22px;
  padding: 0; border: 1px solid var(--rule-strong); border-radius: 100px;
  background: var(--paper); color: var(--ink-45); cursor: pointer; font-size: 10px;
  opacity: 0; transition: opacity .15s ease;
}
.wa:hover .wa-hide, .wa:focus-within .wa-hide, .wa-hide:focus-visible { opacity: 1; }
.wa-hide:hover { color: var(--ink); border-color: var(--ink); }
@media (hover: none) and (pointer: coarse) { .wa-hide { opacity: 1; } }
@media (max-width: 820px) {
  .wa {
    right: calc(var(--gutter) + var(--safe-right, 0px));
    bottom: calc(var(--tabbar-h) + var(--safe-bottom) + 14px);
  }
  .wa-link { height: 46px; }
}
@media (prefers-reduced-motion: reduce) {
  .wa, .wa-link, .wa-label, .wa-hide { transition: none; }
}
/* == end widgets == */

/* == editorial desk == */
/* ==================================================================== */
/* The desk's three kinds on the page: the editorial card and the        */
/* exclusive band above the river, the kind label on a tile, the head   */
/* of a kind river, the notes under a desk story. Gold is opinion (the  */
/* editorial, a reader's account), red is the news the desk broke (the  */
/* exclusive); neither is re-darkened, both are the package's own.      */
/* ==================================================================== */
.kind-label {
  display: inline-block; font: 700 10.5px var(--sans); letter-spacing: calc(.09em * var(--track));
  text-transform: uppercase; padding: 2px 6px; border-radius: 2px; line-height: 1.5;
  color: var(--ink); background: var(--wash); white-space: nowrap; vertical-align: middle;
}
/* Red is a ground here and says what its type is, as every red ground on
   the sheet must (tests/test_accent_contrast.py); gold takes ink, the one
   pairing the package rates it for. */
.kind-label.is-red  { background: var(--red); color: var(--knockout); }
.kind-label.is-gold { background: var(--gold); color: var(--ink); }
.kind-mark {
  display: inline-block; font: 700 10.5px var(--sans); letter-spacing: calc(.08em * var(--track));
  color: var(--meta); border: 1px solid var(--rule-heavy); border-radius: 2px; padding: 1px 6px;
  text-transform: uppercase; vertical-align: middle; cursor: help;
}
.kind-mark.is-gold { color: var(--gold-type); border-color: var(--gold); }
.first-on { font: 400 12px var(--sans); color: var(--meta); }
.kind-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 10px; }
.kind-row .kicker { font: 700 12px var(--sans); letter-spacing: calc(.06em * var(--track)); color: var(--red-type); text-transform: uppercase; }
.day-kicker .kind-label { font-size: 9.5px; padding: 1px 5px; }

/* The editorial's card: one column wide, a gold rule above, the face. */
.editorial-card {
  position: relative; margin: 0 0 28px; padding: 18px 0 16px; border-top: 3px solid var(--gold);
  border-bottom: 1px solid var(--rule);
}
.editorial-kicker, .editorial-author { display: flex; align-items: center; gap: 10px; }
.editorial-kicker { margin-bottom: 12px; font: 400 12px var(--sans); color: var(--meta); }
.editorial-kicker .fill, .editorial-author .fill, .exclusive-head .fill, .kind-head .fill { flex: 1; }
.editorial-link { display: block; color: inherit; text-decoration: none; }
.editorial-link:hover .editorial-headline { color: var(--gold-type); text-decoration: none; }
.editorial-headline { font: 500 30px/1.16 var(--serif); margin: 0 0 10px; text-wrap: balance; max-width: 30ch; letter-spacing: 0; }
.editorial-headline:lang(hi), .editorial-headline:lang(mr) { line-height: 1.28; }
.editorial-headline:lang(kn) { line-height: 1.4; }
.editorial-standfirst { font: 400 16px/1.5 var(--sans); color: var(--ink-70); margin: 0 0 14px; max-width: 62ch; }
.editorial-author { font: 400 13.5px var(--sans); color: var(--ink); }
.editorial-author .name { font-weight: 600; }
.editorial-author .title { color: var(--meta); }
.editorial-author .all, .exclusive-head .all, .desk-note a { text-decoration: underline; text-underline-offset: 2px; color: inherit; }
.editorial-face { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; background: var(--wash); filter: grayscale(1); }
.desk-note.is-author .editorial-face { width: 56px; height: 56px; }

/* The exclusives' band: a red rule, a row of cards that scrolls on a phone. */
.exclusive-band { margin: 0 0 28px; padding: 14px 0 6px; border-top: 3px solid var(--red); border-bottom: 1px solid var(--rule); }
.exclusive-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; font: 400 13px var(--sans); color: var(--meta); }
.exclusive-head h2 { margin: 0; }
.exclusive-row {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(240px, 1fr); gap: 20px;
  overflow-x: auto; padding-bottom: 10px; scroll-snap-type: x proximity;
}
.exclusive-card { display: flex; flex-direction: column; min-width: 0; color: inherit; text-decoration: none; scroll-snap-align: start; }
.exclusive-shot { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; object-position: center top; background: var(--wash); border-radius: 2px; margin-bottom: 10px; cursor: zoom-in; }
.exclusive-kicker { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font: 500 11.5px var(--sans); color: var(--meta); text-transform: uppercase; letter-spacing: calc(.05em * var(--track)); }
.exclusive-kicker .kind-label { text-transform: uppercase; }
.exclusive-headline { font: 500 19px/1.2 var(--serif); margin: 0 0 6px; text-wrap: pretty; letter-spacing: 0; }
.exclusive-headline:lang(hi), .exclusive-headline:lang(mr) { line-height: 1.3; }
.exclusive-standfirst { font: 400 13.5px/1.5 var(--sans); color: var(--ink-70); margin: 0; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.exclusive-card:hover .exclusive-headline { color: var(--red-type); }

/* The head of a kind river. */
.kind-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 16px; margin: 0 0 24px; padding-bottom: 16px; border-bottom: 1px solid var(--ink); }
.kind-head .kind-kicker { flex-basis: 100%; font: 700 11px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--meta); display: flex; align-items: center; gap: 8px; }
.kind-head h1 { font: 500 34px/1.12 var(--serif); margin: 0; letter-spacing: 0; }
.kind-head h1:lang(hi), .kind-head h1:lang(mr) { line-height: 1.26; }
.kind-head .standfirst, .kind-head .kind-author { flex-basis: 100%; margin: 0; max-width: 66ch; }
.kind-head .kind-author { display: flex; gap: 14px; align-items: flex-start; font: 400 14px/1.5 var(--sans); }
.kind-head .kind-author .name { font-weight: 600; }
.kind-head .kind-author .note { margin: 4px 0 0; color: var(--meta); font-size: 13px; }
.kind-face { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; background: var(--wash); filter: grayscale(1); flex: none; }
.kind-head .kind-actions { flex-basis: 100%; }
.kind-head .note { font: 400 13px var(--sans); color: var(--meta); }
.kind-editorial .kind-head { border-bottom-color: var(--gold); }
.kind-exclusive .kind-head { border-bottom-color: var(--red); }
/* The desk's four packages (/khabren/<slug>/): the rule under the head is gold,
   the paper's choosing rather than its own reporting (red) or opinion (ink),
   and the row of the four sits between the head and the stories. */
.kind-highlight .kind-head { border-bottom-color: var(--gold); margin-bottom: 14px; }
.highlight-row { margin: 0 0 22px; }
.kind-foot { margin: 28px 0 0; padding-top: 16px; border-top: 1px solid var(--rule); font: 400 13px/1.55 var(--sans); color: var(--ink-70); max-width: 72ch; }
.kind-foot p { margin: 0 0 8px; }

/* Under a desk story: the reply, the outcome, the author, the disclaimer. */
.desk-note { margin: 26px 0 0; padding: 16px 18px; border: 1px solid var(--rule-strong); border-radius: 2px; background: var(--card); font: 400 14.5px/1.55 var(--sans); }
.desk-note .label { font: 700 10.5px var(--sans); letter-spacing: calc(.1em * var(--track)); color: var(--meta); margin-bottom: 8px; }
.desk-note p { margin: 0 0 8px; }
.desk-note p:last-child { margin-bottom: 0; }
.desk-note .muted { color: var(--meta); font-size: 13px; }
.desk-note.is-reply { border-left: 3px solid var(--red); }
.desk-note.is-outcome { border-left: 3px solid var(--gold); }
.desk-note.is-author { display: flex; gap: 16px; align-items: flex-start; border-left: 3px solid var(--gold); }
.desk-note.is-author .name { font-weight: 600; margin-bottom: 4px; }
.desk-note.is-disclaimer { background: var(--wash); }

/* The Awaaz door: the contact form's shape with grouped fields and checks. */
.awaaz-page { padding-top: 26px; }
.awaaz-form { gap: 26px; }
.awaaz-consents { padding-top: 6px; border-top: 1px solid var(--rule); }
.kind-river { padding-top: 22px; }
.voice-disclaimer { margin: 0 0 8px; }
.voice-officer { margin: 0; color: var(--meta); }
.awaaz-notice p { font: 400 14.5px/1.55 var(--sans); color: var(--ink-70); max-width: 66ch; margin: 0 0 10px; }
.awaaz-notice .standfirst { color: var(--ink); }
.awaaz-dpdp { margin: 12px 0 22px; max-width: 66ch; }
.awaaz-dpdp summary { font: 600 13.5px var(--sans); cursor: pointer; padding: 8px 0; color: var(--ink); }
.awaaz-dpdp p { font-size: 13.5px; }
.awaaz-group { border: 0; padding: 0; margin: 0 0 8px; display: grid; gap: 16px; min-width: 0; }
.awaaz-group legend { font: 700 11.5px var(--sans); letter-spacing: calc(.09em * var(--track)); color: var(--red-type); padding: 0; margin-bottom: 10px; }
.contact-field.is-check label { display: flex; gap: 10px; align-items: flex-start; font: 400 14px/1.5 var(--sans); letter-spacing: 0; text-transform: none; color: var(--ink); }
.contact-field.is-check input { width: 20px; height: 20px; min-height: 0; flex: none; margin-top: 2px; accent-color: var(--red); }
/* Django 5 draws a RadioSelect as nested divs, one per choice, so the
   choice rows are the field's inner divs and the controls are typed. */
.contact-field > div { display: grid; gap: 8px; }
.contact-field > div > div > label { display: flex; gap: 10px; align-items: center; font: 400 14px var(--sans); letter-spacing: 0; text-transform: none; color: var(--ink); }
.contact-field input[type="radio"], .contact-field input[type="checkbox"] { width: 20px; height: 20px; min-height: 0; flex: none; padding: 0; accent-color: var(--red); }
.rail-for-you .rail-note { font: 400 12.5px/1.5 var(--sans); color: var(--meta); margin: -6px 0 12px; }

@media (max-width: 820px) {
  .editorial-headline { font-size: 24px; }
  /* Inside the column, not bled to the screen edge: the band's own rule
     and the river below it share one left edge, and the row scrolls within
     it with the last card peeking to say so. */
  .exclusive-row { grid-auto-columns: 82%; gap: 14px; }
  .kind-head h1 { font-size: 26px; }
}
/* == end editorial desk == */

/* == motion == */
/* ==================================================================== */
/* Motion, September 2026: sections rise into place as they scroll in,  */
/* cards lift a hair on hover, and the two desk rules draw themselves.  */
/* All of it inside `prefers-reduced-motion: no-preference`, all of it  */
/* the package's 200ms, and none of it changes a colour or a face: the  */
/* look is the client's, the movement is the page's manners. The class  */
/* `.reveal` is held at zero opacity only under `html.pt24-motion`,     */
/* which pt24.js writes once it holds an observer, so a page without    */
/* the script draws everything in place, as the river reveal does.      */
/* ==================================================================== */
@media (prefers-reduced-motion: no-preference) {
  html.pt24-motion .reveal {
    opacity: 0; transform: translateY(10px);
    transition: opacity 260ms ease-out, transform 260ms cubic-bezier(.2, 0, .2, 1);
  }
  html.pt24-motion .reveal.is-in { opacity: 1; transform: none; }

  .day-tile .day-link, .exclusive-card, .rail-item, .brief-tile, .editorial-link {
    transition: transform 200ms ease-out, box-shadow 200ms ease-out, color 160ms ease-out;
  }
  .exclusive-card:hover, .day-tile .day-link:hover { transform: translateY(-2px); }
  .day-shot img, .exclusive-shot { transition: transform 420ms cubic-bezier(.2, 0, .2, 1), opacity 140ms ease-out; }
  .day-tile .day-link:hover .day-shot img, .exclusive-card:hover .exclusive-shot { transform: scale(1.025); }
  .day-shot, .exclusive-shot { overflow: hidden; }

  .editorial-card::before, .exclusive-band::before {
    content: ""; position: absolute; left: 0; top: -3px; height: 3px; width: 0;
    transition: width 700ms cubic-bezier(.2, 0, .2, 1);
  }
  /* The rule is drawn by the pseudo-element, in the accent as a ground and
     never as type; the border it replaces goes clear so the two do not
     print twice. */
  .editorial-card::before { background: var(--gold); }
  .exclusive-band::before { background: var(--red); }
  .editorial-card { border-top-color: transparent; }
  .exclusive-band { position: relative; border-top-color: transparent; }
  .editorial-card.is-in::before, .exclusive-band.is-in::before { width: 100%; }
  html:not(.pt24-motion) .editorial-card::before, html:not(.pt24-motion) .exclusive-band::before { width: 100%; }

  .kind-label.is-red { animation: pt24-kind-in 600ms ease-out both; }
  @keyframes pt24-kind-in { from { opacity: 0; transform: translateX(-4px); } to { opacity: 1; transform: none; } }

  .btn-ink, .btn-line { transition: background-color 160ms ease-out, color 160ms ease-out, transform 120ms ease-out; }
  .btn-ink:active, .btn-line:active { transform: translateY(1px); }
}
/* == end motion == */
