/* ============================================================
   SyncHub design system.

   Ported verbatim from docs/remodel/mockups/design-system.css, which the
   re-model pack treats as the specification. Only the @font-face block below
   is added: the mockups pulled IBM Plex from Google Fonts, and DS3 requires it
   self-hosted so the interface renders correctly with outbound internet
   blocked.

   Do not edit this file to work around a design rule. If a rule is wrong,
   change the rule in docs/remodel/design-decisions-synchub.md and say so.
   `python3 docs/remodel/check-design.py` enforces the mechanical half.

   Fonts: IBM Plex Sans and IBM Plex Mono, SIL Open Font License 1.1,
   from github.com/IBM/plex.
   ============================================================ */

/* Sans 400/500/600/700 and Mono 400/500 — the weights this stylesheet uses.
   font-display: swap so text is readable before the face arrives. */
@font-face {
  font-family: "IBM Plex Sans"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("../fonts/IBMPlexSans-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans"; font-style: normal; font-weight: 500;
  font-display: swap; src: url("../fonts/IBMPlexSans-Medium.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans"; font-style: normal; font-weight: 600;
  font-display: swap; src: url("../fonts/IBMPlexSans-SemiBold.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans"; font-style: normal; font-weight: 700;
  font-display: swap; src: url("../fonts/IBMPlexSans-Bold.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("../fonts/IBMPlexMono-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500;
  font-display: swap; src: url("../fonts/IBMPlexMono-Medium.woff2") format("woff2");
}

/* ============================================================
   SyncHub — design system, ported from HAL MSP Platform
   Reference: reference-design-system-hal.md
   Plain CSS so it works offline and mirrors the Django-template
   approach agreed in decision D6 (no React, no CDN).
   ============================================================ */

:root {
  --background: 0 0% 100%;
  --foreground: 240 10% 3.9%;
  --card: 0 0% 100%;
  --card-foreground: 240 10% 3.9%;
  --primary: 239 84% 67%;
  --primary-foreground: 0 0% 100%;
  --secondary: 240 4.8% 95.9%;
  --muted: 240 4.8% 95.9%;
  --muted-foreground: 240 3.8% 46.1%;
  --accent: 240 4.8% 95.9%;
  --destructive: 0 84.2% 60.2%;
  --border: 240 5.9% 90%;
  --input: 240 5.9% 90%;
  --ring: 239 84% 67%;
  --success: 160 84% 39%;
  --warning: 38 92% 50%;
  --info: 217 91% 60%;
  --radius: 2px;              /* SyncHub override — was 0.25rem in HAL */
  --btn: 240 10% 8%;          /* SyncHub override — buttons are black, not primary */
  --btn-foreground: 0 0% 100%;
  --content-bg: #F8F9FA;
}

.dark {
  --background: 240 10% 3.9%;
  --foreground: 240 4.8% 95.9%;
  --card: 240 6% 6%;
  --card-foreground: 240 4.8% 95.9%;
  --secondary: 240 5.9% 16%;
  --muted: 240 5.9% 16%;
  --muted-foreground: 240 5% 75%;
  --accent: 240 5.9% 16%;
  --border: 240 5.9% 20%;
  --input: 240 5.9% 16%;
  --content-bg: hsl(240 10% 3.9%);
  --btn: 0 0% 96%;
  --btn-foreground: 240 10% 6%;
}

* { box-sizing: border-box; border-color: hsl(var(--border)); }

html { font-size: 18px; }           /* HAL base — deliberately large */

body {
  margin: 0;
  font-family: var(--font, "IBM Plex Sans"), -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  /* Plex Sans has proper tabular figures, so numbers line up in columns without
     needing a monospace face. Mono is reserved for identifiers where the shape
     of a character carries meaning — item codes, endpoints, keys. */
  font-variant-numeric: tabular-nums;
  background: hsl(var(--background));
  color: hsl(var(--foreground));
  -webkit-font-smoothing: antialiased;
  font-size: 0.778rem;              /* 14px at 18px root */
  line-height: 1.5;
}

h1, h2, h3, h4 { font-family: inherit; font-weight: 700; margin: 0; }
code, .mono { font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 0.72rem; }
a { color: hsl(var(--primary)); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- app shell ---------- */
.app { display: flex; min-height: 100vh; }

.sidebar {
  width: 14rem; flex-shrink: 0;
  background: hsl(var(--background));
  border-right: 1px solid hsl(var(--border));
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
}
.sidebar-logo {
  height: 3.5rem; flex-shrink: 0;
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0 0.85rem;
  border-bottom: 1px solid hsl(var(--border));
}
.logo-mark {
  width: 1.7rem; height: 1.7rem; flex-shrink: 0;
  border-radius: var(--radius);
  background: hsl(var(--primary));
  display: flex; align-items: center; justify-content: center;
  color: #fff;
}
.logo-text { font-weight: 700; font-size: 0.95rem; color: hsl(var(--primary)); letter-spacing: -0.01em; }

.nav { flex: 1; padding: 0.6rem; overflow-y: auto; }
.nav-label {
  font-size: 0.55rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.08em; color: hsl(var(--muted-foreground));
  padding: 0.6rem 0.6rem 0.3rem;
}
.nav-item {
  display: flex; align-items: center; gap: 0.6rem;
  padding: 0.42rem 0.6rem; margin-bottom: 0.1rem;
  border-radius: 2px;
  color: hsl(var(--muted-foreground));
  font-size: 0.76rem;
  border-left: 2px solid transparent;
  cursor: pointer; text-decoration: none;
}
.nav-item:hover { color: hsl(var(--foreground)); background: hsl(var(--muted)); text-decoration: none; }
.nav-item.active {
  color: hsl(var(--foreground));
  background: hsl(var(--primary) / 0.1);
  border-left-color: hsl(var(--primary));
  font-weight: 500;
}
.nav-item .ico { width: 0.9rem; height: 0.9rem; flex-shrink: 0; }
.nav-count {
  margin-left: auto; font-size: 0.6rem; font-weight: 700;
  padding: 0.05rem 0.3rem; border-radius: 999px;
}
.nav-foot { margin-top: auto; padding: 0.6rem; border-top: 1px solid hsl(var(--border)); }
.user-row {
  display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0.4rem;
  /* An anchor since My account exists. It carries no underline and no link
     colour, because it is a nav item that happens to be at the bottom. */
  border-radius: 3px; color: inherit; text-decoration: none;
}
.user-row:hover { background: hsl(var(--accent)); text-decoration: none; }
.user-row.active { background: hsl(var(--accent)); }
/* A list of plain statements - capabilities, checks - rather than a bulleted
   list of prose. The marker adds nothing when every item is two words. */
ul.plain { list-style: none; margin: 0; padding: 0; }
ul.plain li { padding: 0.18rem 0; border-bottom: 1px solid hsl(var(--border)); }
ul.plain li:last-child { border-bottom: 0; }

/* A segmented control that is really a radio group. The radio itself is hidden
   rather than absent, so the whole thing still submits with the form, is
   reachable with a keyboard, and needs no script - the mockup used buttons and
   would have needed all three writing by hand. */
.seg { display: inline-flex; border: 1px solid hsl(var(--input)); border-radius: 3px;
       overflow: hidden; }
.seg label { display: inline-flex; cursor: pointer; border-right: 1px solid hsl(var(--input)); }
.seg label:last-child { border-right: 0; }
.seg input { position: absolute; opacity: 0; width: 0; height: 0; }
.seg span { padding: 0.22rem 0.5rem; font-size: 0.64rem; white-space: nowrap;
            color: hsl(var(--muted-foreground)); }
.seg label:hover span { background: hsl(var(--accent)); }
.seg input:checked + span { background: hsl(var(--btn)); color: hsl(var(--btn-foreground));
                            font-weight: 600; }
.seg input:focus-visible + span { outline: 2px solid hsl(var(--ring)); outline-offset: -2px; }

/* The day at a glance on Schedules: twenty-four hour cells, office hours tinted,
   a dot where a fixed-time task runs. Tinted with a background-image over an
   opaque colour rather than an alpha fill, per the no-translucent-box rule. */
.tl { display: flex; height: 2.1rem; border: 1px solid hsl(var(--border));
      border-radius: 2px; overflow: hidden; }
.tl i { flex: 1; border-right: 1px solid hsl(var(--border)); position: relative;
        background-color: hsl(var(--card));
        background-image: linear-gradient(hsl(var(--muted)), hsl(var(--muted))); }
.tl i:last-child { border-right: 0; }
.tl i.office { background-image: linear-gradient(hsl(38 92% 50% / 0.14), hsl(38 92% 50% / 0.14)); }
.tl i span { position: absolute; inset: auto 0 2px 0; text-align: center;
             font-size: 0.5rem; color: hsl(var(--muted-foreground)); }
.tl i b { position: absolute; top: 3px; left: 50%; transform: translateX(-50%);
          width: 0.32rem; height: 0.32rem; border-radius: 999px;
          background: hsl(var(--foreground)); }
.avatar {
  width: 1.5rem; height: 1.5rem; border-radius: 999px;
  background: hsl(var(--primary) / 0.2); color: hsl(var(--primary));
  display: flex; align-items: center; justify-content: center;
  font-size: 0.6rem; font-weight: 700; flex-shrink: 0;
}
.user-meta { min-width: 0; }
.user-name { font-size: 0.68rem; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-role { font-size: 0.58rem; color: hsl(var(--muted-foreground)); }

main { flex: 1; min-width: 0; background: var(--content-bg); }
.page { padding: 1.35rem; display: flex; flex-direction: column; gap: 1.35rem; }

/* ---------- page header ---------- */
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
.page-head h1 { font-size: 1.22rem; display: flex; align-items: center; gap: 0.4rem; }
.page-sub { font-size: 0.72rem; color: hsl(var(--muted-foreground)); margin-top: 0.15rem; }
.head-actions { display: flex; align-items: center; gap: 0.4rem; flex-shrink: 0; }
.help-btn {
  width: 1.3rem; height: 1.3rem; border-radius: 999px; border: 0;
  background: transparent; color: hsl(var(--muted-foreground));
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.7rem;
}
.help-btn:hover { background: hsl(var(--accent)); }

/* ---------- cards ---------- */
.card {
  background: hsl(var(--card)); color: hsl(var(--card-foreground));
  border: 1px solid hsl(var(--border));
  border-radius: 3px;                     /* rounded-lg */
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.04);
}
.card-head {
  padding: 0.78rem 0.88rem 0.5rem;
  display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
}
.card-title { font-size: 0.78rem; font-weight: 500; display: flex; align-items: center; gap: 0.4rem; }
.card-desc { font-size: 0.68rem; color: hsl(var(--muted-foreground)); font-weight: 400; }
.card-body { padding: 0.88rem; }
.card-body.tight { padding: 0; }
.card-foot {
  border-top: 1px solid hsl(var(--border));
  padding: 0.5rem 0.88rem;
  display: flex; align-items: center; justify-content: space-between;
  font-size: 0.68rem; color: hsl(var(--muted-foreground));
}

/* ---------- metric tiles ---------- */
.metric-card {
  background: hsl(var(--card)); border: 1px solid hsl(var(--border));
  padding: 0.88rem; border-radius: 2px;
}
.metric-label {
  font-size: 0.55rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.08em; color: hsl(var(--muted-foreground));
}
.metric-value { font-size: 1.33rem; font-weight: 700; margin-top: 0.15rem;
                font-variant-numeric: tabular-nums; }
/* A figure may carry state colour — a red backlog is a fact, not decoration (DS2/DS6).
   Available to every page, so the treatment is a system feature and not a one-off. */
.metric-value.ok   { color: hsl(160 84% 32%); }
.metric-value.warn { color: hsl(32 88% 40%); }
.metric-value.bad  { color: hsl(0 72% 48%); }

/* State as a text colour. There was no way to say "this line is an error"
   short of a badge or a callout, so a form message written as `tiny bad`
   rendered in plain grey - the only feedback the password form gives, and it
   did not read as a failure. Colour still means state, only (DS6). */
.txt-ok   { color: hsl(160 84% 30%); }
.txt-warn { color: hsl(28 85% 38%); }
.txt-bad  { color: hsl(0 72% 48%); }
.metric-note { font-size: 0.62rem; color: hsl(var(--muted-foreground)); margin-top: 0.1rem; }
.stat-icon {
  width: 1.9rem; height: 1.9rem; border-radius: 2px;
  background: hsl(var(--muted)); color: hsl(var(--foreground));
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}

/* ---------- grid ---------- */
.grid { display: grid; gap: 0.88rem; }
.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* Five feeds, so five cards. Not a metric row - DS9 governs .metric-card
   in a .g4, and these are cards carrying a figure, a state and a sentence. */
.g5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.g-main { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 1.35rem; }
@media (max-width: 1100px) { .g4, .g3, .g-main { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 760px)  { .g4, .g3, .g2, .g-main { grid-template-columns: 1fr; } }
.stack { display: flex; flex-direction: column; gap: 0.88rem; }
.row { display: flex; align-items: center; gap: 0.5rem; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; }

/* ---------- connection test rows ----------
   The target is a URL: one unbreakable token. A flex item defaults to
   min-width:auto, so that token refused to shrink, and the badge - which is
   white-space:nowrap and so cannot shrink either - was pushed straight through
   the right wall of the dialog. The text column may shrink and wrap; the badge
   may not, because a badge that wraps breaks DS8.

   A row is one to three lines now that the reason is on it, so the badge is
   pinned level with the endpoint name rather than floated to the middle of a
   wrapped block. */
.conn-row { align-items: flex-start; }
.conn-what { min-width: 0; }
.conn-what .mono { overflow-wrap: anywhere; }
.conn-row > .badge { flex-shrink: 0; }

/* ---------- badges: colour ONLY for state ---------- */
.badge {
  display: inline-flex; align-items: center; gap: 0.28rem;
  border-radius: 3px;
  border: 1px solid; font-weight: 500; white-space: nowrap;
  font-size: 0.62rem; padding: 0.08rem 0.36rem;
}
.badge .dot { width: 0.3rem; height: 0.3rem; border-radius: 999px; flex-shrink: 0; }
.b-neutral { background: hsl(240 5% 50% / 0.1); color: hsl(240 5% 40%); border-color: hsl(240 5% 50% / 0.2); }
.b-ok      { background: hsl(160 84% 39% / 0.1); color: hsl(160 84% 30%); border-color: hsl(160 84% 39% / 0.2); }
.b-warn    { background: hsl(38 92% 50% / 0.12); color: hsl(28 85% 38%); border-color: hsl(38 92% 50% / 0.25); }
.b-bad     { background: hsl(0 84% 60% / 0.1);  color: hsl(0 72% 44%);  border-color: hsl(0 84% 60% / 0.2); }
.b-info    { background: hsl(217 91% 60% / 0.1); color: hsl(217 80% 45%); border-color: hsl(217 91% 60% / 0.2); }
.dark .b-neutral { color: hsl(240 5% 72%); }
.dark .b-ok   { color: hsl(160 60% 60%); }
.dark .b-warn { color: hsl(38 85% 65%); }
.dark .b-bad  { color: hsl(0 75% 70%); }
.dark .b-info { color: hsl(217 85% 72%); }
.d-ok{background:hsl(160 84% 39%)} .d-bad{background:hsl(0 84% 60%)}
.d-warn{background:hsl(38 92% 50%)} .d-neutral{background:hsl(240 5% 55%)}
.d-info{background:hsl(217 91% 60%)}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.35rem;
  font-size: 0.72rem; font-weight: 500; white-space: nowrap;
  height: 1.72rem; padding: 0 0.7rem;
  border-radius: 3px;
  border: 1px solid transparent; cursor: pointer;
  background: hsl(var(--btn)); color: hsl(var(--btn-foreground));
}
.btn:hover { filter: brightness(1.35); text-decoration: none; }
.dark .btn:hover { filter: brightness(0.9); }
.btn-outline { background: hsl(var(--card)); color: hsl(var(--foreground)); border-color: hsl(var(--input)); }
.btn-outline:hover { background: hsl(var(--accent)); filter: none; }
.btn-ghost { background: transparent; color: hsl(var(--foreground)); }
.btn-ghost:hover { background: hsl(var(--accent)); filter: none; }
.btn-danger { background: hsl(var(--destructive)); color: #fff; }
.btn-sm { height: 1.44rem; padding: 0 0.5rem; font-size: 0.66rem; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ---------- tables: high density ---------- */
table { width: 100%; border-collapse: collapse; }
/* A column that should be exactly as wide as its content, letting the columns
   without it absorb the slack. Needed wherever a table has few columns: the
   last one otherwise takes all the leftover width, and a status badge in it
   stretches to fill — a 630px box with two words floating in the middle. */
th.fit, td.fit { width: 1%; white-space: nowrap; }
thead th {
  font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: hsl(var(--muted-foreground)); text-align: left;
  padding: 0.44rem 0.66rem; border-bottom: 1px solid hsl(var(--border));
  background: hsl(var(--muted) / 0.5); white-space: nowrap;
}
tbody td { padding: 0.44rem 0.66rem; border-bottom: 1px solid hsl(var(--border)); vertical-align: middle; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: hsl(var(--muted) / 0.45); }
tbody tr.clickable { cursor: pointer; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }
.muted { color: hsl(var(--muted-foreground)); }
.strong { font-weight: 600; }
.tiny { font-size: 0.62rem; }

/* ---------- filter bar ---------- */
.filters { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
input[type=text], input[type=date], select {
  height: 1.72rem; font-size: 0.72rem; font-family: inherit;
  padding: 0 0.5rem; border-radius: 3px;
  border: 1px solid hsl(var(--input)); background: hsl(var(--background));
  color: hsl(var(--foreground));
}
input:focus, select:focus { outline: 2px solid hsl(var(--ring) / 0.5); outline-offset: 0; }
.search { position: relative; flex: 1; min-width: 12rem; max-width: 22rem; }
.search input { width: 100%; padding-left: 1.6rem; }
.search .ico { position: absolute; left: 0.45rem; top: 50%; transform: translateY(-50%); color: hsl(var(--muted-foreground)); width: 0.8rem; height: 0.8rem; }

/* ---------- callouts ---------- */
.callout {
  border: 1px solid; border-radius: 3px; padding: 0.78rem 0.88rem;
  display: flex; gap: 0.66rem; align-items: flex-start;
}
.callout-bad  { background: hsl(0 84% 60% / 0.06);  border-color: hsl(0 84% 60% / 0.25); }
.callout-warn { background: hsl(38 92% 50% / 0.07); border-color: hsl(38 92% 50% / 0.3); }
.callout-ok   { background: hsl(160 84% 39% / 0.06); border-color: hsl(160 84% 39% / 0.22); }
.callout-info { background: hsl(var(--muted)); border-color: hsl(var(--border)); }
.callout h3 { font-size: 0.82rem; margin-bottom: 0.15rem; }
.callout p { margin: 0; font-size: 0.72rem; color: hsl(var(--muted-foreground)); }
/* A callout is a badge and a body. The body always takes the rest of the row and
   the badge always sits on the first line of text, so neither needs saying at
   the call site — every mockup wrote both by hand as an inline style. */
.callout > div { flex: 1; min-width: 0; }
.callout > .badge { margin-top: 0.1rem; }
.callout-actions { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.6rem; }

/* ---------- timeline (record journey) ---------- */
.timeline { position: relative; padding-left: 1.4rem; }
.timeline::before {
  content: ""; position: absolute; left: 0.42rem; top: 0.3rem; bottom: 0.3rem;
  width: 1px; background: hsl(var(--border));
}
.tl-item { position: relative; padding-bottom: 0.95rem; }
.tl-item:last-child { padding-bottom: 0; }
.tl-dot {
  position: absolute; left: -1.4rem; top: 0.16rem;
  width: 0.85rem; height: 0.85rem; border-radius: 999px;
  border: 2px solid hsl(var(--background));
  display: flex; align-items: center; justify-content: center;
}
.tl-title { font-size: 0.76rem; font-weight: 600; }
.tl-meta { font-size: 0.64rem; color: hsl(var(--muted-foreground)); }
.tl-body { font-size: 0.7rem; margin-top: 0.2rem; }

/* ---------- misc ---------- */
.kv { display: grid; grid-template-columns: auto 1fr; gap: 0.3rem 0.9rem; font-size: 0.72rem; }
.kv dt { color: hsl(var(--muted-foreground)); white-space: nowrap; }
.kv dd { margin: 0; text-align: right; font-weight: 500; }
.divider { height: 1px; background: hsl(var(--border)); }
/* Inside a dialog a divider always separates two blocks and a field always
   follows a callout after a gap. Both were written inline on every dialog in
   the mockups, which is how the gaps drifted between them. */
.dialog-body .divider { margin: 0.8rem 0; }
.dialog-body .callout + .field { margin-top: 0.75rem; }
.lead-out { margin: 0 0 0.7rem; }
/* A line of context under something, rather than over it. Paired with
   .lead-out so the two directions are named rather than written inline. */
.lead-in { margin: 0.35rem 0 0; }
.addr { margin: 0; line-height: 1.65; }
/* A table too wide for its card scrolls inside the card, never taking the page
   with it. Twelve columns of order line is the case this exists for. */
.scroll-x { overflow-x: auto; }
.find-title { font-size: 1rem; margin: 0; }
.find-head { align-items: flex-start; }
.empty { text-align: center; padding: 2.2rem 1rem; color: hsl(var(--muted-foreground)); }
.empty .ico { width: 1.8rem; height: 1.8rem; margin: 0 auto 0.5rem; display: block; }
.empty h3 { font-size: 0.82rem; font-weight: 500; color: hsl(var(--foreground)); }
.empty p { font-size: 0.72rem; margin: 0.2rem 0 0.8rem; }
.tabs { display: inline-flex; background: hsl(var(--muted)); padding: 0.15rem; border-radius: 3px; gap: 0.1rem; }
.tab {
  font-size: 0.7rem; padding: 0.28rem 0.6rem; border-radius: 3px;
  border: 0; background: transparent; cursor: pointer; color: hsl(var(--muted-foreground));
  font-family: inherit;
}
.tab.active { background: hsl(var(--background)); color: hsl(var(--foreground)); font-weight: 500; box-shadow: 0 1px 2px rgb(0 0 0 / 0.06); }
a.tab { text-decoration: none; }

/* The top-level strip on Problems and Held drop-ship lines: two halves of one
   question rather than two states of one list, so it sits above the screen's
   own tabs with a gap, and reads slightly larger. */
.tabs-top { margin-bottom: 0.9rem; }
.tabs-top .tab { font-size: 0.76rem; padding: 0.34rem 0.75rem; }

/* The count the red callout used to carry. Muted by default because a tab
   with nothing wrong on it should not shout; `bad` is the held count, which
   is the one that means a supplier has not been told. */
.tab .count {
  margin-left: 0.4rem; padding: 0 0.3rem; border-radius: 999px;
  background: hsl(var(--muted-foreground) / 0.16); color: hsl(var(--foreground));
  font-size: 0.68rem; font-variant-numeric: tabular-nums;
}
.tab .count.bad { background: hsl(0 84% 60% / 0.16); color: hsl(0 72% 42%); }
.age-old { color: hsl(0 72% 44%); font-weight: 600; }
.age-mid { color: hsl(28 85% 38%); font-weight: 500; }
.mock-note {
  background: hsl(var(--primary) / 0.07); border: 1px dashed hsl(var(--primary) / 0.35);
  border-radius: 3px; padding: 0.6rem 0.8rem; font-size: 0.68rem;
  color: hsl(var(--foreground));
}
.mock-note strong { color: hsl(var(--primary)); }

/* ---------- tables: one size, one badge width ---------- */

/* Every cell's primary line reads at the same size, whatever helper class the
   markup happens to carry. A genuine SECOND line inside a cell may be smaller —
   that is the only exception. */
tbody td,
tbody td.tiny,
tbody td.mono,
tbody td > .tiny,
tbody td > .mono,
tbody td > span,
tbody td > .strong { font-size: 0.7rem; }

tbody td .strong + .tiny,
tbody td .strong + .muted,
tbody td > div + div,
tbody td > div + .tiny,
tbody td small { font-size: 0.6rem; }

thead th { font-size: 0.6rem; }

/* Badges inside a table all take the same width, so a status column reads as a
   column rather than a ragged edge.

   min-width alone was not enough: it set a floor, so "Synced" and "Missing in
   Interprise" still came out different widths and the column stayed ragged.
   width:100% makes each badge fill its cell, and because a table column is
   already as wide as its widest cell, every badge in the column ends up the
   width of the longest label — which is the rule we actually wanted. */
tbody td .badge {
  min-width: 6.5rem;
  justify-content: center;
  box-sizing: border-box;
}
/* :only-child is load-bearing, not incidental. The mechanism is "the column is
   as wide as its widest cell, so a badge filling its cell is as wide as the
   longest label" — and that only holds while the badge IS the cell. Relaxing it
   to :first-child so a badge could carry a line of detail beneath it made the
   badge fill a column sized by the detail text instead: a 280px box with a
   label floating in the middle of it. Put the detail in its own cell. */
tbody td:has(> .badge:not(.tag):only-child) > .badge { width: 100%; }
/* except where a badge is a compact inline tag rather than a status */
tbody td .badge.tag { min-width: 0; }

/* Record identifiers — SKUs, item codes, supplier codes — are DATA, and read in
   the table's own typeface so a row does not mix two faces. Plex Sans already
   separates 1/l/I and 0/O clearly (the reason it was chosen in DS3), so nothing
   is lost by dropping the monospaced face here; tabular figures and a little
   letter-spacing keep codes scannable down the column.

   Genuine code keeps IBM Plex Mono: HTTP calls (POST /salesorder), Celery task
   paths (order_export.tasks.export_sales_order) and API parameter names. Those
   are still marked .mono. */
.rid { font-variant-numeric: tabular-nums; letter-spacing: 0.015em; }

/* ---------- full-bleed tables, but content still aligns to the card ----------
   .card-body.tight removes padding so row borders and hover states run the
   full width of the card. Without an inset on the outer cells, though, the
   text sits hard against the card edge and the card reads as unpadded. First
   and last cells therefore carry the card's own 0.88rem, so every column
   lines up with the card's other content. */
.card-body.tight > table > thead > tr > th:first-child,
.card-body.tight > table > tbody > tr > td:first-child { padding-left: 0.88rem; }
.card-body.tight > table > thead > tr > th:last-child,
.card-body.tight > table > tbody > tr > td:last-child  { padding-right: 0.88rem; }
.card-body.tight > table { margin-bottom: 0; }

/* ---------- dialogs ----------
   Per the design system: dialogs are for quick, low-field actions — confirm,
   edit one thing, preview. Anything with four or more fields or multiple
   sections gets its own page instead. max-w-lg or max-w-2xl, never full screen.
   Footer: cancel on the left, primary action on the right. */
.scrim {
  position: fixed; inset: 0; z-index: 90; display: none;
  background: rgb(0 0 0 / 0.42);
  align-items: flex-start; justify-content: center;
  padding: 4rem 1rem 2rem;
  overflow-y: auto;
}
.scrim.open { display: flex; }
.dialog {
  background: hsl(var(--card)); color: hsl(var(--foreground));
  border: 1px solid hsl(var(--border)); border-radius: 3px;
  width: 100%; max-width: 28rem;
  box-shadow: 0 18px 40px -12px rgb(0 0 0 / 0.35);
}
.dialog.wide { max-width: 38rem; }
.dialog-head { padding: 0.88rem 0.95rem 0.5rem; }
.dialog-head h3 { font-size: 0.88rem; }
.dialog-head p { font-size: 0.7rem; color: hsl(var(--muted-foreground)); margin: 0.2rem 0 0; }
.dialog-body { padding: 0.6rem 0.95rem 0.88rem; }
.dialog-foot {
  border-top: 1px solid hsl(var(--border));
  padding: 0.6rem 0.95rem;
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
}
.dialog-foot .right { display: flex; gap: 0.4rem; margin-left: auto; }
.dialog-x {
  float: right; background: none; border: 0; cursor: pointer; font-size: 0.85rem;
  color: hsl(var(--muted-foreground)); line-height: 1; padding: 0.1rem 0.2rem;
}
.dialog-x:hover { color: hsl(var(--foreground)); }

/* form fields, shared by dialogs and full-page forms */
.field { display: flex; flex-direction: column; gap: 0.25rem; margin-bottom: 0.65rem; }
.field label { font-size: 0.68rem; font-weight: 500; }
.field .hint { font-size: 0.6rem; color: hsl(var(--muted-foreground)); }
.field input, .field select, .field textarea { width: 100%; font-family: inherit; }
.field textarea { padding: 0.4rem 0.5rem; border: 1px solid hsl(var(--input));
                  border-radius: 3px; background: hsl(var(--background));
                  color: hsl(var(--foreground)); font-size: 0.72rem; }
.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
@media (max-width: 620px) { .pair { grid-template-columns: 1fr; } }

/* ---------- figures ----------
   Numbers that are references rather than code: order numbers, HTTP status,
   IP addresses. Plex Sans, tabular, so they align in a column and still read
   as part of the interface rather than as a code snippet. */
.figs { font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }

/* A field that sits alone on its row rather than in a .pair. Without this a
   three-option select stretches the whole width of a card - 880px of chrome
   for the word "On" - while the identical select one card above, which happens
   to be in a .pair, looks right. */
.field.solo select, .field.solo input[type=text] { max-width: 20rem; }

/* ---------- numbered explanation ----------
   "What this feed does", and anything else that is a sequence rather than a
   list. Looser line height than body text because these are read once,
   carefully, by somebody who does not yet know what the feed does. */
.steps { margin: 0; padding-left: 1.15rem; font-size: 0.72rem; line-height: 1.75; }
.steps li { margin-bottom: 0.25rem; }
.steps li:last-child { margin-bottom: 0; }

/* ---------- readiness checklist ----------
   Supplier setup, and anything else that asks "is this fit to run yet". Each
   row is a badge and a sentence, and the badge must not stretch: DS8's width
   rule is right for a status column in a table and wrong here, where the badge
   sits beside prose rather than under a heading. */
/* A two-column grid rather than a stack of flex rows, so the badge column
   sizes itself to the longest badge *in this list* and every sentence starts
   on one line. A fixed min-width cannot do it: "Configured" and "Not
   configured" differ by half again, and a width that suits one list is wrong
   for the next. `display: contents` on the row lifts its two children into the
   grid so the rows still read as rows in the markup.

   Same problem DS8 solves for a table column, in a list rather than a column. */
.chk { display: grid; grid-template-columns: max-content minmax(0, 1fr);
       gap: 0.6rem 0.45rem; align-items: start; }
.chk-row { display: contents; }
.chk-row .badge { justify-self: stretch; justify-content: center; }
.chk-txt { font-size: 0.66rem; line-height: 1.5; }
.chk-txt .chk-why { display: block; color: hsl(var(--muted-foreground)); margin-top: 0.1rem; }

/* ---------- raw payload viewer ----------
   Shared by Problem detail and the Diagnostics API log. Plex Mono is correct
   here: this genuinely IS code, unlike the record identifiers in a table (DS7). */
.raw { font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: 0.64rem;
       line-height: 1.6; background: hsl(var(--muted));
       border: 1px solid hsl(var(--border)); border-radius: 3px;
       padding: 0.6rem 0.7rem; margin: 0; overflow-x: auto;
       white-space: pre-wrap; word-break: break-word; }
/* the one line that caused the failure, marked in the payload itself */
.raw .hl { background: hsl(0 84% 60% / 0.16); border-radius: 2px;
           box-shadow: 0 0 0 1px hsl(0 72% 52% / 0.35); }
.rawlbl { font-size: 0.55rem; font-weight: 700; text-transform: uppercase;
          letter-spacing: 0.09em; color: hsl(var(--muted-foreground));
          margin: 0.75rem 0 0.3rem; }
.rawlbl:first-child { margin-top: 0; }
/* Its own pane class rather than .view — .view.on is display:flex with a
   1.35rem gap, which is right for a page and far too loose inside a dialog. */
.rawpane { display: none; }
.rawpane.on { display: block; }

/* ---------- unauthenticated screens ----------
   Login, password reset, two-factor. No sidebar: nobody is signed in yet, so
   there is nothing to navigate to. One centred column, narrow enough that the
   single action is unmistakable. */
.auth {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 2rem 1.2rem; background: var(--content-bg);
}
.auth-box { width: 100%; max-width: 21.5rem; }
.auth-brand {
  display: flex; align-items: center; justify-content: center; gap: 0.5rem;
  margin-bottom: 1.15rem;
}
.auth-brand .logo-mark { width: 1.55rem; height: 1.55rem; }
.auth-brand .logo-text { font-size: 0.95rem; font-weight: 700; letter-spacing: -0.02em; }
.auth-title { font-size: 1rem; font-weight: 700; letter-spacing: -0.015em; }
.auth-sub { font-size: 0.7rem; color: hsl(var(--muted-foreground)); margin-top: 0.15rem; }
.auth-foot {
  margin-top: 0.9rem; text-align: center; font-size: 0.66rem;
  color: hsl(var(--muted-foreground));
}
.auth-foot a { font-weight: 500; }
/* the single action fills the card, so there is never a question which it is */
.btn-block { width: 100%; height: 1.9rem; }

/* one box per digit, so a six-digit code cannot be mistaken for a password */
.code-in {
  display: flex; gap: 0.4rem; justify-content: space-between; margin: 0.15rem 0 0.2rem;
}
.code-in input {
  width: 100%; text-align: center; height: 2.3rem; font-size: 1rem; font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
  border: 1px solid hsl(var(--input)); border-radius: 3px;
  background: hsl(var(--background)); color: hsl(var(--foreground)); font-family: inherit;
}
.code-in input:focus { outline: 2px solid hsl(var(--ring) / 0.45); outline-offset: 1px; }

/* ---------- error pages ----------
   403 and 404 keep the shell, because the user is signed in and the fastest
   thing we can do is put them back on a real screen. 500 does not, because we
   cannot assume anything about application state at that point. */
.errpage {
  max-width: 30rem; margin: 3.5rem auto; text-align: center;
}
.errpage .code {
  font-size: 2.6rem; font-weight: 700; line-height: 1; letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums; color: hsl(var(--muted-foreground) / 0.55);
}
.errpage h1 { font-size: 1.1rem; margin: 0.5rem 0 0.3rem; }
.errpage p { font-size: 0.75rem; line-height: 1.65; color: hsl(var(--muted-foreground)); margin: 0; }
.errpage .acts { display: flex; gap: 0.4rem; justify-content: center; margin-top: 1.1rem; flex-wrap: wrap; }
.errpage .ref {
  margin-top: 1.3rem; padding-top: 0.9rem; border-top: 1px solid hsl(var(--border));
  font-size: 0.62rem; color: hsl(var(--muted-foreground));
}

/* ---------- tab panes ----------
   .page is a flex column with a gap, which spaces its DIRECT children. A tab
   pane wrapper becomes that single direct child, so without this the cards
   inside it sit flush against each other. The pane repeats the page's own
   layout so content spaces identically whether or not it sits behind a tab. */
.view { display: none; }
.view.on { display: flex; flex-direction: column; gap: 1.35rem; }

/* ---------- error pages, standalone and in-shell ----------
   These carry detail below the message - a reference, the path that was asked
   for - and that detail reads badly centred, hence the left-aligned variant.

   Added 1 Sep 2026, promoting what the mockups expressed as inline styles.
   Under DS13 the stylesheet is the only place presentation lives, so a spacing
   tweak that recurs belongs here rather than in the markup. */
body.standalone { background: var(--content-bg); }
/* Scoped to the standalone page on purpose. Unscoped, this also matched the
   `field solo` modifier below - Settings' one-per-row fields - and centred
   them inside 3.5rem of padding, which is how the Load on Interprise card
   came to render with its control adrift in the middle of the box. */
body.standalone .solo { max-width: 33rem; margin: 0 auto; padding: 3.5rem 1.4rem; }
body.standalone .solo .auth-brand { justify-content: flex-start; margin-bottom: 1.6rem; }

.errpage.detailed { margin: 0; text-align: left; }
.errpage.detailed .acts { justify-content: flex-start; }

.errpage p + p { margin-top: 0.6rem; }
.errpage > .callout { margin-top: 1.7rem; }
.errpage > .callout .badge { margin-top: 0.1rem; }
.errpage > .card { margin-top: 1.2rem; text-align: left; }
.errpage > .card p.tiny { margin: 0; }
.errpage .divider { margin: 0.8rem 0; }

.nav-foot .user-row { padding: 0.3rem 0.4rem; }

/* ---------- table and dialog helpers ----------
   The mockups expressed these three as inline styles. DS13 does not allow that
   in a Django template, so they become classes with names that say what they
   are for. */

/* A tick-or-dash column in a matrix, where the mark means more than the label. */
.centre { text-align: center; }

/* A button that fills its dialog and reads as a list item rather than a
   control floating in space - the row-action menu is built from these. */
.btn.full { width: 100%; justify-content: flex-start; }

/* Destructive, and only ever secondary: the colour is the warning, the button
   itself stays outlined so it cannot be mistaken for the primary action. */
.btn.danger { color: hsl(0 72% 48%); }
.btn.danger:hover { border-color: hsl(0 72% 48% / 0.5); }

/* A checkbox and its label on one line, in the auth screens. */
.checkline { gap: 0.4rem; margin: 0.15rem 0 0.85rem; }

/* A one-time code: wide tracking so six digits are read as six, not as a
   number. Tabular figures come from .figs. */
.code-input { letter-spacing: 0.35em; text-align: center; font-size: 1.05rem; }

/* A selection column. Narrow enough that the checkbox does not push the first
   real column off its own alignment, and shared so every table that grows one
   gets the same width rather than each picking its own. */
th.pick, td.pick { width: 1.6rem; padding-right: 0; }


/* =====================================================================
   The Overview data-flow board (synchub-6ui).

   Lifted from the mockup verbatim below, comment and all, because the mockup
   is the specification and a re-typed stylesheet is a second copy to keep in
   step. Geometry lives in apps/dashboard/board.py as data - the SVG path and
   the two percentage offsets per link - so a link cannot reach the drawing
   without its state and its count arriving with it.
   ===================================================================== */

  /* ---------------------------------------------------------------------
     The data-flow board, drawn for the system that exists.

     overview.html carries the original: 33 feeds across 4 systems, with
     rotation, racks and column packing to make that many nodes fit. The real
     system is thirteen feeds across three, so none of that machinery is
     needed and all of it is removed. The layout here is fixed — seven nodes
     at written coordinates — which is why this file is a fraction of the
     length of the one it replaces.

     What it answers that the connection-test dialog does not: which feeds
     stop when a link is down. The dialog says "Magento SFTP: cannot reach".
     This says that, and that it is why the two price feeds have not run.

     Height is the binding constraint. Plan step 4.1 requires the figures and
     the callout above the fold on 1920×1080, so the board gets 240px and has
     to earn them.
     --------------------------------------------------------------------- */

  .board {
    position: relative; width: 100%; aspect-ratio: 1600/440; min-width: 44rem;
    background:
      linear-gradient(to right, hsl(var(--border)/0.5) 1px, transparent 1px) 0 0/40px 40px,
      linear-gradient(to bottom, hsl(var(--border)/0.5) 1px, transparent 1px) 0 0/40px 40px,
      hsl(var(--muted)/0.35);
    border: 1px solid hsl(var(--border)); border-radius: 3px; overflow: hidden;
  }
  #links { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; }

  /* Boxes always occlude connectors, never the reverse — a line crossing a
     node reads as a connection that is not there. */
  .node {
    position: absolute; transform: translate(-50%, -50%); z-index: 1;
    box-sizing: border-box; background: hsl(var(--card));
    border: 1px solid hsl(var(--border)); border-radius: 2px;
    display: flex; flex-direction: column; justify-content: center;
    padding: 0 0.65rem; cursor: pointer;
    transition: box-shadow .18s, border-color .18s, filter .25s;
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
  }
  .node:hover { border-color: hsl(var(--foreground)/0.45);
                box-shadow: 0 3px 8px -2px rgb(0 0 0 / 0.14); z-index: 9; }
  .node .nm { font-weight: 600; font-size: 0.72rem; letter-spacing: -0.01em;
              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .node .sb { font-size: 0.55rem; color: hsl(var(--muted-foreground));
              white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
              font-family: "IBM Plex Mono", ui-monospace, monospace; }
  .node .rag { position: absolute; top: 0.38rem; right: 0.42rem;
               width: 0.4rem; height: 0.4rem; border-radius: 999px; }

  .node.hub { border-width: 2px; border-color: hsl(var(--foreground)/0.55); }
  .node.hub .nm { font-size: 0.86rem; }
  .node.hub .sb { font-family: inherit; font-size: 0.58rem; }

  /* DS6: colour means state, only. Tint with a background-image over an
     opaque background-color — EX2, because a translucent box would show the
     grid and any passing connector straight through itself. */
  .node.s-a { border-color: hsl(38 92% 45% / 0.75);
              background-image: linear-gradient(hsl(38 92% 50% / 0.09), hsl(38 92% 50% / 0.09)); }
  .node.s-r { border-color: hsl(0 72% 52% / 0.8);
              background-image: linear-gradient(hsl(0 84% 60% / 0.08), hsl(0 84% 60% / 0.08)); }
  /* "Not enabled" is dashed with muted contents, never a see-through box. */
  .node.s-n { border-style: dashed; }
  .node.s-n .nm, .node.s-n .sb { opacity: 0.5; }
  .node.s-n .rag { opacity: 0.55; }

  .rag.g { background: hsl(160 84% 39%); }
  .rag.a { background: hsl(38 92% 50%); }
  .rag.r { background: hsl(0 84% 58%); }
  .rag.n { background: hsl(240 5% 62%); }

  .lnk { fill: none; stroke: hsl(var(--muted-foreground)/0.38); stroke-width: 1.6; }
  .lnk.a { stroke: hsl(38 92% 45% / 0.85); stroke-width: 2; }
  .lnk.r { stroke: hsl(0 72% 52% / 0.85); stroke-width: 2; stroke-dasharray: 6 5; }
  .lnk.n { stroke: hsl(var(--muted-foreground)/0.2); stroke-dasharray: 3 5; }

  /* An invisible, thick copy of each connector, so the line can be clicked.
     The visible stroke is 1.6px and nobody can hit that; the card invites
     "select a link", so the link itself has to be a target. */
  .hit { fill: none; stroke: transparent; stroke-width: 20; cursor: pointer; pointer-events: stroke; }

  /* A label sits on its line, so it is an opaque chip rather than a tint. */
  .lbl {
    position: absolute; transform: translate(-50%, -50%); z-index: 2;
    background: hsl(var(--card)); border: 1px solid hsl(var(--border));
    border-radius: 2px; padding: 0.05rem 0.32rem; white-space: nowrap;
    font-size: 0.55rem; color: hsl(var(--muted-foreground));
    font-variant-numeric: tabular-nums; cursor: pointer;
  }
  .lbl b { font-weight: 600; color: hsl(var(--foreground)); }

  /* Selecting fades the connectors hard and desaturates the boxes. It never
     makes a box translucent. */
  .board.focus .lnk { opacity: 0.08; }
  .board.focus .lnk.lit { opacity: 1; }
  .board.focus .lbl { opacity: 0.15; }
  .board.focus .lbl.lit { opacity: 1; }
  .board.focus .node { filter: grayscale(1); }
  .board.focus .node .nm { color: hsl(var(--muted-foreground)/0.55); }
  .board.focus .node .sb { color: hsl(var(--muted-foreground)/0.4); }
  .board.focus .node .rag { opacity: 0.2; }
  .board.focus .node.lit { filter: none; z-index: 2;
    box-shadow: 0 0 0 2px hsl(var(--ring)/0.3), 0 3px 8px -2px rgb(0 0 0 / 0.14); }
  .board.focus .node.lit .nm { color: hsl(var(--foreground)); }
  .board.focus .node.lit .sb { color: hsl(var(--muted-foreground)); }
  .board.focus .node.lit .rag { opacity: 1; }

  /* The line under the board is the point of the whole thing: it names the
     feeds that stop. Without it this is decoration. */
  .board-foot {
    display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
    padding: 0.6rem 0.1rem 0; font-size: 0.7rem;
  }
  .board-foot .what { color: hsl(var(--muted-foreground)); }
  .board-foot .what b { color: hsl(var(--foreground)); font-weight: 600; }
  .legend { display: flex; gap: 0.85rem; flex-wrap: wrap; margin-left: auto; }
  .legend span { display: flex; align-items: center; gap: 0.3rem;
                 font-size: 0.6rem; color: hsl(var(--muted-foreground)); }
  .legend i { width: 0.45rem; height: 0.45rem; border-radius: 999px; }

/* ── Traffic over time, and a breakdown by endpoint (API access, plan 5.6) ──
   Both are SVG: a bar's height is data, and DS13 keeps presentation in the
   stylesheet, so the geometry belongs in element attributes rather than in a
   style attribute. Everything that is presentation - colour, the height of the
   box, the dashed rule - is here. */
.chart {
  display: block;
  width: 100%;
  height: 9rem;
  margin-top: 0.8rem;
  overflow: visible;
}
.chart rect { fill: hsl(var(--muted-foreground) / 0.32); }
.chart rect.over { fill: hsl(0 84% 60%); }
.chart line.chart-limit {
  stroke: hsl(0 84% 60% / 0.7);
  stroke-width: 1;
  stroke-dasharray: 4 3;
}
.chart-axis { display: flex; justify-content: space-between; margin-top: 0.3rem; }
.chart-axis span { font-size: 0.55rem; color: hsl(var(--muted-foreground)); }

.bar-row {
  display: grid;
  grid-template-columns: 11rem 1fr 4rem;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.35rem;
}
.bar-row .figs { text-align: right; }
.bar { display: block; width: 100%; height: 0.6rem; }
.bar .bar-track { fill: hsl(var(--muted)); }
.bar .bar-fill { fill: hsl(var(--muted-foreground) / 0.5); }

/* ---------- run detail dialog (Activity) ----------
   Lists inside a dialog scroll on their own, so ninety held lines do not push
   the Close button off the screen. */
.run-metrics { margin-bottom: 0.9rem; }
.run-error { margin-top: 0.8rem; }
.run-list-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin: 0.7rem 0 0.35rem; font-size: 0.72rem;
}
.run-list-note { margin: 0.35rem 0 0; }
.list-scroll {
  max-height: 13rem; overflow-y: auto;
  border: 1px solid hsl(var(--border)); border-radius: 3px;
}
.list-scroll thead th { position: sticky; top: 0; background: hsl(var(--card)); }
