/* ==========================================================================
   Theme token layer (dark mode)
   --------------------------------------------------------------------------
   A semantic CSS custom-property layer sits on top of the stylesheet so the
   whole UI can be re-themed from one place. Every color further down the file
   references a var(--…) token; there are no bare hex/rgb color literals in the
   rules below (only inside these :root token blocks).

   LIGHT is the default and reproduces the historical appearance exactly.
   DARK applies when:
     - the user explicitly selects Dark            -> :root[data-theme="dark"]
     - the user is on Auto and the OS is dark       -> @media + [data-theme="auto"]
     - no JS / no stored choice and the OS is dark  -> @media + :not([data-theme])
   The theme is set synchronously in an inline <head> script (see base.html),
   which stamps data-theme on <html> before paint to avoid a flash (FOUC).

   The DARK token block is intentionally duplicated across the explicit
   selector and the prefers-color-scheme media query — that is the standard,
   build-free way to express "dark, and also dark-when-OS-says-so".
   ========================================================================== */
:root {
  color-scheme: light;

  /* Surfaces */
  --color-bg: #fff;             /* page / body background */
  --color-surface: #fff;        /* cards, dialogs, menus, inputs */
  --color-surface-2: #f6f7f9;   /* subtle inset panels (extract, summary) */
  --color-surface-3: #f5f5f5;   /* code chips, table head fills */
  --color-header-bg: #2b3a55;   /* top nav bar + dropdown menu */

  /* Text */
  --color-text: #1a1a1a;        /* primary body text */
  --color-text-muted: #555;     /* secondary meta text */
  --color-text-subtle: #888;    /* tertiary / disabled-ish text */
  --color-text-faint: #999;     /* faintest hints (never, empty) */
  --color-on-header: #d7deec;   /* text on the dark header */
  --color-on-header-strong: #fff;
  --color-on-accent: #fff;      /* text on an accent-filled button */

  /* Borders */
  --color-border: #ddd;         /* default hairline */
  --color-border-subtle: #eee;  /* faint list dividers */
  --color-border-strong: #ccc;  /* icon buttons, thumbnails */
  --color-border-blue: #cdd5e3; /* blue-tinted borders (panels, chips) */

  /* Accent / brand */
  --color-accent: #2b3a55;
  --color-accent-hover: #33425b;
  --color-link: #1565c0;
  --color-link-hover: #0d47a1;
  --color-accent-soft-bg: #eef1f6;   /* blue-tint chip / hover fills */
  --color-accent-soft-bg-hover: #dde6f3;
  --color-accent-soft-border: #c7d2e2;

  /* Header accents */
  --color-header-badge-bg: rgba(255, 255, 255, 0.1);
  --color-nav-hover-bg: rgba(255, 255, 255, 0.12);

  /* Status: danger (reds) */
  --color-danger-text: #b00020;
  --color-danger-text-deep: #a12622;
  --color-danger-bg: #fdecea;
  --color-danger-border: #f1c0c0;

  /* Status: warning (ambers) */
  --color-warning-text: #8a6d00;
  --color-warning-bg: #fdf3d0;
  --color-warning-bg-soft: #fff6e5;
  --color-warning-border: #ecdfb4;

  /* Status: success (greens) */
  --color-success-text: #1b6e2e;
  --color-success-bg: #e6f4ea;
  --color-success-border: #c8e6c9;

  /* Info (light blue flashes) */
  --color-info-bg: #eef4ff;
  --color-info-border: #cdddf7;

  /* Highlight (priority-term marker) */
  --color-highlight-bg: #fff3bf;

  /* Star toggle (My Sources) */
  --color-star: #b9921f;
  --color-star-hover: #d6a715;
  --color-star-on: #e0a800;

  /* Badges (source types) */
  --color-badge-rss-bg: #d4e5f7;
  --color-badge-rss-text: #1565c0;
  --color-badge-crawl-bg: #f3e5f5;
  --color-badge-crawl-text: #6a1b9a;
  --color-badge-mechanism-bg: #e8edf5;
  --color-badge-mechanism-text: #33425b;
  --color-badge-manual-bg: #ede7d4;
  --color-badge-manual-text: #7a5c00;
  --color-badge-count-bg: #d32f2f;   /* nav "Publish" notification bubble */

  /* Shadows / overlays */
  --shadow-sm: rgba(0, 0, 0, 0.15);
  --shadow-md: rgba(0, 0, 0, 0.25);
  --shadow-lg: rgba(0, 0, 0, 0.3);
  --color-backdrop: rgba(0, 0, 0, 0.45);
}

/* --- DARK tokens: explicit user override --------------------------------- */
:root[data-theme="dark"] {
  color-scheme: dark;

  --color-bg: #14171c;
  --color-surface: #1c2129;
  --color-surface-2: #232a34;
  --color-surface-3: #232a34;
  --color-header-bg: #1a2130;

  --color-text: #e6e8eb;
  --color-text-muted: #a4abb6;
  --color-text-subtle: #8b93a0;
  --color-text-faint: #6f7783;
  --color-on-header: #c4ccdb;
  --color-on-header-strong: #fff;
  --color-on-accent: #fff;

  --color-border: #2c3440;
  --color-border-subtle: #262d38;
  --color-border-strong: #3a4452;
  --color-border-blue: #34405a;

  --color-accent: #34507d;
  --color-accent-hover: #3f5d90;
  --color-link: #6aa9ec;
  --color-link-hover: #8dc0f5;
  --color-accent-soft-bg: #262f3f;
  --color-accent-soft-bg-hover: #2f3a4e;
  --color-accent-soft-border: #3a4761;

  --color-header-badge-bg: rgba(255, 255, 255, 0.08);
  --color-nav-hover-bg: rgba(255, 255, 255, 0.1);

  --color-danger-text: #f28b8b;
  --color-danger-text-deep: #f0a19b;
  --color-danger-bg: #3a1f1f;
  --color-danger-border: #5a2e2e;

  --color-warning-text: #e6c15a;
  --color-warning-bg: #33290f;
  --color-warning-bg-soft: #2c250f;
  --color-warning-border: #4d3f1a;

  --color-success-text: #6fce8b;
  --color-success-bg: #16301f;
  --color-success-border: #285238;

  --color-info-bg: #1a2436;
  --color-info-border: #2c3d59;

  --color-highlight-bg: #4d4413;

  --color-star: #d6a715;
  --color-star-hover: #f0c02a;
  --color-star-on: #f0c02a;

  --color-badge-rss-bg: #1c3350;
  --color-badge-rss-text: #7fb4ee;
  --color-badge-crawl-bg: #35234a;
  --color-badge-crawl-text: #c497e6;
  --color-badge-mechanism-bg: #273349;
  --color-badge-mechanism-text: #9db3d6;
  --color-badge-manual-bg: #3a331f;
  --color-badge-manual-text: #d3ba75;
  --color-badge-count-bg: #e5555b;   /* nav "Publish" notification bubble */

  --shadow-sm: rgba(0, 0, 0, 0.5);
  --shadow-md: rgba(0, 0, 0, 0.6);
  --shadow-lg: rgba(0, 0, 0, 0.7);
  --color-backdrop: rgba(0, 0, 0, 0.65);
}

/* --- DARK tokens: Auto (or no stored choice) following a dark OS ---------- */
@media (prefers-color-scheme: dark) {
  :root[data-theme="auto"],
  :root:not([data-theme]) {
    color-scheme: dark;

    --color-bg: #14171c;
    --color-surface: #1c2129;
    --color-surface-2: #232a34;
    --color-surface-3: #232a34;
    --color-header-bg: #1a2130;

    --color-text: #e6e8eb;
    --color-text-muted: #a4abb6;
    --color-text-subtle: #8b93a0;
    --color-text-faint: #6f7783;
    --color-on-header: #c4ccdb;
    --color-on-header-strong: #fff;
    --color-on-accent: #fff;

    --color-border: #2c3440;
    --color-border-subtle: #262d38;
    --color-border-strong: #3a4452;
    --color-border-blue: #34405a;

    --color-accent: #34507d;
    --color-accent-hover: #3f5d90;
    --color-link: #6aa9ec;
    --color-link-hover: #8dc0f5;
    --color-accent-soft-bg: #262f3f;
    --color-accent-soft-bg-hover: #2f3a4e;
    --color-accent-soft-border: #3a4761;

    --color-header-badge-bg: rgba(255, 255, 255, 0.08);
    --color-nav-hover-bg: rgba(255, 255, 255, 0.1);

    --color-danger-text: #f28b8b;
    --color-danger-text-deep: #f0a19b;
    --color-danger-bg: #3a1f1f;
    --color-danger-border: #5a2e2e;

    --color-warning-text: #e6c15a;
    --color-warning-bg: #33290f;
    --color-warning-bg-soft: #2c250f;
    --color-warning-border: #4d3f1a;

    --color-success-text: #6fce8b;
    --color-success-bg: #16301f;
    --color-success-border: #285238;

    --color-info-bg: #1a2436;
    --color-info-border: #2c3d59;

    --color-highlight-bg: #4d4413;

    --color-star: #d6a715;
    --color-star-hover: #f0c02a;
    --color-star-on: #f0c02a;

    --color-badge-rss-bg: #1c3350;
    --color-badge-rss-text: #7fb4ee;
    --color-badge-crawl-bg: #35234a;
    --color-badge-crawl-text: #c497e6;
    --color-badge-mechanism-bg: #273349;
    --color-badge-mechanism-text: #9db3d6;
    --color-badge-manual-bg: #3a331f;
    --color-badge-manual-text: #d3ba75;
    --color-badge-count-bg: #e5555b;   /* nav "Publish" notification bubble */

    --shadow-sm: rgba(0, 0, 0, 0.5);
    --shadow-md: rgba(0, 0, 0, 0.6);
    --shadow-lg: rgba(0, 0, 0, 0.7);
    --color-backdrop: rgba(0, 0, 0, 0.65);
  }
}

/* === END THEME TOKENS — no raw color literals below this line === */

:root {
  font-family: system-ui, -apple-system, sans-serif;
  line-height: 1.5;
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
}

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1.25rem;
  background: var(--color-header-bg);
  color: var(--color-on-accent);
  gap: 1.5rem;
}

.site-header-brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.site-header-brand strong {
  color: var(--color-on-accent);
  user-select: none;
}

.site-header-brand:hover {
  opacity: 0.8;
}

.groong-logo {
  height: 28px;
  width: 28px;
  object-fit: contain;
}

.site-header-version {
  display: flex;
  align-items: center;
  margin-left: auto;
  font-size: 0.75rem;
}

.version-badge {
  background: var(--color-header-badge-bg);
  padding: 0.3rem 0.6rem;
  border-radius: 3px;
  font-family: monospace;
  color: var(--color-on-header);
}

.version-overlay {
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  background: var(--color-link);
  border: 1px solid var(--color-nav-hover-bg);
  border-radius: 4px;
  padding: 0.35rem 0.6rem;
  font-size: 0.6rem;
  font-family: monospace;
  color: var(--color-on-accent);
  z-index: 1000;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  text-decoration: none;
  cursor: pointer;
}

.version-overlay:hover,
.version-overlay:focus {
  background: var(--color-link-hover);
}

.version-overlay code {
  color: var(--color-on-header);
  background: var(--shadow-sm);
  padding: 0.1rem 0.25rem;
  border-radius: 2px;
}

.version-badge code {
  color: var(--color-on-header);
  background: var(--shadow-sm);
  padding: 0.1rem 0.3rem;
  border-radius: 2px;
  font-size: 0.85em;
}

.site-user {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.9rem;
}

.logout-form {
  display: inline;
  margin: 0;
}

.site-main {
  padding: 1.25rem;
  max-width: 60rem;
}

/* Full-width pages (e.g. the wide, many-column admin Sources table) opt out of
   the default 60rem content column and fill the browser window. */
.site-main--full {
  max-width: none;
}

.login {
  max-width: 22rem;
  margin: 3rem auto;
  padding: 0 1rem;
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.login-form input {
  padding: 0.5rem;
  margin-bottom: 0.5rem;
  font-size: 1rem;
}

.login-form button {
  padding: 0.55rem;
  font-size: 1rem;
  cursor: pointer;
}

.form-error {
  color: var(--color-danger-text);
  font-weight: 600;
}

.login-note {
  margin-top: 1rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

/* Sign in with Google (TICKET-049) */
.google-signin {
  display: block;
  text-align: center;
  padding: 0.55rem;
  margin-bottom: 0.75rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text);
  text-decoration: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: 4px;
  cursor: pointer;
}

.google-signin:hover {
  background: var(--color-surface-2);
}

.login-divider {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.75rem 0;
  color: var(--color-text-subtle);
  font-size: 0.85rem;
}

.login-divider::before,
.login-divider::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--color-border);
}

/* --- Discovery queue (TICKET-009) --- */
.queue-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.btn {
  display: inline-block;
  box-sizing: border-box;
  padding: 0.35rem 0.7rem;
  font-family: inherit;
  font-size: 0.9rem;
  /* Explicit so <a class="btn"> and <button class="btn"> render the SAME height:
     anchors would otherwise inherit the body's line-height (1.5) while buttons
     use the UA default (~normal), leaving them visibly different heights. */
  line-height: 1.4;
  border: 1px solid var(--color-accent);
  border-radius: 3px;
  background: var(--color-accent);
  color: var(--color-on-accent);
  text-decoration: none;
  vertical-align: middle;
  cursor: pointer;
}

/* An inline icon inside a text `.btn` (e.g. the batch Summarize / Post / Reject
   buttons): sit it just below the baseline and space it from the label. */
.btn .icon {
  vertical-align: -0.15em;
  margin-right: 0.35em;
}

.btn-reject {
  background: var(--color-surface);
  color: var(--color-danger-text);
  border-color: var(--color-danger-text);
}

.btn-post {
  background: var(--color-success-text);
  border-color: var(--color-success-text);
}

/* A disabled .btn must LOOK disabled. The row buttons carry solid custom
   backgrounds (e.g. .btn-post's green), which override the browser's default
   greying — so a disabled Post button looked fully active while clicking it did
   nothing. Grey it out and block the cursor. The reason lives as a title on the
   wrapping .post-form (a disabled button can't show its own tooltip), so the
   whole area also shows the not-allowed cursor. */
.btn:disabled,
.btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.post-form[title] {
  cursor: not-allowed;
}

/* Inline error shown when a single queue Post fails (e.g. no source text). */
.queue-item-error {
  margin: 0.25rem 0 0;
  color: var(--color-danger-text);
  font-size: 0.85rem;
}

/* Status column: the article's state signals gathered into their own column
   between the content and the action buttons — a top-aligned, fixed-width
   vertical stack of pills (post-readiness first, then flagged / priority /
   paywall folded out of the headline). */
.queue-item-status {
  /* The status pills (Ready to post / Low Quality / flagged / priority /
     paywall …) lay out in a single wrapping row so they don't stack up the
     card's height; they wrap to a second line only when the card is narrow. */
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  gap: 0.25rem;
  padding-top: 0.1rem;
}

/* Relevance-score column: two compact label/value chips (Keyword, AI), sitting
   between the status column and the actions. Neutral (token) styling — these are
   numbers, not states, so they don't compete with the coloured status pills. */
.queue-item-scores {
  /* Lay the four score chips out in a single wrapping row so they don't eat
     vertical space in the card; each chip shrinks to its own content (label +
     value) rather than stretching to the card width. Wraps to a second line on
     a narrow card. */
  display: flex;
  flex-flow: row wrap;
  gap: 0.35rem;
  padding-top: 0.1rem;
  max-width: 100%;
}

.score-chip {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.1rem 0.5rem;
  border-radius: 4px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  font-size: 0.78rem;
  line-height: 1.5;
  cursor: help;
}

.score-chip-label {
  color: var(--color-text-muted);
  font-weight: 600;
}

.score-chip-value {
  color: var(--color-text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Status pill: a small rounded chip. Variants colour by meaning; any pill whose
   full detail lives in its tooltip gets the help cursor. Wraps rather than
   overflowing its column. */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.5;
}

.status-ready {
  color: var(--color-success-text);
  background: var(--color-success-bg);
  border-color: var(--color-success-border);
}

.status-blocked {
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
  cursor: help;
}

.status-lowwords {
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
  cursor: help;
}

/* Quality/tags/relevance warning chips (summarized items). Two severities:
   warn = orange, critical = red. */
.status-warn {
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
  cursor: help;
}

.status-critical {
  color: var(--color-danger-text);
  background: var(--color-danger-bg);
  border-color: var(--color-danger-text);
  cursor: help;
}

.status-flagged {
  color: var(--color-danger-text);
  background: var(--color-danger-bg);
  border-color: var(--color-danger-text);
  cursor: help;
}

.status-priority {
  color: var(--color-accent);
  background: var(--color-accent-soft-bg);
  border-color: var(--color-accent-soft-border);
  cursor: help;
}

.status-paywall {
  color: var(--color-warning-text);
  background: var(--color-warning-bg-soft);
  border-color: var(--color-warning-border);
  cursor: help;
}

/* Source filter (TICKET-058). */
/* The queue / rejected filter bar is a SINGLE row BY CONSTRUCTION.
 *
 * It used to be `flex-wrap: wrap`, so staying on one line depended on the
 * controls happening to fit inside the 60rem article column. Every fix was a
 * width shave (drop the inline labels, cap the selects, narrow the date
 * inputs) that only bought headroom until the next filter was added — the bar
 * has now wrapped twice for exactly that reason. The failure mode is fixed
 * here instead of the widths:
 *
 *   1. `nowrap` — a second row is impossible, whatever is added.
 *   2. the SELECTS give up width first (they truncate), so buttons and
 *      toggles never squish.
 *   3. the bar may grow past the article column toward the viewport edge
 *      (the column is left-aligned, so growing right is safe) — capped so the
 *      page itself never gets a horizontal scrollbar.
 *   4. only if all that is still not enough does the bar scroll sideways.
 *
 * Adding another filter can therefore never produce a second row. The nowrap
 * invariant is pinned by tests/test_filter_bar_css.py.
 */
.queue-source-filter {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 0.5rem;
  margin: 1rem 0 0;
  font-size: 0.85rem;
  width: max-content;
  max-width: calc(100vw - 2.5rem);
  overflow-x: auto;
}

/* Fixed-size members: they keep their natural width and never squish. */
.queue-source-filter > .queue-date-range,
.queue-source-filter > .queue-filter-actions,
.queue-source-filter > .queue-filter-clear,
.queue-source-filter > .queue-filter-check,
.queue-source-filter > button {
  flex: 0 0 auto;
}

/* Inline checkbox filter (Live screen's "Show deleted"): the box and its label
   sit on one line, vertically centred, and the whole thing is clickable. */
.queue-filter-check {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  white-space: nowrap;
  cursor: pointer;
}

/* Discover-queue variant: the filter bar is intentionally TWO rows (source /
   group / date on top; the narrowing filters — summarized / tagged / flagged /
   word count — plus Apply and Clear below). Each row is its own flex container
   that WRAPS gracefully, so adding another control costs a wrap, never a broken
   layout or a hand-tuned width. This overrides the single-row nowrap base for
   the Discover surface only; the other queues keep the compact one-row bar. */
.queue-source-filter.queue-filter-bar {
  flex-direction: column;
  align-items: stretch;
  flex-wrap: wrap;
  width: auto;
  max-width: 100%;
  overflow-x: visible;
}

.queue-filter-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Word-count range filter: a terse label + two small numeric inputs joined by an
   en-dash, kept together as one wrap unit (like the date range). Narrow inputs
   so the pair stays compact, but they sit in a wrapping row so they never force
   the bar to overflow. */
.queue-words-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  white-space: nowrap;
}

.queue-words-label {
  color: var(--color-text-muted);
  font-weight: 600;
}

.queue-words-filter input[type="number"] {
  width: 5rem;
  font-size: 0.85rem;
  padding: 0.2rem 0.3rem;
}

.queue-words-sep {
  color: var(--color-text-muted);
}

/* Shared queue search bar (title/author/body keyword search), used by the
   Discover, Reject, and Publish queues. */
.queue-search {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 1rem 0 0;
  font-size: 0.85rem;
}

.queue-search input[type="search"] {
  font-size: 0.85rem;
  padding: 0.25rem 0.4rem;
  min-width: 16rem;
  flex: 1 1 16rem;
  max-width: 28rem;
}

/* Caveat line under the bar (Rejected: body text has a retention horizon).
   Full-width so it wraps onto its own row beneath the controls. */
.queue-search-note {
  flex: 1 0 100%;
  margin: 0;
  font-size: 0.78rem;
  color: var(--color-text-muted);
}

/* Keyset "Load more" row: a full-width list item so the button centers under the
   accumulated rows; it swaps itself out for the next page (see the partial). */
.queue-load-more {
  list-style: none;
  display: flex;
  justify-content: center;
  padding: 0.75rem 0;
}

.queue-source-filter label {
  color: var(--color-text-muted);
  font-weight: 600;
}

.queue-source-filter select,
.queue-source-filter input[type="date"],
.queue-source-filter input[type="search"] {
  font-size: 0.85rem;
  padding: 0.2rem 0.3rem;
}

/* Sources search box (TICKET-112): a modest fixed width so the filter bar stays
   compact and doesn't stretch the whole row. */
.queue-source-filter input[type="search"] {
  width: 12rem;
}

/* "★ Starred only" toggle on My Sources (TICKET-112): same inline layout as the
   admin "Flagged only" toggle but in the accent colour (starring isn't a
   warning/danger action). */
.queue-starred-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 600;
  color: var(--color-accent);
  cursor: pointer;
}

/* The selects are the bar's shock absorber: they shrink first (the native
   control truncates its label) so a long publication name — or one more filter
   — costs select width rather than pushing the toggles onto a second row. */
.queue-source-filter select {
  flex: 0 1 auto;
  min-width: 4.5rem;
  max-width: 11rem;
}

/* The published-date range: two pickers joined by an en-dash, as one nowrap
   unit so "from – to" never splits across lines. */
.queue-date-range {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

.queue-date-range input[type="date"] {
  width: 7rem;
}

.queue-date-sep {
  color: var(--color-text-muted);
}

/* "Apply" + the admin "Flagged only" toggle share one nowrap group so they
   always sit on the same line: when the filter bar wraps, the pair moves
   together rather than splitting across two rows. */
.queue-filter-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* Admin-only "Flagged only" filter toggle (queue-flagged): a plain inline
   checkbox + label sitting alongside the source/date filters. Still used by the
   Rejected queue's flagged checkbox. */
.queue-flagged-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 600;
  color: var(--color-danger-text);
  cursor: pointer;
}

/* Tri-state queue filter (Summarized / Tagged / Flagged): a terse inline label
   + Any/Yes/No select. Neutral by default; ``.is-danger`` tints the Flagged one
   red to match its badge. The select is a descendant of .queue-source-filter, so
   it inherits the shrink-first "shock absorber" rule that keeps the bar one row. */
.queue-tri-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

.queue-tri-filter.is-danger {
  color: var(--color-danger-text);
}

.queue-tri-filter select {
  font-weight: 400;
}

/* Small "Clear dates" reset next to the date-range inputs. */
.queue-filter-clear {
  font-size: 0.8rem;
  color: var(--color-text-muted);
  white-space: nowrap;
}

/* Rendered even with no filters set (so the control is discoverable and the
   bar's width doesn't jump); muted + non-interactive until there's something
   to clear. */
.queue-filter-clear.is-disabled {
  opacity: 0.45;
  pointer-events: none;
}

/* Saved filters dropdown — a native <details> disclosure sitting just below the
   filter bar. Summary is a compact toggle; the menu is an absolutely-positioned
   panel with the saved-filter list + the "Save current filters" form. */
.saved-filters {
  position: relative;
  margin: 0.35rem 0 0;
  width: max-content;
}

.saved-filters > summary {
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--color-accent);
  padding: 0.15rem 0;
  user-select: none;
}

.saved-filters-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 100;
  margin-top: 0.25rem;
  min-width: 18rem;
  max-width: min(26rem, 92vw);
  padding: 0.5rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 6px;
  box-shadow: 0 6px 20px var(--shadow-md);
}

.saved-filters-list {
  list-style: none;
  margin: 0 0 0.5rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.saved-filter {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.saved-filter-apply {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.3rem 0.4rem;
  border-radius: 3px;
  color: var(--color-text);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.saved-filter-apply:hover {
  background: var(--color-accent-soft-bg);
}

.saved-filter-default,
.saved-filter-delete {
  flex: 0 0 auto;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.2rem 0.3rem;
  font-size: 0.95rem;
  line-height: 1;
  color: var(--color-text-muted);
}

.saved-filter-default.is-default {
  color: var(--color-accent);
}

.saved-filter-delete {
  color: var(--color-danger-text);
}

.saved-filter-empty {
  color: var(--color-text-muted);
  font-size: 0.85rem;
  padding: 0.3rem 0.4rem;
}

.saved-filter-save {
  display: flex;
  gap: 0.35rem;
  align-items: center;
  border-top: 1px solid var(--color-border);
  padding-top: 0.5rem;
}

.saved-filter-name {
  flex: 1 1 auto;
  min-width: 0;
}

/* Sortable column headers (TICKET-055). */
.queue-sortbar {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  margin: 1rem 0 0;
  font-size: 0.85rem;
}

.queue-sort-label {
  color: var(--color-text-muted);
  font-weight: 600;
}

.queue-sort {
  color: var(--color-accent);
  text-decoration: none;
}

.queue-sort:hover {
  text-decoration: underline;
}

.queue-sort.active {
  font-weight: 700;
}

.queue-sort-arrow {
  font-size: 0.8em;
}

/* Sortable column headers (TICKET-112 follow-up): the header cell itself is the
   sort control (Admin / My Sources tables), so the old separate "Sort:" bar is
   gone. The link inherits the muted header colour and only turns accent when it's
   the active sort; the ↕ arrow reuses .queue-sort-arrow. */
.sources-table th a.col-sort,
.my-sources-table thead th a.col-sort {
  display: inline-flex;
  align-items: baseline;
  gap: 0.2rem;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
.sources-table th a.col-sort:hover,
.my-sources-table thead th a.col-sort:hover {
  text-decoration: underline;
}
.sources-table th a.col-sort.active,
.my-sources-table thead th a.col-sort.active {
  color: var(--color-accent);
  font-weight: 700;
}

.queue {
  list-style: none;
  padding: 0;
  margin: 1rem 0;
}

.queue-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 0;
  border-top: 1px solid var(--color-border);
}

/* Brief highlight when arriving via a ?focus deep-link (Submit -> "Open in the
   discovery queue"), so the just-submitted item is easy to spot in the list. */
.queue-item--flash {
  animation: queue-item-flash 2s ease-out 1;
}

@keyframes queue-item-flash {
  0%,
  40% {
    background-color: var(--color-highlight-bg);
  }
  100% {
    background-color: transparent;
  }
}

@media (prefers-reduced-motion: reduce) {
  .queue-item--flash {
    animation: none;
    background-color: var(--color-highlight-bg);
  }
}

.queue-item-checkbox {
  flex-shrink: 0;
  margin-top: 0.5rem;
  cursor: pointer;
}

.queue-item-main {
  flex: 1;
  min-width: 0;
}

.queue-item-title {
  margin: 0 0 0.2rem;
  font-weight: 600;
}

/* Headline links out to the source article. Keep it looking like the headline
   (inherit colour, no default underline); underline only on hover/focus to
   signal it's clickable. */
.queue-item-title a {
  color: inherit;
  text-decoration: none;
}

.queue-item-title a:hover,
.queue-item-title a:focus-visible {
  text-decoration: underline;
}

/* English secondary headline (TICKET-063): muted subtitle under the original,
   shown only for items in a language the viewer does not read. Wraps long
   headlines gracefully rather than truncating. */
.queue-item-title-en {
  margin: 0 0 0.2rem;
  font-size: 0.9rem;
  font-weight: 400;
  font-style: italic;
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.queue-item-meta {
  margin: 0;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

/* Expandable draft summary on a queue item (TICKET-072 #4). */
.queue-item-summary {
  margin-top: 0.35rem;
  font-size: 0.85rem;
}

.queue-item-summary > summary {
  cursor: pointer;
  color: var(--color-text-muted);
  font-weight: 600;
}

.queue-item-summary-body {
  margin-top: 0.35rem;
  margin-left: 1rem;
  padding: 0.5rem 0.75rem;
  background: var(--color-surface-2);
  border-radius: 3px;
  white-space: pre-wrap;
}

.paywall {
  color: var(--color-warning-text);
  font-weight: 600;
}

.priority-badge {
  display: inline-block;
  margin-right: 0.35rem;
  padding: 0.05rem 0.4rem;
  border-radius: 0.75rem;
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
}

/* "⚑ flagged" badge: an item carries ≥1 open feedback report (TICKET-098). */
.flagged-badge {
  display: inline-block;
  margin-right: 0.35rem;
  padding: 0.05rem 0.4rem;
  border-radius: 0.75rem;
  background: var(--color-danger-bg);
  color: var(--color-danger-text);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
}

/* Per-source open-flag count on the admin Sources list: click-through to the
   source's "Flagged only" Discovery Queue view. */
.source-flagged {
  display: inline-block;
  margin-top: 0.2rem;
  padding: 0.05rem 0.4rem;
  border-radius: 0.75rem;
  background: var(--color-danger-bg);
  color: var(--color-danger-text);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
}
.source-flagged:hover { text-decoration: underline; }

/* Flag report (TICKET-098): the per-item parse-error report control. The Flag
   icon button opens a MODAL <dialog> (rather than an inline expander that shifted
   the row/card), so the composer floats over the page and nothing jumps. */
.flag-dialog {
  width: min(24rem, 92vw);
  border: 1px solid var(--color-border-blue);
  border-radius: 8px;
  padding: 1rem 1.25rem 1.25rem;
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: 0 10px 40px var(--shadow-md);
  font-size: 0.9rem;
}
.flag-dialog::backdrop {
  background: var(--color-backdrop);
}

.flag-dialog .flag-categories {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0 0 0.4rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border-strong);
  border-radius: 3px;
}
.flag-dialog .flag-categories legend {
  font-weight: 600;
  padding: 0 0.3rem;
}

.flag-dialog .flag-category {
  font-weight: normal;
  color: var(--color-text-muted);
}

.flag-dialog .flag-details {
  display: block;
  margin: 0.4rem 0;
  color: var(--color-text-muted);
}

.flag-dialog .flag-details textarea {
  display: block;
  width: 100%;
  font: inherit;
}

/* Dialog footer: Cancel + Submit, right-aligned. */
.flag-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.btn-flag {
  background: var(--color-surface);
  color: var(--color-warning-text);
  border-color: var(--color-warning-text);
}

.queue-item-actions {
  /* A column: the action-icon row on top, a stable message strip below. */
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  flex-shrink: 0;
}

/* Keep the per-article action icons (Open · Summarize · Post · Reject · Flag)
   on a SINGLE row — they never wrap. flex-shrink:0 (on the actions column) means
   the card's main column (flex:1, min-width:0) wraps/shrinks first, so the
   actions win the space contest and stay one line. */
.queue-item-action-buttons {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 0.3rem;
}

/* Stable strip below the buttons where htmx action indicators
   ("Summarizing…"/"Posting…") render — a reserved-height line so a message
   appearing/disappearing never shifts the buttons or the card. Truncates rather
   than widening the actions column. */
.queue-item-action-msg {
  min-height: 1.1em;
  font-size: 0.78rem;
  line-height: 1.1;
  color: var(--color-text-muted);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 12rem;
}

/* In the table the action buttons are left-aligned in their cell, so the strip
   is too (the card's actions are right-aligned). */
.queue-cell-actions .queue-item-action-msg {
  text-align: left;
}

.reject-form {
  display: inline;
  margin: 0;
}

.queue-empty {
  color: var(--color-text-muted);
}

/* Queue pager: "Showing X–Y of N" plus the page links. Rendered above the list
   and repeated below it on a paginated queue. */
.queue-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  margin: 0.75rem 0;
  font-size: 0.9rem;
  color: var(--color-text-muted);
}

.queue-pager-controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.queue-pager-position {
  white-space: nowrap;
}

.queue-pager-link {
  white-space: nowrap;
}

/* "count all" — the on-demand total. Counting the filtered set is expensive
   enough (it reads every candidate article body) that it is an explicit action
   rather than something the page does for you; styled as a quiet inline link so
   it reads as an option, not a primary control. */
.queue-pager-count {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--color-link);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}

.queue-pager-count:hover {
  text-decoration: none;
}

.queue-pager-count[disabled] {
  color: var(--color-text-muted);
  cursor: progress;
  text-decoration: none;
}

.queue-pager-bottom {
  border-top: 1px solid var(--color-border-subtle);
}

/* Cross-page selection opt-in ("Select all N matching this filter"). The shared
   .batch-scope box styling (info-colored, bordered) is reused by the publish
   queue's static bulk-action note, so keep the base rule layout-neutral. */
.batch-scope {
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--color-info-border);
  border-radius: 4px;
  background: var(--color-info-bg);
  font-size: 0.9rem;
}

.batch-scope[hidden] {
  display: none;
}

/* The interactive discovery-queue scope control sits on its OWN full-width strip
   below the action row (not inline among the buttons), so its long label can
   never push or overlap them. Flex-lay out its inner button + active label.
   :not([hidden]) keeps the class-level [hidden] { display:none } winning despite
   the id's higher specificity when the scope is inactive. */
#batch-scope:not([hidden]),
.batch-scope-active:not([hidden]) {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.btn-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-link);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.btn-link:hover {
  color: var(--color-link-hover);
}

/* Batch summarization actions bar (Sprint 2 Feature #1) */
.queue-batch-actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 1rem 0;
  padding: 0.75rem 0;
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

/* The action controls (Select all, count, Generate/Post/Reject) live on one
   row; wrap rather than overlap if the viewport is narrow. The cross-page
   ".batch-scope" strip is a SEPARATE sibling below this row, so its long label
   never competes with these buttons for horizontal space. */
.queue-batch-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
}

.batch-select-all {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  user-select: none;
}

.batch-select-all input[type="checkbox"] {
  cursor: pointer;
}

.batch-count {
  color: var(--color-text-muted);
  font-size: 0.9rem;
  flex-shrink: 0;
}

#batch-form,
#batch-post-form,
#batch-reject-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
}

#batch-generate-btn,
#batch-post-btn,
#batch-reject-btn {
  flex-shrink: 0;
}

#batch-generate-btn:disabled,
#batch-post-btn:disabled,
#batch-reject-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

#batch-spinner,
#batch-post-spinner,
#batch-reject-spinner {
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

/* Feedback strip below the batch buttons: the in-flight spinner and the
   post/reject result banner live here on their own line, so a message that
   appears mid-action never pushes the buttons around. A small reserved height
   keeps the row from jumping when a message shows/hides. */
.queue-batch-feedback {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 1.25rem;
  font-size: 0.9rem;
}

.batch-status {
  color: var(--color-danger-text);
  font-size: 0.9rem;
}

.batch-status-ok {
  color: var(--color-success-text);
}

.batch-status-error {
  color: var(--color-danger-text);
  font-weight: 600;
}

.htmx-indicator {
  display: none;
}

.htmx-request .htmx-indicator {
  display: inline;
}

.submit-form {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  max-width: 30rem;
}

.submit-form input {
  padding: 0.5rem;
  font-size: 1rem;
}

.submit-form button {
  align-self: flex-start;
  margin-top: 0.5rem;
}

/* --- Review workspace (TICKET-024) --- */
.review-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.review-head-titles {
  min-width: 0;
}

/* Flag + Reject sit together on the right of the review head. */
.review-head-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

/* Inline result of filing a flag from the review workspace. */
.review-flag-status {
  margin: 0.25rem 0 0;
  min-height: 1.2em;
}

/* "Flagged" badge in the review head when the item has open editor reports.
   Shown to every editor (the report list itself stays admin-only). */
.review-flag-indicator {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.4rem;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--color-warning-border);
  border-radius: 999px;
  background: var(--color-warning-bg-soft);
  color: var(--color-warning-text);
  font-size: 0.8rem;
  font-weight: 600;
  text-decoration: none;
  width: fit-content;
}

a.review-flag-indicator:hover {
  background: var(--color-warning-bg);
}

/* English secondary headline on review + preview (TICKET-063): muted subtitle
   beneath the prominent original/edited title. */
.review-title-en,
.preview-title-en {
  margin: -0.4rem 0 0.6rem;
  font-size: 1rem;
  font-weight: 400;
  font-style: italic;
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.btn-secondary {
  background: var(--color-surface);
  color: var(--color-accent);
}

.form-success {
  color: var(--color-success-text);
  font-weight: 600;
}

.muted {
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

/* "Also file under" multi-category group in the review workspace: the select
   holds the primary; these checkboxes manage the additional categories. Wrapped
   flex so any vocabulary size lays out without horizontal overflow. */
.extra-categories {
  margin: 0.5rem 0 0;
  padding: 0.4rem 0.6rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: 3px;
}

.extra-categories legend {
  font-weight: 600;
  font-size: 0.85rem;
  padding: 0 0.3rem;
  color: var(--color-text-muted);
}

.extra-categories-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 1rem;
}

.extra-category-option {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.9rem;
  white-space: nowrap;
}

.slug-preview {
  margin: 0.25rem 0 0;
  font-size: 0.9rem;
}

.slug-preview code {
  color: var(--color-text-muted);
}

.slug-preview-warn {
  color: var(--color-warning-text);
}

.extract-panel {
  margin: 1rem 0;
}

.source-extract {
  white-space: pre-wrap;
  max-height: 16rem;
  overflow-y: auto;
  padding: 0.75rem;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 3px;
  font-size: 0.9rem;
}

.inline-form,
.paste-form {
  margin: 0.5rem 0;
}

.paste-form {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  max-width: 40rem;
}

/* "Edit extract" disclosure: a plain, discreet toggle so the read-only extract
   stays the default view; expanded it reveals the prefilled .paste-form. */
.extract-edit {
  margin: 0.5rem 0;
}

.extract-edit > summary {
  cursor: pointer;
  width: fit-content;
  color: var(--color-accent);
  font-size: 0.9rem;
}

.review-form {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  max-width: 44rem;
}

.review-form label {
  font-weight: 600;
  margin-top: 0.5rem;
}

.review-form input[type="text"],
.review-form textarea,
.review-form select {
  padding: 0.5rem;
  font-size: 1rem;
  font-family: inherit;
}

/* Title + Summary share one container so both inputs align (TICKET-043). */
.article-fields {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin: 0.5rem 0;
}

.field-block {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* Stacked label above its control, same width for Title and Summary. */
.field-block > label {
  font-weight: 600;
  margin-top: 0;
}

.field-block > input[type="text"],
.field-block > textarea {
  width: 100%;
  box-sizing: border-box;
}

.summary-highlight-legend {
  margin: 0.15rem 0;
}

.summary-preview {
  padding: 0.6rem 0.75rem;
  margin-bottom: 0.4rem;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 3px;
  white-space: pre-wrap;
  line-height: 1.5;
}

.priority-term {
  background: var(--color-highlight-bg);
  border-radius: 2px;
  padding: 0 0.15rem;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

.generate-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0.5rem 0;
}

.htmx-indicator {
  display: none;
  color: var(--color-text-muted);
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  display: inline;
}

.tag-picker {
  position: relative;
}

.tag-results {
  margin: 0.25rem 0;
}

.priority-matches {
  margin: 0.75rem 0;
  padding: 0.6rem 0.75rem;
  background: var(--color-warning-bg-soft);
  border: 1px solid var(--color-warning-border);
  border-radius: 4px;
}

.priority-matches-head {
  margin: 0 0 0.4rem;
  font-weight: 600;
  color: var(--color-warning-text);
}

.priority-match-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.priority-match-list li {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.priority-match {
  padding: 0.1rem 0.5rem;
  background: var(--color-warning-bg);
  color: var(--color-warning-text);
  border: 1px solid var(--color-warning-border);
  border-radius: 12px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.priority-match:hover {
  background: var(--color-warning-bg);
}

.priority-match-why {
  font-size: 0.8rem;
}

/* Ingestion Keywords (TICKET-164) — reuses .priority-match-list layout. */
.ingestion-keywords {
  margin: 0.75rem 0;
}
.ingestion-keyword-set {
  margin: 0.5rem 0 0.2rem;
  font-weight: 600;
  font-size: 0.85rem;
}
.ingestion-keyword {
  font-size: 0.9rem;
}
.ingestion-keyword-en {
  font-size: 0.8rem;
}

.tag-options {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 3px;
  max-height: 12rem;
  overflow-y: auto;
}

.tag-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.4rem 0.6rem;
  background: var(--color-surface);
  border: none;
  border-bottom: 1px solid var(--color-border-subtle);
  cursor: pointer;
  font-size: 0.9rem;
}

.tag-option:hover {
  background: var(--color-accent-soft-bg);
}

.selected-tags {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0;
  margin: 0.5rem 0;
}

.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.5rem;
  background: var(--color-accent-soft-bg);
  border: 1px solid var(--color-accent-soft-border);
  border-radius: 12px;
  font-size: 0.85rem;
}

.tag-remove {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  color: var(--color-danger-text);
}

/* Category facet pickers (region / domain / other). Chips are colour-coded by
   facet so the three buckets read at a glance; the widgets otherwise reuse the
   tag-picker layout classes. */
.category-pickers {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin: 0.5rem 0;
}

.category-picker-group {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.category-picker-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text-muted);
}

.category-chip--region {
  background: var(--color-info-bg);
  border-color: var(--color-info-border);
}

.category-chip--domain {
  background: var(--color-success-bg);
  border-color: var(--color-success-border);
  color: var(--color-success-text);
}

.category-chip--other {
  background: var(--color-warning-bg-soft);
  border-color: var(--color-warning-border);
  color: var(--color-warning-text);
}

/* Enriched picker: layout option rows as name + badges, with keyboard + */
/* already-selected states, plus priority/rank badges on options and chips. */
.tag-option {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.tag-option-name {
  flex: 1 1 auto;
  min-width: 0;
}

.tag-option.is-active {
  background: var(--color-accent-soft-bg-hover);
}

.tag-option.is-selected {
  opacity: 0.55;
  cursor: default;
}

.tag-option-added {
  display: none;
  font-size: 0.75rem;
  color: var(--color-accent);
}

.tag-option.is-selected .tag-option-added {
  display: inline;
}

.tag-badge {
  flex: 0 0 auto;
  font-size: 0.7rem;
  line-height: 1;
  padding: 0.1rem 0.35rem;
  border-radius: 8px;
  font-variant-numeric: tabular-nums;
}

.tag-badge--priority {
  color: var(--color-accent);
}

.tag-badge--rank {
  background: var(--color-accent-soft-bg);
  border: 1px solid var(--color-accent-soft-border);
  color: var(--color-accent);
}

/* AI-applied tag. Deliberately muted and outline-only, unlike the accent-filled
   rank badge: an auto tag is a suggestion awaiting the editor's judgement, not
   an achievement, and it should not compete with the priority star beside it. */
.tag-badge--auto {
  background: transparent;
  border: 1px dashed var(--color-border);
  color: var(--color-text-muted);
  font-size: 0.62rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.tag-match-alias {
  flex: 0 0 auto;
  font-size: 0.72rem;
  font-style: italic;
  color: var(--color-accent);
}

/* Queue-row quick-tag control: compact, collapsed by default. */
.queue-item-tags,
.queue-item-categories {
  margin-top: 0.4rem;
}

.queue-item-tags > summary,
.queue-item-categories > summary {
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--color-accent);
}

.queue-item-tags .tag-picker,
.queue-item-categories .category-pickers {
  margin-top: 0.4rem;
}

/* Pillar filter (soft highlight + float): token picker above the tag search. */
.pillar-filter {
  margin-bottom: 0.5rem;
}

.pillar-options {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 3px;
  max-height: 12rem;
  overflow-y: auto;
}

.pillar-option {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  width: 100%;
  text-align: left;
  padding: 0.35rem 0.6rem;
  background: var(--color-surface);
  border: none;
  border-bottom: 1px solid var(--color-border-subtle);
  cursor: pointer;
  font-size: 0.9rem;
}

.pillar-option:hover,
.pillar-option.is-active {
  background: var(--color-accent-soft-bg-hover);
}

.pillar-breadcrumb {
  font-size: 0.72rem;
  color: var(--color-accent);
  opacity: 0.8;
}

.pillar-tokens {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0;
  margin: 0.4rem 0 0;
}

.pillar-token {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.5rem;
  background: var(--color-accent-soft-bg);
  border: 1px solid var(--color-accent-soft-border);
  border-radius: 12px;
  font-size: 0.8rem;
}

.pillar-remove {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  color: var(--color-danger-text);
}

/* A tag matched by the active pillar filter: highlighted + a diamond badge. */
.tag-option.in-pillar {
  background: var(--color-accent-soft-bg);
}

.tag-badge--pillar {
  color: var(--color-accent);
}

.select-row {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.select-row span {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.review-actions {
  display: flex;
  gap: 0.75rem;
  margin-top: 1rem;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
}

.review-actions-right {
  display: flex;
  gap: 0.75rem;
  align-items: center;
}

/* Reserved strip below the action row for the "Publishing…" indicator, so it
   never widens the button group (which would shove Publish sideways). Right-
   aligned to sit under the Publish button; min-height reserves the line whether
   or not the message is showing, so nothing below shifts either. */
.review-publish-status {
  min-height: 1.2rem;
  margin-top: 0.35rem;
  text-align: right;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

/* Ambient autosave indicator (replaces the Save button). */
.save-status {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 1.4rem;
}
.save-status--saving { color: var(--color-text-muted); }
.save-status--saved { color: var(--color-success-text); }
.save-status--error { color: var(--color-danger-text-deep); }
.save-status .save-status-text::before {
  content: "•";
  margin-right: 0.35rem;
}
.save-status--saving .save-status-text::before { content: "⟳"; }
.save-status--saved .save-status-text::before { content: "✓"; }
.save-status--error .save-status-text::before { content: "⚠"; }
.save-retry {
  background: none;
  border: none;
  color: var(--color-link);
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
  font: inherit;
}

/* "options…" link next to Publish (non-default distribution dialog). */
.publish-options-link {
  font-size: 0.85rem;
  color: var(--color-link);
  cursor: pointer;
}

/* Inline publish-validation errors above the action row. */
.review-errors {
  margin: 1rem 0 0;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--color-danger-border);
  background: var(--color-danger-bg);
  border-radius: 4px;
  color: var(--color-danger-text-deep);
  font-size: 0.9rem;
}
.review-errors-head { margin: 0 0 0.25rem; font-weight: 600; }
.review-errors ul { margin: 0; padding-left: 1.2rem; }

.preview-panel {
  margin: 1.5rem 0;
  padding: 1rem;
  border: 1px solid var(--color-border);
  border-radius: 3px;
}

.preview-note {
  font-size: 0.8rem;
  color: var(--color-text-muted);
  margin-top: 0;
}

.preview-title a {
  /* Use the browser's default link colors (blue unvisited, purple visited)
     so the editor can see at a glance whether they've opened the source. */
  color: revert;
  text-decoration: none;
}

.preview-title a:hover,
.preview-title a:focus {
  text-decoration: underline;
}

.preview-source-meta {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  margin-top: 0;
}

.preview-summary {
  white-space: pre-wrap;
}

.preview-tag {
  display: inline-block;
  margin-right: 0.4rem;
  padding: 0.1rem 0.45rem;
  background: var(--color-accent-soft-bg);
  border-radius: 10px;
  font-size: 0.8rem;
}

/* --- Publish dialog + status (TICKET-031, ui-flows S5/S6) --- */
.publish-panel {
  margin: 1rem 0;
  padding: 1rem;
  border: 1px solid var(--color-border-blue);
  border-radius: 6px;
  background: var(--color-surface-2);
}

.publish-targets {
  border: 0;
  margin: 0 0 0.75rem;
  padding: 0;
}

.publish-target {
  display: block;
  margin: 0.25rem 0;
}

.publish-content-type {
  margin: 0.5rem 0;
}

.publish-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.publish-step {
  margin: 0.4rem 0;
}

.publish-step.failed {
  color: var(--color-danger-text-deep);
  font-weight: 600;
}

.publish-step.ok {
  color: var(--color-success-text);
}

.publish-step.pending {
  color: var(--color-warning-text);
}

/* Bulk-select column (TICKET-077): the Live screen shares the Rejected view's
   queue-item flex row, so the checkbox is a normal flex child at the head of the
   Select column. Deleted rows leave the cell empty (they can't be taken down
   again). */
.published-select {
  cursor: pointer;
}

/* Public-path cell on the Live table: keep the monospace path from forcing the
   table wide — let it wrap/break within a sensible column. */
.queue-cell-public {
  max-width: 22rem;
  overflow-wrap: anywhere;
}
.queue-cell-public code {
  font-size: 0.8rem;
}

/* "With selected:" bulk-action toolbar above the list (TICKET-077). */
.bulk-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.5rem 0;
}
.bulk-actions__label {
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

/* Lifecycle status badge: a small pill prefixing the headline on the Live
   screen's queue-item rows (functional, minimal). */
.published-status {
  display: inline-block;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.05rem 0.4rem;
  border-radius: 0.25rem;
  margin-right: 0.4rem;
}
.status-live { background: var(--color-success-bg); color: var(--color-success-text); }
.status-unpublished { background: var(--color-warning-bg); color: var(--color-warning-text); }
.status-deleted { background: var(--color-border-subtle); color: var(--color-text-subtle); }

.btn-small { font-size: 0.8rem; padding: 0.15rem 0.5rem; }
.btn-danger { color: var(--color-danger-text); border-color: var(--color-danger-text); }

.flash {
  padding: 0.4rem 0.6rem;
  background: var(--color-info-bg);
  border: 1px solid var(--color-info-border);
  border-radius: 0.25rem;
}

.live-banner {
  padding: 0.5rem 0.7rem;
  background: var(--color-warning-bg-soft);
  border: 1px solid var(--color-warning-border);
  border-radius: 0.3rem;
  color: var(--color-warning-text);
}

/* --- Primary navigation (TICKET-041) --- */
.site-nav {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-left: 1.5rem;
  flex: 1;
}

.nav-link {
  display: inline-block;
  padding: 0.35rem 0.75rem;
  border-radius: 4px;
  color: var(--color-on-header);
  text-decoration: none;
  font-size: 0.8rem;
  line-height: 1.2;
  transition:
    background 0.12s ease,
    color 0.12s ease;
}

.nav-link:hover,
.nav-link:focus-visible {
  background: var(--color-nav-hover-bg);
  color: var(--color-on-accent);
}

.nav-link.active {
  background: var(--color-on-header-strong);
  color: var(--color-accent);
  font-weight: 600;
}

/* "New" — the one create action in the nav. It reads as a plain header link like
   the others (no outline/box, so its visible extent is just the text and every
   nav item occupies the same total width), kept subtly distinct as the create
   CTA by a heavier weight. When you ARE on the New page it fills green (its own
   active state), matching how the other links fill when active. Its hover/active
   overrides beat the generic .nav-link rules by specificity. */
.nav-link-new {
  background: transparent;
  color: var(--color-on-header);
  font-weight: 600;
}

.nav-link-new:hover,
.nav-link-new:focus-visible {
  background: var(--color-nav-hover-bg);
  color: var(--color-on-accent);
}

.nav-link-new.active {
  background: var(--color-success-text);
  color: var(--color-success-bg);
}

/* Red count bubble on the "Publish" nav link — how many articles are queued but
   not yet published, visible on every page. Superscript-positioned so it reads
   as a small badge riding the top-right of the word. */
.nav-badge {
  display: inline-block;
  margin-left: 0.15rem;
  padding: 0 0.3rem;
  min-width: 0.85rem;
  border-radius: 0.7rem;
  background: var(--color-badge-count-bg);
  color: var(--color-on-accent);
  font-size: 0.6rem;
  font-weight: 700;
  line-height: 1.4;
  text-align: center;
  vertical-align: super;
}

/* Admin dropdown menu */
.nav-dropdown {
  position: relative;
}

.nav-dropdown-toggle {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.35rem 0.75rem;
}

.nav-dropdown-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  background: var(--color-header-bg);
  border-radius: 4px;
  min-width: 200px;
  box-shadow: 0 4px 12px var(--shadow-sm);
  z-index: 100;
  /* Clip child hover backgrounds to the rounded corners. */
  overflow: hidden;
}

/* Admin section tabs — the 3rd-level navigation under the 2-level Admin menu.
   Rendered by _admin_tabs.html at the top of each section screen. */
.admin-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  border-bottom: 1px solid var(--color-border-blue);
  margin: 0 0 1.25rem;
}
.admin-tab {
  padding: 0.5rem 0.9rem;
  margin-bottom: -1px; /* sit the active underline on the row's border */
  border-bottom: 2px solid transparent;
  color: var(--color-text-muted);
  text-decoration: none;
  font-weight: 500;
  white-space: nowrap;
}
.admin-tab:hover {
  color: var(--color-text);
}
.admin-tab.active {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
  font-weight: 600;
}

.nav-dropdown.open .nav-dropdown-menu {
  display: block;
}

.nav-dropdown-item {
  display: block;
  padding: 0.75rem 1rem;
  color: var(--color-on-header);
  text-decoration: none;
  font-size: 0.95rem;
  transition:
    background 0.12s ease,
    color 0.12s ease;
}

.nav-dropdown-item:first-child {
  border-radius: 4px 4px 0 0;
}

.nav-dropdown-item:last-child {
  border-radius: 0 0 4px 4px;
}

.nav-dropdown-item:hover,
.nav-dropdown-item:focus-visible {
  background: var(--color-nav-hover-bg);
  color: var(--color-on-accent);
  outline: none;
}

.nav-dropdown-item.active {
  background: var(--color-on-header-strong);
  color: var(--color-accent);
  font-weight: 600;
}

/* Sources admin (S8) */

/* Horizontal-scroll safety net: on narrow viewports the 8-column table keeps
   its min-width and scrolls instead of wrapping/clipping. */
.sources-table-wrap {
  overflow-x: auto;
  margin-bottom: 1.5rem;
}

/* Generic admin list table (e.g. Keyword Sets). Uses NATURAL (auto) column
   sizing — unlike .sources-table, whose per-column nth-child widths + fixed
   layout + overflow:hidden are tuned for the 10-column publications table and
   would squeeze/clip the Actions column of a shorter table. Each column sizes to
   its content, and the Actions column never wraps or clips its buttons. */
.admin-table-wrap {
  overflow-x: auto;
  margin-bottom: 1.5rem;
}
.admin-table {
  width: 100%;
  border-collapse: collapse;
}
.admin-table th,
.admin-table td {
  text-align: left;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--color-border-blue);
  font-size: 0.85rem;
  vertical-align: top;
}
.admin-table .col-num {
  text-align: right;
  white-space: nowrap;
}
/* Keep the expandable keyword list from stretching the table too wide. */
.admin-table .source-keywords-cell {
  max-width: 26rem;
}
/* Actions column: shrink to its buttons and keep them on one line, uncut. */
.admin-table td.source-actions,
.admin-table th:last-child {
  width: 1%;
  white-space: nowrap;
}

.sources-table {
  width: 100%;
  min-width: 55rem;
  border-collapse: collapse;
  table-layout: fixed;
}

.sources-table th,
.sources-table td {
  text-align: left;
  padding: 0.35rem 0.5rem;
  border-bottom: 1px solid var(--color-border-blue);
  font-size: 0.8rem;
  vertical-align: top;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Column widths for the EIGHT-column publications table. Keep these in sync
   with the <th> order in admin_sources.html: adding/removing a column shifts
   every nth-child, and with `table-layout: fixed` + `overflow: hidden` a column
   left without a rule gets squeezed and clips its content (the Actions icons). */
.sources-table th:nth-child(1),
.sources-table td:nth-child(1) { width: 13%; }   /* Name */
.sources-table th:nth-child(2),
.sources-table td:nth-child(2) { width: 10%; }   /* Domain */
.sources-table th:nth-child(3),
.sources-table td:nth-child(3) { width: 5%; }    /* Lang */
.sources-table th:nth-child(4),
.sources-table td:nth-child(4) { width: 7%; }    /* Policy */
.sources-table th:nth-child(5),
.sources-table td:nth-child(5) { width: 5%; overflow: visible; }  /* Keywords */
.sources-table th:nth-child(6),
.sources-table td:nth-child(6) { width: 14%; white-space: normal; }  /* Feeds */
.sources-table th:nth-child(7),
.sources-table td:nth-child(7) { width: 8%; white-space: normal; }   /* Items */
.sources-table th:nth-child(8),
.sources-table td:nth-child(8) { width: 12%; overflow: visible; white-space: normal; }  /* Polling */
.sources-table th:nth-child(9),
.sources-table td:nth-child(9) { width: 6%; }    /* Status */
.sources-table th:nth-child(10),
.sources-table td:nth-child(10) { width: 20%; overflow: visible; }  /* Actions */

/* Per-publication stats cell (items ingested + last activity). */
.source-stats {
  font-size: 0.75rem;
  line-height: 1.3;
}
.source-stats .stat-total {
  display: block;
  font-weight: 600;
}
.source-stats .stat-date {
  color: var(--color-text-muted);
}
.source-stats .stat-never {
  color: var(--color-text-faint);
}

.source-row.inactive {
  color: var(--color-text-subtle);
}

/* Feeds modal: the mechanisms sub-table + add-feed form for one publication,
   opened by clicking a Feeds chip. Rendered in the top layer, so it's not
   clipped by the fixed table's overflow:hidden. */
.feeds-dialog {
  width: min(48rem, 92vw);
  max-height: 85vh;
  overflow: auto;
  border: 1px solid var(--color-border-blue);
  border-radius: 8px;
  padding: 1rem 1.25rem 1.25rem;
  box-shadow: 0 10px 40px var(--shadow-md);
}
.feeds-dialog::backdrop {
  background: var(--color-backdrop);
}
.feeds-dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.5rem;
}
.feeds-dialog-head h3 {
  margin: 0;
  font-size: 1rem;
}
.feeds-dialog-close-form {
  margin: 0;
}
.feeds-dialog-close {
  font-size: 1rem;
  line-height: 1;
}

/* Feeds chips are buttons that open the modal — reset the native button chrome
   so they still look like badges, and hint clickability. */
.badge-chip {
  border: 0;
  cursor: pointer;
  font-family: inherit;
  vertical-align: middle;
}
.badge-chip:hover,
.badge-chip:focus-visible {
  filter: brightness(0.95);
  outline: 2px solid var(--color-accent-soft-border);
  outline-offset: 1px;
}
.badge-none.badge-chip:hover,
.badge-none.badge-chip:focus-visible {
  filter: none;
  color: var(--color-accent-hover);
}

.mechanisms-table {
  width: 100%;
  border-collapse: collapse;
  margin: 0.5rem 0 0.75rem;
}

.mechanisms-table th,
.mechanisms-table td {
  text-align: left;
  padding: 0.3rem 0.5rem;
  border-bottom: 1px solid var(--color-border-blue);
  font-size: 0.78rem;
  vertical-align: top;
}

.mechanism-row.inactive {
  color: var(--color-text-subtle);
}

.mechanism-readonly {
  color: var(--color-text-subtle);
  font-style: italic;
}

/* Feed name (mechanism label) shown above its URL / crawl config. */
.mechanism-label {
  font-weight: 600;
}
.mechanism-config-path {
  color: var(--color-text-muted);
  font-size: 0.92em;
}
.crawl-sections li strong {
  font-weight: 600;
}

.crawl-selectors {
  margin-top: 0.35rem;
  font-size: 0.85em;
}
.crawl-selectors summary {
  cursor: pointer;
  color: var(--color-text-muted);
}
.crawl-selectors-dl {
  margin: 0.35rem 0 0;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.15rem 0.6rem;
}
.crawl-selectors-dl dt {
  font-weight: 600;
  color: var(--color-text-muted);
}
.crawl-selectors-dl dd {
  margin: 0;
}
.crawl-selectors-dl code {
  background: var(--color-surface-3);
  padding: 0 0.25rem;
  border-radius: 3px;
}
.crawl-sections {
  margin: 0;
  padding-left: 1rem;
  word-break: break-all;
}

.badge-mechanism {
  background: var(--color-badge-mechanism-bg);
  color: var(--color-accent-hover);
  margin-left: 0;
  margin-right: 0.35rem;
  text-transform: none;
  letter-spacing: 0;
}

.badge-manual {
  background: var(--color-badge-manual-bg);
  color: var(--color-badge-manual-text);
}

/* Read-only source-group badges (Source Groups): shown under a source's name on
   My Sources. Reuses the mechanism-badge palette so they read as quiet chips. */
.badge-group {
  background: var(--color-badge-mechanism-bg);
  color: var(--color-accent-hover);
  margin-left: 0;
  margin-right: 0.35rem;
  text-transform: none;
  letter-spacing: 0;
}

.my-source-groups {
  margin-top: 0.25rem;
}

.badge-none {
  background: transparent;
  color: var(--color-text-faint);
  font-weight: normal;
  text-transform: none;
  letter-spacing: 0;
  margin-left: 0;
}

.add-mechanism-form {
  margin-top: 0.5rem;
  padding: 0.5rem 0.75rem;
  border: 1px dashed var(--color-border-blue);
  border-radius: 4px;
}

.add-mechanism-form h4 {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
}

.status-active,
.poll-ok {
  color: var(--color-success-text);
  font-weight: 600;
}

.status-inactive,
.poll-err {
  color: var(--color-danger-text);
  font-weight: 600;
}

.poll-never {
  color: var(--color-text-subtle);
}

/* Per-row action feedback banner (e.g. a blocked source delete). Rendered as a
   page-level banner so the message never shifts the table's action buttons. */
.source-status:empty {
  display: none;
}

.source-status:not(:empty) {
  margin: 0.75rem 0;
  padding: 0.5rem 0.7rem;
  background: var(--color-danger-bg);
  border: 1px solid var(--color-danger-border);
  border-radius: 0.3rem;
}

.source-actions {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.3rem;
  align-items: center;
}

.source-actions a,
.source-actions button {
  font-size: 0.8rem;
  padding: 0.2rem 0.45rem;
  white-space: nowrap;
  flex-shrink: 0;
}

.source-actions form {
  display: inline;
  margin: 0;
}

/* Icon-only action buttons (edit / poll / pause·play / delete). */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* border-box so the 1px border is counted inside the 30px: <button> defaults
     to border-box but <a class="btn-icon"> (Edit) would use content-box and end
     up 2px taller, mismatching the other action icons. */
  box-sizing: border-box;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--color-border-strong);
  border-radius: 6px;
  background: var(--color-surface);
  color: var(--color-text-muted);
  cursor: pointer;
  line-height: 0;
}
.btn-icon:hover {
  background: var(--color-surface-2);
  color: var(--color-text);
  border-color: var(--color-text-faint);
}
.btn-icon .icon {
  display: block;
}
.btn-icon-danger {
  color: var(--color-danger-text);
  border-color: var(--color-danger-border);
}
.btn-icon-danger:hover {
  background: var(--color-danger-bg);
  color: var(--color-danger-text);
  border-color: var(--color-danger-text);
}

/* A disabled icon button must look disabled (the row's Post icon greys out when
   the item isn't postable; the reason lives in the Status column + tooltip). */
.btn-icon:disabled,
.btn-icon[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Colour-code the Post action (green) at a glance among the icon-only row
   actions. Post summarizes-if-missing before staging, so it subsumes the old
   accent "AI Post" button. */
.btn-icon-post {
  color: var(--color-success-text);
  border-color: var(--color-success-border);
}
.btn-icon-post:hover:not(:disabled) {
  background: var(--color-success-bg);
  color: var(--color-success-text);
  border-color: var(--color-success-text);
}

.source-form {
  display: grid;
  gap: 0.4rem;
  max-width: 32rem;
}

/* Page-action buttons above the sources table ("Poll all sources" +
   "Add a publication"). */
.sources-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

/* "N filtered out" note beside a source's item total (TICKET-163). Muted so it
   reads as secondary to the primary items count. */
.stat-filtered {
  color: var(--color-text-muted);
  font-size: 0.85em;
}

/* Keyword Set picker (TICKET-163) — the checkbox list on the source create
   modal / edit form. A scrollable column so a large library stays usable. */
.keyword-set-picker {
  border: 1px solid var(--color-border-blue);
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
  margin: 0;
}
.keyword-set-picker legend {
  font-weight: 600;
  padding: 0 0.25rem;
}
.keyword-set-options {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  max-height: 14rem;
  overflow-y: auto;
}
.keyword-set-option {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}
.keyword-set-option .keyword-set-meta {
  color: var(--color-text-muted);
  font-size: 0.85em;
}
/* Per-set word-count gate ("min words") on the picker (TICKET-163). Pushed to
   the right of each option; muted so it reads as an advanced knob. */
.keyword-set-option .keyword-set-minwords {
  margin-left: auto;
  color: var(--color-text-muted);
  font-size: 0.85em;
  white-space: nowrap;
}
.keyword-set-option .minwords-input {
  width: 5rem;
}

/* My Sources (TICKET-065/066): one compact row per source with a star icon. */

.my-sources-intro {
  color: var(--color-text-muted);
  margin: 0 0 1rem;
}

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

/* ===== Discovery queue as a table (My Sources pattern, TICKET-queue-table) =====
   #queue is a <div> wrapping this table (see _queue_table.html). Each item is a
   <tbody class="queue-item"> — the old flex-card `.queue-item` rule is overridden
   below so the tbody behaves as a real table row-group. */
.queue-table-wrap {
  overflow-x: auto; /* 9 columns — scroll on narrow screens instead of overflowing */
}

.queue-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.queue-table thead th {
  text-align: left;
  padding: 0.4rem 0.6rem;
  font-size: 0.78rem;
  color: var(--color-text-muted);
  border-bottom: 2px solid var(--color-border-blue);
  white-space: nowrap;
  vertical-align: bottom;
}

.queue-table td {
  padding: 0.5rem 0.6rem;
  vertical-align: top;
}

/* Override the legacy flex-card `.queue-item` so a <tbody> lays out as a table
   row-group. Divide BETWEEN items (a top border on each item's main row), not
   between an item's own two rows. */
.queue-table tbody.queue-item {
  display: table-row-group;
}
.queue-table .queue-item-main > td {
  border-top: 1px solid var(--color-border);
  padding-bottom: 0.25rem;
}
.queue-table .queue-item-detail > td {
  padding-top: 0;
  border-top: 0;
}

.queue-table .col-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.queue-table .col-select {
  width: 1.8rem;
  text-align: center;
}
.queue-table .col-actions {
  white-space: nowrap;
}

/* Headline cell: optional thumbnail floated beside the stacked title/subtitle. */
.queue-cell-headline {
  min-width: 16rem;
}
.queue-cell-headline .queue-thumb {
  float: left;
  margin: 0.1rem 0.6rem 0.2rem 0;
}
.queue-headline-text {
  overflow-wrap: anywhere;
}
a.queue-item-title {
  color: inherit;
  text-decoration: none;
  font-weight: 600;
}
a.queue-item-title:hover,
a.queue-item-title:focus-visible {
  text-decoration: underline;
}
.queue-cell-headline .queue-item-title-en {
  display: block;
  margin: 0.1rem 0 0;
}

/* Source cell: publication name over a muted author line. */
.queue-cell-source-name {
  display: block;
}
.queue-cell-author {
  display: block;
  font-size: 0.8rem;
  color: var(--color-text-muted);
}

.queue-cell-date,
.queue-cell-lang {
  white-space: nowrap;
  color: var(--color-text-muted);
}

/* Status cell: pills stacked vertically, each shrunk to its content. */
.queue-cell-status {
  min-width: 8rem;
}
.queue-cell-status .status-pill {
  display: flex;
  width: max-content;
  max-width: 100%;
  margin-bottom: 0.2rem;
}

/* Actions cell: the buttons wrap as a row; the Flag composer drops below. */
.queue-cell-actions .queue-actions {
  /* Keep the action icons on a single row (never wrap); the cell sizes to fit
     the row and the wider Headline column gives way instead. */
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 0.3rem;
}

/* Don't let the actions column be squeezed narrow enough to clip the icons. */
.queue-cell-actions {
  white-space: nowrap;
  width: 1%;
}
.queue-cell-actions .post-form,
.queue-cell-actions .reject-form {
  margin: 0;
}

/* Rejected/Live queues page via a "Load more" control that lives INSIDE the
   table as its own <tbody>/<tr> (so it stays valid table content and htmx can
   append the next tbodies after it). Override the legacy flex `.queue-load-more`
   card rule so the tbody lays out as a real row-group and the button centers in
   its full-width cell. */
.queue-table tbody.queue-load-more {
  display: table-row-group;
}
.queue-table .queue-load-more td {
  text-align: center;
  padding: 0.75rem 0;
  border-top: 1px solid var(--color-border);
}

.my-sources-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.my-sources-table thead th {
  text-align: left;
  padding: 0.4rem 0.6rem;
  font-size: 0.78rem;
  color: var(--color-text-muted);
  border-bottom: 2px solid var(--color-border-blue);
  white-space: nowrap;
}

.my-sources-table td {
  padding: 0.4rem 0.6rem;
  vertical-align: middle;
}

/* One publication = one <tbody> (main row + keyword detail row). Divide between
   publications, not between a publication's two rows. */
.my-source .my-source-detail td {
  border-bottom: 1px solid var(--color-border-blue);
  padding-top: 0;
}
.my-source-main td {
  padding-bottom: 0.2rem;
}

/* Right-aligned numeric columns with tabular figures so digits line up. */
.my-sources-table .col-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.my-sources-table .col-star {
  width: 1.6rem;
  text-align: center;
}

.my-source-name {
  font-weight: 600;
}

.my-source-mechanisms {
  white-space: normal;
}
.my-source-mechanisms .badge {
  margin: 0 0.25rem 0.15rem 0;
}

.star-toggle {
  margin: 0;
  display: inline-flex;
}

.star-btn {
  appearance: none;
  background: none;
  border: none;
  padding: 0 0.15rem;
  margin: 0;
  cursor: pointer;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--color-star);
  align-self: center;
}

.star-btn:hover,
.star-btn:focus-visible {
  color: var(--color-star-hover);
}

.star-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 3px;
}

.star-btn.starred {
  color: var(--color-star-on);
}

/* My Sources S3: per-source stats, Poll button, and inline keyword editor. */

.my-sources-table td.stat-total {
  color: var(--color-text-muted);
}

.my-sources-table td.stat-visible {
  color: var(--color-success-text);
  font-weight: 600;
}

.my-sources-table td.stat-date {
  color: var(--color-text-subtle);
  font-size: 0.82rem;
  white-space: nowrap;
}

.my-sources-table td.stat-date .stat-never {
  font-style: italic;
}

.poll-form {
  margin: 0;
  display: inline-flex;
}

.poll-btn {
  appearance: none;
  background: var(--color-accent-soft-bg);
  border: 1px solid var(--color-accent-soft-border);
  border-radius: 4px;
  padding: 0.1rem 0.6rem;
  font-size: 0.82rem;
  cursor: pointer;
  color: var(--color-accent);
}

.poll-btn:hover,
.poll-btn:focus-visible {
  background: var(--color-accent-soft-bg-hover);
}

.poll-btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.source-keywords {
  font-size: 0.82rem;
}

.source-keywords > summary {
  cursor: pointer;
  color: var(--color-accent);
  list-style: none;
  user-select: none;
}

.source-keywords > summary::-webkit-details-marker {
  display: none;
}

.source-keywords-form {
  display: inline-flex;
  gap: 0.35rem;
  margin: 0.35rem 0 0;
}

.source-keywords-form input[type="text"] {
  padding: 0.15rem 0.4rem;
  border: 1px solid var(--color-accent-soft-border);
  border-radius: 4px;
  min-width: 16rem;
}

.source-keywords-hint {
  color: var(--color-text-subtle);
  font-size: 0.78rem;
  margin: 0.25rem 0 0;
}

/* Global (admin) keyword filter shown read-only to editors in My Sources. */
.source-keywords-global {
  font-size: 0.8rem;
  color: var(--color-text-muted);
  margin: 0 0 0.4rem;
}
.kw-global-count {
  color: var(--color-text-subtle);
  font-weight: normal;
  font-size: 0.8rem;
}

/* Global keyword filter in the admin Ingestion Sources table: a click-to-expand
   count (some sources carry ~100 terms, too long to show inline). */
.source-keywords-cell .kw-details > summary {
  cursor: pointer;
  color: var(--color-link);
  list-style: none;
}
.source-keywords-cell .kw-details > summary::-webkit-details-marker {
  display: none;
}
.source-keywords-cell .kw-none {
  color: var(--color-text-faint);
}
.kw-list {
  color: var(--color-text-muted);
  font-size: 0.8rem;
  overflow-wrap: anywhere;
}

.my-source-msg {
  display: inline-block;
  margin-top: 0.35rem;
  font-size: 0.8rem;
  color: var(--color-success-text);
}

.my-source-msg.my-source-err {
  color: var(--color-danger-text-deep);
}

/* Publish Queue (batch publishing) */
.publish-queue-count {
  font-weight: normal;
  color: var(--color-text-muted);
}
/* The Publish Queue shares the .queue-table look with the other three queues.
   It differs in one structural way: rows are plain <tr>s in a single <tbody>
   (no per-item <tbody>, no detail row), so the shared `.queue-item-main`
   between-items divider doesn't apply — draw the row dividers on the cells
   directly instead. Everything else (width, font, <thead>, cell padding) comes
   from `.queue-table`. */
.publish-queue-table {
  margin: 1rem 0;
}
.publish-queue-table tbody tr > td {
  border-top: 1px solid var(--color-border);
}
.publish-queue-publish-all {
  margin-top: 1rem;
}

/* "Publish All" + "Remove All" sit side by side under the queue table. */
/* One row for every publish-queue page action (Publish All, Remove All, Export
   JSON, Back). Wraps on narrow screens; Back is pushed to the far right with
   margin-left below, so the alignment survives buttons being added or removed. */
.publish-queue-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.75rem 0;
}

.publish-queue-toolbar > .publish-queue-toolbar-back {
  margin-left: auto;
}

/* "Publish All" / "Remove All" are <button>s wrapped in a <form> (they POST),
   while Export / Back are bare <a class="btn">. Without this, the <form> is the
   flex item and its line box is taller than the button inside, so those two
   buttons render taller than the anchors beside them. display:contents drops the
   form's box so the button itself becomes the flex item — all four buttons are
   then identical in height and vertically aligned. The form still submits. */
.publish-queue-toolbar > form {
  display: contents;
}

/* Badge styling for source types */
.badge {
  display: inline-block;
  padding: 0.25rem 0.6rem;
  margin-left: 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  border-radius: 3px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.badge-rss {
  background: var(--color-badge-rss-bg);
  color: var(--color-badge-rss-text);
}

.badge-web_crawl {
  background: var(--color-badge-crawl-bg);
  color: var(--color-badge-crawl-text);
}

/* podcast_json (podcasts.groong.org app.json). Reuses the neutral mechanism
   palette so it reads as a quiet chip and adds no new theme tokens. */
.badge-podcast_json {
  background: var(--color-badge-mechanism-bg);
  color: var(--color-badge-mechanism-text);
}

/* Radio button and fieldset styling */
fieldset {
  border: none;
  padding: 0;
  margin: 1rem 0;
}

fieldset legend {
  font-weight: 600;
  color: var(--color-text-muted);
  margin-bottom: 0.5rem;
}

.radio-label {
  display: block;
  margin-bottom: 0.5rem;
  cursor: pointer;
  font-weight: normal;
}

.radio-label input[type="radio"] {
  margin-right: 0.5rem;
  cursor: pointer;
}

/* Alert styling for validation messages */
.alert {
  padding: 0.75rem 1rem;
  margin: 1rem 0;
  border-radius: 3px;
  line-height: 1.6;
}

.alert-success {
  background: var(--color-success-bg);
  border: 1px solid var(--color-success-border);
  color: var(--color-success-text);
}

.alert-danger {
  background: var(--color-danger-bg);
  border: 1px solid var(--color-danger-border);
  color: var(--color-danger-text-deep);
}

.alert .mt-2 {
  margin-top: 0.5rem;
}

.alert .text-muted {
  opacity: 0.85;
  font-size: 0.9rem;
}

.alert .small {
  font-size: 0.85rem;
}

/* Site-wide admin banner (AI health): sits between the header and <main>,
   matching the default content column's padding/width. */
.ai-health-banner {
  margin: 1rem 1.25rem 0;
  max-width: 57.5rem;
}

/* --------------------------------------------------------------------------- */
/* Floating modal (HTMX overlays such as the changelog) + changelog content.   */
/* Changelog styles live here (not inline in changelog.html) so they apply to  */
/* both the full page and the modal, which is injected into arbitrary pages.   */
/* --------------------------------------------------------------------------- */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: var(--color-backdrop);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem;
  z-index: 2000;
}

.modal-window {
  background: var(--color-surface);
  border-radius: 8px;
  box-shadow: 0 10px 40px var(--shadow-lg);
  width: 100%;
  max-width: 720px;
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--color-border-blue);
}

.modal-header h2 {
  margin: 0;
  font-size: 1.25rem;
  color: var(--color-text);
}

.modal-close {
  background: none;
  border: none;
  font-size: 1.6rem;
  line-height: 1;
  color: var(--color-text-subtle);
  cursor: pointer;
  padding: 0 0.25rem;
}

.modal-close:hover,
.modal-close:focus {
  color: var(--color-text);
}

.modal-body {
  padding: 1rem 1.25rem 1.5rem;
  overflow-y: auto;
}

/* Changelog content (shared by full page and modal). */
.changelog-container {
  max-width: 900px;
  margin: 2rem auto;
  padding: 0 1rem;
}

.changelog-container h1 {
  font-size: 2rem;
  margin-bottom: 1rem;
  color: var(--color-text);
}

.changelog-content h2 {
  font-size: 1.5rem;
  margin-top: 2rem;
  margin-bottom: 1rem;
  color: var(--color-text-muted);
  border-bottom: 2px solid var(--color-border);
  padding-bottom: 0.5rem;
}

/* The first heading in the modal body needs no big top gap. The changelog body
   is now split into .changelog-page wrappers, so the first h2 is nested one
   level deeper than before (page div → h2) rather than a direct child. */
.modal-body .changelog-content .changelog-page:first-child > h2:first-child {
  margin-top: 0;
}

/* Progressive "Load More" paging: pages past the first are hidden until the
   button reveals them. The [hidden] attribute already implies display:none, but
   pin it explicitly so no later rule can override it. */
.changelog-content .changelog-page[hidden] {
  display: none;
}

.changelog-more {
  margin-top: 2rem;
  text-align: center;
}

.changelog-content h3 {
  font-size: 1.1rem;
  margin-top: 1.5rem;
  margin-bottom: 0.75rem;
  color: var(--color-text-muted);
  font-weight: 600;
}

.changelog-content h4 {
  font-size: 1rem;
  margin-top: 1rem;
  margin-bottom: 0.5rem;
  color: var(--color-text-muted);
  font-weight: 600;
}

.changelog-content ul {
  margin: 0.75rem 0 0.75rem 1.5rem;
  padding: 0;
}

.changelog-content li {
  margin: 0.35rem 0;
  line-height: 1.6;
}

.changelog-content p {
  margin: 0.75rem 0;
  line-height: 1.6;
  color: var(--color-text-muted);
}

.changelog-content strong {
  color: var(--color-text);
  font-weight: 600;
}

.changelog-content code {
  background: var(--color-surface-3);
  padding: 0.2rem 0.4rem;
  border-radius: 3px;
  font-family: 'Courier New', monospace;
  font-size: 0.9em;
}

.changelog-empty {
  color: var(--color-text-faint);
  font-style: italic;
  padding: 2rem;
  text-align: center;
}

/* Live poll-status badge (TICKET-093). Shown on My Sources + admin Ingestion
   Sources, auto-refreshed via HTMX OOB swaps. Colors: ok=green, failed=red,
   never=grey, polling=blue with a subtle pulsing dot. */
.poll-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  font-size: 0.8rem;
  line-height: 1.4;
  white-space: nowrap;
  vertical-align: middle;
}

.poll-badge-ok {
  background: var(--color-success-bg);
  color: var(--color-success-text);
}

.poll-badge-failed {
  background: var(--color-danger-bg);
  color: var(--color-danger-text-deep);
}

.poll-badge-never {
  background: var(--color-surface-3);
  color: var(--color-text-subtle);
}

.poll-badge-polling {
  background: var(--color-info-bg);
  color: var(--color-link);
}

.poll-badge-polling .poll-dot {
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: currentColor;
  animation: poll-pulse 1s ease-in-out infinite;
}

@keyframes poll-pulse {
  0%, 100% { opacity: 0.35; transform: scale(0.85); }
  50% { opacity: 1; transform: scale(1.15); }
}

.mechanism-poll-at {
  color: var(--color-text-faint);
  font-size: 0.8rem;
  margin-left: 0.35rem;
}

@media (prefers-reduced-motion: reduce) {
  .poll-badge-polling .poll-dot { animation: none; }
}

/* Clickable poll badge -> run-history modal (TICKET-123). Inherits the badge
   look; unsets the default <button> chrome so it reads as a badge, not a form
   control. */
.poll-badge-link {
  border: none;
  font: inherit;
  cursor: pointer;
  text-decoration: none;
}

.poll-badge-link:hover,
.poll-badge-link:focus-visible {
  filter: brightness(0.95);
  outline: 1px solid var(--color-border-blue);
  outline-offset: 1px;
}

/* Poll-run history modal (TICKET-123). Reuses .modal-backdrop/.modal-window/
   .modal-header/.modal-body from the changelog modal. */
.poll-runs-meta {
  margin: 0 0 0.75rem;
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

.poll-runs-config {
  display: block;
  color: var(--color-text-faint);
  font-family: 'Courier New', monospace;
  font-size: 0.85rem;
  word-break: break-all;
}

.poll-run {
  border: 1px solid var(--color-border);
  border-radius: 6px;
  margin-bottom: 0.5rem;
  padding: 0.25rem 0.5rem;
}

.poll-run > summary {
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.run-chip {
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
}

.run-chip-ok {
  background: var(--color-success-bg);
  color: var(--color-success-text);
}

.run-chip-fail {
  background: var(--color-danger-bg);
  color: var(--color-danger-text-deep);
}

.run-time { font-variant-numeric: tabular-nums; }

.run-dur { color: var(--color-text-faint); }

.run-counts { color: var(--color-text-faint); }

.run-log {
  margin: 0.5rem 0 0.25rem;
  padding: 0.5rem;
  background: var(--color-surface-3);
  border-radius: 4px;
  font-family: 'Courier New', monospace;
  font-size: 0.8rem;
  line-height: 1.4;
  max-height: 40vh;
  overflow: auto;
  overflow-x: auto;
  white-space: pre;
}

/* Small copy affordance pinned to the top-right of each run's log box. */
.run-log-wrap {
  position: relative;
}

.run-copy {
  position: absolute;
  top: 0.9rem;
  right: 0.5rem;
  z-index: 1;
  padding: 0.15rem 0.5rem;
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--color-text-faint);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  cursor: pointer;
  opacity: 0.85;
}

.run-copy:hover {
  opacity: 1;
  color: var(--color-text);
}

.run-copy-done {
  color: var(--color-success-text);
  border-color: var(--color-success-border);
  opacity: 1;
}

.poll-runs-empty {
  color: var(--color-text-faint);
  font-style: italic;
  padding: 1.5rem;
  text-align: center;
}

/* --------------------------------------------------------------------------
   Parser Accuracy report (TICKET-097, ADR-008 §D5)
   -------------------------------------------------------------------------- */
.accuracy-filter {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem 0;
  flex-wrap: wrap;
}
.accuracy-filter label { font-weight: 600; }

.accuracy-table,
.accuracy-items-table,
.publish-log-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 1rem;
  font-size: 0.9rem;
}
.accuracy-table th,
.accuracy-table td,
.accuracy-items-table th,
.accuracy-items-table td,
.publish-log-table th,
.publish-log-table td {
  border: 1px solid var(--color-border);
  padding: 0.4rem 0.5rem;
  text-align: left;
  vertical-align: top;
}
.accuracy-table thead th,
.accuracy-items-table thead th,
.publish-log-table thead th { background: var(--color-surface-3); }

/* Activity Log kind filter (TICKET-161): a small inline "Action" dropdown above
   the table, styled like the other filter bars. */
.activity-filter {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.75rem 0 0.25rem;
  font-size: 0.9rem;
}
.activity-filter select {
  padding: 0.2rem 0.4rem;
}

/* Activity Log action badges (TICKET-161): one pill per event kind, reusing the
   shared .status-pill pill shape with a distinct colour per action. */
.activity-kind {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}
.activity-kind-publish {
  color: var(--color-success-text);
  background: var(--color-success-bg);
  border-color: var(--color-success-border);
}
.activity-kind-republish {
  color: var(--color-badge-rss-text);
  background: var(--color-badge-rss-bg);
}
.activity-kind-unpublish {
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
}
.activity-kind-delete {
  color: var(--color-danger-text);
  background: var(--color-danger-bg);
  border-color: var(--color-danger-text);
}
.activity-kind-restore {
  color: var(--color-badge-crawl-text);
  background: var(--color-badge-crawl-bg);
}

/* Publish-state pill (TICKET-142): reuses the shared .poll-badge* classes; the
   link wrapper just drops the default underline so the pill reads as one unit. */
.publish-log-build-link {
  text-decoration: none;
}
.publish-log-build-link:hover .poll-badge,
.publish-log-build-link:focus-visible .poll-badge {
  filter: brightness(0.95);
}

.accuracy-table td.cov-cell,
.accuracy-table td.body-cell { text-align: right; }

/* A gap: an expected field below the coverage threshold. */
.gap-cell {
  background: var(--color-danger-bg);
  color: var(--color-danger-text-deep);
  font-weight: 600;
}
.gap-cell--inline {
  display: inline;
  padding: 0 0.25rem;
  border-radius: 2px;
}
.gap-link { color: var(--color-danger-text-deep); }
.body-cell.has-offenders { background: var(--color-warning-bg-soft); }
.not-expected { color: var(--color-text-faint); }

.config-path {
  font-size: 0.8rem;
  word-break: break-all;
}

/* Feedback rollup + open-feedback triage (TICKET-099, ADR-008 §D5). */
.accuracy-section { margin-top: 2rem; }
.feedback-rollup td.feedback-count-cell { text-align: right; }
.feedback-count-cell.has-open { background: var(--color-danger-bg); }
.feedback-open { font-weight: 600; color: var(--color-danger-text-deep); }
.feedback-tag {
  display: inline-block;
  background: var(--color-accent-soft-bg);
  border-radius: 2px;
  padding: 0 0.35rem;
  margin: 0 0.15rem 0.15rem 0;
  font-size: 0.85rem;
}
.open-feedback-table td { vertical-align: top; }
.feedback-resolve-disclosure summary { cursor: pointer; }
.resolve-note { display: block; margin: 0.35rem 0; }
.resolve-note textarea { width: 100%; }

.empty-note { color: var(--color-text-muted); margin: 1rem 0; }
.form-warning {
  background: var(--color-warning-bg-soft);
  border: 1px solid var(--color-warning-border);
  color: var(--color-warning-text);
  padding: 0.5rem 0.75rem;
  border-radius: 4px;
}

/* Editor parse-error reports on the review page (ADR-008). */
.feedback-reports {
  background: var(--color-danger-bg);
  border: 1px solid var(--color-danger-border);
  border-radius: 4px;
  padding: 0.5rem 0.75rem;
  margin: 1rem 0;
}
.feedback-reports-head { margin: 0 0 0.5rem; font-size: 1rem; color: var(--color-danger-text-deep); }
.feedback-reports-list { list-style: none; margin: 0; padding: 0; }
.feedback-report { padding: 0.35rem 0; border-top: 1px solid var(--color-danger-border); }
.feedback-report:first-child { border-top: 0; }
.feedback-report-details { margin: 0.2rem 0; white-space: pre-wrap; }
.feedback-report-meta { font-size: 0.85rem; }
.feedback-report-note { margin: 0.4rem 0 0; font-size: 0.85rem; }

/* Clickable thumbnail of the featured image on the review page. */
.featured-image-preview {
  display: inline-block;
  margin-top: 0.4rem;
  border: 1px solid var(--color-border-strong);
  border-radius: 4px;
  overflow: hidden;
  line-height: 0;
}
.featured-image-preview img {
  max-width: 240px;
  max-height: 160px;
  object-fit: cover;
}

/* Queue featured-image thumbnail with pure-CSS hover-to-zoom (Discovery,
   Rejected, Publish queues). The small thumb sits inline; hovering reveals a
   larger preview positioned to its right, above sibling rows. */
.queue-thumb {
  position: relative;
  display: inline-block;
  flex-shrink: 0;
  line-height: 0;
}

.queue-thumb-img {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border: 1px solid var(--color-border-strong);
  border-radius: 4px;
  display: block;
}

.queue-thumb-zoom {
  display: none;
  position: absolute;
  z-index: 50;
  top: 0;
  left: calc(100% + 8px);
  padding: 3px;
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  border-radius: 4px;
  box-shadow: 0 4px 16px var(--shadow-md);
}

.queue-thumb:hover .queue-thumb-zoom,
.queue-thumb:focus-within .queue-thumb-zoom {
  display: block;
}

.queue-thumb-zoom img {
  display: block;
  max-width: 360px;
  max-height: 360px;
  object-fit: contain;
}

/* Keep the Publish-queue image column tight to the thumbnail. */
.pq-image {
  width: 64px;
}

/* --------------------------------------------------------------------------
   Theme toggle (Auto / Light / Dark) in the site header.
   A compact segmented control that reads on the dark header in both themes.
   -------------------------------------------------------------------------- */
.theme-toggle {
  display: inline-flex;
  flex-shrink: 0;
  gap: 1px;
  padding: 2px;
  border: 1px solid var(--color-header-badge-bg);
  border-radius: 6px;
  background: var(--color-header-badge-bg);
}

.theme-btn {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--color-on-header);
  font: inherit;
  font-size: 0.72rem;
  line-height: 1.2;
  padding: 0.2rem 0.5rem;
  border-radius: 4px;
  cursor: pointer;
}

.theme-btn:hover {
  background: var(--color-nav-hover-bg);
  color: var(--color-on-header-strong);
}

.theme-btn:focus-visible {
  outline: 2px solid var(--color-on-header-strong);
  outline-offset: 1px;
}

/* Selected state (the JS sets aria-pressed="true"). */
.theme-btn[aria-pressed="true"] {
  background: var(--color-on-header-strong);
  color: var(--color-accent);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Settings → Discovery Queue: compact grouped grid. The page was a tall
   single-column stack; group the controls into labeled cards laid out in a
   responsive grid so the common toggles are all visible without scrolling.
   Colors via tokens only.
   -------------------------------------------------------------------------- */
.settings-actions {
  margin: 0.5rem 0 1rem;
}

/* The toggle/filter cards vary a lot in height (Display is tall, others short).
   A CSS *grid* couples each row's height to its tallest card, so the short cards
   left big empty gaps below them. Lay them out as a MASONRY instead (CSS
   multi-column): cards flow into balanced columns and stack with no row-height
   coupling, so there are no gaps. Capped at 2 columns, collapsing to 1 when
   narrow. */
.settings-grid {
  columns: 17rem 2;
  column-gap: 1rem;
}
/* Cards must not be split across a column break, and carry their own vertical
   rhythm (column layout has no `gap` between stacked items). */
.settings-grid > .settings-group {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  margin: 0 0 1rem;
}
/* Separate the toggle masonry from the language section below it. */
.settings-grid + .settings-grid {
  margin-top: 0.5rem;
}
/* The two language-related groups are similar height and want wide cells, so
   keep them as a real two-up grid (side by side, collapsing to one column when
   narrow) rather than folding them into the masonry. */
.settings-grid--lang {
  columns: auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: 1rem;
  align-items: start;
}
.settings-grid--lang > .settings-group {
  margin: 0;
}

/* A labeled group "card". */
.settings-group {
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  padding: 0.75rem 1rem 0.9rem;
  background: var(--color-surface);
}
.settings-group h2 {
  margin: 0 0 0.4rem;
  font-size: 0.95rem;
}
.settings-group .settings-note {
  margin: 0 0 0.6rem;
  font-size: 0.8rem;
  color: var(--color-text-muted);
}

/* Checkbox / radio rows within a group. */
.settings-checkbox {
  display: flex;
  gap: 0.45rem;
  align-items: baseline;
  margin: 0.3rem 0;
}
.settings-checkbox input {
  flex: 0 0 auto;
}

.settings-field-label {
  display: block;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  margin-bottom: 0.2rem;
}

/* The reading-languages checkboxes and the per-language keyword fields both flow
   into their own auto-fit sub-grid instead of stacking tall. */
.settings-lang-grid {
  border: 0;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.1rem 0.75rem;
}
.settings-keyword-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0.5rem 0.9rem;
}
.settings-keyword-field label {
  display: block;
  font-size: 0.8rem;
  color: var(--color-text-muted);
}
.settings-keyword-field input {
  width: 100%;
}

/* --------------------------------------------------------------------------
   Account management (My Account + admin Users). Colors via tokens only.
   -------------------------------------------------------------------------- */

/* My Account: render each label + value on the SAME row (a definition grid
   instead of the default <dl> stacking dt above dd). */
.account-details {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.4rem 1.25rem;
  align-items: baseline;
  margin: 0.5rem 0 0;
  max-width: 32rem;
}
.account-details dt {
  font-weight: 600;
  color: var(--color-text-muted);
}
.account-details dd {
  margin: 0;
}

/* Admin Users roster. Wrapper scrolls on narrow screens so the manage
   controls never wrap or clip. */
.users-table-wrap {
  overflow-x: auto;
}
.users-table {
  width: 100%;
  min-width: 44rem;
  border-collapse: collapse;
}
.users-table th,
.users-table td {
  text-align: left;
  padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--color-border);
  vertical-align: middle;
  font-size: 0.85rem;
}
.users-table th {
  color: var(--color-text-muted);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
/* Zebra striping: alternate rows get a subtle tinted fill. */
.users-table tbody tr:nth-child(even) {
  background: var(--color-accent-soft-bg);
}
/* Email / status hint sits on its own line under the name, muted. */
.users-table .settings-source {
  display: block;
  font-size: 0.75rem;
}
/* Keep the manage controls (role picker + activate/deactivate) on one line. */
.user-actions {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 0.75rem;
  white-space: nowrap;
}
.user-actions .inline-form {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

/* --- Composer image picker (TICKET-130, ADR-009 Surface C) ----------------- */
.image-picker {
  margin: 0.5rem 0 0;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 0.4rem 0.6rem;
  background: var(--color-surface-2);
}

.image-picker > summary {
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 600;
}

.image-picker-help {
  margin: 0.5rem 0;
  font-size: 0.8rem;
}

.image-picker-search {
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 0.5rem;
}

.image-picker-msg {
  margin: 0.25rem 0;
  font-size: 0.85rem;
}

.image-picker-msg--error {
  color: var(--color-warning-text);
}

/* Thumbnail grid: interim CDN base is storage.googleapis.com (full-size WEBP;
   no resized variants yet — ADR-009), so the browser downscales to ~140px via
   the fixed card width + object-fit. */
.image-picker-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.6rem;
  max-height: 22rem;
  overflow-y: auto;
}

.image-picker-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface);
  overflow: hidden;
}

.image-picker-thumb {
  width: 100%;
  height: 100px;
  object-fit: cover;
  background: var(--color-surface-3);
}

.image-picker-meta {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.35rem 0.45rem;
  font-size: 0.75rem;
  line-height: 1.25;
}

.image-picker-license {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
}

.image-picker-credit {
  color: var(--color-text);
}

.image-picker-caption {
  color: var(--color-text-muted);
}

.image-picker-actions {
  display: flex;
  gap: 0.3rem;
  padding: 0 0.45rem 0.45rem;
}

.btn-small {
  padding: 0.15rem 0.4rem;
  font-size: 0.75rem;
}

/* --- Image Archive management page (/images) ------------------------------ */
/* Two ingest forms side by side; each lays its fields out as an aligned
   label/field table (CSS grid, so the two forms stay visually consistent
   without a real <table>). */
.image-ingest-forms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.image-form {
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-surface-2);
  padding: 0.75rem 1rem 1rem;
}

.image-form h2 {
  margin: 0 0 0.75rem;
  font-size: 1rem;
}

.image-form-grid {
  display: grid;
  grid-template-columns: 8.5rem 1fr;
  align-items: baseline;
  gap: 0.5rem 0.75rem;
}

.image-form-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text-muted);
  text-align: right;
}

.image-form-field {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

/* Consistent field sizing across both forms. */
.image-form-field input,
.image-form-field select {
  width: 100%;
  box-sizing: border-box;
  font-size: 0.85rem;
}

.image-form-hint {
  font-size: 0.7rem;
  color: var(--color-text-subtle);
}

.image-form-actions {
  margin-top: 0.9rem;
  text-align: right;
}

/* Row-based archive. Mirrors the .users-table idiom (hairline rows, uppercase
   muted headers, zebra striping). Deliberately NOT wrapped in an overflow-x
   container: a scroll container would clip the hover-zoom overlay. */
.images-table {
  width: 100%;
  border-collapse: collapse;
}
.images-table th,
.images-table td {
  text-align: left;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--color-border);
  vertical-align: middle;
  font-size: 0.85rem;
}
.images-table th {
  color: var(--color-text-muted);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.images-table tbody tr:nth-child(even) {
  background: var(--color-accent-soft-bg);
}

/* Column widths: fixed for the small metadata columns so the two free-text
   columns (caption, credit) absorb the remaining space and wrap instead of
   blowing out the layout. */
.images-col-thumb {
  width: 4rem;
}
.images-col-license {
  width: 7rem;
}
.images-col-dims,
.images-col-size {
  width: 6rem;
  white-space: nowrap;
}
.images-col-actions {
  width: 7rem;
  white-space: nowrap;
}
.images-col-actions a + a {
  margin-left: 0.5rem;
}

/* Long caption/credit text wraps and breaks rather than widening the table. */
.images-table .image-cell-text {
  overflow-wrap: anywhere;
}

.images-table .image-none {
  color: var(--color-text-faint);
}

.images-table .image-license {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: 0.75rem;
  color: var(--color-text-muted);
}

/* Tiny thumbnail. The interim CDN serves full-size WEBP with no resized
   variants (ADR-009), so the browser downscales; explicit width/height keep
   every row the same height regardless of the source aspect ratio. */
.image-thumb {
  display: block;
  width: 48px;
  height: 48px;
  object-fit: cover;
  border: 1px solid var(--color-border-strong);
  border-radius: 3px;
  background: var(--color-surface-3);
}

/* Hover/focus zoom.
   The enlarged preview lives in an absolutely-positioned span, so it is out of
   normal flow: showing it can never change the size of the cell, the row, or
   the table. It is only faded/scaled in, and painted above the table via
   z-index. Anchored to the right of the thumbnail and vertically centred. */
.image-thumb-zoom {
  position: relative;
  display: inline-block;
  line-height: 0;
}

.image-thumb-preview {
  position: absolute;
  left: calc(100% + 0.5rem);
  top: 50%;
  transform: translateY(-50%) scale(0.9);
  transform-origin: left center;
  z-index: 50;
  padding: 0.25rem;
  border: 1px solid var(--color-border-strong);
  border-radius: 4px;
  background: var(--color-surface);
  box-shadow: 0 4px 14px var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
}

.image-thumb-preview img {
  display: block;
  width: 280px;
  height: auto;
  max-height: 280px;
  object-fit: contain;
}

.image-thumb-zoom:hover .image-thumb-preview,
.image-thumb-zoom:focus-visible .image-thumb-preview,
.image-thumb-zoom:focus .image-thumb-preview {
  opacity: 1;
  visibility: visible;
  transform: translateY(-50%) scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .image-thumb-preview {
    transition: none;
    transform: translateY(-50%);
  }
  .image-thumb-zoom:hover .image-thumb-preview,
  .image-thumb-zoom:focus-visible .image-thumb-preview,
  .image-thumb-zoom:focus .image-thumb-preview {
    transform: translateY(-50%);
  }
}

/* --- Per-image entity (depicts QID) curation panel (TICKET-136) ----------- */
/* Renders in a full-width row beneath each image row. Collapsed <details> by
   default (open only when the image already has entities), so the archive stays
   scannable and the panel only takes space when in use. All colors reuse the
   existing theme tokens — no new tokens introduced. */
.entity-row > .entity-cell {
  padding-top: 0;
  padding-bottom: 0.6rem;
}

.entity-details {
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface-2);
  padding: 0.35rem 0.6rem;
}

.entity-summary {
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text-muted);
}

.entity-list {
  list-style: none;
  margin: 0.5rem 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.entity-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.entity-qid {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  color: var(--color-link);
}

.entity-canonical-badge {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.05rem 0.35rem;
  border-radius: 3px;
  color: var(--color-success-text);
  background: var(--color-success-bg);
  border: 1px solid var(--color-success-border);
}

.entity-detach {
  margin-left: auto;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-danger-text);
  border-radius: 3px;
  line-height: 1;
  padding: 0.05rem 0.4rem;
  cursor: pointer;
  font-size: 0.9rem;
}

.entity-detach:hover {
  background: var(--color-danger-bg);
  border-color: var(--color-danger-border);
}

.entity-empty {
  margin: 0.5rem 0;
  font-size: 0.8rem;
}

.entity-add {
  margin-top: 0.5rem;
  border-top: 1px solid var(--color-border-subtle);
  padding-top: 0.5rem;
}

.entity-add-label,
.entity-manual-label {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-text-muted);
  margin-bottom: 0.2rem;
}

.entity-search {
  width: 100%;
  max-width: 24rem;
  box-sizing: border-box;
  font-size: 0.85rem;
}

.entity-search-msg {
  margin: 0.35rem 0;
  font-size: 0.8rem;
}

.entity-search-msg--error {
  color: var(--color-warning-text);
}

.entity-search-list {
  list-style: none;
  margin: 0.35rem 0;
  padding: 0;
  max-width: 24rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-surface);
  max-height: 16rem;
  overflow-y: auto;
}

.entity-search-item + .entity-search-item {
  border-top: 1px solid var(--color-border-subtle);
}

.entity-search-pick {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  width: 100%;
  text-align: left;
  border: 0;
  background: none;
  cursor: pointer;
  padding: 0.35rem 0.5rem;
  font: inherit;
}

.entity-search-pick:hover,
.entity-search-pick:focus-visible {
  background: var(--color-accent-soft-bg);
}

.entity-search-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-text);
}

.entity-search-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.75rem;
  color: var(--color-text-muted);
}

.entity-search-desc {
  font-size: 0.75rem;
  color: var(--color-text-muted);
}

.entity-manual {
  margin-top: 0.5rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.entity-manual-label {
  margin-bottom: 0;
}

.entity-qid-input {
  width: 8rem;
  font-size: 0.85rem;
  box-sizing: border-box;
}

/* Wider: this field now also accepts a pasted Wikidata URL, not just a QID. */
.entity-qid-input--wide {
  width: 18rem;
  max-width: 100%;
}

.entity-qid-input:invalid {
  border-color: var(--color-danger-border);
}

/* Narrow screens: stack labels above fields so the forms stay usable. */
@media (max-width: 40rem) {
  .image-form-grid {
    grid-template-columns: 1fr;
    gap: 0.15rem;
  }
  .image-form-label {
    text-align: left;
    margin-top: 0.4rem;
  }
}

/* Source Ingestion report — searchable multi-source filter (client-side; see
   _source_picker.html + source_picker.js). Options drop below the search box;
   selected sources render as removable chips whose hidden inputs submit. */
.ingest-filter {
  flex-wrap: wrap;
  align-items: flex-start;
}
.source-picker {
  position: relative;
  min-width: 16rem;
}
.source-search {
  width: 100%;
}
.source-options {
  position: absolute;
  z-index: 50;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 16rem;
  overflow-y: auto;
  margin: 0.15rem 0 0;
  padding: 0;
  list-style: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border-blue);
  border-radius: 6px;
  box-shadow: 0 6px 20px var(--shadow-md);
}
.source-options li {
  margin: 0;
}
.source-option {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.35rem 0.6rem;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: var(--color-text);
}
.source-option:hover {
  background: var(--color-nav-hover-bg);
}
.source-option-added {
  display: none;
  color: var(--color-text-muted);
  font-size: 0.8em;
}
.source-option[data-selected] .source-option-added {
  display: inline;
}
.source-option[data-selected] .source-option-name {
  color: var(--color-text-muted);
}
.source-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0.35rem 0 0;
  padding: 0;
  list-style: none;
}
.source-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.1rem 0.2rem 0.1rem 0.5rem;
  border: 1px solid var(--color-border-blue);
  border-radius: 999px;
  font-size: 0.8rem;
  background: var(--color-surface);
}
.source-remove {
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--color-text-muted);
  font-size: 1rem;
  line-height: 1;
  padding: 0 0.2rem;
}
.source-remove:hover {
  color: var(--color-text);
}

/* ------------------------------------------------------------------ *
 * Similar coverage — queue badge + compare view (TICKET-174).
 *
 * The badge is one quiet line inside the queue item. It only ever renders
 * for editors who opted in, so it must not reserve space, shift the row's
 * columns, or compete with the headline. Tint is redundant encoding: every
 * state is already distinguished by its text and its icon.
 * ------------------------------------------------------------------ */
.dup-badge {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem;
  margin: 0.25rem 0 0;
  padding: 0.15rem 0.5rem;
  border: 1px solid transparent;
  border-radius: 6px;
  font-size: 0.8rem;
  line-height: 1.4;
  text-decoration: none;
  color: var(--color-text);
}
.dup-badge:hover {
  text-decoration: underline;
}
.dup-badge-text {
  font-weight: 600;
}
.dup-badge-reason {
  color: var(--color-text-muted);
}
.dup-badge-covered {
  background: var(--color-accent-soft-bg);
  border-color: var(--color-accent-soft-border);
}
.dup-badge-possible {
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
}
.dup-badge-possible .dup-badge-reason {
  color: var(--color-warning-text);
}
.dup-badge-published {
  color: var(--color-success-text);
  background: var(--color-success-bg);
  border-color: var(--color-success-border);
}
.dup-badge-published .dup-badge-reason {
  color: var(--color-success-text);
}
/* "Similar to a rejected item" (T-178): the quietest state on the row — no
   tint at all, muted text throughout, so it can never be mistaken for live
   coverage. Text + icon still carry the meaning without any colour. */
.dup-badge-rejected,
.dup-badge-rejected .dup-badge-reason {
  color: var(--color-text-muted);
}
.dup-badge-rejected {
  border-color: var(--color-border-subtle);
}
.dup-badge-rejected .dup-badge-text {
  font-weight: 500;
}

/* Compare view. Columns are a flex row that SCROLLS at n>=5 rather than
   shrinking to unreadable widths, and each column has a floor width — so a
   later ticket adding another control cannot squeeze the layout. */
.compare-lead,
.compare-optin {
  margin: 0 0 0.75rem;
  color: var(--color-text-muted);
}
.compare-columns {
  display: flex;
  gap: 1rem;
  align-items: stretch;
  overflow-x: auto;
  padding-bottom: 0.5rem;
}
.compare-col {
  flex: 1 1 22rem;
  min-inline-size: 18rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  padding: 0.75rem;
  background: var(--color-surface);
}
.compare-col-anchor {
  border-color: var(--color-accent-soft-border);
  background: var(--color-accent-soft-bg);
}
.compare-col-rejected {
  opacity: 0.7;
}
.compare-col-source {
  margin: 0;
  font-size: 1rem;
}
.compare-col-this {
  color: var(--color-text-muted);
  font-weight: 400;
}
.compare-col-when,
.compare-col-credits,
.compare-col-reason,
.compare-col-lede,
.compare-col-caution,
.compare-col-blocked-label,
.compare-col-title-en,
.compare-dismissed-by {
  color: var(--color-text-muted);
  font-size: 0.85rem;
}
.compare-col-title {
  margin: 0.4rem 0;
  font-weight: 600;
}
/* The English translation of the headline (T-182): a quiet second line under
   the original, never a column of its own — it wraps with the headline and adds
   no width constraint, so the strip still survives 2–6 columns and "Show all". */
.compare-col-title-en {
  margin: -0.2rem 0 0.4rem;
  font-weight: 400;
  display: block;
}
.compare-col-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0.4rem 0;
}
.compare-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
}
.compare-chip-possible {
  color: var(--color-warning-text);
  background: var(--color-warning-bg);
  border-color: var(--color-warning-border);
}
/* "Likely original" — evidence-gated, so it may carry the accent emphasis the
   purely factual chips do not. Text says everything; tint is redundant. */
.compare-chip-original {
  color: var(--color-accent);
  background: var(--color-accent-soft-bg);
  border-color: var(--color-accent-soft-border);
}
.compare-chip-published {
  color: var(--color-success-text);
  background: var(--color-success-bg);
  border-color: var(--color-success-border);
}
.compare-col-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-block-start: 0.6rem;
}
.compare-inline-form {
  display: inline;
}
/* "Pick this one" (TICKET-176): the primary action of a column. The button and
   its consequence line stack as one block so the sub-text reads as part of the
   control, not as a stray caption between the other actions. */
.compare-pick-form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.2rem;
}
.compare-pick-note {
  font-size: 0.78rem;
  color: var(--color-text-muted);
}
/* The review workspace's "N similar versions rejected — Undo" note. Same quiet
   treatment as the compare view's banner; it is information, not an alarm. */
.pick-note {
  margin: 0 0 0.6rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-accent-soft-bg);
  font-size: 0.9rem;
}
.pick-note-dismiss {
  margin-inline-start: 0.6rem;
}
.compare-col-blocked {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.compare-banner,
.compare-expander {
  margin: 0 0 0.6rem;
}
.compare-banner {
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-accent-soft-bg);
}
.compare-bulk {
  margin-block-start: 1rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
}
.compare-bulk-list {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
}
.compare-bulk-name-possible {
  color: var(--color-warning-text);
}
.compare-dismissed {
  margin-block-start: 1.5rem;
  font-size: 0.85rem;
}
.compare-dismissed h2 {
  font-size: 0.95rem;
}
.compare-dismissed ul {
  margin: 0.3rem 0 0;
  padding-inline-start: 1.1rem;
}
/* "Rejected versions" (T-178): context under the strip, not choices. Muted and
   list-shaped on purpose — it must never read as another column to pick from. */
.compare-rejected {
  margin-block-start: 1.5rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}
.compare-rejected h2 {
  font-size: 0.95rem;
}
.compare-rejected ul {
  margin: 0.3rem 0 0;
  padding-inline-start: 1.1rem;
}
.compare-rejected li {
  margin-block-end: 0.2rem;
}
.compare-rejected-source {
  font-weight: 600;
}
/* The list is already muted at 0.85rem, so the English headline needs its own
   distinction to keep reading as secondary next to the native title: italic,
   no extra color. Sits last in the entry, so it wraps under the compact line
   rather than splitting it. */
.compare-rejected-title-en {
  margin: 0;
  font-style: italic;
}
