/*
 * SK Assistant — the look, built for a phone held in one hand.
 *
 * FOUR CONSTRAINTS SHAPED EVERYTHING BELOW, AND ALL FOUR COME FROM WHAT THIS
 * PAGE IS FOR RATHER THAN FROM TASTE.
 *
 * 1. UNKNOWN MUST NOT LOOK LIKE ZERO. `.num--unknown` is dimmer, lighter and
 *    narrower than a figure, so the em dash that src/core/events.ts:37 insists
 *    on for an unreported token count can never be skimmed as a 0. The whole
 *    event log distinguishes "used none" from "did not report"; a stylesheet
 *    that paints them identically throws that away at the last possible moment.
 *
 * 2. A BROKEN COMPONENT MUST NOT LOOK HEALTHY. `.live--down` is not a greyed
 *    out version of `.live--up`; it is critical-coloured, carries the word
 *    "Disconnected" and counts down to its own retry. A stale page wearing a
 *    live badge is the single failure this dashboard exists to refuse.
 *
 * 3. COLOUR IS NEVER THE ONLY SIGNAL. Every status carries an icon, a colour
 *    and a word. That is not only for colour vision: this gets read on a phone
 *    outdoors, where a 3:1 hue difference is gone entirely.
 *
 * 4. THE THUMB REACHES THE BOTTOM, NOT THE TOP. Primary actions live in
 *    `.bottombar`, every hit target is >= 44px, and `env(safe-area-inset-*)`
 *    is honoured so a notch or a home indicator never eats a control.
 *
 * THE PALETTE IS FIXED AND VALIDATED. Do not add hues. Categorical is capped at
 * three and used in one fixed order; the nine ordered stages get a single-hue
 * ordinal ramp, because nine categorical hues for nine ordered things says
 * "unrelated" about a sequence.
 */

/* ==========================================================================
   1 · Reset — only the parts that actually bite
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
html, body { margin: 0; padding: 0; }
body { min-height: 100%; }
button, input, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; margin: 0; padding: 0; cursor: pointer; text-align: inherit; }
h1, h2, h3, h4, p, figure, pre, dl, dd { margin: 0; }
ul, ol { list-style: none; padding: 0; margin: 0; }
a { color: inherit; }
svg { display: block; }
:focus-visible { outline: 2px solid var(--cat-1); outline-offset: 2px; border-radius: 4px; }

/* ==========================================================================
   2 · Tokens
   Light is the base. Dark redefines ONLY what changes, under both the media
   query (system setting, which stamps no attribute) and the explicit
   attribute, so an eventual toggle wins in both directions.
   ========================================================================== */
:root {
  /* One family throughout, numbers included. A separate "numeric" face buys
     nothing here and costs a second rendering of every digit. */
  --font: system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --surface: #fcfcfb;
  --page: #f9f9f7;
  --ink: #0b0b0b;
  --secondary: #52514e;
  --muted: #898781;
  --grid: #e1e0d9;
  --axis: #c3c2b7;
  --border: rgba(11, 11, 11, .10);

  /* Categorical. Three, in this order, never cycled. Series four does not
     exist; a chart that needs one needs to be two charts. */
  --cat-1: #2a78d6;
  --cat-2: #eb6834;
  --cat-3: #1baf7a;

  /* Ordinal, one blue hue, nine steps for the nine ordered stages. */
  --ord-1: #86b6ef;
  --ord-2: #6da7ec;
  --ord-3: #5598e7;
  --ord-4: #3987e5;
  --ord-5: #2a78d6;
  --ord-6: #256abf;
  --ord-7: #1c5cab;
  --ord-8: #184f95;
  --ord-9: #104281;

  --tint: rgba(42, 120, 214, .10);
  --sheet-scrim: rgba(11, 11, 11, .38);
  --elevation: 0 12px 32px rgba(11, 11, 11, .16);
}

/* Status colours are FIXED. They do not theme and they are never reused as a
   series colour — a bar that happens to be green must not be readable as
   "good". Contrast is carried by the icon and the word beside them. */
:root {
  --good: #0ca30c;
  --warning: #fab219;
  --serious: #ec835a;
  --critical: #d03b3b;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --surface: #1a1a19;
    --page: #0d0d0d;
    --ink: #ffffff;
    --secondary: #c3c2b7;
    --muted: #898781;
    --grid: #2c2c2a;
    --axis: #383835;
    --border: rgba(255, 255, 255, .10);

    --cat-1: #3987e5;
    --cat-2: #d95926;
    --cat-3: #199e70;

    /* THE RAMP INVERTS ON DARK, and that is the point of the "never darker
       than #184f95" rule: #104281 on a #0d0d0d page is a bar you cannot see.
       So the sequence runs dim-to-bright here instead of light-to-dark, which
       is the direction a sequential ramp has to run on a dark ground anyway.
       Nine slots, eight permitted values — stages 8 and 9 share the brightest.
       That costs nothing because every ordinal mark in this file is DIRECTLY
       LABELLED with its stage name; the hue orders, it never identifies. */
    --ord-1: #184f95;
    --ord-2: #1c5cab;
    --ord-3: #256abf;
    --ord-4: #2a78d6;
    --ord-5: #3987e5;
    --ord-6: #5598e7;
    --ord-7: #6da7ec;
    --ord-8: #86b6ef;
    --ord-9: #86b6ef;

    --tint: rgba(57, 135, 229, .16);
    --sheet-scrim: rgba(0, 0, 0, .60);
    --elevation: 0 12px 32px rgba(0, 0, 0, .55);
  }
}

:root[data-theme="dark"] {
  --surface: #1a1a19;
  --page: #0d0d0d;
  --ink: #ffffff;
  --secondary: #c3c2b7;
  --muted: #898781;
  --grid: #2c2c2a;
  --axis: #383835;
  --border: rgba(255, 255, 255, .10);

  --cat-1: #3987e5;
  --cat-2: #d95926;
  --cat-3: #199e70;

  --ord-1: #184f95;
  --ord-2: #1c5cab;
  --ord-3: #256abf;
  --ord-4: #2a78d6;
  --ord-5: #3987e5;
  --ord-6: #5598e7;
  --ord-7: #6da7ec;
  --ord-8: #86b6ef;
  --ord-9: #86b6ef;

  --tint: rgba(57, 135, 229, .16);
  --sheet-scrim: rgba(0, 0, 0, .60);
  --elevation: 0 12px 32px rgba(0, 0, 0, .55);
}

/* ==========================================================================
   3 · Page frame
   ========================================================================== */
body {
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.45;
  color: var(--ink);
  background: var(--page);
  -webkit-font-smoothing: antialiased;
  /* The page itself never rubber-bands. Individual scrollers opt back in to
     containment; the body must not bounce a sheet's backdrop around. */
  overscroll-behavior-y: none;
}

#root { min-height: 100vh; min-height: 100dvh; }

/*
 * THE APP SHELL OWNS THE SCROLL, AND THAT IS NOT A STYLING PREFERENCE.
 *
 * The shell is exactly one viewport tall and clips, so `.scroll` inside it is
 * the only thing on the page that moves. Three behaviours depend on that being
 * true, and all three silently do nothing if the document scrolls instead:
 *
 *   - `overscroll-behavior: contain` on `.scroll` — without a real scroller
 *     there is nothing to contain, and a sheet drags the page behind it.
 *   - pull-to-refresh — the gesture arms on `scrollTop === 0`, and an element
 *     that never scrolls reports 0 from every position on the page, so the
 *     pull would fire halfway down the timeline.
 *   - following the tail of a live timeline only when he is already at it —
 *     that test compares scrollHeight, scrollTop and clientHeight on `.scroll`,
 *     which are indistinguishable on an element that is not a scroller.
 *
 * 100vh first as the fallback: dvh is Safari 15.4+, and the older behaviour
 * (a shell slightly taller than the visible area) is worse than this but far
 * better than no height at all.
 */
.shell {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

.pane {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}

/* --- top bar ------------------------------------------------------------ */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 12px;
  padding-top: env(safe-area-inset-top);
  padding-left: max(12px, env(safe-area-inset-left));
  padding-right: max(12px, env(safe-area-inset-right));
  min-height: calc(52px + env(safe-area-inset-top));
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid var(--border);
}

.topbar__back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-left: -10px;
  border-radius: 10px;
  color: var(--cat-1);
  flex: none;
}
.topbar__back:active { background: var(--tint); }

.topbar__titles { min-width: 0; flex: 1; }
.topbar__title {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar__sub {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- scroller ----------------------------------------------------------- */
.scroll {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 12px;
  padding-left: max(12px, env(safe-area-inset-left));
  padding-right: max(12px, env(safe-area-inset-right));
  padding-bottom: 16px;
}

/* --- bottom bar --------------------------------------------------------- */
/* In the flow rather than fixed. It is the last row of a flex column that is
   exactly one viewport tall, so it sits at the bottom without being positioned
   there — which means it cannot overlap the last card, and `.scroll` does not
   need a padding hack sized to guess its height. */
.bottombar {
  flex: none;
  display: flex;
  gap: 8px;
  padding: 8px 12px;
  padding-bottom: calc(8px + env(safe-area-inset-bottom));
  padding-left: max(12px, env(safe-area-inset-left));
  padding-right: max(12px, env(safe-area-inset-right));
  background: var(--surface);
  border-top: 1px solid var(--border);
}

/* ==========================================================================
   4 · Buttons
   Every one is >= 44px tall. A 32px button is a miss on a moving train.
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 11px;
  font-size: 15px;
  font-weight: 550;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--ink);
  white-space: nowrap;
  transition: transform .12s ease, background-color .12s ease;
}
.btn:active { transform: scale(.975); }
.btn[disabled] { opacity: .45; pointer-events: none; }
.btn--grow { flex: 1; }
.btn--primary { background: var(--cat-1); border-color: transparent; color: #ffffff; }
.btn--danger { color: var(--critical); border-color: color-mix(in srgb, var(--critical) 40%, transparent); }
.btn--danger-solid { background: var(--critical); border-color: transparent; color: #ffffff; }
.btn--quiet { background: transparent; border-color: transparent; color: var(--cat-1); }
.btn--small { min-height: 36px; padding: 0 11px; font-size: 13px; border-radius: 9px; }

/* ==========================================================================
   5 · Cards and section furniture
   ========================================================================== */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px;
  margin-bottom: 12px;
}
.card--flush { padding: 0; overflow: hidden; }

.card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-bottom: 12px;
}
/* The title never wraps and the note yields. A two-line uppercase label beside
   a two-line note is four lines of furniture above one chart, on the screen
   with the least room. */
.card__title {
  flex: none;
  white-space: nowrap;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
}
.card__note {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  font-size: 12px;
  color: var(--muted);
}

.timeline-tools {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 9px;
  margin-left: auto;
}
.segmented {
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--page);
}
.segmented button {
  min-height: 44px;
  padding: 0 9px;
  border-radius: 7px;
  color: var(--muted);
  font-size: 12px;
}
.segmented button.is-active {
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .12);
}

.empty {
  padding: 32px 16px;
  text-align: center;
  color: var(--muted);
  font-size: 14px;
}

.mono { font-family: var(--font-mono); font-size: .92em; }

/* Figures that stack in a column line up. Prose does not use this — tabular
   digits in a sentence read as a receipt. */
.tnum { font-variant-numeric: tabular-nums; }

.num--unknown {
  color: var(--muted);
  opacity: .75;
  font-weight: 400;
  letter-spacing: .04em;
}

/* ==========================================================================
   6 · Status — icon + colour + word, everywhere, no exceptions
   ========================================================================== */
.status {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  font-weight: 550;
  white-space: nowrap;
}
.status__icon { flex: none; width: 14px; height: 14px; }

.s-good     { color: var(--good); }
.s-warning  { color: #8a6100; }         /* #fab219 on white is 1.9:1 — the word needs a darker ink than the mark */
.s-serious  { color: #a8482a; }         /* likewise: the dot stays #ec835a, the text darkens to carry the meaning */
.s-critical { color: var(--critical); }
.s-running  { color: var(--cat-1); }
.s-idle     { color: var(--muted); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .s-warning { color: #f0bc55; }
  :root:not([data-theme="light"]) .s-serious { color: #f0a184; }
  :root:not([data-theme="light"]) .s-good { color: #35c035; }
  :root:not([data-theme="light"]) .s-critical { color: #f06a6a; }
}
:root[data-theme="dark"] .s-warning { color: #f0bc55; }
:root[data-theme="dark"] .s-serious { color: #f0a184; }
:root[data-theme="dark"] .s-good { color: #35c035; }
:root[data-theme="dark"] .s-critical { color: #f06a6a; }

/* ==========================================================================
   7 · Task list
   ========================================================================== */
.tasklist { display: block; }

.home-section { margin-bottom: 22px; }
.home-section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 2px 2px 9px;
}
.home-section__title {
  font-size: 12px;
  font-weight: 650;
  letter-spacing: .065em;
  text-transform: uppercase;
  color: var(--muted);
}
.home-section__note, .tree__count {
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.home-empty, .tree__empty, .history-warning {
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
  padding: 13px 14px;
}
.history-warning { padding-left: 2px; padding-right: 2px; }

.row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px;
  min-height: 72px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  margin-bottom: 8px;
  transition: transform .12s ease;
}
.row:active { transform: scale(.99); }
.row--active {
  min-height: 92px;
  border-color: color-mix(in srgb, var(--cat-1) 42%, var(--border));
  box-shadow: 0 8px 24px color-mix(in srgb, var(--cat-1) 10%, transparent);
}

.row__body { min-width: 0; flex: 1; }
.row__title {
  font-size: 15px;
  font-weight: 550;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.row__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-top: 5px;
  font-size: 12px;
  color: var(--muted);
}
.row__chev { flex: none; color: var(--axis); }

.tree {
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}
.tree__client + .tree__client { border-top: 1px solid var(--border); }
.tree__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 48px;
  padding: 0 13px;
  cursor: pointer;
}
.tree__summary::before {
  content: "›";
  flex: none;
  color: var(--axis);
  font-size: 20px;
  line-height: 1;
  transform-origin: center;
  transition: transform .12s ease;
}
.tree__client[open] > .tree__summary::before,
.tree__project[open] > .tree__summary::before { transform: rotate(90deg); }
.tree__summary--client { font-size: 14px; font-weight: 650; }
.tree__summary--project { min-height: 44px; font-size: 13.5px; font-weight: 550; }
.tree__projects { padding: 0 8px 8px 20px; }
.tree__project {
  border-left: 1px solid var(--border);
  border-top: 1px solid var(--border);
}
.tree__project:first-child { border-top: 0; }
.tree__tasks { padding: 0 6px 5px 10px; }
.tree__tasks .row { background: var(--page); min-height: 64px; }
.tree__count { margin-left: auto; white-space: nowrap; font-weight: 400; }

/* --- progress ring ------------------------------------------------------ */
/* Replaces reading a status word at a glance: the arc says how far, the colour
   and the centre glyph say how it is going. The word is still in the row. */
.ring { flex: none; position: relative; width: 44px; height: 44px; }
.ring__svg { width: 44px; height: 44px; transform: rotate(-90deg); }
.ring__track { fill: none; stroke: var(--grid); stroke-width: 4; }
.ring__arc {
  fill: none;
  stroke-width: 4;
  stroke-linecap: round;
  transition: stroke-dashoffset .4s ease;
}
.ring__label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}

/* ==========================================================================
   8 · Stat tiles — big figure, small label
   ========================================================================== */
.tiles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
@media (min-width: 420px) { .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 11px 12px;
  min-width: 0;
}
.tile__value {
  font-size: 25px;
  font-weight: 600;
  letter-spacing: -.025em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tile__unit { font-size: 14px; font-weight: 500; color: var(--secondary); margin-left: 1px; }
.tile__label {
  margin-top: 3px;
  font-size: 11.5px;
  color: var(--muted);
  letter-spacing: .01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ==========================================================================
   9 · Stage flow — the hero
   Horizontally scrollable on a phone; one glance answers "where is it, and is
   it moving". Nodes carry an icon, a word and a duration; never colour alone.
   ========================================================================== */
.flow {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  margin: 0 -14px;
  padding: 2px 14px 6px;
  scrollbar-width: thin;
}
.flow::-webkit-scrollbar { height: 4px; }
.flow::-webkit-scrollbar-thumb { background: var(--axis); border-radius: 2px; }

.flow__track { display: flex; align-items: flex-start; min-width: min-content; }

.node {
  position: relative;
  flex: none;
  width: 78px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  scroll-snap-align: center;
  background: none;
}
/* The connector belongs to the node on its right, so the first node has none
   and the line never dangles off the end of the track. */
.node + .node::before {
  content: "";
  position: absolute;
  left: -39px;
  top: 17px;
  width: 78px;
  height: 2px;
  background: var(--grid);
}
.node + .node.is-reached::before { background: var(--axis); }

.node__disc {
  position: relative;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--grid);
  background: var(--surface);
  color: var(--muted);
  z-index: 1;
}
.node__icon { width: 17px; height: 17px; }

/* `.node--pending` likewise has no rule: the base .node__disc above is exactly
   what a stage that has not started should look like. */
.node--done .node__disc { border-color: var(--good); color: var(--good); }
.node--running .node__disc { border-color: var(--cat-1); background: var(--cat-1); color: #ffffff; }
.node--failed .node__disc { border-color: var(--critical); background: var(--critical); color: #ffffff; }
.node--skipped .node__disc { border-style: dashed; }

.node__num {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 8px;
  background: var(--page);
  border: 1px solid var(--border);
  color: var(--muted);
  font-size: 9.5px;
  font-weight: 650;
  line-height: 13px;
  font-variant-numeric: tabular-nums;
}

.node__name {
  margin-top: 6px;
  font-size: 11.5px;
  font-weight: 550;
  line-height: 1.25;
  color: var(--ink);
}
.node__state {
  font-size: 10.5px;
  line-height: 1.3;
  font-weight: 550;
}
.node__dur {
  font-size: 10.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  line-height: 1.35;
}

/* The running node is the only thing on the page that moves. Motion here is
   load-bearing — it is the difference between "working" and "hung", which is
   the question the whole dashboard exists to answer. */
.node--running .node__disc::after {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 2px solid var(--cat-1);
  animation: ska-pulse 1.9s ease-out infinite;
}
@keyframes ska-pulse {
  0%   { transform: scale(.82); opacity: .85; }
  100% { transform: scale(1.25); opacity: 0; }
}

/* ==========================================================================
   10 · Meters — budget against its cap
   ========================================================================== */
.meter + .meter { margin-top: 13px; }
.meter__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 5px;
}
.meter__name { font-size: 13px; font-weight: 500; }
.meter__nums { font-size: 12.5px; color: var(--secondary); font-variant-numeric: tabular-nums; }
.meter__track {
  position: relative;
  height: 10px;
  border-radius: 5px;
  background: var(--grid);
  overflow: hidden;
}
/* `.meter--ok` has no rule ON PURPOSE: the default fill below IS the healthy
   state, and an empty ruleset for it would read as an unfinished one. Only the
   two states that change anything are declared. */
.meter__fill {
  height: 100%;
  border-radius: 5px 4px 4px 5px;
  background: var(--cat-1);
  transition: width .4s ease;
}
.meter--warning .meter__fill { background: var(--warning); }
.meter--critical .meter__fill { background: var(--critical); }
.meter__word { margin-top: 4px; font-size: 11.5px; }

/* ==========================================================================
   11 · Bar chart — stage durations
   Horizontal, ordinal ramp, direct-labelled. 4px rounded data-end anchored to
   the baseline; the zero end stays square so the baseline reads as a baseline.
   ========================================================================== */
.bars { position: relative; }

.bars__grid {
  position: absolute;
  left: 84px;
  right: 58px;
  top: 0;
  bottom: 0;
  pointer-events: none;
}
.bars__gridline {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--grid);
}
.bars__gridline:first-child { background: var(--axis); }

.bar {
  position: relative;
  display: grid;
  /* The value column fits the widest string it can hold, "19m 11s+" for a
     stage that is still running, without ellipsis. */
  grid-template-columns: 84px minmax(0, 1fr) 58px;
  align-items: center;
  gap: 0;
  height: 20px;
  margin-bottom: 2px;   /* 2px between bars, as specified */
}
.bar__name {
  font-size: 11.5px;
  color: var(--secondary);
  padding-right: 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bar__track { position: relative; height: 100%; display: flex; align-items: center; }
.bar__fill {
  height: 14px;
  min-width: 2px;
  border-radius: 0 4px 4px 0;
  transition: width .4s ease;
}
.bar__value {
  font-size: 11.5px;
  color: var(--secondary);
  font-variant-numeric: tabular-nums;
  text-align: right;
  padding-left: 8px;
  white-space: nowrap;
}
.bar__value--unknown { color: var(--muted); opacity: .75; }

.o1 { background: var(--ord-1); }
.o2 { background: var(--ord-2); }
.o3 { background: var(--ord-3); }
.o4 { background: var(--ord-4); }
.o5 { background: var(--ord-5); }
.o6 { background: var(--ord-6); }
.o7 { background: var(--ord-7); }
.o8 { background: var(--ord-8); }
.o9 { background: var(--ord-9); }

/* ==========================================================================
   12 · Token composition — three series, stacked, direct-labelled
   Never a fourth series, never a second axis. cacheRead + cacheCreate are one
   thing called "cached" because he does not care which half of the cache it
   came from, only that it was not fresh input.
   ========================================================================== */
.stack {
  display: flex;
  height: 16px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--grid);
}
.stack__seg { height: 100%; min-width: 0; transition: width .4s ease; }
.stack__seg--1 { background: var(--cat-1); }
.stack__seg--2 { background: var(--cat-2); }
.stack__seg--3 { background: var(--cat-3); }

.legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 11px; }
.legend__item { display: flex; align-items: center; gap: 7px; font-size: 12.5px; }
.legend__swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.legend__name { color: var(--secondary); }
.legend__value { font-weight: 600; font-variant-numeric: tabular-nums; margin-left: auto; }

/* ==========================================================================
   13 · Work-unit waves — the parallel-versus-sequential picture
   ========================================================================== */
.wave + .wave { margin-top: 14px; }
.wave__head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 7px;
}
.wave__n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 6px;
  background: var(--tint);
  color: var(--cat-1);
  font-size: 11px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  flex: none;
}
.wave__desc { font-size: 11.5px; color: var(--muted); }

.wave__chips { display: flex; flex-wrap: wrap; gap: 6px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  min-height: 32px;
  padding: 5px 10px;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 12.5px;
  line-height: 1.25;
}
.chip__dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--axis); }
.chip__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip__state { font-size: 10.5px; font-weight: 600; flex: none; }

.chip--passed  { border-color: color-mix(in srgb, var(--good) 45%, transparent); }
.chip--passed .chip__dot { background: var(--good); }
.chip--running { border-color: color-mix(in srgb, var(--cat-1) 55%, transparent); background: var(--tint); }
.chip--running .chip__dot { background: var(--cat-1); }
.chip--failed  { border-color: color-mix(in srgb, var(--critical) 50%, transparent); }
.chip--failed .chip__dot { background: var(--critical); }
.chip--abandoned { border-color: color-mix(in srgb, var(--serious) 50%, transparent); }
.chip--abandoned .chip__dot { background: var(--serious); }
.chip--waiting .chip__dot { background: var(--axis); }

.wave__deps { margin-top: 4px; font-size: 11px; color: var(--muted); }

/* ==========================================================================
   14 · Timeline
   ========================================================================== */
.tl { position: relative; }

.tl__item {
  position: relative;
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr);
  gap: 10px;
  padding-bottom: 14px;
}
/* The connector is drawn by the gutter of each row except the last, so it can
   never run past the final event into empty space. */
.tl__item:not(:last-child) .tl__gutter::after {
  content: "";
  position: absolute;
  left: 14px;
  top: 30px;
  bottom: -14px;
  width: 1.5px;
  background: var(--grid);
}
.tl__gutter { position: relative; }
.tl__mark {
  width: 29px;
  height: 29px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--secondary);
}
.tl__mark svg { width: 14px; height: 14px; }
.tl__mark--good { color: var(--good); border-color: color-mix(in srgb, var(--good) 45%, transparent); }
.tl__mark--critical { color: var(--critical); border-color: color-mix(in srgb, var(--critical) 45%, transparent); }
.tl__mark--warning { color: var(--warning); border-color: color-mix(in srgb, var(--warning) 55%, transparent); }
.tl__mark--accent { color: var(--cat-1); border-color: color-mix(in srgb, var(--cat-1) 45%, transparent); }

.tl__body { min-width: 0; padding-top: 3px; }

/* The whole row is the affordance for opening the step. Full width and
   left-aligned so it does not read as a button, only behave as one. */
.tl__open { display: block; width: 100%; text-align: left; border-radius: 8px; }
.tl__open:active { background: var(--tint); }

.tl__top { display: flex; align-items: baseline; gap: 8px; }
.tl__summary { font-size: 14px; font-weight: 500; line-height: 1.35; min-width: 0; flex: 1; }
.tl__when { font-size: 11.5px; color: var(--muted); white-space: nowrap; flex: none; }

.tl__stage {
  margin-top: 4px;
  font-size: 10.5px;
  color: var(--muted);
}
.tl__stagename { font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }
/* NOT uppercased. `text-transform` on the whole line turned "240ms" into
   "240MS", which reads as a different unit. */
.tl__dur { font-variant-numeric: tabular-nums; }

/* A duration bar inside a timeline row is scaled against the longest event in
   the same task, so relative length is meaningful within one timeline only. */
.tl__durbar {
  margin-top: 6px;
  height: 5px;
  border-radius: 0 3px 3px 0;
  background: var(--cat-1);
  min-width: 2px;
  opacity: .55;
}

.tl__chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
.tok {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 7px;
  border-radius: 6px;
  background: var(--page);
  border: 1px solid var(--border);
  font-size: 11px;
  color: var(--secondary);
  font-variant-numeric: tabular-nums;
}
.tok__swatch { width: 7px; height: 7px; border-radius: 2px; flex: none; }

.tl__actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }

/* --- payload disclosure ------------------------------------------------- */
.payload { margin-top: 8px; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
.payload__toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 40px;
  padding: 0 10px;
  font-size: 12px;
  font-weight: 550;
  color: var(--secondary);
  background: var(--page);
}
.payload__kind { color: var(--muted); font-weight: 500; }
.payload__meta { margin-left: auto; color: var(--muted); font-weight: 400; font-variant-numeric: tabular-nums; }
.payload__text {
  margin: 0;
  padding: 10px;
  max-height: 46vh;
  overflow: auto;
  overscroll-behavior: contain;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: var(--font-mono);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--secondary);
  background: var(--surface);
  border-top: 1px solid var(--border);
}
.payload__note { padding: 8px 10px; font-size: 11.5px; border-top: 1px solid var(--border); }

/* ==========================================================================
   15 · Live indicator, banners, warnings
   ========================================================================== */
.live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  font-weight: 600;
  padding: 4px 9px;
  border-radius: 8px;
  border: 1px solid var(--border);
  white-space: nowrap;
  flex: none;
}
.live__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }

.live--up { color: var(--good); border-color: color-mix(in srgb, var(--good) 40%, transparent); }
.live--up .live__dot { background: var(--good); animation: ska-blink 2.4s ease-in-out infinite; }

.live--connecting { color: var(--secondary); }
.live--connecting .live__dot { background: var(--axis); }

/* Not a greyed-out "up". A dead stream is a failure and looks like one. */
.live--down {
  color: var(--critical);
  border-color: color-mix(in srgb, var(--critical) 55%, transparent);
  background: color-mix(in srgb, var(--critical) 8%, transparent);
}
.live--down .live__dot { background: var(--critical); }

.live--idle { color: var(--muted); }
.live--idle .live__dot { background: var(--muted); }

@keyframes ska-blink { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

.banner {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 11px 12px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface);
  margin-bottom: 12px;
  font-size: 13px;
  line-height: 1.4;
}
.banner__icon { flex: none; width: 17px; height: 17px; margin-top: 1px; }
.banner__body { min-width: 0; flex: 1; }
.banner__title { font-weight: 600; }
.banner__text { color: var(--secondary); margin-top: 2px; }
.banner__text .btn { display: flex; margin-top: 9px; }

.banner__close {
  flex: none;
  width: 44px;
  height: 44px;
  margin: -12px -12px -12px 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  border-radius: 10px;
}
.banner__close:active { background: var(--tint); }

.banner--critical { border-color: color-mix(in srgb, var(--critical) 45%, transparent); background: color-mix(in srgb, var(--critical) 7%, transparent); }
.banner--critical .banner__icon { color: var(--critical); }
.banner--warning { border-color: color-mix(in srgb, var(--warning) 55%, transparent); background: color-mix(in srgb, var(--warning) 10%, transparent); }
.banner--warning .banner__icon { color: #8a6100; }
.banner--serious { border-color: color-mix(in srgb, var(--serious) 50%, transparent); background: color-mix(in srgb, var(--serious) 9%, transparent); }
.banner--serious .banner__icon { color: #a8482a; }
.banner--accent { border-color: color-mix(in srgb, var(--cat-1) 45%, transparent); background: var(--tint); }
.banner--accent .banner__icon { color: var(--cat-1); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .banner--warning .banner__icon { color: #f0bc55; }
  :root:not([data-theme="light"]) .banner--serious .banner__icon { color: #f0a184; }
}
:root[data-theme="dark"] .banner--warning .banner__icon { color: #f0bc55; }
:root[data-theme="dark"] .banner--serious .banner__icon { color: #f0a184; }

/* ==========================================================================
   16 · Pull to refresh
   ========================================================================== */
.ptr {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  overflow: hidden;
  color: var(--muted);
  font-size: 12px;
  font-weight: 550;
}
.ptr__spinner { width: 16px; height: 16px; color: var(--cat-1); }
.ptr--armed { color: var(--cat-1); }
.ptr--busy .ptr__spinner { animation: ska-spin 900ms linear infinite; }
@keyframes ska-spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   17 · Sheets — compose, answer, feedback, confirm
   ========================================================================== */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--sheet-scrim);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  animation: ska-fade .16s ease-out;
}
@keyframes ska-fade { from { opacity: 0; } to { opacity: 1; } }

.sheet {
  width: 100%;
  max-width: 560px;
  max-height: 92dvh;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-radius: 18px 18px 0 0;
  border: 1px solid var(--border);
  border-bottom: 0;
  box-shadow: var(--elevation);
  padding: 8px 16px calc(16px + env(safe-area-inset-bottom));
  animation: ska-rise .2s cubic-bezier(.22, 1, .36, 1);
}
@keyframes ska-rise { from { transform: translateY(14%); } to { transform: translateY(0); } }

.sheet__grip {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--axis);
  margin: 4px auto 12px;
  flex: none;
}
.sheet__title { font-size: 17px; font-weight: 600; letter-spacing: -.01em; }
.sheet__text { font-size: 13.5px; color: var(--secondary); margin-top: 6px; line-height: 1.45; }
.sheet__scroll { overflow-y: auto; overscroll-behavior: contain; flex: 1; margin: 0 -2px; padding: 0 2px; }
.sheet__actions { display: flex; gap: 8px; margin-top: 14px; flex: none; }

.field { margin-top: 12px; }
.field__label { display: block; font-size: 12px; font-weight: 600; color: var(--secondary); margin-bottom: 6px; }
.input, .textarea {
  width: 100%;
  padding: 11px 12px;
  border-radius: 11px;
  border: 1px solid var(--axis);
  background: var(--page);
  color: var(--ink);
  /* 16px minimum: iOS Safari zooms the whole viewport on focus below it, and a
     zoomed viewport does not zoom back out. */
  font-size: 16px;
  line-height: 1.45;
}
.textarea { min-height: 108px; resize: vertical; }
.input:focus, .textarea:focus { border-color: var(--cat-1); outline: none; }

.quote {
  margin-top: 10px;
  padding: 10px 12px;
  border-left: 3px solid var(--cat-1);
  border-radius: 0 8px 8px 0;
  background: var(--page);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--secondary);
}

/* Sheet-scale supporting text. Deliberately NOT `.card__note`, which is
   right-aligned for a card header and reads as a stray caption in a form. */
.hint { margin-top: 9px; font-size: 12px; color: var(--muted); }

.formerror { margin-top: 10px; font-size: 13px; }

/* ==========================================================================
   18 · Login
   ========================================================================== */
.login {
  min-height: 100vh;
  min-height: 100dvh;
  /* Scrollable rather than clipped: an on-screen keyboard can leave less room
     than this needs, and a sign-in button pushed off a page that cannot scroll
     is a locked door. */
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 24px;
  padding-left: max(24px, env(safe-area-inset-left));
  padding-right: max(24px, env(safe-area-inset-right));
  max-width: 420px;
  margin: 0 auto;
}
.login__mark {
  width: 52px;
  height: 52px;
  border-radius: 14px;
  background: var(--cat-1);
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 19px;
  letter-spacing: .02em;
  margin-bottom: 18px;
}
.login__title { font-size: 24px; font-weight: 650; letter-spacing: -.02em; }
.login__sub { font-size: 14px; color: var(--secondary); margin-top: 6px; }

/* ==========================================================================
   19 · Boot / fatal screens (index.html renders these before app.js runs)
   ========================================================================== */
.boot {
  max-width: 34rem;
  margin: 0 auto;
  padding: 15dvh 24px 24px;
  text-align: center;
}
.boot__mark {
  width: 48px;
  height: 48px;
  border-radius: 13px;
  background: var(--cat-1);
  color: #ffffff;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 16px;
}
.boot__title { font-size: 20px; font-weight: 650; letter-spacing: -.02em; }
.boot__body { margin-top: 10px; color: var(--secondary); font-size: 14px; line-height: 1.55; }
.boot--error .boot__mark { background: var(--critical); }
.boot--error .boot__title { color: var(--critical); }

/* ==========================================================================
   20 · Reduced motion
   Every animation above is decoration EXCEPT the running pulse, and even that
   duplicates a word already on the node. So all of it goes.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .node--running .node__disc::after { display: none; }
  .live--up .live__dot { animation: none; }
}

/* ==========================================================================
   21 · Wide screens
   The phone layout is the source of truth. This widens it; it does not replace
   it. Above 900px the list and the detail sit side by side, because at that
   width the drill-down wastes an entire half of the screen.
   ========================================================================== */
@media (min-width: 900px) {
  .shell--split {
    display: grid;
    grid-template-columns: 360px minmax(0, 1fr);
  }
  .pane--list { border-right: 1px solid var(--border); background: var(--surface); }
  /* The rail is the raised surface here, so its rows invert to stay legible
     against it — the same two tokens, swapped, rather than a third colour. */
  .pane--list .row { background: var(--page); }
  /* Centred rather than left-aligned: a 900px column pinned to the left edge
     of a 1400px pane reads as a layout that failed to fill, not as a measure. */
  .pane--detail .scroll { max-width: 900px; width: 100%; margin: 0 auto; }
  /* Back is meaningless when the list is permanently on screen beside it. */
  .topbar__back--split { display: none; }
  .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .sheet { border-radius: 18px; }
  .scrim { align-items: center; padding: 24px; }
}
