/* ==================================================================
   Airplay Live - /speech  (talk-break recognition)
   Loads on top of tokens.css + fd_system.css + overview.css; uses the
   --fd-* / --ap-* tokens so it themes in both dark and light.
   Namespace: .sp-*
   Character: the same calm document as /overview and /controls. The
   transcript is the content, so type leads and chrome stays thin.
   ================================================================== */

.sp-hero { padding-bottom: 18px; }

/* The tagline row carries the beta pill at its right edge: the page is a
   working feature, not a finished one, and that belongs next to the promise
   rather than buried in the prose. Same blue as .ov-link so "in progress"
   reads as one signal across the site. */
.sp-hero__tag {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
}
.sp-statentity__desc { color: var(--ap-text-tertiary); font-size: .9rem; margin: .35rem 0 0; }
.sp-beta {
  flex: 0 0 auto;
  padding: 3px 9px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--fd-accent) 45%, transparent);
  background: color-mix(in srgb, var(--fd-accent) 12%, transparent);
  color: var(--fd-accent);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px; font-weight: 600; letter-spacing: .18em;
  text-transform: uppercase; line-height: 1.4;
}

/* ---- KPI row ----------------------------------------------------- */
.sp-kpi {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
  margin: 2px 0 0;
}

/* ---- prose ------------------------------------------------------- */
.sp-lede { color: var(--fd-ink); margin: 0 0 14px; }
.sp-lede--related { margin-top: 6px; }
.sp-lede--back { margin-top: 14px; }
/* deck-style sub-line: smaller than a normal lede, quoted by a faint left rule (matches overview/signal.php .its-vmail-line) */
.sp-vmail-line {
  font-size: 13.5px; line-height: 1.55; color: var(--fd-ink-2); max-width: 52ch;
  padding-left: 12px; border-left: 1px solid var(--fd-hair);
}
.sp-why { padding-top: 24px; }

/* ---- Tune-out gauge (demo gadget) --------------------------------
   Horizontal bar, center tick = station baseline. Track width and the
   max distance either fill can reach from center are both half of the
   original layout. Only one of --neg (left, red) / --pos (right, green)
   is ever visible; JS swaps which one is showing instantly (no
   left/position transition) so a side change never reads as a cross-fade
   through the middle - only each side's own width animates, reflecting
   a 25s rolling average of the (still noisy, per-break/per-play) rate.

   OLD (kept for reference, not used): a single .sp-gauge__fill div slid
   across center with `transition: width .5s ease, left .5s ease`, which
   made crossing from red to green look like fill bleeding through the
   middle rather than a mechanical swap:
     .sp-gauge__fill {
       position: absolute; top: 0; bottom: 0; left: 50%; width: 0;
       background: var(--ap-red);
       transition: width .5s ease, left .5s ease, background-color .3s ease;
     }
     .sp-gauge__fill.is-good { background: var(--ap-green); }
     .sp-gauge__fill.is-bad { background: var(--ap-red); } */
/* Small, clearly-bounded demo card (bar + play button + caption), instead of
   a bare progress bar floating in the page: same card surface as the rest
   of the site's figures, so it reads as one deliberate component. */
.sp-gauge-row {
  position: relative;
  display: flex; align-items: flex-start; gap: 12px; margin: 26px 0 0;
  max-width: 360px; padding: 16px 18px 26px;
  background: var(--fd-panel); border: 1px solid var(--fd-hair); border-radius: 10px;
}
.sp-gauge__clock {
  position: absolute; right: 18px; bottom: 8px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px; letter-spacing: .06em;
  color: var(--ap-text-tertiary);
  margin: 0;
}
.sp-gauge { max-width: 210px; flex: 1 1 auto; }
.sp-gauge__track {
  position: relative;
  height: 14px;
  border-radius: 999px;
  background: var(--fd-panel-2);
  border: 1px solid var(--fd-hair);
  overflow: hidden;
}
.sp-gauge__center {
  position: absolute; top: -2px; bottom: -2px; left: 50%;
  width: 2px; margin-left: -1px;
  background: var(--fd-ink-3);
  z-index: 1;
}
.sp-gauge__fill {
  position: absolute; top: 0; bottom: 0; width: 0;
  transition: width .5s ease;
}
.sp-gauge__fill--neg { right: 50%; background: var(--ap-red); }
.sp-gauge__fill--pos { left: 50%; background: var(--ap-green); }
.sp-gauge__caption {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  margin: 8px 0 0;
  font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ap-text-secondary);
}
.sp-gauge__play {
  flex: none;
  width: 22px; height: 22px;
  margin-top: -3px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--fd-hair);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition: border-color .2s ease;
}
.sp-gauge__play:hover,
.sp-gauge__play[data-state="playing"] {
  border-color: var(--fd-ink-3);
}
.sp-gauge__play-icon { width: 13px; height: 13px; fill: var(--fd-ink); }
.sp-gauge__play-icon.is-tri { margin-left: 1px; }
.sp-gauge__play-icon rect { fill: var(--fd-ink); }
.sp-note {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px; line-height: 1.7; letter-spacing: .02em;
  color: var(--fd-ink-3); margin: 14px 0 0;
}
.sp-note--gdpr { margin-top: 22px; }
.sp-note--tight { margin-top: 0; }
/* easter-egg quote (owner's call, 2026-08-23): same big-quote-mark idiom as
   graphs/index.php's .gr-story, horizontal orientation, transparent-ish hairline
   color rather than full ink so it reads as decoration, not a citation. */
.sp-joke { position: relative; margin: 28px 0 0 10px; text-align: left; }
.sp-joke::before, .sp-joke::after {
  font-family: Georgia, "Times New Roman", serif; font-size: 40px; line-height: 1;
  color: var(--fd-hair-strong); display: inline; vertical-align: -0.18em;
}
.sp-joke::before { content: "\201C"; margin-right: 6px; }
.sp-joke::after  { content: "\201D"; margin-left: 6px; }
.sp-joke__text { font-style: italic; color: var(--fd-ink-2); font-size: 15px; }
.sp-joke__cap { margin-top: 2px; margin-left: 30px; }

/* easter-egg #2 (owner's call, 2026-08-27): collapsed by default so it reads
   as decoration next to the Metallica quip, not a feature. */
.sp-aiconvo { margin: 36px 0 0; text-align: left; }
.sp-aiconvo__summary {
  cursor: pointer; list-style: none; display: inline-block;
  font-style: italic; color: var(--fd-ink-3); font-size: 13px;
}
.sp-aiconvo__summary::-webkit-details-marker { display: none; }
.sp-aiconvo__summary::before { content: "+\00a0"; font-style: normal; }
.sp-aiconvo[open] .sp-aiconvo__summary::before { content: "\2212\00a0"; font-style: normal; }
.sp-aiconvo__summary:hover { color: var(--fd-ink-2); }
.sp-aiconvo__body {
  margin: 10px auto 0 0; padding-left: 12px; border-left: 2px solid var(--fd-hair);
  color: var(--fd-ink-2); font-size: 13.5px; line-height: 1.6; max-width: 62ch;
  text-align: left;
}
.sp-aiconvo__body p { margin: 0 0 10px; }
.sp-aiconvo__body p:last-child { margin-bottom: 0; }
.sp-aiconvo__who { font-weight: 600; color: var(--fd-ink); }
.sp-aiconvo__log { font-family: "IBM Plex Mono", monospace; font-size: 12px; color: var(--fd-ink-3); }
.sp-aiconvo .sp-joke__cap { margin-left: 12px; }
.sp-empty { color: var(--fd-ink-2); }
.sp-divider { border: none; border-top: 1px solid var(--fd-hair); margin: 32px 0; }
.sp-caption {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; color: var(--ap-text-tertiary); line-height: 1.6;
  margin: 10px 0 0; letter-spacing: .08em; text-transform: uppercase;
}

/* Topic Summary curve (2026-09-06): chart card, sp_talk_score_chart_svg()'s
   output (talk/speech_topics.php) styled for this page's --ap-* dark theme
   rather than the report's own :root tokens. */
.sp-curve-card {
  margin-top: 10px; padding: 14px 16px;
  border: 1px solid var(--fd-hair); border-radius: 8px;
  background: rgba(var(--ap-white-rgb), .025);
}
html.light .sp-curve-card { background: rgba(var(--ap-black-rgb), .02); }
.sp-curve-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.sp-curve-head b { font-size: 13px; letter-spacing: .03em; }
.sp-curve-score { font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 20px; font-weight: 600; color: var(--ap-accent); }
.sp-curve-card svg { margin-top: 22px; }

.sp-revisit { margin-top: 40px; padding-top: 40px; border-top: 1px solid var(--fd-hair); }
.sp-revisit__title { margin: 0 0 20px; }
.sp-revisit__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-top: 0; }
.sp-revisit__info { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; }
.sp-revisit__info b { font-size: 13px; letter-spacing: .03em; color: var(--ap-text-primary); }
.sp-revisit__info span { font-size: 11px; line-height: 1.5; color: var(--ap-text-secondary); }
.sp-revisit__nav { display: inline-flex; flex-shrink: 0; border: 1px solid var(--fd-hair-strong); border-radius: 6px; overflow: hidden; }
.sp-revisit__nav button { appearance: none; border: 0; padding: 7px 9px; background: var(--fd-panel-2); color: var(--ap-text-secondary); font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 10px; font-weight: 500; letter-spacing: .06em; cursor: pointer; }
.sp-revisit__nav button + button { border-left: 1px solid var(--fd-hair-strong); }
.sp-revisit__nav button:hover { background: var(--fd-panel); color: var(--ap-text-primary); }
.sp-revisit__nav button:focus-visible { outline: 2px solid var(--ap-text-primary); outline-offset: -3px; }
.sp-revisit__nav button:disabled { color: var(--ap-text-tertiary); cursor: default; }
.sp-revisit__card { padding: 14px 16px; border: 1px solid var(--fd-hair); border-radius: 8px; background: rgba(var(--ap-white-rgb), .025); }
html.light .sp-revisit__card { background: rgba(var(--ap-black-rgb), .02); }
.sp-revisit__panel { margin-top: 10px; }
.sp-revisit__panel svg { margin-top: 8px; }
.sp-revisit__score { margin: 8px 0 0; text-align: right; font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 11px; color: var(--ap-text-secondary); }
.sp-revisit__score b { font-weight: 600; color: var(--ap-text-primary); }

/* ---- topic preview boxes (mirrors navigate.php .nv-signal-*, own sp-
   namespace: these are the Speech app's boxes, not the guide's) ---------- */
.sp-signal-pair { display: flex; gap: 10px; margin: 18px 0 0; }
.sp-signal-pair .sp-signal-box { flex: 1; }
/* Square corners + a thin flat left edge, same channel-strip language as
   events.php's .sp-evcard (2026-08-27): these two preview boxes are the
   same "worst/best held talk break" reading as that page's tune-out cards,
   so they now share its signature instead of a rounded, wider edge. */
.sp-signal-box {
  border-radius: 3px; padding: 11px 14px; font-size: 12px; line-height: 1.65;
}
.sp-signal-box.is-bad {
  background: rgba(var(--ap-red-rgb),.05);
  border: 1px solid rgba(var(--ap-red-rgb),.16);
  border-left: 2px solid rgba(var(--ap-red-rgb),.55);
}
.sp-signal-box.is-good {
  background: rgba(var(--ap-green-rgb),.04);
  border: 1px solid rgba(var(--ap-green-rgb),.14);
  border-left: 2px solid rgba(var(--ap-green-rgb),.5);
}
.sp-signal-title {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-size: 10px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase;
  font-family: "IBM Plex Mono", ui-monospace, monospace; margin: 0 0 5px;
}
.sp-signal-box.is-bad  .sp-signal-title { color: rgba(var(--ap-red-rgb),.82); }
.sp-signal-box.is-good .sp-signal-title { color: rgba(var(--ap-green-rgb),.82); }
.sp-signal-sd { cursor: help; }
/* Aired-at time: deliberately uncolored (owner's call, 2026-08-27) so it
   reads as a plain timestamp, not a second signal competing with the
   red/green topic label it sits beside. */
.sp-signal-time {
  font-weight: 600; letter-spacing: .04em; color: var(--fd-ink-3);
  text-align: right; white-space: nowrap;
}
.sp-signal-body { color: var(--fd-ink-2); margin: 0; font-style: italic; }
.sp-label-short { display: none; }
@media (max-width: 480px) {
  .sp-label-full { display: none; }
  .sp-label-short { display: inline; }
}
@media (max-width: 640px) {
  .sp-signal-pair { flex-direction: column; }
}

/* ---- "Currently monitoring" aside: deliberately quiet, not a full
   .ov-section - indented editorial-note treatment (left rule + smaller
   secondary-gray text) so the reference station reads as a footnote, not
   as content competing with the topic-scoring signals above it. --- */
.sp-monitor {
  margin: 20px 0 0; padding: 18px 0 4px 16px;
  border-left: 2px solid var(--fd-hair);
}
.sp-monitor__lede {
  color: var(--fd-ink-2); font-size: 13.5px; line-height: 1.6;
  margin: 0 0 10px; max-width: 62ch;
}
.sp-monitor__lede:last-of-type { margin-bottom: 0; }
@media (max-width: 640px) {
  .sp-monitor { padding-left: 12px; }
}
/* modifier: used inline after a section h2 (Topic Scoring Methodology),
   where the section itself already supplies the top gap */
.sp-monitor--flush { margin-top: 16px; }

/* ---- "Currently monitoring" PLAY control and the KPI strip
   inside the fold (three columns) --- */

.sp-listen-btn {
  display: inline-flex; align-items: center; gap: 5px;
  height: 23px; padding: 0 9px;
  border: 1px solid var(--fd-hair); border-radius: 6px;
  background: transparent; color: var(--fd-ink-2); cursor: pointer;
  font-family: "IBM Plex Mono", monospace; font-size: 10px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase;
}
.sp-listen-btn:hover { color: var(--fd-ink); border-color: var(--fd-ink-3); }
.sp-listen-btn[data-state="playing"] { color: var(--ap-green); border-color: var(--ap-green); }
.sp-kpi-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 10px 0 4px; }
.sp-kpi-cell { text-align: left; }
.sp-kpi-num {
  font-size: 22px; line-height: 1.1; color: var(--fd-ink);
  font-variant-numeric: tabular-nums;
}
.sp-kpi-lbl {
  margin-top: 3px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--fd-ink-3);
}
.sp-src-link { color: var(--fd-accent); text-decoration: underline; text-underline-offset: 2px; }
@media (max-width: 640px) {
  .sp-kpi-row { grid-template-columns: repeat(3, 1fr); gap: 6px; }
  .sp-kpi-num { font-size: 18px; }
}

/* ---- clock histogram --------------------------------------------- */
.sp-tl svg { display: block; width: 100%; height: auto; }
/* Small breathing room between the Voiceprints note above and the ribbon
   chart below it: the note otherwise sat flush against the chart. */
#ribbon { margin-top: 14px; }

/* ---- end-to-end timeline -----------------------------------------
   Green is reserved sitewide for a positive audience-hold outcome (see
   Break Highlights / Show Ranking); plain talk content uses cyan here
   instead so the two meanings never collide on the same page. Stop set
   moves to a muted blue/slate so it stays visually distinct from talk. */
.sp-tl__bed { fill: var(--fd-hair); opacity: .5; }
.sp-tl__mark { fill: var(--ap-sky); }
.sp-tl__mark--ad { fill: var(--ap-navy-600); }
.sp-tl__mark--news { fill: var(--ap-amber); }
.sp-tl__mark--promo { fill: var(--ap-indigo); }
.sp-tl__tick { stroke: var(--fd-ink-3); stroke-width: 1; opacity: .45; }
.sp-tl__lbl {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px; font-weight: 600; letter-spacing: .08em;
  fill: var(--fd-ink-3);
}

/* ---- transcript --------------------------------------------------- */

.sp-item {
  border-top: 1px solid var(--fd-hair);
  padding: 16px 0 18px;
}
.sp-item:last-child { border-bottom: 1px solid var(--fd-hair); }

/* ---- event card grid (2026-08-23 redesign): a balanced multi-column grid
   instead of a full-width stacked list - the "unbalanced/fragmented" reading
   came from single-column cards whose header row crammed three unrelated
   facts (type, time, tier, SD, excess) onto one line with an absolutely
   positioned OPEN button fighting them for space. Cards now size themselves
   (auto-fill/minmax) so 1, 3, or a dozen cards all sit in a grid that reads
   as even, not a lopsided leftover column. */
.sp-evgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 14px; align-items: stretch;
}
.sp-evgrid--folded { margin-top: 14px; }
/* Break Highlights (WORKLOG 2026-08-24): always exactly 2 cards, side by
   side - a fixed 2-column cap reads more deliberately paired than the
   auto-fill grid above would on a wide viewport (which could stretch a
   lone pair to 3+ implied columns before wrapping). Falls back to 1 column
   under 620px, same breakpoint idiom as the rest of this page's grids. */
.sp-evgrid--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 10px; margin-bottom: 22px; }
/* talk/stats.php's "And one more thing..." section: same paired-card grid,
   but the gap above it should match the other sp-stats sections' caption-to-
   content spacing (1rem), not the tighter 10px this grid uses elsewhere. */
.sp-stats__longest-grid { margin-top: 1rem; }
.sp-entity-breaks { padding-top: 24px; }
.sp-all-breaks-title {
  margin: 0 0 20px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px; font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; color: var(--fd-ink-2);
}
.sp-topic-all__tagmeta {
  flex: none; font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--fd-ink-3); text-align: right;
}
.sp-topic-all__standalone { display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap; margin:0 0 40px; }
.sp-topic-all__standalone .sp-topic-all__tagmeta { text-align:left; }
/* talk/stats.php footline: same meta chip style, standalone (no sort toggle
   beside it), so left-align it same as the topic.php pairing above. */
p.sp-topic-all__tagmeta { text-align:left; margin:14px 0 0; }
.sp-stats__footline { display:flex; align-items:center; justify-content:space-between; gap:16px; flex-wrap:wrap; }
.sp-stats__footline > .sp-topic-all__tagmeta { margin:0; }
.sp-stats__footnav { margin:0 0 0 auto; }
.sp-topic-all__top-divider { margin-top:0; }
.sp-all-breaks-title--scored { display:flex; align-items:center; justify-content:space-between; gap:12px; }
@media (max-width: 620px) {
  .sp-evgrid--pair { grid-template-columns: 1fr; gap: 10px; margin-bottom: 16px; }
  /* Below this breakpoint the pair stacks to one column (single card per
     row), so the cross-row height reserved for OPEN/conclusion above no
     longer has anything to align against - tighten each card instead
     (owner request 2026-08-24: 4 stacked cards read too tall on mobile). */
  .sp-evgrid--pair .sp-evcard { padding: 13px 12px 10px; }
  .sp-evgrid--pair .sp-evcard__open { padding: 3px 8px; font-size: 10px; }
  .sp-evgrid--pair .sp-evcard__row { margin-bottom: 8px; }
  .sp-evgrid--pair .sp-evcard__text { max-height: 42px; margin-bottom: 8px; }
  .sp-evgrid--pair .sp-evcard__foot { padding-top: 8px; }
  .sp-evgrid--pair .sp-evcard__conclusion { min-height: 0; margin: 6px 0 2px; }
}
/* Wrap div is the grid item now (owner request 2026-08-27: conclusion line
   sits under the card, not inside it), so the card itself stays full-height
   via flex:1 and the conclusion sentence stacks below without becoming its
   own grid cell. */
.sp-evcard-wrap { display: flex; flex-direction: column; }
.sp-evcard {
  position: relative; display: flex; flex-direction: column; flex: 1;
  background: var(--fd-panel); border: 1px solid var(--fd-hair);
  border-radius: 3px; padding: 19px 15px 13px;
  overflow: hidden;
}
/* Tier-colored top edge, left-to-right fade (same idiom as pop.php's
   .ep-widget::before on the Estimates Live console) - quiet, not another
   color chart, just a faint echo of the tier read out in the footer. */
.sp-evcard::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, var(--sp-tier-color), transparent 55%);
  opacity: .5;
}
/* Tier color used to run down the card's left edge on every card at once
   (2026-08-23 signature element); with the page's other colored labels
   (type badges, filter chips, group tick marks) that read as a color chart
   rather than a reading (2026-08-27, owner's call). The color now lives in
   exactly one place per card: the tier word in the footer below - still
   the same --sp-tier-color source of truth, just not painted onto the
   whole card. */
.sp-evcard--is-tuneout    { --sp-tier-color: var(--ap-red); }
.sp-evcard--is-soft       { --sp-tier-color: var(--ap-amber-600); }
.sp-evcard--is-held-soft  { --sp-tier-color: var(--ap-green); }
.sp-evcard--is-held       { --sp-tier-color: var(--ap-green-500); }
.sp-evcard__row {
  display: flex; align-items: center; gap: 9px; margin-bottom: 12px;
}
.sp-evcard__time {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11.5px; font-weight: 600; letter-spacing: .03em;
  color: var(--fd-ink); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.sp-evcard__dur {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; color: var(--fd-ink-3); font-variant-numeric: tabular-nums;
  margin-left: auto; padding-right: 4px;
}
.sp-evcard__dur--head {
  font-size: 12.5px; color: var(--fd-ink-2); margin-left: 0; padding-right: 0;
}
.sp-evcard__time--foot { margin-left: auto; }
/* A named topic is the rare case, not the norm, on a page whose content is
   individual talk segments rather than a recurring show topic (2026-08-27,
   owner's call) - so it renders only when present, inline at the head of
   the transcript snippet rather than as its own line (topics here never
   run long enough to need wrapping/clamping of their own), in the same
   small, tracked, uppercase gray voice as the page's other recurring
   labels (filter chips, tier word). The transcript snippet budget is
   nipped by the topic's length (see the PHP above) so the two together
   still read as one card-sized paragraph. */
.sp-evcard__topic {
  color: var(--fd-ink-3); font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px;
}
.sp-evcard__text {
  margin: 0 0 12px; color: var(--fd-ink-2); font-size: 13.5px; line-height: 1.55;
  flex: 1 1 auto;
  /* Cap at 3 lines (62px = 3 * 20.9px line box) so a long snippet can't grow
     the card - fade the last line toward the card's own panel background
     instead of a hard cut, same mask-image idiom as navigate.php's
     .nv-inact-card, one notch lighter (74%) since this box is much
     shorter than that table. */
  max-height: 62px; overflow: hidden;
  -webkit-mask-image: linear-gradient(179deg, black 74%, transparent 100%);
  mask-image: linear-gradient(179deg, black 74%, transparent 100%);
}
/* Footer: tier + SD + excess, one quiet line under a rule rather than fighting
   the header for space - this is the "so what" of the card, read last. */
.sp-evcard__foot {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  padding-top: 10px; margin-top: auto; border-top: 1px solid var(--fd-hair);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
}
.sp-evcard__tier {
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
.sp-evcard__tier.is-held      { color: var(--ap-green-500); }
.sp-evcard__tier.is-held-soft { color: var(--ap-green); }
.sp-evcard__tier.is-soft      { color: var(--ap-amber-600); }
.sp-evcard__tier.is-tuneout   { color: var(--ap-red); }
.sp-evcard__sdmeta {
  font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--fd-ink-3);
}
/* Rerun marker (WORKLOG 2026-08-24): sits left of the raw SD reading in
   .sp-evcard__perf (topic.php's foot slot) rather than replacing it - a
   rerun still needs its own performance figure next to the marker. */
.sp-evcard__rerun {
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fd-ink-3);
}
/* topic.php's foot slot (WORKLOG 2026-08-24): unlike signals.php's tier
   badge, this always shows a raw SD reading (not gated by the hold/tuneout
   threshold) - topic.php's cards were left with an empty bottom-left corner
   otherwise, since most never cross that threshold (see sp_conclusion_line's
   docblock for the same "descriptor alone" reasoning). No tier color here:
   an unclassified SD figure is not a verdict, so it stays neutral. */
.sp-evcard__perf {
  display: flex; align-items: baseline; gap: 8px;
}
.sp-evcard__sdraw {
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: none;
  color: var(--fd-ink-3);
}
/* Preview-only conclusion line (2026-08-27): sits below the foot rule, its
   own quiet sentence rather than competing with the SD/excess figures. */
.sp-evcard__conclusion {
  font-size: 12px; line-height: 1.4; color: var(--fd-ink-2);
  margin: 8px 0 4px; padding-left: 8px;
  min-height: calc(1.4em * 2); /* reserve 2 lines so cards in the same row
     stay aligned whether or not this card has a conclusion yet */
}

/* OPEN and the optional rerun marker share the card header with type and
   duration. Keeping them in normal flow prevents the actions from becoming
   a separate first row on narrow screens. */
.sp-evcard__topright {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 8px;
}
.sp-evcard__open {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fd-ink-2); background: var(--fd-panel-2);
  border: 1px solid var(--fd-hair-strong); border-radius: 6px;
  padding: 4px 10px; cursor: pointer; transition: background .14s, color .14s;
}
.sp-evcard__open:hover { background: var(--fd-panel-3); color: var(--fd-ink); }
@media (max-width: 480px) {
  .sp-evcard__row { gap: 7px; }
  .sp-evcard__topright { gap: 6px; }
  .sp-evcard__open { padding: 3px 8px; font-size: 10px; }
}

/* Break-type badge on each event card + the filter chip row above the
   groups (2026-08-27, owner's call): same colors as index.php's ribbon
   (--ap-green talk / --ap-sky stop set / --ap-amber newscast / --ap-indigo
   imaging) via one --sp-type-color custom property per modifier, so the
   badge's text color and the filter chip's dot background always agree
   without repeating the color in two places. */
.sp-item__type--talk  { --sp-type-color: var(--ap-green); }
.sp-item__type--ad    { --sp-type-color: var(--ap-sky); }
.sp-item__type--news  { --sp-type-color: var(--ap-amber); }
.sp-item__type--promo { --sp-type-color: var(--ap-indigo); }
.sp-item__type {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--sp-type-color);
  border: 1px solid currentColor; border-radius: 999px;
  padding: 2px 9px; white-space: nowrap;
}
/* On the event cards specifically, the pill matches the filter chips'
   look exactly (2026-08-23, owner's call): neutral outline + neutral
   label text, with the type color demoted to a small leading dot (same
   idea as .sp-event-filter__dot). That keeps the card from reading as a
   color chart while still letting the type be told apart at a glance,
   and the pill stays visibly distinct from the header's gray "Open"
   button. */
.sp-evcard__type {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fd-ink-3);
  border: 1px solid var(--fd-hair); border-radius: 999px;
  padding: 5px 12px; white-space: nowrap;
}
.sp-evcard__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--sp-type-color); flex: none; }

/* One dropdown replaces what used to be four separate export buttons
   (2026-08-27, owner's call: four buttons next to the filter chips read as
   button clutter, not a clear "pick one" action). */
.sp-export {
  display: inline-flex; align-items: center; gap: 7px; margin: 24px 0 0 auto;
}
.sp-export__label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fd-ink-3); white-space: nowrap;
}
.sp-export__select {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; font-weight: 600; letter-spacing: .02em;
  color: var(--fd-ink-2); background: var(--fd-panel-2);
  border: 1px solid var(--fd-hair); border-radius: 6px;
  padding: 5px 10px; cursor: pointer; max-width: 100%;
  transition: color .15s, border-color .15s;
}
.sp-export__select:hover, .sp-export__select:focus-visible { color: var(--fd-ink); border-color: var(--fd-ink-3); }

/* Breaks archive search: free text + kind/sort, same visual language as the
   export dropdown above (2026-08-30). */
.sl-search { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin: 18px 0 0; }
.sl-footer { border-top: 0; }
.sl-footer-divider { margin-bottom: 0; }
.sl-search__field { display: flex; flex-direction: column; gap: 4px; }
.sl-search__label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fd-ink-3);
}
.sl-search__input {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12.5px; color: var(--fd-ink);
  background: var(--fd-panel-2); border: 1px solid var(--fd-hair); border-radius: 6px;
  height: 34px; box-sizing: border-box; padding: 0 10px; min-width: 220px; max-width: 100%;
  transition: border-color .15s;
}
.sl-search__input:hover, .sl-search__input:focus-visible { border-color: var(--fd-ink-3); outline: none; }
.sl-search__btn {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fd-ink-2); background: var(--fd-panel-2);
  border: 1px solid var(--fd-hair); border-radius: 6px;
  height: 34px; box-sizing: border-box; padding: 0 14px; cursor: pointer; transition: color .15s, border-color .15s;
}
.sl-search__btn:hover, .sl-search__btn:focus-visible { color: var(--fd-ink); border-color: var(--fd-ink-3); }
.sl-search-results {
  width: min(920px, calc(100vw - 32px)); max-height: calc(100vh - 48px);
  padding: 0; border: 1px solid var(--fd-hair-strong); border-radius: 10px;
  background: var(--fd-panel); color: var(--fd-ink);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5); overflow: hidden;
}
.sl-search-results::backdrop { background: rgba(0, 0, 0, .6); }
.sl-search-results__head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: 16px 18px; border-bottom: 1px solid var(--fd-hair);
}
.sl-search-results__title { margin: 0; font-size: 16px; font-weight: 700; color: var(--fd-ink); }
.sl-search-results__summary {
  min-height: 1.4em; margin: 4px 0 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; line-height: 1.4; color: var(--fd-ink-3);
}
.sl-search-results__close {
  flex: none; padding: 0 4px; border: 0; background: none;
  color: var(--fd-ink-3); font-size: 22px; line-height: 1; cursor: pointer;
}
.sl-search-results__close:hover, .sl-search-results__close:focus-visible { color: var(--fd-ink); }
.sl-search-results__body {
  max-height: calc(100vh - 190px); padding: 14px 18px;
  overflow: auto; overscroll-behavior: contain;
}
.sl-search-results__body .sp-mon__meta { margin: 0 0 8px; }
.sl-search-results__body .sp-empty { margin: 10px 0; }
.sl-search-results__body .sl-log { min-width: 620px; }
.sl-search-results__foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 16px;
  padding: 12px 18px; border-top: 1px solid var(--fd-hair);
}
@media (max-width: 640px) {
  .sl-search-results { width: calc(100vw - 20px); max-height: calc(100vh - 20px); }
  .sl-search-results__head, .sl-search-results__body, .sl-search-results__foot { padding-left: 12px; padding-right: 12px; }
  .sl-search-results__body { max-height: calc(100vh - 160px); }
}
.sp-evmodal {
  width: min(640px, calc(100vw - 32px)); max-height: calc(100vh - 64px);
  padding: 0; border: 1px solid var(--fd-hair-strong); border-radius: 10px;
  background: var(--fd-panel); color: var(--fd-ink);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
  overflow-y: auto;
}
/* Mobile browsers report 100vh taller than the address-bar-shrunk visible
   viewport, so the UA's centered dialog (margin: auto both edges) opened
   with its top edge already above the visible area. A fixed top margin
   instead of auto anchors the dialog under the top edge every time; dvh
   (falls back silently where unsupported) tracks the toolbar. */
@media (max-width: 640px) {
  .sp-evmodal, .sl-search-results {
    margin: 14px auto auto;
    max-height: calc(100vh - 28px);
    max-height: calc(100dvh - 28px);
  }
}
.sp-evmodal::backdrop { background: rgba(0, 0, 0, .6); }
.sp-evmodal__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--fd-hair);
}
.sp-evmodal__headtext { min-width: 0; flex: 1; }
/* Daypart eyebrow as the big title, with type/topic/time echoing the card's
   own row right below it. Performance numbers live in the quiet modal footer
   so this header stays focused on identifying the break. */
.sp-evmodal__show { margin: 0 0 4px; font-size: 16px; font-weight: 700; color: var(--fd-ink); }
.sp-evmodal__meta {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
}
.sp-evmodal__metaleft {
  font-size: 12px; font-weight: 600; letter-spacing: .04em; color: var(--fd-ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sp-evmodal__metatime {
  font-size: 12px; color: var(--fd-ink-3); white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* Same idiom as Topic Monitor's .sp-mon2-group__hosts - only shown when
   sp_show_attribution() actually matched a real show (2026-08-27). */
.sp-evmodal__hosts { margin: 3px 0 0; font-size: 12px; color: var(--fd-ink-3); }
.sp-evmodal__hosts[hidden] { display: none; }
.sp-evmodal__stats {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; line-height: 1.4; color: var(--fd-ink-3);
  font-variant-numeric: tabular-nums; text-align: right;
}
.sp-evmodal__stats[hidden] { display: none; }
.sp-evmodal__lead {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; color: var(--fd-ink-3);
}
.sp-evmodal__close {
  border: none; background: none; color: var(--fd-ink-3);
  font-size: 22px; line-height: 1; cursor: pointer; padding: 0 4px;
}
.sp-evmodal__close:hover { color: var(--fd-ink); }
/* Two boxes, split cold down the middle (owner spec 2026-08-27): Before
   left-aligned, After right-aligned, so the pair reads as "coming from the
   left, going to the right" rather than two identical stacked rows. */
.sp-evmodal__neighbors {
  display: flex; margin: 0; border-bottom: 1px solid var(--fd-hair);
}
.sp-evmodal__neighbors[hidden] { display: none; }
/* display:flex + align-items here (bugfix 2026-08-27) is what actually
   pins a chip to its box's edge - align-items lives on the FLEX CONTAINER,
   not the item, so the align-self rules that used to sit on
   .sp-evmodal__neighbor--link below were no-ops: nbox was a plain block,
   never a flex container, so a neighbour with a real break to show (the
   only case with a chip on BOTH sides at once) rendered its After chip
   hugging the left like Before instead of the right edge. */
.sp-evmodal__nbox {
  flex: 1 1 0; min-width: 0; padding: 10px 18px;
  display: flex; flex-direction: column; align-items: flex-start;
}
.sp-evmodal__nbox--before { text-align: left; border-right: 1px solid var(--fd-hair); }
.sp-evmodal__nbox--after { text-align: right; align-items: flex-end; }
.sp-evmodal__nbox-label {
  margin: 0 0 4px; font-size: 10px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--fd-ink-3);
}
/* Plain text by default (music neighbours, or breaks with nothing to show):
   button element kept for markup simplicity, but it only looks/acts like a
   button once .sp-evmodal__neighbor--link is toggled on by JS. */
.sp-evmodal__neighbor {
  display: flex; flex-direction: column; align-items: flex-start; width: 100%;
  border: none; background: none; padding: 0; margin: 0;
  font: inherit; text-align: left; color: var(--fd-ink); cursor: default;
}
.sp-evmodal__nbox--after .sp-evmodal__neighbor { align-items: flex-end; }
.sp-evmodal__neighbor-line1 {
  font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 100%;
}
.sp-evmodal__neighbor-line2 {
  font-size: 12px; color: var(--fd-ink-2); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 100%;
}
/* A clickable neighbour (owner request 2026-08-27) reads as its own small
   button/chip, not just underlined text sitting in the box - the whole
   two-line block is already the click target (button element), so the
   visual should say so. width:auto shrinks it to content size (the base
   rule above fills the nbox for the plain/non-clickable text case) - now
   that .sp-evmodal__nbox is itself the flex container (see above), that
   shrunk chip is positioned by nbox's own align-items, no align-self
   needed on the chip itself. */
.sp-evmodal__neighbor--link {
  width: auto; max-width: 100%;
  padding: 7px 10px; border: 1px solid var(--fd-hair-strong); border-radius: 8px;
  background: var(--fd-panel-2); color: var(--fd-accent); cursor: pointer;
  transition: background .14s, border-color .14s;
}
.sp-evmodal__neighbor--link:hover { background: var(--fd-panel-3); border-color: var(--fd-accent); }
/* Track neighbour with a resolved iTunes preview clip (owner request,
   WORKLOG "Before/After-boksien nuolipilarit + kappaleen audio"): same chip
   look as a clickable break neighbour, but it plays instead of navigating -
   see speech_topics.php's sp_talk_card_modal() script for the click
   handler and window.AP_ITUNES.lookupPreview() call that toggles this class
   on once a clip actually resolves (~90% of tracks per owner estimate; the
   rest stay plain, undisguisable text, same as a neighbour with nothing to
   show - no blind fallback, see assets/itunes_preview.js). */
.sp-evmodal__neighbor--clip {
  width: auto; max-width: 100%;
  padding: 7px 10px; border: 1px solid var(--fd-hair-strong); border-radius: 8px;
  background: var(--fd-panel-2); color: var(--fd-accent); cursor: pointer;
  transition: background .14s, border-color .14s;
}
.sp-evmodal__neighbor--clip:hover { background: var(--fd-panel-3); border-color: var(--fd-accent); }
.sp-evmodal__neighbor--clip.is-playing { border-color: var(--fd-accent); background: var(--fd-panel-3); }
.sp-evmodal__neighbor-clip {
  display: none; font-size: 10px; font-weight: 700; letter-spacing: .06em;
  margin-top: 3px; color: var(--fd-ink-3);
}
.sp-evmodal__neighbor--clip .sp-evmodal__neighbor-clip { display: block; }
.sp-evmodal__neighbor--clip.is-playing .sp-evmodal__neighbor-clip { color: var(--fd-accent); }

/* Edge arrow pillars (owner note 2026-08-27, WORKLOG): only a clickable
   neighbour (a talk break with a transcript, see .sp-evmodal__neighbor--link
   above) gets one, at the box's outer edge, pointing further away from the
   flagged break - Before points left, After points right. A track neighbour
   (kind='track') gets the CLIP chip above instead - it plays in place
   rather than moving the modal to different content, so it does not need a
   "further away" arrow. */
.sp-evmodal__nbox--before:has(.sp-evmodal__neighbor--link) { position: relative; padding-left: 28px; }
.sp-evmodal__nbox--before:has(.sp-evmodal__neighbor--link)::before {
  content: ""; position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
  border-style: solid; border-width: 6px 8px 6px 0;
  border-color: transparent var(--fd-accent) transparent transparent;
}
.sp-evmodal__nbox--after:has(.sp-evmodal__neighbor--link) { position: relative; padding-right: 28px; }
.sp-evmodal__nbox--after:has(.sp-evmodal__neighbor--link)::after {
  content: ""; position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  border-style: solid; border-width: 6px 0 6px 8px;
  border-color: transparent transparent transparent var(--fd-accent);
}
.sp-evmodal__back {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 12px 18px 0; padding: 0; border: none; background: none;
  color: var(--fd-accent); font-size: 12.5px; font-weight: 600;
  letter-spacing: .04em; cursor: pointer;
}
.sp-evmodal__back:hover { text-decoration: underline; text-underline-offset: 2px; }
.sp-evmodal__back[hidden] { display: none; }
.sp-evmodal__transcript {
  margin: 0; padding: 16px 18px; color: var(--fd-ink-2);
  font-size: 15px; line-height: 1.62; white-space: pre-wrap;
  overflow-y: auto; max-height: calc(100vh - 220px);
}
.sp-evmodal__topic-passage {
  display: block; margin: 12px -4px; padding: 11px 14px 12px;
  border-left: 2px solid var(--fd-accent); border-radius: 0 6px 6px 0;
  background: var(--fd-panel-2); color: var(--fd-ink);
}
.sp-evmodal__topic-passage-label {
  display: block; margin: 0 0 6px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px; font-weight: 600; line-height: 1.3;
  letter-spacing: .1em; color: var(--fd-ink-3);
}
/* Small brand-only close (owner report 2026-08-27: the modal ended bare
   under the transcript). Deliberately minimal - just the page's own footer
   idiom (.ov-footer__brand) scaled to the modal, not a repeat of the full
   page footer's nav links, which have no meaning inside a dialog. */
.sp-evmodal__footer {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 10px 18px; border-top: 1px solid var(--fd-hair);
}
.sp-evmodal__footer-brand {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; letter-spacing: .18em; color: var(--fd-ink-3);
}

@media (max-width: 720px) {
  .sp-kpi { grid-template-columns: repeat(2, 1fr); }

}

/* ---- pipeline: live state of a run that takes hours ---------------- */

@keyframes sp-pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

.sp-vps {
  margin: 14px 0 0; padding: 10px 0 0;
  border-top: 1px solid var(--fd-hair);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; color: var(--fd-ink-3);
  font-variant-numeric: tabular-nums;
}
.sp-vps__row { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px 18px; }
.sp-vps__row + .sp-vps__row { margin-top: 6px; }
.sp-vps__label {
  font-size: 9px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--fd-ink-2);
}
.sp-vps__item { display: inline-flex; align-items: baseline; gap: 6px; }
.sp-vps__item b { font-weight: 600; color: var(--fd-ink-2); }
.sp-vps__item span { letter-spacing: .04em; text-transform: uppercase; }
@media (max-width: 640px) {
  .sp-vps__row { gap: 8px 14px; }
  .sp-vps__label { flex-basis: 100%; }
}

/* ---- the join: talk breaks against the listener curve -------------- */
/* table-layout: fixed + shared per-column widths (owner report 2026-08-27):
   the "+N more" fold is a SEPARATE nested <table class="sp-join sp-join--nested">
   (see speech/signals.php $sp_join_row), not more rows of the same table, so
   with the default auto layout each table sizes its own columns from its own
   content and the two drift out of alignment as soon as the folded rows'
   text differs from the visible rows' (Length in particular walks left).
   Fixed widths make both tables size identically regardless of content. */
.sp-join { width: 100%; border-collapse: collapse; font-size: 13.5px; table-layout: fixed; }
.sp-join th:nth-child(1), .sp-join td:nth-child(1) { width: 11%; }
.sp-join th:nth-child(2), .sp-join td:nth-child(2) { width: 15%; }
.sp-join th:nth-child(3), .sp-join td:nth-child(3) { width: 18%; }
.sp-join th:nth-child(4), .sp-join td:nth-child(4) { width: 18%; }
.sp-join th:nth-child(5), .sp-join td:nth-child(5) { width: 18%; }
.sp-join th:nth-child(6), .sp-join td:nth-child(6) { width: 20%; }
.sp-join th {
  text-align: right; padding: 0 0 8px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px; letter-spacing: .11em; text-transform: uppercase;
  color: var(--fd-ink-3); font-weight: 600;
  border-bottom: 1px solid var(--fd-hair);
}
.sp-join th:first-child, .sp-join td:first-child { text-align: left; }
.sp-join td {
  text-align: right; padding: 7px 0; color: var(--fd-ink-2);
  font-variant-numeric: tabular-nums; border-bottom: 1px solid var(--fd-hair);
}
.sp-join__t { font-family: "IBM Plex Mono", ui-monospace, monospace; color: var(--fd-ink); }
/* Four bands, quartiles of the measured excess distribution (see
   sp_excess_band() in index.php). The middle half stays uncoloured on purpose:
   only the two ends are a reading. */
.sp-join__ex.is-held    { color: var(--ap-green-500); opacity: .8; }
.sp-join__ex.is-flat    { color: var(--fd-ink-3); }
.sp-join__ex.is-typical { color: var(--fd-ink-2); }
.sp-join__ex.is-soft    { color: var(--ap-amber-600); opacity: .8; }
.sp-join__ex.is-tuneout { color: var(--ap-red); font-weight: 600; opacity: .82; }

/* "+N more" fold row: reuses .sp-mon2-more__trigger's look (quiet, not a
   call to action) but the cell itself needs to override the numeric-table
   defaults (right-aligned, bottom-ruled) it would otherwise inherit from
   .sp-join td. */

@media (max-width: 640px) { .sp-join { font-size: 12.5px; } }

/* ---- fold: a whole section behind its own heading (Topic report, the
   per-hour raw join under "Did the talk hold the audience") -------------- */
.sp-details__summary {
  cursor: pointer; list-style: none;
}
.sp-details__summary::-webkit-details-marker { display: none; }
.sp-details__summary::before {
  content: "\25b8\00a0\00a0"; color: var(--fd-ink-3); font-size: 13px;
}
.sp-details[open] .sp-details__summary::before { content: "\25be\00a0\00a0"; }
.sp-details__summary:hover .ov-h2 { color: var(--fd-accent); }
.sp-admin-h2 {
  margin: 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 600; letter-spacing: .16em;
  color: var(--fd-ink-3); text-transform: uppercase;
}
.sp-details__summary:hover .sp-admin-h2 { color: var(--fd-ink-2); }
.sp-details__summary:focus-visible, .sp-mon2-more__trigger:focus-visible, .sp-mon2-nav-btn:focus-visible, .sp-lang:focus-visible, .sp-export__select:focus-visible {
  outline: 2px solid var(--fd-accent); outline-offset: 3px;
}
/* Full-size destination rows for the talk archive and statistics pages. */
.sp-destination {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  text-decoration: none;
}
.sp-destination:hover .ov-h2 { color: var(--fd-accent); }
.sp-destination:hover .sp-mon__meta { color: var(--fd-ink-2); }

.sp-details__body { margin-top: 14px; }

.sp-page-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  gap: 6px;
  margin: 0 0 22px;
  padding: 8px 0;
  overflow-x: auto;
  scrollbar-width: thin;
  background: color-mix(in srgb, var(--fd-page) 94%, transparent);
  border-top: 1px solid var(--fd-hair);
  border-bottom: 1px solid var(--fd-hair);
  backdrop-filter: blur(10px);
}
.sp-page-nav__link {
  flex: 0 0 auto;
  padding: 6px 9px;
  border-radius: 999px;
  color: var(--fd-ink-2);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .08em;
  line-height: 1;
  text-decoration: none;
  text-transform: uppercase;
}
.sp-page-nav__link:hover { color: var(--fd-ink); background: var(--fd-panel-2); }
.sp-page-nav__link:focus-visible { outline: 2px solid var(--fd-accent); outline-offset: 1px; }
.sp-page-nav__link[aria-current="location"] { color: var(--fd-accent); background: var(--fd-panel-2); }
.sp-page-nav + .ov-section { border-top: 0; }
/* Same-page jump targets (the nav's own links, and the entity/topic pages'
   "Back to ..." links) land right under the sticky .sp-page-nav otherwise,
   clipping the section's own heading under it. */
.ov-section[id] { scroll-margin-top: 56px; }
/* Tune-out events and Did the talk hold the audience sit stacked directly on
   top of each other - the same kind of thing, so a divider between them would
   read as separating two topics rather than two views of one. Only the gap
   collapses; the fold keeps its own bottom padding, so an opened fold still
   has room to breathe from whatever follows it. A modifier class, not a plain
   "+" adjacent-sibling rule: Tune-out events' own (hidden, position:fixed)
   modal markup sits between the two sections in the DOM and would break that
   selector. */
.sp-details--stacked { border-top: 0; padding-top: 12px; }
/* The three reader-facing information destinations form one compact group:
   one divider introduces it, while equal spacing keeps its rows together. */
.sp-info-sections {
  padding: 34px 0;
  border-top: 1px solid var(--fd-hair);
}
/* Breaks / Analyses / Scoring quick-nav row (2026-09-06): three equal
   destinations - the first two are plain page links, Scoring opens the
   existing Topic Scoring Methodology <details> as a modal instead of a
   third kind of navigation (see the script block for how). */
.sp-quicknav { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.sp-quicknav-btn {
  display: inline-flex; align-items: center;
  padding: 9px 16px; border-radius: 8px;
  border: 1px solid var(--ap-hairline, rgba(148,163,184,.18));
  background: transparent; color: var(--ap-text-secondary);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  text-decoration: none; cursor: pointer;
}
.sp-quicknav-btn:hover { color: var(--ap-text-primary); border-color: var(--ap-accent); }
/* "All" prefix before the Topics/Breaks pair (2026-09-10): avoids repeating
   "All topics" next to a page whose own heading is Topic Scoring. */
.sp-quicknav-label {
  display: inline-flex; align-items: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ap-text-tertiary, var(--ap-text-secondary));
}

/* Generic small modal, built by the Scoring quick-nav button's script -
   mirrors assets/system_modal.js's shape (backdrop + centered panel) but
   local to this page since it just repositions content already on it,
   no iframe/network needed. */
.sp-modal { position: fixed; inset: 0; z-index: 1350; display: flex; align-items: flex-start; justify-content: center; padding: 24px 16px; }
.sp-modal[hidden] { display: none !important; }
.sp-modal__bd { position: fixed; inset: 0; background: rgba(0,0,0,.65); }
.sp-modal__panel {
  position: relative; z-index: 1; width: 100%; max-width: 720px;
  max-height: calc(100dvh - 48px); overflow-y: auto;
  background: var(--ap-navy-950, #0a0d13);
  border: 1px solid var(--ap-hairline, rgba(148,163,184,.18));
  border-radius: 14px; padding: 20px 22px 26px;
}
.sp-modal__close {
  position: sticky; top: 0; float: right; margin-left: 12px;
  width: 32px; height: 32px; border-radius: 8px; border: 0;
  background: rgba(255,255,255,.06); color: var(--ap-text-primary);
  font-size: 18px; line-height: 1; cursor: pointer;
}
.sp-modal__body details.sp-details { border: 0; }
/* The moved-in <details> keeps its native toggle behaviour otherwise -
   clicking its own heading would collapse the body while the modal stays
   open. Only the disclosure control is disabled; the heading/meta tag stay
   visible and readable. */
.sp-modal__body .sp-details__summary { cursor: default; pointer-events: none; }
.sp-info-sections > .ov-section {
  padding: 12px 0;
  border-top: 0;
}
#operations { padding-bottom: 16px; }
#capture #export { margin-top: 12px; }
#capture .sp-export { margin-top: 0; }
/* Most recent rerun: always expanded on desktop (no fold affordance), still
   a real collapsible <details> on narrow viewports where space is tighter. */
@media (min-width: 721px) {
  .sp-details--rerun .sp-details__summary { pointer-events: none; cursor: default; }
  .sp-details--rerun .sp-details__summary::before { display: none; }
}

/* ---- transcript archive: latest break, then a rail into the history ------ */

/* ---- hero: the same face as /overview, /graphs and /controls ------------- */
.gr-hero { padding: 40px 0 6px; }
.gr-hero__title {
  font-size: 30px; font-weight: 600; line-height: 1.1; letter-spacing: .04em;
  text-transform: uppercase; color: var(--fd-ink); margin: 0 0 10px;
}
.gr-hero__tag {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11.5px; font-weight: 500; letter-spacing: .14em;
  text-transform: uppercase; color: var(--fd-ink-3); margin: 0;
}
@media (max-width: 560px) { .gr-hero__title { font-size: 24px; } }

/* ── retired reference station ────────────────────────────────────────────────
   The page named one station in a mail to customers, and the station has since
   changed. The old name therefore stays on the heading row rather than vanishing
   from under a link somebody already holds: muted, on the right, and it opens
   the archive where its transcripts still are. */
.sp-h2row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
.sp-h2row > .sp-event-group__label { margin: 0; }

/* All breaks archive heading (talk/breaks.php): title + count on one line,
   lede below, same as .sp-h2row + .sp-lede used to render. On a phone the
   count is less useful than the lede sentence above the table, so it moves
   after the lede instead of wrapping between the title and it. */
.sl-archive-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 16px;
}
/* .ov-h2's own 20px bottom margin (overview.css) is what puts normal space
   under a heading everywhere else on this page - zeroing it here (2026-08-30
   flex refactor) was the actual cause of the title hugging the lede below it. */
.sl-archive-head__title { margin: 0 0 20px; order: 1; }
.sl-archive-head__meta { margin-left: auto; order: 2; }
/* Same bottom margin .sp-lede carries everywhere else on this page - the
   flex refactor (2026-08-30) zeroed it by accident, leaving this the only
   lede on talk/* with no breathing room below it. */
.sl-archive-head__lede { flex-basis: 100%; order: 3; margin: 0 0 14px; }
@media (max-width: 640px) {
  .sl-archive-head__meta { order: 4; margin-left: 0; flex-basis: 100%; }
}

/* ── EN / original words ──────────────────────────────────────────────────────
   The reference station is chosen on audience, not on language, so the talk is
   read here in translation by default. The button is the way back to the words
   as they were spoken; it sits at the right end of the heading row it belongs
   to, the same place and weight as the archived-station tag. */
.sp-lang {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 7px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--fd-ink-3);
  background: transparent;
  border: 1px solid var(--fd-hair); border-radius: 999px;
  padding: 4px 11px; cursor: pointer; white-space: nowrap;
  transition: color .15s, border-color .15s;
}
.sp-lang:hover { color: var(--fd-ink-2); border-color: var(--fd-ink-3); }
/* .sp-lang sets display, so it would otherwise win over the UA [hidden]
   rule (same specificity, author sheet loads later) - state it explicitly
   so JS toggling the hidden attribute (nothing open to translate yet) works. */
.sp-lang[hidden] { display: none; }
.sp-lang__flag { font-size: 12px; line-height: 1; letter-spacing: 0; }

.sp-h2row__actions { display: inline-flex; align-items: center; gap: 10px; }

/* ── talk log (/speech/log.php) ──────────────────────────────────────────────
   Songs and talk breaks on one timeline. The row kinds have to be told apart
   at a glance without the colours competing with the excess column: talk is
   the only one that carries weight, an ident the near-invisible one, and
   ad/news/promo (content_type, 2026-08-21) sit dimmed in between - present
   and labelled, but visibly not editorial talk.

   The join table it inherits from is all right-aligned numbers with no gutter,
   which works when every column is a number. Here two columns are text and
   left-aligned, so the clock ran straight into the artist while the number
   columns floated apart. Fixed widths for everything that is a number or a
   clock, the title taking whatever is left, and one gutter between columns. */
.sl-log { table-layout: fixed; }
.sl-log--day col:nth-child(1) { width: 12%; }
.sl-log--day col:nth-child(2), .sl-log--day col:nth-child(3) { width: 23%; }
.sl-log--day col:nth-child(4) { width: 10%; }
.sl-log--day col:nth-child(5) { width: 12%; }
.sl-log--day col:nth-child(6) { width: 20%; }
.sl-log--search col:nth-child(1), .sl-log--search col:nth-child(2) { width: 12%; }
.sl-log--search col:nth-child(3), .sl-log--search col:nth-child(4) { width: 23%; }
.sl-log--search col:nth-child(5) { width: 10%; }
.sl-log--search col:nth-child(6) { width: 20%; }
.sl-log th, .sl-log td { padding: 11px 0 11px 16px; border-bottom: 1px solid var(--fd-hair); }
.sl-log th:first-child, .sl-log td:first-child { padding-left: 0; }
.sl-log th { color: var(--fd-ink-3); font-size: 10px; letter-spacing: .12em; text-transform: uppercase; }
.sl-log tbody tr:last-child td { border-bottom: 0; }
.sl-log .sp-join__t { width: 52px; white-space: nowrap; }
.sl-log .sl-who  { width: 26%; text-align: left; }
.sl-log .sl-what { width: 55%; text-align: left; color: var(--fd-ink-2); }
.sl-log th.sl-th-left { text-align: left; }
.sl-log .sl-len  { width: 62px; }
.sl-log .sl-lstn { width: 78px; }
.sl-log .sp-join__ex { width: 96px; white-space: nowrap; text-align: left; }
.sl-log .sl-who, .sl-log .sl-what {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sl-row--talk    { background: rgba(var(--ap-white-rgb), .035); }
.sl-row--ident .sl-who, .sl-row--ident .sl-what { color: var(--ap-text-tertiary); }
/* ad/news/promo/talk: content_type from tools/speech_ingest_breaks.php
   (2026-08-21). Owner (2026-08-21): dimming these read as harder to spot, not
   easier - all speech-table rows read at the same weight regardless of
   content_type, the label alone (Stop set / Newscast / Station ID / Talk
   break) carries the distinction. Only the ident row (music side, a DJ mix
   with no speech content at all) stays dimmed. These four kinds render one
   merged .sl-what cell (colspan 2, no .sl-who) since 2026-08-21 - no more
   artist/title split for non-song rows, see speech/breaks.php. */
.sl-row--ad, .sl-row--news, .sl-row--promo { background: rgba(var(--ap-white-rgb), .035); }
.sl-row--ad .sl-what, .sl-row--news .sl-what, .sl-row--promo .sl-what,
.sl-row--talk .sl-what {
  font-weight: 400; color: var(--fd-ink);
}
/* Placeholder for a talk break whose transcript is not on disk yet; the page
   script (see below) fills it from /talk/status.php's live pipeline state. */
.sl-pending { color: var(--fd-ink-3); }
.sl-daybox {
  border: 1px solid var(--fd-hair); border-radius: 10px;
  padding: 15px 16px 12px; margin: 8px 0 16px;
}
.sl-daybox .sp-h2row { margin-bottom: 0; }
.sl-daybox .sp-h2row > .sp-event-group__label:first-child { margin-top: 0; }
.sl-daybox .sl-days:last-child, .sl-daybox .sl-stations:last-child { margin-bottom: 0; }
.sl-days, .sl-stations {
  display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 14px;
}
/* Same chip as an event card's Open button (.sp-evcard__open), so the day
   picker reads as the same UI family as the highlights above it. */
.sl-day {
  display: inline-flex; align-items: center;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fd-ink-2); background: var(--fd-panel-2);
  border: 1px solid var(--fd-hair-strong); border-radius: 6px;
  padding: 2px 10px; text-decoration: none; transition: background .14s, color .14s;
}
.sl-day:hover  { background: var(--fd-panel-3); color: var(--fd-ink); }
.sl-day.is-on  { background: rgba(var(--ap-white-rgb), .10); color: var(--fd-ink); }
@media (min-width: 641px) {
  /* force a fixed 7-per-row grid so the wrap point does not drift with each
     day's label width, and gives wrapped rows a visible row-gap. */
  .sl-days { display: grid; grid-template-columns: repeat(7, max-content); gap: 10px 6px; }
}
@media (max-width: 640px) {
  /* the listener count is the one column a phone can lose: the excess above
     it is the reading, and the count only sizes it. Targeted by class, not
     nth-child - the day table's Break cell has colspan="2", so the "Listener
     movement" <th> lands at nth-child(5), not "Listeners" as nth-child(5)
     assumed. The search table has no Listeners column at all, so it is left
     alone here. */
  .sl-log--day th:nth-child(4), .sl-log--day td:nth-child(4) { display: none; }
  .sl-log th, .sl-log td { padding-left: 10px; }
  .sl-log .sl-who { width: 30%; }
  /* day picker is newest-first (breaks.php $sl_days), so keeping the first
     9 buttons keeps the most recent days on a phone-width row. */
  .sl-days .sl-day:nth-child(n+10) { display: none; }
}

/* ---- talk log: the break in full ------------------------------------------
   A timeline row has room for the opening words only. The rest of the break is
   what tells a live read from an ad bed and a tune-out from a coincidence, so
   the row opens it rather than sending the reader to the transcript archive. */
.sl-row.is-open { cursor: pointer; }
.sl-row.is-open:hover { background: rgba(var(--ap-white-rgb), .075); }
.sl-row.is-open:focus-visible { outline: 1px solid var(--fd-accent); outline-offset: -1px; }

/* ---- topic.php TOPIC MONITOR (Section 1, docs/TOPIC_MONITOR_UI.md): the
   list, not the table. Reuses .sp-details__summary for its arrow marker; the
   suffix "2" only tells it apart from index.php's own .sp-mon__* (hand-
   curated repeat-segment watch, unrelated feature despite the similar
   name). ------------------------------------------------------------- */
/* One header per contiguous on-air run (sp_group_monitor_rows()), so SHOW/
   HOST is not repeated on every row under it. */
/* Light per-show card (2026-08-22, owner's call): several groups back to
   back read as one unbroken column of rows; a faint background + border
   gives each show its own edge without turning it into a heavy panel. */
.sp-mon2-group, .sp-mon2-single {
  margin-top: 16px;
  padding: 14px 16px 6px;
  border: 1px solid var(--fd-hair); border-radius: 8px;
  background: rgba(var(--ap-white-rgb), .025);
}
html.light .sp-mon2-group, html.light .sp-mon2-single { background: rgba(var(--ap-black-rgb), .02); }
.sp-mon2-group:first-child, .sp-mon2-single:first-child { margin-top: 8px; }
/* Topic Monitor panel: current show/topic card + the hour's talk-break
   timeline read as one component, not a card followed by a loose chart.
   The card's own border/background move to the shared panel; .sp-mon2-single
   still exists standalone elsewhere, so this override is scoped to when it
   sits inside .sp-monitor-panel. */
.sp-monitor-panel {
  margin-top: 8px; padding: 14px 16px 16px;
  border: 1px solid var(--fd-hair); border-radius: 8px;
  background: rgba(var(--ap-white-rgb), .025);
}
html.light .sp-monitor-panel { background: rgba(var(--ap-black-rgb), .02); }
.sp-monitor-panel .sp-mon2-single {
  margin-top: 0; padding: 0 0 6px; border: 0; background: none;
}
.sp-monitor-panel #ribbon { margin-top: 6px; padding-top: 10px; border-top: 1px solid var(--fd-hair); }
/* One show visible at a time (2026-08-22, owner's call); .sp-mon2-nav-btn
   toggles .is-active client-side. */
.sp-mon2-group:not(.is-active) { display: none; }
.sp-mon2-group__head {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 14px;
  margin: 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 600; letter-spacing: .08em;
  color: var(--fd-ink-2);
}
.sp-mon2-group__headline { display: inline; }
.sp-mon2-group__updated,
.sp-mon2-group__processing {
  margin-left: auto;
  font-weight: 500; letter-spacing: .04em; color: var(--fd-ink-3);
  white-space: nowrap;
}
.sp-mon2-group__processing { text-align: center; }
.sp-mon2-group__live {
  color: var(--fd-ink);
  white-space: nowrap;
}
.sp-mon2-group__live i {
  display: inline-block; width: 7px; height: 7px;
  margin-right: 6px; border-radius: 50%; background: #ef4444;
  animation: sp-pulse 1.8s ease-in-out infinite;
}
.sp-mon2-group__label { color: var(--fd-ink-3); }
.sp-mon2-group__show { color: var(--fd-ink); text-decoration: none; }
.sp-mon2-group__show:hover,
.sp-mon2-group__hosts a:hover { text-decoration: underline; }
.sp-mon2-group__hosts {
  margin: 3px 0 0; font-size: 12px; color: var(--fd-ink-3);
}
.sp-mon2-group__hosts a { color: inherit; text-decoration: none; }
@media (max-width: 640px) {
  .sp-mon2-group__updated,
  .sp-mon2-group__processing { flex-basis: 100%; margin-left: 0; text-align: left; }
  .sp-mon2-group__updating { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .sp-mon2-group__live i { animation: none; }
}
.sp-mon2-list { list-style: none; margin: 6px 0 0; padding: 0; }
.sp-mon2-item { border-top: 1px solid var(--fd-hair); }
.sp-mon2-item:last-child { border-bottom: 1px solid var(--fd-hair); }
.sp-mon2-item__head {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 12px;
  padding: 7px 0;
}
.sp-mon2-item__dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  align-self: center; font-style: normal;
}
.sp-mon2-item__dot--hi { background: var(--ap-green); }
.sp-mon2-item__dot--mid { background: var(--ap-accent); }
.sp-mon2-item__dot--lo { background: var(--ap-red); }
.sp-mon2-item__time {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px; font-weight: 600; color: var(--fd-ink);
  font-variant-numeric: tabular-nums;
}
.sp-mon2-item__topic { font-size: 12.5px; color: var(--fd-ink); }
.sp-mon2-item__voice {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; letter-spacing: .04em; color: var(--fd-ink-3);
  white-space: nowrap;
}
/* Topic-level score pill (2026-08-22, owner's call): shows the same
   repeat-averaged score as Topic Summary for this row's topic, not the
   row's own single-airing excess - a demo reader cannot tell the two
   apart, so both sections now agree on one number per topic. Same pill
   shape/coloring as .sp-sum-row__perf below. */
/* Small and colorless on purpose (owner's call, 2026-08-22): this is the
   same topic-level score as Topic Summary's colored pill, just repeated
   next to each airing here for context - keeping it colorless and a size
   down avoids inviting the "why is this scoring different" question. */
.sp-mon2-item__score {
  margin-left: auto;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px; font-weight: 600; letter-spacing: .06em;
  color: var(--fd-ink);
  border: 1px solid var(--fd-hair); border-radius: 999px;
  padding: 2px 8px; white-space: nowrap;
}
@media (max-width: 640px) {
  .sp-mon2-item__score { margin-left: 0; order: 4; flex-basis: 100%; }
}
.sp-mon2-item__body { padding: 0 0 14px 21px; margin: 0; }
/* One <p> per voice turn (sp_topic_paragraphs()) - tight vertical rhythm
   between them so a voice handoff reads as one quote with a beat, not as
   two unrelated paragraphs. */
.sp-mon2-item__quote {
  margin: 0 0 6px; color: var(--fd-ink-2); font-size: 13px;
  font-style: italic; line-height: 1.6;
}
.sp-mon2-item__quote:last-child { margin-bottom: 0; }
/* Mobile quote clamp (2026-08-24): the language toggle swaps the whole
   quote between English (usually the more compact MT rendering) and the
   original words as spoken, which can run much longer character-wise -
   clamp both to the same line count on narrow screens so the list stays
   scannable regardless of which language is shown. */
.sp-mon2-quote-more {
  display: none; margin: 0 0 10px 21px; padding: 0; border: 0; background: none;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; letter-spacing: .04em; color: var(--fd-ink-3);
  cursor: pointer; text-decoration: underline;
}
@media (max-width: 640px) {
  .sp-mon2-item__quote[data-sp-clamp] {
    display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .sp-mon2-quote-more.is-visible { display: inline-block; }
}
/* docs/TOPIC_SCORING.md "Decision: segment long breaks into topics"
   (2026-08-27): a break past the single-topic ceiling may cover more than
   one subject - this lists the other topics found inside it, each with its
   own audience-hold reading, under the same break's transcript. */
.sp-mon2-item__splits {
  list-style: none; margin: 0 0 0 21px; padding: 8px 0 0;
  border-top: 1px dashed var(--fd-hair);
  display: flex; flex-direction: column; gap: 4px;
}
.sp-mon2-item__split {
  display: flex; align-items: baseline; gap: 8px; font-size: 11.5px;
}
.sp-mon2-item__split-range {
  font-size: 10px; color: var(--fd-ink-3); white-space: nowrap;
}
.sp-mon2-item__split-topic { color: var(--fd-ink-2); }
.sp-mon2-item__split-excess {
  margin-left: auto; font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; font-weight: 600; color: var(--fd-ink-3); white-space: nowrap;
}
.sp-mon2-item__split-excess.is-bad  { color: rgba(var(--ap-red-rgb),.82); }
.sp-mon2-item__split-excess.is-good { color: rgba(var(--ap-green-rgb),.82); }

/* "+N more" fold (demo-scope cap, SP_MONITOR_VISIBLE_PER_GROUP): deliberately
   quiet - reads as a continuation of the list, not another call to action. */
.sp-mon2-more__trigger {
  display: block; cursor: pointer; list-style: none;
  padding: 6px 0; margin-left: 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; letter-spacing: .04em; color: var(--fd-ink-3);
}
.sp-mon2-more__trigger::-webkit-details-marker { display: none; }
.sp-mon2-more__trigger:hover { color: var(--fd-ink-2); }
.sp-mon2-list--nested { margin-top: 0; }
.sp-mon2-list--nested .sp-mon2-item:first-child { border-top: 0; }
.sp-mon2-list--nested .sp-mon2-item:last-child { border-bottom: 0; }

/* Mobile row count (SP_MONITOR_VISIBLE_MOBILE) vs desktop's
   (SP_MONITOR_VISIBLE_PER_GROUP): the rows in between are rendered twice by
   PHP - once in the main list, once in the nested "+more" list - and shown
   in exactly one place per breakpoint here, so the "+N more" count always
   matches what is actually still folded away. */
.sp-mon2-more__count--mobile { display: none; }
.sp-mon2-list--nested .sp-mon2-item--extra-nested { display: none; }
@media (max-width: 640px) {
  .sp-mon2-item--extra { display: none; }
  .sp-mon2-more__count--desktop { display: none; }
  .sp-mon2-more__count--mobile { display: inline; }
  .sp-mon2-list--nested .sp-mon2-item--extra-nested { display: list-item; }
}

/* Footer row: "+N more" fold on the left, show-to-show navigation on the
   right. align-items: flex-start (not center) - center was letting the nav
   buttons drift down into the middle of the fold once it opened, since
   align-items: center re-centers against the row's full (now taller)
   height; flex-start pins the buttons to the top regardless of whether the
   fold is open. .sp-mon2-more needs flex: 1 1 auto too: unset, a flex item
   defaults to flex-grow: 0 and only takes its own content's width, so the
   opened nested list was squeezed into the "+N more" label's width instead
   of the row's full width, which is what was pushing the bars in from the
   right edge (2026-08-22 bug, screenshot). */
.sp-mon2-group__footer {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; margin-top: 2px; padding-bottom: 8px;
}
.sp-mon2-more { flex: 1 1 auto; min-width: 0; }
.sp-mon2-group__nav { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; margin-top: 4px; }
.sp-mon2-nav-btn {
  cursor: pointer;
  background: transparent; color: var(--fd-ink-2);
  border: 1px solid var(--fd-hair); border-radius: 6px;
  padding: 3px 10px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 500; letter-spacing: .08em;
  text-transform: uppercase; white-space: nowrap;
}
.sp-mon2-nav-btn:hover { color: var(--fd-ink); border-color: var(--fd-ink-3); }
.sp-mon2-nav-btn[disabled] {
  cursor: default; color: var(--fd-ink-4, var(--fd-hair));
  border-color: var(--fd-hair); background: var(--fd-surface-2, transparent);
}
.sp-mon2-nav-btn[disabled]:hover { color: var(--fd-ink-4, var(--fd-hair)); border-color: var(--fd-hair); }
.sp-mon2-item__voicelabel {
  font-style: normal; font-weight: 600; color: var(--fd-ink-3);
}
@media (max-width: 640px) {
  .sp-mon2-item__body { padding-left: 0; }
}

/* ---- topic.php TOPIC SUMMARY (Section 2, docs/TOPIC_MONITOR_UI.md): a
   scanned list, not the numbers-only table index.php's own topic monitor
   uses. Shows sp_topic_highlights()'s curated 3 top / 2 middle / 3 low pick
   (owner's call, 2026-08-22, replacing the old every-topic list) with
   sp_topic_score()'s own number and a left accent rule that carries the
   green/red held/tuned-out read so the reader does not have to interpret
   the number to get the verdict. Scroll window once there are more rows
   than fit "at a glance" (same pattern as .sp-tmon__tablewrap below). --- */
.sp-sum-wrap { max-height: 340px; overflow-y: auto; }
.sp-sum-wrap--gap { margin-top: 14px; }
.sp-sum-list { list-style: none; margin: 0; padding: 0; }
.sp-sum-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px 8px 10px; margin-left: -1px;
  border-left: 2px solid transparent;
  border-bottom: 1px solid var(--fd-hair);
}
.sp-sum-row:last-child { border-bottom: none; }
.sp-sum-row.is-good { border-left-color: rgba(var(--ap-green-rgb), .55); }
.sp-sum-row.is-mid  { border-left-color: rgba(var(--ap-white-rgb), .25); }
.sp-sum-row.is-bad  { border-left-color: rgba(var(--ap-red-rgb), .55); }
/* Mobile trim (owner's call, 2026-08-27): drops the curated pick from
   3 top/2 mid/3 low to 2/1/2 - see the index.php $sp_mobHide comment for
   which three rows this removes and why. */
@media (max-width: 640px) {
  .sp-sum-row--mobile-hide { display: none; }
}
html.light .sp-sum-row.is-mid { border-left-color: rgba(var(--ap-black-rgb), .2); }
.sp-sum-row__title {
  display: flex; align-items: baseline; gap: 7px;
  flex: 1 1 auto; min-width: 0;
  text-decoration: none; color: inherit;
}
.sp-sum-row__title:hover .sp-sum-row__topic { color: var(--fd-accent, var(--fd-ink)); }
.sp-sum-row__topic {
  flex: 0 1 auto; min-width: 0;
  font-size: 12.5px; font-weight: 600; letter-spacing: .02em;
  color: var(--fd-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sp-sum-row__n {
  flex: 0 0 auto;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; color: var(--fd-ink-3); white-space: nowrap;
  background: rgba(var(--ap-white-rgb), .06);
  border-radius: 999px; padding: 1px 7px;
}
html.light .sp-sum-row__n { background: rgba(var(--ap-black-rgb), .05); }
.sp-sum-row__perf {
  flex: 0 0 auto;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; white-space: nowrap;
  min-width: 66px; text-align: right;
}
/* Weighted score marker (WORKLOG 2026-08-24): dotted underline is the
   standard "hover for why" affordance - this score was moved by a tune-out
   weight, not a plain average, and the tooltip (title attr) says by how
   much and why. */
.sp-sum-row__perf--weighted {
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

/* Show-team ranking. The show is the measured unit; host names are context,
   not individually scored links. Rebuilt 2026-09-06 to match the
   Mentions/Context box: light panel-2 card, no leftover accent bar. */
.sp-show-rank-box {
  display: block; margin-top: 0;
  padding: 6px 20px; box-sizing: border-box;
  background: var(--fd-panel-2, var(--fd-panel)); border: 1px solid var(--fd-hair);
  border-radius: 10px;
}
.sp-show-rank { list-style: none; margin: 0; padding: 0; }
.sp-show-rank__item {
  position: relative; display: flex; align-items: baseline; flex-wrap: wrap; gap: 5px 8px;
  padding: 16px 0; border-bottom: 1px solid var(--fd-hair);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 600; letter-spacing: .05em;
}
/* Only 3 rows ever show here (top 3), so host/time gets its own line under
   the show name rather than fighting it for space on one row. */
.sp-show-rank__meta {
  flex-basis: 100%; display: flex; align-items: baseline; gap: 5px 8px;
  font-weight: 400; color: var(--fd-ink-3);
}
.sp-show-rank__number { color: var(--fd-ink-3); }
.sp-show-rank__score {
  display: inline-flex; align-items: center;
  padding: 2px 8px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--fd-accent) 42%, var(--fd-hair));
  background: color-mix(in srgb, var(--fd-accent) 10%, var(--fd-panel));
  color: var(--fd-accent); white-space: nowrap;
  font-size: 10px; letter-spacing: .06em;
}
/* Rank change vs. last week, as an in-row pill (was absolutely positioned
   outside the box, 2026-08-30 fix). */
.sp-show-rank__previous {
  margin-left: auto; min-width: 34px; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  padding: 2px 9px; border-radius: 999px;
  border: 1px solid var(--fd-hair); background: var(--fd-panel);
  white-space: nowrap; color: var(--fd-ink-3);
}
.sp-show-rank__previous.is-up { color: var(--ap-green-500); border-color: color-mix(in srgb, var(--ap-green-500) 45%, var(--fd-hair)); }
.sp-show-rank__previous.is-down { color: var(--ap-red); border-color: color-mix(in srgb, var(--ap-red) 45%, var(--fd-hair)); }
.sp-show-rank__legend {
  display: flex; flex-wrap: wrap; gap: 5px 14px; margin: 9px 0 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; color: var(--fd-ink-3);
}
/* Legend + privacy line now live inside .sp-show-rank-box as its footer, so
   the box carries one clear metadata area instead of loose text below it. */
.sp-show-rank-box .sp-show-rank__legend:first-of-type { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--fd-hair); }
.sp-show-rank__legend--anon { margin-top: .4em; }
.sp-show-rank__item:first-child { border-top: 1px solid var(--fd-hair); }
.sp-show-rank__show { color: var(--fd-ink); text-decoration: none; }
.sp-show-rank__show:hover { color: var(--fd-accent); text-decoration: underline; }
.sp-show-rank__show:focus-visible { outline: 2px solid var(--ap-accent); outline-offset: 3px; }
.sp-show-rank__hosts { color: var(--fd-ink-2); }
.sp-show-rank__time, .sp-show-rank__sep { color: var(--fd-ink-3); }
@media (max-width: 640px) {
  .sp-show-rank__item { align-items: flex-start; }
}

/* OVERPERFORMING / UNDERPERFORMING: .ov-h2 itself for font-size/weight/case
   (same as "Every Break, Unfiltered"), just re-spaced since this one sits
   mid-section, between cards, rather than opening one. */
/* Group labels get their own voice, distinct from the section titles
   around them (2026-08-27, owner's call: default .ov-h2 read as just
   another section heading, not a two-way split worth pausing on). IBM
   Plex Mono, wide tracking, a small tier-colored tick instead of the
   uppercase-sans default - the same accent language as the card edges
   below, so the split reads before the cards even load. */
.sp-event-group__label {
  display: flex; align-items: center; gap: 8px;
  margin: 40px 0 16px; padding: 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--fd-ink-2);
}
.sp-event-group__label::before {
  content: ""; width: 14px; height: 2px; flex: 0 0 auto;
  background: var(--sp-group-color, var(--fd-hair-strong));
}
.sp-event-group__label--hold { --sp-group-color: var(--ap-green-500); }
.sp-event-group__label--loss { --sp-group-color: var(--ap-red); }
.sp-event-group__label:first-child { margin-top: 30px; }
/* Inside a fold summary (Break Highlights' rerun pair), the label IS the
   clickable heading, not a mid-section divider - the summary needs to be a
   flex row itself so the arrow marker and the label's own tick+text sit on
   one line instead of the label's block-level 40px top margin pushing it
   below the arrow onto its own line. */
.sp-details__summary:has(.sp-event-group__label) {
  display: flex; align-items: center;
}
.sp-details__summary > .sp-event-group__label { margin: 0; }

/* "+N more" fold (demo-scope cap, $sp_event_visible_cap in events.php):
   same quiet, not-a-call-to-action look as Topic Monitor's
   .sp-mon2-more__trigger - reads as a continuation of the pile, not
   another button competing with "Open". */
.sp-event-more__trigger {
  display: block; cursor: pointer; list-style: none;
  padding: 8px 0; margin: 4px 0 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; letter-spacing: .04em; color: var(--fd-ink-3);
}
.sp-event-more__trigger::-webkit-details-marker { display: none; }
.sp-event-more__trigger:hover { color: var(--fd-ink-2); }

/* ---- topic monitor: hand-curated repeat-segment watch -------------- */
.sp-mon__meta {
  margin-left: auto;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10.5px; font-weight: 500; letter-spacing: .12em;
  text-transform: uppercase; color: var(--fd-ink-3); white-space: nowrap;
}
/* Small ghost status chip, so the ingest-lag note reads as its own status
   indicator instead of competing on the same line as the selected date. */
.sl-processed-chip {
  padding: 3px 9px; border: 1px solid var(--fd-hair); border-radius: 999px;
  background: var(--fd-panel);
}
/* ---- topic.php TOPIC SCORING METHODOLOGY (bottom section, linked from
   "More on scoring" under TOPIC MONITOR's GDPR note): reuses the former
   Demo Vault's "Time to a Reliable Score" chart data but token-colored and
   with only the Reliable curve -
   Locked (12x median) is a POP-value-only concept the text here never
   mentions. viewBox cut from 190 to 150 tall (owner's call, 2026-08-22),
   coordinates rescaled rather than squashed. --------------------------- */
.sp-meth-sub {
  font-size: 13px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--fd-ink);
  margin: 26px 0 8px;
}
.sp-meth-sub:first-of-type { margin-top: 4px; }
.sp-meth-card {
  background: rgba(var(--ap-white-rgb), .04);
  border: 1px solid var(--fd-hair); border-radius: 8px;
  padding: 14px 16px 12px; margin-top: 16px;
  width: 80%; max-width: 80%;
}
html.light .sp-meth-card { background: rgba(var(--ap-black-rgb), .03); }
.sp-meth-chart { display: block; width: 100%; height: auto; }
.sp-meth-axis { stroke: var(--fd-hair); stroke-width: 1; }
.sp-meth-tick {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px; fill: var(--fd-ink-3);
}
.sp-meth-line { fill: none; stroke-width: 2.2; stroke-linejoin: round; stroke-linecap: round; }
.sp-meth-line--reliable { stroke: var(--fd-accent); }
.sp-meth-line--locked { stroke: var(--ap-green); }
.sp-meth-dot--reliable { fill: var(--fd-accent); }
.sp-meth-dot--locked { fill: var(--ap-green); }
.sp-meth-legend { display: flex; gap: 16px; margin-bottom: 10px; }
.sp-meth-legend span {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--fd-ink-3);
}
.sp-meth-swatch { width: 9px; height: 9px; border-radius: 3px; display: inline-block; }
.sp-meth-swatch--reliable { background: var(--fd-accent); }
.sp-meth-swatch--locked { background: var(--ap-green); }

/* Day map (talk/breaks.php, 2026-08-27) - one broadcast day as a tape
   strip: a topic segment's own start/end against the clock, not the coarser
   talk_breaks boundary. A cassette-counter reading, not a bar chart: fixed
   3-hour ticks below a single unbroken lane, IBM Plex Mono throughout to
   match every other timestamp on this page. Color is status (the same
   held/typical/soft/tune-out bands the archive table already uses via
   sl_excess_band()), never per-topic identity - a day can carry more
   distinct topics than any categorical palette should try to hold apart,
   and "which segments held the audience" is the question this answers, not
   "which topic is which color". */
.sl-daymap { margin: 14px 0 4px; }
.sl-daymap__strip {
  position: relative; height: 34px; border-radius: 4px;
  background: var(--fd-panel-2, var(--fd-panel));
  border: 1px solid var(--fd-hair);
  overflow: hidden;
}
.sl-daymap__blk {
  position: absolute; top: 5px; bottom: 5px;
  border-radius: 4px;
  background: var(--fd-ink-3); opacity: .55;
  cursor: default;
}
.sl-daymap__blk + .sl-daymap__blk { box-shadow: -2px 0 0 0 var(--fd-panel-2, var(--fd-panel)); }
/* Normal/Low loss/Stop set-newscast were all the same gray at close-together
   opacities (.16/.28/.55) and read as near-indistinguishable; spread the
   luminance further apart (2026-09-19) while keeping the "gray = neutral,
   not held or lost" convention. */
.sl-daymap__blk.is-context  { background: var(--fd-ink-3); opacity: .40; }
.sl-daymap__blk.is-held     { background: var(--ap-green-500); opacity: .85; }
/* Near-ambient (0 to SL_EXCESS_HELD), owner's call 2026-08-27: pale, not
   green - "roughly at baseline" should not read as "held well". */
.sl-daymap__blk.is-flat     { background: var(--fd-ink-3); opacity: .14; }
.sl-daymap__blk.is-typical  { background: var(--fd-ink-3); opacity: .68; }
.sl-daymap__blk.is-soft     { background: var(--ap-amber-600); opacity: .8; }
.sl-daymap__blk.is-tuneout  { background: var(--ap-red); opacity: .9; }
.sl-daymap__tick {
  position: absolute; top: 0; bottom: 0; width: 1px;
  background: rgba(var(--fd-hair-rgb, 148,163,184), .35);
}
.sl-daymap__axis { position: relative; height: 16px; margin-top: 2px; }
.sl-daymap__hr {
  position: absolute; transform: translateX(-50%);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; color: var(--fd-ink-3); white-space: nowrap;
}
.sl-daymap__hr:first-child { transform: none; }
.sl-daymap__hr:last-child { transform: translateX(-100%); }
/* DAY/WEEK now sits on the timeline's own eyebrow line (moved out of the
   legend row below, 2026-09-19 - the toggle previously read as tacked onto
   the legend rather than belonging to the chart it controls). */
.sl-daymap__head { display: flex; align-items: center; justify-content: space-between; gap: 8px 14px; }
.sl-daymap__head .sp-caption { margin: 0; }
/* The badge count varies with what a day/week actually contains, so the
   badges wrap in their own group instead of sharing a wrap context with
   DAY/WEEK - otherwise the toggle's vertical position moved with them. */
.sl-daymap__legend {
  display: flex; flex-wrap: nowrap; align-items: flex-start; justify-content: space-between;
  gap: 8px 14px; margin-top: 10px;
}
.sl-daymap__badges { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; }
.sl-daymap__range { flex: none; display: inline-flex; }
.sl-daymap__rangebtn {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 600; letter-spacing: .06em;
  color: var(--fd-ink-3); background: transparent;
  border: 1px solid var(--fd-hair);
  padding: 3px 10px; cursor: pointer;
  text-decoration: none; display: inline-block;
}
.sl-daymap__rangebtn:first-child { border-radius: 999px 0 0 999px; border-right: none; }
.sl-daymap__rangebtn:last-child  { border-radius: 0 999px 999px 0; }
.sl-daymap__rangebtn.is-active {
  color: var(--fd-ink); background: var(--fd-panel-2, var(--fd-panel));
  border-color: var(--fd-ink-3);
}
.sl-daymap__rangebtn:hover { color: var(--fd-ink); }
/* WEEK strip: 4 daypart blocks wide enough to carry their own label, unlike
   the DAY strip's many thin per-segment blocks. */
.sl-daymap__blk--week {
  display: flex; align-items: center; justify-content: center;
}
.sl-daymap__wklabel {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fd-ink); opacity: .8; white-space: nowrap;
}
.sl-daymap__weeknote { font-size: 11px; color: var(--fd-ink-3); margin: 8px 0 0; }
.sl-daymap__lgnote { font-size: 11px; color: var(--fd-ink-3); margin: 8px 0 0; }
.sl-daymap__lg {
  position: relative; padding-left: 15px;
  font-size: 11px; color: var(--fd-ink-2);
}
.sl-daymap__lg::before {
  content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 9px; height: 9px; border-radius: 3px;
}
.sl-daymap__lg.is-held::before    { background: var(--ap-green-500); }
.sl-daymap__lg.is-flat::before    { background: var(--fd-ink-3); opacity: .14; }
.sl-daymap__lg.is-typical::before { background: var(--fd-ink-3); opacity: .68; }
.sl-daymap__lg.is-soft::before    { background: var(--ap-amber-600); }
.sl-daymap__lg.is-tuneout::before { background: var(--ap-red); }
.sl-daymap__lg.is-context::before { background: var(--fd-ink-3); opacity: .40; }
@media (max-width: 640px) {
  .sl-daymap__hr:nth-child(even) { display: none; }
}
/* Speech Statistics */
.sp-stats__stations { display:flex; flex-wrap:wrap; gap:.5rem; margin:1.25rem 0 2rem; }
.sp-stats__station { color:var(--ap-text-secondary); border:1px solid var(--fd-hair); border-radius:999px; padding:.45rem .8rem; text-decoration:none; }
.sp-stats__station.is-active { color:var(--ap-text-primary); border-color:var(--fd-accent); }
/* Content-type + locals filters sit on the same line as the map's caption,
   above the map, so the reader sees what can be filtered before the visual
   rather than discovering it below a fully-read chart. */
.sp-stats__mentions-head {
  display:flex; flex-wrap:wrap; align-items:center; gap:.5rem 1rem; margin-bottom:.5rem;
}
.sp-stats__mentions-head .sp-caption { margin:0; }
.sp-stats__mentions-head .sp-stats__ct-toggle-wrap,
.sp-stats__mentions-head .sp-stats__mentions-toggle-wrap { margin:0; }
.sp-stats__ct-toggle-wrap { display:flex; justify-content:flex-end; margin:.75rem 0 1.25rem; }
.sp-stats__mentions-toggle-wrap { display:flex; justify-content:flex-end; margin:.5rem 0; }
.sp-stats__summary { display:flex; align-items:baseline; gap:.75rem; margin:1.5rem 0 2.5rem; color:var(--ap-text-secondary); }
.sp-stats__summary strong { color:var(--ap-text-primary); font-size:2rem; }
.sp-stats__cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)); gap:.75rem; margin:1rem 0; }
.sp-stats__card { display:flex; flex-direction:column; gap:.35rem; padding:1rem; border:1px solid var(--fd-hair); border-radius:10px; background:var(--fd-panel); color:var(--ap-text-secondary); }
.sp-stats__card { text-decoration:none; }
.sp-stats__card:hover { border-color:var(--fd-accent); }
.sp-stats__card:focus-visible, .sp-stats__group a:focus-visible { outline:2px solid var(--fd-accent); outline-offset:3px; }
.sp-stats__card strong { color:var(--ap-text-primary); font-size:1.1rem; }
.sp-stats__kind { color:var(--ap-text-tertiary); font-size:.72rem; letter-spacing:.08em; text-transform:uppercase; }
.sp-stats__desc { color:var(--ap-text-tertiary); font-size:.78rem; line-height:1.3; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sp-stats__section-title { margin:0 0 8px; }
.sp-joke + .sp-stats__section-title { margin-top:24px; }
/* Section intros read as normal prose (index/overview .ov-p), not the small
   muted .sp-note caption they inherited from before - captions stay small
   and sit under a chart/graph, not under a heading (2026-09-06). */
.sp-stats__section-note {
  font-family: inherit; font-size:15px; line-height:1.6; letter-spacing:normal;
  color: var(--ap-text-primary); margin:0 0 16px; max-width:68ch;
}
.sp-stats__section-note + .sp-stats__cards,
.sp-stats__section-note + .sp-stats__groups,
.sp-stats__section-note + .sp-stats__mictime,
.sp-stats__section-note + .sp-show-rank-box,
.sp-stats__section-note + .sp-stats__longest-grid { margin-top:0; }
.sp-stats__groups { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:1rem; margin-top:1rem; }
/* People/Countries/Cities (the three primary kinds) are short, 5-row lists:
   force them onto one row so Cities does not wrap alone under a big empty
   gap next to People/Countries. Extra kinds revealed by "Show more" still
   flow with auto-fit at 220px, since there is no fixed count for those. */
@media (min-width: 640px) {
  .sp-stats__groups { grid-template-columns: repeat(3, 1fr); }
}
.sp-stats__group { padding:1rem; border-top:2px solid var(--fd-hair); }
.sp-stats__group h3 { margin:0 0 .75rem; color:var(--ap-text-primary); }
.sp-stats__group ol { margin:0; padding:0; list-style:none; }
.sp-stats__group li { border-bottom:1px solid var(--fd-hair); }
.sp-stats__group li a { display:flex; justify-content:space-between; gap:1rem; padding:.45rem 0; color:var(--ap-text-secondary); text-decoration:none; }
.sp-stats__group li a:hover span { color:var(--fd-accent); }
.sp-stats__group li strong { color:var(--ap-text-primary); }
/* Monitor/terminal redesign (2026-09-06, owner's call): this box was full-
   width with a 5.5rem watermark word and read as far heavier than the
   Most Mentioned content above it. Narrowed to a small "screen" - fixed
   max-width, all-monospace, small text throughout - closer to a printed
   terminal readout you browse with a button than a report exhibit. */
.sp-stats__mentioncontext {
  margin:2rem 0 0; padding:1rem 1.1rem; max-width:460px;
  border:1px solid var(--fd-hair); border-radius:6px;
  background:var(--fd-panel-2, var(--fd-panel));
  font-family:ui-monospace,"SFMono-Regular",Menlo,Consolas,monospace;
}
/* Heading/lede/"BUT..." moved out of the box and into normal page flow
   (2026-09-10, owner's call): they read as a subheading + body copy
   introducing the exhibit, not as part of the terminal-style readout
   itself, so the box now holds only the two example cards. */
.sp-stats__mentioncontext-title { margin: 1.5rem 0 0; }
.sp-stats__mentioncontext-but { font-family: inherit; font-size: 11.5px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--ap-text-tertiary); margin: 0 0 .75rem; }
.sp-stats__mentioncontext-head { display:flex; flex-wrap:wrap; align-items:center; justify-content:flex-end; gap:.5rem 1rem; margin-bottom:.5rem; }
.sp-stats__mentioncontext-toggle { display:flex; align-items:center; gap:.85rem; line-height:1; }
.sp-stats__mentioncontext-toggle .sp-stats__more { margin:0; padding:0; line-height:1; font-size:.72rem; }
.sp-stats__mentioncontext-toggle .sp-stats__more[disabled] { opacity:.4; cursor:default; text-decoration:none; }
.sp-stats__locals-toggle { display:flex; align-items:center; gap:.4rem; color:var(--ap-text-secondary); font-size:.72rem; line-height:1; cursor:pointer; }
/* Examples stack (owner's call 2026-09-06: side-by-side read as a
   comparison table; this is two independent illustrations, one at a time
   conceptually, so they read top to bottom like a report's exhibits). */
.sp-stats__mentioncontext-examples { display:flex; flex-direction:column; gap:.6rem; margin-top:.4rem; }
.sp-stats__mentioncontext-ex { padding:.6rem .7rem; border:1px solid var(--fd-hair); border-left-width:2px; border-radius:4px; background:var(--fd-panel); }
.sp-stats__mentioncontext-ex.is-good { border-left-color: rgba(var(--ap-green-rgb), .55); }
.sp-stats__mentioncontext-ex.is-bad  { border-left-color: rgba(var(--ap-red-rgb), .55); }
.sp-stats__mentioncontext-ex p { margin:0 0 .4rem; color:var(--ap-text-secondary); font-size:.72rem; line-height:1.4; }
.sp-stats__mentioncontext-ex p:last-child { margin-bottom:0; }
.sp-stats__mentioncontext-ex p strong { color:var(--ap-text-primary); font-weight:inherit; }
/* No good/bad color coding here (owner's call 2026-09-06): this box reads
   as a plain measurement exhibit, not a status readout - green/red belongs
   to the Audience hold/loss and tier UI elsewhere, not here. */
.sp-stats__mentioncontext-meta { position:relative; margin-top:.7rem !important; padding-top:.5rem; font-size:.66rem !important; color:var(--ap-text-tertiary) !important; }
.sp-stats__mentioncontext-meta::before { content:''; position:absolute; top:0; left:0; width:28px; height:1px; background:var(--fd-hair); }
.sp-stats__mentioncontext [data-sp-pair-shuffle] { grid-column:auto; margin-top:0; }
.sp-stats__group p { color:var(--ap-text-tertiary); }
.sp-stats__mictime { margin:1rem 0; }
.sp-stats__mictime-bar { display:flex; height:14px; border-radius:999px; overflow:hidden; background:var(--fd-hair); }
.sp-stats__mictime-seg.is-male { background:#5b8def; }
.sp-stats__mictime-seg.is-female { background:#e0679a; }
.sp-stats__mictime-legend { display:flex; gap:1.5rem; margin-top:.6rem; color:var(--ap-text-secondary); }
.sp-stats__card--wide { max-width:640px; }
.sp-stats__snippet { color:var(--ap-text-tertiary); font-size:.85rem; line-height:1.5; }
.sp-stats__card[hidden], .sp-stats__group li[hidden] { display:none; }
@media (max-width: 640px) {
  /* Most Mentioned: 5 rows per group on desktop, 3 on a phone. */
  .sp-stats__group li:nth-child(n+4) { display: none; }
}
.sp-stats__more { grid-column:1/-1; margin-top:.5rem; padding:.4rem 0; border:none; background:none; color:var(--fd-accent); font-size:.85rem; cursor:pointer; text-align:left; }
.sp-stats__group .sp-stats__more { grid-column:auto; }
.sp-stats__map { width:70%; margin:1rem 0 .75rem; }
.sp-stats__map-canvas { display:block; width:100%; height:138px; }
.sp-stats__map-legend { display:flex; align-items:center; gap:.35rem; margin:.6rem 0 0; color:var(--ap-text-tertiary); font-size:.75rem; }
.sp-stats__map-dot { display:inline-block; width:8px; height:8px; border-radius:50%; }
.sp-stats__map-dot--country { background:#f5b754; }
.sp-stats__map-dot--city { background:#fb7185; margin-left:.6rem; }
@media (max-width:640px) { .sp-stats__map { width:100%; } }
.sp-stats__more:hover { text-decoration:underline; }
.sp-statentity__summary { display:flex; flex-wrap:wrap; gap:1rem; margin:1.5rem 0 2.5rem; }
.sp-statentity__metric { min-width:130px; padding:1rem; border-top:2px solid var(--fd-accent); background:var(--fd-panel); }
.sp-statentity__metric strong { display:block; color:var(--ap-text-primary); font-size:1.6rem; }
.sp-statentity__metric span { color:var(--ap-text-tertiary); font-size:.78rem; }
.sp-note + .sp-statentity__combolist { margin-top:1rem; }
.sp-statentity__combolist { list-style:none; margin:0 0 2rem; padding:0; display:flex; flex-direction:column; gap:.5rem; }
.sp-statentity__combolist li { display:flex; align-items:center; gap:.75rem; padding:.6rem .8rem; background:var(--fd-panel); border-left:2px solid var(--fd-accent); }
.sp-statentity__combolist li > span:first-child { flex:1; color:var(--ap-text-primary); }
.sp-statentity__combon { color:var(--ap-text-tertiary); font-size:.8rem; }
.sp-statentity__paircaveat { color:var(--ap-text-secondary); }


.sp-evmodal__notice {
  margin: 16px 0 12px;
  padding: 9px 12px;
  border: 1px solid var(--fd-hair);
  border-radius: 7px;
  background: var(--fd-panel);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ap-text-secondary);
}
