/* ============================================================
   vibeship.eu — the signed-in dashboard.
   Same dark devtool aesthetic as the rest of the site; every
   colour and radius comes from the tokens in styles.css.
   ============================================================ */

.dash {
  max-width: 820px;
  margin: 0 auto;
  padding: 48px 24px 100px;
}

/* ---------- the sign-in gate ---------- */
/* Shown while /v1/auth/me is in flight and whenever the answer isn't a
   signed-in user. Centred and sparse so a signed-out visitor sees a
   deliberate page rather than a broken one. */
.dash-gate {
  text-align: center;
  padding: 60px 0 80px;
}
.dash-gate h1 {
  font-size: clamp(28px, 5vw, 38px);
  margin: 0 0 12px;
  letter-spacing: -0.02em;
}
.dash-gate-text {
  color: var(--text-dim);
  max-width: 44ch;
  margin: 0 auto 28px;
}
.dash-gate-text a { color: var(--accent); }
.dash-gate-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}

/* ---------- hero ---------- */
.dash-hero { margin-bottom: 36px; }
.dash-hero h1 {
  font-size: clamp(30px, 6vw, 44px);
  margin: 0 0 8px;
  letter-spacing: -0.02em;
}
.dash-sub { color: var(--text-dim); margin: 0; }

/* ---------- cards ---------- */
/* A flat fill made every card the same slab as the one above it. The gradient
   is barely there — six values of lightness — but it gives each card a top edge
   to catch the eye and a bottom that recedes, which is all the separation a
   stack of them needs. */
.dash-card {
  background: linear-gradient(180deg, #17171c 0, #131317 100%);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 24px;
  margin-bottom: 24px;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.03) inset,
    0 12px 34px -22px rgba(0, 0, 0, 0.9);
}
/* The launcher is the one destructive action on the page; the glow is how it
   reads as different from the four save buttons above it. */
.dash-card-accent {
  border-color: var(--border-2);
  box-shadow: 0 0 0 1px var(--accent-glow);
}
.dash-card h2 {
  font-size: 18px;
  margin: 0 0 6px;
  letter-spacing: -0.01em;
}
.dash-card-lead {
  color: var(--text-dim);
  font-size: 14px;
  margin: 0 0 20px;
  max-width: 62ch;
}

/* ---------- site switcher ---------- */
.dash-siteswitch { padding: 16px 24px; }
.dash-siteswitch-row {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 16px;
  flex-wrap: wrap;
}
.dash-siteswitch .audit-label { margin: 0 0 6px; }
#dash-site-select {
  min-width: 16rem;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
#dash-site-select:hover:not(:disabled) { border-color: var(--border-2); }
#dash-site-select:focus-visible { box-shadow: 0 0 0 3px var(--accent-glow); }
#dash-site-select:disabled { opacity: 0.8; cursor: default; }

/* ---------- section tabs ---------- */
/* Underline rather than a pill row: the panel below is a stack of bordered
   cards already, and a second boxed control on top of it reads as clutter. */
.dash-tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 24px;
  border-bottom: 1px solid var(--border);
}
.dash-tab {
  position: relative;
  appearance: none;
  background: none;
  border: 0;
  margin-bottom: -1px;
  padding: 10px 14px 12px;
  font: inherit;
  font-size: 14px;
  color: var(--text-mute);
  cursor: pointer;
  transition: color 160ms ease;
}
/* The underline is drawn on the tab rather than as a border, so it can grow
   from the middle instead of appearing fully formed on the tab you landed on. */
.dash-tab::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transition: transform 200ms cubic-bezier(.2,.7,.3,1);
}
.dash-tab:hover { color: var(--text-dim); }
.dash-tab:hover::after { transform: scaleX(0.4); background: var(--border-2); }
.dash-tab:focus-visible {
  outline: none;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  box-shadow: 0 0 0 3px var(--accent-glow);
}
.dash-tab-on { color: var(--text); }
.dash-tab-on::after,
.dash-tab-on:hover::after { transform: scaleX(1); background: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .dash-tab::after { transition: none; }
}

/* A set-once field that's been committed. */
.dash-lock {
  color: var(--text-dim);
  border-left: 2px solid var(--border-2);
  padding-left: 10px;
}

/* ---------- inputs ---------- */
/* Matches #url on the audit page: monospace, inset, accent ring on focus. */
.dash input[type="text"],
.dash input[type="url"],
.dash input[type="password"],
.dash select {
  width: 100%;
  font-family: var(--mono);
  font-size: 14px;
  padding: 11px 13px;
  background: var(--code-bg);
  border: 1px solid var(--border-2);
  border-radius: var(--radius-sm);
  color: var(--text);
  outline: none;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.dash input:focus,
.dash select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-glow);
}
.dash input::placeholder { color: var(--text-mute); }
.dash .audit-label { margin-top: 16px; }
.dash form > .audit-label:first-child,
.dash fieldset > .audit-label:first-of-type { margin-top: 0; }

.dash-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
.dash-grid .audit-label { margin-top: 16px; }

.dash-fieldset {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 18px;
  margin: 0 0 20px;
}
.dash-fieldset legend {
  padding: 0 8px;
  font-size: 13px;
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* The "use a throwaway account" warning. Amber rather than red: it is a
   caution about consequences, not a report of something broken. */
.dash-warn {
  color: var(--amber);
  border-left: 2px solid var(--amber);
  padding-left: 10px;
  margin-top: 0;
}

/* The source-code connection. It sits below the "link to your code" form in
   the same card, so it needs a little air above it to read as the separate
   thing it is: the link is a note, this is a permission. */
#gh-block { margin-top: 24px; }

/* Additional projects. A list rather than a table: each row is one link plus
   one sentence about whether we can actually read it, and the sentence is the
   point — a paid-for project nobody shared on GitHub must not look covered. */
#extra-repos-block { margin-top: 24px; }
.extra-repos { list-style: none; margin: 0 0 16px; padding: 0; }
.extra-repo {
  display: flex; flex-wrap: wrap; gap: 8px 16px;
  justify-content: space-between; align-items: flex-start;
  padding: 10px 0; border-top: 1px solid var(--line, rgba(0,0,0,0.08));
}
.extra-repo:last-child { border-bottom: 1px solid var(--line, rgba(0,0,0,0.08)); }
.extra-repo-main { flex: 1 1 260px; min-width: 0; }
.extra-repo-main code { font-size: 13px; word-break: break-all; }
.extra-repo-main .audit-hint { margin: 4px 0 0; }
.extra-repo .dash-actions { margin: 0; }
.btn-small { padding: 4px 10px; font-size: 13px; }
/* The "we can read this" line: green because it is the one row state that
   means the money is buying something. */
.dash-ok { color: var(--green); margin-top: 0; }
.gh-state {
  margin: 0 0 10px;
  font-size: 14px;
  color: var(--text);
}
#gh-repo { margin-bottom: 4px; }

.dash-details { margin-top: 18px; }
.dash-details summary {
  cursor: pointer;
  font-size: 13px;
  color: var(--text-dim);
  user-select: none;
}
.dash-details summary:hover { color: var(--text); }
.dash-details[open] summary { margin-bottom: 12px; }

/* ---------- save row ---------- */
.dash-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 20px;
  flex-wrap: wrap;
}
.dash-status { font-size: 13px; color: var(--text-mute); }
.dash-status-busy { color: var(--text-dim); }
.dash-status-ok   { color: var(--green); }
.dash-status-bad  { color: var(--red); }

/* ---------- what the next run will cover ---------- */
/* Shown before the launch button so "we never tested this" is something the
   user reads now, not half an hour later in the report. */
.dash-coverage {
  list-style: none;
  margin: 0 0 22px;
  padding: 0;
  display: grid;
  gap: 8px;
}
.dash-cov {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  font-size: 13px;
  line-height: 1.5;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
}
.dash-cov span:first-child {
  font-family: var(--mono);
  font-weight: 700;
  line-height: 1.4;
}
.dash-cov-yes {
  color: var(--text-dim);
  border-color: rgba(74, 222, 128, 0.28);
}
.dash-cov-yes span:first-child { color: var(--green); }
.dash-cov-no {
  color: var(--text-dim);
  border-color: rgba(245, 158, 11, 0.32);
  background: rgba(245, 158, 11, 0.05);
}
.dash-cov-no span:first-child { color: var(--amber); }

/* The jump to the form that closes the gap. A link, not a button, because it
   navigates rather than acts — it just happens to move a tab, not a page. */
.dash-cov-fix {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: inherit;
  color: var(--accent);
  cursor: pointer;
  white-space: nowrap;
}
.dash-cov-fix:hover { text-decoration: underline; }
.dash-cov-fix:focus-visible {
  outline: none;
  border-radius: 3px;
  box-shadow: 0 0 0 3px var(--accent-glow);
}

/* ---------- authorization checkbox ---------- */
.dash-consent {
  display: flex;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--border-2);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.5;
  cursor: pointer;
}
.dash-consent input { margin-top: 3px; accent-color: var(--accent); }
.dash-consent strong { color: var(--text); }

.dash .btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ---------- history ---------- */
.dash-history { display: grid; gap: 8px; }
.dash-empty { color: var(--text-mute); font-size: 14px; margin: 0; }

.dash-run {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 14px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  transition: border-color 120ms ease, background 120ms ease;
}
.dash-run:hover {
  border-color: var(--border-2);
  background: var(--bg-elev);
}
.dash-run-when {
  font-size: 13px;
  color: var(--text-mute);
  white-space: nowrap;
}
.dash-run-url {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- danger zone ---------- */
.dash-danger { border-color: rgba(239, 68, 68, 0.3); }
.dash-danger h2 { color: var(--red); }
.btn-danger {
  background: transparent;
  color: var(--red);
  border: 1px solid rgba(239, 68, 68, 0.5);
}
.btn-danger:hover { background: rgba(239, 68, 68, 0.1); border-color: var(--red); }
.btn-danger[disabled] { opacity: 0.5; cursor: not-allowed; }

/* ---------- onboarding wizard ---------- */
.wiz-steps {
  list-style: none;
  display: flex;
  gap: 8px;
  margin: 0 0 20px;
  padding: 0;
  flex-wrap: wrap;
}
.wiz-steps li {
  flex: 1;
  min-width: 8rem;
  font-size: 13px;
  color: var(--text-mute);
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  border-bottom: 2px solid var(--border);
}
.wiz-steps li.wiz-on {
  color: var(--text);
  border-bottom-color: var(--accent);
}
.wiz-steps li.wiz-done {
  color: var(--text-dim);
  border-bottom-color: var(--green);
}
.wiz-choice { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }

/* ---------- recon output ---------- */
.recon-out { margin-top: 18px; display: grid; gap: 18px; }
.recon-out:empty { margin-top: 0; }
.recon-group h4 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  margin: 0 0 8px;
}
.recon-facts {
  display: grid;
  grid-template-columns: 10rem 1fr;
  gap: 4px 12px;
  margin: 0;
  font-size: 14px;
}
.recon-facts dt { color: var(--text-mute); }
.recon-facts dd { margin: 0; color: var(--text); font-family: var(--mono); font-size: 13px; }
.recon-items { margin: 0; padding: 0; list-style: none; display: grid; gap: 4px; }
.recon-items.recon-mono li { font-family: var(--mono); font-size: 12.5px; }
.recon-items li { color: var(--text-dim); font-size: 14px; word-break: break-all; }
.recon-items.recon-mono { columns: 2; column-gap: 24px; }
.recon-items.recon-mono li { break-inside: avoid; }
.recon-more { color: var(--text-mute) !important; font-style: italic; }

/* API descriptor: the money find — make it stand out. */
.recon-alarm {
  border: 1px solid var(--red);
  background: rgba(239, 68, 68, 0.06);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
}
.recon-alarm h4 { color: var(--red); }
.recon-warn {
  border: 1px solid var(--amber);
  background: rgba(245, 158, 11, 0.06);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
}
.recon-warn h4 { color: var(--amber); }
.recon-hit {
  border: 1px solid rgba(124, 92, 255, 0.3);
  background: rgba(124, 92, 255, 0.05);
  border-radius: var(--radius-sm);
  padding: 14px;
}
.recon-hit .recon-items { columns: 1; }
.recon-hit a { color: var(--accent); }
.recon-kind {
  font-family: var(--mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(124, 92, 255, 0.15);
  color: var(--accent);
}

@media (max-width: 560px) { .recon-items.recon-mono { columns: 1; } }

/* ============================================================
   your last result
   ============================================================
   The top of the page, and the reason most visits happen. Three moves, in
   the order someone reads them: a verdict tinted by the grade, the charts
   naming what the test actually went after, then the list of what to do.

   The charts are where the visual budget goes. They are the part of the
   report that can say "this stage tested nothing" — a distinction the data
   loses and a reader can't afford to, since a stage with no service
   description to work through has ruled nothing out. Everything around them
   stays quiet so they read as the instrument panel they are. */

.dash-results { margin-bottom: 28px; }

/* ---------- verdict ---------- */
.res-head {
  background: var(--bg-card);
  border: 1px solid var(--border);
  /* The grade's colour, as a hairline along the top edge. Set per tone
     below; the neutral default covers a run still in flight. */
  border-top: 3px solid var(--border-2);
  border-radius: var(--radius);
  padding: 26px 28px 22px;
}
.res-tone-good { border-top-color: var(--green); }
.res-tone-warn { border-top-color: var(--amber); }
.res-tone-bad  { border-top-color: var(--red); }
/* A run that produced nothing gets no colour at all. Amber would read as
   "some concerns" and green as "fine"; both are claims about a site nobody
   managed to test. */
.res-tone-stalled { border-top-style: dotted; border-top-color: var(--text-dim); }
.res-tone-stalled h2 { margin: 0; font-size: 22px; letter-spacing: -0.02em; }

.res-eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-mute);
  margin: 0 0 14px;
  word-break: break-word;
}
/* Who the site sells to, as decided by the trust stage. A pill in the
   eyebrow rather than a card of its own: it is context for the result, not
   a result. The colour is a hue per audience, never a grade colour — "sells
   to people" is not good or bad news. */
.res-audience {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 9px;
  border: 1px solid var(--border-2);
  border-radius: 999px;
  font-family: var(--sans, inherit);
  font-size: 11.5px;
  letter-spacing: 0.01em;
  color: var(--text);
  vertical-align: middle;
  cursor: help;
}
.res-audience-b2b  { border-color: rgba(96, 165, 250, 0.45); }
.res-audience-b2c  { border-color: rgba(192, 132, 252, 0.45); }
.res-audience-both { border-color: rgba(45, 212, 191, 0.45); }
.res-audience-unclear { border-style: dashed; color: var(--text-dim); }
.res-verdict-row { display: flex; align-items: center; gap: 18px; }
.res-verdict-row h2 {
  margin: 0;
  font-size: 26px;
  line-height: 1.2;
  letter-spacing: -0.02em;
}
/* The one hero figure on the page: the score drawn as the share of 100 it
   actually is, with the letter it produces underneath. The ring, the number
   and the letter all come out of gradeFromFindings, so none of the three can
   contradict the other two. */
.res-dial {
  position: relative;
  flex: none;
  width: 128px;
  height: 128px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.res-dial svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.res-dial-track { fill: none; stroke: var(--border); stroke-width: 8; }
.res-dial-fill {
  fill: none;
  stroke: currentColor;
  stroke-width: 8;
  stroke-linecap: round;
}
.res-dial-good { color: var(--green); }
.res-dial-warn { color: var(--amber); }
.res-dial-bad  { color: var(--red); }
/* Proportional figures: at this size tabular digits make a number like 121
   read as loose. */
.res-dial-num {
  position: relative;
  font-size: 38px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--text);
}
.res-dial-letter {
  position: relative;
  margin-top: 7px;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: currentColor;
}

/* The stat strip under the headline: how much of the run reported, how much
   held, how much didn't, how long it took. */
.res-stats {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 24px;
  margin: 10px 0 0;
  padding: 0;
}
.res-stats li { display: flex; align-items: baseline; gap: 6px; }
.res-stat-num { font-size: 17px; font-weight: 600; color: var(--text); }
.res-stat-label { font-size: 13px; color: var(--text-mute); }

.res-count { margin: 4px 0 0; color: var(--text-dim); font-size: 15px; }
.res-verdict { margin: 16px 0 0; color: var(--text-dim); }
.res-jump {
  margin: 16px 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
}
.res-jump a,
.res-more a {
  color: var(--accent);
  font-size: 15px;
  border-bottom: 1px solid transparent;
}
.res-jump a:hover,
.res-more a:hover { border-bottom-color: var(--accent); }
.res-more { margin: 14px 0 0 12px; font-size: 14px; }

/* Progress, for a run still going. */
.res-head-live h2 { margin: 0; font-size: 26px; letter-spacing: -0.02em; }
.res-meter {
  height: 6px;
  margin-top: 18px;
  border-radius: 999px;
  background: var(--bg);
  overflow: hidden;
}
.res-meter-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  transition: width .4s ease;
}
@media (prefers-reduced-motion: reduce) {
  .res-meter-fill { transition: none; }
}

/* ============================================================
   the instrument panel
   ============================================================
   Three drawings of the same run, in the order the questions get asked: what
   could this test see, how much did each part turn up, and where did the half
   hour go. One axis each, and every value written next to its bar as well as
   drawn — nothing here is readable only by comparing lengths.

   The rule the whole panel is built around: a part that tested nothing is
   drawn as a gap, never as a short bar. A short bar is a claim ("we looked and
   found little"); the dotted track is the truth ("we didn't look"). */

.res-instruments {
  margin-top: 16px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px 24px 20px;
}
.res-h3 {
  margin: 26px 0 12px;
  font-size: 15px;
  letter-spacing: -0.01em;
}
.res-instruments > .res-h3:first-child { margin-top: 0; }

/* ---------- what this test could see ---------- */
/* Same shape as the coverage list on the launcher, deliberately: one is what
   the next run will reach, this is what the last one did. */
.res-scope {
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
  display: grid;
  gap: 8px;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.res-chip {
  display: flex;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.res-chip-mark { font-family: var(--mono); font-weight: 700; line-height: 1.4; }
.res-chip-label { font-size: 13px; color: var(--text); }
.res-chip p { margin: 2px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--text-mute); }
.res-chip-yes { border-color: rgba(74, 222, 128, 0.28); }
.res-chip-yes .res-chip-mark { color: var(--green); }
.res-chip-no {
  border-color: rgba(245, 158, 11, 0.32);
  background: rgba(245, 158, 11, 0.05);
}
.res-chip-no .res-chip-mark { color: var(--amber); }

/* ---------- the bar charts ---------- */
.chart + .chart { margin-top: 26px; }
.chart-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.chart-head h4 { margin: 0; font-size: 14px; letter-spacing: -0.01em; }
.chart-note { font-family: var(--mono); font-size: 12px; color: var(--text-mute); }
.chart-key {
  list-style: none;
  display: flex;
  gap: 16px;
  margin: 0;
  padding: 0;
  font-size: 12px;
  color: var(--text-mute);
}
.chart-key li { display: flex; align-items: center; gap: 6px; }
.key { display: block; width: 10px; height: 10px; border-radius: 2px; }
.key-bad  { background: var(--red); }
.key-warn { background: var(--amber); }

.cbars { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.cbar {
  display: grid;
  grid-template-columns: minmax(7rem, 11rem) 1fr minmax(4rem, auto);
  grid-template-areas: "name track val";
  align-items: center;
  gap: 12px;
}
.cbar-name { grid-area: name; font-size: 13px; color: var(--text-dim); }
.cbar-track {
  grid-area: track;
  display: flex;
  height: 12px;
  border-radius: 2px;
  background: var(--bg);
}
.cbar-fill { display: flex; height: 100%; }
/* Square where it grows from, rounded where it ends. */
.cbar-seg { display: block; min-width: 5px; }
.cbar-seg + .cbar-seg { margin-left: 2px; }   /* the surface gap, never a border */
.cbar-seg:last-child { border-radius: 0 4px 4px 0; }
.cbar-seg-bad  { background: var(--red); }
.cbar-seg-warn { background: var(--amber); }
.cbar-seg-time { background: var(--accent); }
.cbar-val {
  grid-area: val;
  text-align: right;
  font-size: 13px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.cbar-val small { display: block; font-size: 11px; color: var(--text-mute); }

/* A part that ran and found nothing: no bar to draw, so the word carries it. */
.cbar-ok .cbar-val { color: var(--green); }
.cbar-ok .cbar-val::before { content: "✓ "; }
/* A part that tested nothing. Dotted and colourless, like the ribbon above —
   an empty solid track would read as a measured zero. */
.cbar-untested .cbar-track,
.cbar-failed .cbar-track,
.cbar-waiting .cbar-track,
.cbar-live .cbar-track {
  background:
    repeating-linear-gradient(to right, var(--border-2) 0 3px, transparent 3px 7px)
    0 50% / 100% 1px no-repeat;
}
.cbar-untested .cbar-val,
.cbar-failed .cbar-val { color: var(--text-dim); }
.cbar-waiting .cbar-val { color: var(--text-mute); }
.cbar-live .cbar-val { color: var(--accent); }
.cbar-time .cbar-val { color: var(--text-dim); }

/* ---------- the score, test by test ---------- */
/* Behind a click in the history card: drawing it means reading every past run
   in full, which is the one genuinely expensive thing this page can do. */
.trend { margin-top: 14px; }
.trend-h { margin: 0 0 10px; font-size: 14px; letter-spacing: -0.01em; }
.trend-svg { width: 100%; height: auto; }
.trend-grid { stroke: var(--border); stroke-width: 1; }
.trend-tick { fill: var(--text-mute); font-family: var(--mono); font-size: 11px; }
.trend-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}
/* The ring is the surface colour, so a dot stays legible where it crosses the
   line or another dot. */
.trend-dot { stroke: var(--bg-card); stroke-width: 2; }
.trend-dot-good { fill: var(--green); }
.trend-dot-warn { fill: var(--amber); }
.trend-dot-bad  { fill: var(--red); }
/* A run where stages never reported. Hollow, because the number under it
   stands on less work than the solid ones next to it. */
.trend-dot-partial { fill: var(--bg-card); stroke: currentColor; }
.trend-dot-partial.trend-dot-good { color: var(--green); }
.trend-dot-partial.trend-dot-warn { color: var(--amber); }
.trend-dot-partial.trend-dot-bad  { color: var(--red); }
.trend-last { fill: var(--text); font-size: 13px; font-weight: 600; }
.trend-values {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin: 10px 0 0;
  padding: 0;
  font-size: 12px;
  color: var(--text-mute);
}
.trend-values strong { font-variant-numeric: tabular-nums; }
.trend-val-good { color: var(--green); }
.trend-val-warn { color: var(--amber); }
.trend-val-bad  { color: var(--red); }

/* ============================================================
   the map of the app
   ============================================================
   Two sources drawn as one picture: what the quick scan sees from outside and
   what the deep test finds once it is inside and reading the code.

   The boxes are HTML and only the wires between them are SVG. That is what
   buys labels that wrap instead of being cut mid-word, boxes that are real
   buttons, and a layout that folds into an accordion on a phone instead of
   being hidden and replaced by a list. */

.appmap {
  margin-top: 4px;

  /* Seven lanes, arranged as three worlds: the outside (slate, blue), your
     app (violet, orchid, magenta — the third being what it talks to, which
     is still the app's own world), and what it is made of (cyan, green).
     Seven unrelated colours would be a rainbow; three families let the eye
     find the regions and still tell neighbouring lanes apart when everything
     is clean and severity has nothing to say. */
  --am-lane-visitors: #8b93a7;
  --am-lane-way-in:   #5b8def;
  --am-lane-site:     #7c5cff;
  --am-lane-service:  #a06bf0;
  --am-lane-talks-to: #c65fc2;
  --am-lane-runs-on:  #35b5cc;
  --am-lane-code:     #46c88a;
}

/* The card breaks out of the 820px column the rest of the dashboard lives in:
   six lanes plus the panel beside them need the room, and this is the one
   thing on the page that earns it. The card moves, not the drawing inside it,
   so its border still frames what it holds. */
#panel-map .dash-card,
.demo-map .dash-card {
  width: min(1560px, calc(100vw - 40px));
  margin-left: 50%;
  transform: translateX(-50%);
}

/* ---------- /demo.html ---------- */
/* The one thing the demo page must never let a reader forget is that it is
   a demo. The badge is the first thing on the page and the only thing on it
   in amber. */
.demo-badge {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 0 0 14px;
  padding: 6px 12px 6px 9px;
  border: 1px solid rgba(245, 158, 11, 0.4);
  border-radius: 999px;
  background: rgba(245, 158, 11, 0.08);
  color: var(--amber);
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.03em;
}
.demo-badge-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--amber); }
.demo-controls { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 18px 0 0; }
/* The deck widens the page; the demo hero keeps the reading column so the
   badge, the intro and the buttons stay together in the middle. */
.deck-report .demo .audit-hero { max-width: 760px; margin-left: auto; margin-right: auto; }
.demo-map { margin: 36px 0 44px; }
.demo-eyebrow {
  margin: 0 0 6px;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-mute);
}
.demo-cta { margin: 48px auto 0; max-width: 640px; text-align: center; }
.demo-cta h2 { margin: 0 0 10px; }
.demo-cta p { color: var(--text-dim); }
.demo-cta-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 18px; }
@media (max-width: 899px) { .demo-map .dash-card { width: auto; margin-left: 0; transform: none; } }

/* ---------- the strip along the top ---------- */
/* How big this application is and how much of it is on fire, answered before
   the picture has to. */
.am-strip { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.am-stat {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 13px 7px 9px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 13px; color: var(--text-dim);
  white-space: nowrap;
}
.am-stat svg { width: 15px; height: 15px; flex: none; }
.am-stat b { font-family: var(--mono); font-weight: 600; font-size: 13px; color: var(--text); }
.am-stat-all  svg { color: var(--accent); }
.am-stat-bad  { border-color: rgba(239, 68, 68, 0.32); }
.am-stat-bad  svg, .am-stat-bad  b { color: var(--red); }
.am-stat-warn { border-color: rgba(245, 158, 11, 0.30); }
.am-stat-warn svg, .am-stat-warn b { color: var(--amber); }
.am-stat-ok   svg, .am-stat-ok   b { color: var(--green); }

.am-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin-bottom: 12px;
  font-size: 12px;
  color: var(--text-mute);
}
.am-legend span { display: flex; align-items: center; gap: 6px; }
.am-key { display: block; width: 9px; height: 9px; border-radius: 3px; }
.am-key-bad  { background: var(--red); }
.am-key-warn { background: var(--amber); }
.am-key-none { background: var(--border-2); }
/* Nobody hovers on a touch screen, so the instruction to do it goes away. */
@media (hover: none) { .am-legend-tip { display: none; } }

/* ---------- map + panel ---------- */
/* Wide enough and the panel is a rail beside the map, so picking a box and
   reading what is on it happens without scrolling at all. The rail only
   appears once there is room for it *and* six lanes at their minimum — taking
   330px away from a 1200px screen bought a panel and cost a sideways scroll,
   which is a bad trade. */
.am-map { display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; align-items: start; }
/* 1540px is not arbitrary: six lanes at their minimum plus their gutters need
   1122px, and the panel needs 300 plus a gap. Anything narrower and the rail is
   bought by squeezing the lanes down to where "Cloudflare" breaks across two
   lines — which is a worse map in exchange for a panel you could have scrolled
   to. Below it the panel goes under the board and a click scrolls it up. */
@media (min-width: 1540px) {
  .am-map { grid-template-columns: minmax(0, 1fr) 300px; }
  .am-rail { position: sticky; top: 78px; }
}

.am-board {
  position: relative;
  overflow-x: auto;
  /* Room for the app frame's label, which sits on the frame's top edge. */
  padding: 24px 4px 6px;
  scrollbar-width: thin;
  scrollbar-color: var(--border-2) transparent;
}
/* The board is a picture with a shape, not a row of columns. A request runs
   left to right along the top — a visitor, the way in, the pages — while the
   application's own parts stack inside one frame: pages over the service
   behind them over what it talks to. The platform is the shelf that frame
   sits on, and the code is the column the platform is built from. Three
   regions wide plus the code, so it fits a laptop without scrolling; below
   the minimum the board scrolls rather than squashing "Cloudflare" onto two
   lines. */
.am-lanes {
  position: relative;
  display: grid;
  grid-template-columns: 132px 184px minmax(600px, 1fr) 216px;
  grid-template-rows: auto auto auto auto;
  grid-template-areas:
    "visitors way-in site     code"
    "visitors way-in service  code"
    "visitors way-in talks-to code"
    ".        .      runs-on  code";
  gap: 26px 26px;
  align-items: start;
  min-width: 1210px;
}
/* Level with the address it reaches, not centred on a column it shares
   with nothing. */
.am-lane[data-lane="visitors"] { grid-area: visitors; align-self: start; margin-top: 36px; }
.am-lane[data-lane="way-in"]   { grid-area: way-in; }
.am-lane[data-lane="site"]     { grid-area: site; }
.am-lane[data-lane="service"]  { grid-area: service; }
.am-lane[data-lane="talks-to"] { grid-area: talks-to; }
.am-lane[data-lane="runs-on"]  { grid-area: runs-on; }
/* The code column hangs from the bottom, level with the platform it is
   built into, so the one wire that reaches it is short and reads as "built
   from" rather than looping up the whole height of the board. */
.am-lane[data-lane="code"]     { grid-area: code; align-self: end; }

/* The frame around the application's own parts. Drawn behind the three
   regions rather than around them in the DOM, so the grid still places each
   region on its own row and the wires still find every box. */
.am-region {
  position: relative;
  align-self: stretch;
  justify-self: stretch;
  grid-column: 3;
  grid-row: 1 / 4;
  /* Wider at the sides than the top: the strip inside the left edge is
     where wires detour down, and it needs to read as a lane, not a border. */
  margin: -12px -24px;
  border: 1px solid color-mix(in srgb, var(--am-lane-site) 30%, var(--border));
  border-radius: 18px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--am-lane-site) 5%, transparent), transparent 60%);
  pointer-events: none;
}
.am-region span {
  position: absolute;
  top: -9px; left: 26px;
  padding: 0 8px;
  background: var(--bg, #0e0e11);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--am-lane-site);
}
/* Behind the lanes, sized to the board once the boxes have landed. */
.am-wires { position: absolute; inset: 0; pointer-events: none; overflow: visible; z-index: 1; }

/* ---------- a region ---------- */
.am-lane {
  position: relative;
  z-index: 2;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--lane) 7%, transparent) 0, transparent 120px),
    #131317;
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 10px 10px 12px;
  transition: opacity 200ms ease;
}
/* Inside the app frame the regions drop their own frames: boxes in boxes in
   a box is a diagram of the CSS, not of the application. */
.am-lane-flow { background: none; border: 0; padding: 0; }
.am-lane[data-lane="runs-on"] {
  background: linear-gradient(0deg, color-mix(in srgb, var(--lane) 8%, transparent), transparent 70%), #131317;
  border-style: dashed;
}
.am-lane[data-lane="visitors"] { background: none; border: 0; padding: 0; }
.am-lane[data-lane="visitors"] .am-lane-head { display: none; }
.am-lane[data-lane="visitors"] .am-node-pill { width: auto; }

.am-lane[data-lane="visitors"] { --lane: var(--am-lane-visitors); }
.am-lane[data-lane="way-in"]   { --lane: var(--am-lane-way-in); }
.am-lane[data-lane="site"]     { --lane: var(--am-lane-site); }
.am-lane[data-lane="service"]  { --lane: var(--am-lane-service); }
.am-lane[data-lane="talks-to"] { --lane: var(--am-lane-talks-to); }
.am-lane[data-lane="runs-on"]  { --lane: var(--am-lane-runs-on); }
.am-lane[data-lane="code"]     { --lane: var(--am-lane-code); }

/* Titles sit on their own ground, above the wires, so a wire running up a
   gutter passes behind "what it talks to" rather than through it. */
.am-lane-head {
  position: relative; z-index: 3;
  display: inline-flex; align-items: center; gap: 8px;
  margin: 0 0 8px;
  padding: 2px 8px 2px 2px;
  border-radius: 999px;
  background: #131317;
  color: var(--text);
  font-weight: 400;
}
.am-stack { position: relative; z-index: 2; }
.am-lane-title {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-dim);
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.am-lane-n {
  flex: none;
  padding: 1px 7px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-family: var(--mono); font-size: 11px; color: var(--text-mute);
}

/* Regions on the request's path lay their boxes out in a row, so the eye
   reads across them; the way in and the code are columns, because that is
   the direction their contents stack in (a domain in front of an edge in
   front of a padlock; a repository holding folders). */
.am-stack { display: flex; flex-direction: column; gap: 8px; }
.am-lane-flow .am-stack { flex-direction: row; flex-wrap: wrap; gap: 10px; align-items: stretch; }
.am-lane-flow .am-node { flex: 0 1 172px; min-width: 150px; width: auto; }
.am-lane-flow .am-node-chip { flex: 0 1 auto; min-width: 0; }
.am-lane-flow .am-expand { flex: 0 1 172px; width: auto; }

/* ---------- the icon chip ---------- */
/* Tinted by the lane, so the columns stay distinguishable when every box on
   the map is clean and severity has no colour to contribute. */
.am-chip {
  display: grid; place-items: center;
  flex: 0 0 32px; width: 32px; height: 32px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--lane, var(--border-2)) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--lane, var(--border-2)) 26%, transparent);
  color: var(--lane, var(--text-dim));
}
.am-chip svg { width: 17px; height: 17px; }
.am-lane-head .am-chip { flex-basis: 26px; width: 26px; height: 26px; border-radius: 8px; }
.am-lane-head .am-chip svg { width: 14px; height: 14px; }

/* ---------- the severity badge ---------- */
/* A filled pill with a number and an icon in it: the shape says "something is
   here", the number says how much, and neither of them is only a colour. */
.am-badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 7px 2px 5px;
  border-radius: 999px;
  font-family: var(--mono); font-size: 11px; font-weight: 600; line-height: 1.5;
  white-space: nowrap;
}
.am-badge svg { width: 11px; height: 11px; }
.am-badge-bad {
  background: rgba(239, 68, 68, 0.16);
  border: 1px solid rgba(239, 68, 68, 0.34);
  color: #ff8b8b;
}
.am-badge-warn {
  background: rgba(245, 158, 11, 0.14);
  border: 1px solid rgba(245, 158, 11, 0.30);
  color: #f6bd57;
}

/* ---------- a box ---------- */
.am-node {
  position: relative;
  display: flex; align-items: flex-start; gap: 10px;
  width: 100%;
  padding: 10px 11px;
  background: linear-gradient(180deg, #1c1c22 0, #17171c 100%);
  border: 1px solid var(--border-2);
  border-radius: 13px;
  color: var(--text);
  text-align: left;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.035) inset,
    0 6px 16px -12px rgba(0, 0, 0, 0.95);
  transition: transform 160ms cubic-bezier(.2,.7,.3,1), border-color 160ms ease,
              box-shadow 160ms ease, opacity 200ms ease;
}
.am-node:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--lane) 52%, var(--border-2));
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.05) inset,
    0 12px 24px -14px rgba(0, 0, 0, 1);
}
.am-node:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Three shapes, because a person arriving at your site and a ready-made part
   you installed are not the same kind of thing. */
.am-node-pill { align-items: center; padding: 9px 14px 9px 10px; border-radius: 999px; }
.am-node-chip { align-items: center; padding: 7px 10px; border-radius: 11px; }
.am-node-chip .am-chip { flex-basis: 26px; width: 26px; height: 26px; border-radius: 8px; }
.am-node-chip .am-chip svg { width: 14px; height: 14px; }

.am-body { flex: 1 1 auto; min-width: 0; }
.am-label {
  display: block;
  font-size: 13.5px; font-weight: 600; line-height: 1.32;
  letter-spacing: -0.005em;
  overflow-wrap: break-word;
}
/* Anything that is a literal string out of the customer's own system is set in
   mono; anything that is our description of it is set in sans. The reader can
   tell at a glance which words are theirs and which are ours. */
.am-label-mono { font-family: var(--mono); font-size: 12.5px; font-weight: 500; }
.am-sub {
  display: block;
  margin-top: 2px;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--text-mute);
  overflow-wrap: break-word;
}
.am-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
.am-peek { font-family: var(--mono); font-size: 11px; color: var(--text-mute); }

.am-node-bad  { border-color: rgba(239, 68, 68, 0.42); }
.am-node-warn { border-color: rgba(245, 158, 11, 0.36); }
.am-node-on {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 0 0 5px var(--accent-glow);
}

/* The chip that unfolds the rest of a lane. It carries the folded boxes'
   worst severity, so a serious page can't hide behind a quiet-looking "+12". */
.am-expand {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%;
  margin-top: 2px;
  padding: 8px 10px;
  background: none;
  border: 1px dashed var(--border-2);
  border-radius: 11px;
  color: var(--text-mute);
  font-family: var(--mono); font-size: 11.5px;
  text-align: left;
  transition: color 140ms ease, border-color 140ms ease;
}
.am-expand:hover { color: var(--text-dim); border-color: color-mix(in srgb, var(--lane) 44%, var(--border-2)); }
.am-expand:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- the wires ---------- */
.am-wire { fill: none; stroke-width: 1.8; opacity: 0.7; transition: opacity 200ms ease, stroke-width 200ms ease; }
.am-flow {
  fill: none; stroke-width: 1.6; stroke-linecap: round;
  stroke-dasharray: 3 11;
  animation: am-dash 2.6s linear infinite;
  transition: opacity 200ms ease;
}
/* Wire and flow are emitted in pairs, so the flows are the even children and
   can be staggered here rather than with an inline delay on every path. */
.am-wires path:nth-child(8n + 4) { animation-delay: 0.65s; }
.am-wires path:nth-child(8n + 6) { animation-delay: 1.3s; }
.am-wires path:nth-child(8n + 8) { animation-delay: 1.95s; }

/* ---------- the trace ---------- */
/* Hover or focus a box and everything not on its route drops back. Twenty
   crossing curves are unreadable; one lit route through them is the whole
   reason the picture is worth drawing. */
.am-tracing .am-node { opacity: 0.25; }
.am-tracing .am-node.am-on-trace { opacity: 1; }
.am-tracing .am-lane { opacity: 0.72; }
.am-tracing .am-lane.am-on-trace { opacity: 1; }
.am-tracing .am-wire { opacity: 0.12; }
.am-tracing .am-wire.am-on-trace { opacity: 0.95; stroke-width: 2.2; }
.am-tracing .am-flow { opacity: 0; }
.am-tracing .am-flow.am-on-trace { opacity: 0.95; }

/* ---------- what a box holds ---------- */
.am-detail {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px 18px;
  min-height: 120px;
}
.am-detail-head { display: flex; align-items: flex-start; gap: 11px; }
.am-detail-head > div { min-width: 0; }
.am-detail h4 { margin: 0 0 2px; font-size: 16px; letter-spacing: -0.01em; overflow-wrap: anywhere; }
.am-detail h5 {
  margin: 16px 0 8px;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
}
.am-chip[data-lane="visitors"] { --lane: var(--am-lane-visitors); }
.am-chip[data-lane="way-in"]   { --lane: var(--am-lane-way-in); }
.am-chip[data-lane="site"]     { --lane: var(--am-lane-site); }
.am-chip[data-lane="service"]  { --lane: var(--am-lane-service); }
.am-chip[data-lane="talks-to"] { --lane: var(--am-lane-talks-to); }
.am-chip[data-lane="runs-on"]  { --lane: var(--am-lane-runs-on); }
.am-chip[data-lane="code"]     { --lane: var(--am-lane-code); }

.am-detail-sub { margin: 0; font-size: 13px; color: var(--text-mute); }
.am-source { margin: 10px 0 0; font-size: 13px; color: var(--text-dim); }
.am-note {
  margin: 10px 0 0;
  padding-left: 12px;
  border-left: 2px solid var(--border-2);
  font-size: 13px;
  color: var(--text-mute);
}
.am-hint, .am-clear { margin: 0; font-size: 14px; color: var(--text-mute); }
.am-values {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 2px 18px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-dim);
  word-break: break-all;
}
.am-more { color: var(--text-mute); font-style: italic; }
.am-find {
  margin-top: 10px;
  padding-left: 12px;
  border-left: 2px solid var(--border-2);
}
.am-find-bad  { border-left-color: var(--red); }
.am-find-warn { border-left-color: var(--amber); }
.am-find strong { font-size: 14px; font-weight: 600; }
.am-find p { margin: 2px 0 0; font-size: 13px; color: var(--text-dim); }
.am-find-where {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-mute);
  word-break: break-all;
}

/* Every list this map shortens for real — past the hard ceiling, not merely
   folded into a lane — says so here. */
.am-notes {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: grid;
  gap: 4px;
  font-size: 12.5px;
  color: var(--text-mute);
}
.am-notes li { padding-left: 14px; position: relative; }
.am-notes li::before { content: "·"; position: absolute; left: 4px; }

/* The scans that would fill the map in, offered where the gap is named. */
.am-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  margin: 14px 0 0;
  font-size: 14px;
}
.am-actions:empty { margin: 0; }

/* ---------- the same map as words ---------- */
/* The boxes are real buttons now, so this is no longer the only version a
   screen reader can use. It is still the version that shows every box a lane
   folded away without opening it, and the version that survives being pasted
   into an email to a developer. */
.am-alt { margin-top: 16px; }
.am-alt > summary {
  cursor: pointer;
  font-size: 13px;
  color: var(--text-dim);
  list-style: none;
}
.am-alt > summary::-webkit-details-marker { display: none; }
.am-alt > summary::before { content: "▸ "; }
.am-alt[open] > summary::before { content: "▾ "; }
.am-list-lane { margin-top: 14px; }
.am-list-lane h5 {
  margin: 0 0 6px;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-mute);
}
.am-list-lane ul { margin: 0; padding-left: 18px; font-size: 13.5px; color: var(--text-dim); }
.am-list-item { cursor: pointer; padding: 2px 4px; border-radius: 4px; }
.am-list-item:hover { background: var(--bg-elev); }
.am-list-item:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--accent-glow);
}
.am-list-on { background: var(--bg-elev); color: var(--text); }
.am-list-sub { color: var(--text-mute); font-size: 12px; }
.am-list-count { font-family: var(--mono); font-size: 12px; }
.am-list-bad  { color: var(--red); }
.am-list-warn { color: var(--amber); }

/* ---------- the entrance ---------- */
/* Left to right, one lane after another, because that is the direction the
   picture is read in. It runs once per visit to the tab; re-rendering after a
   new scan is not an arrival. */
@keyframes am-lane-in { from { opacity: 0; transform: translateY(10px); } }
@keyframes am-node-in { from { opacity: 0; transform: translateY(8px) scale(0.985); } }
@keyframes am-dash    { to { stroke-dashoffset: -28; } }
@keyframes am-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
  50%      { box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.16); }
}

.am-map-in .am-lane { animation: am-lane-in 380ms cubic-bezier(.2,.7,.3,1) backwards; }
.am-map-in .am-lane:nth-child(2) { animation-delay: 55ms; }
.am-map-in .am-lane:nth-child(3) { animation-delay: 110ms; }
.am-map-in .am-lane:nth-child(4) { animation-delay: 165ms; }
.am-map-in .am-lane:nth-child(5) { animation-delay: 220ms; }
.am-map-in .am-lane:nth-child(6) { animation-delay: 275ms; }
.am-map-in .am-node { animation: am-node-in 320ms cubic-bezier(.2,.7,.3,1) backwards; }
.am-map-in .am-node:nth-child(2) { animation-delay: 40ms; }
.am-map-in .am-node:nth-child(3) { animation-delay: 66ms; }
.am-map-in .am-node:nth-child(4) { animation-delay: 92ms; }
.am-map-in .am-node:nth-child(5) { animation-delay: 118ms; }
.am-map-in .am-node:nth-child(6) { animation-delay: 144ms; }
.am-map-in .am-node:nth-child(7) { animation-delay: 170ms; }
.am-map-in .am-node:nth-child(n + 8) { animation-delay: 196ms; }

/* Only the serious ones, and only the badge. Nineteen pulsing boxes is a
   fairground, and the reader stops seeing any of them. */
.am-node-bad .am-badge-bad { animation: am-pulse 3.4s ease-in-out infinite; }

/* ---------- narrow: the lanes become an accordion ---------- */
/* The drawing used to be hidden below this width and the list took over. It
   doesn't need to be: stacked lanes are the same map, and the first two are
   open because they are the two that are true of every site. */
@media (max-width: 899px) {
  #panel-map .dash-card { width: auto; margin-left: 0; transform: none; }
  .am-board { overflow-x: visible; }
  .am-lanes {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "visitors" "way-in" "site" "service" "talks-to" "runs-on" "code";
    gap: 12px; min-width: 0;
  }
  .am-region { display: none; }
  .am-lane-flow { background: #131317; border: 1px solid var(--border); padding: 10px 10px 12px; }
  .am-lane[data-lane="visitors"] .am-lane-head { display: flex; }
  .am-wires { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .am-map-in .am-lane,
  .am-map-in .am-node { animation: none; }
  .am-node { transition: border-color 160ms ease, box-shadow 160ms ease; }
  .am-node:hover { transform: none; }
  .am-node-bad .am-badge-bad { animation: none; }
  .am-flow { display: none; }
}

/* The second consent line. Quieter than the one above it: that one gates the
   button and this one is an option, and styling them identically made the
   launch form read as two equal demands. */
.dash-consent-extra { margin-top: 12px; opacity: 0.92; }
.dash-consent-extra span { font-size: 14px; }

/* ---------- what to fix ---------- */
.res-h {
  margin: 30px 0 14px;
  font-size: 19px;
  letter-spacing: -0.01em;
  scroll-margin-top: 80px;   /* clears the sticky nav when jumped to */
}
.res-card {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 14px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-2);
  border-radius: var(--radius);
  padding: 20px 22px;
  margin-bottom: 12px;
}
.res-card-bad  { border-left-color: var(--red); }
.res-card-warn { border-left-color: var(--amber); }
.res-mark {
  font-size: 18px;
  line-height: 1.3;
  text-align: center;
  font-weight: 700;
}
.res-card-bad  .res-mark { color: var(--red); }
.res-card-warn .res-mark { color: var(--amber); }
.res-card h3 { margin: 6px 0 8px; font-size: 18px; letter-spacing: -0.01em; }
/* Every paragraph in this panel carries its own class rather than inheriting
   from a `.res-card p` reset. The reset wins on specificity against a single
   class, and the only way out of that is !important — which the next person
   then has to fight too. */
.res-lead { margin: 0; color: var(--text-dim); }
.res-stage-tag {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-mute);
}
.res-why {
  margin: 10px 0 0;
  padding-left: 12px;
  border-left: 2px solid var(--border-2);
  color: var(--text-mute);
  font-size: 15px;
}

/* What to do about it. The steps read like the rest of the card; the brief
   under them is machine-facing, so it is styled as code and folded away —
   it is there to be copied, not read. */
.res-fix { margin-top: 14px; }
.res-fix-h {
  margin: 0 0 6px;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-mute);
}
.res-fix-steps {
  margin: 0;
  padding-left: 18px;
  color: var(--text-dim);
  font-size: 15px;
}
.res-fix-steps li { margin-bottom: 5px; }
.res-fix-steps li:last-child { margin-bottom: 0; }

.res-prompt {
  margin-top: 14px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
}
.res-prompt > summary {
  cursor: pointer;
  font-size: 14px;
  color: var(--accent-text);
  list-style: none;
}
.res-prompt > summary::-webkit-details-marker { display: none; }
.res-prompt > summary::before { content: "▸ "; }
.res-prompt[open] > summary::before { content: "▾ "; }
.res-prompt-note {
  margin: 8px 0 10px;
  font-size: 14px;
  color: var(--text-mute);
}
.res-prompt-copy {
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  color: var(--text);
  background: var(--bg-card);
  border: 1px solid var(--border-2);
  border-radius: var(--radius-sm);
  padding: 7px 14px;
  margin-bottom: 10px;
}
.res-prompt-copy:hover { border-color: var(--accent); color: var(--accent-text); }
.res-prompt-text {
  margin: 0;
  max-height: 260px;
  overflow: auto;
  padding: 12px;
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-dim);
  white-space: pre-wrap;
  word-break: break-word;
}

/* The raw rows. Tool wording and affected addresses live here and nowhere
   above, so the summary stays readable by someone who doesn't code. */
.res-rows { margin-top: 14px; }
.res-rows > summary {
  cursor: pointer;
  font-size: 14px;
  color: var(--text-mute);
  list-style: none;
}
.res-rows > summary::-webkit-details-marker { display: none; }
.res-rows > summary::before { content: "▸ "; }
.res-rows[open] > summary::before { content: "▾ "; }
.res-rows > summary:hover { color: var(--text-dim); }
.res-row {
  margin-top: 12px;
  padding-left: 12px;
  border-left: 2px solid var(--border-2);
}
.res-row-bad  { border-left-color: var(--red); }
.res-row-warn { border-left-color: var(--amber); }
.res-row h4 { margin: 0 0 4px; font-size: 15px; font-weight: 600; }
.res-row-fix { margin: 0; font-size: 14px; color: var(--text-dim); }
.res-row-where {
  margin: 0 0 4px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-mute);
  word-break: break-all;
}

/* What the second look set aside: a neutral edge, never red or amber, and
   the reason in the same voice as a fix. */
.res-aside > summary::before { color: var(--text-mute); }
.res-row-aside { border-left-color: var(--border-2); opacity: 0.85; }
.res-row-reason { margin: 0; font-size: 14px; color: var(--text-dim); }

/* ---------- what's already working ---------- */
.res-fine {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 22px;
  margin-top: 16px;
}
.res-fine > summary {
  cursor: pointer;
  color: var(--text-dim);
  font-size: 15px;
  list-style: none;
}
.res-fine > summary::-webkit-details-marker { display: none; }
.res-fine > summary::before { content: "▸ "; color: var(--green); }
.res-fine[open] > summary::before { content: "▾ "; }
.res-fine > summary:hover { color: var(--text); }
.res-fine-item {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 14px;
  margin-top: 16px;
}
.res-fine-item .res-mark { color: var(--green); }
.res-fine-item h3 { margin: 0 0 4px; font-size: 16px; }
.res-fine-item p { margin: 0; color: var(--text-mute); font-size: 15px; }

/* ---------- nothing run yet ---------- */
.res-empty {
  background: var(--bg-card);
  border: 1px dashed var(--border-2);
  border-radius: var(--radius);
  padding: 28px;
}
.res-empty h2 { margin: 0 0 8px; font-size: 20px; letter-spacing: -0.01em; }
.res-empty p { margin: 0; color: var(--text-dim); }

/* ---------- narrow screens ---------- */
@media (max-width: 640px) {
  .dash { padding: 32px 16px 72px; }
  .dash-card { padding: 18px; }
  .dash-grid { grid-template-columns: 1fr; }
  .res-head { padding: 20px 18px 18px; }
  .res-verdict-row { gap: 14px; }
  .res-verdict-row h2, .res-head-live h2 { font-size: 21px; }
  .res-dial { width: 92px; height: 92px; }
  .res-dial-num { font-size: 32px; }
  .res-dial-letter { font-size: 11px; margin-top: 3px; }
  .res-instruments { padding: 18px; }
  /* The label needs the full width before the bar is worth drawing. */
  .cbar {
    grid-template-columns: 1fr auto;
    grid-template-areas: "name val" "track track";
    gap: 4px 12px;
  }
  .res-card { padding: 16px 18px; grid-template-columns: 20px 1fr; gap: 10px; }
  /* The address is the least useful of the three on a small screen, and it is
     the one that forces the row to overflow. */
  .dash-run { grid-template-columns: 1fr auto; }
  .dash-run-url { display: none; }
}
