/* Inter, self-hosted, exactly as logistixpert.de ships it - same two files, so
   the product and the site are set in the same face. Self-hosted rather than
   pulled from a CDN: nothing about opening this tool should reach a third
   party, which is the same line the website makes.

   It is a variable font (100-900), which is what the weights below assume.
   Named-but-never-loaded is how this started: the stack said Inter, nothing
   fetched it, and every browser fell through to whatever the OS supplies. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url("/static/fonts/sourceserif4-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url("/static/fonts/sourceserif4-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  color-scheme: light;

  /* Ground and ink. One set, not two: there used to be a --color-text beside
     --ink and a --color-border beside --line, a percent apart and both in
     use, which is why greys and hairlines drifted from page to page. */
  --bg: #f4f7fb;
  --ink: #142033;
  --muted: #667085;
  --line: #d7e0ea;

  /* The brand: the navy the sidebar is cut from and the green of the mark. */
  --navy: #003f91;
  --navy-deep: #002b63;
  --green: #6abf2b;

  /* Accents the interface leans on, distinct from the brand on purpose. */
  --accent: #0f766e;
  --accent-2: #2563eb;

  /* Semantic, and only semantic — never borrowed for decoration. */
  --danger: #b42318;
  --warning: #f79009;

  /* Spelled out four different ways before this; ids, paths and payloads all
     want the same face. */
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  /* The other half of the brand site's pairing, and it earns its place in one
     job only: the headings of an analysis report. A report is the document
     this tool exists to produce - often the one a customer files as evidence -
     and a serif gives it the register of a record rather than of a screen.
     Never for UI chrome; a control set in a book face reads as a mistake. */
  --serif: "Source Serif 4", Georgia, "Times New Roman", serif;

  /* A scale, because almost everything used to inherit the body's 15px and
     the rest had drifted into 39 different sizes. Labels, dense data, prose,
     headings - four jobs, and a step for each with room either side. */
  --text-2xs: 11px;    /* uppercase labels, chips, badges */
  --text-xs: 12.5px;   /* captions, hints, secondary meta */
  --text-sm: 13.5px;   /* tables, nav, anything dense */
  --text-md: 15px;     /* body copy */
  --text-lg: 17px;     /* subheads */
  --text-xl: 20px;     /* panel headings */
  --text-2xl: 24px;    /* section titles */
  --text-3xl: 32px;    /* a number meant to be read across a room */

  /* Spacing, for the same reason the type has a scale: every gap used to be
     picked by hand, so two panels on one page could sit 6 px and 18 px apart
     with nothing to say why. Four steps and two for the wide ones. */
  --space-1: 4px;      /* inside a line: a label and the value under it */
  --space-2: 8px;      /* between pills, chips, inline controls */
  --space-3: 12px;     /* the floor between any two stacked things */
  --space-4: 16px;     /* between a heading and what it introduces */
  --space-5: 24px;     /* between the parts of a panel */
  --space-6: 32px;     /* between sections of a page */

  --radius-md: 10px;
  --radius-lg: 16px;
  /* Two levels, because an interface only needs two: things that rest on the
     page, and things that float above it. Everything used to sit at 8px/24px,
     so nothing read as nearer than anything else. */
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04), 0 1px 3px rgba(15, 23, 42, 0.05);
  --shadow-md: 0 2px 4px rgba(15, 23, 42, 0.05), 0 12px 28px rgba(15, 23, 42, 0.10);
}

* { box-sizing: border-box; }
/* Form controls do not inherit the page font - every browser hands them its
   own default instead. Without this the buttons, selects and inputs stayed on
   the OS face while everything around them moved to Inter, which is the same
   split-personality problem one level down. */
button, input, select, textarea { font-family: inherit; font-size: inherit; }
body {
  margin: 0;
  min-height: 100vh;
  background:
    radial-gradient(circle at top left, rgba(37, 99, 235, 0.12), transparent 34rem),
    radial-gradient(circle at top right, rgba(15, 118, 110, 0.14), transparent 30rem),
    linear-gradient(180deg, #f8fbff 0%, var(--bg) 45%, #eef4f8 100%);
  color: var(--ink);
  /* Spelled out rather than in the `font` shorthand, so the size comes from
     the scale like every other size on the page. */
  font-family: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: var(--text-md);
  line-height: 1.5;
}
.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 28px;
  background: rgba(255, 255, 255, 0.88);
  border-bottom: 1px solid rgba(215, 224, 234, 0.9);
  backdrop-filter: blur(14px);
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
}
.brand-logo {
  display: block;
  width: 288px;
  max-width: 42vw;
  height: auto;
}
.brand-mark {
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  font-size: var(--text-xs);
  letter-spacing: 0.03em;
}
nav { display: flex; gap: 8px; flex-wrap: wrap; }
nav a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 700;
  padding: 8px 10px;
  border-radius: 7px;
}
nav a:hover { color: var(--accent); background: #edf7f6; }
.nav-form { display: inline-flex; margin: 0; }
.nav-form button {
  padding: 8px 10px;
  background: #eef4fb;
  color: var(--muted);
  box-shadow: none;
}
a { color: var(--accent-2); }
.container { width: min(1280px, calc(100% - 32px)); margin: 30px auto; }
.page-head {
  /* The widest gap on the page: the title and the work under it are two
     different things, and the eye should have to travel. */
  margin-bottom: var(--space-6);
  padding: 6px 0 2px;
}
/* Headings on the scale too. h3 and h4 had no size at all, so they fell back
   to the browser's em multiples - which is how a "Failed Checks" subhead ended
   up at 17.55px, a size nothing else on the page uses. */
h1, h2, h3, h4 { margin: 0 0 var(--space-3); line-height: 1.15; }
h1 { font-size: clamp(30px, 4vw, 46px); letter-spacing: 0; }
h2 { font-size: var(--text-xl); }
h3 { font-size: var(--text-lg); }
h4 { font-size: var(--text-md); }
/* <small> has a browser default of 0.83em, which lands on whatever fraction
   its parent happens to be — the last way a size could still drift. */
small { font-size: var(--text-xs); }

/* The report, and only the report. Its headings carry the brand serif so the
   analysis output reads as a record rather than as a screen — the same
   pairing the website uses. Everything the reader operates rather than reads
   stays in Inter, so the filter chips, buttons and table headers underneath
   are deliberately left out. */
[data-document="report"] h1,
[data-document="report"] h2,
[data-document="report"] h3 {
  font-family: var(--serif);
  font-weight: 600;
  letter-spacing: -0.005em;
}
/* Except the ones that are labels wearing a heading tag — small, uppercase
   and letter-spaced. A book face set that way reads as a mistake. */
[data-document="report"] .run-scope h3 {
  font-family: inherit;
  font-weight: 700;
  letter-spacing: 0.06em;
}
/* And except the inspector's title, which is not a heading at all: it is the
   check id and the field path of whatever is selected, and an identifier
   belongs in the face the rest of the payload is set in. */
[data-document="report"] #viewer-title {
  font-family: var(--mono);
  font-size: var(--text-md);
  font-weight: 700;
  letter-spacing: 0;
}
p { color: var(--muted); }
.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 14px;
  margin: 18px 0;
}
.auth-shell {
  min-height: calc(100vh - 140px);
  display: grid;
  place-items: center;
}
.auth-card {
  width: min(520px, 100%);
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 28px;
  box-shadow: var(--shadow-md);
}
.auth-brand { margin-bottom: 18px; }
.auth-logo {
  display: block;
  width: min(360px, 100%);
  height: auto;
  margin: 0 auto 22px;
}
.auth-card h1 { font-size: var(--text-3xl); }
.metrics div, .panel {
  background: rgba(255, 255, 255, 0.94);
  border: 1px solid rgba(215, 224, 234, 0.95);
  border-radius: 8px;
  padding: 18px;
  box-shadow: var(--shadow-sm);
}
.narrow-panel {
  width: min(520px, 100%);
}
.metrics div {
  position: relative;
  overflow: hidden;
}
.metrics div::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
}
.metrics strong { display: block; font-size: var(--text-3xl); }
.metrics span { color: var(--muted); font-weight: 600; }
/* The verdict card holds a seal, not a number: stack the seal over its label so
   the inline seal and the "Verdict" label cannot collide on one line. */
.metrics div.verdict-metric { display: flex; flex-direction: column; justify-content: center; gap: 6px; }
.metrics div.verdict-metric .verdict-seal { font-size: var(--text-xl); }
/* Choices to run an eye down, one per line: versions, message kinds. A row of
   chips reads as a sentence and hides how many there are. */
.tick-list {
  display: grid;
  gap: 4px;
  margin-top: .3rem;
}
.tick-item {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-size: var(--text-sm);
}
.tick-item small { color: var(--muted); margin-left: .25rem; }

/* A decision with consequences, after several files were uploaded at once.
   Deliberately not .upload-panel and not .tick-list: the first is a two-column
   grid for a picker beside a button, which lands one option per column, and
   the second is a compact one-line-per-tick list with no room to say what a
   choice means. Here each option is a line of its own with its consequence
   spelled out underneath, because the two answers store different things. */
.batch-decide { display: grid; gap: 16px; margin-top: 14px; }
.choice-list { display: grid; gap: 12px; border: 0; padding: 0; margin: 0; min-width: 0; }
.choice-list > .form-group-title { padding: 0; margin: 0 0 .2rem; }
.choice { display: grid; grid-template-columns: auto 1fr; gap: .6rem; align-items: start; cursor: pointer; }
.choice input[type="radio"] { margin: .3rem 0 0; }
.choice-text strong { display: block; }
.choice-text small { display: block; color: var(--muted); }
.choice-field { display: grid; gap: .3rem; max-width: 34rem; }
.choice-field small { color: var(--muted); }

/* The project picker sits in a table cell beside plain text, so it reads as
   part of the row rather than as a control dropped into it: a step
   under the cell's own type, and a width it cannot grow out of. A project
   named after a customer and a hall is longer than the column can ever be,
   so it is cut rather than allowed to push the table sideways. */
td select[name="project_id"] {
  font-size: var(--text-2xs);
  /* No width:100% - a select with no intrinsic width lets the column collapse
     to the width of its header. It sizes to its longest option instead, with
     a floor so it stays clickable and a ceiling so one long project name
     cannot push the row's actions off the page. */
  min-width: 7rem;
  max-width: 14rem;
  text-overflow: ellipsis;
  padding: 4px 6px;
}
/* The offer that stands in for the picker while a workspace has no project:
   two words that must not break over two lines in a narrow column. */
.project-empty { white-space: nowrap; }
/* Created and Owner: which run it is matters, when and by whom is context.
   Set to the same step as the project picker beside them, so the three read
   as one band of secondary information rather than three competing ones. */
.cell-meta { font-size: var(--text-2xs); color: var(--muted); white-space: nowrap; }
/* The four severity counts in one column. They were four, each as wide as its
   own heading rather than as the digit beneath it, and together they took a
   sixth of a table that scrolled on a 27-inch screen. Kept on one line: the
   row is read across, and a count that wrapped under its neighbour would be
   read as belonging to the next column. */
.sev-cell { white-space: nowrap; }
/* The spacing sits on a wrapper rather than on the pills: a rule of its own on
   `.severity-count` would be the first one in this file, and the house check
   that every pill declares `white-space: nowrap` reads the first it finds. */
.sev-group { display: inline-flex; gap: 3px; align-items: center; }

/* The account page: tiles that are links where there is something to open, and
   plain where the number is the whole story. A folded panel keeps the same
   frame as an open one, so the page does not jump about as boxes are opened. */
/* Tiles that are links where there is something to open. Every tile rule in
   this file targets `.metrics div`, so a link got no frame and no accent stripe
   and stood beside its neighbours as bare text - which is exactly how the
   Analyses tile looked. The frame is repeated here rather than widened above,
   because widening `.metrics div` to `.metrics a` would reach every page that
   ever puts a link in a tile. */
.account-metrics a {
  text-decoration: none;
  color: inherit;
  position: relative;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.94);
  border: 1px solid rgba(215, 224, 234, 0.95);
  border-radius: 8px;
  padding: 18px;
  box-shadow: var(--shadow-sm);
  display: block;
}
.account-metrics a::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
}
.account-metrics a:hover strong { color: var(--accent); }
/* And a step down from the dashboard's number-across-a-room: these are counts
   you read while sitting at the page, not from the other side of a hall.
   Written with both classes because a later block in this file sets
   `.metrics strong` to clamp(24px, 3vw, 34px) - equal specificity, so it wins
   on position alone, and a rule that depends on where it sits is a rule that
   breaks the next time the file is reordered. */
.metrics.account-metrics strong { font-size: var(--text-xl); }
.metrics.account-metrics span { font-size: var(--text-xs); }
/* The same stripe the tiles beside it carry, from that same later block. */
.account-metrics a::before { width: 5px; background: linear-gradient(180deg, var(--navy), var(--green)); }
.metrics.account-metrics a { min-height: 0; }
.account-findings { margin: 12px 0 0; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
details.panel > summary {
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
}
details.panel > summary strong { font-size: var(--text-md); }
details.panel[open] > summary { margin-bottom: 12px; }

/* The check selection on the analyze page, folded because everything relevant
   is already ticked. Narrowed, it says so in its own frame. */
.check-panel > summary { cursor: pointer; }
.check-panel-narrowed > summary strong { color: var(--accent); }
/* Message kinds, comma-separated: several fit on one line, many wrap inside
   the cell instead of stretching it. */
/* Wide enough for two or three pills a line: at 12rem each kind took a line
   of its own and a six-type capture made the row six lines tall. */
.kind-cell { white-space: normal; min-width: 20rem; max-width: 26rem; }
/* An imported-at column is read as a moment, not compared character by
   character - but it must not wrap into two lines while doing it. */
td.stamp { white-space: nowrap; }

/* A third for the verdict, two for the audit trail beside it. Below the
   breakpoint the two stack, like every other pair on the page. */
.grid.split-third {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 18px;
  align-items: start;
}
@media (max-width: 900px) {
  .grid.split-third { grid-template-columns: 1fr; }
}
/* A hash has no spaces to break at, so the value column is told to break
   anywhere rather than push the panel wider than its share of the row. Scoped
   to that one column on purpose: the same rule on every cell once turned
   "Severity" into "Seve rity". */
.audit-table td + td { overflow-wrap: anywhere; }
.grid.two, .form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 18px;
  align-items: start;
}
/* A report is read top to bottom, and its panels are not comparable pairs:
   a scorecard table next to a three-item risk list leaves the short one
   trailing half a page of white. One box per row, each as wide as the page. */
[data-document="report"] .grid.two {
  grid-template-columns: 1fr;
}
/* The verdicts are the one deliberate exception: with custom rules a run has
   the standard's verdict and the custom one, and two cards of the same shape
   side by side are exactly the comparison the page owes the reader. Without
   custom rules the single card keeps the same half of the page, not a
   banner's width for one word. */
.verdict-cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  align-items: start;
}
@media (max-width: 1100px) {
  .verdict-cards { grid-template-columns: 1fr; }
}
.verdict-card h2 { margin-bottom: 2px; }
.verdict-card .hint { margin: 0 0 12px; }
.verdict-top { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 2px 0 12px; }
.verdict-pill, .score-pill {
  font-size: var(--text-xl);
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 5px 18px;
  border-radius: 999px;
}
.verdict-pass { color: #166534; background: #dcfce7; }
.verdict-warning { color: #92400e; background: #fef3c7; }
.verdict-fail { color: #991b1b; background: #fee2e2; }
.verdict-none { color: var(--muted); background: #eef2f7; }
.score-pill { color: #12356b; background: #e6edf7; }
/* Coverage: the same shape as the score pill, tinted by class, because the
   two are read as a pair. A run can be green on one and pale on the other -
   that contrast is the whole point of showing them side by side. */
.coverage-pill {
  font-size: var(--text-xl);
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 5px 18px;
  border-radius: 999px;
  text-decoration: none;
  color: #12356b;
  background: #e6edf7;
}
.coverage-pill span { font-size: var(--text-sm); font-weight: 600; }
.coverage-sample { color: #991b1b; background: #fee2e2; }
.coverage-partial { color: #92400e; background: #fef3c7; }
.coverage-broad { color: #12356b; background: #e6edf7; }
.coverage-certification { color: #166534; background: #dcfce7; }
.coverage-gates { list-style: none; margin: 6px 0 0; padding: 0; display: grid; gap: 4px; }
.coverage-gates li { font-size: var(--text-sm); padding-left: 22px; position: relative; }
.coverage-gates li::before { position: absolute; left: 0; font-weight: 700; }
.gate-met { color: var(--muted); }
.gate-met::before { content: "\2713"; color: #166534; }
.gate-open::before { content: "\2013"; color: #92400e; }
.gate-holders { color: var(--muted); }
.coverage-gaps { margin-top: 14px; }
.coverage-gaps summary { font-weight: 600; cursor: pointer; }
.coverage-gaps ul { margin: 8px 0 0; padding-left: 18px; display: grid; gap: 8px; }
.coverage-gaps li strong { display: block; }
.coverage-gaps li span { color: var(--muted); font-size: var(--text-sm); }
/* The fleet's errors as one list: the filter sits in the table header, where
   a reader looks for it, rather than above the card. */
.perf-error-filter { display: flex; align-items: center; gap: 8px; }
.perf-error-filter label { font-size: var(--text-sm); color: var(--muted); }
.perf-error-filter select { min-width: 12rem; }
/* The ruleset file, formatted: long enough to need its own scroll, and read
   rather than skimmed - so it keeps the monospace column width of the message
   views instead of stretching across the page. */
/* ============================== Landing page ============================= */
/* The first page for somebody who is not signed in. It shares the palette and
   the type of the application - a visitor should recognise the product they
   land in - but none of its chrome: no sidebar, no workspace banner, nothing
   that belongs to a session that does not exist yet. */
.landing { max-width: 68rem; margin: 0 auto; padding: var(--space-6) var(--space-4) var(--space-6); }
/* The hero band of logistixpert.de, brought across as it is built there: the
   same photograph at the same 22% over the site's own ink, under the same two
   gradients - a 104-degree wash and a blue glow out of the top left corner.
   Somebody who arrives from the product page should recognise the ground they
   are standing on before they read a word.

   Only the band, not the page: the site is dark at the top and light beneath
   it, and so is this. */
.landing-hero {
  text-align: center;
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: #0c1524;
  color: #eef1f6;
  padding: clamp(40px, 5vw, 88px) clamp(20px, 4vw, 56px);
  margin-bottom: var(--space-6);
}
.landing-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  background-image: url("/static/landing-bg.jpg");
  background-size: cover;
  background-position: center 46%;
  opacity: 0.22;
  filter: saturate(0.35);
}
.landing-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(104deg, #0c1524 0%, rgba(12, 21, 36, 0.94) 42%,
                    rgba(12, 21, 36, 0.68) 72%, rgba(12, 21, 36, 0.8) 100%),
    radial-gradient(120% 90% at 8% 0%, rgba(42, 74, 155, 0.42) 0%, transparent 62%);
}
/* On the dark band the type is light, and the note under the buttons keeps its
   quieter step without falling into the background it now sits on. */
.landing-hero h1 { color: #ffffff; }
.landing-hero .landing-claim { color: rgba(238, 241, 246, 0.86); }
.landing-hero .landing-note { color: rgba(238, 241, 246, 0.66); }

/* Edge to edge, like the band on logistixpert.de. The page gets a container of
   its own rather than breaking out of the narrow one with `100vw`: that trick
   counts the scrollbar as page width and hands you a horizontal scrollbar on
   every machine that shows one. What is constrained is the content, not the
   band - which is how the site does it too. */
/* Both written with the class they have to beat: a later `.container` rule in
   this file sets 1480px, and `.landing` its own 68rem, so equal specificity
   would leave the band at whatever those two agreed on - which is exactly
   where it stayed on the first attempt. */
.container.landing-container { width: 100%; max-width: none; margin: 0 0 30px; padding: 0; }
.landing-container > .landing { max-width: none; margin: 0; padding: 0 0 var(--space-6); }
.landing-hero h1,
.landing-hero .landing-claim,
.landing-hero .landing-actions,
.landing-hero .landing-note { max-width: 860px; margin-inline: auto; }
/* Everything under the band keeps the page's own measure. */
.landing-facts,
.landing-foot { width: min(1280px, calc(100% - 32px)); margin-inline: auto; }
/* The product page's hero, wider than a wordmark and carrying its own frame:
   it holds four screenshots, so it needs the room to be read as pictures
   rather than as a smudge, and a rounded edge keeps it from butting into the
   heading beneath. */
.landing-hero-art {
  /* 880 was the measure of the narrow card; the band is the whole width now
     and the picture carries four screenshots, which want the room - though not
     the whole of it: 1584 crowded the band's own edges, so it stands back a
     fifth from there. */
  width: min(1267px, 100%);
  height: auto;
  margin: 0 auto var(--space-5);
  display: block;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}
.landing-hero h1 {
  /* Not the serif: that face belongs to a report, which is a document
     somebody files. This is a page somebody operates. */
  font-size: clamp(28px, 4vw, 42px);
  line-height: 1.15;
  margin-bottom: var(--space-4);
}
.landing-claim {
  max-width: 44rem;
  margin: 0 auto var(--space-5);
  font-size: var(--text-lg);
  line-height: 1.6;
  color: var(--ink);
}
.landing-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); justify-content: center; }
.landing-note { margin-top: var(--space-3); font-size: var(--text-sm); color: var(--muted); }
.landing-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-6);
}
.landing-facts article {
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5);
}
.landing-facts h2 { font-size: var(--text-lg); color: var(--navy-deep); }
.landing-facts p { margin: 0; color: var(--muted); line-height: 1.6; }
.landing-foot {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--muted);
}
.landing-foot p + p { margin-top: var(--space-2); }

/* Coverage in a list, beside the score: same shape as the score badge so the
   pair reads as a pair, and the class carries the colour - a high score next
   to a pale coverage is exactly the contrast the column is there to show. */
/* An action in a table header keeps to one line: the header is a flex row and
   a two-word button was wrapping into "Add a / member". */
.table-header .page-actions .button,
.table-header .page-actions button { white-space: nowrap; }

.coverage-badge {
  display: inline-block;
  min-width: 3.2rem;
  text-align: center;
  padding: 2px 8px;
  border-radius: 999px;
  font-weight: 700;
  font-size: var(--text-sm);
  color: #12356b;
  background: #e6edf7;
}
.coverage-badge.coverage-sample { color: #991b1b; background: #fee2e2; }
.coverage-badge.coverage-partial { color: #92400e; background: #fef3c7; }
.coverage-badge.coverage-broad { color: #12356b; background: #e6edf7; }
.coverage-badge.coverage-certification { color: #166534; background: #dcfce7; }

/* Whether a ruleset agrees with the standard, as one glyph in its row. The
   colour carries the meaning and the tooltip carries the sentence; the count
   beside it says how many rules are involved. */
.rs-flag { display: inline-flex; align-items: center; gap: var(--space-1); text-decoration: none; }
/* The glyph takes the colour of the state, like every other icon in the
   app: without an explicit fill an inline SVG paints itself black and the
   difference between "agrees" and "contradicts" is a shape nobody reads. */
.rs-flag svg { width: 18px; height: 18px; fill: currentColor; }
.rs-flag span { font-size: var(--text-xs); font-weight: 700; }
.rs-flag-ok { color: #166534; }
.rs-flag-note { color: var(--warning); }
.rs-flag-bad { color: var(--danger); }
.rs-flag-bad:hover, .rs-flag-note:hover { text-decoration: underline; }
.ruleset-json {
  margin: 0;
  max-height: 70vh;
  overflow: auto;
  font-family: var(--mono);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--ink);
  white-space: pre;
}
/* The verdict as an inspection seal. Colour rides on the container as
   currentColor, so rim, glyph and a tinted face all follow one declaration, and
   the label may wrap instead of forcing the box open. */
.verdict-seal { display: inline-flex; align-items: center; gap: 8px; max-width: 100%; font-weight: 700; font-size: var(--text-md); line-height: 1.15; }
.verdict-seal .seal-label { white-space: normal; overflow-wrap: break-word; }
/* Two characters of air after the word, so a tinted cell around the seal does
   not stop hard against the last letter. */
.verdict-seal { padding-right: 0.6em; }
.seal-icon { width: 1.9em; height: 1.9em; flex: 0 0 auto; }
.seal-rim, .seal-mark { stroke: currentColor; }
.seal-face { fill: color-mix(in srgb, currentColor 14%, white); }
.seal-mark-fill { fill: currentColor; }
.verdict-seal.verdict-pass { color: #166534; }
.verdict-seal.verdict-pass_with_warnings, .verdict-seal.verdict-warning { color: #92400e; }
.verdict-seal.verdict-fail { color: #991b1b; }
.verdict-seal.verdict-not_applicable, .verdict-seal.verdict-none { color: #64748b; }
.verdict-severities { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 4px; }
.verdict-card .table-scroll { margin-top: 12px; }
.verdict-scores td:last-child { text-align: right; font-weight: 700; color: #12356b; }
/* The compare page: movement is the message, so the deltas carry the colour
   and the unchanged tail steps back. */
.delta-worse { color: #991b1b; font-weight: 700; }
.delta-better { color: #166534; font-weight: 700; }
.compare-unchanged td { color: var(--muted); }
/* What became of a finding. "Open" is the absence of a chip on purpose. */
.dispo-chip {
  font-size: var(--text-2xs);
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid transparent;
  background: #f1f5f9;
  color: #475569;
  cursor: pointer;
  white-space: nowrap;
}
.dispo-chip.dispo-accepted { background: #dcfce7; color: #166534; }
.dispo-chip.dispo-false_positive { background: #e2e8f0; color: #334155; }
.dispo-chip.dispo-to_fix { background: #fef3c7; color: #92400e; }
.dispo-chip.dispo-open { background: #fff; border-color: var(--line); color: #94a3b8; }
/* Covered by a standing waiver rather than decided here: same colours, dashed
   edge - a rule about the kind, not a decision about the finding. */
.dispo-chip.waived { border-style: dashed; border-color: currentColor; }
/* The dialog is native; only its box is ours. */
.dispo-dialog { border: 0; border-radius: var(--radius-lg); box-shadow: var(--shadow-md); padding: 0; width: min(460px, 92vw); }
.dispo-dialog::backdrop { background: rgba(15, 23, 42, 0.45); }
.dispo-dialog form { padding: 20px; display: grid; gap: 12px; }
.dispo-dialog h3 { margin: 0; }
.dispo-dialog .dispo-finding { font-family: var(--mono); font-size: var(--text-xs); color: var(--muted); overflow-wrap: break-word; }
.dispo-dialog textarea { min-height: 84px; resize: vertical; }
.dispo-dialog .actions { margin: 0; justify-content: flex-end; }

/* The coverage explanation. Wider than a disposition dialog because it is
   prose about seven figures rather than one field to fill in, and scrollable
   because on a short screen it would otherwise run off the bottom. */
.coverage-help { width: min(680px, 94vw); }
.coverage-help-body { padding: 22px 24px 20px; max-height: 82vh; overflow-y: auto; }
.coverage-help-body h3 { margin: 0 0 6px; }
.coverage-help-body h4 { margin: 18px 0 4px; font-size: var(--text-sm); }
.coverage-help-body p { margin: 0 0 4px; line-height: 1.55; }
.coverage-help-body code { font-family: var(--mono); font-size: var(--text-sm); }
.coverage-help-body .actions { margin-top: 18px; justify-content: flex-end; }

/* A button that reads as a link: it opens an explanation rather than changing
   anything, and a second solid button beside a heading would claim more of the
   eye than the heading. */
.linkish {
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  font: inherit;
  font-size: var(--text-sm);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  white-space: nowrap;
}
.linkish:hover { color: var(--ink); }

/* Where a custom ruleset and the standard disagree. A hard conflict is a rule
   to rewrite and gets the weight of one; the softer two are decisions to
   confirm, not faults, and must not shout at somebody whose plant standard is
   simply narrower than VDA5050. */
.rs-conflict {
  border-left: 3px solid var(--line);
  border-radius: 0 8px 8px 0;
  padding: 10px 14px;
  margin-top: 10px;
  background: #f8fafc;
}
.rs-conflict-hard { border-left-color: #b91c1c; background: #fef2f2; }
.rs-conflict-soft { border-left-color: #94a3b8; }
.rs-conflict-head { display: flex; gap: 8px; align-items: center; margin-bottom: 4px; flex-wrap: wrap; }
.rs-conflict strong { display: block; }
.rs-conflict p { margin: 3px 0 0; font-size: var(--text-sm); }
.rs-conflict code { font-family: var(--mono); font-size: var(--text-xs); overflow-wrap: break-word; }
/* The same statement repeated at the rule itself. `rs-conflict` is the hard
   kind and shares its name with the block class above, so the inline form has
   to undo the block's border and background. */
.rs-inline-conflict { margin: 6px 0 0; font-size: var(--text-sm); color: var(--muted); }
.rs-inline-conflict.rs-conflict {
  border: 0;
  padding: 0;
  background: none;
  color: #b91c1c;
}
.rs-inline-conflict strong { display: inline; }
.standard-ok { color: var(--muted); }

/* The verdict on a file the moment it is taken in. Loud enough not to be
   scrolled past, quiet enough not to read as a refusal - the upload succeeded,
   and a deliberate deviation is the customer's to make. */
.upload-review { border-left: 4px solid #94a3b8; }
.upload-review-hard { border-left-color: #b91c1c; }
.upload-review h2 { margin: 0 0 6px; font-size: var(--text-md); }
.upload-review p { margin: 0 0 8px; }
.upload-review-list { margin: 0 0 8px; padding-left: 18px; }
.upload-review-list li { font-size: var(--text-sm); margin-bottom: 3px; }
.upload-review-list code { font-family: var(--mono); font-size: var(--text-xs); }
.dispo-meta { font-size: var(--text-2xs); color: var(--muted); }
/* The compare picker: one row per candidate run, the whole row the link. */
.compare-pick { padding: 20px; display: grid; gap: 12px; }
.compare-pick h3 { margin: 0; }
.compare-pick .hint { margin: 0; }
.compare-choices { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; max-height: 320px; overflow-y: auto; }
.compare-choices a {
  display: flex;
  gap: 12px;
  align-items: baseline;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  text-decoration: none;
  color: inherit;
}
.compare-choices a:hover { border-color: var(--accent-2); background: #f6fafd; }
.compare-pick .actions { margin: 0; justify-content: flex-end; }
/* The carry offer holds a form: sentence left, button right, one row. */
.carry-offer { display: flex; align-items: center; gap: 16px; }
.carry-offer span { flex: 1; }
.carry-offer .inline-form { flex: 0 0 auto; }
/* And a guaranteed gap between the boxes, so nothing on a report ever sits
   flush against the box above it. */
[data-document="report"] > section {
  margin-bottom: 18px;
}
table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 8px 24px rgba(20, 32, 51, 0.05);
}
/* A table narrower than its content does not wrap, it clips - and clipped
   text reads as text that was never there. The fix is to let the content
   shrink, not to let the table grow: the `breakable` filter marks the joints
   in an identifier so a check id or a field path stops holding a whole column
   open. `width: 100%` on a table is a floor rather than a ceiling, so anything
   that still cannot fit widens the table and the .table-scroll wrapper
   (overflow-x: auto, further down) scrolls it instead of clipping. Sizing the
   table to max-content instead, as this briefly did, stops the sentence
   columns wrapping at all and sends a nine-column findings table straight off
   the side of the screen. */
.table-scroll > table { width: 100%; }
/* Lining figures, because these tables are read down their columns: message
   counts, four severity counts and a score. Proportional digits make those
   columns ripple - a 1 is not the width of a 0 - and a table nobody can scan
   vertically is not doing its job. */
th, td { text-align: left; padding: 11px 12px; border-bottom: 1px solid #edf1f5; vertical-align: top;
         font-variant-numeric: tabular-nums; overflow-wrap: break-word; }
/* A column label may wrap between its two words - "Raised by" over two lines
   is fine, and holding it on one costs the sentence columns their room - but
   never inside one. Mid-word is what turned "Severity" into "Seve rity" and
   "Message" into three lines. */
th { overflow-wrap: normal; }
/* An identifier has no spaces, so a narrow column has nowhere to wrap it and
   the column holds itself open. The break opportunities come from the
   `breakable` filter, which marks the joints - after a separator, and at the
   humps of camelCase - so lastNodeSequenceId wraps as lastNode/SequenceId
   rather than wherever the line happened to run out. */
.ident { font-family: var(--mono); font-size: var(--text-xs); line-height: 1.45; }
th { background: #edf4fb; font-weight: 700; color: #344054; }
tr:last-child td { border-bottom: 0; }
.truncate {
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.severity-count {
  display: inline-flex;
  white-space: nowrap;
  min-width: 28px;
  justify-content: center;
  padding: 3px 8px;
  border-radius: 999px;
  font-weight: 700;
  font-size: var(--text-xs);
}
.severity-critical { color: #991b1b; background: #fee2e2; }
.severity-major { color: #9a3412; background: #ffedd5; }
.severity-minor { color: #854d0e; background: #fef3c7; }
.severity-info { color: #1d4ed8; background: #dbeafe; }
.finding-row { transition: background 0.15s ease; }
.finding-row:hover, .finding-row.is-selected { background: #eefaf7; }
form { display: grid; gap: 14px; }
label { display: grid; gap: 6px; font-weight: 700; color: #344054; }
label.inline, .toggle-row { display: flex; align-items: center; gap: 8px; }
.toggle-row {
  align-self: end;
  min-height: 43px;
  padding: 10px 12px;
  background: #f6fafc;
  border: 1px solid var(--line);
  border-radius: 8px;
}
input, select {
  width: 100%;
  padding: 11px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font: inherit;
}
input:focus, select:focus {
  outline: 3px solid rgba(37, 99, 235, 0.15);
  border-color: var(--accent-2);
}
/* Both, not only the checkbox. `input { width: 100% }` above is right for a
   text field and wrong for a tick: a radio that had not been exempted took the
   whole row in the export dialog and pushed its own label out of the way. */
input[type="checkbox"], input[type="radio"] { width: auto; accent-color: var(--accent); }
button, .button {
  display: inline-block;
  width: fit-content;
  border: 0;
  border-radius: 8px;
  padding: 10px 14px;
  background: linear-gradient(135deg, var(--accent), #14958b);
  color: #fff;
  text-decoration: none;
  /* A control label is dense UI, not prose. Left to inherit the body size it
     came out a step larger than it had been under the browser default, and a
     row of six page actions started wrapping onto three lines. */
  font-size: var(--text-sm);
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 8px 18px rgba(15, 118, 110, 0.18);
}
button:hover, .button:hover { filter: brightness(0.98); transform: translateY(-1px); }
.button.compact {
  padding: 7px 10px;
  font-size: var(--text-xs);
  white-space: nowrap;
}
button:disabled, .button:disabled {
  cursor: not-allowed;
  filter: grayscale(0.35);
  opacity: 0.55;
  transform: none;
}
.button.danger {
  background: linear-gradient(135deg, var(--danger), #e11d48);
  box-shadow: 0 8px 18px rgba(180, 35, 24, 0.16);
}
.inline-form {
  display: inline-block;
  margin-left: 8px;
}
.inline-form button { margin: 0; }
.actions, .action-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  margin: 16px 0;
}
.filter-panel {
  display: flex;
  gap: 12px;
  align-items: end;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.filter-panel label {
  min-width: min(360px, 100%);
}
.muted {
  color: var(--muted);
  font-size: var(--text-xs);
}
.admin-actions {
  display: grid;
  gap: 8px;
  min-width: 280px;
}
.admin-actions form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.admin-actions input {
  width: min(180px, 100%);
  padding: 8px 10px;
}
.filter-bar {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  /* Clear of the rule above it, so the chips are not glued to the heading. */
  margin: 10px 0 12px;
}
.filter-chip {
  background: #ffffff;
  color: var(--accent);
  border: 1px solid var(--line);
  padding: 7px 10px;
  font-size: var(--text-xs);
  box-shadow: none;
}
.filter-chip.is-active {
  background: linear-gradient(135deg, var(--accent-2), var(--accent));
  color: #ffffff;
  border-color: transparent;
}
.settings-toolbar {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  color: var(--muted);
  padding-top: 6px;
}
.settings-toolbar strong { color: var(--ink); }
.manual-layout {
  display: grid;
  grid-template-columns: minmax(210px, 260px) 1fr;
  gap: 18px;
  align-items: start;
}
/* Twenty-five entries for an administrator, and it has to stand still while
   the manual scrolls past it - so it is set as tight as it can be read at:
   no gap between the rows, the padding carrying the whole rhythm, and the
   dense type step the tables use. The max-height is the safety net rather
   than the plan: on a short screen it scrolls itself instead of pushing the
   page. */
.manual-toc {
  position: sticky;
  top: 104px;
  display: grid;
  gap: 0;
  max-height: calc(100vh - 124px);
  overflow-y: auto;
}
/* Both written with `.panel` alongside, because a later block in this file sets
   the panel padding and the link margin, and equal specificity would leave the
   outcome to whichever line happens to come last. The 12px margin every link
   in a panel carries was the whole difference between a 23px row and a 35px
   one - twenty-one entries of it is a quarter of a screen. */
.panel.manual-toc { padding: 12px; }
.panel.manual-toc a { margin: 0; }
.manual-toc strong {
  color: var(--ink);
  margin-bottom: 6px;
}
.manual-toc a {
  text-decoration: none;
  font-weight: 600;
  font-size: var(--text-sm);
  line-height: 1.25;
  color: var(--muted);
  padding: 3px 7px;
  border-radius: 6px;
}
.manual-toc a:hover {
  color: var(--accent);
  background: #edf7f6;
}
.manual-content {
  display: grid;
  gap: 16px;
}
.manual-section {
  scroll-margin-top: 110px;
}
.manual-section ul,
.manual-section ol {
  margin: 0;
  padding-left: 22px;
  color: var(--muted);
}
.manual-section li { margin: 6px 0; }
.manual-section code {
  padding: 2px 5px;
  border-radius: 5px;
  background: #eef4fb;
  color: #1d4ed8;
}
.policy-list {
  display: grid;
  grid-template-columns: minmax(150px, 220px) 1fr;
  gap: 8px 16px;
  color: var(--muted);
}
.policy-list dt {
  font-weight: 700;
  color: #344054;
}
.policy-list dd {
  margin: 0;
}
.check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 14px;
}
.check-grid.compact { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.check-group {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px;
  background: linear-gradient(180deg, #ffffff, #f8fbfd);
}
.check-group legend { padding: 0 6px; font-weight: 700; color: var(--accent); }
.check-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 10px;
  align-items: start;
  padding: 8px 0;
  border-bottom: 1px solid #edf1f5;
  font-weight: 500;
}
.check-row:last-child { border-bottom: 0; }
.check-row input { width: auto; margin-top: 3px; }
.check-row label { display: block; font-weight: 500; }
.check-row small { display: block; color: var(--muted); font-size: var(--text-xs); }
.info-dot {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 999px;
  background: #e7f3ff;
  color: var(--accent-2);
  box-shadow: none;
  font-size: var(--text-xs);
  line-height: 1;
}
.info-dot:hover,
.info-dot:focus {
  transform: none;
  filter: none;
  background: var(--accent-2);
  color: #fff;
}
.info-dot::after {
  content: attr(data-tooltip);
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 20;
  width: min(340px, 80vw);
  padding: 10px 12px;
  border-radius: 8px;
  background: #101828;
  color: #fff;
  box-shadow: var(--shadow-md);
  font-weight: 500;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-4px);
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.info-dot:hover::after,
.info-dot:focus::after {
  opacity: 1;
  transform: translateY(0);
}
.upload-panel { display: grid; gap: 14px; grid-template-columns: 1fr auto; align-items: end; }
.file-picker {
  position: relative;
  display: block;
  width: 100%;
  padding: 12px;
  border: 1px dashed #9eb3c7;
  border-radius: 8px;
  background: #f8fbfd;
  color: var(--muted);
  cursor: pointer;
}
.file-picker input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}
.error { color: var(--danger); font-weight: 700; }
.success { color: #177245; }
.hint {
  /* It used to carry a -4px top margin, which is how a hint came to sit on
     the line above it wherever it followed anything but a heading. Under a
     heading it still wants to be close, and that is said where it is true. */
  margin: 0 0 2px;
  font-size: var(--text-xs);
}
:is(h1, h2, h3, h4, .field-label, .eyebrow) + .hint { margin-top: calc(-1 * var(--space-1)); }
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgba(16, 24, 40, 0.52);
}
.modal-backdrop[hidden] { display: none; }
.modal-card {
  width: min(1100px, 100%);
  max-height: min(86vh, 920px);
  overflow: hidden;
  display: grid;
  grid-template-rows: auto 1fr;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 24px 70px rgba(16, 24, 40, 0.24);
}
.modal-head {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: start;
  padding: 18px 20px;
  border-bottom: 1px solid var(--line);
}
.modal-head h2 { margin-bottom: 4px; }
.modal-head p { margin: 0; }
/* Scope switch in the context modal: which neighbourhood is shown. It sits
   between the finding and the panes it governs, centred over them. */
.context-scope { justify-content: center; margin: 14px 0 2px; }
.context-scope .filter-chip:disabled { opacity: 0.45; cursor: default; }
.modal-close {
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 999px;
  background: #eef4fb;
  color: var(--ink);
  box-shadow: none;
  font-size: var(--text-2xl);
  line-height: 1;
}
.context-stack {
  display: grid;
  gap: 14px;
  overflow: auto;
  padding: 18px 20px 20px;
}
.context-message h3 {
  margin: 0 0 4px;
  font-size: var(--text-md);
  color: var(--accent);
}
/* Which vehicle a context column belongs to — so a neighbouring column that is
   a different vehicle reads as one, not as the same stream. */
.context-vehicle { margin: 0 0 8px; font-size: var(--text-xs); font-weight: 600; color: var(--muted); font-family: var(--mono); }
/* Previous, current and next side by side: a reader compares fields across
   the three, and a stack forces scrolling between every comparison. Below the
   width where three columns still fit they fall back to one. */
[data-context-messages] {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  align-items: stretch;
}
[data-context-messages].is-single { grid-template-columns: minmax(0, 1fr); }
/* Three JSON columns need the room; the other dialogs stay as they were. */
.modal-card.context-card { width: min(1600px, 100%); }
.context-message { display: flex; flex-direction: column; min-width: 0; }
.context-message pre {
  flex: 1;
  max-height: min(58vh, 620px);
  overflow: auto;
  margin: 0;
}
.context-message.is-current h3 { color: var(--navy-deep); }
@media (max-width: 1100px) {
  [data-context-messages] { grid-template-columns: minmax(0, 1fr); }
}
.context-finding {
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: 8px;
  padding: 10px 14px;
  font-size: var(--text-sm);
  background: #f8fafc;
}
.context-finding .cf-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.context-finding .cf-head code { background: rgba(15, 23, 42, 0.06); border-radius: 5px; padding: 2px 7px; font-size: var(--text-xs); }
.context-finding p { margin: 0; }
.context-finding .cf-rec { margin-top: 4px; color: #475569; }
.context-finding.sev-critical { border-left-color: #dc2626; background: #fef2f2; }
.context-finding.sev-major { border-left-color: #ea580c; background: #fff7ed; }
.context-finding.sev-minor { border-left-color: #ca8a04; background: #fefce8; }
.context-finding.sev-info { border-left-color: #2563eb; background: #eff6ff; }
.ctx-line { display: block; }

/* MQTT capture profiles */
.profile-form summary {
  cursor: pointer;
  font-weight: 700;
  color: var(--ink);
  font-size: var(--text-lg);
}
.profile-form[open] summary { margin-bottom: 14px; }
.profile-card { margin-top: 14px; }
/* The reports are a different subject from the live boxes above them, so the
   gap is wider than the 14px that separates one profile from the next. */
.dashboard-reports { margin-top: 28px; }
.profile-head { display: flex; justify-content: space-between; gap: 16px; align-items: start; flex-wrap: wrap; }
.profile-title h2 { margin-bottom: 4px; display: flex; align-items: center; gap: 10px; }
.profile-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.profile-actions form { margin: 0; }
.run-badge {
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.6px;
  color: #fff;
  background: #16a34a;
  border-radius: 999px;
  padding: 3px 10px;
}

/* A report signed by someone else says so at the top of the page. */
.brand-strip {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
  padding: 8px 14px;
  border-left: 4px solid #12356b;
  background: #f8fafc;
  border-radius: 6px;
  font-weight: 700;
}

.brand-strip img { max-height: 34px; }

/* The licence speaks up only when it changes what someone can do today. */
.license-banner {
  margin: 0 0 18px;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: var(--text-md);
  border: 1px solid #fcd34d;
  background: #fffbeb;
  color: #92400e;
}

.license-banner.expired {
  border-color: #fca5a5;
  background: #fef2f2;
  color: #991b1b;
}

.license-banner a { font-weight: 700; }

.license-status {
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.6px;
  border-radius: 999px;
  padding: 3px 12px;
  color: #fff;
  background: #16a34a;
}

.license-status.grace { background: #d97706; }
.license-status.expired { background: #dc2626; }

.feature-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 6px; }
.feature-list .tick { font-weight: 700; }
.feature-list .tick.yes { color: #16a34a; }
.feature-list .tick.no { color: #94a3b8; }
tr.row-over td { background: #fef2f2; }

/* An encrypted connection says so in the profile line; the trailing "!" marks
   the case where the certificate is deliberately not verified. */
.tls-badge {
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.5px;
  color: #0f766e;
  background: #ccfbf1;
  border: 1px solid #99f6e4;
  border-radius: 999px;
  padding: 1px 7px;
}
.live-chip {
  font-size: var(--text-xs);
  color: #334155;
  background: #f1f5f9;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 12px;
}
.runs-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); margin-top: 10px; }
.runs-table th, .runs-table td { text-align: left; padding: 6px 10px; border-bottom: 1px solid #eef2f7; }
.runs-table .run-active { background: #f0fdf4; }
.run-row-actions { display: flex; gap: 6px; align-items: center; }
.run-row-actions form { margin: 0; }

/* MQTT subscriber stream */
.sub-stream {
  max-height: 560px;
  min-height: 260px;
  overflow: auto;
  font-size: var(--text-xs);
  line-height: 1.5;
}
.sub-stream .st-head { display: block; color: #7dd3fc; font-weight: 700; }
.sub-stream .st-body { display: block; color: #e2e8f0; }
.success-note { color: #166534; background: #f0fdf4; border: 1px solid #bbf7d0; }

/* MQTT subscriber message inspector */
.inspector { display: grid; grid-template-columns: minmax(340px, 5fr) 6fr; gap: 16px; align-items: start; }
.inspector-detail { display: grid; gap: 16px; }
.msg-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 660px;
  overflow-y: auto;
  padding-right: 4px;
}
/* Grouped findings render as rows, not as spaced cards. */
.msg-list:has(.fg) { gap: 0; border: 1px solid var(--line); border-radius: 8px; }
.msg-item {
  display: grid;
  grid-template-columns: 10px 1fr auto;
  gap: 10px;
  align-items: center;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  box-shadow: none;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.msg-item:hover { background: #f8fafc; }
.msg-item.is-selected { border-color: #2563eb; box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.18); }
.msg-item .mi-dot { width: 10px; height: 10px; border-radius: 50%; background: #94a3b8; }
.msg-item .mi-main { min-width: 0; display: block; }
.msg-item .mi-topic {
  display: block;
  font-weight: 600;
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.msg-item .mi-meta { display: block; font-size: var(--text-2xs); color: var(--muted); }
.msg-item .mi-meta code { font-size: var(--text-2xs); }
.msg-item .mi-badge { font-size: var(--text-xs); font-weight: 700; color: #15803d; }
.msg-item.is-ok .mi-dot { background: #16a34a; }
.msg-item.is-ok { border-left: 3px solid #16a34a; }
.msg-item.sev-critical .mi-dot, .msg-item.sev-major .mi-dot { background: #dc2626; }
.msg-item.sev-critical { border-left: 3px solid #b91c1c; background: #fef2f2; }
.msg-item.sev-major { border-left: 3px solid #dc2626; background: #fef2f2; }
.msg-item.sev-minor { border-left: 3px solid #ca8a04; background: #fefce8; }
.msg-item.sev-minor .mi-dot { background: #ca8a04; }
.msg-item.sev-info { border-left: 3px solid #2563eb; background: #eff6ff; }
.msg-item.sev-info .mi-dot { background: #2563eb; }
.msg-item.sev-critical .mi-badge, .msg-item.sev-major .mi-badge { color: #b91c1c; }
.msg-item.sev-minor .mi-badge { color: #a16207; }
.msg-item.sev-info .mi-badge { color: #1d4ed8; }
.ri-list-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.ri-modes { display: flex; gap: 6px; }
.ri-modes .is-active { background: #2563eb; color: #fff; border-color: #2563eb; }
#ri-filters { margin: 10px 0 6px; }
#ri-filters .filter-chip span { opacity: 0.7; font-variant-numeric: tabular-nums; }
#ri-msg-list .mi-meta { white-space: normal; }
#sub-detail-findings, #ri-detail-findings { display: grid; gap: 10px; max-height: 260px; overflow-y: auto; }
.ok-note {
  margin: 0;
  padding: 10px 14px;
  border: 1px solid #bbf7d0;
  border-left: 4px solid #16a34a;
  border-radius: 8px;
  background: #f0fdf4;
  color: #166534;
  font-size: var(--text-sm);
}
@media (max-width: 1100px) {
  .inspector { grid-template-columns: 1fr; }
  .msg-list { max-height: 320px; }
}
.sub-h3 { margin: 14px 0 8px; font-size: var(--text-md); }
.compact-metrics { margin: 0 0 6px; }
.compact-metrics div strong { font-size: var(--text-2xl); }
.ctx-line.is-hl {
  background: rgba(220, 38, 38, 0.28);
  box-shadow: inset 3px 0 0 #ef4444;
  border-radius: 3px;
}
pre {
  max-height: 460px;
  overflow: auto;
  padding: 12px;
  background: #101828;
  color: #eff6ff;
  border-radius: 8px;
  font-size: var(--text-xs);
  line-height: 1.55;
}
@media (max-width: 760px) {
  .topbar { align-items: flex-start; flex-direction: column; gap: 10px; }
  .manual-layout { grid-template-columns: 1fr; }
  .manual-toc { position: static; }
  .policy-list { grid-template-columns: 1fr; }
  .upload-panel { grid-template-columns: 1fr; }
  th, td { padding: 9px; }
}

/* SaaS app shell refresh */
body {
  background:
    radial-gradient(circle at 16% 0%, rgba(0, 63, 145, 0.12), transparent 30rem),
    radial-gradient(circle at 88% 10%, rgba(106, 191, 43, 0.12), transparent 26rem),
    linear-gradient(135deg, #f8fbff 0%, var(--bg) 55%, #eef6fb 100%);
  color: var(--ink);
}
.app-shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 312px minmax(0, 1fr);
}
.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  padding: 18px 14px;
  background: linear-gradient(180deg, var(--navy-deep), #063c80 58%, #052f67);
  color: #fff;
  box-shadow: 14px 0 36px rgba(0, 43, 99, 0.16);
  overflow-y: auto;
}
.sidebar-brand {
  display: block;
  padding: 6px;
  margin-bottom: 6px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.sidebar .brand-logo {
  width: 256px;
  max-width: 100%;
  padding: 6px;
  border-radius: var(--radius-md);
  background: #fff;
}
.side-nav {
  display: grid;
  gap: 2px;
  /* A step below body size: navigation is scanned, not read, and it should
     not compete with the page it points at. */
  font-size: var(--text-sm);
}
.nav-section {
  margin: 18px 11px 6px;
  color: rgba(255, 255, 255, 0.45);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
/* Weight carries the hierarchy rather than shouting it: every item at 500,
   and the page you are on is the one bold thing in the column. */
.side-nav a {
  color: rgba(255, 255, 255, 0.82);
  text-decoration: none;
  font-weight: 500;
  letter-spacing: 0.004em;
  padding: 8px 11px;
  border-radius: var(--radius-md);
  transition: background 0.16s ease, color 0.16s ease, transform 0.16s ease;
}
.side-nav a:hover,
.side-nav a.active {
  color: #fff;
  background: rgba(255, 255, 255, 0.14);
  transform: translateX(2px);
}
.side-nav a.active {
  font-weight: 600;
  box-shadow: inset 3px 0 0 var(--green);
}
.app-content {
  min-width: 0;
}
.topbar {
  position: sticky;
  top: 0;
  display: flex;
  justify-content: flex-end;
  gap: 14px;
  min-height: 68px;
  padding: 14px 28px;
  background: rgba(255, 255, 255, 0.82);
  border-bottom: 1px solid rgba(216, 226, 239, 0.86);
  backdrop-filter: blur(18px);
}
.topbar-status,
.user-area {
  display: flex;
  align-items: center;
  gap: 10px;
}
.topbar-status {
  margin-right: auto;
  color: var(--muted);
  font-weight: 600;
}
.status-dot {
  width: 10px;
  height: 10px;
  border-radius: 999px;
  background: var(--green);
  box-shadow: 0 0 0 4px rgba(106, 191, 43, 0.16);
}
/* The dot names the instance at a glance: prod green, test blue, dev red. */
.status-dot--test { background: #2563eb; box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.16); }
.status-dot--dev { background: #dc2626; box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.16); }
.user-pill {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--navy-deep);
  font-weight: 700;
}
.container {
  width: min(1480px, calc(100% - 48px));
  margin: 28px auto 48px;
}
.auth-container {
  width: min(760px, calc(100% - 32px));
}
.page-head {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: end;
  margin-bottom: 22px;
}
.page-head h1 {
  font-size: clamp(28px, 3vw, 40px);
  color: var(--navy-deep);
}
.page-head p {
  max-width: 820px;
  margin: 0;
}
.page-actions {
  display: flex;
  /* 12px floor between boxes, same as everywhere else. */
  gap: 12px;
  flex-wrap: wrap;
  align-items: center;
}
.panel,
.metrics div,
.card,
.table-card,
.quick-card {
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid rgba(216, 226, 239, 0.96);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.panel,
.card,
.table-card,
.quick-card {
  padding: 20px;
}
.card-header,
.table-header,
.section-header {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  align-items: center;
  margin-bottom: var(--space-4);
}
.card-header p,
.table-header p,
.section-header p {
  margin: 3px 0 0;
}
.metrics {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-5);
}
.metrics div {
  min-height: 104px;
  padding: 18px;
}
.metrics div::before {
  width: 5px;
  background: linear-gradient(180deg, var(--navy), var(--green));
}
.metrics strong {
  color: var(--navy-deep);
  font-size: clamp(24px, 3vw, 34px);
}
.metrics span {
  color: var(--muted);
}
button,
.button {
  border-radius: var(--radius-md);
  background: linear-gradient(135deg, var(--navy), #0d5ec4);
  box-shadow: 0 10px 22px rgba(0, 63, 145, 0.18);
}
.button.secondary,
button.secondary {
  border: 1px solid var(--line);
  background: #fff;
  color: var(--navy);
  box-shadow: var(--shadow-sm);
}
.button.accent,
button.accent {
  background: linear-gradient(135deg, #5aa826, var(--green));
  color: #fff;
}
.button.danger,
button.danger {
  /* Light stop to dark, the way every other button is built — the two reds
     that used to be two tokens are one token and its highlight. */
  background: linear-gradient(135deg, #d92d20, var(--danger));
}
button:focus-visible,
.button:focus-visible,
a:focus-visible,
input:focus,
select:focus {
  outline: 3px solid rgba(47, 128, 237, 0.2);
  outline-offset: 2px;
}
.table-card {
  padding: 0;
  overflow: hidden;
}
.table-card > .table-header.panel {
  margin: 0;
  border-width: 0 0 1px;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: none;
  background: #fff;
}
.table-card .filter-bar {
  padding: 0 20px 14px;
}
.table-scroll {
  overflow-x: auto;
}
table {
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: #f0f6ff;
  color: var(--navy-deep);
}
tr:hover td {
  background: #f9fcff;
}
.badge,
.score-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 26px;
  padding: 4px 9px;
  border-radius: 999px;
  font-weight: 700;
  font-size: var(--text-xs);
}
.score-good { color: #166534; background: #dcfce7; }
.score-warn { color: #92400e; background: #fef3c7; }
.score-bad { color: #991b1b; background: #fee2e2; }
.severity-count {
  min-height: 26px;
  border-radius: 999px;
}
.empty-state {
  display: grid;
  place-items: center;
  gap: 10px;
  min-height: 180px;
  padding: 28px;
  text-align: center;
  color: var(--muted);
  border: 1px dashed var(--line);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.72);
}
.quick-actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
  margin: 18px 0;
}
.quick-card {
  display: grid;
  gap: 10px;
  color: inherit;
  text-decoration: none;
}
.quick-card strong {
  color: var(--navy-deep);
  font-size: var(--text-lg);
}
.quick-card span {
  color: var(--muted);
}
.quick-card:hover {
  transform: translateY(-2px);
  border-color: rgba(0, 63, 145, 0.26);
}
.form-grid {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
/* The setup panel asks two questions: what is analyzed, and what it is
   measured against. The second row holds pickers of unequal height — a list
   plus a line of explanation next to a plain select — so its cells align at
   the top instead of stretching to the tallest one. Stretching is what left
   the empty half-screen under the first four fields. */
/* Only the reference row needs this: its cells are of unequal height — a list
   plus a line of explanation next to a plain select — and stretching them to
   the tallest is what left the empty half-screen. Both rows hold exactly three
   fields and nothing full-width, so auto-fit collapses to the same three
   columns in each and they line up. */
.reference-grid { align-items: start; }
.form-group-title {
  margin: 20px 0 10px;
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: #64748b;
}
.form-group-title span {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  color: #94a3b8;
  margin-left: 8px;
}
/* Picking several of a handful of things: a checkbox list rather than a
   multi-select. Every option is visible without being discovered, and taking
   two of three needs no modifier key. Scrolls at six rows so a long shelf of
   factsheets cannot push the run button off the screen. */
/* auto-fill rather than auto-fit: the capture profile's run row holds one
   field when the account has no projects, and a lone select stretched across
   the whole panel reads as a mistake. Keeping the empty tracks also lines the
   row up with the three references underneath it. */
.run-grid { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
/* The analysis setup is one question with two halves: which file, and what is
   in it. The file gets the wider half - it carries a name, a message count and
   a kind on one line - and the two tick lists share the other. Below 60em the
   halves stack, which is the same order read down a phone. */
@media (min-width: 60em) {
  .analysis-setup.run-grid { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); align-items: start; }
}
.capture-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-4); align-items: start; }
.field { display: grid; gap: 6px; }
.field-label { font-weight: 700; color: #344054; }
.pick-list {
  border: 1px solid #d0d5dd;
  border-radius: 8px;
  background: #fff;
  max-height: 168px;
  overflow-y: auto;
}
.pick-item {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 7px 10px;
  border-top: 1px solid #eef2f7;
  font-weight: 400;
  cursor: pointer;
}
.pick-item:first-child { border-top: none; }
.pick-item:hover { background: #f8fafc; }
.pick-item input { width: auto; margin: 2px 0 0; flex: none; }
.pick-text { display: grid; gap: 1px; min-width: 0; }
/* Same treatment as the tables: the names in a 240px cell are file names and
   vehicle ids, and they wrap at their joints rather than wherever the line
   ran out. */
.pick-text strong { font-size: var(--text-md); color: #16233a; overflow-wrap: break-word; }
.pick-text small { color: #64748b; overflow-wrap: break-word; }
/* A factsheet or ruleset written for another target version is not a choice,
   so the list does not offer it. The empty note below says why a list is
   bare, which is what a greyed-out row used to be for. */
.pick-item[hidden] { display: none; }
.pick-empty {
  margin: 0;
  padding: 12px;
  border: 1px dashed #d0d5dd;
  border-radius: 8px;
  color: #64748b;
  font-size: var(--text-sm);
}
.pick-list > .pick-empty { border: none; padding: 10px 12px; }

/* The manufacturer switch is a modifier on the factsheet choice above it, so
   it reads as one sentence with its reason rather than as a fourth field. */
.inline-check.stacked { align-items: flex-start; margin-top: 12px; }
.inline-check.stacked input { margin-top: 3px; }
.inline-check.stacked small { display: block; font-weight: 400; margin-top: 2px; max-width: 90ch; }
.form-section {
  display: grid;
  gap: 14px;
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: #fbfdff;
}
.form-section h2,
.form-section h3 {
  margin-bottom: 2px;
}
.helper-text,
small {
  color: var(--muted);
  font-weight: 500;
}
input,
select {
  border-radius: var(--radius-md);
}
input[type="password"] {
  background-color: #fff !important;
}
.upload-panel {
  display: grid;
  gap: 14px;
  grid-template-columns: 1fr;
}
.file-picker {
  display: grid;
  place-items: center;
  min-height: 168px;
  padding: 24px;
  border: 2px dashed #9eb9da;
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, #ffffff, #f4f9ff);
  text-align: center;
}
.file-picker strong {
  display: block;
  color: var(--navy-deep);
  font-size: var(--text-lg);
}
.code-card pre,
pre {
  border: 1px solid #243145;
  border-radius: var(--radius-md);
}
.check-grid,
.check-grid.compact {
  grid-template-columns: 1fr;
}
.check-group {
  border-radius: var(--radius-lg);
  background: #fff;
  padding: 0;
  overflow: hidden;
}
.check-group legend {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  width: calc(100% + 2px);
  margin: -1px -1px 0;
  padding: 14px 16px;
  color: var(--navy-deep);
  background: #f4f8ff;
  border-bottom: 1px solid var(--line);
}
.check-group legend::after {
  content: "";
  display: table;
  clear: both;
}
.check-group .check-row {
  padding: 12px 16px;
}
.check-tools {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.analysis-footer {
  position: sticky;
  bottom: 0;
  z-index: 4;
  display: flex;
  justify-content: flex-end;
  padding: 14px 0 0;
  background: linear-gradient(180deg, rgba(245, 249, 255, 0), var(--bg) 46%);
}
.status-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
  margin: 16px 0;
}
.status-card {
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: #fff;
  box-shadow: var(--shadow-sm);
}
.status-card strong {
  display: block;
  margin-top: 4px;
  color: var(--navy-deep);
  font-size: var(--text-xl);
}
.status-card span {
  color: var(--muted);
  font-weight: 600;
}
.menu-button,
.nav-toggle {
  display: none;
}
@media (max-width: 980px) {
  .app-shell {
    grid-template-columns: 1fr;
  }
  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 30;
    width: min(280px, 86vw);
    transform: translateX(-105%);
    transition: transform 0.2s ease;
  }
  .nav-toggle:checked ~ .sidebar {
    transform: translateX(0);
  }
  .menu-button {
    display: inline-flex;
    align-items: center;
    min-height: 36px;
    padding: 8px 11px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: #fff;
    color: var(--navy);
    font-weight: 700;
    cursor: pointer;
  }
  .topbar {
    justify-content: space-between;
    padding: 12px 16px;
  }
  .topbar-status {
    display: none;
  }
  .container {
    width: min(100% - 24px, 100%);
    margin-top: 20px;
  }
  .page-head {
    align-items: start;
    flex-direction: column;
  }
}
@media (max-width: 640px) {
  .metrics,
  .quick-actions,
  .status-card-grid {
    grid-template-columns: 1fr;
  }
  .user-area {
    gap: 6px;
  }
  .user-pill {
    max-width: 140px;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  th,
  td {
    white-space: nowrap;
  }
}

/* Final app-shell guards: keep legacy global nav/card styles from leaking in. */
body .app-shell {
  display: grid;
  grid-template-columns: 312px minmax(0, 1fr);
}
body .app-shell .sidebar {
  display: block;
}
body .app-shell .side-nav {
  display: grid;
  flex-wrap: initial;
  gap: 4px;
}
body .app-shell .side-nav a,
body .app-shell .nav-section {
  display: block;
}
body .app-shell .sidebar .brand-logo {
  width: 256px;
  max-width: 100%;
}
body .app-shell .nav-toggle {
  display: none;
}
body .app-shell .quick-actions {
  display: grid;
}
body .app-shell .quick-card {
  display: grid;
  text-decoration: none;
}
@media (max-width: 980px) {
  body .app-shell {
    grid-template-columns: 1fr;
  }
  body .app-shell .sidebar {
    position: fixed;
  }
}

/* ---------- Detailed findings page ---------- */
.detail-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}
.detail-list { display: grid; gap: 16px; min-width: 0; }
.detail-group > h2 { font-size: var(--text-md); margin: 0 0 8px; color: #475569; }
.detail-check {
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  background: #fff;
  overflow: hidden;
  margin-bottom: 8px;
}
.detail-check-head {
  display: grid;
  /* severity · family · title · count */
  grid-template-columns: auto auto 1fr auto;
  gap: 10px;
  align-items: center;
  width: 100%;
  padding: 10px 12px;
  background: #f8fafc;
  border: 0;
  cursor: pointer;
  text-align: left;
}
.detail-check-head:hover { background: #f1f5f9; }
.detail-check-title { display: grid; min-width: 0; }
.detail-check-title code { font-size: var(--text-xs); color: #0f172a; }
.detail-check-title small { color: #64748b; }
.detail-count {
  background: #e2e8f0;
  border-radius: 999px;
  padding: 2px 9px;
  font-size: var(--text-xs);
  color: #334155;
}
.detail-check-body { padding: 10px 12px; border-top: 1px solid #e2e8f0; }
.detail-desc { margin: 0 0 6px; font-size: var(--text-sm); color: #334155; }
.spec-ref { margin: 0 0 8px; font-size: var(--text-2xs); color: #94a3b8; }
.finding-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.finding-entry {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 10px;
  align-items: center;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
  text-align: left;
}
.finding-entry:hover { border-color: #94a3b8; background: #f8fafc; }
.finding-entry.is-selected { border-color: #2563eb; background: #eff6ff; }
.finding-entry-main { display: grid; min-width: 0; }
.finding-entry-main code { font-size: var(--text-xs); color: #0f172a; }
.finding-entry-main small {
  color: #64748b;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.finding-msg { font-size: var(--text-2xs); color: #64748b; }

.detail-viewer { position: sticky; top: 16px; min-width: 0; }
/* Title and the Context button share the header row; the title is a check id
   plus a field path and must truncate rather than push the button out. */
.viewer-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.viewer-head h2 { margin: 0; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.viewer-head .button { flex: 0 0 auto; }
.code-view {
  margin: 10px 0 0;
  max-height: 62vh;
  overflow: auto;
  background: #0f172a;
  border-radius: 10px;
  padding: 10px 0;
  font-size: var(--text-xs);
  line-height: 1.55;
}
.code-line { display: grid; grid-template-columns: 44px 1fr; position: relative; }
.code-line .ln {
  text-align: right;
  padding-right: 10px;
  color: #64748b;
  user-select: none;
}
.code-line .lc { color: #e2e8f0; white-space: pre; padding-right: 12px; }
.code-line.is-highlighted { background: rgba(250, 204, 21, 0.16); }
.code-line.is-highlighted .lc { color: #fde68a; }
.code-line.is-highlighted .ln { color: #fbbf24; }
.code-line.is-highlighted::before {
  content: "";
  position: absolute;
  left: 0;
  width: 3px;
  height: 1.55em;
  background: #fbbf24;
}
@media (max-width: 1100px) {
  .detail-layout { grid-template-columns: 1fr; }
  .detail-viewer { position: static; }
}

/* ---------- Visualization page ---------- */
.viz-tabs { display: flex; gap: 6px; margin-bottom: 14px; }
.viz-tab {
  padding: 8px 16px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
  color: #475569;
  font-weight: 600;
}
.viz-tab.is-active { background: #2563eb; color: #fff; border-color: #2563eb; }
.viz-tab:disabled { opacity: 0.4; cursor: default; }
.check-tabs { margin: 14px 0 10px; }

/* Timing tolerance, at the head of the factsheet tab: a settings row rather
   than a check row, so it reads as something that governs the checks below. */
.tolerance-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 18px;
  margin-bottom: 12px;
  padding: 14px 16px;
}
.tolerance-bar > div:first-child { flex: 1 1 320px; }
.tolerance-bar strong { display: block; font-size: var(--text-md); }
.tolerance-bar span { color: var(--muted); font-size: var(--text-sm); }
.tolerance-bar label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.unit-field { display: flex; align-items: center; gap: 6px; }
.unit-field input { width: 5.5rem; }
.unit-field > span { font-size: var(--text-sm); font-weight: 600; color: var(--muted); }
.viz-toolbar {
  display: flex;
  gap: 16px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.viz-legend { display: inline-flex; gap: 10px; align-items: center; font-size: var(--text-xs); color: #64748b; }
.viz-legend .lg { width: 14px; height: 14px; border-radius: 4px; display: inline-block; vertical-align: middle; }
.lg-base { background: #2563eb; }
.lg-horizon { background: repeating-linear-gradient(45deg, #94a3b8, #94a3b8 3px, #cbd5e1 3px, #cbd5e1 6px); }
.lg-action { background: #f59e0b; }
.lg-lif { background: #475569; }

.viz-compat {
  margin: 0 0 12px;
  padding: 8px 12px;
  border-radius: 8px;
  font-size: var(--text-sm);
  line-height: 1.5;
  border: 1px solid #334155;
  background: #0b1220;
  color: #cbd5e1;
}
.viz-compat .cm { color: #94a3b8; font-weight: 600; }
.viz-compat.is-ok { border-color: #15803d; background: #0b1f14; color: #bbf7d0; }
.viz-compat.is-warn { border-color: #b45309; background: #241606; color: #fde68a; }
.viz-compat.is-fail { border-color: #b91c1c; background: #250d0d; color: #fecaca; }
.viz-compat.is-unknown { border-color: #334155; background: #0b1220; color: #cbd5e1; }

.vz-lif-edge { stroke: #334155; stroke-width: 1.5; }
.vz-lif-node { fill: #1e293b; stroke: #475569; stroke-width: 1; }

.viz-canvas-wrap { position: relative; }
#order-svg {
  width: 100%;
  height: auto;
  background: #0f172a;
  border-radius: 12px;
  border: 1px solid #1e293b;
}
.vz-edge { stroke: #3b82f6; stroke-width: 3; }
.vz-edge.vz-horizon { stroke: #64748b; stroke-dasharray: 6 5; }
.vz-edge.vz-has-action { stroke: #f59e0b; }
.vz-node { fill: #3b82f6; stroke: #0f172a; stroke-width: 2; cursor: pointer; }
.vz-node.vz-horizon { fill: #64748b; }
.vz-action-ring { fill: none; stroke: #f59e0b; stroke-width: 2.5; }
.vz-label { fill: #e2e8f0; font-size: var(--text-xs); font-weight: 600; }
.vz-node-g { cursor: pointer; }
.viz-tooltip {
  position: absolute;
  z-index: 5;
  background: #0b1220;
  color: #e2e8f0;
  border: 1px solid #334155;
  border-radius: 8px;
  padding: 6px 10px;
  font-size: var(--text-xs);
  pointer-events: none;
  max-width: 260px;
}

.diff-output { overflow-x: auto; }
.diff-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.diff-table th, .diff-table td {
  text-align: left;
  padding: 6px 10px;
  border-bottom: 1px solid #e2e8f0;
  vertical-align: top;
}
.diff-table code { font-size: var(--text-xs); color: #0f172a; }
.diff-changed { background: #fef9c3; }
.diff-added { background: #dcfce7; }
.diff-removed { background: #fee2e2; }

/* Honeypot: kept in the layout flow but off-screen, so people never see it
   while naive form-filling bots still find and complete it. Not display:none,
   which some bots specifically skip. Screen readers are excluded via
   aria-hidden and the field is removed from the tab order. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Preview marking for pre-release specifications (currently M2X 0.9.0) */
.preview-note {
  border-left: 4px solid var(--warning);
  margin-bottom: 16px;
}
.preview-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  background: #fef3c7;
  color: #854d0e;
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* LIF layout graph controls */
.viz-controls { display: inline-flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.viz-controls .button.compact { min-width: 34px; text-align: center; }
.toggle-inline {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--text-xs);
  font-weight: 600;
  color: #475569;
  cursor: pointer;
}
.toggle-inline input { width: auto; margin: 0; }

/* LIF graph tooltip error line */
.viz-tooltip .tip-error { color: #fca5a5; font-weight: 600; }

/* Run export dialog */
.export-card { width: min(520px, 100%); max-height: none; }
.export-body { padding: 18px 20px 20px; display: grid; gap: 14px; }
.export-body .form-section { display: grid; gap: 8px; margin: 0; }
/* Compact on purpose: with the full field padding this input stood 47px
   tall inside a 23px line, and the option it belongs to towered over the
   one above it. */
.inline-number { width: 92px; display: inline-block; margin: 0 4px; padding: 5px 8px; }

/* Dashboard: combined metric tiles and nested run rows */
.metric-of { font-size: 0.5em; font-weight: 700; color: var(--muted); }
.nested-runs { padding: 4px 8px; }
.nested-runs .runs-table { box-shadow: none; border: 0; background: transparent; margin: 0; }
.nested-runs .runs-table td { border-bottom: 1px solid #f1f5f9; padding: 4px 8px; }
.nested-runs .runs-table tr:last-child td { border-bottom: 0; }
.profile-summary-row > td { vertical-align: middle; }

/* Dashboard: runs listed under their profile */
.profile-first-row > td { border-top: 2px solid var(--line); }
.profile-first-row:first-child > td { border-top: 0; }

/* Run inspector: clearly separated from the profile list above it */
.run-inspector {
  margin-top: 26px;
  border: 2px solid var(--accent-2);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.6);
  box-shadow: 0 18px 40px rgba(37, 99, 235, 0.12);
  overflow: hidden;
}
.ri-banner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 16px 20px;
  background: linear-gradient(120deg, #1d4ed8, #0f766e);
  color: #fff;
}
.ri-banner h2 { margin: 2px 0 4px; color: #fff; }
.ri-banner p { margin: 0; color: rgba(255, 255, 255, 0.85); font-size: var(--text-sm); }
.ri-eyebrow {
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.75);
}
.ri-banner .button { background: rgba(255, 255, 255, 0.16); box-shadow: none; }
.run-inspector > .metrics,
.run-inspector > .inspector,
.run-inspector > .hint { margin: 16px; }

/* Findings grouped by the message they were found in.
   The list is a compact index by default; a row opens on click. */
.fg {
  border-bottom: 1px solid #eef2f7;
  background: #fff;
}
.fg:last-child { border-bottom: 0; }
.fg-head {
  display: grid;
  grid-template-columns: 14px 9px 1fr auto auto;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 7px 10px;
  background: transparent;
  border: 0;
  border-left: 3px solid transparent;
  border-radius: 0;
  box-shadow: none;
  cursor: pointer;
  font: inherit;
  color: inherit;
  text-align: left;
}
.fg-head:hover { background: #f6f9fd; transform: none; }
.fg.sev-critical .fg-head { border-left-color: #b91c1c; }
.fg.sev-major .fg-head { border-left-color: #dc2626; }
.fg.sev-minor .fg-head { border-left-color: #ca8a04; }
.fg.sev-info .fg-head { border-left-color: #2563eb; }
.fg.is-selected .fg-head { background: #eff6ff; }
.fg-caret { color: var(--muted); font-size: var(--text-2xs); text-align: center; }
.fg-dot { width: 9px; height: 9px; border-radius: 50%; background: #94a3b8; }
.fg.sev-critical .fg-dot, .fg.sev-major .fg-dot { background: #dc2626; }
.fg.sev-minor .fg-dot { background: #ca8a04; }
.fg.sev-info .fg-dot { background: #2563eb; }
.fg-main { min-width: 0; display: block; }
.fg-topic {
  display: block;
  font-weight: 600;
  font-size: var(--text-xs);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fg-meta {
  display: block;
  font-size: var(--text-2xs);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fg-count { font-size: var(--text-2xs); color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.fg-head .severity-count { font-size: var(--text-2xs); padding: 2px 7px; }
/* Closed by default - only an opened row shows its findings. */
.fg-body { display: none; }
.fg.is-open .fg-body {
  display: grid;
  gap: 6px;
  padding: 2px 12px 10px 36px;
  background: #fbfdff;
}
.fg-finding { border-left: 3px solid #cbd5e1; padding: 3px 0 3px 9px; }
.fg-finding.sev-critical { border-left-color: #b91c1c; }
.fg-finding.sev-major { border-left-color: #dc2626; }
.fg-finding.sev-minor { border-left-color: #ca8a04; }
.fg-finding.sev-info { border-left-color: #2563eb; }
.ff-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.ff-head .severity-count { font-size: var(--text-2xs); padding: 2px 7px; }
.ff-id { font-size: var(--text-xs); font-weight: 700; }
.ff-desc { display: block; margin-top: 2px; font-size: var(--text-xs); color: #475569; }
.fi-path { font-size: var(--text-2xs); background: rgba(15, 23, 42, 0.06); border-radius: 4px; padding: 1px 6px; }

/* Which checks a run applied, and why the others did not. */
.coverage-table td { vertical-align: middle; }
.coverage-group td {
  background: #f1f5f9;
  font-weight: 700;
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 6px 10px;
}
.coverage-row.cov-disabled td, .coverage-row.cov-unknown td { color: #94a3b8; }
.coverage-row.cov-disabled code, .coverage-row.cov-unknown code { opacity: 0.65; }
.cov-chip {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: var(--text-2xs);
  font-weight: 700;
  white-space: nowrap;
}
.cov-chip-evaluated { background: #dcfce7; color: #166534; }
.cov-chip-no_data { background: #e0f2fe; color: #075985; }
.cov-chip-disabled { background: #e2e8f0; color: #475569; }
.cov-chip-unknown { background: #f1f5f9; color: #64748b; }
.cov-chip-pass { background: #dcfce7; color: #166534; }
.cov-chip-fail { background: #fee2e2; color: #991b1b; }

/* Vehicle filter: a second row of chips under the severity filter. */
.vehicle-bar { margin-top: -4px; }
.filter-label {
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  align-self: center;
  margin-right: 2px;
}
.fg-vehicle { font-weight: 600; }

/* Enterprise management: short forms sit on one line, actions stay compact. */
.form-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
}
.form-row label { flex: 1 1 220px; margin: 0; }
.form-row .toggle-row { flex: 0 0 auto; align-self: center; }
.form-row button { flex: 0 0 auto; }
.enterprise-card + .enterprise-card { margin-top: 16px; }
/* One cell per setting, each a field with its own button underneath. The
   forms used to be flex rows aligned to their own bottom edge, so a card with
   five of them put five buttons at five different heights. */
/* Two columns, fixed: auto-fit gave four at desktop width and then dropped the
   last setting onto a row of its own with three empty cells beside it. */
.setting-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px 24px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 18px;
}
@media (max-width: 900px) { .setting-grid { grid-template-columns: 1fr; } }
/* The button sits at the bottom of its cell, so the buttons of one row line up
   even when one cell holds two fields and the next holds one. */
.setting { display: flex; flex-direction: column; }
.setting > label { margin: 0; }
.setting .form-grid { gap: 12px 16px; }
.setting .actions { margin: 12px 0 0; }
.setting > .actions:last-child { margin-top: auto; padding-top: 12px; }
/* Adding a member is the one frequent action, so it sits on one line directly
   above the list it changes rather than in the grid of settings. */
.setting-add {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 12px;
  margin-bottom: 16px;
}
.setting-add > label:first-child { flex: 1 1 240px; margin: 0; }
.setting-add .toggle-row { align-self: end; }
.row-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.inline-field { display: inline-flex; align-items: center; gap: 6px; }
.inline-field input { width: 8em; }
/* The reporting block only shows the fields of the chosen mode. */
#report-fields { display: grid; gap: 12px; margin-top: 10px; }

/* `hidden` must win over layout rules: .form-grid { display: grid } and friends
   are more specific than the UA's [hidden] { display: none }, so a block marked
   hidden stayed visible. */
[hidden] { display: none !important; }

/* Admin: occasional forms fold away, the user list groups by enterprise. */
.admin-form { margin-bottom: 12px; }
.admin-form > summary {
  cursor: pointer;
  font-weight: 700;
  font-size: var(--text-md);
  list-style: none;
}
.admin-form > summary::before { content: "▸ "; color: var(--muted); }
.admin-form[open] > summary::before { content: "▾ "; }
.admin-form > summary + * { margin-top: 14px; }
.search-box { margin: 0; min-width: 260px; }
.search-box input { width: 100%; }
.user-group + .user-group { margin-top: 6px; }
.user-group-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 14px 0 6px;
  padding: 0 4px;
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.user-group-count {
  background: #e2e8f0;
  color: #475569;
  border-radius: 999px;
  padding: 1px 8px;
  font-size: var(--text-2xs);
  letter-spacing: 0;
}
.row-manage > summary { cursor: pointer; font-size: var(--text-xs); font-weight: 600; white-space: nowrap; }
.row-manage-body { display: grid; gap: 6px; padding-top: 6px; min-width: 220px; }
.row-manage-body form { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.row-manage-body input { max-width: 150px; }

/* Topbar identity: who you are, which enterprise you act in, and what that
   lets you do. */
.user-context {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.06);
  color: #334155;
  font-weight: 700;
  font-size: var(--text-sm);
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.role-pill {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.role-admin { background: #dcfce7; color: #166534; }
.role-superuser { background: #dbeafe; color: #1e40af; }
.role-member { background: #e2e8f0; color: #475569; }
@media (max-width: 720px) {
  /* On a narrow bar the name and the role are what matter. */
  .user-context { display: none; }
}

/* Dashboard: the sections are separate topics, not one long list. */
.dash-section + .dash-section {
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
}
.refresh-note {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.05);
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.refresh-note::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #22c55e;
}

/* Project filter: a selector that sits in a page's action row. */
.project-filter { display: inline-flex; align-items: center; margin: 0; }
.project-filter label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.project-filter select { min-width: 190px; margin: 0; }

/* Action columns: icons instead of a row of labelled buttons, which wrapped
   into two lines once the owner and project columns arrived. */
.icon-actions {
  white-space: nowrap;
}
.icon-actions > *,
.icon-actions .inline-form {
  display: inline-flex;
  vertical-align: middle;
  margin: 0;
}
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  margin: 0 3px 0 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: #334155;
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.icon-button svg { width: 17px; height: 17px; fill: currentColor; }
.icon-button:hover { background: #eff6ff; border-color: #bfdbfe; color: var(--navy-deep); }
.icon-button.primary { border-color: #bfdbfe; color: var(--navy-deep); background: #eff6ff; }
.icon-button.primary:hover { background: #dbeafe; }
/* button.danger paints a solid red button; an icon needs the colour on the
   glyph, not behind it, so the background is reset here. */
button.icon-button.danger,
.icon-button.danger { background: #fff; border-color: var(--line); color: #b91c1c; }
button.icon-button.danger:hover,
.icon-button.danger:hover { background: #fee2e2; border-color: #fecaca; color: #991b1b; }
.icon-button:focus-visible { outline: 2px solid var(--navy-deep); outline-offset: 2px; }

/* A completed project is kept for reference, so its row reads as inactive. */
tr.is-completed td { color: var(--muted); }
tr.is-completed strong { font-weight: 600; }

/* No project yet: say so instead of leaving a gap where the selector belongs. */
.project-filter-empty {
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  border: 1px dashed var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: var(--text-xs);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.project-filter-empty:hover { border-color: #bfdbfe; color: var(--navy-deep); }

/* The project reads as context, the profile name as the subject. */
.profile-project { color: var(--muted); font-weight: 700; }

/* Live view: a map that fills its panel, and two panels of detail below it. */
.live-head-meta { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 0; }
.live-map { background: #0f172a; border-radius: 12px; overflow: hidden; }
.live-map svg { display: block; width: 100%; height: auto; }
.live-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
  margin: 10px 0 4px;
  font-size: var(--text-xs);
  color: var(--muted);
}
.live-legend i { display: inline-block; width: 12px; height: 12px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }
/* Fixed layout so a column's width no longer follows its content: live values
   change every frame, and an auto table re-flowed the columns on each update,
   making them jump. Longer values are clipped — acceptable here. */
.veh-table { table-layout: fixed; width: 100%; }
.veh-table td, .veh-table th {
  padding: 6px 10px; font-size: var(--text-xs);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; margin-right: 6px; }
/* The diff reads left to right: what it was, what it became. */
.diff-table { width: 100%; border-collapse: collapse; }
.diff-table td {
  padding: 5px 8px;
  font-size: var(--text-xs);
  border-bottom: 1px solid #eef2f7;
  font-family: var(--mono);
}
.diff-table td.k { color: #475569; font-family: inherit; }
.diff-row.changed td.a { background: #fee2e2; color: #991b1b; text-decoration: line-through; }
.diff-row.changed td.b { background: #dcfce7; color: #166534; font-weight: 700; }
.diff-row.same td { color: #94a3b8; }

/* Replay: transport controls above a scrub bar, both full width. */
/* The transport hovers as a floating overlay pinned to the bottom of the
   viewport, so it stays reachable whether you are looking at the map or the
   state diff — no need for a second copy further down the page. */
.replay-bar {
  display: grid;
  gap: 6px;
  position: fixed;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  z-index: 40;
  width: max-content;
  max-width: calc(100% - 24px);
  padding: 8px 12px;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.22);
  backdrop-filter: blur(4px);
  background: color-mix(in srgb, var(--bg) 90%, transparent);
}
/* The floating bar sits over the foot of the page; keep the last content clear
   of it. */
.replay-scroll-guard { height: 76px; }
.replay-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.replay-controls .icon-button { font-size: var(--text-xs); line-height: 1; }
/* The second transport beside the State diff heading: compact, and it pushes
   the vehicle picker to the right edge of the header. */
.replay-speed {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 0 auto;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.replay-speed select { margin: 0; min-width: 110px; }
#rp-scrub { width: min(340px, 60vw); margin: 0; accent-color: var(--navy-deep); }
.replay-position {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  font-weight: 700;
}
.replay-position .muted { font-weight: 600; }

/* ------------------------- selecting several rows ------------------------- */
/* The tick column stays narrow and out of the way; the bar that acts on the
   selection only appears once something is ticked, so a list nobody is tidying
   looks exactly as it did before. */
.bulk-col {
  width: 34px;
  padding-right: 0 !important;
  text-align: center;
}
.bulk-col input[type="checkbox"] {
  width: 15px;
  height: 15px;
  margin: 0;
  vertical-align: middle;
  accent-color: var(--navy-deep);
  cursor: pointer;
}
.bulk-form { display: none; }
.table-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.bulk-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 6px 5px 12px;
  border: 1px solid #fecaca;
  border-radius: 999px;
  background: #fef2f2;
}
.bulk-bar[hidden] { display: none; }
.bulk-count {
  font-size: var(--text-xs);
  font-weight: 700;
  color: #991b1b;
  white-space: nowrap;
}
.runs-toolbar {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
}
.runs-toolbar .bulk-bar { margin: 0; }

/* --------------------------- the chosen upload ---------------------------- */
/* Once a file is picked the box stops inviting and starts confirming: the
   document itself, by name and size, so nobody submits the wrong log. */
.file-picker .has-file {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 14px 20px;
  border: 1px solid #bfdbfe;
  border-radius: var(--radius-md);
  background: #fff;
  text-align: left;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}
.file-picker .fp-icon {
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  color: var(--navy-deep);
}
.file-picker .fp-text { display: block; }
.file-picker .fp-text strong {
  font-size: var(--text-md);
  word-break: break-all;
}
.file-picker .fp-text small { color: var(--muted); }

/* Editing a project happens in a dialog; the row only holds the trigger. */
.modal-card.edit-card { width: min(560px, 100%); }
.modal-body {
  display: grid;
  gap: 14px;
  padding: 18px 20px 20px;
}
.modal-body .actions { display: flex; gap: 10px; }

/* An irreversible action gets its own corner of the page, not a button that
   sits next to "Change password" as if it were of the same weight. */
.danger-zone {
  border-color: #fecaca;
  background: #fff7f7;
}
.danger-zone h2 { color: #991b1b; }
.danger-zone summary {
  cursor: pointer;
  font-weight: 700;
  color: #991b1b;
}
.danger-zone[open] summary { margin-bottom: 10px; }
.danger-zone details > form { display: grid; gap: 12px; margin-top: 12px; }

/* ------------------------- the administrator's overview ------------------- */
/* Everything in the installation on one page: grouped by enterprise, then by
   the user who produced it, with one row per report, layout or log. */
.filter-panel label.grow { flex: 1; }
.filter-panel .hint { margin-bottom: 8px; white-space: nowrap; }
.work-user {
  border-top: 1px solid var(--line);
  padding: 14px 20px 6px;
}
.work-user[hidden] { display: none; }
.work-user-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.work-user-head strong { font-size: var(--text-md); }
.work-user-head .hint { margin: 0 0 0 auto; }
.work-user .table-scroll table { font-size: var(--text-sm); }
.kind-chip {
  display: inline-block;
  min-width: 62px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-align: center;
  text-transform: uppercase;
}
.kind-report { background: #eff6ff; color: #1d4ed8; }
.kind-lif { background: #f0fdf4; color: #15803d; }
.kind-log { background: #fefce8; color: #a16207; }


/* ---------------------------------------------------------------- wall ----
   One box per capture profile, as on the MQTT page, with the vehicles it has
   heard as a list underneath. Colour says one thing per row: is this vehicle
   moving, standing, quiet, or in trouble. */
.wall-strip {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 10px;
  margin-bottom: 16px;
}

.wall-stat {
  background: #fff;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  padding: 10px 14px;
}

.wall-stat strong { display: block; font-size: var(--text-3xl); line-height: 1.1; color: #12356b; }
.wall-stat span { font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: 0.08em; color: #64748b; }
.wall-stat.sev-critical strong { color: #dc2626; }
.wall-stat.sev-major strong { color: #ea580c; }
.wall-stat.sev-minor strong { color: #ca8a04; }
.wall-stat.sev-info strong { color: #2563eb; }

/* The run counts of a profile, in the pills the run list uses, between what
   the profile is and what can be done with it. */
.profile-severity { display: flex; gap: 6px; align-items: center; margin-left: auto; padding-right: 14px; }
.profile-severity[hidden] { display: none; }

.wall-pulse { font-size: var(--text-xs); color: #64748b; align-self: center; }
/* The simulator's controls sit at the top, so they need air under them. */
.sim-actions { margin-bottom: 18px; }

/* What the workspace holds, under what it is doing. */
/* auto-fit rather than a fixed four: an account whose plan carries no custom
   rulesets gets three tiles, and a fixed grid would leave a hole where the
   fourth used to be. The min() keeps a single column on a narrow screen
   without a breakpoint of its own. */
.inventory-strip { grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr)); margin-top: 22px; }
.inventory-strip .wall-stat { text-decoration: none; display: block; }
.inventory-strip .wall-stat:hover { border-color: #94a3b8; }
.inventory-lists { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr)); gap: 14px; }
.recent-list { list-style: none; margin: 0; padding: 0; }
.recent-list li { padding: 7px 0; border-top: 1px solid #eef2f7; font-size: var(--text-sm); }
.recent-list li:first-child { border-top: none; }
.recent-list a { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recent-list .muted { font-size: var(--text-xs); }

/* The last few replay sources, offered as plain links inside their forms. */
.card-note {
  display: block;
  margin-top: 2px;
  font-size: var(--text-xs);
  font-weight: 500;
  color: #64748b;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.recent-replays form { display: flex; align-items: baseline; gap: 8px; }
.linklike {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--navy);
  cursor: pointer;
  text-align: left;
}
.linklike:hover { text-decoration: underline; }
.linklike:disabled { color: #94a3b8; cursor: default; text-decoration: none; }
.live-chip.muted { color: #94a3b8; }

/* The vehicle list inside a profile box. */
.veh-state {
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: #64748b;
}

tr.veh-driving .veh-state { color: #16a34a; }
tr.veh-silent .veh-state { color: #94a3b8; }
tr.veh-warning .veh-state { color: #c2410c; }
tr.veh-error .veh-state { color: #b91c1c; }
tr.veh-warning td { background: #fff7ed; }
tr.veh-error td { background: #fef2f2; }
tr.veh-silent td { color: #94a3b8; }

.v-uninit { color: #b45309; font-weight: 700; }


/* --- message context inside the live view --------------------------------
   The window that opens when a finding is clicked: what was found, and the
   message it was found in with the offending field marked. */
.list-more { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.ctx-finding {
  margin: 0 0 6px;
  padding-left: 8px;
  border-left: 3px solid #cbd5e1;
  font-size: var(--text-sm);
}

.ctx-finding.sev-critical { border-left-color: #dc2626; }
.ctx-finding.sev-major { border-left-color: #ea580c; }
.ctx-finding.sev-minor { border-left-color: #ca8a04; }
.ctx-finding.sev-info { border-left-color: #2563eb; }
.ctx-path { display: block; font-family: var(--mono); font-size: var(--text-xs); color: #64748b; }
#ctx-payload { max-height: 44vh; overflow: auto; }
.fg-head[role="button"] { cursor: pointer; }
.fg-head[role="button"]:hover { background: #f1f5f9; }

/* The finding rows in the live list carry their own text, not just a colour. */
.fg-items { list-style: none; margin: 4px 0 0; padding: 0 0 0 26px; }
.fg-items li {
  font-size: var(--text-sm);
  line-height: 1.45;
  color: #334155;
  padding: 2px 0 2px 8px;
  border-left: 3px solid #cbd5e1;
}

.fg-items li.sev-critical { border-left-color: #dc2626; }
.fg-items li.sev-major { border-left-color: #ea580c; }
.fg-items li.sev-minor { border-left-color: #ca8a04; }
.fg-items li.sev-info { border-left-color: #2563eb; }
.fg-items code { font-size: var(--text-xs); color: #12356b; }
.fg-path { font-family: var(--mono); font-size: var(--text-2xs); color: #64748b; }
.fg-rest { color: #94a3b8; border-left-color: transparent !important; }

/* What a report was made from, above the verdict it reached. */
.run-scope .scope-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px 18px; margin: 0; }
.run-scope dt { font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: 0.07em; color: #94a3b8; }
.run-scope dd { margin: 2px 0 0; font-size: var(--text-md); overflow: hidden; text-overflow: ellipsis; }
.run-scope h3 { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: #64748b; margin: 14px 0 6px; }
/* A caveat next to the field it qualifies. Quiet on purpose: it must be
   findable without competing with the value it stands beside. */
.run-scope dd .scope-note { display: block; margin-top: 3px; font-size: var(--text-2xs); color: #64748b; }
.run-scope dd .scope-note:hover { color: #334155; }
/* A passage quoted from the standard, set apart so our words and the
   committee's cannot be confused for one another. */
.spec-quote { margin: 0 0 14px; padding: 10px 14px; border-left: 3px solid #94a3b8; background: #f8fafc; }
.spec-quote p { margin: 0 0 6px; }
.spec-quote p:last-child { margin-bottom: 0; }
.type-counts { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; font-size: var(--text-sm); }
.type-counts li { background: #f1f5f9; border-radius: 999px; padding: 3px 11px; }
.type-counts .tc-count { font-weight: 700; color: #12356b; }
.run-scope h3 .scope-count { background: #12356b; color: #fff; border-radius: 999px; padding: 1px 9px; margin-left: 6px; font-size: var(--text-xs); }
.type-counts.vehicle-names li { font-family: var(--mono); }
/* What the vehicle was measured against, inside its own pill: the factsheet's
   file name, or "from the traffic". The pill grows a second line for it. */
.vehicle-names li .veh-sheet {
  display: block;
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: var(--text-2xs);
  color: #64748b;
  margin-top: 1px;
}
/* Measured or not, said in the list itself: an unmeasured vehicle standing
   next to a measured one must not read as a passing one. */
.type-counts.vehicle-names li.veh-measured { background: #dcfce7; color: #14532d; }
.type-counts.vehicle-names li.veh-measured::before { content: "✓ "; font-weight: 700; }
.type-counts.vehicle-names li.veh-unmeasured { background: #fef3c7; color: #78350f; }
.type-counts.vehicle-names li.veh-unmeasured::before { content: "○ "; font-weight: 700; }
.run-scope .error-hint { color: #92400e; margin: 8px 0 0; }
/* A switch that changes what gets measured belongs next to the thing it
   changes, reading as one sentence rather than as a second field. */
.inline-check { display: flex; align-items: center; gap: 8px; margin: 8px 0 0;
  font-weight: 500; font-size: var(--text-sm); text-transform: none; letter-spacing: 0; }
.inline-check input { width: auto; margin: 0; }

/* Which body of rules raised a finding. Five families, five muted grounds -
   quiet enough not to fight the severity pill beside them, distinct enough to
   be told apart at a glance down the column. The custom amber is the same one
   the complete verdict uses, so a reader meets it twice and learns it once. */
.fam {
  display: inline-block;
  padding: 2px 7px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.fam-vda { background: #e8eefb; color: #12356b; border-color: #c9d8f4; }
.fam-fs  { background: #e4f2ef; color: #0f5f58; border-color: #bfe0d9; }
.fam-m2x { background: #efeafc; color: #5b21b6; border-color: #ddd2f6; }
.fam-lif { background: #eef2f7; color: #44546b; border-color: #dae1ea; }
.fam-cr  { background: #fdf3e3; color: #78350f; border-color: #f0dcb7; }
/* A classification, not prose: the column takes what the badge needs and no
   more, so the sentence columns keep the room. */
.fam-cell { white-space: nowrap; width: 1%; }

/* ---- custom rulesets ---------------------------------------------------- */
/* The complete verdict stands next to the VDA5050 one, related but visibly
   its own row: an amber edge, because it includes rules the standard does
   not know. */
.complete-metrics { border-left: 3px solid #b7791f; }
.complete-metrics > div > strong { color: #78350f; }

/* Its own full-width row in the report: four columns, one of them a sentence.
   The id and the counts never wrap, so the label gets whatever is left. */
.custom-checks th:first-child, .custom-checks td:first-child { white-space: nowrap; }
.custom-checks th:nth-child(3), .custom-checks td:nth-child(3) { white-space: nowrap; }
.custom-checks th:last-child, .custom-checks td:last-child { text-align: right; width: 1%; white-space: nowrap; }
.custom-checks tr.muted td { color: #94a3b8; }

/* Nothing of its own any more: a custom ruleset's checks are built from the
   same .check-grid / .check-group / .check-row markup as every other tab, so
   they inherit its styling. Only the spacing where the profile form stacks
   the lists under the picker is left to say. */
.ruleset-checks { margin-top: 12px; }
/* A custom ruleset's tab sits in the same strip as the VDA5050 ones but carries the
   amber edge the complete verdict uses, so a glance says whose rules these
   are. Inside a tab the fieldset is already the whole panel, so it drops the
   extra top margin. */
.viz-tab.custom-tab { border-left: 3px solid #b7791f; }
.viz-tab.custom-tab.is-active { border-left-color: #78350f; }
.check-tab-panel .ruleset-checks { margin-top: 0; }
.custom-note { margin: 0 0 12px; color: var(--muted); font-size: var(--text-sm); max-width: 80ch; }

.ruleset-check { padding: 12px 0; border-top: 1px solid #eef2f7; }
.ruleset-check:first-child { border-top: none; }
.rc-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.rc-head .rc-id { font-size: var(--text-xs); color: #12356b; background: #f1f5f9; border-radius: 4px; padding: 1px 7px; }
.ruleset-check p { margin: 6px 0 0; max-width: 75ch; }
.rc-rule { margin-top: 6px; }
.rc-rule pre { background: #0f172a; color: #e2e8f0; border-radius: 8px; padding: 10px 12px; font-size: var(--text-xs); overflow-x: auto; }
.rc-rule summary { cursor: pointer; color: #64748b; font-size: var(--text-sm); }
tr.muted td { color: #94a3b8; }

/* A per-version run in the report list: the summary line reads like a normal
   run (white), its version reports nested underneath on a light grey and a
   touch smaller. */
tr.group-row td { background: transparent; }
.group-toggle { display: inline-flex; align-items: center; gap: 8px; background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; }
.group-toggle .group-caret { transition: transform .12s ease; color: var(--muted); }
.group-toggle.is-open .group-caret { transform: rotate(90deg); }
tr.subrow td { background: #f4f6f8; font-size: var(--text-2xs); }
tr.subrow .severity-count, tr.subrow .score-badge, tr.subrow .badge { font-size: var(--text-2xs); }
/* The version behind the run id, on every row now rather than only on the
   members of a per-version run. Quieter than the id it follows: the id is what
   you look for, the version is what tells you which of two runs of one file
   you are looking at. */
.row-lead { color: var(--muted); font-weight: 600; font-size: var(--text-2xs); margin-left: .35rem; }

/* The waiver line under a disposition is a secondary offer, not a heading. */
.toggle-row .toggle-note { font-size: var(--text-sm); font-weight: 400; color: var(--muted); }

/* The package download belongs to the verdict it exports, so it sits in that
   box, on the line of its heading, pushed to the right edge. */
.verdict-card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.verdict-card-head h2 { margin: 0; }

/* Zoom controls read as magnifiers, so the glyph says what it does without a
   tooltip. Square, so + and - keep the same footprint. */
.button.icon-only { display: inline-flex; align-items: center; justify-content: center; padding: 6px; }
.mag-icon { width: 1.15em; height: 1.15em; display: block; }

/* The platform mark beside the workspace dot: present, not loud. */
.platform-mark { height: 32px; width: auto; display: block; flex: 0 0 auto; }
.logo-preview { max-height: 64px; width: auto; display: block; margin: 8px 0 12px; }

/* ============================== Report tabs =============================== */
/* Findings and Check Coverage share one box: two views of the same run, so a
   tab rather than a second card. */
.report-tabs { display: flex; gap: 6px; margin-bottom: 6px; }
.report-tab {
  border: 1px solid #d7e0ea;
  border-radius: 8px 8px 0 0;
  background: #f1f5f9;
  padding: 8px 18px;
  font-size: var(--text-md);
  font-weight: 700;
  color: #475569;
  cursor: pointer;
}
.report-tab.is-active {
  background: #fff;
  color: #12356b;
  border-bottom-color: #fff;
}
.report-tab:hover:not(.is-active) { background: #e2e8f0; }

/* The coverage summary next to the verdict: counts only, the full list lives
   in the Check Coverage tab below. */
.coverage-summary { width: 100%; border-collapse: collapse; }
.coverage-summary th, .coverage-summary td {
  padding: 5px 8px;
  border-bottom: 1px solid #eef2f7;
  font-size: var(--text-sm);
  text-align: left;
}
.coverage-summary th.num, .coverage-summary td.num { text-align: right; }
.coverage-summary thead th { color: #64748b; font-size: var(--text-xs); }
.coverage-summary .cov-version { font-weight: 700; color: #12356b; vertical-align: top; }

/* The verdict table: findings pills sit before the score, so the pills say
   what the number cost. */
.verdict-scores .score-sev { text-align: right; white-space: nowrap; }
.verdict-scores .score-sev .severity-count { margin-left: 4px; }
.verdict-scores td.score-val { text-align: right; font-weight: 700; color: #12356b; width: 3.5em; }

/* ====================== Detailed findings as a tree ======================= */
/* An opened family must read as one block: the head keeps the accent, the
   children hang from a rail and step in, slightly smaller than the head. */
.detail-check.is-open { border-color: #2563eb; box-shadow: 0 1px 4px rgba(37, 99, 235, 0.12); }
.detail-check.is-open > .detail-check-head { background: #eff6ff; }
.detail-check.is-open > .detail-check-head .detail-check-title code { color: #12356b; }
.detail-check-body {
  margin-left: 14px;
  border-left: 3px solid #bfdbfe;
  padding-left: 14px;
}
.detail-check-body .finding-entry { font-size: var(--text-sm); padding: 6px 10px; }
.detail-check-body .finding-entry-main code { font-size: var(--text-2xs); }

/* =========================== Spacing, standing rule ====================== */
/* Two stacked things keep at least one step of air between them - boxes on a
   page, and everything inside a box just the same. It used to hold for the
   boxes only, which is how a paragraph came to sit flush under the grid it
   belongs to and a pill to touch the heading beside it.
   Adjacent margins collapse, so anything that brings more space keeps it;
   this is a floor, not a setting. Grids and flex rows bring their own gap and
   it must not fall under --space-3 either.
   Kept at the end of the file on purpose: it ties with the single-class rules
   of the components and wins by order, so a box cannot quietly opt out of the
   floor by setting margin: 0 somewhere above. Where a component really must
   sit tighter, it says so with two classes - and then it is a decision
   somebody wrote down rather than a default nobody chose. */
main.container > * + * { margin-top: var(--space-3); }
.panel > * + *,
.card > * + *,
.table-card > * + *,
.quick-card > * + *,
.modal-card > * + *,
.section-head > * + *,
.table-header > div > * + * { margin-top: var(--space-3); }

/* A pill is a word in a box, and a box needs air on every side. In a row of
   them the row sets the gap; standing alone next to text it brings its own,
   so a severity chip cannot end up against the sentence it qualifies. */
:where(.severity-count, .score-pill, .coverage-pill, .verdict-pill, .run-badge,
       .perf-unacked, .tls-badge) { margin-right: var(--space-2); }
:where(.severity-count, .score-pill, .coverage-pill, .verdict-pill, .run-badge,
       .perf-unacked, .tls-badge):last-child { margin-right: 0; }
/* An inline code pill is a box in the middle of a sentence and needs the same
   air as any other box - without it the border sits against the word beside
   it and the line reads as one long token. */
:where(p, li, td, dd, summary, .hint, .rc-head) > code { margin: 0 2px; }
/* Except where a flex row already spaces them - a margin on top of a gap is
   two answers to one question. */
:where(.verdict-top, .verdict-severities, .type-counts, .icon-actions, .page-actions,
       .pick-list, .coverage-gates) :where(.severity-count, .score-pill, .coverage-pill,
       .verdict-pill, .run-badge, .perf-unacked, .tls-badge) { margin-right: 0; }

/* ========================= Performance review ============================ */
.performance-table th.num, .performance-table td.num { text-align: right; }
.performance-table .perf-span { font-size: var(--text-xs); color: #64748b; white-space: nowrap; }
.perf-error-link { font-weight: 700; color: #991b1b; text-decoration: underline; }
.perf-definitions { margin: 8px 0 0; padding-left: 18px; display: grid; gap: 6px; }
.perf-definitions li { font-size: var(--text-sm); color: #334155; }
.perf-unacked {
  display: block;
  font-size: var(--text-2xs);
  font-weight: 700;
  color: #991b1b;
  white-space: nowrap;
}

/* The timeframe control on the Performance Review. One line: the recording's
   own span, the two moments, the button. It is a setting for the page, not a
   subject of its own, so it takes a strip rather than a panel with a heading -
   the answer underneath is what the reader came for. */
.timeframe-bar { padding: 10px 14px; }
.timeframe-bar form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  font-size: var(--text-sm);
}
.timeframe-span { margin-right: auto; color: var(--muted); }
.timeframe-span strong { color: var(--ink); margin-right: .5rem; }
.timeframe-span em { font-style: normal; }
.timeframe-to { color: var(--muted); }
.timeframe-bar input { width: 12.5rem; font-size: var(--text-sm); padding: 5px 8px; }
.timeframe-bar .error { margin: 8px 0 0; }

/* The rules of the Performance Review sit behind a footnote: a reader who
   knows them should not scroll past them under every table. */
.perf-footnote { margin: 14px 0 0; font-size: var(--text-xs); color: var(--muted); }
.linklike {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--accent-2);
  text-decoration: underline;
  cursor: pointer;
}
.modal-card.perf-help-card { width: min(720px, 100%); }
