/* ===================================================================
   Mann Dashboard — visual system
   -------------------------------------------------------------------
   Direction: a console, not a report. The chrome is deliberately
   near-neutral graphite because the only colour on the page that
   carries meaning is an agent's accent — whose data am I looking at.
   Two voices of type: a heavy, tightly-tracked system sans for names
   and numbers, and a wide-tracked monospace for every label the app
   writes itself (PINNED, COMING UP, times, table headers, agent
   bylines). Nothing here loads from the network.
   =================================================================== */

:root {
  color-scheme: light dark;

  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, monospace;

  /* surfaces & ink — light */
  --bg: #eef0f3;
  --bg-2: #e4e7ec;
  --panel: #fbfcfd;
  --panel-2: #f1f3f6;
  --text: #10151b;
  --text-2: #39434f;
  --muted: #667487;
  --border: #d9dfe7;
  --hairline: rgba(16, 21, 27, .09);
  --shadow: 0 1px 1px rgba(16, 21, 27, .04), 0 10px 26px -16px rgba(16, 21, 27, .30);
  --ring: rgba(42, 120, 214, .45);

  --accent-default: #2a78d6;
  --accent: var(--accent-default);
  --good: #06760a;
  --bad: #c0342f;
  --err: #b32b26;

  /* chart series — the validated categorical order, light steps.
     Slots are assigned in this order and never cycled. */
  --s1: #2a78d6; --s2: #eb6834; --s3: #1baf7a; --s4: #eda100;
  --s5: #e87ba4; --s6: #008300; --s7: #4a3aa7; --s8: #e34948;
  --grid-line: #e3e7ec;
  --axis-line: #c6ced8;

  --r: 14px;
  --r-sm: 9px;
  --gap: 1rem;
  --rail-x: 4.5rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0c1014;
    --bg-2: #10151a;
    --panel: #14181d;
    --panel-2: #1a2028;
    --text: #e9edf2;
    --text-2: #c2cbd6;
    --muted: #8494a6;
    --border: #242c36;
    --hairline: rgba(233, 237, 242, .10);
    --shadow: 0 1px 1px rgba(0, 0, 0, .40), 0 14px 34px -18px rgba(0, 0, 0, .85);
    --ring: rgba(57, 135, 229, .55);

    --accent-default: #3987e5;
    --good: #0ca30c;
    --bad: #e66767;
    --err: #e88a86;

    --s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500;
    --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;
    --grid-line: #212932;
    --axis-line: #313b46;
  }
}

/* ---------- base ---------- */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 15px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  /* clip, not hidden: hidden would make the body a scroll container and put
     the sticky top bar at risk. Wide content is expected to scroll inside its
     own box (.table-scroll, .chart-wrap); this is the backstop. */
  overflow-x: clip;
}

a { color: inherit; }
button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip {
  position: absolute; left: .5rem; top: -4rem; z-index: 200;
  background: var(--panel); border: 1px solid var(--border);
  padding: .5rem .75rem; border-radius: var(--r-sm); text-decoration: none;
  transition: top .15s ease;
}
.skip:focus { top: .5rem; }

/* The label voice: everything the app says about itself. */
.eyebrow {
  margin: 0;
  font: 500 .6875rem/1.4 var(--mono);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
  display: flex; align-items: center; gap: .5rem;
}
.eyebrow .dot {
  width: 7px; height: 7px; border-radius: 2px;
  background: var(--accent); flex: none;
}

/* ---------- top bar ---------- */

#topbar {
  position: sticky; top: 0; z-index: 60;
  display: flex; align-items: center; gap: .5rem;
  padding: .55rem max(.75rem, env(safe-area-inset-left)) .55rem max(.75rem, env(safe-area-inset-right));
  /* Enough tint that content scrolling underneath reads as texture, not as
     half-legible text sitting on top of the bar.
     The opaque declaration first is not redundant: Safari 16.0–16.1 has
     backdrop-filter but NOT color-mix, so the @supports test below passes while
     the tint itself is dropped as an invalid value — which left the bar fully
     transparent with the page scrolling through it. A plain background ahead of
     it means the cascade degrades to opaque instead of to nothing. */
  background: var(--bg);
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  backdrop-filter: saturate(1.6) blur(14px);
  border-bottom: 1px solid var(--hairline);
}
@supports not (backdrop-filter: blur(1px)) { #topbar { background: var(--bg); } }

/* Only on screen when a refresh has failed under a snapshot already showing:
   the app's own voice, the failure colour, and a button because it retries. */
.conn {
  flex: none; display: inline-flex; align-items: center;
  max-width: 12rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding: .3rem .6rem; cursor: pointer;
  background: var(--panel);
  border: 1px solid var(--err); border-radius: 999px;
  color: var(--err);
  font: 500 .6875rem/1.4 var(--mono); letter-spacing: .04em;
}
.conn[hidden] { display: none; }
.conn:hover { background: var(--panel-2); }

#site-title {
  display: flex; align-items: center; gap: .5rem;
  font: 600 .6875rem/1 var(--mono);
  letter-spacing: .2em; text-transform: uppercase;
  text-decoration: none; color: var(--text);
  padding: .35rem .25rem; white-space: nowrap;
}
#site-title .mark {
  width: 10px; height: 10px; border-radius: 3px;
  background: var(--accent-default);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-default) 20%, transparent);
}

.spacer { flex: 1 1 auto; }

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem; flex: none;
  background: transparent; border: 1px solid transparent;
  border-radius: var(--r-sm); color: var(--muted);
  cursor: pointer; font-size: .95rem;
}
.icon-btn:hover { color: var(--text); background: var(--panel-2); }
/* Every color-mix() background in this file carries a plain one in front of it:
   where color-mix is unsupported the whole declaration is dropped, and a state
   that is signalled by its fill would otherwise show no state at all. */
.icon-btn[aria-pressed="true"] {
  color: var(--accent-default); border-color: var(--accent-default);
  background: var(--panel-2);
  background: color-mix(in srgb, var(--accent-default) 12%, transparent);
}
#menu-btn { display: none; }
.bars, .bars::before, .bars::after {
  display: block; width: 15px; height: 1.5px; background: currentColor; border-radius: 2px;
}
.bars { position: relative; }
.bars::before, .bars::after { content: ''; position: absolute; left: 0; }
.bars::before { top: -5px; }
.bars::after { top: 5px; }

#nav { display: flex; align-items: center; gap: .125rem; flex-wrap: wrap; }
.nav-link {
  position: relative;
  display: block; padding: .45rem .6rem;
  font: 450 .8125rem/1.2 var(--mono); letter-spacing: .01em;
  color: var(--muted); text-decoration: none; border-radius: var(--r-sm);
  /* A section title can be arbitrarily long; in the bar it truncates (the
     full name is in the tooltip), in the mobile panel it wraps. */
  white-space: nowrap; max-width: 13rem; overflow: hidden; text-overflow: ellipsis;
}
.nav-link:hover { color: var(--text); background: var(--panel-2); }
.nav-link.active { color: var(--text); }
.nav-link.active::after {
  content: ''; position: absolute; left: .6rem; right: .6rem; bottom: .05rem;
  height: 2px; border-radius: 2px; background: var(--accent);
}

/* ---------- arrange bar ---------- */

#arrange-bar {
  position: sticky; top: 3.4rem; z-index: 55;
  display: flex; align-items: center; gap: 1rem;
  padding: .5rem 1rem;
  background: var(--bg-2);
  background: color-mix(in srgb, var(--accent-default) 14%, var(--bg));
  border-bottom: 1px solid var(--border);
  border-bottom: 1px solid color-mix(in srgb, var(--accent-default) 35%, transparent);
  font: 500 .75rem/1.4 var(--mono); letter-spacing: .04em;
  color: var(--text-2);
}
#arrange-bar[hidden] { display: none; }
.ghost {
  margin-left: auto; padding: .3rem .7rem; cursor: pointer;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px;
  font: 500 .75rem/1.4 var(--mono);
}
.ghost:hover { border-color: var(--accent-default); }

/* ---------- login gate ---------- */

.gate {
  min-height: 100dvh;
  display: grid; place-items: center; padding: 1.5rem;
  background: var(--bg);
  background:
    radial-gradient(120% 80% at 50% -20%, color-mix(in srgb, var(--accent-default) 14%, transparent), transparent 70%),
    var(--bg);
}
.gate-card {
  width: min(26rem, 100%);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  padding: 2rem 1.75rem 1.75rem;
}
.gate-card h1 {
  margin: .75rem 0 .5rem;
  font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.1rem);
  font-weight: 750; letter-spacing: -.03em; line-height: 1.1;
}
.gate-sub { margin: 0 0 1.5rem; color: var(--muted); font-size: .9375rem; }

.field { display: block; }
.field .eyebrow { margin-bottom: .4rem; }
.field input, .field textarea, .field select {
  width: 100%; margin: 0;
  padding: .65rem .75rem;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
}
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--ring);
}
#gate-form button {
  width: 100%; margin-top: 1rem; padding: .7rem 1rem; cursor: pointer;
  background: var(--accent-default); color: #fff;
  border: 0; border-radius: var(--r-sm);
  font: 600 .875rem/1.4 var(--sans); letter-spacing: .01em;
}
#gate-form button:hover { filter: brightness(1.08); }
#gate-form button:disabled { opacity: .6; cursor: progress; }

.err { color: var(--err); font-size: .875rem; }
#gate-err { margin: .9rem 0 0; }

/* ---------- main layout ---------- */

#main {
  max-width: 1280px; margin: 0 auto;
  padding: 1.5rem max(1rem, env(safe-area-inset-left)) 5rem max(1rem, env(safe-area-inset-right));
}
#main:focus { outline: none; }

.page-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem 1rem;
  margin: .5rem 0 1.75rem;
}
.page-head .eyebrow { flex-basis: 100%; }
/* Titles, labels and agent names are written by agents. Any of them can be a
   single 240-character word, and none of them may push the page sideways. */
.page-title, .eyebrow, .section-label, .card-body { overflow-wrap: anywhere; }
.page-title {
  margin: 0; min-width: 0;
  font-size: clamp(2rem, 1.4rem + 2.6vw, 3rem);
  font-weight: 760; letter-spacing: -.035em; line-height: 1.02;
}
.page-icon { margin-right: .4rem; font-weight: 400; letter-spacing: 0; }

.section-label {
  display: flex; align-items: baseline; gap: .75rem;
  margin: 2.25rem 0 .85rem;
  font: 600 .6875rem/1.4 var(--mono);
  letter-spacing: .18em; text-transform: uppercase; color: var(--text-2);
}
/* The rule runs out to the margin after the label and its count, so the two
   read as one line rather than as a heading with a stray note beside it. */
.section-label > span:first-child { order: 0; }
.section-note { order: 1; color: var(--muted); font-weight: 400; letter-spacing: .08em; }
.section-label::after {
  content: ''; order: 2; flex: 1 1 auto; height: 1px; background: var(--hairline);
}

.empty {
  margin: 0 0 1rem; color: var(--muted); font-size: .9375rem;
}

/* tabs */
.tabs {
  display: flex; gap: .25rem; margin: -.75rem 0 1.25rem;
  overflow-x: auto; scrollbar-width: none;
  border-bottom: 1px solid var(--hairline);
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  position: relative; flex: none;
  padding: .5rem .85rem .6rem;
  font: 450 .8125rem/1.4 var(--mono);
  color: var(--muted); text-decoration: none; white-space: nowrap;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--text); }
.tab.active::after {
  content: ''; position: absolute; left: .5rem; right: .5rem; bottom: -1px;
  height: 2px; background: var(--accent); border-radius: 2px;
}

/* ---------- cards ---------- */

/* The 12-column grid.
   A widget's `span` arrives from /api/view already resolved — lib/declare.php
   has turned v1's config.size, an explicit span and the per-type default into
   one integer — so nothing here, and nothing in app.js, reads config.size. The
   two are allowed to disagree once a widget has been re-spanned; span wins. */
.grid {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(12, minmax(0, 1fr));
  align-items: start;
}

.card {
  position: relative;
  grid-column: span var(--span, 6);
  display: flex; flex-direction: column;
  min-width: 0;
  /* Widgets lay themselves out against the card they are in, not the window:
     the same calendar has to work in a full-width row and in one grid column. */
  container-type: inline-size;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  overflow: hidden;
}
/* The accent rail: a section's colour, on the edge, never washing the card. */
.card::before {
  content: ''; position: absolute; inset: 0 auto 0 0; width: 3px;
  background: var(--accent);
}
.spine-card::before { background: none; }

.card-head {
  display: flex; align-items: center; gap: .5rem;
  padding: .85rem 1rem .1rem 1.15rem;
}
.card-head h3 {
  margin: 0; min-width: 0;
  font-size: .9375rem; font-weight: 640; letter-spacing: -.01em;
  overflow-wrap: anywhere;
}
.card-body { padding: .75rem 1rem 1rem 1.15rem; min-width: 0; }
.card-head + .card-body { padding-top: .6rem; }

.card-src {
  display: flex; align-items: center; gap: .45rem;
  margin-top: auto;
  padding: .55rem 1rem .65rem 1.15rem;
  border-top: 1px solid var(--hairline);
  font: 450 .6875rem/1.4 var(--mono); letter-spacing: .08em; text-transform: uppercase;
}
.card-src .dot { width: 6px; height: 6px; border-radius: 2px; background: var(--accent); flex: none; }
.card-src a { color: var(--muted); text-decoration: none; }
.card-src a:hover { color: var(--text); }

/* arrange mode */
.tools { display: flex; gap: .2rem; margin-left: auto; flex: none; }
.tool {
  width: 1.6rem; height: 1.6rem; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel-2); color: var(--muted);
  border: 1px solid var(--border); border-radius: 6px;
  font-size: .75rem; line-height: 1;
}
.tool:hover { color: var(--text); border-color: var(--accent); }
.tool.on {
  color: var(--accent-default); border-color: var(--accent-default);
  background: var(--panel-2);
  background: color-mix(in srgb, var(--accent-default) 14%, transparent);
}
body.arrange .card { border-style: dashed; }
body.arrange .page-head .tools { align-self: center; }

/* ---------- stat ---------- */

.stat-value {
  display: flex; align-items: baseline; gap: .3rem; flex-wrap: wrap;
  margin: .35rem 0 0;
  font-size: clamp(2.1rem, 1.5rem + 2.2vw, 2.9rem);
  font-weight: 720; letter-spacing: -.045em; line-height: 1;
  /* At this tracking, proportional digits make a number that updates jitter
     sideways under its own label. */
  font-variant-numeric: tabular-nums;
}
.stat-unit { font-size: .4em; font-weight: 500; letter-spacing: 0; color: var(--muted); }
.stat-delta {
  display: inline-flex; align-items: center; gap: .3rem;
  margin-top: .6rem;
  font: 500 .8125rem/1 var(--mono); letter-spacing: .02em;
}
/* Direction is the arrow's job. Colour only appears once an agent has said
   which direction it was hoping for (`better`), because sign is not polarity. */
.stat-delta { color: var(--text-2); }
.stat-delta.flat { color: var(--muted); }
.stat-delta.pol-good { color: var(--good); }
.stat-delta.pol-bad { color: var(--bad); }
.stat-delta-note { color: var(--muted); letter-spacing: .06em; }
.stat-label { margin-top: .5rem; color: var(--muted); font-size: .875rem; }

.chart-note {
  margin: .5rem 0 0;
  font: 500 .6875rem/1.4 var(--mono); letter-spacing: .04em;
  color: var(--muted);
}

/* ---------- charts ---------- */

/* A chart's height follows the card it sits in, not the window: at 1280px the
   same widget can be a quarter of a row or the whole of it, and only the card
   knows which. This was three span-class rules and a media query in v1. */
.chart-wrap { position: relative; height: 15rem; min-width: 0; }
@container (min-width: 34rem) { .chart-wrap { height: 18rem; } }
@container (max-width: 22rem) { .chart-wrap { height: 13.5rem; } }

/* ---------- table ---------- */

.table-scroll { overflow-x: auto; margin: 0 -1rem 0 -1.15rem; padding: 0 1rem 0 1.15rem; }
/* The table keeps its natural column widths and the wrapper scrolls; squashing
   "MN-2026-041" into three lines to fit a phone helps nobody. Cells still wrap
   once they get genuinely long. */
.table-scroll table {
  border-collapse: collapse; font-size: .875rem;
  width: max-content; min-width: 100%;
}
.table-scroll th, .table-scroll td { max-width: 28rem; }
.table-scroll th {
  position: sticky; top: 0;
  text-align: left; white-space: nowrap; cursor: pointer;
  padding: .4rem .7rem .5rem 0;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  font: 500 .6875rem/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted);
}
.table-scroll th:hover { color: var(--text); }
.table-scroll th .sort { color: var(--accent); }
.table-scroll td {
  padding: .45rem .7rem .45rem 0;
  border-bottom: 1px solid var(--hairline);
  vertical-align: top;
}
.table-scroll th.num, .table-scroll td.num { text-align: right; padding-right: 1.1rem; }
.table-scroll td.num { font-variant-numeric: tabular-nums; }
.table-scroll tr:last-child td { border-bottom: 0; }
.table-scroll tbody tr:hover td { background: var(--panel-2); }

/* ---------- calendar: the time spine ---------- */

/* The measure is capped so the answer buttons stay near the event they answer,
   however wide the card gets. */
.cal { position: relative; padding-top: .25rem; max-width: 62rem; }
.cal-rail {
  position: absolute; left: var(--rail-x); top: 1.5rem; bottom: .6rem;
  width: 2px; border-radius: 2px;
  /* Without the plain rail first, a browser that lacks color-mix drops the
     whole gradient and the time spine — the one bold element on the page —
     simply is not drawn. */
  background: var(--axis-line);
  background: linear-gradient(var(--axis-line) 85%, color-mix(in srgb, var(--axis-line) 10%, transparent));
}

.chips { display: flex; flex-wrap: wrap; gap: .35rem; margin: 0 0 1rem; }
.chip {
  padding: .25rem .6rem; cursor: pointer;
  background: var(--panel-2); color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px;
  font: 450 .6875rem/1.5 var(--mono); letter-spacing: .06em;
  white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.chip:hover { color: var(--text); }
.chip.on {
  color: var(--text); border-color: var(--accent);
  background: var(--panel-2);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
}
.chip.sm {
  cursor: default; padding: .1rem .45rem; font-size: .625rem;
  white-space: normal; overflow: visible;
}

.ev-day {
  position: relative;
  margin: 1.15rem 0 .5rem;
  padding-left: calc(var(--rail-x) + 1.15rem);
  font: 600 .6875rem/1.4 var(--mono);
  letter-spacing: .16em; text-transform: uppercase; color: var(--text-2);
}
.ev-day:first-child { margin-top: .25rem; }
.ev-day::before {
  content: ''; position: absolute; left: calc(var(--rail-x) - 3px); top: .15rem;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--panel); border: 2px solid var(--axis-line);
}
.ev-day .wk { color: var(--muted); font-weight: 400; margin-left: .5rem; letter-spacing: .1em; }

.ev-row {
  position: relative;
  display: grid;
  grid-template-columns: var(--rail-x) 1.15rem minmax(0, 1fr) auto;
  align-items: baseline;
  padding: .3rem 0;
  border-radius: var(--r-sm);
}
.ev-row:hover { background: var(--panel-2); }
.ev-time {
  grid-column: 1; justify-self: end; padding-right: .75rem;
  font: 450 .75rem/1.5 var(--mono); font-variant-numeric: tabular-nums;
  color: var(--muted); white-space: nowrap;
}
.ev-dot {
  grid-column: 2; justify-self: start;
  width: 9px; height: 9px; margin-left: -3.5px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--panel);
  align-self: center;
}
.ev-main { grid-column: 3; min-width: 0; }
.ev-title { font-weight: 550; letter-spacing: -.005em; overflow-wrap: anywhere; }
a.ev-title { text-decoration: none; border-bottom: 1px solid var(--hairline); }
a.ev-title:hover { border-bottom-color: var(--accent); }
.ev-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .6rem;
  margin-top: .15rem;
  font: 400 .75rem/1.5 var(--sans); color: var(--muted);
}
.ev-src { display: inline-flex; align-items: center; gap: .35rem; }
.ev-src::before {
  content: ''; width: 6px; height: 6px; border-radius: 2px; background: var(--accent);
}
.ev-status {
  font: 500 .625rem/1.5 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--bad);
}
.card-body > .empty { margin: .25rem 0 0; }
.ev-past .ev-row { opacity: .55; }
.ev-past .ev-row:hover { opacity: 1; }

.fb-btns { grid-column: 4; display: flex; gap: .2rem; padding-left: .5rem; align-self: center; }
.fb {
  width: 1.75rem; height: 1.75rem; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--muted);
  border: 1px solid transparent; border-radius: 7px;
  font-size: .75rem; line-height: 1;
}
.fb:hover { color: var(--text); background: var(--panel); border-color: var(--border); }
.fb.on {
  color: var(--accent); border-color: var(--accent);
  background: var(--panel-2);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.cal-more { margin-top: 1rem; }

/* In a narrow card the answers drop below the event rather than squeezing the
   title into a two-word column. */
@container (max-width: 34rem) {
  .ev-row { grid-template-columns: var(--rail-x) 1.15rem minmax(0, 1fr); padding-bottom: .45rem; }
  .fb-btns { grid-column: 3; grid-row: 2; padding-left: 0; margin-top: .15rem; justify-content: flex-start; }
  .fb-btns .fb { border-color: var(--hairline); }
}

/* ---------- markdown ---------- */

.md { color: var(--text-2); }
.md > :first-child { margin-top: 0; }
.md > :last-child { margin-bottom: 0; }
.md h3, .md h4, .md h5, .md h6 {
  margin: 1.25rem 0 .4rem; color: var(--text);
  font-weight: 640; letter-spacing: -.015em;
}
.md h3 { font-size: 1.0625rem; }
.md h4 { font-size: .9375rem; }
.md h5, .md h6 {
  font: 600 .6875rem/1.4 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
.md p { margin: 0 0 .75rem; }
.md ul { margin: 0 0 .75rem; padding-left: 1.1rem; }
.md li { margin-bottom: .2rem; }
.md li::marker { color: var(--accent); }
.md code {
  padding: .1em .35em; border-radius: 5px;
  background: var(--panel-2); border: 1px solid var(--hairline);
  font: .875em/1.4 var(--mono);
}
.md a { color: var(--text); text-decoration-color: var(--accent); text-underline-offset: 2px; }
.md strong { font-weight: 650; color: var(--text); }
.md hr {
  margin: 1.1rem 0; border: 0; border-top: 1px solid var(--hairline);
}
/* Scrolls itself rather than widening the card — a table an agent wrote can
   have any number of columns, and the grid cannot give it more room. */
.md-table { margin: 0 0 .85rem; overflow-x: auto; }
.md-table table { border-collapse: collapse; font-size: .875rem; width: max-content; min-width: 100%; }
.md-table th {
  text-align: left; padding: .3rem .7rem .4rem 0;
  border-bottom: 1px solid var(--border);
  font: 500 .6875rem/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
}
.md-table td {
  padding: .4rem .7rem; padding-left: 0; max-width: 24rem;
  border-bottom: 1px solid var(--hairline); vertical-align: top;
}
.md-table tr:last-child td { border-bottom: 0; }
.md-table th.r, .md-table td.r { text-align: right; font-variant-numeric: tabular-nums; }
.md-table th.c, .md-table td.c { text-align: center; }

/* ---------- forms ---------- */

.fb-form { display: grid; gap: .85rem; }
.fb-form .field { display: grid; gap: .35rem; }
.fb-form .check { display: flex; align-items: center; gap: .5rem; }
.fb-form .check input { width: auto; accent-color: var(--accent); }
.fb-form input, .fb-form textarea, .fb-form select {
  width: 100%; padding: .55rem .65rem;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); color: var(--text);
}
.fb-form input:focus, .fb-form textarea:focus, .fb-form select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring);
}
.fb-form textarea { resize: vertical; min-height: 4.5rem; }
.fb-form button, .note-form button {
  justify-self: start; padding: .5rem 1rem; cursor: pointer;
  background: var(--accent); color: #fff; border: 0; border-radius: var(--r-sm);
  font: 600 .8125rem/1.4 var(--sans);
}
.fb-form button:hover, .note-form button:hover { filter: brightness(1.08); }
.fb-form button:disabled, .note-form button:disabled { opacity: .55; cursor: default; }
.sent {
  margin: 0; display: flex; align-items: center; gap: .5rem;
  font: 500 .8125rem/1.5 var(--mono); letter-spacing: .06em; color: var(--good);
}
.sent + .chip { margin-top: .85rem; }
.spine-card .card-body { padding-top: 1.15rem; padding-bottom: 1.25rem; }

.note-box { margin-top: 2.5rem; }
.note-form {
  display: grid; gap: .6rem;
  max-width: 34rem;
  padding: 1rem 1.15rem 1.1rem;
  background: var(--panel-2);
  border: 1px dashed var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r);
}
.note-form .field { display: grid; gap: .4rem; }
.note-form textarea {
  resize: vertical; padding: .55rem .65rem;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-sm); color: var(--text);
}
.note-form textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }

/* ---------- html widget (sandboxed) ---------- */

.html-frame {
  display: block; width: 100%; border: 0;
  min-height: 3rem; background: transparent;
  border-radius: var(--r-sm);
}

/* ---------- flash ---------- */

#flash {
  position: fixed; left: 50%; bottom: 1.25rem; z-index: 200;
  transform: translate(-50%, 1rem);
  padding: .55rem 1rem;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px;
  box-shadow: var(--shadow);
  font: 450 .8125rem/1.4 var(--mono);
  opacity: 0; pointer-events: none;
  transition: opacity .2s ease, transform .2s ease;
  max-width: calc(100vw - 2rem);
}
#flash.on { opacity: 1; transform: translate(-50%, 0); }

/* ---------- motion ---------- */

@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}
#main.entering .card {
  animation: rise .34s cubic-bezier(.22, .8, .3, 1) backwards;
  animation-delay: calc(var(--i, 0) * 28ms);
}
#main.entering .page-head { animation: rise .3s cubic-bezier(.22, .8, .3, 1) backwards; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ---------- responsive ---------- */

@media (max-width: 900px) {
  #menu-btn { display: inline-flex; }
  #nav {
    position: fixed; left: 0; right: 0; top: 3.4rem; z-index: 55;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: .5rem;
    background: var(--panel);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow);
    /* Its own height PLUS the bar it hides behind: a percentage alone leaves
       the last link peeking out above the top bar. */
    transform: translateY(calc(-100% - 3.5rem));
    visibility: hidden;
    transition: transform .22s cubic-bezier(.22, .8, .3, 1), visibility 0s .22s;
    max-height: calc(100dvh - 3.4rem); overflow-y: auto;
  }
  body.nav-open #nav { transform: none; visibility: visible; transition-delay: 0s; }
  .nav-link {
    padding: .7rem .75rem; font-size: .875rem; border-radius: var(--r-sm);
    max-width: none; white-space: normal; overflow: visible; overflow-wrap: anywhere;
  }
  .nav-link.active { background: var(--panel-2); }
  .nav-link.active::after { left: 0; top: .5rem; bottom: .5rem; right: auto; width: 2px; height: auto; }
  #arrange-bar { top: 3.4rem; }
}

@media (max-width: 760px) {
  /* The warning keeps its words; the timestamp behind it is in the tooltip. */
  .conn-at { display: none; }
  :root { --gap: .875rem; --rail-x: 3.25rem; }
  #main { padding-top: 1.15rem; }
  .page-head { margin-bottom: 1.25rem; }
  .section-label { margin-top: 1.75rem; }
}

@media (max-width: 560px) {
  /* The feedback buttons drop under the event rather than squeezing the
     title into a two-word column. */
  .ev-row { grid-template-columns: var(--rail-x) 1.15rem minmax(0, 1fr); padding-bottom: .45rem; }
  .fb-btns { grid-column: 3; grid-row: 2; padding-left: 0; margin-top: .15rem; justify-content: flex-start; }
  .fb-btns .fb { border-color: var(--hairline); }
  .stat-value { font-size: 2.25rem; }
}

@media (min-width: 1400px) {
  #main { max-width: 1360px; }
}

/* ===================================================================
   v2 — sections, the 12-column grid's collapse, depth-3 nav, the
   multi-view calendar, and five new widget types.

   Same system, extended: the two voices of type are unchanged (mono
   for every label the app writes about itself, sans for everything an
   agent wrote), the accent still means "whose data is this", and the
   calendar's time spine is still the instrument — the month grid is a
   way of FINDING a day, and the day it finds is drawn on the same
   rail, with the same rows and the same answer buttons, as before.

   One colour is new: a category's. It appears only as a small solid
   swatch or a 3px edge beside its own name, never as a wash behind
   text, so it can never fight the accent for meaning and can never
   drop text below contrast. Identity here is always name-first.
   =================================================================== */

/* ---------- grid collapse ---------- */

/* The two breakpoints the spec names, as mutually exclusive ranges rather than
   overlapping ones — an attribute selector beating a bare .card on specificity
   is not a thing worth debugging later. */

/* Tablet: a quarter-row card is unreadable at this width, so anything asked to
   be three columns or narrower takes half the row. Wider spans are left alone:
   an 8 at 900px is still a sensible 8. */
@media (min-width: 701px) and (max-width: 1024px) {
  .card[data-span="1"], .card[data-span="2"], .card[data-span="3"] { grid-column: span 6; }
}
/* Phone: one card per row, whatever it asked for. */
@media (max-width: 700px) {
  .card { grid-column: 1 / -1; }
}

/* ---------- Home's pinned band packs ---------- */

/* Home is the glance screen, and the one band on the dashboard whose cards did
   not choose to sit together: they come from as many pages as Jessica starred
   them from. A `span` answers "how much of MY page do I want", a question
   nobody asked on Home — so reading four default sixes literally put two cards
   on a row and pushed the fourth below the fold. This band therefore packs by
   each card's natural minimum, which is v1's rule for Home restored and
   nothing more: one 19rem track per card, as many per row as fit.

   The exception is a widget that asked for MORE THAN HALF a row. Span 7 and up
   is a card that is genuinely wide rather than merely default-sized — a
   calendar at 12 is the real case — and squeezing a month grid into a 19rem
   track to save a row would be the same mistake in the other direction. It
   gets the whole width.

   These rules come after the two collapse breakpoints deliberately: `span 6`
   inside an auto-fill grid would mean six of its 19rem TRACKS, so the tablet
   rule has to be overridden rather than merely outweighed, and equal
   specificity makes source order the thing that decides. Nothing outside this
   band is touched. */
.grid-pinned { grid-template-columns: repeat(auto-fill, minmax(min(100%, 19rem), 1fr)); }
.grid-pinned > .card { grid-column: auto; }
.grid-pinned > .card[data-span="7"],
.grid-pinned > .card[data-span="8"],
.grid-pinned > .card[data-span="9"],
.grid-pinned > .card[data-span="10"],
.grid-pinned > .card[data-span="11"],
.grid-pinned > .card[data-span="12"] { grid-column: 1 / -1; }

/* An undeclared band is an agent's mistake that the backend deliberately keeps
   visible rather than dropping the widget. It has to read as unintended: muted
   ink, a dashed rule instead of a solid one, and the offending slug spelled out
   where a card count would be. */
.section-label.undeclared { color: var(--muted); }
.section-label.undeclared::after {
  height: 0; background: none;
  border-top: 1px dashed var(--border);
}
.section-label.undeclared .section-note { font-style: italic; }

/* ---------- depth-3 nav ---------- */

/* Level 2 is the tab row. Level 3 is a row of pills under it — deliberately a
   different component, because two identical rows read as one confusing bar and
   the reader cannot tell which level she is moving in. Both scroll sideways
   instead of wrapping, which is what makes this work at 390px. */
.subtabs {
  display: flex; gap: .3rem; margin: -.5rem 0 1.25rem;
  overflow-x: auto; scrollbar-width: none;
}
.subtabs::-webkit-scrollbar { display: none; }
.subtab {
  flex: none; padding: .28rem .7rem;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--panel-2);
  font: 450 .75rem/1.5 var(--mono); letter-spacing: .04em;
  color: var(--muted); text-decoration: none; white-space: nowrap;
}
.subtab:hover { color: var(--text); }
.subtab.active {
  color: var(--text); border-color: var(--accent);
  background: var(--panel-2);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

/* ---------- calendar chrome ---------- */

.cal-bar {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: .45rem .75rem; margin: 0 0 .35rem;
}
.cal-nav { display: flex; gap: .2rem; flex: none; }
.cal-step {
  width: 1.7rem; height: 1.7rem; padding: 0; cursor: pointer; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel-2); color: var(--muted);
  border: 1px solid var(--border); border-radius: 7px;
  font: 450 .8125rem/1 var(--mono);
}
.cal-step:hover { color: var(--text); border-color: var(--accent); }
/* "Today" gets its word, not a glyph: it is the one control here whose job a
   reader cannot guess from an arrow. */
.cal-step.cal-today {
  width: auto; padding: 0 .55rem;
  font-size: .625rem; letter-spacing: .12em; text-transform: uppercase;
}
.cal-title {
  font: 600 .75rem/1.4 var(--mono);
  letter-spacing: .12em; text-transform: uppercase; color: var(--text);
  white-space: nowrap;
}
.cal-count {
  font: 450 .6875rem/1.4 var(--mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); white-space: nowrap;
}

/* One-of-N, so the buttons are joined into a single control. The category
   filters below are many-of-N and stay separate round chips: the shape of the
   control says which kind of choice it is. */
.seg {
  display: inline-flex; flex: none; margin-left: auto;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--panel-2); overflow: hidden;
}
.seg-btn {
  padding: .25rem .7rem; cursor: pointer;
  background: transparent; border: 0; color: var(--muted);
  font: 450 .6875rem/1.6 var(--mono); letter-spacing: .08em;
  white-space: nowrap;
}
.seg-btn + .seg-btn { border-left: 1px solid var(--border); }
.seg-btn:hover { color: var(--text); }
.seg-btn.on {
  color: var(--text);
  background: var(--panel);
  background: color-mix(in srgb, var(--accent) 16%, var(--panel));
}

/* The legend IS the filter. Everything is on by default, so an on chip looks
   plain and an off one is struck through — state never rests on the swatch. */
.legend { display: flex; flex-wrap: wrap; gap: .35rem; margin: .35rem 0 .9rem; }
.chip.cat { display: inline-flex; align-items: center; gap: .4rem; }
.chip.cat .sw {
  width: 8px; height: 8px; border-radius: 2px; flex: none;
  background: var(--cat, var(--axis-line));
}
.legend .chip { color: var(--text-2); }
.legend .chip.on {
  border-color: var(--border);
  background: var(--panel-2);
}
.legend .chip.off {
  color: var(--muted); text-decoration: line-through;
  background: transparent;
}
.legend .chip-n {
  font-variant-numeric: tabular-nums; color: var(--muted); letter-spacing: 0;
}
.cal-reset { border-style: dashed; }

/* ---------- calendar: month ---------- */

.mv-dow, .mv-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.mv-dow-c {
  padding: .2rem .35rem .35rem;
  font: 500 .625rem/1.4 var(--mono); letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted); text-align: center;
}
.mv-dow-c .dow-n { display: none; }

/* A lattice of hairlines, not a tray of boxes: the month reads as one surface
   with days marked on it, which is the same restraint the time spine has. */
.mv-grid {
  border-top: 1px solid var(--hairline);
  border-left: 1px solid var(--hairline);
}
.mv-cell {
  display: flex; flex-direction: column; gap: 2px;
  min-height: 5.6rem; padding: .28rem .3rem .35rem;
  text-align: left; cursor: pointer;
  background: transparent; color: inherit;
  border: 0;
  border-right: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  font: inherit;
  overflow: hidden;
}
.mv-cell:hover { background: var(--panel-2); }
.mv-cell.out { color: var(--muted); }
.mv-cell.out .mv-num { opacity: .45; }
.mv-cell.sel {
  background: var(--panel-2);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}
/* Today is outlined, never filled: filling it would compete with the selected
   day, and on most days they are the same cell. */
.mv-cell.today { box-shadow: inset 0 0 0 2px var(--accent); }
.mv-num {
  font: 500 .75rem/1.3 var(--mono); font-variant-numeric: tabular-nums;
  color: var(--text-2); flex: none;
}
.mv-cell.today .mv-num { color: var(--text); font-weight: 700; }

.mv-chips { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* The category colour is a 3px edge and a faint wash — never a saturated fill
   behind text, which is how a yellow category ends up illegible in light mode.
   The text itself stays in an ink token. */
.mv-chip {
  display: flex; align-items: baseline; gap: .25rem; min-width: 0;
  padding: .05rem .25rem .05rem .3rem;
  border-left: 3px solid var(--cat, var(--axis-line));
  border-radius: 0 3px 3px 0;
  background: var(--panel-2);
  background: color-mix(in srgb, var(--cat, var(--axis-line)) 13%, transparent);
  font: 450 .6875rem/1.45 var(--sans); color: var(--text-2);
}
.mv-chip-t {
  flex: none; font: 450 .625rem/1.45 var(--mono);
  font-variant-numeric: tabular-nums; color: var(--muted);
}
.mv-chip-n { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mv-chip.was { opacity: .62; }
.mv-more {
  font: 450 .625rem/1.4 var(--mono); letter-spacing: .04em; color: var(--muted);
  padding-left: .3rem;
}

.mv-dots { display: none; flex-wrap: wrap; gap: 2px; align-content: flex-start; }
.mv-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--cat, var(--axis-line));
}
.mv-dot.was { opacity: .55; }
.mv-dot-more {
  font: 500 .625rem/.8 var(--mono); color: var(--muted); margin-left: 1px;
}

/* THE PHONE MONTH GRID.
   The grid stays a grid. Below ~530px a chip cannot hold a legible word in a
   50px cell, so each event becomes a category dot and the day panel underneath
   carries the detail — the same panel, the same interaction, on every width.
   Switching to the agenda instead would make the view switcher lie about what
   is on screen, and would take away the one question only a month grid answers:
   which weekends are still free. Both markups are in the DOM and the container
   query picks one, so a resize is instant and never needs a re-render. */
@container (max-width: 44rem) {
  .mv-chip-t { display: none; }
  .mv-cell { min-height: 5rem; }
}
@container (max-width: 33rem) {
  .mv-chips, .mv-more { display: none; }
  .mv-dots { display: flex; }
  .mv-cell { min-height: 3.1rem; gap: 3px; padding: .22rem .2rem; align-items: center; }
  .mv-num { font-size: .6875rem; }
  .mv-dow-c { padding: .2rem 0 .3rem; letter-spacing: .06em; }
  .mv-dow-c .dow-w { display: none; }
  .mv-dow-c .dow-n { display: inline; }
}

/* ---------- calendar: week ---------- */

.wv {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  border-top: 1px solid var(--hairline);
}
.wv-col {
  display: flex; flex-direction: column; gap: 2px;
  min-height: 7rem; padding: 0 .25rem .5rem;
  border-left: 1px solid var(--hairline);
}
.wv-col:last-child { border-right: 1px solid var(--hairline); }
.wv-col.sel { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.wv-head {
  display: flex; align-items: baseline; gap: .35rem;
  padding: .35rem .25rem .4rem; cursor: pointer;
  background: transparent; border: 0; border-bottom: 1px solid var(--hairline);
  color: var(--muted); text-align: left;
  font: 500 .625rem/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase;
}
.wv-head:hover { color: var(--text); }
.wv-num { font-size: .8125rem; font-variant-numeric: tabular-nums; color: var(--text-2); letter-spacing: 0; }
.wv-col.today .wv-head { color: var(--text); }
.wv-col.today .wv-num {
  color: var(--text); font-weight: 700;
  box-shadow: inset 0 0 0 2px var(--accent);
  border-radius: 5px; padding: 0 .25rem;
}
.wv-none { padding: .4rem .3rem; color: var(--muted); font: 450 .75rem/1 var(--mono); }
.wv-ev {
  display: flex; flex-direction: column; gap: 0; min-width: 0;
  margin-top: 2px; padding: .18rem .3rem .22rem .35rem; cursor: pointer;
  text-align: left; color: inherit;
  border: 0; border-left: 3px solid var(--cat, var(--axis-line));
  border-radius: 0 4px 4px 0;
  background: var(--panel-2);
  background: color-mix(in srgb, var(--cat, var(--axis-line)) 11%, transparent);
}
.wv-ev:hover { background: color-mix(in srgb, var(--cat, var(--axis-line)) 22%, transparent); }
.wv-ev.was { opacity: .68; }
.wv-t {
  font: 450 .625rem/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--muted);
}
.wv-n {
  font: 450 .75rem/1.35 var(--sans); color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------- all day ---------- */

/* A date-only event has no clock, and the app says so in its own voice: mono,
   upper-cased, the same treatment every label the app writes about itself gets.
   It sits exactly where the clock would, in all three views, because the left
   column is the column a reader scans for "when" — a blank there would read as
   missing data instead of as an answer. Sans and lower case would read as
   something the agent wrote, and no agent wrote this.

   Slightly smaller than the clock it replaces: seven characters where "8:30p"
   had five, and it must not out-shout the title. */
/* `.run` is the same slot saying the same KIND of thing — the app's own word
   where a clock would be — for the second and later days of a multi-day run:
   CONT'D, then LAST DAY. Same treatment as ALL DAY, one step quieter, because
   the day a festival started is more interesting than the middle of it. */
.ev-time.allday, .mv-chip-t.allday, .wv-t.allday,
.ev-time.run, .mv-chip-t.run, .wv-t.run {
  font: 500 .625rem/1.35 var(--mono);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-2);
}
.ev-time.run, .mv-chip-t.run, .wv-t.run { color: var(--muted); }
/* The spine's rail is 4.5rem wide and 3.25rem on a phone, where "ALL DAY" no
   longer fits on one line. It wraps rather than clips: two short right-aligned
   lines still read as one label, a truncated "ALL D" reads as a bug. */
.ev-time.allday, .ev-time.run { white-space: normal; text-align: right; }
.mv-chip-t.allday, .wv-t.allday, .mv-chip-t.run, .wv-t.run { white-space: nowrap; }

/* A CONTINUATION IS THE SAME EVENT CARRYING ON, and it has to read that way in
   every projection — otherwise a three-day festival reads as three festivals,
   which is the same lie as not showing it at all, told the other way round.

   The cue is the category edge, because that edge is already the thing that
   says "this belongs to that category" on the day it starts: it goes DASHED,
   which in this stylesheet already means "not quite a solid fact" (a paused
   watchlist row uses the same idea). On the phone, where a chip becomes a
   single dot, the dot goes hollow — the same statement in the only ink a 6px
   mark has. Neither cue rests on colour, and neither is the only carrier: the
   rail or the chip's time slot says CONT'D or LAST DAY in words. */
.mv-chip.cont, .wv-ev.cont { border-left-style: dashed; }
.ev-row.cont .ev-dot {
  background: transparent;
  box-shadow: 0 0 0 3px var(--panel), inset 0 0 0 2px var(--accent);
}
.mv-dot.cont {
  background: transparent;
  box-shadow: inset 0 0 0 2px var(--cat, var(--axis-line));
}

/* The extent of a multi-day event, first in the meta line so it sits next to
   the day the row hangs off: "ALL DAY" on the left, "AUG 28 – 30" here. Mono
   and muted — it is the app reporting the span, not part of the title. */
.ev-thru {
  font: 500 .625rem/1.5 var(--mono);
  letter-spacing: .08em; text-transform: uppercase;
  font-variant-numeric: tabular-nums; color: var(--muted);
  white-space: nowrap;
}

/* Seven text columns need about 90px each. Below that the week stacks into
   seven labelled bands — still exactly one week, which is the thing the view
   is for, just read downwards. */
@container (max-width: 44rem) {
  .wv { grid-template-columns: 1fr; border-top: 0; }
  .wv-col {
    min-height: 0; padding: .1rem 0 .35rem;
    border-left: 0; border-top: 1px solid var(--hairline);
  }
  .wv-col:last-child { border-right: 0; }
  .wv-head { border-bottom: 0; padding-bottom: .2rem; }
  .wv-n { white-space: normal; }
}

/* ---------- calendar: the day panel ---------- */

/* What the grids open. Same rail, same rows, same answer buttons as the agenda:
   a month cell is a way of finding a day, and a day always looks like a day. */
.cal-day {
  margin-top: 1.1rem; padding-top: .15rem;
  border-top: 1px solid var(--hairline);
}
.cal-day-head {
  display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
  margin: .7rem 0 .1rem;
  font: 600 .6875rem/1.4 var(--mono);
  letter-spacing: .14em; text-transform: uppercase; color: var(--text);
}
.cal-day-n { font-weight: 400; letter-spacing: .1em; color: var(--muted); }
.cal-day .ev-day { display: none; } /* the panel's own heading already said the day */
.cal-day .cal { padding-top: .1rem; }
.cal-day .ev-row:first-of-type { margin-top: .35rem; }
.ev-row.was { opacity: .55; }
.ev-row.was:hover { opacity: 1; }

/* ---------- timeline ---------- */

/* The spine, reused whole. Only the left column changes meaning: a clock in a
   calendar, a clock under a date heading here. */
.tl .ev-body {
  margin: .2rem 0 0; color: var(--text-2); font-size: .875rem;
  overflow-wrap: anywhere;
}
.tl .ev-row { align-items: start; padding-top: .35rem; padding-bottom: .35rem; }
.tl .ev-dot { align-self: start; margin-top: .45rem; }
.tl .ev-time { padding-top: .05rem; }

/* ---------- watchlist ---------- */

.wl-sum {
  display: flex; flex-wrap: wrap; gap: .3rem .9rem; margin: 0 0 .7rem;
  font: 500 .6875rem/1.4 var(--mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted);
}
.wl-sum-late { color: var(--bad); }

.wl { display: flex; flex-direction: column; }
.wl-item {
  display: grid;
  grid-template-columns: 1.1rem minmax(0, 1fr) auto;
  gap: 0 .6rem; align-items: start;
  padding: .6rem .1rem .6rem .5rem;
  border-top: 1px solid var(--hairline);
}
.wl-item:first-child { border-top: 0; }
/* A late row is findable by scanning the left edge, so the agent's declared
   order can be left exactly as it is. */
.wl-item.late { box-shadow: inset 2px 0 0 var(--bad); }
.wl-item.asleep { opacity: .68; }
.wl-item.asleep:hover { opacity: 1; }

/* The dot is the status the agent ASSERTED. Open ring = still open, filled =
   it fired, dashed = deliberately asleep. The word is always beside it. */
.wl-dot { width: 9px; height: 9px; border-radius: 50%; margin-top: .42rem; flex: none; }
.wl-dot.watching { border: 2px solid var(--accent); }
.wl-dot.hit { background: var(--good); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 20%, transparent); }
.wl-dot.paused { border: 2px dashed var(--muted); }

.wl-body { min-width: 0; }
.wl-what { font-weight: 550; letter-spacing: -.005em; overflow-wrap: anywhere; }
a.wl-what { text-decoration: none; border-bottom: 1px solid var(--hairline); }
a.wl-what:hover { border-bottom-color: var(--accent); }
.wl-why { margin-top: .1rem; color: var(--muted); font-size: .8125rem; }
.wl-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: .3rem .6rem;
  margin-top: .3rem;
}
.wl-status {
  font: 500 .625rem/1.5 var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted);
}
.wl-status.hit { color: var(--good); }
.wl-status.watching { color: var(--text-2); }
.wl-cadence {
  font: 450 .6875rem/1.5 var(--mono); letter-spacing: .04em; color: var(--muted);
}
.wl-cadence::before { content: '↻ '; }

.wl-side {
  display: flex; flex-direction: column; align-items: flex-end; gap: .05rem;
  text-align: right; flex: none; max-width: 9rem;
}
.wl-when-l {
  font: 450 .625rem/1.4 var(--mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted);
}
.wl-when {
  font: 500 .875rem/1.25 var(--mono); font-variant-numeric: tabular-nums;
  color: var(--text);
}
.wl-when.late { color: var(--bad); }
.wl-tag {
  margin-top: .1rem; padding: .05rem .35rem;
  border: 1px solid var(--bad); border-radius: 4px; color: var(--bad);
  font: 600 .5625rem/1.5 var(--mono); letter-spacing: .14em; text-transform: uppercase;
}
.wl-note { font: 400 .6875rem/1.4 var(--sans); color: var(--muted); }

@container (max-width: 24rem) {
  .wl-item { grid-template-columns: 1.1rem minmax(0, 1fr); }
  .wl-side {
    grid-column: 2; align-items: flex-start; text-align: left; max-width: none;
    flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: .2rem .5rem;
    margin-top: .3rem;
  }
}

/* ---------- list ---------- */

.ls { margin: 0; padding: 0; list-style: none; }
.ls-item {
  display: grid; grid-template-columns: 1.2rem minmax(0, 1fr) auto;
  gap: 0 .55rem; align-items: baseline;
  padding: .35rem .1rem;
  border-top: 1px solid var(--hairline);
}
.ls-item:first-child { border-top: 0; }
.ls-mark {
  font: 450 .6875rem/1.6 var(--mono); font-variant-numeric: tabular-nums;
  color: var(--muted); text-align: right;
}
.ls-item.done .ls-mark { color: var(--good); }
.ls-body { min-width: 0; }
.ls-text { font-weight: 500; overflow-wrap: anywhere; }
a.ls-text { text-decoration: none; border-bottom: 1px solid var(--hairline); }
a.ls-text:hover { border-bottom-color: var(--accent); }
.ls-item.done .ls-text { text-decoration: line-through; color: var(--muted); font-weight: 400; }
.ls-sec { display: block; color: var(--muted); font-size: .8125rem; }
.ls-badge { align-self: center; }

/* ---------- stat_group ---------- */

/* A KPI row, one size down from the hero stat: six numbers that each shout as
   loudly as a headline is a row with no headline in it. */
.sg {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
  gap: .9rem 1.4rem;
}
.sg-item { min-width: 0; }
.sg-label {
  font: 500 .625rem/1.4 var(--mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted);
  overflow-wrap: anywhere;
}
.sg-value {
  display: flex; align-items: baseline; gap: .25rem; flex-wrap: wrap;
  margin-top: .15rem;
  font-size: clamp(1.35rem, 1.1rem + .8vw, 1.75rem);
  font-weight: 700; letter-spacing: -.04em; line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.sg-item .stat-delta { margin-top: .3rem; font-size: .75rem; flex-wrap: wrap; }
/* Narrow: the note wraps to its own line rather than being hidden. It is the
   basis of the number above it ("since 2026-08-01", "vs Jun-Jul (fluid flux)"),
   and a delta without its basis misleads more than it informs. */
.sg-item .stat-delta-note { flex-basis: 100%; }
@container (min-width: 30rem) {
  .sg-item .stat-delta-note { flex-basis: auto; }
}

/* ---------- progress ---------- */

.pg-top { display: flex; align-items: baseline; justify-content: space-between; gap: .6rem 1rem; flex-wrap: wrap; }
.pg-pct {
  display: flex; align-items: baseline; gap: .25rem; flex-wrap: wrap;
  font-size: clamp(1.9rem, 1.5rem + 1.6vw, 2.6rem);
  font-weight: 720; letter-spacing: -.045em; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.pg-of {
  font: 450 .8125rem/1.4 var(--mono); font-variant-numeric: tabular-nums;
  letter-spacing: .02em; color: var(--muted);
}
/* A thin mark on its own track, anchored to its zero, with the 4px rounded
   data-end the rest of the system's marks use. */
.pg-track {
  position: relative; height: 10px; margin-top: .75rem;
  background: var(--panel-2);
  border: 1px solid var(--hairline); border-radius: 999px;
  overflow: hidden;
}
.pg-fill {
  height: 100%; min-width: 3px;
  background: var(--accent);
  border-radius: 0 4px 4px 0;
}
/* Past the target: the bar is full and says so with a texture, rather than
   being clamped into a silent claim of being exactly done. */
.pg-over .pg-fill {
  background: var(--good);
  background: repeating-linear-gradient(115deg,
    var(--good) 0 6px, color-mix(in srgb, var(--good) 55%, var(--panel)) 6px 12px);
}
.pg-label { margin-top: .55rem; color: var(--muted); font-size: .875rem; }

/* ---------- admin page (/admin.php) ----------
   Added for Task 14. Only .admin-scoped classes and names nothing else uses,
   so the dashboard's own styles above are untouched. Same tokens, same two
   voices of type: mono for the labels the app writes, sans for names. */

.admin-head { margin-bottom: 1.75rem; }
.admin-head .page-title { margin: .35rem 0 .5rem; }
.admin .section-label { margin-top: 2.25rem; }

.admin-table {
  width: 100%; border-collapse: collapse;
  font-size: .9375rem;
}
.admin-table th {
  text-align: left; padding: .5rem .9rem .5rem 0; white-space: nowrap;
  border-bottom: 1px solid var(--border);
  font: 500 .6875rem/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted);
}
.admin-table td {
  padding: .6rem .9rem .6rem 0; vertical-align: middle;
  border-bottom: 1px solid var(--hairline);
}
.admin-table tr:last-child td { border-bottom: 0; }
.admin-table .num { text-align: right; padding-right: 1.1rem; font-variant-numeric: tabular-nums; }
.admin-table td:not(.desc) { white-space: nowrap; }
.admin-table .desc { color: var(--text-2); max-width: 22rem; overflow-wrap: anywhere; white-space: normal; }
.admin-table .stale { color: var(--muted); }
.admin-table code { font: 500 .8125rem/1.4 var(--mono); }

.row-actions { display: flex; gap: .35rem; justify-content: flex-end; }
.row-actions form { display: inline; }

.ghost.danger { color: var(--bad); }
.ghost.danger:hover { border-color: var(--bad); }

.row-form {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: .75rem;
  margin-top: 1.25rem;
}
.row-form .field { flex: 1 1 14rem; }
.row-form button { flex: none; padding-block: .55rem; }

.notice {
  margin: 0 0 1.25rem; padding: .7rem .9rem;
  background: var(--panel); border: 1px solid var(--border);
  border-left: 3px solid var(--good); border-radius: var(--r-sm);
  color: var(--text-2); font-size: .9375rem;
}
.notice.bad { border-left-color: var(--bad); color: var(--err); }

.keybox { padding: 1rem 1.15rem 1.15rem; margin-bottom: 1.5rem; }
.keybox .eyebrow { margin-bottom: .5rem; }
.keyvalue {
  display: block; padding: .7rem .8rem; margin-bottom: .6rem;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--r-sm);
  font: 500 .875rem/1.5 var(--mono); overflow-wrap: anywhere; user-select: all;
}
.keynote { margin: 0; color: var(--muted); font-size: .8125rem; }

.badge {
  margin-left: .4rem; padding: .1rem .4rem; border-radius: 999px;
  background: var(--panel-2); border: 1px solid var(--border);
  font: 500 .625rem/1.6 var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted);
}

@media (max-width: 700px) {
  .admin-table, .admin-table thead, .admin-table tbody, .admin-table tr, .admin-table th, .admin-table td { display: block; }
  .admin-table thead { display: none; }
  .admin-table tr { padding: .75rem 0; border-bottom: 1px solid var(--hairline); }
  .admin-table td { border: 0; padding: .15rem 0; white-space: normal; }
  .admin-table .num { text-align: left; padding-right: 0; }
  .row-actions { justify-content: flex-start; margin-top: .5rem; }
}
