:root {
  --bg: #14161b;
  --fg: #d6d9e0;
  --muted: #7e8493;
  --dim: #1c1f26;
  --line: #292d36;

  --accent: #8ec7b6;
  --ok: #82c2a0;
  --warn: #e0b87f;
  --bad: #de8a8a;

  /* Claude's signature terracotta — drives the usage progress fills so the
     limit bars read like claude.ai's own usage screen. */
  --claude: #d97757;

  --c-openai:     #6fb8c9;
  --c-anthropic:  #d99a6c;
  --c-claude-web: #d98a6e;
  --c-manual:     #a899d6;
  --c-webhook:    #aac279;
  --c-demo:       #d68ec0;
  --c-fallback:   #b8bcc6;

  --safe-top: env(safe-area-inset-top, 0);
  --safe-bottom: env(safe-area-inset-bottom, 0);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  background: var(--bg);
  color: var(--fg);
  font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
  font-weight: 440;
  font-feature-settings: "tnum" 1, "ss01" 1;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

body {
  min-height: 100vh;
  min-height: 100dvh;
  /* Lock the page to the viewport width so a stray wide element can't push
     the body past 100vw, which would let mobile browsers auto-zoom-out. The
     internal panels that intentionally scroll horizontally (.views, the tab
     strip) carry their own overflow:auto. */
  max-width: 100vw;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  padding:
    calc(0.75rem + var(--safe-top))
    0.75rem
    calc(0.75rem + var(--safe-bottom));
  gap: 0.75rem;
  transition: padding 220ms cubic-bezier(.2,.7,.25,1), gap 220ms cubic-bezier(.2,.7,.25,1);
}

button, a { font: inherit; color: inherit; text-decoration: none; cursor: pointer; }
button { background: none; border: 0; }

/* ─────────────────── refresh progress ─────────────────── */
/* Thin hairline pinned to the very top of the viewport, fills from left to
   right over REFRESH_MS so the user has an ambient sense of when fresh data
   is about to land. Restarted by app.js at the end of each successful poll
   via the Web Animations API (no class toggling needed). */

.refresh-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 50;
  pointer-events: none;
  overflow: hidden;
  background: transparent;
}
.refresh-progress-bar {
  height: 100%;
  width: 100%;
  background: var(--accent);
  opacity: 0.32;
  transform: scaleX(0);
  transform-origin: left center;
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .refresh-progress { display: none; }
}

/* ─────────────────── top bar ─────────────────── */

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.25rem 0.25rem;
}

.bar-status {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.18em;
}

.bar-dot {
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  background: var(--muted);
}
.dot--ok   { background: var(--ok);   box-shadow: 0 0 9px var(--ok); }
.dot--warn { background: var(--warn); box-shadow: 0 0 9px var(--warn); }
.dot--bad  { background: var(--bad);  box-shadow: 0 0 9px var(--bad); }
.dot--idle { background: #444; }

.bar-actions { display: flex; gap: 0.4rem; }

.pill {
  display: inline-flex;
  align-items: center;
  padding: 0.45rem 0.85rem;
  border: 2px solid var(--fg);
  border-radius: 999px;
  background: var(--bg);
  color: var(--fg);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  line-height: 1;
}
.pill:hover { background: var(--fg); color: var(--bg); }
.pill[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #000;
}

/* ─────────────────── view tabs ─────────────────── */

.view-tabs {
  display: flex;
  gap: 0.4rem;
  padding: 0.15rem;
  /* Mobile: the row of five tabs would otherwise be wider than the viewport,
     which makes the browser zoom the whole page out to fit. Let the row
     scroll horizontally instead so the page itself always matches the device
     width and the user can't accidentally pinch-zoom-out the dashboard. */
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
}
.view-tabs::-webkit-scrollbar { display: none; }

.view-tab {
  /* `1 0 auto` = grow to fill if there's slack, but never shrink below content
     (so labels stay readable). Combined with the parent's overflow-x: auto,
     tabs evenly distribute on desktop and become a swipeable strip on
     narrow phones. */
  flex: 1 0 auto;
  scroll-snap-align: start;
  white-space: nowrap;
  padding: 0.65rem 0.7rem;
  border: 2px solid var(--line);
  border-radius: 999px;
  background: var(--bg);
  color: var(--muted);
  font: inherit;
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
@media (max-width: 480px) {
  /* On narrow phones, also trim the tab chrome a notch so most of them fit
     without needing to scroll. */
  .view-tab {
    padding: 0.55rem 0.6rem;
    font-size: 0.66rem;
    letter-spacing: 0.16em;
  }
}
.view-tab:hover { color: var(--fg); border-color: var(--fg); }
.view-tab[aria-selected="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #000;
}

.view-tab-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.3rem;
  height: 1.1rem;
  padding: 0 0.4rem;
  background: var(--accent);
  color: #000;
  border-radius: 999px;
  font-size: 0.65rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}
.view-tab[aria-selected="true"] .view-tab-badge {
  background: #000;
  color: var(--accent);
}

/* ─────────────────── views (swipeable on mobile, tab-swap on desktop) ─────────────────── */

.views {
  flex: 1;
  /* The min/max pair pins .views to exactly the space body's column flex
     allocates (= remaining after header/tabs/footer). Without max-height,
     mobile browsers happily promote .views' cross-axis to fit the tallest
     child's content — so a TASKS view with many cards drags the whole row
     taller than the viewport and the other panels' centred contents land
     way below the fold. */
  min-height: 0;
  max-height: 100dvh;
  display: flex;
  flex-direction: row;
  /* Gap between panels so a drag reveals a strip of background between them
     instead of one panel sliding straight under the next. */
  gap: 1rem;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.views::-webkit-scrollbar { display: none; }

.view {
  flex: 0 0 100%;
  width: 100%;
  /* Explicit height: 100% rather than relying on align-items: stretch from
     the row parent. Some mobile browsers, with a tall child like TASKS,
     promote the row's cross-axis size to fit the tallest content unless we
     pin every view to the parent's own definite height. Without this, the
     UPDATES view's centred .ambient ends up positioned far below the fold. */
  height: 100%;
  min-height: 100%;
  scroll-snap-align: start;
  /* `always` forces the gesture to settle on the very next snap point even
     when momentum would have carried it past — otherwise a fast swipe from
     TASKS lands directly on SPEND and the user never sees USAGE. */
  scroll-snap-stop: always;
  overflow-y: auto;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.view::-webkit-scrollbar { display: none; }

/* On mobile pin the body to the viewport height (not just min-height) so the
   flex column can't grow taller than what's actually visible. Without this,
   browsers that resolve `100dvh` as `100vh` (no dvh support, or while the
   address bar is visible) let body extend past the viewport. Combined with
   `.view { height: 100% }`, the panel bottom then sits below the visible
   area and the inner overflow-y:auto can scroll content into that hidden
   strip — so the last items never appear "at the bottom of the screen".
   Desktop (>=760px, below) overrides this back to body-scrolls behavior. */
@media (max-width: 759.98px) {
  body { height: 100dvh; }
  .view { -webkit-overflow-scrolling: touch; }
}

.view-empty {
  border: 2px dashed var(--line);
  border-radius: 18px;
  padding: 2rem 1.2rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
}
.view-empty-title {
  font-size: 0.95rem;
  font-weight: 650;
  letter-spacing: 0.22em;
  color: var(--muted);
}
.view-empty-sub {
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.5;
  max-width: 360px;
}

/* ─────────────────── neon palette ─────────────────── */

.palette-block {
  border: 2px solid var(--fg);
  border-radius: 18px;
  background: var(--bg);
  padding: 1.1rem 1.2rem 1.2rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.palette-hint {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.5;
}

.palette {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 0.55rem;
}

.swatch {
  display: inline-flex;
  align-items: center;
  padding: 0.55rem 0.95rem;
  border-radius: 999px;
  border: 0;
  color: #000;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-feature-settings: normal;
  font-size: 0.86rem;
  font-weight: 650;
  letter-spacing: 0.02em;
  line-height: 1;
  cursor: pointer;
  text-transform: uppercase;
  /* Each swatch carries its own glow via inline --swatch-color so the
     box-shadow is colored to match — that's the "svítící" feel. */
  background: var(--swatch-color, #fff);
  box-shadow:
    0 0 0 2px rgba(0, 0, 0, 0.35) inset,
    0 0 18px 0 color-mix(in srgb, var(--swatch-color, #fff) 60%, transparent);
  transition: transform 0.12s ease, box-shadow 0.18s ease;
}
.swatch:hover {
  transform: translateY(-1px);
  box-shadow:
    0 0 0 2px rgba(0, 0, 0, 0.35) inset,
    0 0 26px 0 color-mix(in srgb, var(--swatch-color, #fff) 80%, transparent);
}
.swatch:active { transform: translateY(0); }
.swatch.is-current {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
}

.palette-reset {
  align-self: flex-start;
  padding: 0.55rem 0.95rem;
  border: 2px solid var(--line);
  border-radius: 999px;
  background: var(--bg);
  color: var(--muted);
  font: inherit;
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  cursor: pointer;
  line-height: 1;
}
.palette-reset:hover { border-color: var(--fg); color: var(--fg); }

.emoji-set {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

/* Circular chip — one emoji per task, like a profile picture. Size matches
   roughly what we'll put on each task card so this preview is faithful. */
.emoji-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  background: var(--dim);
  border: 1.5px solid var(--line);
  font-size: 1.45rem;
  line-height: 1;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", system-ui;
}

/* ─────────────────── hero (TODAY) ─────────────────── */

.hero {
  background: var(--dim);
  color: var(--fg);
  border: 1px solid var(--line);
  padding: 1.3rem 1.4rem 1.4rem;
  border-radius: 18px;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.hero-label {
  font-size: 0.82rem;
  font-weight: 650;
  letter-spacing: 0.32em;
  color: var(--muted);
}

.hero-value {
  font-size: clamp(2.2rem, 9vw, 3.6rem);
  font-weight: 650;
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  word-break: break-all;
}

.hero-sub {
  margin-top: 0.4rem;
  font-size: 0.92rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.hero.is-empty { background: var(--dim); color: var(--fg); }
.hero.is-empty .hero-value { font-size: clamp(2rem, 8vw, 3.2rem); }

/* ─────────────────── stat blocks ─────────────────── */

.stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
}

.stat {
  background: var(--bg);
  border: 2px solid var(--fg);
  border-radius: 18px;
  padding: 1.1rem 1.2rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.stat--wide { width: 100%; }

.stat-label {
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.28em;
  color: var(--muted);
}

.stat-value {
  font-size: clamp(1.9rem, 9vw, 3.4rem);
  font-weight: 650;
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  word-break: break-all;
}

/* ─────────────────── limits (progress bars) ─────────────────── */

.limits {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.limit {
  position: relative;
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--dim);
  padding: 1rem 1.15rem 1.05rem;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}
.limit + .limit { margin-top: 0.5rem; }
.limit.is-warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); }
.limit.is-bad  { border-color: color-mix(in srgb, var(--bad) 55%, var(--line)); }

/* Dedicated "current usage" graph card at the top of the USAGE tab. The
   per-limit cards below no longer carry a background chart — the curve lives
   here, full-size, alongside the consumption-rate readouts. */
.usage-graph {
  margin-bottom: 0.75rem;
}
.ug-card {
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--dim);
  padding: 1.1rem 1.1rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}
.usage-graph[data-tone="warn"] .ug-card { border-color: var(--warn); }
.usage-graph[data-tone="bad"]  .ug-card { border-color: var(--bad); }

.ug-head {
  display: flex;
  align-items: baseline;
  gap: 0.9rem;
}
.ug-pct {
  font-size: clamp(1.8rem, 8vw, 2.6rem);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  line-height: 0.95;
  color: var(--claude);
}
.ug-head-meta {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.ug-name {
  font-size: 1.05rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--fg);
}
.ug-reset {
  color: var(--muted);
  font-size: 0.92rem;
  font-weight: 500;
}

.ug-graph-wrap {
  height: 180px;
  border-radius: 12px;
  overflow: hidden;
  background: var(--dim);
  border: 1.5px solid var(--line);
  position: relative;      /* anchor the hover overlay */
  cursor: crosshair;
}
.ug-graph {
  width: 100%;
  height: 100%;
  display: block;
}
.ug-graph-fill {
  fill: var(--claude);
  fill-opacity: 0.16;
}
.ug-graph-line {
  fill: none;
  stroke: var(--claude);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}
.usage-graph[data-tone="warn"] .ug-graph-fill { fill: var(--warn); }
.usage-graph[data-tone="warn"] .ug-graph-line { stroke: var(--warn); }
.usage-graph[data-tone="bad"]  .ug-graph-fill { fill: var(--bad); }
.usage-graph[data-tone="bad"]  .ug-graph-line { stroke: var(--bad); }

/* Hover readout: a vertical guide, a dot pinned to the curve, and a small tip
   showing the time + usage % at the cursor. These are HTML siblings of the SVG
   (not inside it) so the per-poll innerHTML swap of #ug-graph leaves them
   intact, and so the dot stays a true circle despite the SVG's stretched
   (preserveAspectRatio="none") coordinate space. */
.ug-hover {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.ug-hover[hidden] { display: none; }
.ug-hover-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  transform: translateX(-50%);
  background: var(--claude);
  opacity: 0.5;
}
.ug-hover-dot {
  position: absolute;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--claude);
  border: 2px solid var(--bg);
  box-shadow: 0 0 0 1px var(--claude);
}
.ug-hover-tip {
  position: absolute;
  top: 8px;
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  padding: 0.3rem 0.45rem;
  border-radius: 8px;
  background: var(--bg);
  border: 1px solid var(--line);
  white-space: nowrap;
  line-height: 1.15;
}
.ug-hover-pct {
  font-size: 0.95rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--claude);
}
.ug-hover-time {
  font-size: 0.72rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.usage-graph[data-tone="warn"] .ug-hover-line,
.usage-graph[data-tone="warn"] .ug-hover-dot { background: var(--warn); }
.usage-graph[data-tone="warn"] .ug-hover-dot { box-shadow: 0 0 0 1px var(--warn); }
.usage-graph[data-tone="warn"] .ug-hover-pct { color: var(--warn); }
.usage-graph[data-tone="bad"] .ug-hover-line,
.usage-graph[data-tone="bad"] .ug-hover-dot { background: var(--bad); }
.usage-graph[data-tone="bad"] .ug-hover-dot { box-shadow: 0 0 0 1px var(--bad); }
.usage-graph[data-tone="bad"] .ug-hover-pct { color: var(--bad); }

.ug-rates {
  display: flex;
  gap: 0.6rem;
}
.ug-rate {
  flex: 1;
  border: 1.5px solid var(--line);
  border-radius: 12px;
  padding: 0.6rem 0.7rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.ug-rate-label {
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.ug-rate-value {
  font-size: clamp(1.2rem, 5vw, 1.6rem);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  line-height: 1;
}

.ug-deriv {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}
.ug-deriv-wrap {
  height: 80px;
  border-radius: 12px;
  overflow: hidden;
  background: var(--dim);
  border: 1.5px solid var(--line);
}
.ug-deriv-graph {
  width: 100%;
  height: 100%;
  display: block;
}
.ug-deriv-fill {
  fill: var(--claude);
  fill-opacity: 0.12;
}
.ug-deriv-line {
  fill: none;
  stroke: var(--claude);
  stroke-width: 1.6;
  stroke-opacity: 0.85;
  vector-effect: non-scaling-stroke;
}

.ug-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin: 0.1rem 0 0.7rem;
  flex-wrap: wrap;
}
.ug-pager {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.ug-nav-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.7rem;
  height: 1.7rem;
  padding: 0;
  border-radius: 9px;
  border: 2px solid var(--line);
  background: var(--bg);
  color: var(--fg);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.15s ease, opacity 0.15s ease;
}
.ug-nav-btn:hover:not(:disabled) { border-color: var(--accent); }
.ug-nav-btn:disabled { opacity: 0.3; cursor: default; }
.ug-range {
  min-width: 8.5rem;
  text-align: center;
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.ug-windows {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border-radius: 10px;
  border: 2px solid var(--line);
}
.ug-win-btn {
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 0.25rem 0.6rem;
  border-radius: 7px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.ug-win-btn:hover { color: var(--fg); }
.ug-win-btn.is-active {
  background: var(--accent);
  color: var(--bg);
}

.limit > .limit-top,
.limit > .limit-bar-wrap,
.limit > .limit-foot {
  position: relative;
  z-index: 1;
}

.limit-top {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  flex-wrap: wrap;
}

.limit-name {
  font-size: 1.05rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--fg);
}

.limit-sub {
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--muted);
}

.limit-percent {
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
}

.limit-bar-wrap {
  position: relative;
  cursor: pointer;
}

.limit-bar {
  height: 12px;
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  border-radius: 999px;
  overflow: hidden;
}
.limit-fill {
  height: 100%;
  background: var(--claude);
  border-radius: 999px;
  /* Longer, eased cubic for a smoother grow rather than the snappier linear
     0.5 s tween. Pairs with the numeric tween on the % readout so the bar
     visually catches up with the number. */
  transition: width 820ms cubic-bezier(.2,.7,.25,1), background-color 240ms ease;
}
.limit-fill--warn { background: var(--warn); }
.limit-fill--bad  { background: var(--bad); }

.limit-particles {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
}

.particle {
  position: absolute;
  top: 50%;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--claude);
  box-shadow: 0 0 8px var(--claude);
  pointer-events: none;
  /* `translate` (not transform) keeps the centering separate from the
     keyframe `transform`, so the arc animation isn't fighting -50%/-50%. */
  translate: -50% -50%;
  animation: particle-arc 900ms cubic-bezier(0.22, 0.7, 0.35, 1) forwards;
  will-change: transform, opacity;
}
.particle--warn {
  background: var(--warn);
  box-shadow: 0 0 8px var(--warn);
}
.particle--bad {
  background: var(--bad);
  box-shadow: 0 0 10px var(--bad);
}

/* Three-stage arc: launch out and up, peak at ~35% of duration, then gravity
   pulls it past the start line and slightly further right. */
@keyframes particle-arc {
  0% {
    transform: translate(0, 0) scale(1.15);
    opacity: 1;
  }
  35% {
    transform: translate(calc(var(--dx, 80px) * 0.35), var(--apex, -28px)) scale(1);
    animation-timing-function: cubic-bezier(0.4, 0, 0.85, 0.5);
  }
  85% {
    opacity: 0.85;
  }
  100% {
    transform: translate(var(--dx, 80px), var(--fall, 36px)) scale(0.3);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .particle { display: none; }
  .limit-bar-wrap { cursor: default; }
}

.limit-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.8rem;
  color: var(--muted);
  font-size: 0.92rem;
  font-weight: 500;
}
.limit-usage { color: var(--muted); }
.limit-reset { color: var(--muted); }

/* Expandable usage-vs-time chart that appears when a limit card is clicked.
   Y axis = elapsed time in the limit's window (0 → 5h / 7d), X axis =
   usedPercent at that moment. The dashed diagonal is the "ideal pace" — on
   it means usage tracks linearly to the reset point. */
#limits-list .limit { cursor: pointer; }
.limit-chart {
  position: relative;
  z-index: 1;
  margin-top: 0.6rem;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 280ms ease, opacity 200ms ease, margin-top 200ms ease;
}
.limit.is-expanded .limit-chart {
  max-height: 300px;
  opacity: 1;
}
.limit-chart-plot {
  position: relative;
  /* Padding hosts the axis labels around the SVG plot. Bumping these in
     CSS won't distort the chart because the SVG only ever fills the
     remaining inner box, not the label gutters. */
  padding: 4px 6px 18px 24px;
}
.limit-chart-svg {
  width: 100%;
  height: 200px;
  display: block;
  overflow: visible;
}
.lc-grid {
  stroke: color-mix(in srgb, var(--fg) 10%, transparent);
  stroke-width: 0.3;
  vector-effect: non-scaling-stroke;
}
.lc-ideal {
  stroke: color-mix(in srgb, var(--fg) 40%, transparent);
  stroke-width: 1;
  stroke-dasharray: 2 2;
  vector-effect: non-scaling-stroke;
}
.lc-line {
  fill: none;
  stroke: var(--claude);
  stroke-width: 1.6;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.lc-line--warn { stroke: var(--warn); }
.lc-line--bad  { stroke: var(--bad); }
/* The dot lives as plain HTML over the plot (not inside the SVG) so it stays
   perfectly round: the SVG uses preserveAspectRatio="none", which would
   stretch an in-SVG <circle> into an ellipse along with the canvas. */
.lc-dot {
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: var(--claude);
  pointer-events: none;
}
.lc-dot--warn { background: var(--warn); }
.lc-dot--bad  { background: var(--bad); }
/* Axis labels live in the plot's CSS padding as plain HTML, so they keep a
   fixed pixel font size regardless of how the SVG canvas stretches. */
.lc-axis {
  position: absolute;
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  pointer-events: none;
}
.lc-axis--y { left: 0; width: 20px; text-align: right; }
.lc-axis--y-top { top: 0; }
.lc-axis--y-bot { bottom: 18px; }
.lc-axis--x { bottom: 2px; }
.lc-axis--x-left  { left: 24px; }
.lc-axis--x-right { right: 6px; }
.limit-chart-legend {
  display: flex;
  gap: 1rem;
  margin-top: 0.4rem;
  font-size: 0.78rem;
  color: var(--muted);
}
.lc-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}
.lc-nav-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border-radius: 8px;
  border: 2px solid var(--line);
  background: var(--bg);
  color: var(--fg);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.15s ease, opacity 0.15s ease;
}
.lc-nav-btn:hover:not(:disabled) { border-color: var(--accent); }
.lc-nav-btn:disabled { opacity: 0.3; cursor: default; }
.lc-range {
  min-width: 9rem;
  text-align: center;
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.lc-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; }
.lc-swatch {
  display: inline-block;
  width: 14px;
  height: 2px;
  border-radius: 2px;
  background: var(--claude);
}
.lc-swatch--ideal {
  background: transparent;
  border-top: 2px dashed color-mix(in srgb, var(--fg) 40%, transparent);
  height: 0;
}
.lc-swatch--warn { background: var(--warn); }
.lc-swatch--bad  { background: var(--bad); }

/* ─────────────────── claude code tasks ─────────────────── */

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

.task {
  position: relative;
  border: 2px solid var(--fg);
  border-radius: 14px;
  background: var(--bg);
  padding: 0.6rem 0.8rem 0.65rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.task + .task { margin-top: 0.4rem; }
.task.task--clickable,
.task-card.task--clickable { cursor: pointer; }

.task-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.task-top-right {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}
.task-emoji-wrap {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  /* Own stacking context so .task-emoji's z-index only ranks above the
     sparks inside, not other positioned elements elsewhere on the card. */
  isolation: isolate;
}
.task-emoji {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
  background: var(--bg);
  border: 1.5px solid var(--line);
  font-size: 1rem;
  line-height: 1;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", system-ui;
  flex-shrink: 0;
}
/* needs_action, or unread + not working → emoji sparkles to grab attention. Same accent
   glow + radial pulse as the boot-head and the limit-bar particles, so the
   "something to look at" cue is visually consistent across the app. The
   .task-spark embers are spawned in JS at ~6–8Hz per emoji. */
.task.is-sparkling .task-emoji {
  border-color: var(--accent);
  animation: emoji-sparkle 3.2s ease-in-out infinite;
}
@keyframes emoji-sparkle {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(0, 0, 0, 0), 0 0 0 0 rgba(0, 0, 0, 0);
    transform: scale(1);
  }
  50% {
    box-shadow: 0 0 4px 0 var(--accent), 0 0 10px 0 color-mix(in srgb, var(--accent) 60%, transparent);
    transform: scale(1.06);
  }
}
.task-spark {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 4px var(--accent), 0 0 9px color-mix(in srgb, var(--accent) 55%, transparent);
  pointer-events: none;
  translate: -50% -50%;
  animation: task-spark 820ms cubic-bezier(0.22, 0.7, 0.35, 1) forwards;
  will-change: transform, opacity;
}
@keyframes task-spark {
  0%   { transform: translate(0, 0) scale(1.15); opacity: 1; }
  35%  {
    transform:
      translate(calc(var(--dx, 40px) * 0.4), calc(var(--dy, 60px) * 0.4 + var(--arc, -16px)))
      scale(1);
    animation-timing-function: cubic-bezier(0.4, 0, 0.85, 0.5);
  }
  85%  { opacity: 0.7; }
  100% { transform: translate(var(--dx, 40px), var(--dy, 60px)) scale(0.2); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .task.is-sparkling .task-emoji { animation: none; }
  .task-card.is-sparkling .focus-att-avatar { animation: none; }
  .task-spark { display: none; }
}

/* ─────────────────── task cards: compact + expandable ───────────────────
 * UPDATES attention cards (focus-att-card) with a collapsible details panel.
 * Collapsed by default; click anywhere on the summary to expand. */
.task-card {
  flex-direction: column;
  gap: 0;
  padding: 0;
  overflow: hidden;
  cursor: pointer;
}
/* Idle/muted tasks dim so the live + needs-you work stands out in the list. */
.task-card.is-idle { opacity: 0.78; }
.task-card.is-muted { opacity: 0.5; }

.task-card-summary {
  display: flex;
  align-items: stretch;
  gap: 0.65rem;
  padding: 0.55rem 0.7rem;
}
/* Compact the avatar / type the same way the UPDATES left column does so the
   row stays glanceable. */
.task-card .focus-att-avatar { width: 2.2rem; height: 2.2rem; border-radius: 0.6rem; }
.task-card .focus-att-emoji  { font-size: 1.15rem; }
.task-card .focus-att-title  { font-size: 0.88rem; }
.task-card .focus-att-wait   { min-width: 3rem; }
.task-card .focus-att-wait-num { font-size: 1.05rem; }
/* The sparkle (needs-you / unread) glow rides on the avatar chip — the bordered
   element here — rather than the bare emoji glyph. Embers still spawn from the
   .task-emoji-wrap host (the avatar carries both classes). */
.task-card.is-sparkling .focus-att-avatar {
  border-color: var(--accent);
  animation: emoji-sparkle 3.2s ease-in-out infinite;
}

/* Caret flips to point down when the card is open. */
.task-card-caret {
  flex: 0 0 auto;
  align-self: center;
  color: var(--muted);
  font-size: 1.3rem;
  line-height: 1;
  transition: transform 200ms ease;
}
.task-card.is-expanded .task-card-caret { transform: rotate(90deg); }

/* Collapsible details. The grid 0fr↔1fr trick animates height without a fixed
   max; the clip layer owns the overflow so the inner divider/padding are
   clipped to nothing when collapsed (no stray 1px line). */
.task-card-details {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 240ms cubic-bezier(.2, .7, .25, 1);
}
.task-card.is-expanded .task-card-details { grid-template-rows: 1fr; }
.task-card-details-clip { overflow: hidden; min-height: 0; }
.task-card-details-inner {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  padding: 0.55rem 0.7rem 0.65rem;
  border-top: 1px solid color-mix(in srgb, var(--proj) 24%, var(--line));
}
.task-card-details-inner .task-foot { margin-top: 0; }
/* Full repo · branch path, shown only in the expanded detail (the head already
   carries the short project name). */
.task-card-repo {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  word-break: break-all;
  text-decoration: none;
}
a.task-card-repo:hover { color: var(--fg); }
/* OPEN ↗ reuses the .task-debug pill shape but is a link. */
a.task-open { text-decoration: none; }

@media (prefers-reduced-motion: reduce) {
  .task-card-details { transition: none; }
  .task-card-caret { transition: none; }
}

/* Repo is the main badge — bright accent fill so it pops the same way the
   hero / limit-fill yellow does on the cards above. */
.task-repo {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  background: var(--accent);
  color: #000;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-feature-settings: normal;
  font-size: 0.82rem;
  font-weight: 650;
  letter-spacing: 0.01em;
  line-height: 1;
  padding: 0.32rem 0.65rem;
  border-radius: 999px;
  word-break: break-word;
}
.task-repo--missing {
  background: var(--dim);
  color: var(--muted);
  border: 1.5px dashed var(--line);
}
a.task-repo { transition: filter 0.12s ease; }
a.task-repo:hover { filter: brightness(0.88); }
.task.is-unread .task-repo::after {
  content: "";
  display: inline-block;
  margin-left: 0.5rem;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: #000;
}

.task-top-left {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  min-width: 0;
}
.task-pr {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1;
  padding: 0.26rem 0.5rem;
  border-radius: 999px;
  border: 1.5px solid var(--line);
  color: var(--muted);
  text-transform: uppercase;
  white-space: nowrap;
}
a.task-pr { transition: filter 0.12s ease; }
a.task-pr:hover { filter: brightness(1.2); }
.task-pr--merged { color: #a371f7; border-color: #a371f7; }
.task-pr--open   { color: var(--ok); border-color: var(--ok); }
.task-pr--draft  { color: var(--muted); border-color: var(--line); }
.task-pr--closed { color: var(--bad); border-color: var(--bad); }
.task-pr--needs-merge { color: var(--warn, #d29922); border-color: var(--warn, #d29922); }
.task-pr--not-merged { color: var(--warn, #d29922); border-color: var(--warn, #d29922); }
.task-pr--no-pr { color: var(--muted); border-color: var(--line); }
.task-pr--on-main  { color: #58a6ff; border-color: #58a6ff; }

/* Diff stats badge: +additions (green) / −deletions (red), like claude.ai. */
.task-diff {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.68rem;
  font-weight: 600;
  line-height: 1;
  padding: 0.26rem 0.5rem;
  border-radius: 999px;
  border: 1.5px solid var(--line);
  white-space: nowrap;
}
.task-diff-add { color: var(--ok); }
.task-diff-del { color: var(--bad); }

/* Compact +/− in the UPDATES attention card. */
.focus-att-diff {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
.pr-checks { display: inline-flex; gap: 0.25rem; }
.pr-diff { display: inline-flex; gap: 0.3rem; }
.pr-check--pass    { color: var(--ok); }
.pr-check--fail    { color: var(--bad); }
.pr-check--pending { color: var(--muted); }

.task-state {
  flex-shrink: 0;
  font-size: 0.6rem;
  font-weight: 650;
  letter-spacing: 0.2em;
  padding: 0.26rem 0.55rem;
  border-radius: 999px;
  border: 1.5px solid var(--line);
  color: var(--muted);
  text-transform: uppercase;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}
.task-state::before {
  content: "";
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: currentColor;
}
.task-state--ok     { color: var(--ok);     border-color: var(--ok);     }
.task-state--ok::before { box-shadow: 0 0 8px var(--ok); animation: task-pulse 2.8s ease-in-out infinite; }
.task-state--accent { color: var(--accent); border-color: var(--accent); }
.task-state--accent::before { box-shadow: 0 0 8px var(--accent); }
.task-state--bad    { color: var(--bad);    border-color: var(--bad);    }
.task-state--bad::before { box-shadow: 0 0 8px var(--bad); }
.task-state--idle   { color: var(--muted); }

@keyframes task-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.4; transform: scale(0.7); }
}
@media (prefers-reduced-motion: reduce) {
  .task-state--ok::before { animation: none; }
}

.task-title {
  font-size: 0.95rem;
  font-weight: 650;
  letter-spacing: 0.02em;
  line-height: 1.2;
  word-break: break-word;
}
a.task-title { display: block; transition: color 0.12s ease; }
a.task-title:hover { color: var(--accent); }

.task-detail {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--fg);
  opacity: 0.9;
  line-height: 1.35;
}

/* AskUserQuestion: the prompt the task is blocked on, plus the answer options
   as chips. Replaces the noise statusDetail ("Waiting on permission: …"). */
.task-question {
  margin-top: 0.15rem;
  padding: 0.4rem 0.55rem;
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
.task-q-prompt {
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--fg);
}
.task-q-opts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.35rem;
}
.task-q-opt {
  font-size: 0.7rem;
  font-weight: 550;
  line-height: 1.2;
  padding: 0.16rem 0.45rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: color-mix(in srgb, var(--accent) 85%, var(--fg));
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
}

.task-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 0.1rem;
}

.task-meta {
  flex: 1;
  min-width: 0;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--muted);
  text-transform: uppercase;
  word-break: break-word;
}
.task-meta .dot-sep { opacity: 0.5; padding: 0 0.1rem; }

.task-foot-actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.task-mute {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  background: transparent;
  color: var(--muted);
  border: 1.5px solid var(--line);
  border-radius: 999px;
  padding: 0;
  cursor: pointer;
  line-height: 0;
}
.task-mute:hover {
  border-color: var(--fg);
  color: var(--fg);
}
.task-mute.is-muted {
  background: var(--accent);
  color: #000;
  border-color: var(--accent);
}

.task-debug {
  flex-shrink: 0;
  background: transparent;
  color: var(--muted);
  border: 1.5px solid var(--line);
  border-radius: 999px;
  padding: 0.25rem 0.7rem;
  font: inherit;
  font-size: 0.62rem;
  font-weight: 650;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  cursor: pointer;
  line-height: 1;
}
.task-debug:hover {
  border-color: var(--fg);
  color: var(--fg);
}
.task-debug[aria-expanded="true"] {
  background: var(--accent);
  color: #000;
  border-color: var(--accent);
}


/* ─────────────────── updates: focus stage (immersive) ─────────────────── */
/*
 * UPDATES is the always-on ambient dashboard — designed to live full screen
 * on a phone propped on the desk. When the user lands on this tab the body
 * gains `.is-immersive` and the regular header/tabs/footer all hide; the
 * focus stage takes over the entire viewport.
 *
 * Layout (top → bottom):
 *
 *   topbar   — connection status + clock on the left, compact stats chips
 *              in the middle (TODAY / MONTH / RATE / TOKENS / SOURCES), and
 *              AWAKE / EXIT controls on the right. The stats chips are the
 *              "small current usage" cue: they stay visible all the time so
 *              one glance at the dashboard already tells you what today's
 *              spend looks like, even when the centre card is dwelling on a
 *              different metric.
 *
 *   main     — the big focus rotation card. When a task is sparkling, just
 *              started working, or today's spend bumped, this slot rotates
 *              through those events. Otherwise it falls back to a big
 *              ambient TODAY-spend hero so the centre of the screen is
 *              never empty.
 *
 *   aside    — three always-visible info sections stacked beneath: LIMITS
 *              (session / weekly progress bars), RECENT TASKS (top few task
 *              cards), and SOURCES (mini dot row showing provider health).
 */

.focus {
  position: relative;
  flex: 1 1 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 0.6rem 0.4rem;
  min-height: 22rem;
}

/* ─── body: two columns (active tasks | usage + projects + sources) ─── */

.focus-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 100%;
  min-width: 0;
  padding: 0.4rem 0.5rem 0.6rem;
}
/* Desktop / wide: active tasks on the left, the usage + projects + sources
   stack on the right. Columns align to the top and scroll with the panel. */
@media (min-width: 860px) {
  .focus-body {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    align-items: start;
    gap: 1.4rem;
    padding: 0.5rem 0.8rem 0.8rem;
  }
}

.focus-col {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-width: 0;
}
.focus-col--side { gap: 1.1rem; }

/* ─── right column blocks: usage + projects + sources, stacked ─── */

.focus-aside-block {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  min-width: 0;
}

.focus-aside-label {
  font-size: 0.62rem;
  font-weight: 650;
  letter-spacing: 0.28em;
  color: var(--muted);
  padding: 0 0.2rem;
}

.focus-aside-empty {
  border: 1.5px dashed var(--line);
  border-radius: 14px;
  padding: 0.7rem 0.9rem;
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.22em;
  text-align: center;
  text-transform: uppercase;
}

.focus-quiet {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.focus-quiet-tasks {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  width: 100%;
}

/* ─── PROJECTS batches (repo identity + active task rows) ─── */
.focus-proj {
  --proj: var(--accent);
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.5rem 0.55rem;
  border-radius: 0.85rem;
  border: 1.5px solid color-mix(in srgb, var(--proj) 32%, var(--line));
  background:
    radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--proj) 14%, transparent), transparent 60%),
    var(--dim);
  min-width: 0;
  transition:
    border-color 360ms cubic-bezier(.2,.7,.25,1),
    box-shadow 360ms cubic-bezier(.2,.7,.25,1),
    transform 360ms cubic-bezier(.22,1,.36,1);
}
.focus-proj.is-needs {
  border-color: color-mix(in srgb, var(--proj) 70%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--proj) 45%, transparent),
              0 0 18px color-mix(in srgb, var(--proj) 28%, transparent);
}

.focus-proj-id {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  text-decoration: none;
  color: var(--fg);
  min-width: 0;
}
a.focus-proj-id:hover .focus-proj-name { text-decoration: underline; }
.focus-proj-swatch {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 0.25rem;
  background: var(--proj);
  box-shadow: 0 0 8px color-mix(in srgb, var(--proj) 60%, transparent);
  flex: 0 0 auto;
}
.focus-proj-name {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}
.focus-proj-count {
  flex: 0 0 auto;
  min-width: 1.1rem;
  height: 1.1rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--proj) 22%, transparent);
  color: color-mix(in srgb, var(--proj) 80%, white);
  font-size: 0.62rem;
  font-weight: 650;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Compact task rows: state-tinted avatar + single-line title. */
.focus-proj-tasks {
  display: flex;
  flex-direction: column;
  gap: 0.22rem;
  min-width: 0;
}
.focus-proj-task {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.18rem 0.3rem;
  border-radius: 0.5rem;
  text-decoration: none;
  color: var(--fg);
  min-width: 0;
  transition: background 0.15s ease;
}
.focus-proj-task:hover { background: color-mix(in srgb, var(--proj) 12%, transparent); }
.focus-proj-title {
  font-size: 0.72rem;
  font-weight: 500;
  line-height: 1.25;
  color: color-mix(in srgb, var(--fg) 88%, transparent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}
.focus-proj-task.is-needs .focus-proj-title { color: var(--fg); font-weight: 550; }
.focus-proj-avatar {
  position: relative;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  background: color-mix(in srgb, var(--proj) 16%, var(--bg));
  border: 1.5px solid color-mix(in srgb, var(--proj) 40%, var(--line));
  text-decoration: none;
  transition: transform 0.12s ease, border-color 0.2s ease;
}
.focus-proj-task:hover .focus-proj-avatar { transform: scale(1.1); }
.focus-proj-emoji { font-size: 0.8rem; line-height: 1; }
/* Active but not working (idle/paused within the window): neutral, no pulse. */
.focus-proj-avatar.is-idle {
  border-color: var(--line);
  background: color-mix(in srgb, var(--proj) 8%, var(--bg));
}
.focus-proj-task.is-idle .focus-proj-title { color: var(--muted); font-weight: 500; }
/* Working = a quiet pulsing ring; review = solid accent ring; needs = bright
   ring + a "?" flag badge so an item awaiting you reads instantly. */
.focus-proj-avatar.is-working {
  animation: focus-proj-pulse 3.6s ease-in-out infinite;
}
.focus-proj-avatar.is-review {
  border-color: color-mix(in srgb, var(--proj) 85%, white);
}
.focus-proj-avatar.is-needs {
  border-color: var(--accent);
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 55%, transparent);
}
/* Stuck = working but silent too long. Amber, urgent pulse, "!" flag. */
.focus-proj-avatar.is-stuck {
  border-color: var(--warn);
  background: color-mix(in srgb, var(--warn) 18%, var(--bg));
  animation: focus-stuck-pulse 2.4s ease-in-out infinite;
}
.focus-proj-task.is-stuck .focus-proj-title { color: var(--warn); font-weight: 550; }
.focus-proj-flag.is-stuck {
  background: var(--warn);
  box-shadow: 0 0 8px color-mix(in srgb, var(--warn) 70%, transparent);
}
/* Failed = the turn crashed. Red flag + red title so it can't be mistaken for
   an idle/working row. */
.focus-proj-task.is-failed .focus-proj-title { color: var(--bad); font-weight: 550; }
.focus-proj-avatar.is-failed {
  border-color: var(--bad);
  background: color-mix(in srgb, var(--bad) 18%, var(--bg));
}
.focus-proj-flag.is-failed {
  background: var(--bad);
  box-shadow: 0 0 8px color-mix(in srgb, var(--bad) 70%, transparent);
}
@keyframes focus-stuck-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn) 45%, transparent); }
  50%      { box-shadow: 0 0 0 4px color-mix(in srgb, var(--warn) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .focus-proj-avatar.is-stuck { animation: none; }
}
.focus-proj-flag {
  position: absolute;
  top: -0.2rem;
  right: -0.2rem;
  width: 0.72rem;
  height: 0.72rem;
  border-radius: 50%;
  background: var(--accent);
  color: #000;
  font-size: 0.52rem;
  font-weight: 650;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 60%, transparent);
}
.focus-proj-more {
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding: 0.1rem 0.3rem 0;
}

/* ─── NEEDS YOU rail (pinned attention, never rotates) ─── */
.focus-attention {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: 100%;
  flex-shrink: 0;
}
.focus-attention[hidden] { display: none; }
/* The ACTIVE column is a vertical list of compact cards — tighter padding +
   type than the old wide rail so more tasks stay glanceable at once. */
/* When focus-att-card is also a task-card the outer border-box has no padding
   — .task-card-summary handles spacing instead. Placed here (after focus-att-card's
   own padding rule) so specificity ties resolve in our favour by source order. */
.focus-att-card.task-card { padding: 0; gap: 0; }
/* Compact sizing for the ACTIVE column — target the summary wrapper now that
   the outer card no longer carries padding or gap. */
.focus-col--tasks .task-card-summary { padding: 0.55rem 0.65rem; gap: 0.6rem; }
.focus-col--tasks .focus-att-avatar { width: 2.2rem; height: 2.2rem; border-radius: 0.6rem; }
.focus-col--tasks .focus-att-emoji { font-size: 1.15rem; }
.focus-col--tasks .focus-att-title { font-size: 0.84rem; }
.focus-col--tasks .focus-att-wait-num { font-size: 1.05rem; }
.focus-col--tasks .focus-att-wait { min-width: 3rem; }

.focus-att-card {
  --proj: var(--accent);
  display: flex;
  align-items: stretch;
  gap: 0.7rem;
  padding: 0.7rem 0.8rem;
  border-radius: 0.9rem;
  text-decoration: none;
  color: var(--fg);
  border: 1.5px solid color-mix(in srgb, var(--proj) 52%, var(--line));
  background:
    radial-gradient(130% 130% at 0% 0%, color-mix(in srgb, var(--proj) 22%, transparent), transparent 62%),
    var(--dim);
  min-width: 0;
}
/* Entry animation is gated on .is-fresh so it only plays when a task first
   enters the rail — never when an existing card is re-rendered/patched, which
   is what made the rail flash on every render tick. */
.focus-att-card.is-fresh {
  animation: focus-att-in 460ms cubic-bezier(.2,.7,.25,1) both;
}
@keyframes focus-att-in {
  from { opacity: 0; transform: translateY(-8px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
/* needs_action is the loudest: proj ring + steady glow pulse. */
.focus-att-card.is-needs {
  border-color: color-mix(in srgb, var(--proj) 80%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--proj) 45%, transparent),
              0 0 18px color-mix(in srgb, var(--proj) 22%, transparent);
  animation: focus-att-glow 4.5s ease-in-out 0.5s infinite;
}
.focus-att-card.is-needs.is-fresh {
  animation: focus-att-in 460ms cubic-bezier(.2,.7,.25,1) both,
             focus-att-glow 4.5s ease-in-out 0.5s infinite;
}
@keyframes focus-att-glow {
  0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--proj) 32%, transparent),
                         0 0 14px color-mix(in srgb, var(--proj) 14%, transparent); }
  50%      { box-shadow: 0 0 0 1px color-mix(in srgb, var(--proj) 60%, transparent),
                         0 0 24px color-mix(in srgb, var(--proj) 28%, transparent); }
}

.focus-att-avatar {
  flex: 0 0 auto;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 0.7rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--proj) 18%, var(--bg));
  border: 1.5px solid color-mix(in srgb, var(--proj) 45%, var(--line));
  align-self: center;
}
.focus-att-emoji { font-size: 1.4rem; line-height: 1; }

.focus-att-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.18rem;
  justify-content: center;
}
.focus-att-head {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
}
.focus-att-proj-pill {
  flex: 0 0 auto;
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 0.12rem 0.45rem;
  border-radius: 999px;
  background: var(--proj);
  color: #000;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 9rem;
  box-shadow: 0 0 6px color-mix(in srgb, var(--proj) 50%, transparent);
}
.focus-att-pill {
  flex: 0 0 auto;
  font-size: 0.58rem;
  font-weight: 650;
  letter-spacing: 0.14em;
  padding: 0.12rem 0.4rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  color: var(--muted);
}
/* Headline merge-state chip — the single status shown in the collapsed card
   head next to the project name. Three variants only (NEED MERGE / UNREAD /
   DONE); the detailed PR/diff/work-state flags live in the expanded body. */
.focus-att-state {
  flex: 0 0 auto;
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  padding: 0.12rem 0.45rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  color: var(--muted);
  white-space: nowrap;
}
.focus-att-state.is-need-merge {
  background: var(--accent);
  color: #000;
  box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 45%, transparent);
}
.focus-att-state.is-unread {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: color-mix(in srgb, var(--accent) 88%, white);
}
.focus-att-state.is-done {
  background: color-mix(in srgb, var(--ok, #2ea043) 16%, transparent);
  color: color-mix(in srgb, var(--ok, #2ea043) 82%, white);
}
/* The PR/diff/work-state flags moved out of the head into the expanded body —
   a single wrapping row at the top of the detail panel. */
.focus-att-exp-flags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}
.focus-att-card.is-needs .focus-att-pill { background: var(--accent); color: #000; }
/* Failed = a crashed turn. Proj ring at full intensity (no glow pulse — it's
   stopped, not waiting). Pill + wait-num stay red to signal urgency. */
.focus-att-card.is-failed {
  border-color: color-mix(in srgb, var(--proj) 72%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--proj) 38%, transparent),
              0 0 14px color-mix(in srgb, var(--proj) 16%, transparent);
}
.focus-att-card.is-failed .focus-att-pill { background: var(--bad); color: #000; }
.focus-att-card.is-failed .focus-att-wait-num { color: var(--bad); }
/* Working = a live turn in flight. Proj color + gentle running pulse shows
   "actively making progress". Pill + wait-num stay green for state legibility. */
.focus-att-card.is-working {
  border-color: color-mix(in srgb, var(--proj) 62%, var(--line));
  animation: focus-att-run 3.6s ease-in-out infinite;
}
.focus-att-card.is-working.is-fresh {
  animation: focus-att-in 460ms cubic-bezier(.2,.7,.25,1) both,
             focus-att-run 3.6s ease-in-out infinite;
}
.focus-att-card.is-working .focus-att-pill { background: var(--ok); color: #000; }
.focus-att-card.is-working .focus-att-wait-num { color: var(--ok); }
@keyframes focus-att-run {
  0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--proj) 22%, transparent); }
  50%      { box-shadow: 0 0 0 1px color-mix(in srgb, var(--proj) 45%, transparent),
                         0 0 16px color-mix(in srgb, var(--proj) 18%, transparent); }
}
/* Idle-but-active (still within the 6h window). Dimmest tier — proj color
   barely visible, not shouting for attention. */
.focus-att-card.is-idle {
  border-color: color-mix(in srgb, var(--proj) 28%, var(--line));
  opacity: 0.78;
}
.focus-att-card.is-idle .focus-att-pill {
  background: color-mix(in srgb, var(--muted) 22%, transparent);
  color: var(--muted);
}
.focus-att-repo {
  font-size: 0.62rem;
  font-weight: 550;
  letter-spacing: 0.04em;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
/* PR / merge-status tags in the compact UPDATES card head. Reuse the .task-pr
   badge styling but shrink it to sit inline with the 0.58rem pill. */
.focus-att-tags {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.focus-att-tags .task-pr {
  font-size: 0.54rem;
  padding: 0.14rem 0.36rem;
  border-width: 1px;
  gap: 0.25rem;
}
.focus-att-title {
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.focus-att-detail {
  font-size: 0.74rem;
  font-weight: 450;
  line-height: 1.3;
  color: color-mix(in srgb, var(--fg) 78%, transparent);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.focus-att-detail[hidden] { display: none; }
/* AskUserQuestion answer options, one compact line under the prompt. */
.focus-att-options {
  font-size: 0.68rem;
  font-weight: 550;
  line-height: 1.3;
  letter-spacing: 0.01em;
  color: color-mix(in srgb, var(--accent) 80%, var(--fg));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 0.1rem;
}
.focus-att-options[hidden] { display: none; }

.focus-att-wait {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  text-align: right;
  padding-left: 0.4rem;
  border-left: 1.5px solid color-mix(in srgb, var(--proj) 20%, var(--line));
  min-width: 3.4rem;
}
.focus-att-wait-num {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
}
.focus-att-card.is-needs .focus-att-wait-num { color: var(--accent); }
.focus-att-wait-lbl {
  font-size: 0.52rem;
  font-weight: 650;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-top: 0.15rem;
}

/* Working state: animated dots replace the label; time shows duration in working state. */
.focus-att-working-dots {
  display: none;
  align-items: center;
  justify-content: flex-end;
  gap: 3px;
  margin-bottom: 0.2rem;
}
.focus-att-card.is-working .focus-att-working-dots { display: flex; }
.focus-att-card.is-working .focus-att-wait-lbl { display: none; }

.focus-att-working-dots span {
  display: block;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ok);
  opacity: 0.4;
  animation: working-dot 1.2s ease-in-out infinite;
}
.focus-att-working-dots span:nth-child(2) { animation-delay: 0.2s; }
.focus-att-working-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes working-dot {
  0%, 60%, 100% { transform: translateY(0) scale(1); opacity: 0.4; }
  30%           { transform: translateY(-4px) scale(1.25); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .focus-att-working-dots span { animation: none; opacity: 0.7; }
}

.focus-att-more {
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding: 0.1rem 0.3rem;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .focus-att-card,
  .focus-att-card.is-needs,
  .focus-att-card.is-working { animation: none; }
}
@keyframes focus-proj-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--proj) 0%, transparent); }
  50%      { box-shadow: 0 0 0 3px color-mix(in srgb, var(--proj) 22%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .focus-proj-avatar.is-working { animation: none; }
  .focus-proj { transition: none; }
}

.focus-limits {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
/* The base .limit rule adds margin-top to sibling .limit nodes; inside the
   focus aside the parent's `gap` already handles spacing, so drop the
   margin to avoid doubling. */
.focus-limits .limit + .limit { margin-top: 0; }
/* Compact the .limit cards a touch when they live in the bottom aside —
   tighter padding so three cards fit alongside tasks + sources without
   pushing the centre stage off the screen. */
.focus-limits .limit {
  padding: 0.75rem 0.9rem 0.85rem;
  transition:
    opacity 360ms cubic-bezier(.2,.7,.25,1),
    transform 360ms cubic-bezier(.2,.7,.25,1);
  opacity: 1;
  transform: translateY(0);
}
.focus-limits .limit-percent { font-size: clamp(1.3rem, 5vw, 1.8rem); }
.focus-limits .limit-name    { font-size: 0.78rem; }
/* The compact aside leans on the big percentage, so drop the trailing
   " used" word there and keep the reset note aligned to the number. */
.focus-limits .limit-used    { display: none; }
.focus-limits .limit-foot    { align-items: center; }
.focus-limits .limit.is-entering {
  opacity: 0;
  transform: translateY(10px);
}
.focus-limits .limit.is-leaving {
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .focus-limits .limit { transition: none; }
}


/* ─────────────────── sources ─────────────────── */

.sources {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.block-label {
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.28em;
  color: var(--muted);
  padding: 0.4rem 0.15rem 0.1rem;
}

.row {
  display: flex;
  align-items: stretch;
  border: 2px solid var(--fg);
  border-radius: 18px;
  background: var(--bg);
  overflow: hidden;
}
.row + .row { margin-top: 0.5rem; }

.row-tab {
  width: 10px;
  flex-shrink: 0;
  background: var(--c-fallback);
}

.row-main {
  flex: 1;
  min-width: 0;
  padding: 0.95rem 1.1rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.row-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.8rem;
  flex-wrap: wrap;
}

.row-name {
  font-size: 1.1rem;
  font-weight: 650;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  word-break: break-word;
}

.row-value {
  font-size: clamp(1.6rem, 7vw, 2.4rem);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1;
}

.row-cats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.45rem;
  margin-top: 0.05rem;
}
.row-cat {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  padding: 0.18rem 0.55rem 0.2rem;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--muted);
}
.row-cat-name { color: var(--muted); }
.row-cat-cost {
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}

.row-bot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 0.8rem;
  flex-wrap: wrap;
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 550;
  letter-spacing: 0.05em;
}

.row-status {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg);
}
.row-status::before {
  content: "";
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--muted);
}
.row-status--ok   { color: var(--ok); }
.row-status--ok::before   { background: var(--ok);   box-shadow: 0 0 10px var(--ok); }
.row-status--warn { color: var(--warn); }
.row-status--warn::before { background: var(--warn); box-shadow: 0 0 10px var(--warn); }
.row-status--bad  { color: var(--bad); }
.row-status--bad::before  { background: var(--bad);  box-shadow: 0 0 10px var(--bad); }
.row-status--idle { color: var(--muted); }
.row-status--idle::before { background: var(--muted); }

.row.is-stale { border-color: var(--warn); }
.row.is-error { border-color: var(--bad); }

.row-error {
  margin-top: 0.4rem;
  font-size: 0.78rem;
  font-weight: 550;
  color: var(--bad);
  word-break: break-word;
}

/* ─────────────────── empty / footer ─────────────────── */

.empty {
  border: 2px dashed var(--line);
  border-radius: 18px;
  padding: 1.5rem 1rem;
  text-align: center;
}
.cta {
  display: inline-block;
  padding: 0.85rem 1.2rem;
  border: 2px solid var(--accent);
  border-radius: 999px;
  background: var(--accent);
  color: #000;
  font-weight: 650;
  letter-spacing: 0.18em;
  font-size: 0.8rem;
}
.cta:hover { background: var(--bg); color: var(--accent); }

.foot {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.24em;
  color: var(--muted);
  padding-top: 0.25rem;
}
.sep { opacity: 0.55; }

/* ─────────────────── desktop / tablet ─────────────────── */

@media (min-width: 760px) {
  body { padding-left: 1.5rem; padding-right: 1.5rem; }
  .view-tabs { max-width: 880px; margin: 0 auto; width: 100%; }
  /* On desktop, abandon the swipe carousel — only the active view is shown
     so the layout reads like a regular tabbed dashboard. The base mobile
     rules pin .views/.view to the viewport so each panel scrolls internally;
     on desktop we want a single page scroll instead, otherwise tall content
     (long limits list, expanded usage chart) gets clipped at the viewport
     bottom because .view has no scrollbar of its own here. */
  .views {
    overflow: visible;
    scroll-snap-type: none;
    display: block;
    max-width: 880px;
    max-height: none;
    margin: 0 auto;
    width: 100%;
  }
  .views:has(.view--updates.is-active) {
    max-width: none;
  }
  .view {
    display: none;
    width: 100%;
    height: auto;
    min-height: 0;
    overflow-y: visible;
  }
  .view.is-active { display: flex; flex-direction: column; gap: 0.8rem; }
  .hero { padding: 1.8rem 2rem; }
  .hero-value { font-size: clamp(3rem, 7vw, 4.5rem); }
}

/* ─────────────────── settings page ─────────────────── */

.page-settings { padding-bottom: calc(2rem + var(--safe-bottom)); }

.settings-shell {
  max-width: 720px;
  margin: 0 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 1.2rem;
  padding: 0.5rem 0.25rem 1rem;
}

.settings-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  color: var(--muted);
}

.section-title {
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.3em;
  color: var(--muted);
  padding: 0.5rem 0.2rem 0.3rem;
}

.connection-wrap + .connection-wrap { margin-top: 0.5rem; }

.config-list { display: flex; flex-direction: column; gap: 0.4rem; }
.config-row {
  border: 2px solid var(--line);
  border-radius: 14px;
  background: var(--bg);
  padding: 0.7rem 0.9rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem;
}
.config-row-meta {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.config-row-title {
  font-weight: 600;
  letter-spacing: 0.04em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.config-row-sub {
  font-size: 0.7rem;
  color: var(--muted);
  letter-spacing: 0.08em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.config-toggle { flex-shrink: 0; }
.config-toggle[aria-pressed="false"] {
  color: var(--muted);
  border-color: var(--line);
}

/* Hidden connections fade back so the user can scan visible sources at
   a glance; they're still fully interactive (HIDE → SHOW). */
.connection-wrap.is-hidden .connection { opacity: 0.55; }
.connection-flag {
  font-size: 0.6rem;
  letter-spacing: 0.25em;
  font-weight: 600;
  color: var(--warn);
  border: 1px solid var(--warn);
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  margin-left: 0.4rem;
  text-transform: uppercase;
}

.hidden-connections { margin-top: 0.75rem; }
.hidden-connections > .connection-wrap { margin-top: 0.5rem; }
.hidden-connections-summary {
  cursor: pointer;
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  padding: 0.35rem 0;
  user-select: none;
}
.hidden-connections-summary:hover { color: var(--fg); }

.pref-row {
  border: 2px solid var(--line);
  border-radius: 14px;
  background: var(--bg);
  padding: 0.85rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
.pref-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 600;
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  color: var(--muted);
}
.pref-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--accent);
  letter-spacing: 0.05em;
}
.pref-hint {
  font-size: 0.72rem;
  color: var(--muted);
  line-height: 1.5;
}
.pref-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  background: var(--line);
  border-radius: 999px;
  outline: none;
}
.pref-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 0;
}
.pref-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 0;
}

.connection {
  border: 2px solid var(--fg);
  border-radius: 16px;
  background: var(--bg);
  padding: 0.95rem 1.1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem;
  flex-wrap: wrap;
}
.connection + .connection { margin-top: 0.5rem; }

.probe-output {
  margin-top: 0.4rem;
  background: var(--dim);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 0.8rem 0.9rem;
  color: var(--fg);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.72rem;
  font-weight: 450;
  word-break: break-word;
  max-height: 60vh;
  overflow: auto;
  line-height: 1.55;
}
.probe-copy-bar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 0.5rem;
}
.probe-copy {
  background: transparent;
  color: var(--muted);
  border: 1.5px solid var(--line);
  border-radius: 999px;
  padding: 0.25rem 0.7rem;
  font: inherit;
  font-size: 0.62rem;
  font-weight: 650;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  cursor: pointer;
  line-height: 1;
}
.probe-copy:hover {
  border-color: var(--fg);
  color: var(--fg);
}
.probe-copy.is-done {
  background: var(--accent);
  color: #000;
  border-color: var(--accent);
}

.json-node { display: block; }
.json-node > .json-summary { cursor: pointer; list-style: none; user-select: none; }
.json-node > .json-summary::-webkit-details-marker { display: none; }
.json-node > .json-summary::before {
  content: '▸';
  display: inline-block;
  width: 0.9em;
  color: var(--muted, #888);
  transition: transform 0.1s ease;
}
.json-node[open] > .json-summary::before { content: '▾'; }
.json-children {
  margin-left: 1.1em;
  border-left: 1px dashed rgba(255, 255, 255, 0.08);
  padding-left: 0.6em;
}
.json-leaf { padding-left: 1.1em; }
.json-key { color: var(--accent, #79b8ff); }
.json-brace { color: var(--muted, #888); }
.json-string { color: #a5d6a7; }
.json-string { word-break: break-all; }
.json-number { color: #f4b24c; }
.json-bool { color: #ff79c6; }
.json-null { color: #888; font-style: italic; }
.json-empty { color: var(--muted, #888); }

.connection-meta {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
  flex: 1;
}

.connection-name {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 1rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.connection-type {
  font-size: 0.65rem;
  font-weight: 650;
  letter-spacing: 0.24em;
  color: var(--muted);
  padding: 0.18rem 0.55rem;
  border: 1.5px solid var(--line);
  border-radius: 999px;
}

.connection-detail {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--muted);
  word-break: break-word;
}

.connection-actions { display: flex; gap: 0.4rem; flex-shrink: 0; }

.button {
  display: inline-flex;
  align-items: center;
  padding: 0.55rem 0.9rem;
  border: 2px solid var(--fg);
  border-radius: 999px;
  background: var(--bg);
  color: var(--fg);
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  line-height: 1;
}
.button:hover { background: var(--fg); color: var(--bg); }
.button--ghost { border-color: var(--line); color: var(--muted); }
.button--ghost:hover { color: var(--fg); border-color: var(--fg); background: transparent; }
.button--bad {
  border-color: var(--bad);
  color: var(--bad);
}
.button--bad:hover { background: var(--bad); color: var(--bg); }
.button--solid {
  border-color: var(--accent);
  background: var(--accent);
  color: #000;
}
.button--solid:hover { background: var(--bg); color: var(--accent); }
.button:disabled { opacity: 0.4; cursor: not-allowed; }

.types {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr));
  gap: 0.6rem;
}

.type-card {
  background: var(--bg);
  border: 2px solid var(--fg);
  border-radius: 16px;
  padding: 1rem 1.1rem;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font: inherit;
  color: inherit;
}
.type-card:hover { background: var(--fg); color: var(--bg); }
.type-card:hover .type-desc { color: rgba(0,0,0,0.7); }
.type-name {
  font-size: 1rem;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.type-desc {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.45;
}

.form {
  background: var(--bg);
  border: 2px solid var(--accent);
  border-radius: 16px;
  padding: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.form-title {
  font-size: 0.75rem;
  font-weight: 650;
  letter-spacing: 0.28em;
  color: var(--accent);
}

.form-row { display: flex; flex-direction: column; gap: 0.35rem; }
.form-row label {
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.22em;
  color: var(--muted);
  text-transform: uppercase;
}
.form-row input {
  background: var(--bg);
  border: 2px solid var(--line);
  border-radius: 10px;
  padding: 0.75rem 0.9rem;
  color: var(--fg);
  font: inherit;
  font-size: 1rem;
  font-weight: 550;
}
.form-row input:focus {
  outline: none;
  border-color: var(--accent);
}

.form-actions {
  display: flex;
  gap: 0.55rem;
  justify-content: flex-end;
  flex-wrap: wrap;
}

.form-error {
  color: var(--bad);
  font-size: 0.85rem;
  font-weight: 600;
}

.note {
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.6;
}
.note code {
  background: var(--dim);
  padding: 0.1em 0.5em;
  border-radius: 6px;
  color: var(--fg);
  font-size: 0.9em;
  font-weight: 550;
}

/* ─────────────────── boot / first-poll fuse ─────────────────── */

.boot {
  position: fixed;
  inset: 0;
  z-index: 999;
  background: var(--bg);
  pointer-events: none;
  transition: opacity 600ms ease;
  perspective: 700px;
  perspective-origin: 50% 50%;
}
.boot.is-done {
  opacity: 0;
}

.boot-stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
}


.boot-head {
  position: absolute;
  top: 0;
  left: 0;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow:
    0 0 4px var(--accent),
    0 0 14px var(--accent),
    0 0 34px var(--accent);
  pointer-events: none;
  will-change: transform;
}

.boot-spark {
  position: absolute;
  top: 0;
  left: 0;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 4px var(--accent), 0 0 9px color-mix(in srgb, var(--accent) 55%, transparent);
  pointer-events: none;
  /* `translate` keeps centering separate from the keyframe transform so the
     arc isn't fighting -50%/-50% (matches limit-bar particle technique). */
  translate: -50% -50%;
  animation: boot-spark 720ms cubic-bezier(0.22, 0.7, 0.35, 1) forwards;
  will-change: transform, opacity;
}

@keyframes boot-spark {
  0%   { transform: translate(0, 0) scale(1.15); opacity: 1; }
  35%  {
    transform:
      translate(
        calc(var(--ex, 60px) * 0.4),
        calc(var(--ey, 40px) * 0.4 + var(--arc, -12px))
      )
      scale(1);
  }
  85%  { opacity: 0.7; }
  100% { transform: translate(var(--ex, 60px), var(--ey, 40px)) scale(0.2); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .boot-head, .boot-spark { display: none; }
}
