/* Publisher's own additions to the Eurosky design system.
 *
 * The system is vendored and generated upstream, so it must never be edited
 * here (vendor/eurosky-design/README.md says why). Anything this app needs
 * that the system does not carry lives in this file instead, written in the
 * same idiom and against the same tokens so it can be folded upstream later
 * if it turns out to be general.
 *
 * Everything below is either a missing variant of an existing component or a
 * piece of app chrome the system has no opinion about.
 */

/* ---------- Tag variants ----------
 * The system ships .es-tag and .es-tag-accent. This app shows status words in
 * tables and lists constantly — pending, held, failed, paused — and needs the
 * warning and informational colours too. Black on all of them, per the
 * guideline: white on green is 2.5:1 and fails AA.
 */
.es-tag-warning { background: var(--es-warning); border-color: var(--es-warning); color: var(--es-fg-on-accent); }
.es-tag-info { background: var(--es-info); border-color: var(--es-info); color: var(--es-fg-on-accent); }

/* ---------- Destructive button ----------
 * The palette has no red, deliberately. A destructive action is therefore an
 * outlined button carrying the warning colour on its edge rather than a filled
 * alarm: it should read as "this one is different", not as a klaxon.
 */
.es-btn-danger { border-color: var(--es-warning); }
.es-btn-danger:hover { background: var(--es-warning); border-color: var(--es-warning); color: var(--es-fg-on-accent); }

/* ---------- Wordmark ----------
 * The header logo. Explicitly not a link in appearance: base.css underlines
 * every anchor and this one must not be underlined or tinted, so it states
 * both rather than relying on a utility winning a specificity argument.
 * currentColor throughout, so the mark is black on cotton and cotton on black
 * and never recoloured — which is what the guideline requires of it.
 */
.pb-wordmark-link, .pb-wordmark-link:hover { text-decoration: none; color: var(--es-fg); }
.pb-wordmark { display: inline-flex; align-items: flex-start; gap: 1px; font-size: var(--es-text-lg); font-weight: var(--es-weight-medium); letter-spacing: var(--es-tracking-tight); line-height: 1; }
.pb-star { width: .42em; height: .42em; fill: currentColor; flex: none; }

/* ---------- Theme switch ---------- */
.pb-theme { display: inline-flex; border: var(--es-border-hairline) solid var(--es-border-strong); border-radius: var(--es-radius-pill); overflow: hidden; }
.pb-theme button { border: 0; background: transparent; color: inherit; padding: 2px var(--es-space-2); font-family: var(--es-font-mono); font-size: var(--es-text-xs); cursor: pointer; line-height: 1.6; }
.pb-theme button[aria-pressed="true"] { background: var(--es-fg); color: var(--es-bg); }

/* ---------- Inline emphasis ----------
 * Text that carries a state rather than sitting in a banner: an error line
 * under a field, "posting stopped" beside a feed name. The palette has no red,
 * so these lean on weight and a rule rather than on colour alone — which also
 * means they survive being read by someone who cannot separate red from green.
 */
.es-danger { color: var(--es-fg); font-weight: var(--es-weight-medium); }
.es-ok { color: var(--es-fg); }

/* A row that needs an operator now. The accent edge does the work; the row
 * itself stays a card so a list of them reads evenly.
 */
.pb-urgent { border-color: var(--es-warning); border-left-width: 3px; }

/* A section break inside a page, matching the system's hairline rules. */
.es-rule-top { border-top: var(--es-border-hairline) solid var(--es-border); }

/* Where an avatar will be once we have resolved one. */
.es-avatar-blank { background: var(--es-bg-sunken); }

/* ---------- Page chrome ----------
 * A full-height frame rather than a centred column: a top bar with a strong
 * rule under it, a linen sidebar that runs the height of the window, and the
 * page itself in a measure that does not grow with the screen. The sidebar
 * is 200px and the measure 900px because a table of seven numeric columns
 * fits in that and a line of prose is still readable across it; wider and
 * the two stop agreeing.
 */
:root {
  --pb-sidebar-w: 200px;
  --pb-measure: 900px;      /* the ordinary page */

  /* Rail and page are one block, and it is the block that gets centred — not
     the page inside a rail pinned to the left edge. Capping the two separately
     is what left the app hugging the left of a wide screen with a field of
     cotton beside it. One variable so the top bar can line its contents up with
     the same edges; a page that needs the wider measure sets it on <body>, so
     the bar widens with the page rather than drifting out of step with it. */
  --pb-block: calc(var(--pb-sidebar-w) + var(--pb-measure));
}


/* The operator's area takes the window, and this is the one place in the app
   where that is right. Nothing here is prose to be read across: it is the
   registry, the feed list, the services page — tables of eight and nine
   columns whose rows are scanned, and the widest of them were scrolling
   inside their cards on a screen with 800px of cotton on either side. Every
   paragraph on these screens keeps its own max-width (--es-layout-measure),
   so the sentences do not stretch with the tables.

   100% rather than a bigger number: a cap is a guess about somebody else's
   monitor, and --pb-main's own padding is what keeps the content off the
   edges. The top bar reads the same variable, so the two still line up. */
.pb-admin { --pb-block: 100%; }

/* The publisher's own pages take the window as well (EUR-404). They were a
   900px column in the middle of the screen, 960px for analytics, with every
   paragraph stopped at 65 characters — so on a laptop a third of the width
   was empty on either side, two cards side by side squeezed a four-word
   setting onto four lines, and a notice ended halfway across its own card.
   Their text runs to the edge of whatever holds it: a card, a row, the page.
   So do the cards that hold a form (.pb-form-narrow, .pb-settings-card and
   the rows under them), which were pinned to 600px at the left of a wide
   page; only the text fields inside keep a width somebody can read along,
   because a field as wide as the window is harder to use, not easier. The
   onboarding pages and the legal texts have a layout of their own. */
.pb-publisher { --pb-block: 100%; }
.pb-publisher .pb-main :is(p, ul, ol, .pb-lede, .pb-ruled-desc, .pb-section-sub, .pb-notes, .pb-step-note,
                           .pb-footnote, .pb-subtitle, .pb-reply-text) { max-width: none; }
.pb-publisher .pb-main :is(.pb-form-narrow, .pb-settings-card, .pb-remove, .pb-add-reporters) { max-width: none; }
/* On a line of its own, as it was when it filled the card — capped, it would
   otherwise sit beside its label. A table's search box is the exception: it
   spans the table it searches (docs/TABLES.md). */
.pb-publisher .pb-main :is(input.es-input, textarea.es-input, .es-textarea):not(.pb-table-tools *) { display: block; max-width: 40rem; }

.pb-shell { display: grid; grid-template-columns: var(--pb-sidebar-w) minmax(0, 1fr); flex: 1; width: 100%; max-width: var(--pb-block); margin-inline: auto; }
.pb-main { min-width: 0; padding: var(--es-space-6) var(--es-space-7) var(--es-space-7); display: flex; flex-direction: column; gap: var(--es-space-5); }

/* Analytics sets its sections further apart than anything else: its parts are
   whole subjects rather than groups of fields, and at 24px they ran together
   into one long sheet of numbers. */
.pb-main-analytics { gap: var(--es-space-6); }

/* ---------- Top bar ----------
 * The wordmark, and on the right the three things that are about you rather
 * than about the page: where else you can go, who you are signed in as, and
 * the way out. The rule under it is the strong one — it separates the app
 * from its chrome, which is the one boundary on the page that is not a
 * section break.
 *
 * .es-topbar is the system's, and stays the system's: only the padding and the
 * ground change here. The bar sits on the page colour rather than on nothing,
 * because the rail below it is linen and a transparent bar would let the two
 * meet with no edge between them.
 */
.es-topbar { display: block; padding: 0; background: var(--es-bg); }
.pb-topbar-inner { display: flex; align-items: center; justify-content: space-between; gap: var(--es-space-4); width: 100%; max-width: var(--pb-block); margin-inline: auto; padding: var(--es-space-4) var(--es-space-6); }

/* The funnel wears the same bar but has no rail under it, so its contents line
   up with its own container instead of with the app's block. */
.ob-topbar .pb-topbar-inner { max-width: var(--es-layout-container); padding-inline: var(--es-space-5); }
.es-topbar-handle { font-size: 13px; color: var(--es-fg-muted); white-space: nowrap; }
.pb-topbar-right { display: flex; align-items: center; gap: var(--es-space-4); }
.pb-topbar-left { display: flex; align-items: center; gap: var(--es-space-3); }
.pb-topbar-link { font-size: var(--es-text-sm); text-decoration: none; }

/* The real mark, as a file. It was an inline SVG — the word set in Inter with a
   star beside it — which took currentColor and so needed nothing for the black
   bar. But the logotype is drawn, not typeset: its own face, its own spacing,
   the star raised as a superscript. An approximation of a logo is a different
   logo, so this is the asset, and the one place that needs it light inverts it. */
.pb-logo { height: 22px; width: auto; }

.pb-title { font-size: var(--es-text-2xl); letter-spacing: var(--es-tracking-tight); margin-bottom: var(--es-space-1); }
.pb-subtitle { color: var(--es-fg-muted); font-size: var(--es-text-sm); }

/* Section heading with the action that belongs to it on the same line. */
.pb-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--es-space-3); margin-bottom: var(--es-space-4); }

/* Stack of cards down a page. */
.pb-stack { display: grid; gap: var(--es-space-5); }


/* ---------- The frame ----------
 * The rail is a field, not a column of links, so it has to reach the bottom of
 * the window even on a page with four rows on it. That means the body is the
 * flex container and the shell is what grows — min-height on the shell alone
 * would leave it measuring from the top of the page rather than from under the
 * bar.
 */
.pb-app { min-height: 100vh; display: flex; flex-direction: column; }

/* ---------- The operator's bar ----------
 * Inverted, and the only inverted surface in the app. The wordmark is a black
 * PNG, so it is inverted in CSS rather than shipped twice — the mark is pure
 * black on transparent and inverting it gives exactly the white the guideline
 * allows it to be.
 */
.es-topbar-admin { background: var(--es-fg); color: var(--es-bg); border-bottom-color: var(--es-fg); }
.es-topbar-admin a, .es-topbar-admin .es-topbar-handle { color: var(--es-bg); }
.es-topbar-admin .es-topbar-handle { color: color-mix(in srgb, var(--es-bg) 70%, var(--es-fg)); }
.es-topbar-admin .es-btn { border-color: var(--es-bg); color: var(--es-bg); background: transparent; }
.es-topbar-admin .es-btn:hover { background: var(--es-bg); color: var(--es-fg); }
.pb-logo-invert { filter: invert(1); }
.pb-admin-mark { font-family: var(--es-font-mono); font-size: var(--es-text-xs); text-transform: uppercase; letter-spacing: .08em; color: var(--es-neutral); }
\n/* ---------- Sidebar ----------
 * Four rows on linen, running the full height of the window. The current one
 * is lifted onto the page's own white and carries a green edge: two signals
 * for one state, because the edge alone is a 3px cue and the white is what
 * makes the row read as continuous with the page beside it.
 *
 * Squared, not rounded. The system's nav rounds its rows, which works for a
 * floating panel; against a full-height field a rounded row reads as a button
 * someone left there.
 */
.pb-sidebar { background: var(--es-bg-sunken); padding-block: var(--es-space-5); }
.pb-sidebar .es-sidenav { display: flex; flex-direction: column; gap: 2px; width: auto; min-height: 0; padding: 0; background: none; border: 0; }
.pb-sidebar .es-sidenav a { display: block; padding: 10px var(--es-space-5); font-size: var(--es-text-sm); text-decoration: none; border-left: 2px solid transparent; border-radius: 0; }
.pb-sidebar .es-sidenav a[aria-current="page"] { font-weight: var(--es-weight-medium); background: var(--es-bg-raised); border-left-color: var(--es-accent); }
.pb-sidebar .es-sidenav a:hover { background: color-mix(in srgb, var(--es-bg-raised) 50%, transparent); }

/* Sub-items appear only under the open section, so they are indented to sit
   under their parent's text rather than its border, and quieter than it. The
   current one drops the border entirely: the parent already carries the mark
   for the branch, and two green edges would argue about which is the page. */
.pb-subnav a { padding: var(--es-space-2) var(--es-space-5) var(--es-space-2) 40px; font-size: 13px; color: var(--es-fg-muted); border-left: 0; }
.pb-subnav a[aria-current="page"] { color: var(--es-fg); font-weight: var(--es-weight-medium); background: none; border-left: 0; }

/* On a phone the sidebar becomes a tab row under the header — not a hamburger.
   Four sections is not enough to hide behind a button, and a menu that has to
   be opened to say where you are is worse than one that scrolls. */
@media (max-width: 47.99rem) {
  .pb-shell { grid-template-columns: minmax(0, 1fr); }
  .pb-sidebar { padding-block: 0; border-bottom: var(--es-border-hairline) solid var(--es-border); }
  .pb-sidebar .es-sidenav { flex-direction: row; overflow-x: auto; gap: 0; }
  .pb-sidebar .es-sidenav a { padding: var(--es-space-2) var(--es-space-3); border-left: 0; border-bottom: 2px solid transparent; white-space: nowrap; min-height: 44px; display: flex; align-items: center; }
  .pb-sidebar .es-sidenav a[aria-current="page"] { background: none; border-left-color: transparent; border-bottom-color: var(--es-accent); }
  /* Children that are real pages stay a list — they are navigation, and a tab
     row with four tabs has room for three more links under the open one.
     Children that are anchors on this page become the select below. */
  .pb-subnav-anchors { display: none; }
  .pb-subnav { flex-direction: row; overflow-x: auto; margin: 0; padding-bottom: var(--es-space-2); }
  .pb-subnav a { padding: var(--es-space-2) var(--es-space-3); white-space: nowrap; }
  .pb-jump { display: block; padding: var(--es-space-2) var(--es-space-3) var(--es-space-3); }
  .pb-main { padding: var(--es-space-5) var(--es-space-4) var(--es-space-6); }
}

/* The count of things waiting on an operator, on its own nav row. */
.pb-badge { display: inline-block; min-width: 1.5em; padding: 0 var(--es-space-1); border-radius: var(--es-radius-pill); background: var(--es-accent); color: var(--es-fg-on-accent); font-family: var(--es-font-mono); font-size: var(--es-text-xs); text-align: center; }

/* ---------- Flash ---------- */
.pb-flash { margin-bottom: var(--es-space-5); }

/* ---------- Definition rows ----------
 * "label: value" lines, which this app is full of — limits, status, contact.
 */
.pb-rows { display: grid; gap: var(--es-space-2); margin: 0; }
.pb-row { display: flex; flex-wrap: wrap; gap: var(--es-space-2) var(--es-space-4); justify-content: space-between; font-size: var(--es-text-sm); }
.pb-row dt { color: var(--es-fg-muted); }
.pb-row dd { margin: 0; }


/* ---------- Buttons: squared and compact ----------
   A deliberate divergence from the vendored system, which specifies pill
   buttons. This app is an administrative tool with four or five actions on a
   row, and at pill radius and 2.5rem tall they read as five equally loud
   suggestions rather than as controls. Squared to the same 4px the inputs use,
   so a row of buttons sits in the same visual family as the form beside it.
   .es-btn-lg keeps its size: the funnel has exactly one call to action per
   screen and that one should be loud. */
.es-btn { border-radius: var(--es-radius-sm); min-height: 2rem; padding-inline: var(--es-space-3); }
.es-btn-lg { min-height: 2.75rem; padding-inline: var(--es-space-5); border-radius: var(--es-radius-sm); }

/* A row of actions where one of them matters. The rest stay outlined and sit
   together so the eye reads "the thing to do, and some other things". */
.pb-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--es-space-2); }
.pb-actions form { display: inline-flex; }

/* ---------- Notices: a rule, not a slab ----------
   The system's .es-banner is a one-line strip with a tag in it — flex, centred,
   saturated fill. Ours carry two sentences of explanation, and a paragraph of
   prose on a solid yellow field is both hard to read and much louder than what
   it says. The colour moves to a 3px edge and the text sits on the ordinary
   raised background, which is what "rules, not shadows" means here. */
.es-banner {
  display: block; padding: var(--es-space-3) var(--es-space-4);
  background: var(--es-bg-raised); color: var(--es-fg);
  border: var(--es-border-hairline) solid var(--es-border);
  border-left: 3px solid var(--es-neutral);
  border-radius: var(--es-radius-sm); line-height: var(--es-leading-normal);
}
.es-banner-accent { background: var(--es-bg-raised); color: var(--es-fg); border-left-color: var(--es-accent); }
.es-banner-info { background: var(--es-bg-raised); color: var(--es-fg); border-left-color: var(--es-info); }
.es-banner-warning { background: var(--es-bg-raised); color: var(--es-fg); border-left-color: var(--es-warning); }
.es-banner strong { display: block; margin-bottom: var(--es-space-1); }
.es-banner p { margin: 0; }
.es-banner p + p { margin-top: var(--es-space-2); }
.es-banner a { color: inherit; }

/* Section headings inside a card. The page title is the large type; a card's
   own heading competing with it is what made these screens read as a stack of
   unrelated posters. */
.pb-card-title { font-size: var(--es-text-md); font-weight: var(--es-weight-medium); margin: 0 0 var(--es-space-3); }

/* A list of things you can open. Rows, not prose: the whole row is the target,
   which is the difference between a list and a paragraph that mentions names. */
.pb-list { display: grid; border: var(--es-border-hairline) solid var(--es-border); border-radius: var(--es-radius-md); overflow: hidden; }
.pb-list-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--es-space-2) var(--es-space-3); padding: var(--es-space-3) var(--es-space-4); border-bottom: var(--es-border-hairline) solid var(--es-border); text-decoration: none; color: var(--es-fg); }
.pb-list-row:last-child { border-bottom: 0; }
.pb-list-row:hover { background: var(--es-bg-sunken); }
.pb-list-row .pb-list-meta { color: var(--es-fg-muted); font-size: var(--es-text-xs); min-width: 0; overflow-wrap: anywhere; }

/* ---------- Sections ----------
   Every screen behind sign-in is a stack of sections, and they were each
   inventing their own heading size and spacing — so the pages read as a list of
   things underneath each other rather than as a page with parts. One rule
   above, one heading size, one gap. The rule is what does the work: it is the
   only thing on these screens that says "a new part starts here". */
.pb-section { margin-top: var(--es-space-6); padding-top: var(--es-space-5); border-top: var(--es-border-hairline) solid var(--es-border); }
.pb-section:first-of-type { margin-top: var(--es-space-5); }
.pb-section-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--es-space-2) var(--es-space-4); margin-bottom: var(--es-space-3); }
.pb-section-head h2 { font-size: var(--es-text-md); font-weight: var(--es-weight-medium); margin: 0; }
.pb-section-sub { margin: var(--es-space-1) 0 0; font-size: var(--es-text-sm); color: var(--es-fg-muted); max-width: var(--es-layout-measure); }
/* A section in parts, each with a table of its own (shared followers: the
   pinned competitors, then every outlet). Smaller than the section's heading,
   never larger, with the section's gap above it. Rules here rather than
   utilities in the markup: this file is unlayered and outranks them. */
.pb-subhead { font-size: var(--es-text-sm); font-weight: var(--es-weight-medium); margin: var(--es-space-5) 0 var(--es-space-2); }
.pb-subhead + .pb-section-sub { margin: 0 0 var(--es-space-2); }
.pb-metrics + .pb-section-sub { margin-top: var(--es-space-3); }
/* A row's pin, as narrow as the button in it. */
.pb-pin-cell { width: 1%; padding-inline-end: 0; }

/* The page's own header. One title, one line under it, the actions beside. */
.pb-head .pb-subtitle { max-width: var(--es-layout-measure); margin-top: var(--es-space-1); }

/* The language picker in the top bar: a select rather than a row of links,
   because seven two-letter links is a lot of furniture for something most
   people touch once. */
.pb-locale-select { min-height: 2rem; width: auto; padding-block: 0; padding-inline: var(--es-space-2); font-family: var(--es-font-mono); font-size: var(--es-text-xs); }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

/* Sub-navigation, shown only while the reader is inside that part of the app.
   Indented and quieter than its parent, and the parent keeps a mark of its own
   so the open branch is legible without two rows both claiming to be current. */
.pb-subnav { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 var(--es-space-2); }
.pb-subnav a { padding-left: var(--es-space-5); font-size: var(--es-text-xs); color: var(--es-fg-muted); }
.pb-subnav a[aria-current="page"] { color: var(--es-fg); }
.es-sidenav a.is-open { font-weight: var(--es-weight-medium); }

/* Notices sit above the page header and needed room between the two: flush
   against an h1 they read as part of the title rather than as something said
   before it. */
.es-banner + .pb-head, .es-card + .pb-head { margin-top: var(--es-space-6); }

/* ---------- Reactions ------------------------------------------------------
   The numbers the network sends back. Four totals, a column a day, and the
   tables underneath them.

   One series, in the accent, rather than four colours stacked: this system has
   three chromatic hues and no red, so the stack is four steps of one hue
   rather than four colours imported from somewhere else. What the chart is for
   is the shape of the month; the exact composition is in the tables. */
.pb-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--es-space-3); }
@media (min-width: 60rem) { .pb-metrics { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.pb-metric dt { font-family: var(--es-font-mono); font-size: var(--es-text-xs); color: var(--es-fg-muted); }
.pb-metric dd { margin: var(--es-space-1) 0 0; font-size: var(--es-text-lg); font-weight: var(--es-weight-medium); }
.pb-metric-delta { display: block; margin-top: 2px; font-size: var(--es-text-xs); color: var(--es-fg-muted); font-weight: var(--es-weight-regular); }

/* A figure that is a MODEL rather than a count. Sized like a headline, because
   it is the answer the section exists to give — but deliberately not set in the
   accent colour the observed numbers carry, so it does not read as one more
   thing we measured. */
.pb-metric-lead { margin: 0; font-size: var(--es-text-lg); font-weight: var(--es-weight-medium); }

/* What a modelled figure is missing, one failing to a line. A list rather than
   a paragraph: the statements pull in opposite directions — the number is too
   high for one reason and too low for another — and run together as prose the
   second reads as walking back the first. */
.pb-notes { margin: var(--es-space-2) 0 0; padding-left: var(--es-space-4); font-size: var(--es-text-sm); color: var(--es-fg-muted); max-width: var(--es-layout-measure); }
.pb-notes li { margin-top: var(--es-space-1); }
/* More reactions than last month is good news and the only direction worth
   colouring. Fewer is not a fault, and painting it red would say it is. */
.pb-delta-up { color: var(--es-accent); }

.pb-chart { display: block; width: 100%; height: auto; }
.pb-chart-grid { stroke: var(--es-border); stroke-width: 1; }
.pb-chart-label { fill: var(--es-fg-muted); font-size: 9px; font-family: var(--es-font-mono); }
.pb-chart-hit { fill: transparent; }
.pb-day-picked { margin: var(--es-space-3) 0 0; font-size: var(--es-text-sm); }
/* A chart whose columns pick a day. The picked one stays solid and the rest
   fade to a ghost of themselves — the shape of the period stays readable
   while the page says which day it is about. */
.pb-chart-pickable .pb-chart-col { cursor: pointer; outline: none; }
.pb-chart-pickable .pb-chart-bar { transition: opacity var(--es-motion-fast) var(--es-motion-ease); }
.pb-chart.has-selection .pb-chart-col:not(.is-selected) .pb-chart-bar { opacity: 0.25; }
.pb-chart.has-selection .pb-chart-col:not(.is-selected):hover .pb-chart-bar { opacity: 0.55; }
.pb-chart-pickable .pb-chart-col:hover .pb-chart-hit { fill: color-mix(in srgb, var(--es-fg) 5%, transparent); }
.pb-chart-pickable .pb-chart-col:focus-visible .pb-chart-hit { stroke: var(--es-focus); stroke-width: 2; }

/* The four kinds stacked, in four steps of ONE hue.
 *
 * Not four hues: the system has three chromatic ones and no red, so a
 * categorical palette would mean importing colours it does not have. A stack
 * is parts of a whole anyway, which is what a ramp is for — and a ramp that
 * varies only in lightness is the one encoding that still works for a reader
 * who cannot separate red from green.
 *
 * Mixed toward --es-fg rather than toward black, so the ramp runs green→black
 * on the light theme and green→white on the dark one. Mixing toward the card
 * in dark mode would have faded the top of every column into the card. */
:root {
  --pb-chart-like: var(--es-accent);
  --pb-chart-repost: color-mix(in oklab, var(--es-accent) 76%, var(--es-fg));
  --pb-chart-reply: color-mix(in oklab, var(--es-accent) 54%, var(--es-fg));
  --pb-chart-quote: color-mix(in oklab, var(--es-accent) 32%, var(--es-fg));
}

/* Adjacent steps of a ramp sit well under 3:1 against each other, so the
   boundary is drawn rather than left to the colours: a hairline in the card's
   own colour, which is also what separates a column from the one behind it. */
.pb-chart-bar { fill: var(--es-accent); stroke: var(--es-bg-raised); stroke-width: 0.75; }
.pb-chart-bar-like { fill: var(--pb-chart-like); }
.pb-chart-bar-repost { fill: var(--pb-chart-repost); }
.pb-chart-bar-reply { fill: var(--pb-chart-reply); }
.pb-chart-bar-quote { fill: var(--pb-chart-quote); }

/* The Topics table's week-by-week columns: small, in the accent, and never a
   second colour — a topic's row says which topic it is, and a hue a topic
   would be a palette the system does not have. A week with too few posts is a
   hairline on the baseline, so an empty week reads as "not drawn" rather than
   as "nothing". */
.pb-spark { display: block; overflow: visible; }
.pb-spark-bar { fill: var(--es-accent); }
.pb-spark-gap { fill: var(--es-border); }
.pb-spark g:hover .pb-spark-bar { fill: color-mix(in oklab, var(--es-accent) 70%, var(--es-fg)); }
/* "All posts" is the row the topics are read against, so it sits apart from
   them: a rule under it, and it does not move when the topics are sorted. */
.pb-topics .pb-topics-all td { border-bottom: var(--es-border-hairline) solid var(--es-border-strong); }

/* A post's topic beside its title: small, and set off from the words it
   follows so it does not read as the end of the headline. */
.pb-topic-badge { margin-left: var(--es-space-2); vertical-align: 1px; }
.pb-subtitle .pb-topic-badge { margin-left: 0; }

.pb-chart-key { display: flex; flex-wrap: wrap; gap: var(--es-space-1) var(--es-space-4); margin: var(--es-space-2) 0 0; padding: 0; list-style: none; font-size: var(--es-text-xs); color: var(--es-fg-muted); }
.pb-chart-key li { display: flex; align-items: center; gap: var(--es-space-2); }
.pb-chart-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; background: var(--es-accent); }
.pb-chart-swatch.pb-chart-bar-like { background: var(--pb-chart-like); }
.pb-chart-swatch.pb-chart-bar-repost { background: var(--pb-chart-repost); }
.pb-chart-swatch.pb-chart-bar-reply { background: var(--pb-chart-reply); }
.pb-chart-swatch.pb-chart-bar-quote { background: var(--pb-chart-quote); }

/* Who reacted: a face, a name, and the size of the crowd behind them.
   The avatar is 32px and never larger — it is a hint at recognition, not a
   portrait, and a table of portraits stops being a table. */
.pb-reader { display: flex; align-items: center; gap: var(--es-space-3); }
.pb-avatar { flex: none; width: 32px; height: 32px; border-radius: 50%; object-fit: cover; background: var(--es-bg-sunken); }
.pb-avatar-empty { border: var(--es-border-hairline) dashed var(--es-border); }

/* The network's own mark, in the one chromatic hue that is not the accent — so
   "verified" never reads as "good" the way a green tick would, and an outlet
   scanning the column is not being told who to approve of. */
/* Ours in the palette's blue; Bluesky's quieter, and an account that issues
   verifications quieter still. Not because one is worth less, but because on
   this service ours is the one that says something about us — and none of them
   is the accent, so "verified" never reads as "approved of". */
.pb-check { margin-left: 0.25em; font-size: 0.8em; color: var(--es-palette-blue); }
.pb-check-eurosky { color: var(--es-palette-blue); }
.pb-check-bluesky { color: color-mix(in oklab, var(--es-palette-blue) 60%, var(--es-fg-muted)); }
.pb-check-verifier { color: var(--es-fg-muted); }

/* A sortable header stays a header: the link takes the text's own colour and
   only the column actually in use carries a mark. */
.pb-sort { color: inherit; text-decoration: none; white-space: nowrap; }
/* The newsroom list sorts in the browser, so its headers are buttons: the same
   look as the links, none of a button's own. */
button.pb-sort { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; text-align: inherit; }
.pb-sort:hover { text-decoration: underline; }
.es-table th.is-sorted { color: var(--es-fg); }
.pb-sort-mark { margin-left: 0.25em; font-size: 0.85em; }

/* Numbers in a column read as a column: same width per digit, right-aligned. */
.pb-num { text-align: right; font-variant-numeric: tabular-nums; }
/* A number column's header sits over its numbers. The design system's
   `.es-table th { text-align: left }` outranks the bare class above, so every
   numeric header was flush left over right-aligned figures — most visible in a
   wide table like Topics, where "Reactions per post" sat a column's width away
   from its own numbers. */
.es-table th.pb-num { text-align: right; }


/* ========== The shared rules ===============================================
 *
 * docs/DESIGN_HANDOFF.md states twelve rules that apply to every screen rather
 * than to any one of them. Nine of those are shape and live here, so a screen
 * being converted spends its markup on what it says and none on restating the
 * house style. The other three are arrangement (one title per page, admin is
 * its own area, methodology collapses into a footnote) and belong to the views.
 *
 * Where the vendored system already gets a rule right, nothing appears below:
 * black links, focus rings, the 120ms motion and the input states are all in
 * vendor/eurosky-design/ already, and repeating them here would only create a
 * second place to change them.
 */

/* ---------- Rule 2 · One title per page ----------
 * 32px, one weight up, tracked in. Never repeated as a card heading beneath —
 * that habit is what made these pages read as a stack of unrelated posters.
 */
.pb-titlebar { display: flex; align-items: center; flex-wrap: wrap; gap: var(--es-space-3); }
.pb-titlebar h1 { font-size: var(--es-text-2xl); line-height: var(--es-leading-tight); font-weight: var(--es-weight-medium); letter-spacing: var(--es-tracking-tight); margin: 0; min-width: 0; overflow-wrap: anywhere; }

/* ---------- Rule 3 · The state tag trails the title ----------
 * Three tones, not one per word. Green is running, yellow is waiting or
 * stopped-but-recoverable, charcoal is switched off by somebody. Black text on
 * all three: white on green is 2.5:1 and fails AA, and the yellow and charcoal
 * would need a different text colour again, so one rule covers all of them.
 */
.pb-state { font-family: var(--es-font-mono); font-size: var(--es-text-xs); font-weight: var(--es-weight-medium); line-height: 1; padding: 6px var(--es-space-2); border-radius: var(--es-radius-sm); white-space: nowrap; color: var(--es-fg-on-accent); background: var(--es-neutral); }
.pb-state-live { background: var(--es-accent); }
.pb-state-pending { background: var(--es-warning); }
.pb-state-off { background: var(--es-neutral); }

/* The same word inside a table or a list, where it trails a name rather than a
   title and must not outweigh it. */
.pb-state-sm { font-size: 11px; padding: 4px 6px; }

/* ---------- Rule 4 · One sentence under the title ----------
 * It carries the state in prose — "Posting is paused until you sign in again."
 * — which is why the screens below have no banners: the banner was always
 * saying what this sentence says, only louder and in a box.
 */
.pb-lede { margin: var(--es-space-2) 0 0; font-size: var(--es-text-md); color: var(--es-fg-muted); max-width: var(--es-layout-measure); }

/* ---------- Rule 5 · Ruled rows ----------
 * A card is for a group of fields. A single setting or a single status is a
 * ruled row instead: a strong rule above it, the label and its explanation on
 * the left, the control or the tag on the right. Putting one switch in a card
 * gives it a frame, a header and a shadow's worth of weight for something that
 * is one line long.
 */
.pb-ruled { border-top: var(--es-border-hairline) solid var(--es-border-strong); }
.pb-ruled-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--es-space-4); align-items: center; padding: var(--es-space-4) 0; border-bottom: var(--es-border-hairline) solid var(--es-border); }
.pb-ruled-row:last-child { border-bottom: 0; }
.pb-ruled-row > :first-child { min-width: 0; }
.pb-ruled-label { font-size: var(--es-text-sm); font-weight: var(--es-weight-medium); }
.pb-ruled-desc { margin: 2px 0 0; font-size: 13px; color: var(--es-fg-muted); max-width: var(--es-layout-measure); }
@media (max-width: 47.99rem) {
  .pb-ruled-row { grid-template-columns: minmax(0, 1fr); gap: var(--es-space-2); }
}

/* ---------- Rule 6 · Card anatomy ----------
 * The system's card is close but not this: its header is 12px muted over a
 * stone hairline, and here the header is a label rather than a heading — 10px
 * mono, letterspaced, over the strong rule. Small enough that it cannot argue
 * with the page title, and ruled strongly enough to say the card starts here.
 */
.es-card-header { padding: var(--es-space-4) var(--es-space-5) var(--es-space-3); font-size: 10px; text-transform: uppercase; letter-spacing: .08em; color: var(--es-fg-muted); border-bottom: var(--es-border-hairline) solid var(--es-border-strong); }
.es-card-body { padding: var(--es-space-4) var(--es-space-5); }

/* Rows inside a card, separated by hairlines rather than gaps: a list of
   settings is a list, and gaps alone make it four floating paragraphs. */
.pb-card-rows > * { padding: var(--es-space-4) var(--es-space-5); border-bottom: var(--es-border-hairline) solid var(--es-border); }
.pb-card-rows > :last-child { border-bottom: 0; }

/* ---------- Rule 7 · Buttons ----------
 * 14px, one weight up, 10 by 14. The system sizes by min-height, which at
 * these paddings leaves a button taller than the text needs; the padding is
 * the size here so a button sits on the same baseline grid as the input beside
 * it. Never more than one primary per region — that rule is the views' to keep.
 */
.es-btn { min-height: 0; padding: 10px 14px; font-size: var(--es-text-sm); font-weight: var(--es-weight-medium); line-height: 1; border-radius: var(--es-radius-sm); }
.es-btn-sm { padding: var(--es-space-2) var(--es-space-3); font-size: 13px; }
.es-btn-lg { min-height: 0; padding: 14px var(--es-space-5); font-size: var(--es-text-md); }

/* The third kind: an action that is a link, because it goes somewhere rather
   than doing something. Black, unboxed, and an arrow when it leaves the page. */
.pb-tertiary { font-size: var(--es-text-sm); text-decoration: none; }
.pb-tertiary:hover { text-decoration: underline; }

/* ---------- Rule 10 · Inline problems ----------
 * A feed that 404s, a sign-in that ran out. The palette has no red and this is
 * the reason it does not need one: a yellow edge and a line of plain prose
 * says "this is broken" without a klaxon, and survives being read by someone
 * who cannot separate red from green.
 */
.pb-problem { border-left: 3px solid var(--es-warning); padding-left: var(--es-space-2); }

/* The way out of a problem, where there is one to take: the same edge in the
   accent, a heading of its own and a primary button, so it reads as the thing
   to do rather than a muted footnote under the warning (the Bluesky upload
   limit and the move to Eurosky, shared/_pds_allowance). */
.pb-remedy { border-left: 3px solid var(--es-accent); padding-left: var(--es-space-2); }
.pb-remedy-title { font-size: var(--es-text-md); font-weight: var(--es-weight-semibold); }
.pb-remedy-body { margin: var(--es-space-1) 0 0; font-size: var(--es-text-sm); color: var(--es-fg); }
.pb-remedy-action { display: flex; flex-direction: column; align-items: center; gap: var(--es-space-1); }
.pb-remedy-action .es-mono { font-size: var(--es-text-xs); }
@media (max-width: 47.99rem) {
  .pb-remedy-action { align-items: flex-start; }
}
/* Between the funnel's own blocks, which are spaced by margins rather than a gap. */
.ob-main .pb-pds { margin-bottom: var(--es-space-5); }

/* ---------- Rule 11 · The footnote ----------
 * Every page that counts something has a paragraph explaining that the count
 * is a floor, or where the number came from. Said inside each section
 * it is three interruptions; said once at the foot it is a note.
 */
.pb-footnote { margin-top: var(--es-space-5); padding-top: var(--es-space-3); border-top: var(--es-border-hairline) solid var(--es-border); font-size: var(--es-text-xs); color: var(--es-fg-muted); max-width: var(--es-layout-measure); }

/* ---------- No shadows ----------
 * The guideline is built on rules and fields, not on depth. The only box-shadow
 * in the system is the input focus ring, which is a ring rather than a shadow.
 */
.es-card, .pb-list, .es-banner { box-shadow: none; }


/* ========== The first-run checklist ========================================
 *
 * A numbered sequence, because what a publisher lacks on their first visit is
 * an order rather than information. The five banners this replaced each said
 * something true and none of them said what to do first.
 *
 * The mark in the left column carries the state, in three forms rather than
 * three colours: filled and ticked is done, outlined in black is the one to do
 * now, outlined in charcoal with the text muted is not yet. A reader who cannot
 * separate the green from the grey still has fill-vs-outline and a tick.
 */
.pb-step { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: var(--es-space-4); padding: var(--es-space-5); border-bottom: var(--es-border-hairline) solid var(--es-border); }
.pb-step:last-child { border-bottom: 0; }

.pb-step-mark { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; font-size: var(--es-text-sm); font-weight: var(--es-weight-medium); line-height: 1; }
.pb-step.is-done .pb-step-mark { background: var(--es-accent); color: var(--es-fg-on-accent); font-weight: var(--es-weight-semibold); }
.pb-step.is-current .pb-step-mark { border: 1.5px solid var(--es-fg); }
.pb-step.is-upcoming .pb-step-mark { border: 1.5px solid var(--es-neutral); color: var(--es-fg-muted); }
.pb-step.is-upcoming .pb-step-title { color: var(--es-fg-muted); }

.pb-step-body { display: flex; flex-direction: column; gap: var(--es-space-3); min-width: 0; }
.pb-step-body > :first-child { margin-top: 0; }
.pb-step-title { font-size: var(--es-text-md); font-weight: var(--es-weight-medium); }
.pb-step-note { margin: 0; font-size: var(--es-text-sm); color: var(--es-fg-muted); max-width: var(--es-layout-measure); }
.pb-step-hint { margin: 0; font-size: 13px; color: var(--es-fg-muted); }
.pb-step-mono { font-family: var(--es-font-mono); font-size: 13px; color: var(--es-fg-muted); }

/* A title and its own line of explanation belong together; the gap between
   steps is what separates one from the next. */
.pb-step-title + .pb-step-note, .pb-step-title + .pb-step-mono { margin-top: -6px; }

/* Step 3: the outlet as the network will see it, and the two answers to it.
   The preview is capped at the width a real post occupies — wider and it stops
   being a preview of anything. */
.pb-step-preview { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--es-space-5); align-items: start; }
.pb-preview-post { max-width: 520px; min-width: 0; display: flex; flex-direction: column; gap: var(--es-space-2); }
.pb-step-aside { display: flex; flex-direction: column; gap: var(--es-space-2); min-width: 180px; }
.pb-step-aside form { display: flex; }
.pb-step-aside form input, .pb-step-aside .es-btn { width: 100%; text-align: center; }

/* Step 4: the address and the button on one line, because it is one action. */
.pb-step-inline { display: flex; flex-wrap: wrap; gap: var(--es-space-2); }
.pb-step-inline .es-input { flex: 1; min-width: 12rem; }

@media (max-width: 47.99rem) {
  .pb-step { grid-template-columns: 28px minmax(0, 1fr); gap: var(--es-space-3); padding: var(--es-space-4); }
  .pb-step-preview { grid-template-columns: minmax(0, 1fr); }
  .pb-step-aside { min-width: 0; }
}

/* ---------- The page head, where a page has a control of its own ----------
 * The title and its sentence on the left, the thing that governs the whole
 * page on the right and level with the title rather than with the sentence.
 */
.pb-head-block { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--es-space-4); }
.pb-head-block > :first-child { min-width: 0; flex: 1 1 22rem; }
.pb-period { display: flex; flex-direction: column; align-items: flex-end; gap: var(--es-space-2); }
.pb-period .pb-section-sub, .pb-period p { margin: 0; text-align: right; font-size: var(--es-text-xs); }

/* Declining an offer, next to the offer. Quieter than accepting it — muted, and
   it only takes the foreground colour on hover — because the row exists to
   suggest the thing, not to talk somebody out of it. */
.pb-decline { color: var(--es-fg-muted); }
.pb-decline:hover { color: var(--es-fg); }

/* ---------- A feed in a list ----------
 * Name and address carry the identity; what it did and when it last read are
 * columns of their own so the eye can run down them. No status column: the
 * state is a word trailing the name, and a fault is a line of prose under the
 * address rather than a badge that has to be decoded.
 */
.pb-feed-row { display: grid; grid-template-columns: minmax(0, 1fr) 120px 100px auto; gap: var(--es-space-4); align-items: start; }
.pb-feed-name { min-width: 0; }
.pb-feed-link { font-weight: var(--es-weight-medium); text-decoration: none; }
.pb-feed-link:hover { text-decoration: underline; }
.pb-feed-read, .pb-feed-count { font-size: 13px; }
.pb-feed-count { font-family: var(--es-font-mono); }
.pb-feed-problem { margin: var(--es-space-2) 0 0; font-size: 13px; }

@media (max-width: 47.99rem) {
  /* Name and one number. The rest is on the feed's own page, which is one tap
     away and has room for it. */
  .pb-feed-row { grid-template-columns: minmax(0, 1fr) auto; gap: var(--es-space-2) var(--es-space-3); }
  .pb-feed-read { display: none; }
}
.pb-feed-purpose { font-size: 13px; margin-top: 2px; }

/* ---------- Forms ----------
 * One gap between fields, set once. The form was spacing itself with a utility
 * that every new field had to remember to carry.
 */
.pb-form { display: flex; flex-direction: column; gap: var(--es-space-5); }
.pb-form-two { display: grid; grid-template-columns: 1fr; gap: var(--es-space-5); }
.pb-form-three { display: grid; grid-template-columns: 1fr; gap: var(--es-space-4); }
@media (min-width: 40rem) {
  .pb-form-two { grid-template-columns: 1fr 1fr; }
  .pb-form-three { grid-template-columns: repeat(3, 1fr); }
}
.pb-check { display: flex; align-items: flex-start; gap: var(--es-space-2); font-size: var(--es-text-sm); }
.pb-check input { margin-top: 3px; flex: none; }
.pb-error-list { margin: 0; padding-left: var(--es-space-5); list-style: disc; }

/* The fields that already have a sensible answer. A rule above the summary so
   it reads as the end of the form proper rather than as another field, and a
   marker that turns rather than a plus that changes meaning. */
.pb-more { border-top: var(--es-border-hairline) solid var(--es-border); padding-top: var(--es-space-4); }
.pb-more > summary { font-size: var(--es-text-sm); font-weight: var(--es-weight-medium); cursor: pointer; list-style: none; display: flex; align-items: center; gap: var(--es-space-2); }
.pb-more > summary::-webkit-details-marker { display: none; }
.pb-more > summary::before { content: "▸"; display: inline-block; transition: transform var(--es-motion-fast) var(--es-motion-ease); }
.pb-more[open] > summary::before { transform: rotate(90deg); }
.pb-more-body { margin-top: var(--es-space-5); }

/* A form is a column of fields, and a column of fields has a comfortable width
   regardless of how wide the page around it is. */
.pb-form-narrow { max-width: 640px; }
.pb-back { display: inline-block; font-size: 13px; color: var(--es-fg-muted); text-decoration: none; }
.pb-back:hover { color: var(--es-fg); }
.pb-remove { margin-top: var(--es-space-6); max-width: 640px; }

/* ---------- Cards one above the other ----------
 * What a thing is set to, and what it has actually produced: a feed's settings
 * and its last posts. They were side by side, and half a column of settings
 * put a button beside every line and a four-word value on four lines; one
 * above the other, each has the width (EUR-404). */
.pb-stack { display: flex; flex-direction: column; gap: var(--es-space-5); }

/* An address has no spaces to break at, so it is allowed to break anywhere
   rather than pushing the card wider than the column. */
.pb-break { overflow-wrap: anywhere; }

/* Setting up domain control: three short instructions with records to copy.
   Prose, so it keeps a block of its own rather than being forced into rows. */
.pb-gate { display: flex; flex-direction: column; gap: var(--es-space-4); margin-top: var(--es-space-4); padding-top: var(--es-space-4); border-top: var(--es-border-hairline) solid var(--es-border); align-items: flex-start; }
.pb-gate-list { margin: var(--es-space-2) 0 0; padding-left: var(--es-space-5); list-style: disc; font-size: 13px; color: var(--es-fg-muted); }
.pb-gate-list code { overflow-wrap: anywhere; }

/* A settings screen is a column of decisions, not a canvas. Capped so the
   fields stay a comfortable line length whatever the window does. */
.pb-settings-card { max-width: 600px; }

/* ---------- A switch ----------
 * One decision, and the control IS the decision — so it submits on change and
 * there is no Save beside it. The word left of it says the state in text as
 * well as in position, because a knob on the left and a knob on the right is
 * the whole difference otherwise.
 */
.pb-toggle-form { display: flex; align-items: center; gap: var(--es-space-3); }
.pb-toggle-label { font-family: var(--es-font-mono); font-size: var(--es-text-xs); color: var(--es-fg-muted); }
.pb-toggle { display: inline-flex; cursor: pointer; }
.pb-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.pb-toggle-track { display: block; width: 40px; height: 22px; border: var(--es-border-hairline) solid var(--es-border-strong); border-radius: var(--es-radius-pill); padding: 2px; background: var(--es-bg-raised); transition: background var(--es-motion-fast) var(--es-motion-ease); }
.pb-toggle-knob { display: block; width: 16px; height: 16px; border-radius: 50%; background: var(--es-fg); transition: transform var(--es-motion-fast) var(--es-motion-ease); }
.pb-toggle input:checked + .pb-toggle-track { background: var(--es-accent); }
.pb-toggle input:checked + .pb-toggle-track .pb-toggle-knob { transform: translateX(18px); }
.pb-toggle input:focus-visible + .pb-toggle-track { outline: 2px solid var(--es-focus); outline-offset: 2px; }

/* ---------- A record to copy ----------
 * What somebody is about to paste into a DNS panel. On the sunken ground, in
 * mono, as a name/value grid — because the thing being read here is a pair of
 * exact strings, and prose around them makes them harder to pick out, not
 * easier.
 */
.pb-proof { margin-top: var(--es-space-3); }
.pb-record { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--es-space-1) var(--es-space-4); margin: var(--es-space-2) 0 0; padding: var(--es-space-3); background: var(--es-bg-sunken); border-radius: var(--es-radius-sm); font-family: var(--es-font-mono); font-size: 13px; }
.pb-record dt { color: var(--es-fg-muted); }
.pb-record dd { margin: 0; overflow-wrap: anywhere; }
.pb-domain-field { max-width: 320px; }

/* ---------- The outlet, and what the outlet looks like ----------
 * The form beside the thing it describes. Editing a brand colour with no sight
 * of what it does is what made this screen a set of fields and a button that
 * went somewhere else.
 */
.pb-outlet { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--es-space-5); align-items: start; }
@media (min-width: 60rem) { .pb-outlet { grid-template-columns: minmax(0, 1fr) 320px; } }

.pb-preview { display: flex; flex-direction: column; gap: var(--es-space-2); }
.pb-preview-label { padding: 0; border: 0; }
.pb-preview-card { overflow: hidden; }
.pb-preview-band { display: block; height: 6px; }
.pb-preview-id { display: flex; align-items: center; gap: var(--es-space-3); }
.pb-preview-avatar { flex: none; width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }
.pb-preview-name { font-weight: var(--es-weight-semibold); overflow-wrap: anywhere; }
.pb-preview-latest { margin-top: var(--es-space-3); padding-top: var(--es-space-3); border-top: var(--es-border-hairline) solid var(--es-border); }

/* ---------- A person in a list ----------
 * Face, name, state, and the one thing you can do about them. The avatar is a
 * hint at recognition rather than a portrait — a list of portraits stops being
 * a list.
 */
.pb-reporter-row { display: grid; grid-template-columns: 36px minmax(0, 1fr) auto auto; gap: var(--es-space-3); align-items: center; }
.pb-reporter-avatar { flex: none; width: 36px; height: 36px; border-radius: 50%; object-fit: cover; }
.pb-add-reporters { max-width: 560px; margin-top: var(--es-space-6); }

@media (max-width: 47.99rem) {
  .pb-reporter-row { grid-template-columns: 36px minmax(0, 1fr) auto; }
  /* The state moves under the name rather than squeezing four columns into a
     phone; the action keeps the right-hand edge it shares with every other row. */
  .pb-reporter-row > .pb-state { grid-column: 2; grid-row: 2; justify-self: start; }
}

/* ---------- An operator's table, as rows ----------
 * Wide enough for six columns and honest about it: every row lands its action
 * in the same column, which is what lets an operator scan down rather than hunt
 * across. Below the breakpoint the numeric columns fold away — on a phone the
 * useful columns are who it is and what to do about them.
 */
.pb-admin-row { display: grid; grid-template-columns: minmax(0, 1fr) 90px 80px 90px 110px 120px; gap: var(--es-space-3); align-items: center; }
.pb-admin-row .pb-num, .pb-admin-signin { font-size: 13px; }
.pb-admin-action { justify-self: end; }
.pb-inbox-row { grid-template-columns: minmax(0, 1fr) 140px; align-items: start; }
.pb-marks { display: flex; flex-wrap: wrap; align-items: center; gap: var(--es-space-2); margin: var(--es-space-2) 0 0; font-size: var(--es-text-xs); }
.pb-state-info { background: var(--es-info); }

@media (max-width: 60rem) {
  .pb-admin-row { grid-template-columns: minmax(0, 1fr) auto; }
  .pb-admin-row .pb-num, .pb-admin-signin { display: none; }
  .pb-inbox-row { grid-template-columns: minmax(0, 1fr) auto; }
}

/* The outlet's colour, as the mark that finds its row. A 4px bar rather than a
   swatch: it is an identifier here, not a value being edited. */
.pb-newsroom-name { display: flex; align-items: center; gap: var(--es-space-3); min-width: 0; }
.pb-accent-bar { flex: none; width: 4px; align-self: stretch; min-height: 28px; border-radius: 2px; }
/* An operator's list as a real table (pb-data-table): a search and a filter
   above it, a pager under it — newsrooms, candidates, feeds, accounts. The
   table scrolls inside its card on a narrow screen rather than folding columns
   away — every column is one somebody sorts by. */
.pb-table-tools { display: flex; flex-wrap: wrap; gap: var(--es-space-2); margin: var(--es-space-4) 0 var(--es-space-3); }
.pb-table-tools input[type="search"] { flex: 1 1 18rem; }
.pb-table-country { flex: 0 1 14rem; }
.pb-table-scroll { overflow-x: auto; }
/* A selection over a table: the checkbox column, and the count with its
   action at the end of the tools row. */
.pb-select-cell { width: 2rem; }
/* An email as it will arrive, on the admin's invitation pages. */
.pb-mail-headers { display: grid; gap: 2px; margin: 0 0 var(--es-space-3); padding-bottom: var(--es-space-3); border-bottom: var(--es-border-hairline) solid var(--es-border); font-size: 13px; }
.pb-mail-headers div { display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); gap: var(--es-space-2); }
.pb-mail-headers dt { color: var(--es-fg-muted); }
.pb-mail-headers dd { margin: 0; overflow-wrap: anywhere; }
.pb-mail-body { font-size: 15px; line-height: 1.5; }
.pb-mail-body p { max-width: none; }
.pb-mail-picture { display: block; width: 100%; height: auto; margin: var(--es-space-4) 0; border: var(--es-border-hairline) solid var(--es-border); border-radius: 6px; }
.pb-mail-footer { margin-top: var(--es-space-4); padding-top: var(--es-space-3); border-top: var(--es-border-hairline) solid var(--es-border); font-size: 12px; color: var(--es-fg-muted); }
.pb-selection { display: inline-flex; align-items: center; gap: var(--es-space-2); margin-left: auto; }
.pb-data-table td { vertical-align: middle; }
.pb-data-table td.pb-coverage { width: 11rem; }
.pb-data-table td.pb-claimed { font-size: 13px; overflow-wrap: anywhere; max-width: 14rem; }
.pb-table-pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--es-space-3) var(--es-space-5); margin-top: var(--es-space-3); font-size: var(--es-text-sm); }
.pb-table-pager[hidden] { display: none; }
.pb-table-pager-size { display: inline-flex; align-items: center; gap: var(--es-space-2); }
.pb-table-pager-size .es-select { width: auto; min-height: 2rem; padding-block: 0; }
.pb-table-pager-status { font-variant-numeric: tabular-nums; }
.pb-table-pager-steps { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--es-space-1); }
/* The page numbers between the steps: the one on screen filled in, and "…"
 * where a run is left out (TablePage#numbers, table_controller.js). */
.pb-table-pager-pages { display: contents; }
.pb-page-btn { min-width: 2rem; padding-inline: var(--es-space-2); justify-content: center; font-variant-numeric: tabular-nums; }
.pb-page-btn[aria-current="page"], .pb-page-btn[aria-current="page"]:hover { background: var(--es-fg); border-color: var(--es-fg); color: var(--es-bg); font-weight: var(--es-weight-medium); cursor: default; }
.pb-page-gap { min-width: 1.25rem; text-align: center; color: var(--es-fg-muted); }
/* On a phone the longest run — two arrows, seven numbers and two gaps — has to
 * fit one line, or the last arrow wraps under the rest on its own. */
@media (max-width: 47.99rem) {
  .pb-table-pager-steps { gap: 2px; }
  .pb-table-pager-steps .pb-icon-btn { width: 1.75rem; }
  .pb-page-btn { min-width: 1.75rem; padding-inline: var(--es-space-1); }
  .pb-page-gap { min-width: 0.75rem; }
}
/* A value with a box of detail over it (tip_controller.js, ApplicationHelper#tip):
 * dotted underneath to say there is more, and the box drawn fixed to the
 * window, so a table's scrolling card cannot cut it off. */
.pb-tip { display: inline-block; cursor: help; text-decoration: underline dotted; text-underline-offset: 3px; border-radius: 2px; }
.pb-tip:focus-visible { outline: 2px solid var(--es-accent); outline-offset: 2px; }
.pb-tip-box { position: fixed; z-index: 50; width: max-content; max-width: min(20rem, calc(100vw - 16px)); padding: var(--es-space-3); background: var(--es-bg-raised); color: var(--es-fg); border: var(--es-border-hairline) solid var(--es-border); border-radius: 6px; box-shadow: 0 6px 24px color-mix(in srgb, var(--es-palette-black) 14%, transparent); font-size: 13px; line-height: 1.5; text-align: left; pointer-events: none; }
.pb-tip-heading { margin-bottom: var(--es-space-1); color: var(--es-fg-muted); }
.pb-tip-list { display: flex; flex-wrap: wrap; gap: 2px var(--es-space-3); margin: 0; padding: 0; list-style: none; font-variant-numeric: tabular-nums; }
.pb-tip-list b { font-weight: var(--es-weight-medium); }
/* A step with only an arrow in it: square, and visibly off at either end. */
.pb-icon-btn { width: 2rem; padding-inline: 0; justify-content: center; }
.pb-icon-btn:disabled { opacity: 0.35; cursor: default; }
/* The facts under a service's status, and the status itself.
 *
 * es-stats is one flex row, which is right where a handful of numbers sit
 * under a heading with the page to spread into. In a table cell it is a floor
 * under the column's width: five facts and their gaps cannot shrink, so the
 * table grew past its card, the card scrolled, and the last two facts were
 * simply off the edge — which is where "events" and "cursor" went. A grid that
 * wraps has a min-content width of one column instead, so the cell takes what
 * the table can spare and the facts run on underneath.
 */
.pb-service-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); gap: var(--es-space-2) var(--es-space-4); }
.pb-service-status { white-space: normal; }
.pb-service-status .es-tag { white-space: normal; }

/* How far along, as a bar: always beside the figure it summarises and never
   instead of it, so a reader the hues fail still has the number. */
.pb-meter { position: relative; display: block; height: 6px; border-radius: 3px; background: var(--es-bg-sunken); overflow: hidden; }
.pb-meter-fill, .pb-meter-known { position: absolute; inset: 0 auto 0 0; display: block; }
.pb-meter-fill { background: var(--es-accent); }
/* The same hue, lighter, rather than a second colour: the two bands are one
   quantity at two stages, and a reader who cannot tell hues apart still reads
   them by lightness — and by the figures beside them. */
.pb-meter-known { background: color-mix(in oklab, var(--es-accent) 35%, var(--es-bg-sunken)); }
.pb-coverage { min-width: 0; }
.pb-coverage-total { margin-bottom: var(--es-space-3); }
/* Figures, not prose: the design system's reading measure is for sentences,
   and a line of counts held to it wraps halfway across a full-width card. */
.pb-coverage-total p { max-width: none; margin-bottom: 0; }
.pb-coverage-total details p { max-width: var(--es-layout-measure); margin-bottom: var(--es-space-2); }
/* The disclosure on its own, without the rule that ends a form. */
.pb-more-quiet { border-top: 0; padding-top: 0; }
.pb-adminfeed-row { grid-template-columns: minmax(0, 1fr) 200px 130px 140px; align-items: start; }
.pb-candidate-row { grid-template-columns: minmax(0, 1fr) 220px 90px 70px 100px; }
/* The counts are the filters: a row of pills, the active one filled. */
.pb-filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--es-space-2); margin: var(--es-space-4) 0 0; }
.pb-filter { display: inline-flex; gap: var(--es-space-2); padding: 2px 10px; border: var(--es-border-hairline) solid var(--es-border); border-radius: var(--es-radius-pill); font-size: 13px; text-decoration: none; color: inherit; }
.pb-filter-on { background: var(--es-accent); color: var(--es-fg-on-accent); border-color: transparent; }
.pb-filter-gap { flex: 1; }
/* Facts about an account, two columns of label and value. */
.pb-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--es-space-3); font-size: 13px; }
.pb-facts dt { font-size: var(--es-text-xs); }
.pb-facts dd { margin: 0; font-variant-numeric: tabular-nums; }
.pb-table { width: 100%; border-collapse: collapse; }
.pb-table th, .pb-table td { padding: 6px 8px; text-align: left; vertical-align: top; border-top: var(--es-border-hairline) solid var(--es-border); }
.pb-table th { border-top: 0; font-size: var(--es-text-xs); font-weight: var(--es-weight-medium); }
.pb-admin-claimed { font-size: 13px; overflow-wrap: anywhere; }

@media (max-width: 60rem) {
  .pb-adminfeed-row, .pb-candidate-row { grid-template-columns: minmax(0, 1fr) auto; }
  .pb-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pb-admin-claimed { display: none; }
}

/* Every other field inherits the body's 16px, which is also the size below
   which iOS zooms the page on focus. The language select was 12px because it
   lived in a top bar; it lives in a settings row now, and on a phone that
   zoom is a page that has to be pinched back afterwards. */
@media (max-width: 47.99rem) {
  .pb-locale-select { font-size: var(--es-text-md); }
}

/* The jump menu exists only where the rail cannot: above the breakpoint the
   sub-rail is the navigation and a second copy of it would be furniture. */
.pb-jump { display: none; }

/* ---------- Things that only exist on a phone ----------
 * The top bar carries the handle, the way into admin and the way out. At phone
 * width it carries the wordmark and one of them, so the other two move to the
 * foot of Settings — which is where somebody goes looking for the way out in
 * any case.
 */
.pb-mobile-only { display: none; }
@media (max-width: 47.99rem) {
  .pb-mobile-only { display: block; margin-top: var(--es-space-6); }

  /* ...and the publisher's bar sheds all three, not two of them: leaving Sign
     out up here would put it on the screen twice. The operator's bar keeps its
     group, because the way back out of admin is in it and there is no Settings
     page on that side to move it to. */
  .es-topbar:not(.es-topbar-admin) .pb-topbar-right { display: none; }
}

/* The comment section. One card per article, one block per reply inside it,
   separated by a hairline rather than by a box each: a stack of cards reads as
   a stack of unrelated things, and these are one conversation.

   The padding is on the reply rather than on the card because the hairlines
   have to run the full width — es-card is a bare container (es-card-body is
   what usually carries padding, and a body per reply would put a box around
   each one again). Each block insets its own content instead. */
.pb-reply { padding: var(--es-space-4); border-top: var(--es-border-hairline) solid var(--es-border); }
.pb-reply:first-child { border-top: 0; }
.pb-reply-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--es-space-1) var(--es-space-3); }
.pb-reply-who { font-weight: var(--es-weight-medium); display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--es-space-2); min-width: 0; }
/* An account nobody has named yet. Monospace because it is an identifier and
   not a name, at the size of the handle it stands in for rather than at the
   weight of one — it should read as missing information, not as somebody
   shouting their DID. */
.pb-reply-unknown { font-family: var(--es-font-mono); font-size: var(--es-text-sm); font-weight: var(--es-weight-regular); color: var(--es-fg-muted); overflow-wrap: anywhere; }
/* Somebody else's words, at whatever length and in whatever script they wrote
   them. overflow-wrap so one unbroken URL cannot push the card sideways on a
   phone; pre-line so their paragraphs survive as paragraphs. */
.pb-reply-text { margin: var(--es-space-2) 0 0; white-space: pre-line; overflow-wrap: anywhere; max-width: var(--es-layout-measure); }
/* The row of actions under a reply sits closer to it than to the next one. */
.pb-reply .pb-actions { margin-top: var(--es-space-3); margin-bottom: 0; }
.pb-reply-avatar { width: 24px; height: 24px; align-self: center; }
.pb-reply-when { color: var(--es-fg-muted); text-decoration: none; white-space: nowrap; }
.pb-reply-when:hover { color: var(--es-fg); text-decoration: underline; }

/* What a reply carried besides its words (replies/_embed). Held to the reading
   measure like the words, so a picture never runs wider than the sentence
   above it. */
.pb-embed-images, .pb-embed-card, .pb-embed-video { margin-top: var(--es-space-2); max-width: var(--es-layout-measure); }
.pb-embed-images { display: grid; gap: 4px; border-radius: var(--es-radius-md); overflow: hidden; }
/* minmax(0, 1fr), not 1fr: a picture's own width would otherwise stretch its
   column, and two pictures side by side came out different sizes. */
.pb-embed-images-2, .pb-embed-images-3, .pb-embed-images-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pb-embed-images-3 .pb-embed-image:first-child { grid-row: span 2; }
.pb-embed-image { display: block; background: var(--es-bg-sunken); }
.pb-embed-image img { display: block; width: 100%; height: 100%; object-fit: cover; }
.pb-embed-images-1 .pb-embed-image img { height: auto; max-height: 28rem; object-fit: contain; }
.pb-embed-images-2 .pb-embed-image img, .pb-embed-images-4 .pb-embed-image img { aspect-ratio: 1; }
.pb-embed-card { display: flex; gap: var(--es-space-3); padding: var(--es-space-3); border: var(--es-border-hairline) solid var(--es-border);
  border-radius: var(--es-radius-md); color: inherit; text-decoration: none; }
a.pb-embed-card:hover { border-color: var(--es-fg-muted); }
.pb-embed-link-thumb { width: 5rem; height: 5rem; flex: none; object-fit: cover; border-radius: var(--es-radius-sm); }
.pb-embed-link-body, .pb-embed-quote { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pb-embed-title { font-weight: var(--es-weight-medium); overflow-wrap: anywhere; }
.pb-embed-desc { font-size: var(--es-text-sm); color: var(--es-fg-muted); overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.pb-embed-quote .pb-embed-desc { color: var(--es-fg); white-space: pre-line; }
.pb-embed-quote-who { display: flex; flex-wrap: wrap; align-items: center; gap: var(--es-space-2); }
.pb-embed-quote-who img { width: 18px; height: 18px; }
.pb-embed-host { overflow-wrap: anywhere; }
.pb-embed-video { position: relative; display: block; border-radius: var(--es-radius-md); overflow: hidden; }
.pb-embed-video img { display: block; width: 100%; max-height: 28rem; object-fit: cover; }
.pb-embed-play { position: absolute; inset: 0; margin: auto; width: 3rem; height: 3rem; display: grid; place-items: center;
  border-radius: 50%; background: rgb(0 0 0 / 0.55); color: #fff; font-size: 1.1rem; }
.pb-reply-hidden { padding: var(--es-space-4); margin: 0; border-top: var(--es-border-hairline) solid var(--es-border); }

/* An answer the outlet already sent, and the box for writing one. Indented
   under the reply it belongs to, because a thread is a shape and a flat list
   of alternating voices is not one. */
.pb-answer { margin: var(--es-space-3) 0 0 var(--es-space-4); padding-left: var(--es-space-3); border-left: 2px solid var(--es-accent); }
.pb-answer-form { margin-top: var(--es-space-3); }
/* The summary is the button, so it must not also draw a disclosure triangle. */
.pb-answer-form > summary { display: inline-block; list-style: none; cursor: pointer; }
.pb-answer-form > summary::-webkit-details-marker { display: none; }
.pb-answer-fields { margin-top: var(--es-space-3); display: grid; gap: var(--es-space-2); max-width: var(--es-layout-measure); }
