/* Reader-first: dense enough to scan a morning's news quickly, quiet
   enough to read. Sidebar ad slots (Milestone 2) go beside .articles. */
:root {
  --bg: #fbfaf8; --fg: #1a1a19; --dim: #6b6b66; --line: #e4e2dd;
  --accent: #1a5f7a; --warn: #8a6d1f; --danger: #9b2c2c; --card: #fff;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16171a; --fg: #e8e6e1; --dim: #9a978f; --line: #2b2d31;
    --accent: #6fb3cc; --warn: #d4b25a; --danger: #e08585; --card: #1d1e22;
  }
}
* { box-sizing: border-box; }
/* Bare links had no colour, so they fell back to the browser default --
   dark blue, effectively invisible on the dark background. Every link
   that is not styled by a more specific rule lands here. */
a { color: var(--accent); }
a:hover { color: var(--fg); }
body {
  margin: 0; background: var(--bg); color: var(--fg);
  font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* iOS Safari inflates text when a block is far wider than the
     viewport -- it measured against the 46rem article column and
     scaled every headline and snippet past the screen edge. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* Nothing may establish a wider page than the phone. Long headlines
   and URLs break rather than push the layout sideways. */
img, video { max-width: 100%; height: auto; }
h1, h2, h3, p, li { overflow-wrap: anywhere; }

/* A backstop, because one overflowing element widens the page for
   EVERY element: text then wraps to the wider page instead of to the
   screen. Catching it here means a future stray width shows up as a
   clipped box rather than as prose running off the side.

   overflow-x MUST live on html alone, not body: overflow on body
   establishes a new scrolling context, and that silently breaks
   `position: sticky` for every descendant in it -- confirmed in both
   WebKit and Chromium, the sticky header scrolled fully out of view
   instead of staying pinned. max-width on body still catches the
   overflow; only the overflow PROPERTY needs to move. */
html { overflow-x: hidden; }
body { max-width: 100%; }

/* Breakpoints are in px on purpose. A rem media query resolves against
   the browser's default font size -- which iOS scales with the user's
   Text Size setting -- so `max-width: 46rem` could fail to match on a
   390px phone whose text is enlarged, leaving the desktop rules in
   force on the one device the query existed to catch. px is measured
   against the viewport and cannot drift. */
header {
  display: flex; align-items: baseline; gap: 1.5rem;
  padding: .9rem 1.2rem; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: var(--bg); z-index: 5;
}
.brand { font-weight: 700; text-decoration: none; color: var(--fg); letter-spacing: -.02em; }
nav { display: flex; gap: 1rem; }
nav a { color: var(--dim); text-decoration: none; font-size: .9rem; }
nav a.on, nav a:hover { color: var(--accent); }
main { max-width: 62rem; margin: 0 auto; padding: 1.2rem; }
/* Article column stays reading-width; the sidebar is the only thing
   that widens the page. Scarce by design: 2-3 slots, never more. */
.layout { display: grid; grid-template-columns: minmax(0,46rem) 1fr;
          gap: 2rem; align-items: start; }
/* The reading column: pagers plus the story list as ONE grid child,
   so .layout keeps the two children its columns describe. min-width:0
   for the same reason .slot has it -- a grid item will not shrink
   below its content, which would defeat the minmax(0,...) floor. */
.storycol { min-width: 0; }
.sidebar { position: sticky; top: 4.5rem; display: flex;
           flex-direction: column; gap: 1rem; }
.slot { border: 1px solid var(--line); border-radius: 6px; padding: .75rem;
        min-height: 5rem; font-size: .8rem; color: var(--dim); }
@media (max-width: 992px) {
  /* minmax(0,46rem) kept a 46rem column on a 390px screen, so the
     story text and images ran off the right edge. */
  .layout { grid-template-columns: minmax(0, 1fr); }
  .sidebar { position: static; flex-direction: row; flex-wrap: wrap;
             max-width: 100%; }
  /* min-width:0 or the 14rem basis becomes a floor: a flex item will
     not shrink below its content, so on a 390px screen the slot stuck
     out past the right edge and widened the PAGE. Text then wrapped to
     that wider page rather than to the screen, which read as headlines
     and snippets running off the side. */
  .slot { flex: 1 1 14rem; min-width: 0; }
}
form.inline { display: inline; margin: 0; }
button.chip { font: inherit; font-size: .8rem; cursor: pointer;
              background: none; }

.bar {
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: .6rem; margin-bottom: 1rem; font-size: .85rem;
  color: var(--dim);
}
.blocked { color: var(--accent); }
.chip {
  display: inline-block; padding: .25rem .6rem; border-radius: 999px;
  border: 1px solid var(--line); color: var(--dim); text-decoration: none;
  font-size: .8rem;
}
.chip.on { border-color: var(--accent); color: var(--accent); }

.articles { list-style: none; margin: 0; padding: 0; }
.articles li {
  position: relative; display: flex; gap: .9rem;
  /* px gutter: see .stories li -- a rem gutter grew with the
     reader's text size and clipped the headline. */
  padding: .95rem 36px .95rem 0;
  border-bottom: 1px solid var(--line);
}
.articles .body { min-width: 0; }
@media (max-width: 544px) {
  .articles li { flex-direction: column; }
}

/* All saved topics live behind a single "Topics" trigger (see
   base.html) rather than a row of inline links: an inline row that
   wraps makes the sticky header permanently taller as more topics are
   saved, and one that scrolls horizontally instead let a tap on it be
   read by iOS Safari as a text-selection drag rather than a click on
   <summary> -- confirmed live, the whole row highlighted for selection
   instead of the panel opening. A single fixed-size trigger avoids
   both: the header's height no longer depends on topic count, and
   there is no scrollable row for a tap to be misread inside.
   Must come BEFORE the phone @media block below: the mobile override
   of .topics-more-panel and this base rule have equal specificity, so
   whichever is LATER in source order wins regardless of which media
   query matches -- the trap that bit nav.sections' predecessor of this
   rule. Defining this first lets the phone block's fixed-overlay
   version win on phones. */
details.topics-more { position: relative; margin-left: auto; }
details.topics-more summary {
  cursor: pointer; list-style: none; color: var(--dim);
  font-size: .85rem; padding: .3rem .6rem; border-radius: 6px;
  /* Mobile Safari shows a text-selection/callout menu on a long-press
     over a button with no explicit -webkit-user-select -- suppress it
     so every tap reads as a tap, not the start of a selection. */
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
details.topics-more summary::-webkit-details-marker { display: none; }
details.topics-more summary:hover,
details.topics-more[open] summary { background: var(--line); color: var(--accent); }
.topics-more-panel {
  position: absolute; top: calc(100% + .5rem); right: 0;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 8px; padding: .5rem; z-index: 10;
  display: flex; flex-direction: column; gap: .1rem;
  min-width: 12rem; max-height: 60vh; overflow-y: auto;
  box-shadow: 0 4px 16px rgba(0,0,0,.15);
}
.topics-more-panel a {
  padding: .35rem .5rem; border-radius: 5px; white-space: nowrap;
  display: flex; justify-content: space-between; gap: .6rem;
}
.topics-more-panel a:hover { background: var(--line); }
.topics-scrim { display: none; }

/* Phones. The header packs a brand, six links, an email address, a
   sign-out button and the topics trigger onto one non-wrapping flex
   line, so on a ~390px screen the last items ran off the right edge --
   wrapping plus a fixed-size trigger (rather than a topic list whose
   width depends on how many are saved) fixes that regardless of
   whether the header stays sticky. */
@media (max-width: 736px) {
  /* Stays sticky (inherits position: sticky from the base header
     rule): the topics trigger needs to stay reachable while scrolling,
     same as on desktop. This does cost more of the screen on a phone
     than a non-sticky header would, an accepted tradeoff. */
  header {
    display: block; padding: .7rem .9rem;
  }
  .brand { display: block; font-size: 1.05rem; margin-bottom: .35rem; }
  header nav {
    display: flex; flex-wrap: nowrap; gap: 1rem;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  header nav::-webkit-scrollbar { display: none; }
  header nav a { white-space: nowrap; }
  /* The address is the least useful thing on a small screen; the
     sign-out button it labels stays. */
  .who { display: none; }
  .signout { position: absolute; top: .6rem; right: .9rem; margin: 0; }
  /* Its own centered row below the nav links, rather than tucked into
     a header corner: the trigger is the only thing on this row now
     that topics no longer show inline, so it reads better centered
     than pinned to an edge. */
  details.topics-more {
    margin: .35rem 0 0; display: flex; justify-content: center;
  }
  /* No rule under the header on a phone. On desktop it separates a
     one-line header from content scrolling under it, but here the
     header is a tall stacked block and the rule landed right under
     the centered Topics trigger, reading as a stray divider rather
     than an edge. The background change already marks the boundary. */
  header { border-bottom: 0; padding-bottom: .15rem; }
  .topics-more-panel {
    position: fixed; top: auto; right: .9rem; left: .9rem;
    bottom: env(safe-area-inset-bottom, 0);
    max-height: 50vh;
  }
  details.topics-more[open] .topics-scrim {
    display: block; position: fixed; inset: 0;
    background: rgba(0,0,0,.35); z-index: 9;
  }
  main { padding: .2rem .9rem .9rem; max-width: 100%; }
  /* The lead image is sized for a desktop column. */
  .lead-story:not(.has-siblings) .lead-img { max-width: 100%; }
  .lead-img, .thumb { width: 100%; height: auto; max-height: 220px;
                      object-fit: cover; }
}
/* Read articles need to be obviously read. Dimming the headline alone
   was too subtle to notice against an already-light-on-dark page. */
.articles li.read { opacity: .55; }
.articles li.read h2 a { color: var(--dim); font-weight: 400; }
.articles li.read h2 a::after {
  content: " ✓"; color: var(--accent); font-size: .8em;
}
.meta { font-size: .78rem; color: var(--dim); display: flex; gap: .5rem; flex-wrap: wrap; }
.src { font-weight: 600; }
/* Repeated source on consecutive rows: present but recessive. */
.src.dim { font-weight: 400; opacity: .45; }
.tag { padding: 0 .35rem; border-radius: 3px; font-size: .7rem; }
.tag.unknown { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
.tag.assumed { background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); }
h2 { margin: .2rem 0 .25rem; font-size: 1.02rem; line-height: 1.35; font-weight: 600; }
h2 a { color: var(--fg); text-decoration: none; }
h2 a:hover { color: var(--accent); }
.snip { margin: 0; color: var(--dim); font-size: .88rem; }
.save {
  position: absolute; top: .9rem; right: 0; background: none; border: 0;
  color: var(--dim); font-size: 1.1rem; cursor: pointer; line-height: 1;
}
.save:hover { color: var(--warn); }
.empty { color: var(--dim); padding: 2rem 0; }

.add { display: flex; gap: .5rem; margin-bottom: .4rem; }
.add input {
  flex: 1; padding: .6rem .7rem; border: 1px solid var(--line);
  border-radius: 6px; background: var(--card); color: var(--fg); font-size: .95rem;
}
.add button {
  padding: .6rem 1rem; border: 0; border-radius: 6px;
  background: var(--accent); color: #fff; font-weight: 600; cursor: pointer;
}
.hint { font-size: .8rem; color: var(--dim); margin: 0 0 1rem; }
.flash { padding: .6rem .8rem; border-radius: 6px; font-size: .88rem; }
.flash.err { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); }
.flash.ok { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }

.feeds { width: 100%; border-collapse: collapse; font-size: .9rem; }
.feeds tr { border-bottom: 1px solid var(--line); }
.feeds tr.muted { opacity: .5; }
.feeds td { padding: .7rem .4rem; vertical-align: top; }
.ftitle { color: var(--fg); text-decoration: none; font-weight: 600; }
.ftitle:hover { color: var(--accent); }
.furl { font-size: .75rem; color: var(--dim); word-break: break-all; }
.ferr { font-size: .75rem; color: var(--danger); margin-top: .2rem; }
.num, .when { color: var(--dim); white-space: nowrap; font-size: .8rem; }
.acts { display: flex; gap: .6rem; justify-content: flex-end; }
.acts form { margin: 0; }
/* prefer/avoid, which source should front a story */
.srcpref { display: flex; gap: .6rem; white-space: nowrap; }
.srcpref form { margin: 0; }
.srcpref .link { font-size: .78rem; color: var(--dim); }
.srcpref .link.on { color: var(--accent); font-weight: 600; }
button.link {
  background: none; border: 0; color: var(--dim); cursor: pointer;
  font-size: .8rem; padding: 0; text-decoration: underline;
}
button.link:hover { color: var(--accent); }
button.link.danger:hover { color: var(--danger); }

/* Discovery: sample headlines prove the feed was actually read. */
.samples { font-size: .75rem; color: var(--dim); margin-top: .25rem;
           font-style: italic; }

.missed { border-top: 1px solid var(--line); padding-top: .8rem;
         margin-top: 1.2rem; }

/* Top stories: image + text, coverage count as the importance cue. */
.stories { list-style: none; margin: 0; padding: 0; }
.stories li {
  position: relative; display: flex; gap: .9rem;
  /* The right gutter reserves room for the absolutely-positioned save
     star. In rem it grew with the reader's text-size setting while the
     screen did not, so on a phone with enlarged text the headline was
     pushed under the gutter and clipped mid-word. The star is a fixed
     size, so its gutter is fixed too. */
  padding: 1rem 36px 1rem 0; border-bottom: 1px solid var(--line);
}
.stories li.read { opacity: .55; }
.stories li.read h2 a { color: var(--dim); font-weight: 400; }
.stories li.read h2 a::after {
  content: " ✓"; color: var(--accent); font-size: .8em;
}
.thumb {
  width: 132px; height: 88px; object-fit: cover; border-radius: 5px;
  flex: 0 0 auto; background: var(--line);
}
.stories .body { min-width: 0; }
/* The lead story wraps its content in .lead-main, not .body, and had
   no rules at all -- so it never got the min-width:0 that stops a flex
   item refusing to shrink below its content. Whether or not it is the
   cause of a report, a flex child holding a headline needs it. */
.stories .lead-main { min-width: 0; flex: 1 1 auto; }
.tag.coverage {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent); font-weight: 600;
}
.also { margin: .3rem 0 0; font-size: .76rem; color: var(--dim); }
.walled { margin: .15rem 0 0; font-size: .74rem; color: var(--warn); }
@media (max-width: 544px) {
  .stories li { flex-direction: column; }
  .thumb { width: 100%; height: 150px; }
}

/* Google News style lead: one prominent story with sibling coverage
   stacked beside it, then compact rows. A page where every story is
   equally loud has no top story. */
/* Two columns only when there is real sibling coverage to fill the
   second one -- otherwise the lead runs full width rather than leaving
   a conspicuous empty gutter. */
.stories li.lead-story { display: block; padding-bottom: 1.4rem; }
.stories li.lead-story.has-siblings {
  display: grid; grid-template-columns: minmax(0,1.5fr) minmax(0,1fr);
  gap: 1.8rem; align-items: start;
}
.lead-story:not(.has-siblings) .lead-img { max-width: 30rem; }
.sib-inline { color: var(--dim); text-decoration: none; }
.sib-inline:hover { color: var(--accent); text-decoration: underline; }
.lead-img {
  width: 100%; aspect-ratio: 16/10; object-fit: cover;
  border-radius: 6px; background: var(--line); margin-bottom: .6rem;
}
.lead-story h2 { font-size: 1.32rem; line-height: 1.28; margin: .25rem 0 .35rem; }
.siblings { list-style: none; margin: 0; padding: 0; }
.siblings li {
  /* Block layout, not flex: the meta line sits ABOVE the headline as a
     caption. Side by side, a long feed title squeezes the headline into
     a five-words-per-line column. */
  display: block; padding: .55rem 0;
  border-bottom: 1px solid var(--line);
}
.siblings li:last-child { border-bottom: 0; }
.siblings .meta {
  display: block; margin-bottom: .1rem;
  font-size: .71rem; color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.siblings .src { font-weight: 600; }
.siblings .when::before { content: "· "; }
.sib-title {
  color: var(--fg); text-decoration: none; font-size: .88rem;
  line-height: 1.35; display: block;
}
.sib-title:hover { color: var(--accent); }
.walled-note { font-size: .74rem; color: var(--warn); padding-top: .5rem; }
details.more { margin-top: .45rem; }
details.more summary {
  cursor: pointer; font-size: .78rem; color: var(--accent);
  list-style: none;
}
details.more summary::-webkit-details-marker { display: none; }
details.more summary:hover { text-decoration: underline; }
.siblings.inline { margin-top: .5rem; padding-left: .85rem;
                   border-left: 2px solid var(--line); }
.siblings.inline li { padding: .45rem 0; }
@media (max-width: 736px) {
  /* Must name .has-siblings: the base rule only makes the lead a grid
     when it HAS siblings, so overriding grid-template-columns on the
     bare selector styled nothing.

     Must be minmax(0,1fr), not bare 1fr. A bare 1fr is minmax(auto,1fr),
     and auto as a MINIMUM means the track will not shrink below its
     content's max-content size. The sibling list's long headlines
     pushed that max-content past the viewport, so the single column
     resolved to a fixed ~431px track regardless of the 346px container
     -- confirmed by rendering the real template: grid-template-columns
     computed to a literal "431.344px" while the <li> itself measured
     346px. minmax(0,1fr) makes 0 the floor instead of auto. */
  .stories li.lead-story.has-siblings {
    grid-template-columns: minmax(0, 1fr); gap: .8rem;
  }
}

/* Video: a play badge over the publisher's own poster frame, linking
   out. We never embed a player -- that would reproduce their content
   and cost them the view. */
.thumbwrap {
  position: relative; flex: 0 0 auto; line-height: 0;
  /* inline-block, and align-self so the wrapper does NOT stretch to the
     flex row's height. As a stretched block it was taller than the
     132x88 image inside it, so the centred play badge floated in empty
     space below the picture. */
  display: inline-block; align-self: flex-start;
}
.lead-wrap { display: block; width: 100%; }
.playbadge {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
  width: 30px; height: 30px; border-radius: 50%;
  background: rgba(0,0,0,.62); color: #fff; font-size: 12px;
  display: flex; align-items: center; justify-content: center;
  padding-left: 2px; pointer-events: none;
}
.playbadge.big { width: 52px; height: 52px; font-size: 20px; }

a.thumbwrap { cursor: pointer; }
a.thumbwrap:hover .thumb, a.thumbwrap:hover .lead-img { opacity: .88; }
.thumb, .lead-img { transition: opacity .12s ease; }

.more-note { font-size: .74rem; color: var(--dim); padding-top: .35rem; }

/* Discovery takes a list of topics, so the input is a textarea. */
.add textarea {
  flex: 1; padding: .6rem .7rem; border: 1px solid var(--line);
  border-radius: 6px; background: var(--card); color: var(--fg);
  font: inherit; font-size: .95rem; resize: vertical; min-height: 2.6rem;
}
.add { align-items: flex-start; }

/* Search + saved topics */
.topiclist { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.chip .count {
  display: inline-block; margin-left: .3rem; padding: 0 .3rem;
  border-radius: 999px; background: var(--accent); color: #fff;
  font-size: .68rem;
}
.savetopic { display: inline-flex; gap: .35rem; align-items: center; }
.savetopic input {
  padding: .2rem .45rem; border: 1px solid var(--line); border-radius: 999px;
  background: var(--card); color: var(--fg); font: inherit; font-size: .8rem;
  width: 11rem;
}

.topicedit { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; }
.topicedit input {
  padding: .25rem .5rem; border: 1px solid var(--line); border-radius: 5px;
  background: var(--card); color: var(--fg); font: inherit; font-size: .82rem;
}

.searchopt {
  display: flex; align-items: center; gap: .35rem; white-space: nowrap;
  font-size: .82rem; color: var(--dim); padding-top: .6rem;
}
.searchopt input { margin: 0; }

/* Progress for long operations (discovery fetches every candidate).
   [hidden] must win: the HTML attribute is only display:none from the
   UA stylesheet, so any author `display` rule overrides it and the
   spinner shows on every page load. */
[hidden] { display: none !important; }

.busy {
  display: flex; align-items: center; gap: .6rem;
  padding: .7rem .85rem; margin: .2rem 0 1rem;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--card); color: var(--dim); font-size: .86rem;
}
.spinner {
  width: 15px; height: 15px; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--accent);
  animation: nfmspin .8s linear infinite;
}
@keyframes nfmspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2.4s; }
}
button:disabled { opacity: .6; cursor: progress; }

/* Discovery adds happen in place: a run takes ~35s, so navigating away
   after one add throws the rest of the results out. */
.addstate { font-size: .8rem; }
.addstate.ok { color: var(--accent); }
.addstate.err { color: var(--danger); }
.addall { margin: .2rem 0 .8rem; }
[aria-busy="true"], .htmx-request { opacity: .55; cursor: progress; }

/* Sign in / register */
.authbox { max-width: 22rem; margin: 3rem auto; }
.authbox h1 { font-size: 1.3rem; margin: 0 0 1rem; }
.authbox label { display: block; margin-bottom: .9rem; font-size: .85rem;
                 color: var(--dim); }
.authbox input {
  display: block; width: 100%; margin-top: .25rem; padding: .55rem .65rem;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--card); color: var(--fg); font: inherit;
}
.authbox button[type=submit] {
  width: 100%; padding: .6rem; border: 0; border-radius: 6px;
  background: var(--accent); color: #fff; font-weight: 600; cursor: pointer;
}
/* Honeypot field on /register: off-screen rather than display:none, so
   a bot that only skips elements it can detect as hidden (checking
   `hidden` or visibility, not layout position) still fills it in. A
   sighted user never sees it; a screen reader is kept off it too via
   aria-hidden and tabindex="-1" in the markup. */
.botcheck-field {
  position: absolute !important; left: -9999px !important;
  width: 1px; height: 1px; overflow: hidden;
}
/* Sign out reads as a control, not a footnote: it was a small grey
   underlined string that looked like body text. */
.signout { display: flex; align-items: center; gap: .6rem; margin-left: auto; }
.who { font-size: .78rem; color: var(--dim); }
.signout button.link {
  text-decoration: none; font-size: .78rem;
  padding: .22rem .6rem; border: 1px solid var(--line); border-radius: 999px;
  color: var(--dim);
}
.signout button.link:hover { color: var(--accent); border-color: var(--accent); }

/* Settings */
.settings { max-width: 34rem; margin: 2rem auto; }
.settings h1 { font-size: 1.3rem; margin: 0 0 1rem; }
.settings h2 { font-size: .95rem; margin: 1.6rem 0 .4rem; }
.settings label { display: block; margin-bottom: .8rem; font-size: .85rem;
                  color: var(--dim); }
.settings input {
  display: block; width: 100%; margin-top: .25rem; padding: .5rem .6rem;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--card); color: var(--fg); font: inherit; font-size: .9rem;
}
.settings button[type=submit] {
  margin-top: .6rem; padding: .55rem 1.1rem; border: 0; border-radius: 6px;
  background: var(--accent); color: #fff; font-weight: 600; cursor: pointer;
}
.testmail { margin-top: 1.4rem; padding-top: 1rem;
            border-top: 1px solid var(--line); }

/* Signed-out explainer. Quiet: informative, not a sales pitch. */
.pitch {
  max-width: 26rem; margin: 1.5rem auto 0; text-align: center;
  font-size: .84rem; line-height: 1.5; color: var(--dim);
}

/* Operator status table */
table.status { width: 100%; border-collapse: collapse; font-size: .85rem;
               margin-bottom: 1rem; }
table.status td { padding: .35rem .2rem; border-bottom: 1px solid var(--line); }
/* Only the two-column failing-feeds table wants its first column wide;
   the wider accounts/bot-review tables would be badly distorted by it. */
table.status:not(.accounts):not(.bot-reviews) td:first-child {
  color: var(--dim); width: 55%;
}
table.status .warn { color: var(--warn); }
table.status tr.warn-row td { color: var(--warn); }
table.status th {
  text-align: left; padding: .3rem .2rem; border-bottom: 2px solid var(--line);
  font-size: .75rem; color: var(--dim); font-weight: 600;
}
table.accounts td, table.bot-reviews td { font-size: .8rem; }
.risk-none { color: var(--dim); }
.risk-low { color: var(--dim); }
.risk-medium { color: var(--warn); font-weight: 600; }
.risk-high { color: var(--danger); font-weight: 700; }

/* Paging through the story list. Centered as one compact group rather
   than space-between: spread across the full column width, "previous
   25" / "page 4" / "next 25" read as three unrelated fragments and
   wrapped onto separate lines in a narrow column (the sidebar leaves
   the story column well under its old space-between layout's comfort
   width). Grouped and centered, it reads as one control at any width
   and wraps as a unit instead of splitting mid-sentence. */
.pager {
  display: flex; align-items: center; justify-content: center;
  gap: .6rem; flex-wrap: wrap; margin: 1.5rem 0; padding: 1rem 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
/* The top copy has nothing above it to be separated from, so it drops
   the upper border and margin; the lower ones are trimmed too, since
   its own bottom rule is already the divider before the first story. */
.pager.pager-top {
  margin: 0 0 .5rem; border-top: 0; padding: 0 0 .75rem;
}
.pagenum { font-size: .8rem; color: var(--dim); padding: 0 .3rem; }

/* "New stories available" after the app returns from the background.
   An offer, not an action: a silent reload would lose scroll position
   and could swap the page out mid-read.

   position: fixed, NOT sticky -- the layout's overflow-x containers
   silently break sticky descendants (see the note above), and this
   element is appended to <body> where it would inherit that trap.
   env(safe-area-inset-bottom) keeps it clear of the iOS home bar. */
.stale-bar {
  position: fixed;
  left: 50%; transform: translateX(-50%);
  bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
  z-index: 20;
  padding: .6rem 1.1rem;
  border: none; border-radius: 999px;
  background: var(--accent); color: #fff;
  font: inherit; font-weight: 600; font-size: .9rem;
  box-shadow: 0 2px 10px rgb(0 0 0 / .25);
  cursor: pointer;
  max-width: calc(100vw - 2rem);
}
.stale-bar:hover { filter: brightness(1.08); }

@media (prefers-reduced-motion: no-preference) {
  .stale-bar { animation: stale-in .18s ease-out; }
  @keyframes stale-in {
    from { opacity: 0; transform: translateX(-50%) translateY(.5rem); }
    to   { opacity: 1; transform: translateX(-50%) translateY(0); }
  }
}

/* An ongoing situation: a playoff series, a war, a named storm. Entries
   that share one are placed consecutively by queries._group_arcs and
   this labels the first of the run. They are NOT merged -- one entry
   for a war is one entry in which the reader can see no event at all.
   Quiet by design: following a story does not make it louder than the
   day's biggest news. */
.arc-heading {
  margin: 1.2rem 0 -.4rem; font-size: .75rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--dim);
}
.arc-name { border-bottom: 2px solid var(--accent); padding-bottom: .15rem; }
/* A left rule ties the run together without boxing it, which would
   fight the borders already drawn between entries. */
.stories li.in-arc { border-left: 2px solid var(--line); padding-left: .9rem; }
