/* Scientific Web Services Portal — design system.
 * Tokens first, then layout, then one section per component family. Every
 * component here is generic (no application-specific class names), so a
 * future SWS service beyond PhageLeads inherits the same look for free.
 */

:root {
  /* Tells the UA both palettes are genuinely supported, so it renders our
   * own dark declarations verbatim instead of algorithmically re-darkening
   * a page it otherwise assumes is light-only -- and gives every unstyled
   * native control (link colours, ::placeholder, <option> popups,
   * scrollbars, autofill, disabled chrome) our declared scheme instead of
   * silently keeping its light defaults. See style.css.contrast.test.js for
   * why this line is load-bearing. */
  color-scheme: light dark;

  /* ── Surfaces & text ─────────────────────────────────────────────── */
  --sws-bg: #f5f7fb;
  --sws-bg-elevated: #ffffff;
  --sws-surface: #ffffff;
  --sws-surface-sunken: #f0f2f7;
  --sws-border: #e0e4ec;
  --sws-border-strong: #c7ccd8;
  --sws-text: #131a26;
  --sws-muted: #5c6577;
  --sws-muted-strong: #3d4657;

  /* Text drawn on a solid status/accent fill (e.g. a done/failed timeline
   * marker) rather than on a page surface -- kept separate from --sws-text
   * because the right ink flips with the theme even though body text does
   * not: dark mode's status colours are pale enough that white ink on them
   * fails contrast (verified in style.css.contrast.test.js). */
  --sws-status-ink: #ffffff;

  /* ── Brand / accent ──────────────────────────────────────────────── */
  --sws-brand: #3552e0;
  --sws-brand-strong: #2338b3;
  --sws-brand-soft: #eaedfd;
  --sws-accent: #12b5a6;
  --sws-accent-soft: #e2f8f5;
  /* The teal stop is darkened from the standalone --sws-accent (#12b5a6)
   * specifically for this gradient: --sws-accent is only ever used as text
   * or a small fill, but this gradient is a background behind fixed white
   * text (.button--primary, .service-card__icon), and #12b5a6 gives that
   * white text only 2.57:1 -- below AA in both themes. #0a7a70 clears
   * 4.5:1 across the whole sweep; see style.css.contrast.test.js. */
  --sws-gradient: linear-gradient(135deg, #3552e0 0%, #6f42e0 50%, #0a7a70 100%);
  /* The fixed white ink .button--primary/.service-card__icon set over
   * --sws-gradient -- named so it's a token, not a #fff bypass, and kept
   * theme-invariant alongside the gradient it always pairs with. */
  --sws-on-brand: #ffffff;

  /* ── Links ───────────────────────────────────────────────────────── */
  --sws-link: var(--sws-brand);
  --sws-link-hover: var(--sws-brand-strong);

  /* ── Status ──────────────────────────────────────────────────────── */
  --sws-success: #1a8f5e;
  --sws-success-soft: #e2f6ec;
  --sws-danger: #c62f3a;
  --sws-danger-soft: #fbe9ea;
  --sws-info: #3552e0;
  --sws-info-soft: #eaedfd;
  --sws-warning: #b8790a;
  --sws-warning-soft: #fbf1de;
  --sws-neutral: #5c6577;
  --sws-neutral-soft: #eceff4;

  /* ── Elevation ───────────────────────────────────────────────────── */
  --sws-shadow-sm: 0 1px 2px rgba(19, 26, 38, 0.06), 0 1px 1px rgba(19, 26, 38, 0.04);
  --sws-shadow-md: 0 6px 20px rgba(19, 26, 38, 0.08), 0 2px 6px rgba(19, 26, 38, 0.05);
  --sws-shadow-lg: 0 16px 40px rgba(19, 26, 38, 0.12), 0 4px 12px rgba(19, 26, 38, 0.06);

  /* ── Shape / spacing / motion ────────────────────────────────────── */
  --sws-radius-sm: 8px;
  --sws-radius-md: 14px;
  --sws-radius-lg: 20px;
  --sws-space-1: 0.25rem;
  --sws-space-2: 0.5rem;
  --sws-space-3: 0.75rem;
  --sws-space-4: 1rem;
  --sws-space-6: 1.5rem;
  --sws-space-8: 2rem;
  --sws-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --sws-motion-fast: 150ms;
  --sws-motion-med: 280ms;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --sws-bg: #0b0e14;
    --sws-bg-elevated: #12151d;
    --sws-surface: #151924;
    --sws-surface-sunken: #0f121a;
    --sws-border: #262c3a;
    --sws-border-strong: #363e50;
    --sws-text: #eef1f7;
    --sws-muted: #9aa3b6;
    --sws-muted-strong: #c3cadb;
    --sws-brand: #7f96ff;
    --sws-brand-strong: #a6b6ff;
    --sws-brand-soft: #1c2140;
    --sws-accent: #37e0cd;
    --sws-accent-soft: #113330;
    --sws-success: #4fd393;
    --sws-success-soft: #133327;
    --sws-danger: #ff8a8a;
    --sws-danger-soft: #3a1a1c;
    --sws-info: #7f96ff;
    --sws-info-soft: #1c2140;
    --sws-warning: #e0ac4c;
    --sws-warning-soft: #332711;
    --sws-neutral: #9aa3b6;
    --sws-neutral-soft: #1c212c;
    --sws-status-ink: var(--sws-bg);
    --sws-link: var(--sws-brand);
    --sws-link-hover: var(--sws-brand-strong);
    /* Redundant with :root's own `light dark` here (this block only ever
     * applies when the OS already prefers dark), kept only so this block
     * stays declaration-identical with :root[data-theme="dark"] below --
     * see style.css.contrast.test.js. The explicit attribute block is
     * where this actually does new work: it forces native control chrome
     * (a bare <select>'s own background, ::placeholder, etc.) to follow an
     * explicit dark override even when the OS itself prefers light, which
     * `color-scheme: light dark` alone cannot do -- it only ever follows
     * the OS, never `data-theme`. */
    color-scheme: dark;
    --sws-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --sws-shadow-md: 0 6px 20px rgba(0, 0, 0, 0.45);
    --sws-shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.55);
  }
}

:root[data-theme="dark"] {
  --sws-bg: #0b0e14;
  --sws-bg-elevated: #12151d;
  --sws-surface: #151924;
  --sws-surface-sunken: #0f121a;
  --sws-border: #262c3a;
  --sws-border-strong: #363e50;
  --sws-text: #eef1f7;
  --sws-muted: #9aa3b6;
  --sws-muted-strong: #c3cadb;
  --sws-brand: #7f96ff;
  --sws-brand-strong: #a6b6ff;
  --sws-brand-soft: #1c2140;
  --sws-accent: #37e0cd;
  --sws-accent-soft: #113330;
  --sws-success: #4fd393;
  --sws-success-soft: #133327;
  --sws-danger: #ff8a8a;
  --sws-danger-soft: #3a1a1c;
  --sws-info: #7f96ff;
  --sws-info-soft: #1c2140;
  --sws-warning: #e0ac4c;
  --sws-warning-soft: #332711;
  --sws-neutral: #9aa3b6;
  --sws-neutral-soft: #1c212c;
  --sws-status-ink: var(--sws-bg);
  --sws-link: var(--sws-brand);
  --sws-link-hover: var(--sws-brand-strong);
  /* This is the case that actually needs it: an explicit dark override on
   * a light-OS browser. Without this, `color-scheme: light dark` at :root
   * keeps following the OS (light), so a control with no author-set
   * background (e.g. a bare <select>) gets painted with the browser's own
   * light chrome while this block hands it near-white authored text --
   * confirmed empirically to be unreadable, ~1:1 contrast. Declaring
   * `dark` here forces that chrome dark too, matching every token above. */
  color-scheme: dark;
  --sws-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --sws-shadow-md: 0 6px 20px rgba(0, 0, 0, 0.45);
  --sws-shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.55);
}

/* The converse of the block above: an explicit light override on a
 * dark-OS browser. :root's own tokens already stay light in this case
 * (the dark media query above is guarded by `:not([data-theme="light"])`),
 * but `color-scheme: light dark` would otherwise still let the OS (dark)
 * paint native control chrome, producing the same kind of mismatch in the
 * opposite direction. */
:root[data-theme="light"] {
  color-scheme: light;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
  background: var(--sws-bg);
  color: var(--sws-text);
  -webkit-font-smoothing: antialiased;
}

/* Baseline for every anchor that isn't given its own colour by a more
 * specific rule further down (e.g. .top-nav a, .job-table__id a). Before
 * this rule existed, those anchors fell through to the UA's own link
 * colours -- and because no `color-scheme` was declared either, that UA
 * palette stayed the *light* one (#0000EE / visited #551A8B) even on a
 * dark page, which is where the reported low-contrast purple link came
 * from. See style.css.contrast.test.js. */
a {
  color: var(--sws-link);
}

a:hover,
a:focus-visible {
  color: var(--sws-link-hover);
}

a:visited {
  color: var(--sws-link);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Shell / nav ───────────────────────────────────────────────────── */

.sws-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.75rem;
  background: var(--sws-bg-elevated);
  border-bottom: 1px solid var(--sws-border);
  position: sticky;
  top: 0;
  z-index: 10;
  backdrop-filter: saturate(180%) blur(8px);
}

.sws-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 800;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  color: var(--sws-text);
  text-decoration: none;
}

.sws-brand::before {
  content: "";
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 3px;
  background: var(--sws-gradient);
}

/* The global nav and (when present) a section-specific secondary nav
 * (`renderAdminNav`/`renderAccountNav`) sit side by side in one group, so
 * the admin/account section never looks like it replaced the ordinary SWS
 * shell -- see `app.js`'s own `renderNav`. A visible divider between the
 * two `.top-nav` elements marks that seam without a heavier visual
 * treatment. */
.sws-nav-group {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  min-width: 0;
}

.sws-nav-group .top-nav + .top-nav {
  padding-left: 1.25rem;
  border-left: 1px solid var(--sws-border);
}

.top-nav {
  display: flex;
  gap: 1.5rem;
}

.top-nav a {
  color: var(--sws-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.92rem;
  padding: 0.35rem 0;
  border-bottom: 2px solid transparent;
  transition: color var(--sws-motion-fast) var(--sws-ease), border-color var(--sws-motion-fast) var(--sws-ease);
}

.top-nav a:hover {
  color: var(--sws-brand);
}

.sws-main {
  max-width: 1040px;
  margin: 0 auto;
  padding: 2.5rem 1.5rem 5rem;
}

.page {
  animation: fade-up var(--sws-motion-med) var(--sws-ease);
}

@keyframes fade-up {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.hero {
  margin-bottom: var(--sws-space-8);
}

.hero h1,
.page > h1 {
  font-size: 2rem;
  letter-spacing: -0.02em;
  margin: 0 0 0.5rem;
}

.page-lede {
  color: var(--sws-muted);
  max-width: 60ch;
  font-size: 1.05rem;
  line-height: 1.5;
}

/* The headline software-version label (ADR-0163) — the upstream scientific
 * software's own version is the prominent label, the SWS integration
 * revision a quieter, explicitly-labelled sibling. Applications with no
 * upstream-software record registered render only the plain, muted
 * "Version X" fallback line these two share a base class with. */
.version-badge {
  margin: 0.1rem 0 0.35rem;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.version-badge__upstream {
  font-weight: 600;
  font-size: 0.95rem;
}

.version-badge__integration {
  font-size: 0.8rem;
}

.back-link {
  display: inline-flex;
  align-items: center;
  margin-bottom: 1.25rem;
  color: var(--sws-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
  transition: color var(--sws-motion-fast) var(--sws-ease);
}

.back-link:hover {
  color: var(--sws-brand);
}

.muted {
  color: var(--sws-muted);
}

/* ── Service cards ─────────────────────────────────────────────────── */

.service-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1.25rem;
}

.service-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  padding: 1.5rem;
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  text-decoration: none;
  color: inherit;
  box-shadow: var(--sws-shadow-sm);
  transition: transform var(--sws-motion-med) var(--sws-ease), box-shadow var(--sws-motion-med) var(--sws-ease),
    border-color var(--sws-motion-med) var(--sws-ease);
}

.service-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--sws-shadow-lg);
  border-color: var(--sws-brand-soft);
}

/* ── Application icon (ADR-0143) ───────────────────────────────────── */
/* The one generic icon container every SWS application renders through —
   `renderApplicationIcon()` in dom.js sets `role="img"`/`aria-label"` and
   injects either the application's own `icon_svg` or the shared default;
   this file only ever sizes/positions the *container* by class
   (`.service-card__icon`, `.service-hero__icon`) — it never selects on an
   application identity. `currentColor` on the inner `<svg>`'s own strokes
   lets one `color` declaration here drive both themes without a
   `prefers-color-scheme` override of its own. */
.sws-app-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.sws-app-icon svg {
  display: block;
  width: 62%;
  height: 62%;
}

.service-card__icon {
  width: 3rem;
  height: 3rem;
  color: var(--sws-on-brand);
  border-radius: var(--sws-radius-md);
  background: var(--sws-gradient);
  margin-bottom: 0.35rem;
}

.service-hero__title-row {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  flex-wrap: wrap;
}

.service-hero__icon {
  width: 3.25rem;
  height: 3.25rem;
  color: var(--sws-brand);
  border-radius: var(--sws-radius-md);
  background: var(--sws-brand-soft);
}

.service-hero__title-row h1 {
  margin: 0;
  font-size: 2rem;
  letter-spacing: -0.02em;
}

.service-card h2 {
  margin: 0;
  font-size: 1.1rem;
  letter-spacing: -0.01em;
}

/* Secondary, subordinate to the tool name above it — an eyebrow-style label,
   not a second title. Communicates compact input/context metadata (e.g.
   "PHAGE GENOME · DNA") without competing with `.service-card h2` for
   attention. */
.service-card__input-label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sws-muted);
}

.service-card p {
  margin: 0;
  color: var(--sws-muted);
  font-size: 0.92rem;
  line-height: 1.45;
}

.service-card__cta {
  margin-top: 0.5rem;
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--sws-brand);
}

/* ── Restricted-access landing state (ADR-0177) ────────────────────── */
/* What a `listed` + `restricted` application shows a caller it will not let
   in. Styled as a calm status panel, deliberately NOT `.alert--error`: the
   application is working exactly as configured, and the visitor found a card
   the catalogue meant them to find. Red would say "something broke". */
.restricted-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 2rem;
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  box-shadow: var(--sws-shadow-sm);
}

.restricted-state__badge {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.restricted-state__badge h1 {
  margin: 0;
  font-size: 1.35rem;
}

.restricted-state__icon {
  font-size: 1.2rem;
  line-height: 1;
}

.restricted-state p {
  margin: 0;
  color: var(--sws-muted);
  max-width: 46ch;
}

/* The first-time-visitor line. Same muted voice as the sentences above it,
   one step smaller so the two-line explanation still reads first. */
.restricted-state__hint {
  font-size: 0.9rem;
}

/* Sign in + Create an account. `wrap` rather than a media query: both labels
   are short, so they sit side by side on anything wider than a narrow phone
   and stack by themselves below that, with no breakpoint to keep in sync
   with the card's own padding. */
.restricted-state__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

/* "Authorized, but has no SWS account yet" — admin Access panel only.
   Warning hue because it is an unfinished onboarding step the administrator
   may want to chase, not an error: the grant itself is valid and will apply
   the moment that person registers. Uses the existing warning tokens, which
   are already defined for both themes. */
.access-grant__pending {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0.05rem 0.4rem;
  border-radius: var(--sws-radius-sm);
  background: var(--sws-warning-soft);
  color: var(--sws-warning);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
  vertical-align: middle;
}

/* ── Restricted application card (ADR-0177) ────────────────────────── */
/* A `listed` + `restricted` application is deliberately public to *discover*
   and authorization-gated to *use*. These rules say exactly that, and nothing
   stronger: the card keeps its ordinary layout, icon, title, summary and
   "Run analysis →" action, and gains a status ribbon plus a footer note.

   The hue is `--sws-danger`, but the meaning is **status, not failure**.
   That is why there is no solid red fill, no red border on the card itself
   and no error iconography — a restricted tool is working correctly, it
   simply is not yours to run. Both tokens already carry dark-theme values,
   so this needs no theme block of its own.

   Every rule below is scoped to `.service-card--restricted`. A public card
   matches none of them and is byte-for-byte unchanged — no extra padding, no
   footer, no reserved space. */
.service-card--restricted {
  position: relative;
  /* Clips the ribbon's overhanging ends to the rounded corner, which is what
     makes it read as a stamped ribbon rather than a floating label. */
  overflow: hidden;
}

/* Two thick parallel rules with the card's own surface between them — the
   background stays visible, so this is a ribbon, not a banner.
   `background` is painted explicitly rather than left transparent: the band is
   opaque against the card, which is what gives it the stamped look and also
   guarantees it can never sit half-over a glyph if a future layout change
   brings content closer to the corner.

   **Upper right, not upper left.** The icon, title and input label are all
   left-aligned and stack from the card's top-left, so a left-corner ribbon
   crossed exactly where they begin and had to be paid for with a reserved
   `padding-top` on the whole card. The top-right corner is empty by
   construction in this layout, so the ribbon costs no space at all and the
   restricted card now has the *identical* box model to a public one — which
   is also why the old `padding-top` is gone rather than merely reduced. */
.service-card__ribbon {
  position: absolute;
  top: 1.75rem;
  /* Pulled out past the right edge so both ends are cut off by the card's own
     rounded corner instead of stopping visibly inside it. */
  right: -3.6rem;
  width: 13.5rem;
  transform: rotate(45deg);
  background: var(--sws-surface);
  border-top: 2px solid var(--sws-danger);
  border-bottom: 2px solid var(--sws-danger);
  padding: 0.3rem 0;
  text-align: center;
  /* Above the card's content box so the rules read as lying on the card
     surface, but deliberately low so a focus ring still paints above it. */
  z-index: 1;
  /* The card is one big anchor; the ribbon must never swallow a click meant
     for it, and it is not itself interactive. */
  pointer-events: none;
}

.service-card__ribbon-text {
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sws-danger);
  /* One line, always: a wrapped ribbon would grow downward into the card and
     is the one way this treatment could reach the title. */
  white-space: nowrap;
}

/* The footer note. Full-bleed to the card's edges (negative margins cancel
   the card's own padding) so it reads as a panel rather than an inset box,
   and `margin-top: auto` pins it to the bottom regardless of how short the
   summary is — which is what keeps a restricted card the same height as the
   public cards beside it in the grid. */
.service-card__restricted-note {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: auto -1.5rem -1.5rem;
  padding: 0.7rem 1.5rem;
  width: calc(100% + 3rem);
  background: var(--sws-danger-soft);
  border-top: 1px solid var(--sws-danger);
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--sws-danger);
}

.service-card__restricted-icon {
  flex: none;
  font-size: 0.9rem;
  line-height: 1.2;
}

/* The sentence sits under the heading rather than beside it, so a long
   application name or a narrow viewport never squeezes it onto one line. */
.service-card__restricted-detail {
  display: block;
  font-weight: 400;
}

/* Narrow viewports: the card goes full width, so the corner the ribbon
   crosses is proportionally shallower. Pulling it slightly further out and up
   keeps the same amount of band visible without letting it reach further into
   the card. */
@media (max-width: 32rem) {
  .service-card__ribbon {
    top: 1.55rem;
    right: -3.9rem;
  }
}

/* Honour a reduced-motion preference by leaving the ribbon completely static
   — it has no animation to begin with, stated here so a future hover effect
   is not added without one. */

/* ── Maintenance treatment (ADR-0189) ──────────────────────────────── */
/* Deliberately built on the restricted card's own ribbon geometry rather
   than as a second, parallel treatment: the corner anchoring, the two-rule
   band, the surface fill, the clipping and the click-through are all
   inherited from `.service-card__ribbon` above, and only the *hue* and the
   *word* change. That is the point — a visitor who has learned to read one
   corner stamp reads this one without being taught anything new.

   The hue is `--sws-warning`, not `--sws-danger`. Restriction is a standing
   property of the tool ("this is not yours to run"); maintenance is a
   transient operational state ("not right now"). Amber says the second thing
   and red would overstate it, and both tokens already carry dark-theme
   values so this needs no theme block of its own.

   `.service-card--maintenance` carries the same containment
   `.service-card--restricted` does, so a card that is *only* in maintenance
   still clips its ribbon to the rounded corner. A card that is both matches
   both rules, which is harmless: the declarations are identical. */
.service-card--maintenance {
  position: relative;
  overflow: hidden;
}

.service-card__ribbon--maintenance {
  border-top-color: var(--sws-warning);
  border-bottom-color: var(--sws-warning);
}

.service-card__ribbon-text--maintenance {
  color: var(--sws-warning);
}

/* The maintenance footer note, mirroring `.service-card__restricted-note`'s
   full-bleed geometry exactly (negative margins cancelling the card's own
   padding, `margin-top: auto` pinning it to the bottom) so a maintenance
   card is the same height as its neighbours in the grid. */
.service-card__maintenance-note {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: auto -1.5rem -1.5rem;
  padding: 0.7rem 1.5rem;
  width: calc(100% + 3rem);
  background: var(--sws-warning-soft);
  border-top: 1px solid var(--sws-warning);
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--sws-warning);
}

/* When a card carries both notes the restricted one is no longer the card's
   last child, so it gives up its own bottom bleed and the maintenance note
   below it keeps theirs. `:not(:last-child)` rather than a modifier class:
   the condition is literally "something follows me", and encoding that in
   markup would mean the renderer had to know about a CSS concern. */
.service-card__restricted-note:not(:last-child) {
  margin-bottom: 0;
}

.service-card__maintenance-icon {
  flex: none;
  font-size: 0.9rem;
  line-height: 1.2;
}

/* The sentence sits under the heading rather than beside it, for the same
   reason `.service-card__restricted-detail` does — and here it matters more,
   because this one is operator-authored and can be a good deal longer. */
.service-card__maintenance-detail {
  display: block;
  font-weight: 400;
}

/* ── Maintenance panel on the service page (ADR-0189) ──────────────── */
/* What replaces the submission form while a tool is in maintenance. Styled
   as a calm status panel and deliberately NOT `.alert--error`, exactly as
   `.restricted-state` is: nothing is broken, the platform is doing what an
   operator told it to. */
.maintenance-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 2rem;
  background: var(--sws-surface);
  border: 1px solid var(--sws-warning);
  border-radius: var(--sws-radius-lg);
  box-shadow: var(--sws-shadow-sm);
}

.maintenance-state__badge {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--sws-warning);
}

.maintenance-state__badge h2 {
  margin: 0;
  font-size: 1.2rem;
}

.maintenance-state__icon {
  font-size: 1.2rem;
  line-height: 1;
}

.maintenance-state p {
  margin: 0;
  color: var(--sws-muted);
  max-width: 46ch;
}

/* ── Genome Library callout (ADR-0164) ─────────────────────────────── */
/* The landing page's entry point into the public genome repository. Built
   deliberately *unlike* `.service-card` above: a single full-width banded
   region rather than a tile in the catalogue grid, a sunken surface rather
   than a raised one, a static accent rule rather than the cards' hover-lift,
   and the accent hue rather than the brand hue — so it never reads as "one
   more application you can run". Nothing here touches `.service-card`,
   `.service-list` or the nav; the callout is a sibling of the grid, so the
   catalogue's own layout is untouched by its presence. */

.library-callout {
  display: flex;
  align-items: center;
  gap: var(--sws-space-4);
  flex-wrap: wrap;
  margin-top: var(--sws-space-8);
  padding: var(--sws-space-6);
  background: var(--sws-surface-sunken);
  border: 1px solid var(--sws-border-strong);
  border-left: 4px solid var(--sws-accent);
  border-radius: var(--sws-radius-lg);
}

.library-callout__icon {
  font-size: 2rem;
  line-height: 1;
  flex-shrink: 0;
}

/* `flex: 1 1 20rem` + `min-width: 0` — the body takes the row when there is
   room and wraps the CTA underneath when there is not, and never forces the
   container wider than the page (an unbroken long word in a `min-width:
   auto` flex item is the usual source of horizontal overflow at 390px). */
.library-callout__body {
  flex: 1 1 20rem;
  min-width: 0;
}

.library-callout__eyebrow {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--sws-accent);
  margin-bottom: var(--sws-space-1);
}

.library-callout__body h2 {
  margin: 0 0 var(--sws-space-2);
  font-size: 1.15rem;
  letter-spacing: -0.01em;
}

.library-callout__body p {
  margin: 0;
  color: var(--sws-muted);
  font-size: 0.92rem;
  line-height: 1.45;
  max-width: 56ch;
}

.library-callout__cta {
  flex-shrink: 0;
}

/* The same explicit, high-contrast ring `.genome-row:focus-visible` already
   uses, for the same reason: this is a single tab stop a keyboard-only
   visitor must be able to see land. Stated here rather than relied upon from
   the UA default, since `.button` restyles the control's own borders. */
.library-callout__cta:focus-visible {
  outline: 3px solid var(--sws-brand);
  outline-offset: 2px;
}

@media (max-width: 640px) {
  /* Stacked, left-aligned, with the CTA a full-width row of its own rather
     than a stranded chip beside wrapped body copy. */
  .library-callout {
    align-items: flex-start;
    padding: var(--sws-space-4);
  }

  .library-callout__cta {
    width: 100%;
  }
}

/* ── Analysis tools section heading ────────────────────────────────── */
/* Sits between the Genome Library callout and `.service-list`, so the
   catalogue grid below reads as its own clearly-introduced section rather
   than an unlabeled continuation of the page. */

.tools-section {
  margin-top: var(--sws-space-8);
  margin-bottom: var(--sws-space-4);
}

.tools-section__title {
  margin: 0 0 var(--sws-space-1);
  font-size: 1.3rem;
  letter-spacing: -0.01em;
}

.tools-section__lede {
  margin: 0;
  color: var(--sws-muted);
  font-size: 0.92rem;
}

/* ── Service detail / submission ──────────────────────────────────── */

.service-hero {
  margin-bottom: var(--sws-space-6);
}

.about-section {
  max-width: 640px;
  margin-top: var(--sws-space-6);
  padding: var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
}

.about-section h2 {
  margin: 0 0 var(--sws-space-4);
  font-size: 1.05rem;
  letter-spacing: -0.01em;
}

/* ADR-0183: the technical-contact card sits directly below "About this
   service" and must read as a *sibling* of it, never as part of it -- the
   two carry different meanings and merging them visually would merge them
   semantically. One shared border-left accent plus the scope line below is
   enough separation without introducing a second card style. */
.about-section--technical {
  border-left: 3px solid var(--sws-brand);
}

.about-section__scope {
  margin: calc(-1 * var(--sws-space-2)) 0 var(--sws-space-4);
  color: var(--sws-muted);
  font-size: 0.86rem;
}

/* Multiple contacts read as a row of compact cards on wide screens and
   stack on narrow ones -- never a dense table, whether there is one
   contact or several (Part C: "easy to scan"). */
.about-section__contacts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sws-space-4) var(--sws-space-6);
}

.about-section__contact {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 12rem;
  flex: 1 1 12rem;
}

.about-section__contact-name {
  margin: 0;
  font-weight: 700;
  font-size: 0.98rem;
}

.about-section__contact-institution {
  margin: 0;
  color: var(--sws-muted);
  font-size: 0.86rem;
}

.about-section__email {
  color: var(--sws-brand);
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 500;
}

.about-section__email:hover,
.about-section__email:focus-visible {
  text-decoration: underline;
}

/* ── Version history — visually secondary to the submission workflow,
   discoverable below it. ─────────────────────────────────────────── */

.version-history {
  max-width: 640px;
  margin-top: var(--sws-space-4);
  padding: var(--sws-space-4) var(--sws-space-6);
}

.version-history h2 {
  margin: 0 0 var(--sws-space-4);
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--sws-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.version-history__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-4);
}

.version-history__entry {
  padding-left: var(--sws-space-4);
  border-left: 2px solid var(--sws-border);
}

.version-history__header {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}

.version-history__version {
  font-weight: 700;
  font-size: 0.92rem;
  font-variant-numeric: tabular-nums;
}

.version-history__badge {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem 0.55rem;
  border-radius: 999px;
  background: var(--sws-success-soft);
  color: var(--sws-success);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.version-history__date {
  color: var(--sws-muted);
  font-size: 0.8rem;
}

.version-history__summary {
  margin: 0;
  font-size: 0.88rem;
  color: var(--sws-muted-strong);
  /* A changelog entry with a long unbroken token (a file/function name, a
     URL) otherwise overflows this card horizontally on a narrow viewport
     -- the same fix the admin job-detail fact grid already established
     (ADR-0138) for the identical shape of problem. Found live, 2026-08-16,
     on SineX's own service page at a 375px viewport. */
  overflow-wrap: anywhere;
}

.version-history__summary--empty {
  font-style: italic;
  color: var(--sws-muted);
}

.version-history__details {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  color: var(--sws-muted);
  overflow-wrap: anywhere;
}

/* ── Citation — visually consistent with About/Version history. ─────── */

.citation-section {
  max-width: 640px;
  margin-top: var(--sws-space-4);
  padding: var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
}

.citation-section h2 {
  margin: 0 0 var(--sws-space-4);
  font-size: 1.05rem;
  letter-spacing: -0.01em;
}

.citation-section__list {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-4);
}

.citation-section__card {
  padding-left: var(--sws-space-4);
  border-left: 2px solid var(--sws-border);
}

/* Prominent among the citation's own fields, but always quieter than the
   page's real <h1> (service-hero) -- this is a citation, not a headline. */
.citation-section__title {
  margin: 0 0 0.3rem;
  font-weight: 700;
  font-size: 0.98rem;
  line-height: 1.4;
  color: var(--sws-text);
}

.citation-section__authors {
  margin: 0 0 0.25rem;
  font-size: 0.88rem;
  color: var(--sws-muted-strong);
  line-height: 1.45;
}

.citation-section__issue {
  margin: 0 0 0.35rem;
  font-size: 0.86rem;
  color: var(--sws-muted);
  font-style: italic;
}

.citation-section__doi {
  display: inline-block;
  color: var(--sws-brand);
  text-decoration: none;
  font-size: 0.86rem;
  font-weight: 500;
  word-break: break-word;
}

.citation-section__doi:hover,
.citation-section__doi:focus-visible {
  text-decoration: underline;
}

.citation-section__actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.6rem;
}

.citation-section__copy-button {
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-md);
  background: var(--sws-surface);
  color: var(--sws-muted-strong);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}

.citation-section__copy-button:hover,
.citation-section__copy-button:focus-visible {
  color: var(--sws-brand);
  border-color: var(--sws-brand);
}

.citation-section__copy-status {
  font-size: 0.78rem;
  color: var(--sws-muted);
  min-height: 1em;
}

@media (max-width: 640px) {
  .citation-section {
    padding: var(--sws-space-4);
  }
}

/* ── Canonical instant example runs ───────────────────────────────── */

.example-action {
  margin-top: var(--sws-space-4);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
}

.example-action__hint {
  font-size: 0.82rem;
}

.example-input-action {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.85rem;
}

.example-input-action__download {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--sws-brand);
  font-weight: 600;
  text-decoration: none;
  overflow-wrap: anywhere;
  cursor: pointer;
}

.example-input-action__download:hover {
  text-decoration: underline;
}

.example-input-action__use {
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--sws-border-strong);
  border-radius: var(--sws-radius-sm);
  background: var(--sws-surface);
  color: var(--sws-text);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.example-input-action__use:hover {
  border-color: var(--sws-brand);
  color: var(--sws-brand);
}

.example-input-action__status {
  color: var(--sws-muted);
  font-size: 0.8rem;
}

.example-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.65rem;
  border-radius: var(--sws-radius-pill, 999px);
  background: var(--sws-accent-soft);
  color: var(--sws-accent);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

.example-hero .analysis-hero__heading {
  flex-wrap: wrap;
}

.example-provenance-note {
  margin-top: var(--sws-space-4);
  font-size: 0.85rem;
}

.example-cta {
  margin-top: var(--sws-space-6);
}

@media (max-width: 640px) {
  .example-hero .analysis-hero__heading {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }
}

.version-picker {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: var(--sws-space-6);
}

.version-select {
  padding: 0.4rem 0.7rem;
  border-radius: var(--sws-radius-sm);
  border: 1px solid var(--sws-border-strong);
  background: var(--sws-surface);
  color: var(--sws-text);
  font: inherit;
  font-weight: 600;
}

.submission-form {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  max-width: 520px;
  padding: var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  box-shadow: var(--sws-shadow-sm);
  transition: opacity var(--sws-motion-med) var(--sws-ease);
}

.submission-form--running {
  /* This dims form text a user is still meant to read while a job is
   * accepted, not a disabled control (which WCAG 1.4.3 exempts from any
   * contrast requirement) -- so the dim has to stop short of the point
   * where it drags a token's contrast below AA. 0.85 is the largest step
   * down (from 0.55) that still keeps every affected text token at or
   * above 4.5:1 against --sws-surface in dark mode (0.55 alone dropped
   * --sws-muted to 3.00:1); see style.css.contrast.test.js.
   * pointer-events: none is what actually communicates the state. */
  opacity: 0.85;
  pointer-events: none;
}

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

.field-label {
  font-weight: 700;
  font-size: 0.88rem;
  color: var(--sws-muted-strong);
}

.text-input {
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--sws-border-strong);
  border-radius: var(--sws-radius-sm);
  font: inherit;
  background: var(--sws-surface);
  color: var(--sws-text);
  transition: border-color var(--sws-motion-fast) var(--sws-ease), box-shadow var(--sws-motion-fast) var(--sws-ease);
}

.text-input:focus-visible {
  outline: none;
  border-color: var(--sws-brand);
  box-shadow: 0 0 0 3px var(--sws-brand-soft);
}

/* ── Drag-and-drop upload zone ─────────────────────────────────────── */

.dropzone {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 6.5rem;
  padding: 1.25rem;
  border: 1.5px dashed var(--sws-border-strong);
  border-radius: var(--sws-radius-md);
  background: var(--sws-surface-sunken);
  cursor: pointer;
  text-align: center;
  transition: border-color var(--sws-motion-fast) var(--sws-ease), background var(--sws-motion-fast) var(--sws-ease);
}

.dropzone:hover {
  border-color: var(--sws-brand);
}

.dropzone--active {
  border-color: var(--sws-brand);
  background: var(--sws-brand-soft);
}

.dropzone__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  overflow: hidden;
}

.dropzone__prompt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  color: var(--sws-muted);
}

.dropzone__icon {
  font-size: 1.4rem;
  color: var(--sws-brand);
}

.dropzone__prompt p {
  margin: 0;
  font-size: 0.9rem;
}

.dropzone__file {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.92rem;
  font-weight: 600;
}

.dropzone__file-name {
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dropzone__file-size {
  color: var(--sws-muted);
  font-weight: 500;
}

/* ── Buttons ───────────────────────────────────────────────────────── */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0.6rem 1.2rem;
  border-radius: var(--sws-radius-sm);
  border: 1px solid transparent;
  font: inherit;
  font-weight: 700;
  font-size: 0.92rem;
  cursor: pointer;
  text-decoration: none;
  transition: transform var(--sws-motion-fast) var(--sws-ease), box-shadow var(--sws-motion-fast) var(--sws-ease),
    opacity var(--sws-motion-fast) var(--sws-ease);
}

.button--primary {
  background: var(--sws-gradient);
  color: var(--sws-on-brand);
  box-shadow: var(--sws-shadow-sm);
}

.button--primary:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: var(--sws-shadow-md);
}

.button--primary:disabled {
  opacity: 0.6;
  cursor: default;
  transform: none;
}

.button--large {
  padding: 0.75rem 1.5rem;
  font-size: 0.98rem;
  align-self: flex-start;
}

.button--secondary {
  background: var(--sws-surface);
  color: var(--sws-brand);
  border-color: var(--sws-border-strong);
}

.button--secondary:hover:not(:disabled) {
  border-color: var(--sws-brand);
}

.button--secondary:disabled {
  opacity: 0.6;
  cursor: default;
}

.button--link {
  background: none;
  border: none;
  color: var(--sws-brand);
  padding: 0;
  font-weight: 600;
  text-decoration: underline;
}

.button__icon {
  font-size: 0.85em;
}

.form-status {
  min-height: 1.2rem;
  font-size: 0.88rem;
  color: var(--sws-muted);
}

.form-status--error {
  color: var(--sws-danger);
}

.alert {
  padding: 0.85rem 1.1rem;
  border-radius: var(--sws-radius-sm);
  font-size: 0.92rem;
}

.alert--error {
  background: var(--sws-danger-soft);
  color: var(--sws-danger);
}

/* ADR-0139 Decision 3: the self-service email-change pending-confirmation
 * note (`profile-page.js`) -- an informational, not an error, state, so it
 * gets its own tone rather than borrowing `.alert--error`'s. */
.alert--info {
  background: var(--sws-info-soft);
  color: var(--sws-info);
}

/* ── Analysis timeline (the centerpiece) ──────────────────────────── */

.run-progress {
  margin-top: var(--sws-space-6);
  max-width: 520px;
  padding: var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  box-shadow: var(--sws-shadow-sm);
}

.analysis-hero {
  padding: var(--sws-space-8);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  box-shadow: var(--sws-shadow-md);
  position: relative;
  overflow: hidden;
}

.analysis-hero::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: var(--sws-gradient);
}

.analysis-hero__heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: var(--sws-space-6);
}

.analysis-hero__heading h1 {
  margin: 0;
  font-size: 1.5rem;
  letter-spacing: -0.01em;
}

.run-state {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0;
  font-weight: 700;
  font-size: 0.9rem;
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
}

.run-state--active {
  color: var(--sws-info);
  background: var(--sws-info-soft);
}

.run-state--done {
  color: var(--sws-success);
  background: var(--sws-success-soft);
}

.run-state--failed {
  color: var(--sws-danger);
  background: var(--sws-danger-soft);
}

.pulse-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentColor;
  animation: pulse 1.6s var(--sws-ease) infinite;
}

@keyframes pulse {
  0%, 100% {
    opacity: 1;
    box-shadow: 0 0 0 0 currentColor;
  }
  70% {
    opacity: 0.6;
    box-shadow: 0 0 0 5px transparent;
  }
}

.analysis-timeline {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin: 0 0 var(--sws-space-6);
  padding: 0;
}

.timeline-step {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex: 1 1 auto;
  padding: 0.5rem 0.75rem 0.5rem 0;
  position: relative;
}

.timeline-step:not(:last-child)::after {
  content: "";
  flex: 1 1 2rem;
  min-width: 1.5rem;
  height: 2px;
  background: var(--sws-border);
  margin: 0 0.5rem;
  align-self: center;
  transition: background var(--sws-motion-med) var(--sws-ease);
}

.timeline-step--done:not(:last-child)::after {
  background: var(--sws-success);
}

.timeline-marker {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  font-size: 0.8rem;
  font-weight: 800;
  border: 2px solid var(--sws-border-strong);
  color: var(--sws-muted);
  background: var(--sws-surface);
  transition: all var(--sws-motion-med) var(--sws-ease);
}

.timeline-marker--done {
  background: var(--sws-success);
  border-color: var(--sws-success);
  color: var(--sws-status-ink);
}

.timeline-marker--active {
  border-color: var(--sws-brand);
  color: var(--sws-brand);
  animation: spin 1.1s linear infinite;
  border-top-color: transparent;
  border-right-color: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .timeline-marker--active {
    animation: none;
    border-color: var(--sws-brand);
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.timeline-marker--failed {
  background: var(--sws-danger);
  border-color: var(--sws-danger);
  color: var(--sws-status-ink);
}

.timeline-step__label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--sws-muted);
  white-space: nowrap;
}

.timeline-step--done .timeline-step__label,
.timeline-step--active .timeline-step__label {
  color: var(--sws-text);
}

.timeline-step--failed .timeline-step__label {
  color: var(--sws-danger);
}

.timeline-detail-panel {
  margin: -0.5rem 0 var(--sws-space-6);
  padding: var(--sws-space-4) var(--sws-space-4) var(--sws-space-4) calc(1.6rem + 0.75rem);
  background: var(--sws-surface-sunken);
  border-radius: var(--sws-radius-md);
  border: 1px solid var(--sws-border);
}

.timeline-detail-panel__heading {
  margin: 0 0 0.6rem;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--sws-muted);
}

.timeline-detail__steps {
  list-style: none;
  margin: 0 0 0.6rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.timeline-detail__step {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.92rem;
  font-weight: 600;
}

.timeline-detail__marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  flex-shrink: 0;
}

.timeline-detail__step--done {
  color: var(--sws-muted);
}

.timeline-detail__step--done .timeline-detail__marker {
  color: var(--sws-success);
}

.timeline-detail__step--active {
  color: var(--sws-text);
}

.timeline-detail__step--active .timeline-detail__marker {
  color: var(--sws-brand);
  animation: pulse 1.6s var(--sws-ease) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .timeline-detail__step--active .timeline-detail__marker {
    animation: none;
  }
}

.timeline-detail__fact {
  margin: 0.3rem 0 0;
  font-size: 0.88rem;
  color: var(--sws-muted-strong);
  font-variant-numeric: tabular-nums;
}

.fact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1rem 2rem;
  margin: 0;
  padding-top: var(--sws-space-6);
  border-top: 1px solid var(--sws-border);
}

.fact-grid dt {
  color: var(--sws-muted);
  font-weight: 600;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 0 0 0.15rem;
}

.fact-grid dd {
  margin: 0;
  font-size: 0.98rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  /* A long, unbroken value (a sha256 digest, an account id) otherwise
     overflows its grid cell rather than wrapping -- a grid item's default
     min-width is auto (content-based), which can exceed the track's own
     minmax(200px, 1fr) sizing for a string with no natural break point.
     Found live in the admin job-detail page (ADR-0138), whose fact grid is
     the first caller to show two 64-hex-character digests side by side. */
  overflow-wrap: anywhere;
  min-width: 0;
}

/* ── Current activity ─────────────────────────────────────────────── */
/* Factual, human-readable visibility into what SWS is doing within the
   active lifecycle stage (progress-activity.js). Deliberately styled as a
   quiet, single block rather than a log/terminal — no monospace font, no
   per-line timestamps in the primary message, no unbounded scroll. Placed
   between the stepper and the Application/Submitted fact grid. */

.current-activity {
  margin: -0.5rem 0 var(--sws-space-6);
  padding: var(--sws-space-4);
  background: var(--sws-surface-sunken);
  border-radius: var(--sws-radius-md);
  border: 1px solid var(--sws-border);
}

.current-activity__stage {
  margin: 0 0 0.3rem;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--sws-muted);
}

/* The completed facts, oldest first, above the current activity — the same
   done/active marker language `.timeline-detail__step` uses in the stepper
   directly above this panel. */
.current-activity__history {
  list-style: none;
  margin: 0 0 0.45rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.current-activity__fact {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--sws-muted-strong);
}

.current-activity__fact .current-activity__marker {
  color: var(--sws-success);
}

.current-activity__marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  flex-shrink: 0;
}

/* The current activity itself: deliberately heavier and darker than the
   completed facts above it, so "what is happening now" never reads as just
   another log line. */
.current-activity__message {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0;
  font-size: 0.98rem;
  font-weight: 600;
  color: var(--sws-text);
}

.current-activity__history + .current-activity__message {
  padding-top: 0.45rem;
  border-top: 1px solid var(--sws-border);
}

.current-activity__message--active .current-activity__marker {
  color: var(--sws-brand);
  animation: pulse 1.6s var(--sws-ease) infinite;
}

.current-activity__message--done .current-activity__marker {
  color: var(--sws-success);
}

/* A job that stopped: neutral, never red. *Why* it stopped is the "What
   happened" section's job — this line only says where it got to. */
.current-activity__message--stopped {
  color: var(--sws-muted-strong);
}

.current-activity__message--stopped .current-activity__marker {
  color: var(--sws-muted);
}

@media (prefers-reduced-motion: reduce) {
  .current-activity__message--active .current-activity__marker {
    animation: none;
  }
}

/* ── Job timing breakdown ─────────────────────────────────────────── */

.job-timing {
  margin-top: var(--sws-space-6);
  padding-top: var(--sws-space-6);
  border-top: 1px solid var(--sws-border);
}

.job-timing__heading {
  margin: 0 0 0.75rem;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--sws-muted);
}

.job-timing__bar {
  display: flex;
  width: 100%;
  height: 8px;
  border-radius: 999px;
  background: var(--sws-surface-sunken);
  overflow: hidden;
  margin-bottom: 0.85rem;
}

.job-timing__bar-segment {
  height: 100%;
  min-width: 2px;
}

.job-timing__rows {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.job-timing__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 0.92rem;
}

.job-timing__row--total {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--sws-border);
  font-weight: 600;
}

.job-timing__label {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--sws-muted-strong);
}

.job-timing__row--total .job-timing__label {
  color: var(--sws-text);
}

.job-timing__swatch {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 999px;
  flex-shrink: 0;
}

.job-timing__value {
  font-variant-numeric: tabular-nums;
  color: var(--sws-text);
}

/* ── Status pills ─────────────────────────────────────────────────── */

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.65rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: capitalize;
}

.status-pill__dot {
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: currentColor;
}

.status-pill--success {
  background: var(--sws-success-soft);
  color: var(--sws-success);
}

.status-pill--danger {
  background: var(--sws-danger-soft);
  color: var(--sws-danger);
}

.status-pill--info {
  background: var(--sws-info-soft);
  color: var(--sws-info);
}

.status-pill--neutral {
  background: var(--sws-neutral-soft);
  color: var(--sws-neutral);
}

/* ── My Jobs table ────────────────────────────────────────────────── */

.table-wrap {
  overflow-x: auto;
  border-radius: var(--sws-radius-lg);
  border: 1px solid var(--sws-border);
  box-shadow: var(--sws-shadow-sm);
}

.job-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--sws-surface);
}

.job-table th,
.job-table td {
  text-align: left;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--sws-border);
  font-size: 0.92rem;
}

.job-table th {
  color: var(--sws-muted);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: var(--sws-surface-sunken);
}

.job-table tbody tr:last-child td {
  border-bottom: none;
}

.job-table tbody tr {
  transition: background var(--sws-motion-fast) var(--sws-ease);
}

.job-table tbody tr:hover {
  background: var(--sws-surface-sunken);
}

.job-table__id a {
  font-weight: 700;
  color: var(--sws-brand);
  text-decoration: none;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.9rem;
  padding: var(--sws-space-8);
  background: var(--sws-surface);
  border: 1px dashed var(--sws-border-strong);
  border-radius: var(--sws-radius-lg);
}

.empty-state__icon {
  font-size: 2rem;
}

/* ── Results ──────────────────────────────────────────────────────── */

.job-results {
  margin-top: var(--sws-space-4);
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* ADR-0172/ADR-0182: prepended ahead of the ordinary result cards when the
   job's application declares an interpretation whose parsers recognise one
   of its declared outputs. Same card chrome as `.result-card` so it reads
   as this section's own lead item, not a stray insertion. It may hold more
   than one interpreted block (a composite declaration renders each block it
   selects), hence the column layout -- a single-block interpretation is
   visually unchanged by it. */
.job-result-interpretation {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-6);
  padding: var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-md);
}

.result-card {
  padding: var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  box-shadow: var(--sws-shadow-sm);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
}

.result-card__header h3 {
  margin: 0 0 0.15rem;
  font-size: 1.02rem;
}

.result-card__header p {
  margin: 0;
}

/* The button and its status message are one unit, with their own small gap,
   so the usually-empty `role="status"` paragraph stops costing a full
   `.result-card` gap on each side of itself. */
.result-card__actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
}

/* A permanently-mounted, usually-empty live region must contribute no
   vertical space of its own. Without this it carries the UA default `<p>`
   margin of 1em top *and* bottom, which does not collapse inside a flex
   container -- ~32px of blank page per result card, on every card, plus the
   container gaps around it. Measured on the live example page 2026-08-29
   before the fix: a download-only card was 184px tall for ~110px of content,
   and each previewed card had a 60px empty band between its Download button
   and its results. Kept mounted rather than removed: `role="status"`
   announces a content mutation inside an already-rendered region far more
   reliably than one that appears from `display: none`. */
.result-download-status {
  margin: 0;
}

.result-download-status:empty {
  /* Belt and braces with `margin: 0` above: an empty status contributes no
     line box either, so the flex gap around it is the only space it can
     ever cost. */
  display: none;
}

.result-preview {
  width: 100%;
  margin-top: 0.25rem;
}

.result-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-sm);
  max-height: 22rem;
  overflow-y: auto;
}

.result-table {
  border-collapse: collapse;
  font-size: 0.85rem;
  width: 100%;
}

.result-table th,
.result-table td {
  border-bottom: 1px solid var(--sws-border);
  padding: 0.4rem 0.65rem;
  white-space: nowrap;
}

.result-table th {
  background: var(--sws-surface-sunken);
  position: sticky;
  top: 0;
}

/* ── ADR-0184 follow-up: result-first presentation ────────────────── */
/* Generic throughout — no rule selects on a tool, an application, a sequence
   id or a filename. */

.result-sequences__actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
  padding-bottom: var(--sws-space-4);
  border-bottom: 1px solid var(--sws-border);
}

/* The biological answer, given the visual weight of an answer. */
.classification {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
  padding: var(--sws-space-4);
  background: var(--sws-surface-sunken);
  border-radius: var(--sws-radius-md);
}

.classification__title,
.sequence-figure__title,
.sequence-details__title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
}

.classification__verdict {
  margin: 0;
  font-weight: 600;
}

/* Rank/value pairs. `auto` first column so the rank column is exactly as wide
   as the longest rank name and the values line up without a fixed width that
   a longer vocabulary would break. */
.classification__ranks {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.2rem var(--sws-space-4);
  margin: 0;
}

.classification__ranks dt {
  color: var(--sws-muted);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  align-self: center;
}

.classification__ranks dd {
  margin: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* An unassigned rank is shown, and shown as *not* an assignment — never
   omitted (a lineage missing a rank reads as complete) and never styled like
   a finding. */
.classification__unassigned {
  color: var(--sws-muted);
  font-weight: 400;
  font-style: italic;
}

.classification__note {
  margin: 0;
  font-size: 0.85rem;
}

.sequence-figure,
.sequence-details {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
}

/* A collapsed supporting output. The three-rule pattern the codebase already
   uses for `<summary>`: `display: flex` kills `list-item`, so the native
   marker must be removed and replaced explicitly or the row reads as a plain
   bold heading with nothing saying it opens. */
.sequence-detail {
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-sm);
  background: var(--sws-surface);
}

.sequence-detail__summary {
  cursor: pointer;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.5rem 0.7rem;
  font-size: 0.9rem;
  font-weight: 600;
  list-style: none;
}

.sequence-detail__summary::-webkit-details-marker {
  display: none;
}

.sequence-detail__summary::before {
  content: "▸";
  color: var(--sws-muted);
}

.sequence-detail[open] > .sequence-detail__summary::before {
  content: "▾";
}

.sequence-detail__body {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
  padding: 0 0.7rem 0.7rem;
  min-width: 0;
}

.sequence-detail__meta {
  margin: 0;
  font-size: 0.8rem;
}

/* A taxonomy disclosure nested inside the Classification panel sits on the
   panel's own sunken ground, so it needs its own surface to stay legible. */
.classification > .sequence-detail {
  background: var(--sws-surface);
}

/* ── ADR-0184: per-sequence results ───────────────────────────────── */
/* Generic presentation throughout — no rule below selects on a tool, an
   application, or a sequence identifier. */

.result-sequences {
  padding: var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  box-shadow: var(--sws-shadow-sm);
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-4);
}

.result-sequences__header h3 {
  margin: 0 0 0.15rem;
  font-size: 1.02rem;
}

.result-sequences__header p {
  margin: 0;
}

/* The selector. Wraps rather than scrolling horizontally: this page must
   have zero horizontal overflow at 390px, and a run's sequence names are
   the one thing a reader must be able to see all of at once. */
.sequence-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.sequence-picker__option {
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-pill, 999px);
  background: var(--sws-surface-sunken);
  color: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  /* A sequence identifier can be long and has no natural break point. */
  overflow-wrap: anywhere;
  max-width: 100%;
}

/* Selection is carried by `aria-selected`, the same attribute assistive
   technology reads — never by a class the two could drift apart on. */
.sequence-picker__option[aria-selected="true"] {
  background: var(--sws-accent-soft);
  border-color: var(--sws-accent);
  color: var(--sws-accent);
}

.sequence-picker__select {
  max-width: 100%;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-sm);
  background: var(--sws-surface);
  color: inherit;
  font-size: 0.9rem;
}

.sequence-panel {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-4);
  min-width: 0;
}

/* The selected sequence's inline figure. Bounded so a tall heatmap cannot
   push that sequence's actual results off the first screen — the same
   argument, and the same bound, the genome page already applies to its own
   result images. */
.sequence-preview {
  display: flex;
  justify-content: center;
}

.sequence-preview .result-preview__image {
  max-height: 26rem;
  width: auto;
  max-width: 100%;
}

.sequence-preview .skeleton--image {
  /* An empty flex item has no intrinsic width, so a skeleton with only a
     `max-width` renders as an invisible full-height gap. Give it a real
     width. */
  width: 28rem;
}

.sequence-panel__title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.sequence-file {
  border-top: 1px solid var(--sws-border);
  padding-top: var(--sws-space-4);
  min-width: 0;
}

.sequence-file__header h4 {
  margin: 0 0 0.15rem;
  font-size: 0.92rem;
  overflow-wrap: anywhere;
}

.sequence-file__header p {
  margin: 0 0 0.5rem;
  font-size: 0.82rem;
}

.sequence-file__actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
}

.result-preview--image {
  display: flex;
  justify-content: center;
}

.result-preview__enlarge-trigger {
  display: block;
  padding: 0;
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-sm);
  background: var(--sws-surface-sunken);
  cursor: zoom-in;
  max-width: 100%;
}

.result-preview__image {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: calc(var(--sws-radius-sm) - 1px);
}

.skeleton--image {
  height: 14rem;
  max-width: 28rem;
}

.image-lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sws-space-6);
  background: rgba(0, 0, 0, 0.72);
}

.image-lightbox__image {
  max-width: min(92vw, 1400px);
  max-height: 90vh;
  width: auto;
  height: auto;
  border-radius: var(--sws-radius-sm);
  box-shadow: var(--sws-shadow-sm);
}

.job-failure {
  margin-top: var(--sws-space-4);
}

/* ── Skeletons ────────────────────────────────────────────────────── */

.skeleton {
  border-radius: var(--sws-radius-lg);
  background: linear-gradient(
    100deg,
    var(--sws-surface-sunken) 30%,
    var(--sws-border) 50%,
    var(--sws-surface-sunken) 70%
  );
  background-size: 200% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
}

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

@keyframes shimmer {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

.skeleton--card {
  height: 9rem;
}

.skeleton--hero {
  height: 12rem;
}

.skeleton--form {
  height: 16rem;
  max-width: 520px;
}

.skeleton--table {
  height: 10rem;
}

/* ── Prose (About) ────────────────────────────────────────────────── */

.page--prose {
  max-width: 68ch;
}

.page--prose h2 {
  margin-top: 2rem;
  font-size: 1.15rem;
}

.page--prose p {
  line-height: 1.6;
  color: var(--sws-muted-strong);
}

.about-lifecycle {
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.about-lifecycle dt {
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--sws-text);
}

.about-lifecycle dd {
  margin: 0.2rem 0 0;
  line-height: 1.6;
  color: var(--sws-muted-strong);
}

/* ── Admin dashboard (ADR-0138) ──────────────────────────────────────── */

.admin-nav__logout {
  color: var(--sws-muted);
  font-weight: 600;
  text-decoration: none;
}

.admin-nav__logout:hover,
.admin-nav__logout:focus-visible {
  color: var(--sws-danger);
}

.admin-page--centered {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - 12rem);
}

.admin-login-card {
  width: 100%;
  max-width: 420px;
  padding: var(--sws-space-8);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  box-shadow: var(--sws-shadow-md);
  text-align: left;
}

.admin-login-card h1 {
  margin: 0 0 0.5rem;
  font-size: 1.35rem;
  letter-spacing: -0.01em;
}

.admin-login-form {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-4);
  margin-top: var(--sws-space-6);
  padding: 0;
  background: none;
  border: none;
  box-shadow: none;
  max-width: none;
}

.admin-skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-6);
}

.admin-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 1rem;
  margin-bottom: var(--sws-space-8);
}

.admin-stat-tile {
  padding: var(--sws-space-4) var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-md);
  box-shadow: var(--sws-shadow-sm);
}

.admin-stat-tile__value {
  font-size: 1.6rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.admin-stat-tile--info .admin-stat-tile__value {
  color: var(--sws-brand);
}

.admin-stat-tile__label {
  margin-top: 0.15rem;
  color: var(--sws-muted);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: capitalize;
}

.admin-section {
  margin-top: var(--sws-space-6);
}

.admin-section__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: var(--sws-space-4);
}

.admin-section__header h2 {
  margin: 0;
  font-size: 1.1rem;
  letter-spacing: -0.01em;
}

.admin-section__header a {
  color: var(--sws-brand);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
}

.admin-section__header a:hover,
.admin-section__header a:focus-visible {
  text-decoration: underline;
}

.admin-job-table__account {
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  color: var(--sws-muted-strong);
}

/* ADR-0139 Decision 8: analytics chart cards -- overview page (global) and
 * the per-application analytics section (`application-detail-page.js`),
 * shared through `admin-components.js`'s own `renderChartCard`. */
.admin-chart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
}

.admin-chart-card {
  padding: var(--sws-space-4) var(--sws-space-6) var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-md);
  box-shadow: var(--sws-shadow-sm);
}

/* ADR-0159 PR B. One card per registered application version, stacked rather
   than gridded: each carries a full resource-profile block (values, ceilings,
   an edit form for a Platform Admin, and a change history), which is far too
   tall to read side by side. Reuses `.admin-chart-card`'s own surface so a
   version card and an analytics card are visibly the same kind of object. */
.admin-version-card + .admin-version-card {
  margin-top: var(--sws-space-4);
}

/* The resource block nests inside a version card, so it drops the outer
   section's own bottom margin rather than compounding the card's padding. */
.admin-resource-profile {
  margin-top: var(--sws-space-3);
  margin-bottom: 0;
}

.admin-chart-card__title {
  margin: 0 0 var(--sws-space-2);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.admin-chart {
  height: 260px;
}

@media (max-width: 640px) {
  .admin-login-card {
    padding: var(--sws-space-6);
  }
}

/* ── Responsive ───────────────────────────────────────────────────── */

@media (max-width: 640px) {
  .sws-main {
    padding: 1.5rem 1rem 4rem;
  }

  .analysis-hero,
  .submission-form,
  .run-progress {
    padding: var(--sws-space-4);
  }

  .analysis-hero__heading {
    flex-direction: column;
    align-items: flex-start;
  }

  .timeline-step__label {
    white-space: normal;
  }
}

/* ── Responsive nav + admin density (mobile widths) ──────────────────
 * `.top-nav` carries 4 items on the public portal and up to 6 in the
 * admin section (5 links from `renderAdminNav` plus its own
 * `.admin-nav__logout` button, `admin/admin-guard.js`) with no existing
 * wrap/scroll treatment at all -- at native phone widths (iPhone SE
 * 375px up through the largest common phones, ~430px) that many items
 * overflow `.sws-header`'s available width.
 *
 * 480px is the threshold chosen here -- deliberately *not* this file's
 * existing 640px breakpoint, which is already claimed above for
 * unrelated public-portal spacing/stacking concerns and would fire well
 * into tablet/landscape-phone widths where this nav already fits. 480px
 * sits just above the largest mainstream phone viewport (iPhone Pro Max,
 * ~430px CSS width) and below the smallest common tablet/landscape-phone
 * width, so this block only ever applies to genuinely narrow,
 * single-column layouts -- the same width class the four chart
 * components' own `NARROW_WIDTH_THRESHOLD` (admin/charts/*.js) uses, so
 * the nav, chart legends, and card/table density all change together at
 * one consistent width rather than three independently-tuned ones.
 *
 * Compact pattern: rather than shrinking nav link font-size (illegible
 * past a point, and does not scale to an unbounded item count), `.top-
 * nav` itself becomes a horizontally scrollable strip at this width --
 * every item keeps its full label and full touch target, just reachable
 * by a swipe instead of a wrap or an overflow clip. A CSS-only scroll
 * strip (vs. a JS hamburger toggle) was chosen because it requires zero
 * new interactive state in `app.js`/`admin-guard.js`'s existing
 * synchronous render functions -- both already return the nav as a
 * plain `<nav class="top-nav">`, and this treatment needs nothing more
 * from them than the class it already carries. */
@media (max-width: 480px) {
  .sws-header {
    padding: 0.75rem 1rem;
    gap: 0.5rem;
  }

  /* The brand wordmark ("Scientific Web Services") competes with the nav
     strip for the same row -- it shrinks and truncates with an ellipsis
     rather than forcing the nav to shrink below its own scrollable
     minimum, since the nav's full item list is the more important thing
     to keep reachable at this width. */
  .sws-brand {
    flex-shrink: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .sws-nav-group {
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none; /* Firefox: hide the scrollbar, scrolling itself is unaffected */
    padding-bottom: 2px; /* keeps a focused/hovered link's underline from being clipped by the scroll container's own edge */
  }

  .sws-nav-group::-webkit-scrollbar {
    display: none; /* Chrome/Safari equivalent of scrollbar-width: none above */
  }

  .top-nav {
    flex-shrink: 0;
  }

  .top-nav a,
  .admin-nav__logout {
    flex-shrink: 0;
    white-space: nowrap;
  }

  /* ── Cards/forms/tables: reduce vertical density, not layout ─────────
   * None of these already overflow at this width (`.admin-chart-grid`/
   * `.admin-stat-grid` are already fluid `auto-fit`/`auto-fill` grids,
   * `.job-table` already scrolls horizontally inside `.table-wrap`) --
   * this is purely trimming padding/gap that reads as excessive once a
   * card or table is the full (narrow) viewport width. */
  .admin-chart-card {
    padding: var(--sws-space-3) var(--sws-space-4) var(--sws-space-4);
  }

  .admin-stat-grid {
    gap: 0.6rem;
    margin-bottom: var(--sws-space-6);
  }

  .admin-stat-tile {
    padding: var(--sws-space-3) var(--sws-space-4);
  }

  .admin-login-card {
    padding: var(--sws-space-4);
  }

  .admin-login-form,
  .submission-form {
    gap: var(--sws-space-3);
  }

  .job-table th,
  .job-table td {
    padding: 0.55rem 0.65rem;
    font-size: 0.85rem;
  }
}

/* ── ADR-0164: public genome repository ────────────────────────────── */

.field-hint {
  font-size: 0.82rem;
  color: var(--sws-muted);
  margin: 0;
}

.field-disclosure {
  font-size: 0.82rem;
  color: var(--sws-muted-strong);
  background: var(--sws-brand-soft);
  border-radius: var(--sws-radius-sm);
  padding: 0.5rem 0.7rem;
  margin: 0;
}

/* ADR-0164: a mutually-exclusive input-alternative-group member (e.g. the
 * accession field once a file has been chosen, or vice versa) once the
 * *other* member already carries a value — disabled, so it's excluded from
 * both native form validation and submission, but always visible, so the
 * choice made is legible rather than a field silently disappearing. */
.field--disabled {
  opacity: 0.55;
}

.field--disabled .text-input,
.field--disabled .dropzone {
  pointer-events: none;
  background: var(--sws-surface-muted, var(--sws-brand-soft));
}

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

/* ── Genome hero ──────────────────────────────────────────────────── */

.genome-hero {
  display: flex;
  align-items: flex-start;
  gap: var(--sws-space-4);
}

.genome-hero__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  background: var(--sws-brand-soft);
  font-size: 1.6rem;
}

.genome-hero__main {
  min-width: 0;
  flex: 1 1 auto;
}

.genome-hero__main h1 {
  overflow-wrap: anywhere;
}

.genome-hero__subtitle {
  margin-top: 0.15rem;
}

.public-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.65rem;
  border-radius: var(--sws-radius-pill, 999px);
  background: var(--sws-success-soft);
  color: var(--sws-success);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

.genome-hero__title {
  margin: 0.35rem 0 0;
  font-size: 0.92rem;
  overflow-wrap: anywhere;
}

/* Page level, never inside a collapsed disclosure -- see
   `renderDriftWarning` in genome-page.js. */
.genome-drift-warning {
  margin-top: var(--sws-space-4);
}

.genome-fact-grid {
  margin-top: var(--sws-space-6);
}

.genome-checksum__value {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

.genome-checksum__value code {
  font-family: var(--sws-font-mono, ui-monospace, monospace);
  font-size: 0.85rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.genome-checksum__copy-button {
  border: 1px solid var(--sws-border-strong);
  background: var(--sws-surface);
  color: var(--sws-brand);
  border-radius: var(--sws-radius-sm);
  padding: 0.1rem 0.5rem;
  font-size: 0.72rem;
  font-weight: 700;
  cursor: pointer;
  flex-shrink: 0;
}

.genome-checksum__copy-button:hover {
  border-color: var(--sws-brand);
}

.genome-checksum__copy-status {
  font-size: 0.72rem;
  color: var(--sws-muted);
}

/* ── Progressive disclosure ("More details") ─────────────────────── */

.genome-more-details {
  margin-top: var(--sws-space-4);
}

.genome-more-details > summary {
  cursor: pointer;
  font-weight: 700;
  color: var(--sws-brand);
  font-size: 0.9rem;
  list-style: none;
}

.genome-more-details > summary::-webkit-details-marker {
  display: none;
}

.genome-more-details > summary::before {
  content: "▸ ";
}

.genome-more-details[open] > summary::before {
  content: "▾ ";
}

.genome-more-details .genome-sources {
  margin-top: var(--sws-space-4);
}

.genome-sources {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--sws-space-4);
}

.genome-source-card {
  border: 1px solid var(--sws-border-strong);
  border-radius: var(--sws-radius-md);
  padding: var(--sws-space-4);
  background: var(--sws-surface);
}

/* ── ADR-0164 Phase 4b: NCBI Taxonomy (genome identity) ──────────── */
/* Deliberately styled as *metadata*, not as a result card: no card border,
   no surface fill, no status chrome — it reads as a continuation of the fact
   grid above it rather than as something a tool produced. Nothing here
   selects on a taxon, a rank name or an accession; every rule is generic, so
   a nine-rank phage lineage and a nine-rank bacterial one get the identical
   treatment. */

.genome-taxonomy {
  margin-top: var(--sws-space-6);
  padding-top: var(--sws-space-4);
  border-top: 1px solid var(--sws-border);
}

.genome-taxonomy__heading {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sws-space-2);
}

.genome-taxonomy__heading h2 {
  margin: 0;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
}

/* The authority label is never optional and never abbreviated: a visitor must
   be able to tell this apart from a tool's own reported classification at a
   glance, without reading further. */
.genome-taxonomy__authority {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sws-brand);
  background: var(--sws-brand-soft);
  border-radius: var(--sws-radius-sm);
  padding: 0.1rem 0.45rem;
}

.genome-taxonomy__focal {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sws-space-2);
  margin: var(--sws-space-3) 0 0;
  /* An unbroken NCBI name is the one string on this page long enough to push
     a 390px viewport sideways; it breaks rather than the page does. */
  overflow-wrap: anywhere;
}

.genome-taxonomy__name {
  font-size: 1rem;
  font-weight: 600;
}

.genome-taxonomy__rank,
.genome-taxonomy__step-rank {
  font-size: 0.72rem;
  color: var(--sws-muted);
  text-transform: lowercase;
}

.genome-taxonomy__taxid {
  font-size: 0.85rem;
  color: var(--sws-muted);
}

/* A wrapping breadcrumb rather than a fixed-width strip: a lineage is
   root-to-parent and arbitrarily long, so it must reflow onto as many lines
   as it needs. `flex-wrap` plus per-item `overflow-wrap` is what keeps the
   page itself from ever scrolling horizontally, at any depth. */
.genome-taxonomy__lineage {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sws-space-1) var(--sws-space-3);
  list-style: none;
  margin: var(--sws-space-3) 0 0;
  padding: 0;
}

.genome-taxonomy__step {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  min-width: 0;
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

/* The separator is generated content, never a text node built from data —
   see `renderTaxonomy`'s own note on why not one character of this chain is
   assembled out of upstream strings. */
.genome-taxonomy__step + .genome-taxonomy__step::before {
  content: "›";
  color: var(--sws-muted);
  margin-right: 0.35rem;
}

.genome-taxonomy__provenance {
  margin: var(--sws-space-3) 0 0;
  font-size: 0.78rem;
}

/* ── Analysis overview ────────────────────────────────────────────── */

.analysis-overview-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sws-space-4);
  margin: var(--sws-space-8) 0 var(--sws-space-4);
}

.analysis-overview-header h2 {
  margin: 0 0 0.25rem;
  font-size: 1.2rem;
  letter-spacing: -0.01em;
}

.analysis-overview-header p {
  margin: 0;
  max-width: 46ch;
}

.analysis-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sws-space-4);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.82rem;
}

.analysis-legend li {
  display: flex;
  align-items: center;
}

/* ── Status indicator (icon + label, never color alone) ──────────── */

.analysis-status {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  font-weight: 700;
  font-size: 0.85rem;
}

.analysis-status__glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  font-size: 0.7rem;
  font-weight: 800;
  flex-shrink: 0;
}

.analysis-status__note {
  font-weight: 500;
  color: var(--sws-muted);
  font-size: 0.78rem;
}

.analysis-status--completed {
  color: var(--sws-success);
}
.analysis-status--completed .analysis-status__glyph {
  background: var(--sws-success-soft);
}

.analysis-status--running {
  color: var(--sws-info);
}
.analysis-status--running .analysis-status__glyph {
  background: var(--sws-info-soft);
}

.analysis-status--failed {
  color: var(--sws-danger);
}
.analysis-status--failed .analysis-status__glyph {
  background: var(--sws-danger-soft);
}

.analysis-status--not-run {
  color: var(--sws-neutral);
}
.analysis-status--not-run .analysis-status__glyph {
  background: var(--sws-neutral-soft);
}

/* ── Unified analysis row list ────────────────────────────────────── */

.analysis-rows {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
}

.analysis-row {
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-md);
  background: var(--sws-surface);
  box-shadow: var(--sws-shadow-sm);
}

.analysis-row__body {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sws-space-3) var(--sws-space-6);
  padding: var(--sws-space-4);
}

.analysis-row__summary-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sws-space-4);
  flex: 1 1 260px;
  min-width: 0;
}

.analysis-row__name {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.analysis-row__version {
  font-weight: 500;
  color: var(--sws-muted);
  font-size: 0.82rem;
}

.analysis-row__time {
  color: var(--sws-muted);
  font-size: 0.82rem;
  white-space: nowrap;
}

.analysis-row__summary {
  margin: 0;
  flex: 1 1 160px;
}

/* ADR-0172: the wrapper `renderToolResultBody` returns — either the
   unchanged `.analysis-row__summary` + `.analysis-row__outputs` pair, or a
   concern summary card above them. A full-width band beneath the summary
   line, exactly like `.analysis-row__outputs` claimed on its own before this
   wrapper existed. */
.analysis-row__result-body {
  flex: 1 1 100%;
  width: 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
}

.analysis-row__actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-left: auto;
}

/* ── ADR-0164 Phase 3: inline result outputs on the genome page ────── */
/* `.analysis-row__body` is a wrapping flex row; the outputs section is a
   full-width band beneath the summary line rather than another column, so
   it claims a whole flex line of its own. Everything below is generic
   presentation — no rule anywhere selects on a tool, an accession, or a
   media type; `role` only decides `<div>`-vs-`<details>` in the markup
   itself, never what is rendered inside either. */
.analysis-row__outputs {
  flex: 1 1 100%;
  width: 100%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-4);
  margin-top: var(--sws-space-2);
}

.result-output {
  min-width: 0;
  border-top: 1px solid var(--sws-border);
  padding-top: var(--sws-space-4);
}

.result-output__title {
  margin: 0 0 0.35rem;
  font-size: 0.95rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.result-output--supporting > summary.result-output__title {
  cursor: pointer;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  /* `display: flex` stops a <summary> being `display: list-item`, which is
     the only display type that renders the native disclosure triangle — so
     the marker has to be replaced explicitly rather than merely inherited.
     Without this, a collapsed supporting output is indistinguishable on
     screen from a plain bold heading and nothing says it opens. Same
     three-rule pattern `.genome-more-details > summary` above already uses,
     for the same reason. */
  list-style: none;
}

.result-output--supporting > summary.result-output__title::-webkit-details-marker {
  display: none;
}

.result-output--supporting > summary.result-output__title::before {
  content: "▸";
  color: var(--sws-muted);
}

.result-output--supporting[open] > summary.result-output__title::before {
  content: "▾";
}

.result-output__meta {
  margin: 0 0 0.5rem;
  font-size: 0.82rem;
  overflow-wrap: anywhere;
}

.result-output--supporting .result-output__meta {
  margin: 0;
  font-weight: 500;
}

/* ── ADR-0172: the generic concern-grouped result interpretation ───── */
/* `concern-summary.js` is content-shape-driven and tool-agnostic; these
   rules select on the card's own state classes only, never a tool or an
   accession. The red accent is restrained by design: the status glyph and
   its short label carry the color, never a paragraph of body text -- and
   every status is legible by glyph and text label alone, never color only. */
.concern-summary {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
}

.concern-summary__status {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
}

.concern-summary__status-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
  font-size: 0.85rem;
  flex-shrink: 0;
}

.concern-summary--detected .concern-summary__status {
  color: var(--sws-danger);
}
.concern-summary--detected .concern-summary__status-glyph {
  background: var(--sws-danger-soft);
}

.concern-summary--none .concern-summary__status {
  color: var(--sws-success);
}
.concern-summary--none .concern-summary__status-glyph {
  background: var(--sws-success-soft);
}

.concern-summary--unavailable .concern-summary__status {
  color: var(--sws-neutral);
}
.concern-summary--unavailable .concern-summary__status-glyph {
  background: var(--sws-neutral-soft);
}

.concern-summary__intro {
  margin: 0;
}

.concern-summary__groups {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
}

.concern-summary__group {
  border-top: 1px solid var(--sws-border);
  padding-top: var(--sws-space-3);
}

.concern-summary__concern {
  margin: 0 0 0.2rem;
  font-size: 0.95rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.concern-summary__count {
  margin: 0 0 0.2rem;
}

.concern-summary__evidence {
  margin: 0;
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

.checkv-summary-section {
  border-top: 1px solid var(--sws-border);
  padding-top: var(--sws-space-4);
}

.checkv-summary {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
}

.checkv-summary__status-label {
  font-weight: 700;
}

.checkv-summary--succeeded .checkv-summary__status-label {
  color: var(--sws-success);
}

.checkv-summary--pending .checkv-summary__status-label,
.checkv-summary--unavailable .checkv-summary__status-label {
  color: var(--sws-neutral);
}

.checkv-summary__contig {
  border-top: 1px solid var(--sws-border);
  padding-top: var(--sws-space-3);
}

.checkv-summary__contig-id {
  margin: 0 0 var(--sws-space-2);
  font-size: 0.95rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.result-output__body {
  min-width: 0;
}

.result-output__actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* ── ADR-0182: locus typing summary + candidate ranking ───────────── */
/* Both blocks are content-shape-driven and tool-agnostic; these rules
   select on the blocks' own classes only, never a tool, an application, or
   a value. Nothing here colours a field by its content: there is no rule
   that reads a confidence string, a coverage figure, or a rank, because a
   colour applied on those would be this stylesheet making a scientific
   claim the renderer deliberately refuses to make. */
.locus-typing,
.candidate-ranking {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
}

.locus-typing__heading,
.candidate-ranking__heading {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
}

.locus-typing__records {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
}

.locus-typing__record + .locus-typing__record {
  border-top: 1px solid var(--sws-border);
  padding-top: var(--sws-space-3);
}

.locus-typing__record-heading {
  margin: 0 0 0.3rem;
  font-size: 0.9rem;
  font-weight: 700;
}

/* A two-column definition list on a comfortable viewport, stacking to one
   column when the label column would squeeze the value into a shard. */
.locus-typing__fields {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: 0.35rem var(--sws-space-4);
  margin: 0;
}

.locus-typing__label {
  margin: 0;
  color: var(--sws-muted);
}

.locus-typing__value {
  margin: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.locus-typing__provenance,
.locus-typing__note {
  margin: 0;
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

.locus-typing__caveat {
  margin: 0;
  font-size: 0.85rem;
  color: var(--sws-muted);
}

/* The shortlist warning sits above the chart and is the one emphasised
   line in the block -- weight and a neutral rule, never a colour that would
   read as a severity. */
.candidate-ranking__warning {
  margin: 0;
  font-weight: 700;
  border-left: 3px solid var(--sws-border);
  padding-left: var(--sws-space-3);
}

.candidate-ranking__subject {
  margin: 0;
}

/* Stated only when the display cut fell inside an exact score tie. Carries
   the same weight as the subject line rather than a severity colour: it is a
   fact about what the drawn bars are, not a warning about the result. */
.candidate-ranking__tie {
  margin: 0;
  font-weight: 600;
}

.candidate-ranking__note {
  margin: 0;
  font-size: 0.85rem;
}

/* Height scales with the ten bars the chart draws; the runtime resizes
   itself from this box via its own `ResizeObserver`. */
.candidate-ranking__chart {
  width: 100%;
  height: 22rem;
}

@media (max-width: 640px) {
  .locus-typing__fields {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.1rem;
  }

  .locus-typing__label {
    font-size: 0.85rem;
  }

  .candidate-ranking__chart {
    height: 18rem;
  }
}

/* An inline image preview is a *look at the result*, not the result: bound
   its height so a tall heatmap cannot push every other output of the same
   tool off the first screen. Deliberately scoped to `.result-output` (the
   genome page's own container) rather than applied to `.result-preview__image`
   globally, so the job- and example-result cards keep exactly the
   presentation they shipped with. `width: auto` alongside `max-height` is
   what preserves the aspect ratio while either bound binds. */
.result-output .result-preview__image {
  max-height: 26rem;
  width: auto;
  max-width: 100%;
}

.result-output .result-preview__enlarge-trigger {
  cursor: zoom-in;
}

/* The truncation/fallback notes every bounded renderer emits ("Showing the
   first 200 of 4,812 rows…", "Preview unavailable…"). Always visible, never
   styled as an error: a truncated preview is the normal, expected outcome
   for a large scientific result, and the Download beside it is the answer. */
.result-preview__note {
  margin: 0.5rem 0 0;
  font-size: 0.82rem;
}

.result-preview__text {
  margin: 0;
  padding: var(--sws-space-4);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-sm);
  background: var(--sws-surface-sunken);
  font-size: 0.82rem;
  line-height: 1.5;
  max-height: 22rem;
  overflow: auto;
  /* Long unbroken tokens (a digest, a URL, a one-line JSON document) must
     wrap inside this box rather than widening the page — this page must
     have zero horizontal overflow at 390px. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.genome-public-note {
  margin-top: var(--sws-space-6);
}

@media (max-width: 640px) {
  .genome-hero {
    flex-direction: column;
    align-items: flex-start;
  }

  .analysis-overview-header {
    flex-direction: column;
  }

  .analysis-row__body {
    flex-direction: column;
    align-items: flex-start;
  }

  /* `flex: 1 1 <px>`'s basis is a *width* in the desktop row layout above;
     left alone here it would be read as a *height* once `.analysis-row__body`
     switches to `flex-direction: column`, stretching each of these to a
     fixed-looking box many lines tall. `flex: auto` makes every child size
     to its own content in both layouts. */
  .analysis-row__summary-line,
  .analysis-row__summary {
    flex: auto;
    width: 100%;
  }

  .analysis-row__actions {
    margin-left: 0;
    width: 100%;
  }
}

/* ── SWS taxonomy enrichment Phase 4d: NCBI <-> classification comparison ──
   No red/green token anywhere below — a `differs` verdict uses the
   platform's neutral accent colour, never `--sws-danger`/`--sws-success`,
   because neither authority is "wrong" (design report §7 rule 1). Every
   verdict carries its own visible text label (never colour/glyph alone),
   matching `.analysis-status`'s own accessibility discipline. */
/* Phase 4e: the comparison is now this genome's primary scientific result,
   rendered at page level (`.genome-taxonomy-comparisons`), directly after
   the fact grid and before "Analysis overview" -- never nested inside a
   tool's own result card any longer. */
.genome-taxonomy-comparisons {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-4);
  margin-bottom: var(--sws-space-4);
}

.taxonomy-comparison {
  flex: 1 1 100%;
  width: 100%;
  min-width: 0;
  margin-top: var(--sws-space-2);
  padding-top: var(--sws-space-4);
  border-top: 1px solid var(--sws-border);
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-2);
}

/* The first comparison card sits directly under the section's own "Taxonomy"
   heading -- no redundant top border/margin between the two. */
.genome-taxonomy-comparisons > h2 + .taxonomy-comparison {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* The overall, whole-genome status (e.g. "Not classified by <tool>") --
   distinct from each row's own per-rank verdict, and never colour-only. */
.taxonomy-comparison__status {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--sws-muted-strong);
}

.taxonomy-comparison__heading {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
}

.taxonomy-comparison__intro {
  margin: 0;
  font-size: 0.85rem;
}

.taxonomy-comparison__table-wrap {
  /* A comparison table can outgrow a narrow viewport; scrolling the table
     itself (never the page) keeps every other section reachable without
     horizontal drift. */
  overflow-x: auto;
}

.taxonomy-comparison__table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}

.taxonomy-comparison__table th,
.taxonomy-comparison__table td {
  padding: 0.4rem 0.6rem;
  text-align: left;
  border-bottom: 1px solid var(--sws-border);
  overflow-wrap: anywhere;
}

.taxonomy-comparison__table thead th {
  font-weight: 700;
  color: var(--sws-muted-strong);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.taxonomy-comparison__row--differs {
  background: var(--sws-warning-soft);
  border-left: 3px solid var(--sws-warning);
}

.taxonomy-comparison__row--differs th {
  font-weight: 700;
}

.taxonomy-comparison__verdict {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  white-space: nowrap;
}

.taxonomy-comparison__row--same .taxonomy-comparison__verdict {
  color: var(--sws-muted-strong);
}

.taxonomy-comparison__row--differs .taxonomy-comparison__verdict {
  color: var(--sws-warning);
  font-weight: 700;
}

.taxonomy-comparison__row--only-reference .taxonomy-comparison__verdict,
.taxonomy-comparison__row--only-classification .taxonomy-comparison__verdict {
  color: var(--sws-info);
}

.taxonomy-comparison__row--absent-both .taxonomy-comparison__verdict,
.taxonomy-comparison__row--unclassified .taxonomy-comparison__verdict {
  color: var(--sws-muted);
}

.taxonomy-comparison__summary,
.taxonomy-comparison__note,
.taxonomy-comparison__provenance p {
  margin: 0;
  font-size: 0.82rem;
}

.taxonomy-comparison__note {
  color: var(--sws-muted-strong);
}

.taxonomy-comparison__unclassified {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.taxonomy-comparison__unclassified p {
  margin: 0;
  font-size: 0.85rem;
}

.taxonomy-comparison__provenance {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding-top: var(--sws-space-2);
  border-top: 1px dashed var(--sws-border);
}

.taxonomy-comparison__provenance p {
  color: var(--sws-muted);
}

.taxonomy-comparison__provenance a {
  color: inherit;
}

/* ── ADR-0164: the Genome Library index ────────────────────────────── */
/* The public index of every genome the detail page above renders one of.
   Deliberately built from the same tokens, the same `.analysis-status--*`
   status chips, and the same card idiom as `.service-card` — a visitor
   moving between the catalogue, the library, and a genome must feel one
   product, not three. Nothing here selects on a genome, an accession or a
   tool identity; every rule is generic. */

.genome-library-hero {
  display: flex;
  gap: var(--sws-space-4);
  align-items: flex-start;
}

.genome-library-hero__icon {
  font-size: 2.25rem;
  line-height: 1;
  flex-shrink: 0;
}

.genome-library-total {
  margin: var(--sws-space-3) 0 0;
  font-size: 0.9rem;
  font-weight: 600;
}

.genome-library-search {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-2);
  margin-bottom: var(--sws-space-6);
}

.genome-library-search__label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--sws-muted-strong);
}

.genome-library-search__controls {
  display: flex;
  gap: var(--sws-space-2);
  flex-wrap: wrap;
}

/* `.button`'s own `display: inline-flex` outranks the UA's
   `[hidden] { display: none }`, so the Clear control stayed laid out — and
   focusable, and in the tab order — while its `hidden` property was true.
   Found in real-browser keyboard verification, not by any unit test: jsdom
   reports `hidden` faithfully and computes no cascade. Restated here, at
   the same specificity as `.button`. */
.genome-library-search .button[hidden] {
  display: none;
}

.genome-library-search__input {
  flex: 1 1 20rem;
  min-width: 0;
  padding: 0.6rem 0.85rem;
  font: inherit;
  color: var(--sws-text);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border-strong);
  border-radius: var(--sws-radius-sm);
  transition: border-color var(--sws-motion-fast) var(--sws-ease),
    box-shadow var(--sws-motion-fast) var(--sws-ease);
}

.genome-library-search__input:focus-visible {
  outline: none;
  border-color: var(--sws-brand);
  box-shadow: 0 0 0 3px var(--sws-brand-soft);
}

.genome-library-results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  gap: 1.25rem;
}

.genome-library-skeleton {
  min-height: 11rem;
}

/* The empty-library and no-results states span the whole grid rather than
   sitting in one narrow column — a single centred sentence inside a
   260px-wide grid cell reads as a broken card, not as a message. */
.genome-library-empty {
  grid-column: 1 / -1;
  padding: var(--sws-space-8) var(--sws-space-6);
  text-align: center;
  background: var(--sws-surface-sunken);
  border: 1px dashed var(--sws-border-strong);
  border-radius: var(--sws-radius-lg);
}

.genome-library-empty p {
  margin: 0 0 var(--sws-space-2);
}

.genome-library-empty p:last-child {
  margin-bottom: 0;
}

/* One genome, as a whole-card link — `.service-card`'s idiom, so the
   library and the catalogue lift and shadow identically on hover. */
.genome-row {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 1.25rem;
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  text-decoration: none;
  color: inherit;
  box-shadow: var(--sws-shadow-sm);
  transition: transform var(--sws-motion-med) var(--sws-ease), box-shadow var(--sws-motion-med) var(--sws-ease),
    border-color var(--sws-motion-med) var(--sws-ease);
}

.genome-row:hover {
  transform: translateY(-3px);
  box-shadow: var(--sws-shadow-lg);
  border-color: var(--sws-brand-soft);
}

/* An explicit, high-contrast focus ring on the card itself: the whole row
   is one tab stop, so the keyboard-only visitor must be able to see exactly
   which genome they are about to open. */
.genome-row:focus-visible {
  outline: 3px solid var(--sws-brand);
  outline-offset: 2px;
}

.genome-row__heading {
  display: flex;
  align-items: center;
  gap: var(--sws-space-2);
  flex-wrap: wrap;
}

.genome-row__accession {
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.genome-row__drift {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.15rem 0.45rem;
  border-radius: 999px;
  color: var(--sws-danger);
  background: var(--sws-danger-soft);
}

.genome-row__organism,
.genome-row__title,
.genome-row__facts,
.genome-row__retrieved {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.45;
}

.genome-row__retrieved {
  font-size: 0.78rem;
  margin-top: auto;
  padding-top: var(--sws-space-2);
}

.genome-row__availability {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sws-space-2);
  margin: var(--sws-space-2) 0 0;
  font-size: 0.85rem;
}

/* The counted variant of the shared status chip (`analysis-status.js`'s
   `renderStatusCount`) — same colours and glyphs as the genome page's own
   per-tool indicators, just compact enough to sit several to a row. */
.analysis-status--count {
  font-size: 0.78rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: var(--sws-neutral-soft);
}

.genome-library-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sws-space-4);
  flex-wrap: wrap;
  margin-top: var(--sws-space-6);
}

.genome-library-pagination__status {
  margin: 0;
  font-size: 0.85rem;
}

.genome-library-pagination__controls {
  display: flex;
  gap: var(--sws-space-2);
}

/* The genome detail page's two sibling links (back to the catalogue,
   across to the library) — one row on wide screens, stacked on narrow. */
.genome-crumbs {
  display: flex;
  gap: var(--sws-space-4);
  flex-wrap: wrap;
  align-items: center;
}

/* ── ADR-0164 Phase 4c: the library's NCBI Taxonomy filter ─────────── */
/* Chips, never a select or a text field: the vocabulary is small, live, and
   entirely server-supplied, and a visitor filtering by taxon is choosing from
   what exists rather than spelling something. Every chip is a real
   `<button>`, so keyboard focus, Enter/Space activation and screen-reader
   announcement come from the platform rather than from this stylesheet. */

.genome-library-taxonomy {
  margin: 0 0 var(--sws-space-6);
  padding: var(--sws-space-4);
  background: var(--sws-surface-sunken);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
}

.genome-library-taxonomy__heading {
  display: flex;
  gap: var(--sws-space-3);
  align-items: baseline;
  flex-wrap: wrap;
}

.genome-library-taxonomy__heading h2 {
  margin: 0;
  font-size: 1rem;
  flex: 1;
  min-width: 0;
}

/* The disclosure control for the whole panel. Reuses the three-rule chevron
   trick `.genome-more-details > summary` established (see that rule), swapped
   from `[open]` to `[aria-expanded="true"]` because this is a real `<button>`,
   not a `<summary>` — the panel also needs an always-visible strip of
   removable chips next to it, which a native `<details>` cannot host without
   its own click toggling the disclosure. Spans the full row and splits its
   label and hint apart with `space-between` so the hint (below) can sit at
   the row's far end without being a second, nested interactive element —
   it's still one `<button>`, just with two visual halves. */
.genome-library-taxonomy__toggle-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.4rem var(--sws-space-3);
  width: 100%;
  padding: 0;
  font: inherit;
  font-size: 1rem;
  font-weight: 700;
  color: var(--sws-text);
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
}

.genome-library-taxonomy__toggle-label {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
}

.genome-library-taxonomy__toggle-label::before {
  content: "▸";
  font-size: 0.8em;
  color: var(--sws-muted-strong);
}

.genome-library-taxonomy__toggle-header[aria-expanded="true"] .genome-library-taxonomy__toggle-label::before {
  content: "▾";
}

.genome-library-taxonomy__toggle-header:focus-visible {
  outline: 2px solid var(--sws-brand);
  outline-offset: 2px;
}

/* States, not a count of taxa — "active" never pluralises regardless of how
   many filters that covers, matching the collapsed-header copy in the page's
   own docstring (`· 1 active`, `· 2 active`). */
.genome-library-taxonomy__active-badge {
  font-weight: 600;
  color: var(--sws-brand);
}

/* The collapsed, no-filters-yet call to action — the disclosure arrow alone
   was too easy to miss. Purely a visual affordance within the single
   `<button>` above, not a second control: hidden the instant the panel
   expands (its own controls take over explaining the section) or a filter
   becomes active (the summary chips are the more important thing then). */
.genome-library-taxonomy__hint {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--sws-brand);
}

.genome-library-taxonomy__hint[hidden] {
  display: none;
}

.genome-library-taxonomy__hint-icon {
  display: inline-flex;
  flex-shrink: 0;
}

/* The collapsed panel's own copy of each applied filter, so a visitor never
   loses sight of a taxonomy filter just because the panel that offered it is
   closed. Wraps rather than truncates for the same reason
   `.genome-library-taxon-chip__label` does. */
.genome-library-taxonomy__summary-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sws-space-2);
  align-items: center;
  margin-top: var(--sws-space-2);
}

.genome-library-taxonomy-summary-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  max-width: 100%;
  padding: 0.2rem 0.6rem;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--sws-text);
  background: var(--sws-brand-soft);
  border: 1px solid var(--sws-brand);
  border-radius: 999px;
}

.genome-library-taxonomy-summary-chip__label {
  min-width: 0;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.genome-library-taxonomy-summary-chip__rank {
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--sws-muted-strong);
}

.genome-library-taxonomy-summary-chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.2rem;
  height: 1.2rem;
  padding: 0;
  font: inherit;
  line-height: 1;
  color: inherit;
  background: transparent;
  border: none;
  border-radius: 999px;
  cursor: pointer;
}

.genome-library-taxonomy-summary-chip__remove:hover {
  background: var(--sws-surface);
}

.genome-library-taxonomy-summary-chip__remove:focus-visible {
  outline: 2px solid var(--sws-brand);
  outline-offset: 1px;
}

.genome-library-taxonomy__body {
  margin-top: var(--sws-space-3);
}

/* ADR-0176: the section heading and the first group's own heading are both
   bold and only 1.6px apart in size, so with no separation they read as one
   two-line label rather than as a section and its first group. Measured at
   0px before this rule, against 16px + a rule between the two groups — the
   first group was *less* separated from the section heading than the second
   group was from the first. */
.genome-library-taxonomy__heading {
  margin-bottom: var(--sws-space-3);
}

/* ADR-0176: the section now holds two authorities, so each is its own
   `role="group"` with its own `<h3>`, its own note, and its own fold. They
   are separated by a rule and vertical space rather than by colour: two
   headings that each name their own authority already say which is which,
   and inventing a "classification colour" would encode provenance in
   something a colour-blind reader cannot see and a screen reader cannot
   read at all. Chips look identical in both groups on purpose — a chip's
   meaning comes from the group it is in, and making them look different
   would suggest the *chips* differ in kind rather than in source. */
.genome-library-taxonomy__group + .genome-library-taxonomy__group {
  margin-top: var(--sws-space-4);
  padding-top: var(--sws-space-4);
  border-top: 1px solid var(--sws-border);
}

.genome-library-taxonomy__group h3 {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* The one-line hint under the search field: says what the field matches, so
   the label itself can stay short (and honest) at "Search genomes". */
.genome-library-search__hint {
  margin: var(--sws-space-2) 0 0;
  font-size: 0.8rem;
}

/* The applied-filter list an empty result set names, each entry prefixed by
   its own authority. A list rather than a sentence: with three independent
   filters the sentence permutations outnumber the information in them. */
.genome-library-empty__filters {
  margin: var(--sws-space-2) 0;
  padding-left: 1.1rem;
  font-size: 0.85rem;
}

/* The authority label, restated in the same idiom as the genome detail
   page's `.genome-taxonomy__authority`. Retained for any consumer that still
   renders it; the library's own groups now carry their authority in the
   group heading instead. */
.genome-library-taxonomy__authority {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sws-muted-strong);
}

.genome-library-taxonomy__note {
  margin: var(--sws-space-2) 0 var(--sws-space-3);
  font-size: 0.85rem;
}

.genome-library-taxonomy__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sws-space-2);
  align-items: center;
}

.genome-library-taxonomy__unavailable {
  margin: 0;
  font-size: 0.85rem;
}

.genome-library-taxon-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  /* `max-width` + wrapping, not `nowrap` + ellipsis: an NCBI scientific name
     can be genuinely long, and a chip row that could push the page wider than
     the viewport is a horizontal-scroll bug waiting for a 390px screen. */
  max-width: 100%;
  padding: 0.3rem 0.7rem;
  font: inherit;
  font-size: 0.85rem;
  color: var(--sws-text);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border-strong);
  border-radius: 999px;
  cursor: pointer;
  transition: border-color var(--sws-motion-fast) var(--sws-ease),
    background var(--sws-motion-fast) var(--sws-ease);
}

.genome-library-taxon-chip:hover {
  border-color: var(--sws-brand);
}

.genome-library-taxon-chip:focus-visible {
  outline: 2px solid var(--sws-brand);
  outline-offset: 2px;
}

/* An NCBI scientific name is bounded (256 bytes) but not short, and a
   hostile one is deliberately not short at all. Two lines, then ellipsis:
   the chip stays a chip, the row stays a row, and the full text is still
   reachable — it is carried in the element's `title`, an attribute rather
   than markup. Wrapping is `anywhere` so a single unbroken 100-character
   "word" breaks instead of widening the page. */
.genome-library-taxon-chip__label {
  min-width: 0;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.genome-library-taxon-chip__count,
.genome-library-taxon-chip__rank {
  font-size: 0.75rem;
  color: var(--sws-muted-strong);
}

/* The applied filter, visually distinct from the selectable ones and always
   drawn first — a filter a visitor cannot see is one they cannot remove. */
.genome-library-taxon-chip--selected {
  cursor: default;
  color: var(--sws-text);
  background: var(--sws-brand-soft);
  border-color: var(--sws-brand);
  font-weight: 600;
}

.genome-library-taxon-chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  padding: 0;
  font: inherit;
  line-height: 1;
  color: inherit;
  background: transparent;
  border: none;
  border-radius: 999px;
  cursor: pointer;
}

.genome-library-taxon-chip__remove:hover {
  background: var(--sws-surface);
}

.genome-library-taxon-chip__remove:focus-visible {
  outline: 2px solid var(--sws-brand);
  outline-offset: 1px;
}

.genome-library-taxonomy__toggle {
  padding: 0.3rem 0.5rem;
  font: inherit;
  font-size: 0.85rem;
  color: var(--sws-brand);
  background: none;
  border: none;
  cursor: pointer;
  text-decoration: underline;
}

.genome-library-empty .button {
  margin-top: var(--sws-space-2);
}

@media (max-width: 640px) {
  .genome-library-hero {
    flex-direction: column;
  }

  .genome-library-results {
    grid-template-columns: 1fr;
  }

  /* Both controls become full-width rows under the input rather than three
     cramped items sharing one line. */
  .genome-library-search__controls > .button {
    flex: 1 1 8rem;
  }

  .genome-library-pagination {
    flex-direction: column;
    align-items: stretch;
  }

  .genome-library-pagination__controls > .button {
    flex: 1 1 0;
  }
}

/* ── Platform activity (ADR-0185) ──────────────────────────────────── */
/* The landing page's activity panel and the `/statistics` page share this
   block. Both are built entirely from the existing design tokens, so the
   dark-theme blocks at the top of this file recolour them with no
   per-theme rule of their own — the same discipline every other component
   here follows. Nothing below animates: ADR-0185 requires the values to be
   plain, static, accessible text, so there is no transition to disable
   under `prefers-reduced-motion`. */

.activity-panel {
  margin-top: var(--sws-space-8);
  padding: var(--sws-space-6);
  background: var(--sws-surface);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  box-shadow: var(--sws-shadow-sm);
}

/* Collapsed until the first response lands, so the landing page does not
   reserve a visible empty box for a panel that may never render. */
.activity-panel--loading {
  display: none;
}

.activity-panel__title {
  margin: 0 0 var(--sws-space-4);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sws-muted);
}

.activity-panel__metrics {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sws-space-6);
}

.activity-metric {
  display: flex;
  flex-direction: column;
  min-width: 6.5rem;
}

.activity-metric__value {
  font-size: 1.75rem;
  font-weight: 650;
  line-height: 1.1;
  color: var(--sws-text);
  /* Tabular figures so a value refreshing from 9 to 10 does not shift the
     labels beside it. */
  font-variant-numeric: tabular-nums;
}

.activity-metric__label {
  margin-top: var(--sws-space-1);
  font-size: 0.8rem;
  line-height: 1.3;
  color: var(--sws-muted);
  max-width: 8rem;
}

.activity-panel__provenance {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: var(--sws-space-4) 0 0;
}

.activity-panel__note {
  font-size: 0.78rem;
  color: var(--sws-muted);
}

.activity-panel__link {
  display: inline-block;
  margin-top: var(--sws-space-4);
  font-size: 0.85rem;
  font-weight: 550;
  color: var(--sws-brand);
  text-decoration: none;
}

.activity-panel__link:hover,
.activity-panel__link:focus-visible {
  text-decoration: underline;
}

/* ── /statistics page ──────────────────────────────────────────────── */

.stat-epoch {
  margin: 0 0 var(--sws-space-6);
  color: var(--sws-muted);
}

.stat-section {
  margin-top: var(--sws-space-8);
}

.stat-section > h2 {
  margin: 0 0 var(--sws-space-4);
  font-size: 1.1rem;
}

.stat-live {
  margin: 0;
  font-size: 1.35rem;
  font-weight: 600;
  color: var(--sws-text);
  font-variant-numeric: tabular-nums;
}

.stat-live__sep {
  color: var(--sws-muted);
}

.stat-figures {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--sws-space-4);
}

.stat-figure {
  display: flex;
  flex-direction: column;
  padding: var(--sws-space-4);
  background: var(--sws-surface-sunken);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-md);
}

.stat-figure__value {
  font-size: 1.6rem;
  font-weight: 650;
  line-height: 1.15;
  color: var(--sws-text);
  font-variant-numeric: tabular-nums;
}

.stat-figure__label {
  margin-top: var(--sws-space-1);
  font-size: 0.85rem;
  color: var(--sws-muted-strong);
}

.stat-figure__hint {
  margin-top: 2px;
  font-size: 0.75rem;
  color: var(--sws-muted);
}

.stat-note {
  margin-top: var(--sws-space-4);
  padding: var(--sws-space-4);
  background: var(--sws-surface-sunken);
  border-left: 3px solid var(--sws-border-strong);
  border-radius: var(--sws-radius-sm);
}

.stat-note h3 {
  margin: 0 0 var(--sws-space-2);
  font-size: 0.9rem;
}

.stat-note p {
  margin: 0 0 var(--sws-space-2);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--sws-muted-strong);
}

.stat-note p:last-child {
  margin-bottom: 0;
}

.stat-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.stat-table th,
.stat-table td {
  padding: var(--sws-space-3) var(--sws-space-2);
  text-align: right;
  border-bottom: 1px solid var(--sws-border);
}

.stat-table thead th,
.stat-table tbody th {
  text-align: left;
  font-weight: 550;
}

.stat-table thead th {
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sws-muted);
}

.stat-tool-versions {
  margin-top: 0.15rem;
  font-size: 0.8rem;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

/* A suppressed cell is dimmed and carries a help cursor, but the "<5" text
   is never the only signal — `statistics-page.js` also attaches the reason
   as `title` and as `.sr-only` text. */
.stat-cell--suppressed {
  color: var(--sws-muted);
  cursor: help;
}

.stat-definitions {
  margin: 0;
}

.stat-definitions dt {
  font-weight: 600;
  font-size: 0.9rem;
}

.stat-definitions dd {
  margin: 2px 0 var(--sws-space-3);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--sws-muted-strong);
}

@media (max-width: 640px) {
  .activity-panel {
    padding: var(--sws-space-4);
  }

  .activity-panel__metrics {
    gap: var(--sws-space-4);
  }

  .activity-metric__value {
    font-size: 1.4rem;
  }
}

/* Declared-screening-panel presentation (`concern-summary.js`'s
   `renderDeclaredScreeningCard`). Every selector here is a state or a
   structural class of the card itself -- no rule names a tool, a category or
   a database, because none of those are known to this stylesheet any more
   than they are to the renderer: they arrive at runtime from the producing
   version's own declared contract. */
.concern-summary__panel-title,
.concern-summary__category-title,
.concern-summary__why-title,
.concern-summary__limitation-title {
  margin: 0 0 0.35rem;
}

.concern-summary__scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.concern-summary__panel-table,
.concern-summary__evidence-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
}

.concern-summary__panel-table th,
.concern-summary__panel-table td,
.concern-summary__evidence-table th,
.concern-summary__evidence-table td {
  text-align: left;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--sws-border);
  vertical-align: top;
  white-space: nowrap;
}

.concern-summary__panel-table th[scope="row"] {
  font-weight: 600;
  white-space: normal;
}

.concern-summary__panel-table tbody tr.is-clean td {
  color: var(--sws-success);
}
.concern-summary__panel-table tbody tr.is-positive td {
  color: var(--sws-danger);
}

.concern-summary__glyph {
  display: inline-block;
  width: 1.2em;
  flex-shrink: 0;
}

.concern-summary__category {
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-sm);
  padding: var(--sws-space-3);
  background: var(--sws-surface-sunken);
}

.concern-summary__categories {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-3);
}

.concern-summary__category-title {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--sws-danger);
}

.concern-summary__category--undeclared .concern-summary__category-title {
  color: var(--sws-neutral);
}

.concern-summary__category-count {
  margin: 0 0 0.5rem;
}

.concern-summary__why {
  margin: 0 0 0.6rem;
}

.concern-summary__why p {
  margin: 0;
}

.concern-summary__evidence-details > summary {
  cursor: pointer;
  font-weight: 600;
}

/* The scope caveat is informational, never a failure: a neutral surface and
   a neutral left rule, deliberately sharing nothing with the danger
   treatment above so it can never read as a detected concern. */
.concern-summary__limitation {
  border-left: 3px solid var(--sws-border-strong);
  background: var(--sws-neutral-soft);
  border-radius: var(--sws-radius-sm);
  padding: var(--sws-space-3);
  color: var(--sws-muted-strong);
}

.concern-summary__limitation-body {
  margin: 0;
}

.concern-summary__undeclared-list {
  margin: 0.4rem 0 0;
  padding-left: 1.2rem;
}

/* ── Per-tool statistics (ADR-0185 Revision 2) ─────────────────────── */
/* Built from the same tokens as the rest of the statistics block, so the
   dark-theme definitions at the top of this file cover these too. */

/* The tool name in the "By scientific tool" table is a real link: it carries
   ordinary link colour, it underlines on hover, and it keeps the shared
   focus-visible treatment. The chevron is decoration (aria-hidden) and never
   the only affordance. */
.stat-tool-link {
  color: var(--sws-brand);
  text-decoration: none;
  font-weight: 550;
}

.stat-tool-link:hover,
.stat-tool-link:focus-visible {
  text-decoration: underline;
}

.stat-tool-link__chevron {
  margin-left: 0.35em;
  color: var(--sws-muted);
}

.stat-tool-link:hover .stat-tool-link__chevron {
  color: var(--sws-brand);
}

.stat-breadcrumb {
  margin: 0 0 var(--sws-space-2);
  font-size: 0.85rem;
}

.stat-breadcrumb a {
  color: var(--sws-muted-strong);
  text-decoration: none;
}

.stat-breadcrumb a:hover,
.stat-breadcrumb a:focus-visible {
  text-decoration: underline;
}

.stat-tool-links {
  margin: 0 0 var(--sws-space-6);
  font-size: 0.9rem;
}

.stat-tool-links a {
  color: var(--sws-brand);
  text-decoration: none;
  font-weight: 550;
}

.stat-tool-links a:hover,
.stat-tool-links a:focus-visible {
  text-decoration: underline;
}

/* A sequence figure and its coverage qualifier stack in one cell, so a
   lower bound is never mistaken for an exact total at a glance. */
.stat-seq {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.25;
}

.stat-seq__detail {
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--sws-muted);
  white-space: nowrap;
}

.stat-version {
  font-variant-numeric: tabular-nums;
}

.stat-badge {
  display: inline-block;
  margin-left: var(--sws-space-2);
  padding: 1px 7px;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--sws-muted-strong);
  background: var(--sws-neutral-soft);
  border-radius: 999px;
  vertical-align: middle;
}

.stat-badge--current {
  color: var(--sws-brand-strong);
  background: var(--sws-brand-soft);
}

.stat-note--inline {
  margin-top: var(--sws-space-4);
  padding: var(--sws-space-3) var(--sws-space-4);
  font-size: 0.82rem;
}

.stat-table--versions th[scope="row"] {
  white-space: nowrap;
}

/* The badge stays visible at every width: "Latest" is information, not
   decoration, and a reader on a phone needs it as much as anyone. Only its
   spacing tightens. */
@media (max-width: 640px) {
  .stat-badge {
    margin-left: var(--sws-space-1);
    padding: 1px 5px;
  }
}

/* ── Completion-notification enrolment (status page) ───────────────────
   The "email me when this finishes" panel (`completion-notification.js`).

   Built entirely from the design tokens at the top of this file, so it
   inherits light/dark automatically and needs no `prefers-color-scheme`
   block of its own — the tokens are already redefined per theme, and a
   second, panel-specific dark block is exactly the kind of duplicate that
   drifts out of step with the first.

   Deliberately quiet: a bordered surface, not a callout. This is an
   optional offer on an anonymous-first service, and giving it the visual
   weight of a warning would make it read as something the visitor has to
   deal with. */

.completion-notification {
  margin-top: var(--sws-space-6);
  padding: var(--sws-space-4) var(--sws-space-6);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-md);
  background: var(--sws-surface);
}

.completion-notification__heading {
  margin: 0 0 var(--sws-space-1);
  font-size: 1rem;
}

.completion-notification__lead {
  margin: 0 0 var(--sws-space-3);
  color: var(--sws-muted-strong);
}

/* The one visual difference the elapsed-wait threshold makes: a brand-tinted
   left edge. Emphasis only — never a colour that reads as an error, because
   a long queue is normal and this panel is not reporting a problem. */
.completion-notification--extended {
  border-left: 3px solid var(--sws-brand);
}

.completion-notification__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sws-space-2);
  margin-top: var(--sws-space-1);
}

/* The input takes the free space; the button keeps its intrinsic width.
   `min-width` stops the field collapsing to nothing in a narrow column
   before the breakpoint below takes over. */
.completion-notification__row > .text-input {
  flex: 1 1 16rem;
  min-width: 0;
}

.completion-notification__error:empty {
  /* Mounted permanently so a screen reader announces text arriving into the
     live region (see the module's own note), but contributing no margin
     while it is empty — otherwise every ordinary render would carry a blank
     gap below the field. */
  display: none;
}

.completion-notification__error {
  margin: var(--sws-space-2) 0 0;
  color: var(--sws-danger);
  font-size: 0.9rem;
}

.completion-notification__status {
  display: flex;
  align-items: center;
  gap: var(--sws-space-2);
  margin: 0 0 var(--sws-space-1);
  font-weight: 600;
}

/* Decorative only — `aria-hidden` in the markup, and every glyph is paired
   with real text, so the state is legible by colour, by glyph and to a
   screen reader, never by any one of those alone (the same discipline
   `analysis-status.js`'s indicators follow). */
.completion-notification__glyph {
  color: var(--sws-brand);
}

.completion-notification--active .completion-notification__glyph {
  color: var(--sws-success);
}

@media (max-width: 640px) {
  .completion-notification {
    padding: var(--sws-space-4);
  }

  /* Field and button become full-width rows rather than sharing one cramped
     line — the same treatment the genome-library search controls get. */
  .completion-notification__row > .text-input,
  .completion-notification__row > .button {
    flex: 1 1 100%;
  }
}

/* The "Resend confirmation" control and its restrained feedback line, shown
   only while an associated address is still unconfirmed. Same token-only
   discipline as the block above — no theme block of its own. */
.completion-notification__resend {
  margin-top: var(--sws-space-3);
}

.completion-notification__resend-status:empty {
  /* Permanently mounted so a screen reader announces text arriving into the
     live region, but contributing no margin while empty. */
  display: none;
}

.completion-notification__resend-status {
  margin: var(--sws-space-2) 0 0;
  color: var(--sws-muted-strong);
  font-size: 0.9rem;
}

@media (max-width: 640px) {
  .completion-notification__resend > .button {
    width: 100%;
  }
}

/* ---------------------------------------------------------------------- */
/* Resource Intelligence (ADR-0188)                                        */
/*                                                                          */
/* The candidate/submitted distinction is carried by STYLE as well as by    */
/* wording: a shadow candidate is always dashed, hollow and muted, and a    */
/* submitted value is always solid and emphasised. A reader who scans only  */
/* shape must still read "hypothetical" vs "real" correctly.                */
/* ---------------------------------------------------------------------- */
.ri-banner {
  border: 1px solid var(--sws-border);
  border-radius: 10px;
  padding: 1rem 1.25rem;
  margin-bottom: 1.25rem;
  background: var(--sws-surface);
}
.ri-banner__row { display: flex; flex-wrap: wrap; gap: 2rem; }
.ri-banner__item { display: flex; flex-direction: column; gap: 0.15rem; }
.ri-banner__label { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--sws-muted); }
.ri-banner__value { font-size: 1.5rem; line-height: 1.1; }
.ri-banner__item--on .ri-banner__value { color: var(--sws-success); }
.ri-banner__item--off .ri-banner__value { color: var(--sws-text); }
.ri-banner__item--danger .ri-banner__value { color: var(--sws-danger); }
.ri-banner__note { margin: 0.85rem 0 0; color: var(--sws-muted); font-size: 0.9rem; }

.ri-hero { border: 1px solid var(--sws-border); border-radius: 10px; padding: 1rem 1.25rem; margin: 1.25rem 0; }
.ri-hero__flow { list-style: none; margin: 0.75rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: stretch; }
.ri-hero__step { flex: 1 1 150px; border: 1px solid var(--sws-border); border-radius: 8px; padding: 0.6rem 0.75rem; display: flex; flex-direction: column; gap: 0.2rem; }
.ri-hero__label { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--sws-muted); }
.ri-hero__value { font-size: 1.15rem; }
/* Hypothetical: dashed and muted, never solid. */
.ri-hero__step--candidate { border-style: dashed; background: transparent; }
.ri-hero__step--candidate .ri-hero__value { color: var(--sws-muted); }
.ri-hero__step--refused .ri-hero__value { color: var(--sws-danger); font-weight: 700; }
/* Real allocation: solid and emphasised. */
.ri-hero__step--submitted { border-width: 2px; border-color: var(--sws-brand); }
.ri-hero__step--submitted .ri-hero__value { color: var(--sws-brand); font-weight: 700; }
.ri-hero__caveat { margin: 0.9rem 0 0; font-size: 0.9rem; color: var(--sws-muted); }

.ri-pipeline { margin: 1.25rem 0; }
.ri-pipeline__list { list-style: none; margin: 0.5rem 0 0; padding: 0; display: grid; gap: 0.4rem; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.ri-pipeline__step { display: flex; gap: 0.6rem; align-items: flex-start; padding: 0.5rem 0.6rem; border-left: 3px solid var(--sws-border); }
.ri-pipeline__index { font-variant-numeric: tabular-nums; color: var(--sws-muted); font-weight: 700; }
.ri-pipeline__detail { display: block; font-size: 0.82rem; color: var(--sws-muted); }

.ri-candidate-chip { display: inline-block; padding: 0.05rem 0.4rem; border: 1px dashed var(--sws-muted); border-radius: 999px; color: var(--sws-muted); font-size: 0.9em; }
.ri-cell--submitted strong { color: var(--sws-brand); }
.ri-unknown { color: var(--sws-muted); font-style: italic; }
.ri-verdict--definitely_unsafe_for_this_observation { color: var(--sws-danger); font-weight: 600; }
.ri-verdict--indeterminate_censored { color: var(--sws-warning); }
.ri-table td, .ri-table th { white-space: nowrap; }
.ri-cell--time { font-variant-numeric: tabular-nums; font-size: 0.85em; }

@media (max-width: 720px) {
  .ri-banner__row { gap: 1rem; }
  .ri-banner__value { font-size: 1.2rem; }
}

.ri-chart-hero-row { margin-bottom: 1rem; }
.ri-chart-hero-row .admin-chart-card { width: 100%; }
/* Taller than the grid cards: this is the chart a viewer reads first. */
.ri-chart--hero .admin-chart-card__chart,
.ri-chart--hero .chart-container { min-height: 300px; }

/* ---------------------------------------------------------------------
   ADR-0195: per-contig classification result view.

   Colour is never the sole carrier of a classification. Every badge states
   its class in words, every count is written out, and the composition bar
   carries an `aria-label` naming each class and its share -- the tint is a
   redundant second encoding for readers who can use it, the same discipline
   `.genome-library-taxonomy__group` states for its own chips. The three
   tones also differ in border weight, so they stay distinguishable in
   greyscale.
   --------------------------------------------------------------------- */

.contig-summary__title {
  margin: 0 0 var(--sws-space-3);
}

.contig-summary__total {
  margin: 0 0 var(--sws-space-2);
  font-size: 1.05rem;
  font-weight: 600;
}

.contig-summary__counts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sws-space-3);
  margin: 0 0 var(--sws-space-3);
  padding: 0;
  list-style: none;
}

.contig-summary__count {
  display: flex;
  align-items: baseline;
  gap: 0.4ch;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  border: 1px solid var(--sws-border);
  background: var(--sws-surface);
}

.contig-summary__count-value {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.contig-summary__count-label {
  color: var(--sws-muted);
  font-size: 0.9rem;
}

.contig-summary__count--phage {
  border-color: var(--sws-success);
}

.contig-summary__count--bacteria {
  border-color: var(--sws-danger);
}

.contig-summary__count--too-short,
.contig-summary__count--excluded,
.contig-summary__count--other {
  border-color: var(--sws-neutral);
}

.contig-summary__bar {
  display: flex;
  width: 100%;
  height: 0.75rem;
  margin-bottom: var(--sws-space-3);
  border-radius: 999px;
  overflow: hidden;
  background: var(--sws-border);
}

.contig-summary__bar-segment {
  flex-basis: 0;
  min-width: 2px;
}

.contig-summary__bar-segment--phage {
  background: var(--sws-success);
}

.contig-summary__bar-segment--bacteria {
  background: var(--sws-danger);
}

.contig-summary__bar-segment--too-short,
.contig-summary__bar-segment--other {
  background: var(--sws-neutral);
}

/* Excluded contigs are hatched rather than merely tinted: a solid neutral
   segment would read as a fourth classification, and this was never
   classified. The hatch is decoration only -- the count, the legend and the
   badge all state it in words. */
.contig-summary__bar-segment--excluded {
  background: repeating-linear-gradient(
    45deg,
    var(--sws-neutral),
    var(--sws-neutral) 3px,
    var(--sws-border) 3px,
    var(--sws-border) 6px
  );
}

.contig-summary__legend,
.contig-summary__renamed,
.contig-summary__excluded,
.contig-summary__truncated {
  margin: 0 0 var(--sws-space-3);
  font-size: 0.9rem;
}

.contig-summary__renamed {
  padding: var(--sws-space-2) var(--sws-space-3);
  border-left: 3px solid var(--sws-accent);
  background: var(--sws-bg);
}

/* ---- Per-contig score graph -------------------------------------------
   One bar per analysed contig. Plain CSS on semantic markup: for the tens of
   rows this draws, a charting runtime would add a dependency, a mount step
   and a failure mode to produce rectangles. No animation anywhere, so there
   is nothing for prefers-reduced-motion to switch off. */
.contig-graph {
  margin: 0 0 var(--sws-space-4);
}

.contig-graph__title {
  margin: 0 0 var(--sws-space-1);
  font-size: 1rem;
}

.contig-graph__help {
  margin: 0 0 var(--sws-space-3);
  font-size: 0.9rem;
}

.contig-graph__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sws-space-1);
}

/* Name | track | score | badge. The track is the only elastic column, so a
   long identifier truncates instead of squeezing the bar away. */
.contig-graph__row {
  display: grid;
  grid-template-columns: minmax(6rem, 12rem) 1fr 3.5rem auto;
  align-items: center;
  gap: var(--sws-space-2);
}

/* Identifier and its length/gene-count subtitle, stacked in the row's one
   "name" column. min-width: 0 is load-bearing here -- without it a grid/flex
   item refuses to shrink below its content's natural width, and the
   ellipsis on the lines inside never engages. */
.contig-graph__label {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  overflow: hidden;
}

.contig-graph__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--sws-font-mono, ui-monospace, monospace);
  font-size: 0.85rem;
}

/* Subordinate to the identifier above it: smaller, muted, never a second bar
   and never implying the graph's bar encodes length. */
.contig-graph__meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.72rem;
  color: var(--sws-muted);
  font-variant-numeric: tabular-nums;
}

.contig-graph__track {
  position: relative;
  display: block;
  height: 0.85rem;
  min-width: 3rem;
  border-radius: 999px;
  background: var(--sws-border);
  overflow: hidden;
}

.contig-graph__fill {
  position: absolute;
  inset-block: 0;
  left: 0;
  border-radius: 999px;
}

.contig-graph__fill--phage {
  background: var(--sws-success);
}

.contig-graph__fill--bacteria {
  background: var(--sws-danger);
}

.contig-graph__fill--too-short,
.contig-graph__fill--other,
.contig-graph__fill--excluded {
  background: var(--sws-neutral);
}

/* The contig-classification threshold, drawn from the result's own
   provenance. Dashed so it reads as a reference line rather than data, and
   restated in words beneath the graph -- it is never the only statement. */
.contig-graph__threshold-line {
  position: absolute;
  inset-block: -2px;
  width: 0;
  border-left: 2px dashed var(--sws-muted-strong);
}

.contig-graph__score {
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  text-align: right;
}

.contig-graph__axis {
  display: flex;
  justify-content: space-between;
  margin-top: var(--sws-space-1);
  font-size: 0.75rem;
  color: var(--sws-muted);
}

/* 0 at the track's left edge, 1 at its right edge, and 0.5 at its exact
   centre: the two outer labels take equal shares of the remaining width, so
   the middle one is centred whatever the labels' own widths are. */
.contig-graph__axis > span {
  flex: 1 1 0;
}

.contig-graph__axis > span:nth-child(2) {
  flex: 0 0 auto;
}

.contig-graph__axis > span:last-child {
  text-align: right;
}

/* One set of columns for the bars and their scale. Each row used to be an
   independent grid, so its elastic track column depended on that row's own
   badge width ("Phage" vs "Bacteria"), and the axis spanned the whole card.
   Here `.contig-graph__plot` owns the columns, the list and every row inherit
   them through `subgrid`, and the axis is placed in the track column: all
   tracks share one left and right edge, and the scale spans exactly that.
   Browsers without subgrid keep the independent-row layout above. */
@supports (grid-template-columns: subgrid) {
  .contig-graph__plot {
    display: grid;
    grid-template-columns: minmax(6rem, 12rem) 1fr 3.5rem auto;
    column-gap: var(--sws-space-2);
  }

  .contig-graph__plot > .contig-graph__list,
  .contig-graph__plot .contig-graph__row {
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
  }

  .contig-graph__plot > .contig-graph__axis {
    grid-column: 2;
  }
}

.contig-graph__threshold-note,
.contig-graph__capped {
  margin: var(--sws-space-2) 0 0;
  font-size: 0.85rem;
}

.contig-graph__capped {
  padding: var(--sws-space-2) var(--sws-space-3);
  border-left: 3px solid var(--sws-accent);
  background: var(--sws-bg);
}

/* Excluded contigs get a list, not a bar -- they have no score to draw. */
.contig-graph__excluded {
  margin-top: var(--sws-space-3);
  padding-top: var(--sws-space-2);
  border-top: 1px solid var(--sws-border);
}

.contig-graph__excluded-title {
  margin: 0 0 var(--sws-space-1);
  font-size: 0.9rem;
}

.contig-graph__excluded-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sws-space-1);
}

.contig-graph__excluded-row {
  display: grid;
  grid-template-columns: minmax(6rem, 12rem) auto 1fr;
  gap: var(--sws-space-2);
  align-items: baseline;
  font-size: 0.85rem;
}

.contig-graph__excluded-length {
  font-variant-numeric: tabular-nums;
  color: var(--sws-muted);
}

/* Narrow viewports: the graph must never need horizontal scrolling, whatever
   the table below it does. Name and meta stack above a full-width track. */
@media (max-width: 640px) {
  /* The track spans the full row here, so a full-width scale beneath the
     rows is already aligned with it: the shared-column grid is switched off
     and each row keeps its own two-line layout. */
  .contig-graph__plot {
    display: block;
  }

  .contig-graph__plot > .contig-graph__list {
    grid-template-columns: none;
  }

  .contig-graph__plot .contig-graph__row {
    grid-template-columns: 1fr auto auto;
  }

  .contig-graph__row {
    grid-template-columns: 1fr auto auto;
    grid-template-areas:
      "name score badge"
      "track track track";
    row-gap: var(--sws-space-1);
  }

  .contig-graph__label {
    grid-area: name;
    max-width: 100%;
  }

  .contig-graph__track {
    grid-area: track;
  }

  .contig-graph__score {
    grid-area: score;
  }

  .contig-graph__excluded-row {
    grid-template-columns: 1fr auto;
  }
}

/* The classification badge. Text first, tint second. */
.contig-badge {
  display: inline-block;
  padding: 0.1rem 0.55rem;
  border-radius: 999px;
  border: 1px solid var(--sws-border);
  font-size: 0.85rem;
  font-weight: 600;
  white-space: nowrap;
}

.contig-badge--phage {
  border-color: var(--sws-success);
  color: var(--sws-success);
}

.contig-badge--bacteria {
  border-color: var(--sws-danger);
  color: var(--sws-danger);
}

.contig-badge--too-short,
.contig-badge--excluded,
.contig-badge--other {
  border-color: var(--sws-neutral);
  color: var(--sws-muted);
}

/* The machine-readable reason, shown beneath the badge so the row explains
   itself without a tooltip. */
.contig-table__excluded-reason {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.8rem;
}

.contig-table__controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sws-space-2);
  margin-bottom: var(--sws-space-2);
}

.contig-table__search {
  flex: 1 1 16rem;
  min-width: 0;
}

.contig-table__status {
  margin: 0 0 var(--sws-space-2);
  font-size: 0.9rem;
}

/* The table scrolls inside its own box rather than widening the page: a long
   contig identifier must never make the whole result view scroll sideways. */
.contig-table__scroll {
  overflow-x: auto;
}

.contig-table__table {
  width: 100%;
}

/* A long identifier wraps within its cell instead of forcing the table
   wider. `anywhere` rather than `break-all` so ordinary identifiers still
   break at their own separators where they have them. */
.contig-table__contig {
  max-width: 22rem;
  overflow-wrap: anywhere;
}

.contig-table__id {
  display: block;
  font-family: var(--sws-font-mono, monospace);
  font-size: 0.9rem;
}

.contig-table__renamed {
  display: block;
  font-size: 0.8rem;
}

.contig-table__numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.contig-table__sort {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  text-align: inherit;
}

.contig-table__sort:hover {
  text-decoration: underline;
}

.contig-table__pager {
  display: flex;
  align-items: center;
  gap: var(--sws-space-3);
  margin-top: var(--sws-space-3);
}

.contig-table__page-of {
  font-size: 0.9rem;
}

.score-distribution {
  margin-top: var(--sws-space-4);
  padding-top: var(--sws-space-4);
  border-top: 1px solid var(--sws-border);
}

.score-distribution__heading {
  margin: 0 0 var(--sws-space-2);
  font-size: 0.95rem;
}

.score-distribution__caption {
  margin: 0 0 var(--sws-space-2);
  font-size: 0.9rem;
}

/* Bounded height so the plot informs the page rather than dominating it --
   the summary and the table stay the primary content. */
.score-distribution__chart {
  width: 100%;
  height: 240px;
}

.run-provenance__summary {
  cursor: pointer;
  font-weight: 600;
}

.run-provenance__note,
.run-provenance__truncated {
  margin: var(--sws-space-2) 0;
  font-size: 0.9rem;
}

.run-provenance__scroll {
  overflow-x: auto;
}

.run-provenance__table th {
  text-align: left;
  white-space: nowrap;
  font-weight: 600;
}

/* A 64-hex digest must wrap rather than widen the disclosure. */
.run-provenance__value {
  font-family: var(--sws-font-mono, monospace);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

.result-too-large p {
  margin: var(--sws-space-2) 0 0;
}

/* ------------------------------------------------------------------------
   Genome map (ADR-0262): a whole-sequence ring and a zoomable linear track
   for any result declaring `genomic_region_map`. Every colour is a token, so
   both themes follow; the export resolves the same tokens to attributes.
   ------------------------------------------------------------------------ */
.genome-map {
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-4);
}

.genome-map__header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sws-space-3);
}

.genome-map__header h3 {
  margin: 0 0 0.25rem;
}

.genome-map__summary,
.genome-map__note,
.genome-map__hint {
  margin: 0;
}

.genome-map__note,
.genome-map__hint,
.genome-map__status {
  color: var(--sws-muted);
  font-size: 0.85rem;
}

.genome-map__sequence-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sws-space-2);
}

.genome-map__select-label {
  font-weight: 600;
}

.genome-map__select {
  min-width: 0;
  max-width: 100%;
  flex: 1 1 18rem;
}

.genome-map__buttons {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sws-space-1);
}

.genome-map__button {
  min-height: 2.25rem;
  min-width: 2.25rem;
  padding: 0.3rem 0.75rem;
  font-size: 0.85rem;
}

/* Ring beside the track on a comfortable viewport; stacked on a narrow one. */
.genome-map__views {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  gap: var(--sws-space-4);
  align-items: start;
}

.genome-map__figure {
  margin: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sws-space-2);
}

.genome-map__figure figcaption {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sws-space-2);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.genome-map__drawing svg {
  display: block;
  width: 100%;
  height: auto;
}

.genome-map__ring .genome-map__drawing svg {
  max-width: 30rem;
  margin: 0 auto;
}

.genome-map__track {
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-md);
  background: var(--sws-surface-sunken);
  touch-action: pan-y;
  cursor: grab;
}

.genome-map__track:focus-visible {
  outline: 2px solid var(--sws-brand);
  outline-offset: 2px;
}

.genome-map__legend {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.5rem;
  color: var(--sws-muted);
  font-size: 0.85rem;
}

.genome-map__swatch {
  display: inline-block;
  width: 0.9rem;
  height: 0.6rem;
  border-radius: 2px;
}

.genome-map__swatch--region {
  background: var(--sws-accent);
}

.genome-map__swatch--selected {
  margin-left: 0.5rem;
  background: var(--sws-brand-strong);
}

.gm-backbone {
  stroke: var(--sws-border-strong);
}

.gm-tick,
.gm-axis {
  stroke: var(--sws-muted);
  stroke-width: 1;
}

.gm-tick-label,
.gm-centre-note {
  fill: var(--sws-muted);
}

.gm-centre,
.gm-region-label {
  fill: var(--sws-text);
}

.gm-region-label {
  pointer-events: none;
}

.gm-region {
  fill: var(--sws-accent);
  cursor: pointer;
  stroke: var(--sws-surface);
  stroke-width: 1;
}

.gm-region:hover,
.gm-region:focus-visible {
  stroke: var(--sws-text);
  stroke-width: 2;
  outline: none;
}

.gm-region--selected {
  fill: var(--sws-brand-strong);
  stroke: var(--sws-text);
  stroke-width: 1.5;
}

/* With a selection, everything else steps back so it reads at a glance. */
.genome-map--has-selection .gm-region:not(.gm-region--selected),
.genome-map--has-selection .gm-overview-region:not(.gm-overview-region--selected) {
  opacity: 0.4;
}

.genome-map--has-selection .gm-region:not(.gm-region--selected):hover,
.genome-map--has-selection .gm-region:not(.gm-region--selected):focus-visible {
  opacity: 1;
}

.gm-region-label--selected {
  fill: var(--sws-brand-strong);
}

.gm-leader {
  stroke: var(--sws-muted);
  pointer-events: none;
}

.gm-selection-mark {
  stroke: var(--sws-brand-strong);
  stroke-width: 1.5;
  stroke-dasharray: 3 2;
  pointer-events: none;
}

.gm-selection-band {
  fill: var(--sws-brand-soft);
  pointer-events: none;
}

.gm-overview-region--selected {
  fill: var(--sws-brand-strong);
}

.genome-map__tooltip {
  position: absolute;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  max-width: 18rem;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--sws-border-strong);
  border-radius: var(--sws-radius-sm);
  background: var(--sws-bg-elevated);
  color: var(--sws-text);
  box-shadow: var(--sws-shadow-md);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
  pointer-events: none;
}

.genome-map__tooltip[hidden] {
  display: none;
}

.genome-map__dialog {
  width: min(96vw, 1500px);
  max-width: none;
  max-height: 94vh;
  padding: var(--sws-space-4);
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-lg);
  overflow: auto;
  background: var(--sws-surface);
  color: var(--sws-text);
  box-shadow: var(--sws-shadow-lg);
}

.genome-map__dialog::backdrop {
  background: rgba(0, 0, 0, 0.6);
}

.genome-map__dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sws-space-3);
  margin-bottom: var(--sws-space-3);
}

.genome-map__dialog-header h3 {
  margin: 0;
  overflow-wrap: anywhere;
}

/* Expanded, the linear browser takes the full width for detailed
   inspection, with the whole-sequence ring beneath it. */
.genome-map__dialog .genome-map__views {
  grid-template-columns: minmax(0, 1fr);
}

.genome-map__dialog .genome-map__linear {
  order: -1;
}

.genome-map__dialog .genome-map__ring .genome-map__drawing svg {
  max-width: 30rem;
}

.gm-overview {
  fill: var(--sws-surface);
  stroke: var(--sws-border-strong);
  cursor: pointer;
}

.gm-overview-region {
  fill: var(--sws-accent);
  pointer-events: none;
}

.gm-window {
  stroke: var(--sws-brand-strong);
  pointer-events: none;
}

.genome-map__detail {
  min-height: 2.5rem;
}

.genome-map__facts {
  display: grid;
  grid-template-columns: minmax(0, 9rem) minmax(0, 1fr);
  gap: 0.3rem var(--sws-space-4);
  margin: 0;
}

.genome-map__facts dt {
  color: var(--sws-muted);
}

.genome-map__facts dd {
  margin: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}

/* The table keeps its shape and scrolls inside its own box on a phone,
   rather than squeezing every header into a column of single letters. */
.genome-map__table-wrap {
  max-height: 24rem;
  overflow: auto;
  border: 1px solid var(--sws-border);
  border-radius: var(--sws-radius-md);
}

.genome-map__table {
  width: 100%;
  min-width: 40rem;
  border-collapse: collapse;
  font-size: 0.875rem;
}

.genome-map__table caption {
  text-align: left;
  padding: var(--sws-space-2) var(--sws-space-3);
  color: var(--sws-muted);
}

.genome-map__table th,
.genome-map__table td {
  padding: 0.4rem var(--sws-space-3);
  border-top: 1px solid var(--sws-border);
  text-align: left;
}

.genome-map__table thead th {
  white-space: nowrap;
}

.genome-map__source-id {
  overflow-wrap: anywhere;
}

.genome-map__sequence {
  min-width: 10rem;
  overflow-wrap: anywhere;
}

.genome-map__table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.genome-map__table tr.is-selected {
  background: var(--sws-brand-soft);
  box-shadow: inset 4px 0 0 var(--sws-brand-strong);
}

.genome-map__row-button {
  border: 0;
  background: none;
  padding: 0;
  color: var(--sws-link);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
}

.genome-map__row-button:hover,
.genome-map__row-button:focus-visible {
  color: var(--sws-link-hover);
  text-decoration: underline;
}

@media (max-width: 760px) {
  .genome-map__views {
    grid-template-columns: minmax(0, 1fr);
  }

  .genome-map__dialog {
    width: 100vw;
    max-height: 100vh;
    padding: var(--sws-space-3);
  }

  .genome-map__facts {
    grid-template-columns: minmax(0, 1fr);
  }
}
