/* ==================================================================
   Command Center OS

   Light product UI. Replaced the dark cinematic direction 2026-08-26
   (see CLAUDE.md). Token names are unchanged on purpose so every
   component kept resolving through the switch; only the values moved.

   Colour carries meaning here. The accent is identity and primary
   actions; the severity ramp is state. Never use the accent to mean
   "bad" -- that is what --crit is for.
   ================================================================== */

:root {
  /* ground vs surface: the app is a white sheet sitting on grey */
  --ground:    #edeff2;
  --bg:        #ffffff;
  --bg-raised: #f7f8fa;
  --bg-inset:  #f1f3f6;

  --line:      #e6e8ec;
  --line-soft: #eef0f3;

  /* Three tiers, all of which carry text somewhere, so all three clear
     AA (4.5:1) on both --bg and --bg-raised. Verified, not assumed. */
  --ink:       #14161c;
  --ink-mid:   #4e535d;
  --ink-dim:   #6c717c;

  /* identity + primary actions only */
  --accent:      #e8174b;   /* identity: fills, dots, bars, borders */
  --accent-ink:  #d11242;   /* the same crimson, dark enough to be words */
  --accent-hover:#c9123f;
  --accent-dim:  #f7c2d1;
  --accent-soft: #fdeaf0;

  /* state. most of what this dashboard reports is a severity. */
  --ok:        #1f9d55;  --ok-soft:   #e8f6ee;  --ok-line:   #bfe3cd;
  --warn:      #d99a00;  --warn-soft: #fdf4e0;  --warn-line: #f0dcae;
  --over:      #e2622a;  --over-soft: #fdeee6;  --over-line: #f5cdb8;
  --crit:      #dc2a2a;  --crit-soft: #fdeaea;  --crit-line: #f3c4c4;
  --info:      #2c6fe5;
  --info-ink:  #265fc9;

  /* Text-safe counterparts. The vivid values above are for dots, bars,
     borders and fills, where 3:1 is the bar. Amber in particular cannot
     carry small text on white -- #d99a00 lands at 2.45:1 -- so anything
     that sets a colour on words uses these instead. */
  --ok-ink:    #14683a;
  --warn-ink:  #8a6300;
  --over-ink:  #b8481a;
  --crit-ink:  #c11f1f;

  /* legacy alias: --have predates the ramp and still reads as "fine" */
  --have:      var(--ok-ink);

  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;

  --r-card: 12px;
  --r-ctl: 8px;
  --shadow: 0 1px 2px rgba(20, 22, 28, .04), 0 4px 12px rgba(20, 22, 28, .05);

  --seg-on:    #ffffff;   /* neutral active fill for segmented controls */
  --seg-shadow:0 1px 2px rgba(20, 22, 28, .10);

  /* Categorical slots for the capacity arc. Identity, not severity — kept
     apart from the ok/warn/over/crit ramp on purpose. Fixed order, never
     cycled: slot N always means the same bucket. Both modes were validated
     rather than eyeballed (worst adjacent CVD ΔE 9.2 light / 8.4 dark, all
     six ≥3:1 on --bg); the light steps are darkened from the reference so
     nothing here needs a contrast exemption. */
  --cat-1: #2a78d6; --cat-2: #eb6834; --cat-3: #1aa875;
  --cat-4: #c78700; --cat-5: #da749a; --cat-6: #4a3aa7;

  --sidebar-w: 236px;
  --sidebar-w-collapsed: 72px;
  --gap: 1px;
}

/* ==================================================================
   Dark theme.

   Sampled from Zach's own Todoist and Day One, which is where the
   direction came from -- both of the apps he has never dropped run
   dark. Only the token VALUES change; not one component rule is
   theme-aware, which is the whole reason the names stayed stable
   through the earlier direction change.

   Every text token here clears AA (4.5:1) on both --bg and
   --bg-raised. Checked numerically, not by eye: on dark the trap is
   the muted greys, exactly as amber was the trap on light.
   ================================================================== */

[data-theme="dark"] {
  --ground:    #141414;
  --bg:        #1e1e1e;
  --bg-raised: #242424;
  --bg-inset:  #2a2a2a;

  --line:      #2f2f2f;
  --line-soft: #272727;

  --ink:       #ececec;
  --ink-mid:   #a6a6a6;
  /* #8e8e8e cleared AA on --bg and --bg-raised but landed at 4.38:1 on
     --bg-inset, which the study did not have. #969696 clears all three. */
  --ink-dim:   #969696;

  --accent:      #ff2d63;
  --accent-ink:  #ff5c83;   /* the brand crimson is 4.29:1 as words on a card */
  --accent-hover:#ff4d78;
  --accent-dim:  #6e1f33;
  --accent-soft: #2a141b;

  --ok:        #4bb377;  --ok-soft:   #16281e;  --ok-line:   #2f5540;
  --warn:      #e0a33a;  --warn-soft: #2b2417;  --warn-line: #574722;
  --over:      #ef8a55;  --over-soft: #2c1e17;  --over-line: #5a3a2a;
  --crit:      #f2645a;  --crit-soft: #2d1818;  --crit-line: #5c2c2c;
  --info:      #6da4ff;
  --info-ink:  #8ab6ff;

  /* On dark the vivid values are already light enough to be words, so
     the ink variants match rather than darkening. The split is kept so
     component rules never need to know which theme they are in. */
  --ok-ink:    #4bb377;
  --warn-ink:  #e0a33a;
  --over-ink:  #ef8a55;
  --crit-ink:  #f2645a;

  --cat-1: #3987e5; --cat-2: #d95926; --cat-3: #199e70;
  --cat-4: #c98500; --cat-5: #d55181; --cat-6: #9085e9;

  --seg-on:    #363636;
  --seg-shadow:0 1px 2px rgba(0, 0, 0, .35);
  --shadow: 0 1px 2px rgba(0, 0, 0, .30), 0 4px 14px rgba(0, 0, 0, .25);
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  /* Ground, not surface. The modules are the white sheets on top. */
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 400;
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* ── spec-sheet rows ─────────────────────────────────────────────── */

.spec-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--line);
}
.spec-row:last-child { border-bottom: 0; }

.spec-key {
  font-family: var(--sans);
  font-size: 12px;
  color: var(--ink-dim);
  white-space: nowrap;
}
.spec-val {
  font-family: var(--mono);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-mid);
  text-align: right;
}
.spec-val.is-accent { color: var(--accent-ink); }
.spec-val.is-warn   { color: var(--warn-ink); }

.eyebrow {
  font-family: var(--sans);
  font-size: 12px;
  color: var(--ink-dim);
}

/* ── shell ───────────────────────────────────────────────────────── */

.shell {
  /* Concrete track values on both sides. Transitioning a grid track whose
     width comes from a var() that changes on the same element leaves the
     computed value stuck at the old width in some engines. */
  display: grid;
  grid-template-columns: 236px 1fr;
  min-height: 100vh;
  transition: grid-template-columns 220ms cubic-bezier(.4, 0, .2, 1);
}
.shell.is-railed { grid-template-columns: 72px 1fr; }

.sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  width: 236px;
  display: flex;
  flex-direction: column;
  padding: 1.1rem .75rem;
  border-right: 1px solid var(--line);
  background: var(--bg-raised);
  z-index: 30;
  transition: width 220ms cubic-bezier(.4, 0, .2, 1);
}
.shell.is-railed .sidebar { width: 72px; align-items: center; }

.brand { display: flex; align-items: center; gap: .65rem; padding: .3rem .3rem 1rem; }
.brand-mark {
  width: 30px; height: 30px; flex: none; border-radius: var(--r-ctl);
  background: var(--accent); color: #fff;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 600; letter-spacing: .02em;
}
.brand-name {
  font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em;
  line-height: 1.2; white-space: nowrap; min-width: 0;
}
.brand-sub {
  display: block; font-size: 11.5px; font-weight: 400;
  color: var(--ink-dim); letter-spacing: 0;
}

.nav { display: flex; flex-direction: column; gap: 2px; width: 100%; }
.nav-item {
  position: relative;
  display: flex; align-items: center; gap: .7rem;
  width: 100%; padding: .55rem .55rem;
  background: transparent; border: 0; border-radius: var(--r-ctl);
  color: var(--ink-mid);
  font-family: var(--sans); font-size: 13.5px; font-weight: 400;
  text-align: left; cursor: pointer;
  transition: color 120ms linear, background 120ms linear;
}
.nav-item svg {
  width: 17px; height: 17px; flex: none;
  stroke: currentColor; fill: none; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.nav-item:hover { color: var(--ink); background: var(--bg-inset); }
.nav-item.is-active { color: var(--ink); background: var(--bg-inset); font-weight: 500; }
.nav-label { white-space: nowrap; overflow: hidden; }

/* Section labels cannot survive the rail, but the grouping they carry must,
   so they become a rule rather than disappearing. */
.sidebar-sec {
  font-family: var(--mono); font-size: 10px; letter-spacing: .11em;
  text-transform: uppercase; color: var(--ink-dim);
  padding: 1.3rem .6rem .4rem; white-space: nowrap;
}

/* ── the rail ─────────────────────────────────────────────────── */
.shell.is-railed .brand { padding: .3rem 0 1rem; justify-content: center; }
.shell.is-railed .brand-name,
.shell.is-railed .nav-label,
.shell.is-railed .sidebar-foot .spec-row,
.shell.is-railed .ghost-btn { display: none; }
.shell.is-railed .nav-item { width: 40px; height: 40px; padding: 0; justify-content: center; }
.shell.is-railed .sidebar-sec {
  padding: 0; text-indent: -999em; overflow: hidden;
  height: 1px; width: 24px; margin: .85rem auto; background: var(--line);
}
.shell.is-railed .sidebar-foot { border-top: 0; padding-top: 0; width: auto; }

/* An icon alone is not a name. */
.shell.is-railed .nav-item::after {
  content: attr(data-label);
  position: absolute; left: calc(100% + 10px); top: 50%;
  transform: translateY(-50%) scale(.96); transform-origin: left center;
  background: var(--ink); color: #fff;
  font-size: 12px; font-weight: 500; padding: .3rem .5rem;
  border-radius: 6px; white-space: nowrap;
  opacity: 0; pointer-events: none; z-index: 40;
  transition: opacity 120ms linear, transform 120ms linear;
}
.shell.is-railed .nav-item:hover::after,
.shell.is-railed .nav-item:focus-visible::after { opacity: 1; transform: translateY(-50%) scale(1); }

.sidebar-foot { margin-top: auto; padding-top: 1rem; border-top: 1px solid var(--line); width: 100%; }

/* ── buttons ─────────────────────────────────────────────────────── */

.ghost-btn, .accent-btn {
  font-family: var(--mono); font-size: 10px;
  padding: 0.5rem 0.85rem; cursor: pointer;
  transition: color 120ms linear, border-color 120ms linear, background 120ms linear;
}
.ghost-btn {
  background: transparent; border: 1px solid var(--line); color: var(--ink-mid);
  white-space: nowrap;
}
/* Full width only in the sidebar — elsewhere it sits beside an input. */
.sidebar-foot .ghost-btn { width: 100%; margin-top: 1rem; }
.ghost-btn:hover { color: var(--ink); border-color: var(--ink-dim); }
.accent-btn {
  background: var(--accent); border: 1px solid var(--accent); color: #0a0a0a;
  font-weight: 500;
}
.accent-btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
button:disabled { opacity: 0.4; cursor: not-allowed; }

/* ── main ────────────────────────────────────────────────────────── */

.main {
  grid-column: 2; display: flex; flex-direction: column; min-width: 0;
  /* The sheet. body is the ground now, so the main column has to paint
     itself white or it shows the grey through. */
  background: var(--bg);
}

.chrome {
  display: flex; align-items: center; gap: .85rem;
  padding: .7rem 1.25rem; border-bottom: 1px solid var(--line);
}
.icon-btn {
  width: 32px; height: 32px; display: grid; place-items: center; flex: none;
  background: transparent; border: 1px solid transparent;
  border-radius: var(--r-ctl); color: var(--ink-mid); cursor: pointer;
  transition: background 140ms, color 140ms, border-color 140ms;
}
.icon-btn:hover { background: var(--bg-inset); color: var(--ink); border-color: var(--line); }
.icon-btn svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.6; }
.crumb { font-size: 13.5px; color: var(--ink-dim); }
.crumb span { margin: 0 .45rem; }
.crumb b { color: var(--ink); font-weight: 600; }

.topbar {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 2rem; flex-wrap: wrap;
  padding: 1.5rem 1.25rem 1.25rem;
  background: var(--bg);
}
.date-line {
  margin: 0.4rem 0 0;
  font-size: clamp(1.6rem, 3.2vw, 2.2rem);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1;
}
.topbar-meta { min-width: 190px; }

/* ── grid ────────────────────────────────────────────────────────── */

.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 1rem;
  /* Cards sit on the ground, not on the sheet. The 1px-gap hairline trick
     the dark build used reads as seams once everything is white. */
  background: var(--ground);
  padding: 1.25rem;
  align-items: start;
}

.module {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: var(--shadow);
  padding: 1.1rem 1.25rem 1.35rem;
  min-width: 0;
}

/* ── stat tiles ───────────────────────────────────────────────── */

.stats {
  grid-column: span 12;
  display: grid;
  /* Six columns: four stat tiles at one each, capacity at two. */
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: .75rem;
  align-items: start;
}
.stat {
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--r-card); box-shadow: var(--shadow);
  padding: .85rem .95rem;
}
.stat-k {
  display: flex; align-items: center; gap: .4rem;
  font-size: 12px; color: var(--ink-mid); margin-bottom: .3rem;
}
.stat-k i {
  width: 6px; height: 6px; border-radius: 50%; display: block;
  background: var(--ink-dim); flex: none;
}
.stat-v {
  font-size: 24px; font-weight: 600; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1.15;
}
.stat-n { font-size: 11.5px; color: var(--ink-dim); margin-top: .1rem; }

/* State lives on the tile, so the dot and the number agree. */
.stat.is-ok   .stat-k i { background: var(--ok); }
.stat.is-warn .stat-k i { background: var(--warn); }
.stat.is-over .stat-k i { background: var(--over); }
.stat.is-crit .stat-k i { background: var(--crit); }
.stat.is-warn .stat-v { color: var(--warn-ink); }
.stat.is-over .stat-v { color: var(--over-ink); }
.stat.is-crit .stat-v { color: var(--crit-ink); }
.module--focus    { grid-column: span 12; }
.module--tasks    { grid-column: span 7; }
.module--projects { grid-column: span 5; }
.module--pipeline { grid-column: span 5; }
/* Neither review surface appears on the Overview (Zach, 2026-08-31). Both
   exist in the markup for the Review view only.

   Proposals came off the Overview because a decision surface and a morning
   surface want different things from you: the Overview answers "what is today"
   and should be readable without deciding anything. The sidebar keeps a
   Proposals count, which is the right division — the count is the pointer,
   the page is the surface. */
.module--review   { grid-column: span 12; }
.module--digest   { grid-column: span 12; }
.module--archive  { grid-column: span 12; }
html[data-view="overview"] .module--review,
html[data-view="overview"] .module--digest,
html[data-view="overview"] .module--archive { display: none; }

.module-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: .75rem;
  padding-bottom: 0.75rem; margin-bottom: 1rem;
  border-bottom: 1px solid var(--line-soft);
}
.module-title {
  margin: 0; font-size: 14.5px; font-weight: 600; letter-spacing: -0.01em;
}
.module-idx {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.16em; color: var(--ink-dim);
}

/* view filtering */
html[data-view="tasks"]    .module:not([data-module="tasks"]),
html[data-view="projects"] .module:not([data-module="projects"]),
html[data-view="pipeline"] .module:not([data-module="pipeline"]) { display: none; }
html[data-view="tasks"]    .module--tasks,
html[data-view="projects"] .module--projects,
html[data-view="pipeline"] .module--pipeline { grid-column: span 12; }

/* Review is the one view that shows two modules: the open proposals you are
   being asked to decide, then the week's pool underneath them. Splitting them
   across two nav items would hide the decisions behind a click. */
html[data-view="review"] .module:not([data-module="review"]):not([data-module="digest"]),
html[data-view="review"] .stats { display: none; }
html[data-view="review"] .module--review,
html[data-view="review"] .module--digest { grid-column: span 12; display: block; }

html[data-view="archive"] .module:not([data-module="archive"]),
html[data-view="archive"] .stats { display: none; }
html[data-view="archive"] .module--archive { grid-column: span 12; display: block; }

/* ── focus module ────────────────────────────────────────────────── */

.focus-edit { display: flex; gap: var(--gap); margin-bottom: 0.75rem; }
.focus-input, .add-input, .add-select, .inline-input {
  flex: 1; min-width: 0;
  background: var(--bg-inset);
  border: 1px solid var(--line);
  color: var(--ink);
  font-family: var(--sans); font-weight: 400;
  padding: 0.7rem 0.85rem;
  transition: border-color 120ms linear;
}
.focus-input { font-size: 1.05rem; }
.focus-input::placeholder, .add-input::placeholder { color: var(--ink-dim); }
.focus-input:focus, .add-input:focus, .inline-input:focus,
.add-select:focus, .nav-item:focus-visible, button:focus-visible {
  outline: none; border-color: var(--accent);
}
.nav-item:focus-visible, button:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }

.focus-status {
  font-size: 12px; color: var(--ink-dim); min-height: 1.2em;
}

.today-list { margin-top: 1.5rem; }
.today-list .spec-row { padding: 0.55rem 0; }

/* ── tasks ───────────────────────────────────────────────────────── */

.add-task { display: flex; gap: var(--gap); margin-bottom: 1.5rem; flex-wrap: wrap; }
.add-task .add-input { flex: 3 1 240px; }
.add-select {
  flex: 1 1 130px;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.1em;
  appearance: none; cursor: pointer;
}
.add-select option { background: var(--bg-inset); color: var(--ink); }

.task-group { border-bottom: 1px solid var(--line); }
.task-group:last-child { border-bottom: 0; }

.group-head {
  display: flex; align-items: center; gap: 0.7rem;
  width: 100%; padding: 0.85rem 0;
  background: none; border: 0; cursor: pointer;
  color: var(--ink); text-align: left;
  font-family: var(--sans); font-size: 13px; font-weight: 400;
}
.group-caret {
  font-family: var(--mono); font-size: 9px; color: var(--ink-dim);
  transition: transform 140ms ease;
}
.task-group.is-collapsed .group-caret { transform: rotate(-90deg); }
.group-name { flex: 1; letter-spacing: 0.01em; }
.group-count {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.1em; color: var(--ink-dim);
}
.group-count.has-overdue { color: var(--accent-ink); }
.task-group.is-collapsed .group-body { display: none; }
.group-body { padding-bottom: 0.7rem; display: flex; flex-direction: column; gap: 0.4rem; }

/* Todoist's treatment: a card per task rather than a dense row. About nine
   fit a screen instead of twenty, which is the point -- this is a list you
   work in, not a table you scan. */
.task {
  display: flex; align-items: flex-start; gap: 0.65rem;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.6rem 0.75rem;
  transition: border-color 140ms linear;
}
.task:hover { border-color: var(--ink-dim); }
.task.is-sub { margin-left: 1.4rem; }

/* Circular, ringed by priority. The ring is now the only place priority is
   stated, so it does the job the P1 label used to. */
.task-check {
  flex: none; position: relative;
  width: 17px; height: 17px; margin-top: 1px;
  background: transparent; cursor: pointer; padding: 0;
  border: 1.8px solid var(--ink-dim);
  border-radius: 50%;
  transition: border-color 120ms linear, background 120ms linear;
}
.task-check:hover { background: var(--bg-inset); }
.task.is-done .task-check { background: var(--ink-dim); border-color: var(--ink-dim); }
/* The tick is knocked OUT of the filled circle rather than drawn on top of it:
   `--bg-raised` is the row behind, so it reads as a hole and inherits a
   contrast pair already proven in both themes. Deliberately `::before` —
   `::after` is the phone touch area, and sharing it would break both. */
.task.is-done .task-check::before {
  content: "";
  position: absolute; left: 50%; top: 50%;
  width: 4px; height: 7.5px;
  border: solid var(--bg-raised);
  border-width: 0 2px 2px 0;
  transform: translate(-50%, -60%) rotate(45deg);
}
.task-check[data-p="4"] { border-color: var(--crit); }
.task-check[data-p="3"] { border-color: var(--over); }
.task-check[data-p="2"] { border-color: var(--info); }

/* Rows read out of the daily brief. The dashboard never writes to Wrike, so
   this marker is a label rather than a control — a rounded square, which is a
   shape Todoist never uses for anything tickable. See the comment in
   tasks.js for why neither grey nor a brand colour was enough on its own. */
.task-marker {
  flex: none; width: 15px; height: 15px; margin-top: 2px;
  background: var(--ink-dim);
  border-radius: 4px;
  cursor: default;
}
.task-source { color: var(--ink-dim); }

/* Permanently visible, unlike .task-actions: it is the only affordance the
   row has, so hiding it until hover would leave the row looking inert. */
.task-open {
  flex: none; position: relative;
  display: grid; place-items: center;
  width: 26px; height: 26px; margin-top: -2px;
  border-radius: var(--r-ctl);
  color: var(--ink-dim);
  transition: background 120ms linear, color 120ms linear;
}
.task-open:hover, .task-open:focus-visible { background: var(--bg-inset); color: var(--info); }

/* Ladder tags (TR#/Z#/lane). Mono because they are codes, not words — one of
   the four places the uppercase-mono treatment genuinely earns its keep. */
.task-ladder { font-family: var(--mono); font-size: 10.5px; color: var(--ink-dim); }

/* ── Today: section heads and the quiet counts ─────────────────────
   The module merges brief rows and task rows into one ranked list, so these
   heads label *urgency*, not source. Source is the marker's job. */
.sec {
  display: flex; align-items: baseline; gap: .45rem;
  margin: .9rem 0 .45rem;
}
.sec:first-child { margin-top: .3rem; }
.sec-n { font-size: 12px; font-weight: 600; color: var(--ink); }
.sec-c { font-family: var(--mono); font-size: 10px; color: var(--ink-dim); }
.sec-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  align-self: center; background: var(--ink-dim);
}
.sec-dot.is-crit { background: var(--crit); }
.sec-dot.is-over { background: var(--over); }
.sec-dot.is-warn { background: var(--warn); }
.sec-dot.is-info { background: var(--info); }

/* Counts, not rows: things to be aware of rather than things to work. */
.quiet { margin-top: .8rem; border-top: 1px solid var(--line-soft); padding-top: .6rem; }
.quiet-row {
  display: flex; align-items: center; gap: .45rem;
  padding: .3rem 0; font-size: 11.5px; color: var(--ink-mid);
}
.quiet-row strong { color: var(--ink); font-weight: 500; }

/* ── capacity today ────────────────────────────────────────────────
   A ratio against a fixed budget that is also a composition, so the arc does
   both. No static key: each segment carries its own label on hover, focus and
   tap, and an offscreen table keeps every value reachable without pointing. */
/* The gauge is roughly twice a stat tile's height, so it claims two rows and
   the four tiles pair up beside it. Left as one row, the tiles sat in a band
   of empty card. */
.stat--cap {
  grid-column: span 2; grid-row: span 2;
  /* Stretch so the card's edges land exactly on the two tiles beside it —
     `.stats` sets align-items:start, which would otherwise leave it short and
     reopen the gap this is here to close. */
  align-self: stretch;
  display: flex; flex-direction: column;
}
.stats .stat:not(.stat--cap) { grid-column: span 2; }
/* The arc is shallow (see SWEEP in capacity.js) so it can span the whole card
   and still come in under the height of the two stat tiles beside it.
   `align-self: stretch` on the card then holds its edges on the tiles.

   Sizing has to work in BOTH directions or the gap returns on a wide display:
   `flex: 1` offers the leftover height, `aspect-ratio` derives the width from
   it, and `max-width: 100%` takes over when width is the binding constraint
   instead (a phone). Because the wrap always equals the arc's own box there is
   no letterboxing either way — which is what lets the centred figure and the
   tooltip be positioned as plain percentages. */
.cap-body { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.cap-wrap {
  position: relative; margin: .1rem auto 0;
  flex: 1 1 auto; min-height: 0; max-width: 100%;
  aspect-ratio: var(--cap-ar, 340 / 114);
}
.cap-wrap svg { display: block; width: 100%; height: 100%; }
/* The hit path is a transparent stroke: claim events along the stroke only,
   so the hollow middle of the arc stays inert. */
.cap-seg { transition: opacity 120ms linear; }
.cap-seg.is-hot { opacity: .78; }
.cap-hit { cursor: default; outline: none; pointer-events: stroke; }
.cap-mid {
  position: absolute; left: 0; right: 0; top: 54%;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  pointer-events: none;
}
.cap-mid b {
  font-size: 32px; font-weight: 600; letter-spacing: -.02em; line-height: 1;
  color: var(--ink);
}
.cap-mid b em { font-size: 15px; font-style: normal; color: var(--ink-mid); margin-left: 1px; }
.cap-mid span { font-size: 11px; color: var(--ink-dim); margin-top: .25rem; }

/* Value leads, label follows — the reader has the segment and wants the number. */
.cap-tip {
  position: absolute; transform: translate(-50%, -130%);
  display: none; align-items: baseline; gap: .35rem;
  background: var(--bg-inset); border: 1px solid var(--line);
  border-radius: var(--r-ctl); padding: .3rem .5rem;
  white-space: nowrap; pointer-events: none; z-index: 5;
  box-shadow: var(--shadow);
}
.cap-tip.is-up { display: flex; }
.cap-tip-v { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.cap-tip-row { display: none; }
.cap-tip-k { width: 10px; height: 2px; border-radius: 1px; align-self: center; flex: none; }
.cap-tip-l { font-size: 11.5px; color: var(--ink-mid); }
/* Pinned to the bottom of the stretched card rather than floating under the
   arc, so the note sits on the same baseline as the lower tiles. */
.cap-note {
  font-size: 11px; color: var(--ink-dim); line-height: 1.4;
  margin-top: auto; padding-top: .5rem;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── the calendar strip ────────────────────────────────────────────
   Time-anchored, so it sits above the ranked list rather than inside it: an
   event is not something you finish, it is something that happens to you. */
.cal { display: flex; flex-direction: column; gap: .3rem; margin: .1rem 0 .2rem; }
.cal-row {
  display: flex; align-items: baseline; gap: .6rem;
  padding: .35rem .55rem; border-radius: 8px;
  background: var(--bg-inset);
  border-left: 2px solid transparent;
}
.cal-row.is-next { border-left-color: var(--accent); }
.cal-t {
  font-family: var(--mono); font-size: 11px; color: var(--ink-mid);
  flex: none; min-width: 52px;
}
.cal-n { font-size: 12.5px; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
.cal-d { font-family: var(--mono); font-size: 10px; color: var(--ink-dim); margin-left: auto; }
.cal-note { font-size: 11.5px; color: var(--ink-dim); padding: .35rem .55rem; }

/* The visible cap. Reads as a control, not a row, because it navigates. */
.more-row {
  display: block; width: 100%; text-align: left;
  margin-top: .35rem; padding: .5rem .75rem;
  background: transparent; border: 1px dashed var(--line);
  border-radius: 10px; cursor: pointer;
  font: inherit; font-size: 11.5px; color: var(--ink-dim);
  transition: border-color 120ms linear, color 120ms linear;
}
.more-row:hover, .more-row:focus-visible { border-color: var(--ink-dim); color: var(--ink-mid); }

/* Sits beside the age in the Today header. */
.module-note { font-family: var(--mono); font-size: 10.5px; color: var(--ink-dim); }

.task-main { flex: 1; min-width: 0; }
.task-content {
  font-size: 14.5px; font-weight: 400; line-height: 1.35;
  word-wrap: break-word; overflow-wrap: anywhere;
}
.task.is-done .task-content { color: var(--ink-dim); text-decoration: line-through; }

.task-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  margin-top: 0.3rem;
  font-size: 11px; color: var(--ink-dim);
}
/* Outlined pills for state, per the design direction. The border does the
   work so a row of them stays quiet next to the task title. */
.task-due, .task-deadline, .task-stall {
  display: inline-flex; align-items: center;
  border: 1px solid var(--line); border-radius: 20px;
  padding: .1rem .45rem; font-size: 11px; font-weight: 500;
  color: var(--ink-mid); white-space: nowrap;
}
.task-pri {
  font-family: var(--mono); font-size: 10.5px; color: var(--ink-dim);
}
.task-due.is-overdue { color: var(--crit-ink); border-color: var(--crit-line); background: var(--crit-soft); }
.task-due.is-today   { color: var(--warn-ink); border-color: var(--warn-line); background: var(--warn-soft); }

.task-actions { display: flex; gap: 0.15rem; flex: none; opacity: 0; transition: opacity 120ms linear; }
.task:hover .task-actions, .task:focus-within .task-actions { opacity: 1; }
/* A text button inside a task row — "Edit" / "Del" / "Esc". Deliberately NOT
   .icon-btn: that name belongs to the 32px square icon buttons in the chrome,
   and sharing it meant this block silently overrode their border and padding
   further down the sheet. */
.row-btn {
  background: none; border: 0; cursor: pointer; padding: 0.25rem 0.4rem;
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.1em;
  color: var(--ink-dim);
}
.row-btn:hover { color: var(--ink); }
.row-btn.is-danger:hover { color: var(--accent); }

.task-edit { display: flex; gap: var(--gap); width: 100%; }
.inline-input { font-size: 14px; padding: 0.45rem 0.6rem; }

/* ── status cards ────────────────────────────────────────────────── */

.status-card { padding: 1.1rem 0; border-bottom: 1px solid var(--line); }
.status-card:first-child { padding-top: 0; }
.status-card:last-child { border-bottom: 0; }

.status-card-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; margin-bottom: 0.65rem;
}
.status-name { font-size: 15px; font-weight: 400; letter-spacing: -0.005em; }
.status-zone {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-dim);
}
.status-line {
  font-size: 13px; font-weight: 400; color: var(--ink-mid);
  margin-bottom: 0.75rem; line-height: 1.45;
}

.flag {
  display: inline-block; margin-top: 0.6rem;
  padding: 0.3rem 0.55rem;
  border-left: 1px solid var(--warn);
  background: var(--warn-soft);
  font-family: var(--mono); font-size: 9.5px;
  letter-spacing: 0.08em; color: var(--warn-ink); line-height: 1.5;
}
.flag.is-stale { border-left-color: var(--over); background: var(--over-soft); color: var(--over-ink); }

/* ── pipeline ────────────────────────────────────────────────────── */

.stage { border-bottom: 1px solid var(--line); }
.stage:last-child { border-bottom: 0; }
.stage-head {
  display: flex; align-items: center; gap: 0.8rem; padding: 0.7rem 0;
}
.stage-bar { flex: none; width: 22px; height: 1px; background: var(--line); }
.stage.has-entries .stage-bar { background: var(--accent); }
.stage-name {
  flex: 1; font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-dim);
}
.stage.has-entries .stage-name { color: var(--ink); }
.stage-count { font-family: var(--mono); font-size: 10px; color: var(--ink-dim); }

.entry { padding: 0.5rem 0 0.9rem 2.2rem; }
.entry-name { font-size: 14px; font-weight: 400; }
.entry-note { font-size: 12.5px; font-weight: 400; color: var(--ink-mid); margin-top: 0.3rem; line-height: 1.45; }

/* ── misc ────────────────────────────────────────────────────────── */

.empty {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.1em;
  color: var(--ink-dim); padding: 1.25rem 0; line-height: 1.7;
}
.empty a { color: var(--accent-ink); }

.foot {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: 1rem 1.25rem 1.75rem; margin-top: auto;
  background: var(--ground);
}

.toast {
  position: fixed; left: 50%; bottom: 1.5rem; transform: translate(-50%, 200%);
  background: var(--bg-inset); border: 1px solid var(--line);
  border-left: 1px solid var(--accent);
  padding: 0.7rem 1.1rem; z-index: 60; max-width: min(92vw, 420px);
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.1em; color: var(--ink-mid);
  transition: transform 180ms ease;
}
.toast.is-up { transform: translate(-50%, 0); }

.nav-toggle { display: none; }

/* ── responsive ──────────────────────────────────────────────────── */

@media (max-width: 1100px) {
  .module--tasks, .module--projects, .module--pipeline { grid-column: span 12; }
}

@media (max-width: 820px) {
  body { font-size: 16px; }
  /* The rail is a desktop affordance. Below 820 the sidebar is a stacked
     bar with its own hamburger, so `is-railed` must not win here -- it has
     higher specificity than a bare `.shell` and would otherwise leave a
     72px dead column on a phone. */
  .shell,
  .shell.is-railed { grid-template-columns: 1fr; }
  .shell.is-railed .sidebar { width: auto; align-items: center; }
  .shell.is-railed .brand-name,
  .shell.is-railed .nav-label,
  .shell.is-railed .sidebar-foot .spec-row,
  .shell.is-railed .ghost-btn { display: revert; }
  .shell.is-railed .nav-item { width: 100%; height: auto; justify-content: flex-start; }
  .shell.is-railed .nav-item::after { display: none; }
  .shell.is-railed .sidebar-sec {
    text-indent: 0; height: auto; width: auto; background: none;
    margin: 0; padding: 1.3rem .6rem .4rem; overflow: visible;
  }
  #railToggle { display: none; }
  .main { grid-column: 1; }

  .nav-toggle {
    display: flex; flex-direction: column; justify-content: center; gap: 5px;
    position: fixed; top: 0.85rem; right: 0.85rem; z-index: 50;
    width: 44px; height: 44px; padding: 0 11px;
    background: var(--bg-raised); border: 1px solid var(--line); cursor: pointer;
  }
  .nav-toggle span { display: block; height: 1px; background: var(--ink); }

  .sidebar {
    position: sticky; top: 0; width: auto; inset: auto;
    flex-direction: row; align-items: center; flex-wrap: wrap;
    padding: 1rem 1.1rem;
    border-right: 0; border-bottom: 1px solid var(--line);
  }
  .brand { margin: 0; flex: 1; }
  .nav {
    display: none; flex-basis: 100%; width: 100%;
    margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line);
  }
  .sidebar.is-open .nav { display: flex; }
  .nav-item { padding: 0.9rem 0.7rem; }          /* 44px+ tap target */
  /* The section label belongs to the drawer, not the collapsed bar -- left
     visible it collides with the hamburger. */
  .sidebar-sec,
  .sidebar-foot {
    display: none; flex-basis: 100%; width: 100%; margin-top: 0.5rem; padding-top: 0;
  }
  /* One rule decides this on mobile: is the drawer open. The earlier
     `.shell.is-railed .sidebar-sec` reset already restores it to text, and
     adding a second `display:none` here beat `.is-open` on equal
     specificity and hid the label even when the drawer was open. */
  .sidebar.is-open .sidebar-sec,
  .sidebar.is-open .sidebar-foot { display: block; }
  .sidebar.is-open .sidebar-sec { padding: 1rem .6rem .3rem; }

  .topbar { padding: 2rem 1.25rem 1.5rem; }
  .topbar-meta { min-width: 100%; }
  .module { padding: 1.5rem 1.25rem; }
  .foot { padding: 1.5rem 1.25rem 2rem; }

  .add-task { gap: 0.5rem; }
  .add-task .add-input, .add-select { flex-basis: 100%; }
  .add-task .accent-btn { width: 100%; padding: 0.85rem; }

  /* Hover-reveal is meaningless on touch: keep actions visible and tappable. */
  .task-actions { opacity: 1; }
  .row-btn { padding: 0.5rem 0.6rem; min-height: 44px; }
  .task-check { width: 20px; height: 20px; position: relative; }
  /* Keep the 20px box visually but give it a 44px touch area. */
  .task-check::after { content: ""; position: absolute; inset: -12px; }
  /* Tracks the 20px circle so the glyph does not shrink inside it. */
  .task.is-done .task-check::before { width: 5px; height: 9px; }
  /* Tracks the checkbox so the two lists stay the same weight on a phone. */
  .task-marker { width: 18px; height: 18px; }
  /* The header is title + age + refresh at 375; the off-TFA note is the one
     of the four that is context rather than state, so it is what gives way. */
  .module-note { display: none; }
  .task-open::after { content: ""; position: absolute; inset: -9px; }
  .task { padding: 0.75rem 0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 1ms !important; }
}

/* Inline links inside task titles (Todoist markdown links). */
.tlink {
  color: var(--info-ink);
  text-decoration: none;
  border-bottom: 1px solid var(--accent-dim);
  transition: color 120ms linear, border-color 120ms linear;
}
.tlink:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* ── search / filter row ─────────────────────────────────────────── */

.search-row {
  display: flex; align-items: center; gap: 0.75rem;
  margin: 0 0 1.25rem; padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--line);
}
.search-input {
  flex: 1; min-width: 0;
  background: transparent; border: 0;
  border-bottom: 1px solid transparent;
  color: var(--ink);
  font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.1em;
  padding: 0.35rem 0;
  transition: border-color 120ms linear;
}
.search-input::placeholder { color: var(--ink-dim); }
.search-input:focus { outline: none; border-bottom-color: var(--accent); }
.search-input::-webkit-search-cancel-button { display: none; }
.search-count {
  font-family: var(--mono); font-size: 10px;
  color: var(--accent-ink); white-space: nowrap;
}

/* ── group header with mute control ──────────────────────────────── */

.group-head-row { display: flex; align-items: center; gap: 0.6rem; }
.group-head-row .group-head { flex: 1; }

.mute-btn {
  flex: none;
  background: none; border: 0; cursor: pointer;
  font-family: var(--mono); font-size: 9px;
  color: var(--ink-dim); padding: 0.3rem 0.45rem;
  opacity: 0; transition: opacity 120ms linear, color 120ms linear;
}
.task-group:hover .mute-btn,
.group-head-row:focus-within .mute-btn { opacity: 1; }
.mute-btn:hover { color: var(--accent); }
.task-group.is-muted .mute-btn { opacity: 1; color: var(--ink-dim); }
.task-group.is-muted .group-name { color: var(--ink-dim); }

.muted-divider {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-top: 2rem; padding: 0.7rem 0 0.5rem;
  border-top: 1px solid var(--line);
}
.label-inline {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-dim);
}

/* ── new task metadata ───────────────────────────────────────────── */

/* A deadline is a harder fact than a due date; it reads in the warn tone. */
.task-deadline { color: var(--warn-ink); border-color: var(--warn-line); }
.task-dur { color: var(--ink-dim); }
.task-stall {
  color: var(--over-ink); border-color: var(--over-line);
  background: var(--over-soft); cursor: help;
}

@media (max-width: 820px) {
  .mute-btn { opacity: 1; padding: 0.5rem; min-height: 44px; }
  .search-input { font-size: 12px; }
  .search-row { flex-wrap: wrap; }
}

/* ── degradation contract (spec §7.3) ────────────────────────────── */

.notice {
  border: 1px solid var(--line);
  border-left: 2px solid var(--ink-dim);
  background: var(--bg-inset);
  padding: 1.1rem 1.25rem;
  margin: 0.5rem 0;
}
.notice.is-unreachable  { border-left-color: var(--accent); }
.notice.is-unconfigured { border-left-color: var(--warn); }
.notice.is-stale        { border-left-color: var(--warn); }

.notice-title {
  font-size: 14px; font-weight: 400; color: var(--ink);
  margin-bottom: 0.3rem;
}
.notice.is-unreachable  .notice-title { color: var(--crit-ink); }
.notice.is-unconfigured .notice-title { color: var(--warn-ink); }

.notice-hint {
  font-size: 13px; font-weight: 400; color: var(--ink-mid);
  line-height: 1.45;
}
.notice-when {
  font-family: var(--mono); font-size: 9.5px;
  color: var(--ink-dim); margin-top: 0.6rem;
}

/* ── per-module age + refresh (spec §7.4) ────────────────────────── */

.module-meta { display: flex; align-items: baseline; gap: 0.9rem; }

.module-age {
  font-family: var(--mono); font-size: 9.5px;
  color: var(--ink-dim); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.module-age.is-stale { color: var(--warn-ink); }
.module-age.is-error { color: var(--crit-ink); }

.module-refresh {
  background: none; border: 0; padding: 0.25rem 0;
  font-family: var(--mono); font-size: 9.5px;
  color: var(--ink-dim); cursor: pointer;
  opacity: 0; transition: opacity 120ms linear, color 120ms linear;
}
.module:hover .module-refresh,
.module-refresh:focus-visible { opacity: 1; }
.module-refresh:hover { color: var(--accent); }
.module-refresh.is-busy { opacity: 1; color: var(--accent); cursor: wait; }
.module-refresh:disabled { cursor: wait; }

@media (max-width: 820px) {
  /* No hover on touch — the control has to be permanently visible. */
  .module-refresh { opacity: 1; padding: 0.5rem 0; min-height: 44px; }
  .module-meta { gap: 1.1rem; }
}

/* ── Gate 3 exemption (spec: MAINTENANCE-MODE and similar) ───────── */
/* Deliberately not the accent: a project allowed to be quiet is not a fault,
   and spending the alarm colour on it teaches the eye to ignore the alarm. */
.spec-val.is-exempt { color: var(--ink-mid); letter-spacing: 0.1em; }
.flag.is-exempt {
  border-left-color: var(--ink-dim);
  background: var(--bg-inset);
  color: var(--ink-mid);
  cursor: help;
}

/* Project name in the Daily Focus list. Not a spec key -- it is a label on a
   row of real content, so it reads as a word rather than a column header. */
.focus-project {
  font-size: 11.5px; color: var(--ink-dim);
  min-width: 92px; flex: none; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}

/* ── card interiors ──────────────────────────────────────────────── */

/* A module viewed on its own spans all twelve columns, which pushes prose
   and key/value rows to ~1300px. Cap the reading measure so a label and its
   value stay associated and sentences stay scannable. */
html[data-view="projects"] .module--projects .module-body,
html[data-view="pipeline"] .module--pipeline .module-body { max-width: 940px; }

.status-line,
.entry-note,
.notice-hint { max-width: 68ch; }

.status-card { padding: 1.15rem 0; }
.status-card:first-child { padding-top: .25rem; }

.status-name {
  font-size: 14.5px; font-weight: 600; letter-spacing: -0.01em;
}
.status-line { font-size: 13px; color: var(--ink-mid); margin-bottom: .65rem; }

/* Metadata rows are a small table, so they keep the mono value and the
   hairline -- but they no longer span the whole card. */
.status-card .spec-row { padding: .35rem 0; }
.status-card .spec-row:last-of-type { border-bottom: 0; }

.stage { padding: .5rem 0; }
.stage-name { font-size: 11px; letter-spacing: .12em; }
.entry { padding: .35rem 0 .85rem 2.1rem; }
.entry-name { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; }
.entry-note { font-size: 13px; margin-top: .2rem; }
.entry .spec-row { padding: .3rem 0; border-bottom: 0; }

/* The flag reads as a note attached to the card, not a banner across it. */
.flag { max-width: 68ch; border-radius: 0 var(--r-ctl) var(--r-ctl) 0; }

/* ── phone ───────────────────────────────────────────────────────── */

@media (max-width: 1080px) {
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Two-up on a phone: capacity takes the full width and the row span goes,
     or it would try to hold a column open beside the tiles. */
  .stat--cap { grid-column: span 2; grid-row: auto; }
  .stats .stat:not(.stat--cap) { grid-column: span 1; }
}

@media (max-width: 820px) {
  .grid { padding: .85rem; gap: .85rem; }
  .module { padding: 1rem 1rem 1.15rem; }
  .topbar { padding: 1.1rem 1rem .9rem; }
  .chrome { padding: .6rem 1rem; }
  .foot { padding: .85rem 1rem 1.5rem; }

  /* Four tiles at 375 wrap every label onto three lines. Two up, and the
     supporting note is the first thing to go -- the number and its state
     dot carry the meaning. */
  .stats { gap: .6rem; }
  .stat { padding: .7rem .8rem; }
  .stat-v { font-size: 21px; }
  .stat-n { display: none; }

  /* The project column was taking a quarter of the screen. On a phone the
     task itself is what matters; the project moves under it. */
  .today-list .spec-row { flex-wrap: wrap; row-gap: .15rem; }
  /* Title on its own line; project and due share the line beneath it, so a
     row costs two lines rather than three. */
  .today-list .focus-title { order: 1; flex-basis: 100%; }
  .focus-project { min-width: 0; order: 2; flex: 1; }
  .today-list .spec-val { order: 3; flex: none; }
}

/* Task title inside the Daily Focus list. */
.focus-title { flex: 1; font-size: 13px; line-height: 1.4; min-width: 0; }

/* ── view controls, right-aligned on the date row ────────────────── */
/* Treatment A at size M in a pill, per the sizing study. The active
   fill is neutral: the accent means identity and primary action, and
   giving it a third job ("which view am I in") would dilute both. */

.viewctl { display: flex; align-items: center; gap: .35rem; }

/* Each toggle carries both glyphs and shows the one for what a click will do,
   so the control needs no pressed state and no JS icon swapping. The button
   itself is a plain .icon-btn — same 32px box and same hover as the sidebar
   collapse, which is the point. */
.icon-btn .ic { display: none; }
.icon-btn .ic-moon,                                   /* light now -> offer dark */
:root[data-theme="dark"] .icon-btn .ic-sun { display: block; }
:root[data-theme="dark"] .icon-btn .ic-moon { display: none; }

.icon-btn .ic-focus,                                  /* grid now -> offer focus */
:root[data-structure="focus"] .icon-btn .ic-grid { display: block; }
:root[data-structure="focus"] .icon-btn .ic-focus { display: none; }

.icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

@media (max-width: 820px) {
  /* 32px is under a comfortable touch target, so the phone gets a larger
     version rather than a control nobody can hit. */
  .icon-btn { width: 44px; height: 44px; }
  .topbar { align-items: flex-start; }
}

/* ── structure: overview grid vs focused view ────────────────────── */
/*
   Grid is the morning dashboard: summary tiles and four panels at once.
   Focus is how the two apps Zach never dropped actually behave -- one
   thing, full width, and the counts live in the sidebar instead of
   taking a row of their own.

   Only the Overview screen differs. The single-module views the nav
   already provides are, by definition, focused in both modes.
*/

.focus-only { display: none; }

html[data-structure="focus"] .focus-only { display: flex; }

html[data-structure="focus"][data-view="overview"] .stats { display: none; }
html[data-structure="focus"][data-view="overview"] .module--tasks,
html[data-structure="focus"][data-view="overview"] .module--projects,
html[data-structure="focus"][data-view="overview"] .module--pipeline { display: none; }
html[data-structure="focus"][data-view="overview"] .module--focus { grid-column: span 12; }

/* Cap the PANEL, not its contents. Capping the inner list left a band of
   empty card to the right of every row, which reads as a layout bug rather
   than as a measure. */
html[data-structure="focus"][data-view="overview"] .module--focus { max-width: 1000px; }

/* ── Daily Focus rows get the same card language ─────────────────── */
/* Same visual grammar as the Tasks module, so the two lists do not read
   as two different products. */
.today-list { display: flex; flex-direction: column; gap: .4rem; }
.today-list .spec-row {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: .6rem .75rem;
  gap: .75rem;
  transition: border-color 140ms linear;
}
.today-list .spec-row:hover { border-color: var(--ink-dim); }
.today-list .spec-row:last-child { border-bottom: 1px solid var(--line); }
.focus-title { font-size: 14.5px; line-height: 1.35; }

/* Priority is a ring in the Tasks module; the P1/P2 labels beside it were
   saying the same thing twice. */
.task-pri { display: none; }

/* ══ review: proposals + sunday digest ═══════════════════════════════
   Spec: 02_forge/dashboard/2026-08-30-intake-evaluation-loop.md.
   No new tokens — the severity ramp already covers everything here, and
   an area is identity rather than severity, so it takes no colour at all. */

.rev-cap {
  display: flex; align-items: center; gap: .75rem;
  padding-bottom: .75rem; margin-bottom: .25rem;
}

/* Outlined pill, same language as .task-due. State only — never an area. */
.rev-pill {
  display: inline-flex; align-items: center;
  border: 1px solid var(--line); border-radius: 20px;
  padding: .15rem .55rem; font-size: 11px; font-weight: 500;
  color: var(--ink-mid); white-space: nowrap; flex: none;
}
.rev-pill.is-warn { color: var(--warn-ink); border-color: var(--warn-line); background: var(--warn-soft); }
.rev-pill.is-over { color: var(--over-ink); border-color: var(--over-line); background: var(--over-soft); }
/* Pending sync is a neutral fact, not a severity: nothing is wrong, the
   agent simply has not run yet. Colouring it would teach the ramp to lie. */
.rev-pill.is-sync { color: var(--ink-mid); background: var(--bg-inset); }

.rev-hint { font-size: 11.5px; color: var(--ink-dim); }

/* "Nothing qualified" is a healthy outcome and has to read as a sentence.
   The shared .empty is mono, 10px and letter-spaced — right for a system
   state like "never synced", wrong here: in that voice a good week looks
   like an error, which is exactly what rule 3 forbids. */
.rev-empty {
  padding: 1.1rem 0 .35rem;
  font-size: 13.5px; line-height: 1.55; color: var(--ink);
}
.rev-empty small { display: block; margin-top: .2rem; font-size: 12px; color: var(--ink-dim); }

.prop { padding: 1.1rem 0; border-bottom: 1px solid var(--line-soft); }
.prop:first-child { padding-top: .35rem; }
.prop:last-child { border-bottom: 0; }

.prop-top { display: flex; align-items: flex-start; gap: .75rem; margin-bottom: .5rem; }
.prop-change {
  margin: 0; flex: 1; min-width: 0;
  font-size: 14px; font-weight: 500; line-height: 1.45; color: var(--ink);
}

.rev-spec { display: flex; flex-wrap: wrap; gap: .2rem 1.25rem; margin-bottom: .75rem; }
.rev-spec > div { display: flex; align-items: baseline; gap: .5rem; }
.rev-spec .spec-val { font-family: var(--mono); font-size: 12px; color: var(--ink-mid); }

.rev-acts { display: flex; flex-wrap: wrap; gap: .5rem; }
.ghost-btn.is-danger { color: var(--crit-ink); border-color: var(--crit-line); }
.ghost-btn.is-danger:hover:not(:disabled) { background: var(--crit-soft); border-color: var(--crit); }
.rev-acts button:disabled { cursor: default; }
/* A disabled primary must not stay a filled accent button. At 45% opacity it
   still reads as the loudest thing on the row, pulling the eye to the one
   control that cannot be used — on a row whose whole message is "already
   decided, waiting on the agent". Disabled drops to the ghost treatment. */
.rev-acts .accent-btn:disabled {
  background: var(--bg-raised); border-color: var(--line); color: var(--ink-dim);
}
.rev-acts .ghost-btn:disabled { color: var(--ink-dim); border-color: var(--line-soft); }

.rev-notice {
  padding: .6rem .75rem; margin: 0 0 .8rem;
  border: 1px solid var(--line); background: var(--bg-inset);
  border-radius: var(--r-ctl);
  font-size: 12.5px; line-height: 1.5; color: var(--ink-mid);
}
.rev-notice b { color: var(--ink); font-weight: 600; }
.rev-reason { margin-top: .4rem; color: var(--ink); font-style: italic; }

.rev-reason-form { margin-top: .7rem; }
.rev-reason-form textarea {
  width: 100%; min-height: 58px; resize: vertical;
  font-family: var(--sans); font-size: 13px; line-height: 1.5;
  color: var(--ink); background: var(--bg-inset);
  border: 1px solid var(--line); border-radius: var(--r-ctl);
  padding: .55rem .7rem;
}
.rev-reason-form textarea:focus-visible { outline: 1px solid var(--accent); border-color: var(--accent); }
.rev-reason-foot { display: flex; align-items: center; gap: .6rem; margin-top: .5rem; flex-wrap: wrap; }

/* ── digest ──────────────────────────────────────────────────────── */

.dig-grp { margin-bottom: 1.4rem; }
.dig-grp:last-child { margin-bottom: 0; }
.dig-grp-head {
  display: flex; align-items: center; gap: .6rem;
  padding-bottom: .45rem; border-bottom: 1px solid var(--line);
}
.dig-grp-head h3 { margin: 0; font-size: 12.5px; font-weight: 600; }
.dig-count { margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--ink-dim); }

.dig-row {
  /* Top-aligned, not centred: a row is a title with a slug under it, and
     centring the marker against the whole two-line block floats it into the
     gap between them. It belongs against the title, which is the row. */
  display: flex; align-items: flex-start; gap: .7rem;
  padding: .55rem 0; border-bottom: 1px solid var(--line-soft);
}
.dig-row:last-child { border-bottom: 0; }

/* The digest's marker slot. Everywhere else in the app a read-only row gets a
   grey rounded square; here it is an outline bulb, because these rows are not
   just read-only — they are ideas nobody has acted on, and that is the fact
   worth carrying. Outline, never filled: a filled glyph in the checkbox slot
   reads as "completed" regardless of what shape it is. See CLAUDE.md. */
/* Aligned to the title's CAP HEIGHT, not to its line box.
   The title is 13px Inter on a 19.5px line: half-leading 1.88px, baseline at
   14.48px, cap top at 14.48 - (0.727 x 13) = 5.03px from the top of the line
   box. The bulb's topmost drawn point sits at y=2.5 in a 24 viewBox, which is
   1.88px down inside an 18px render — plus half a stroke, because a stroked
   path paints outward from its centreline and the outer edge is what the eye
   aligns to. 5.03 - 1.88 + 0.75 = 3.9px, rounded to 4.
   Verified by pixel, not by eye: sampled from a render, the bulb's topmost
   painted row now lands on the title's cap row exactly. At 3px it sat one
   pixel high, which is precisely that half-stroke. */
.dig-mark {
  flex: none; display: flex; align-items: flex-start; justify-content: center;
  width: 18px; height: 18px; margin-top: 4px;
  color: var(--ink-dim);
}
.dig-row:hover .dig-mark { color: var(--warn); }
.dig-main { flex: 1; min-width: 0; }
.dig-title { display: block; font-size: 13px; color: var(--ink); }
.dig-meta { display: block; font-family: var(--mono); font-size: 11px; color: var(--ink-dim); margin-top: .1rem; }

.rev-retag {
  flex: none; font-family: var(--sans); font-size: 11.5px; font-weight: 500;
  color: var(--ink-mid); background: var(--bg-inset);
  border: 1px solid var(--line); border-radius: 20px; padding: .2rem .5rem;
}

/* Held rows are the boundary rule made visible. A dashed outline rather than
   the solid read-only marker: there is no item here to read, only a count. */
.dig-row.is-held .dig-title { color: var(--ink-mid); }
/* Same cap-height rule, different glyph geometry: the dashed square is drawn
   edge to edge, so it takes the full 5px rather than the bulb's 3px. */
.dig-lock {
  flex: none; width: 16px; height: 16px; margin-top: 5px; border-radius: 5px;
  border: 1px dashed var(--ink-dim); background: transparent; opacity: .8;
}

/* On a phone the title is the row; a select sharing its line squeezes it to
   three wrapped lines. Below 560px the control drops underneath instead. */
@media (max-width: 560px) {
  /* Only the control wraps. The marker stays on the title's line — it says
     what kind of row this is, and a marker floating above its own row is
     worse than the squeeze it was meant to fix. */
  .dig-row { flex-wrap: wrap; }
  .rev-retag { flex: 0 0 auto; width: auto; margin: .35rem 0 0 1.55rem; }
  .rev-retag::before { content: ""; }
  .dig-row > .rev-retag { order: 3; }
  .dig-row::after { content: ""; flex-basis: 100%; order: 2; }
  .rev-reason-foot { gap: .5rem .6rem; }
}

/* ── archive ─────────────────────────────────────────────────────── */

.arc-tallies { display: flex; flex-wrap: wrap; gap: .5rem; padding: .35rem 0 .9rem; }
.arc-tally {
  flex: 1; min-width: 118px;
  background: var(--bg-inset); border: 1px solid var(--line);
  border-radius: var(--r-ctl); padding: .55rem .7rem;
}
.arc-tally-k { font-size: 11.5px; color: var(--ink-dim); }
.arc-tally-v {
  font-size: 21px; font-weight: 600; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; line-height: 1.2;
}
.arc-tally-n { font-size: 11px; color: var(--ink-dim); }
.arc-tally.is-direct .arc-tally-v { color: var(--ok-ink); }
/* Held is not a fault, so it is not on the severity ramp. Amber says "this
   number is about you needing to be somewhere else", which is what it means. */
.arc-tally.is-held .arc-tally-v { color: var(--warn-ink); }

.arc-area {
  flex: none; font-family: var(--mono); font-size: 11px; color: var(--ink-dim);
  width: 104px; text-align: right; margin-top: 3px;
}
.arc-tier {
  flex: none; font-size: 11px; font-weight: 500; line-height: 1;
  border: 1px solid var(--line); border-radius: 20px; padding: .25rem .55rem;
  color: var(--ink-mid); white-space: nowrap; margin-top: 2px;
}
.arc-tier.direct { color: var(--ok-ink); border-color: var(--ok-line); background: var(--ok-soft); }

.arc-foot {
  margin-top: 1.1rem; padding-top: .8rem; border-top: 1px solid var(--line);
  font-size: 12px; line-height: 1.55; color: var(--ink-dim);
}

@media (max-width: 560px) {
  .arc-area { width: auto; text-align: left; margin-left: 1.55rem; }
}
