/* ============================================================
   COMMAND CENTER — Blueprint design system · single entry point
   ------------------------------------------------------------
   Link ONLY this file from a page; it pulls in the four verbatim
   design sources (ported 1:1 from claude.ai/design) and then
   applies the choices the design landed on:
     · JetBrains Mono everywhere (vendored, no CDN)
     · MONO accent (greyscale "signal" colour)
     · blueprint variant: 0 radius · hairline · grid + scanlines
   Theme/density live on <html> (data-theme, data-density,
   data-scanlines); the blueprint variant lives on the root
   element (class "oh-root" + data-v="blueprint").
   ============================================================ */

/* @import rules must come first. Order matters: later files override earlier. */
@import "theme.css";      /* reset · theme tokens · app shell · command palette */
@import "operhud.css";    /* 4 type sizes · variant system · bento home · HUD page · readouts */
@import "blocks.css";     /* bp-* components: buttons, inputs, table, kanban, modal, ... + gallery chrome */
@import "blocks2.css";    /* bp-* v2: overlays, charts, lists/trees, indicators, terminal */

/* ------------------------------------------------------------
   VENDORED FONT — JetBrains Mono (latin subset, ~21KB/weight)
   ------------------------------------------------------------ */
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("/static/fonts/jetbrains-mono-400.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("/static/fonts/jetbrains-mono-500.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("/static/fonts/jetbrains-mono-600.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("/static/fonts/jetbrains-mono-700.woff2") format("woff2");
}

/* ------------------------------------------------------------
   LOCKED CHOICES — one typeface, MONO accent (overrides theme.css)
   These come AFTER the @imports so they win the cascade.
   ------------------------------------------------------------ */
:root {
  /* the whole UI is one mono typeface — hierarchy comes from weight/colour/spacing/caps */
  --sans: "JetBrains Mono", ui-monospace, monospace;
  --mono: "JetBrains Mono", ui-monospace, monospace;
}

/* MONO accent — greyscale "system signal", no glow (the blueprint look) */
:root, [data-theme="dark"] {
  --accent:      oklch(0.78 0.006 240);
  --accent-2:    oklch(0.70 0.02 240);
  --accent-ink:  oklch(0.16 0.01 240);
  --accent-soft: color-mix(in oklch, var(--accent) 14%, transparent);
  --accent-line: color-mix(in oklch, var(--accent) 38%, transparent);
  --glow:        transparent;
}
[data-theme="light"] {
  --accent:      oklch(0.46 0.01 240);
  --accent-2:    oklch(0.40 0.02 240);
  --accent-ink:  oklch(0.98 0.005 240);
  --accent-soft: color-mix(in oklch, var(--accent) 10%, transparent);
  --accent-line: color-mix(in oklch, var(--accent) 30%, transparent);
  --glow:        transparent;
}

/* ------------------------------------------------------------
   GLUE — bits the prototype did in JS/markup, done here in CSS
   ------------------------------------------------------------ */

/* the vendored icons render as inline <svg class="bpi-svg"> (see web/icons.py).
   make them behave like the prototype's icon spans inside flex rows. */
.bpi-svg { display: inline-block; vertical-align: middle; flex: none; }

/* live "pulse" indicators (oh-live, oh-rail-dot) reference this keyframe;
   it lived in a direction file the gallery never loaded, so define it here. */
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* file-tree chevron rotates open (the prototype set this inline per-node;
   JS toggles .open on the caret instead). */
.bp-tree-cv.open > .bpi-svg { transform: rotate(90deg); transition: transform .14s ease; }

/* select / dropdown caret rotation already handled by blocks.css via .open;
   keep the icon centred regardless of set. */
.bp-select-btn .cv, .bp-acc-h .cv { display: grid; place-items: center; }

/* overlays must stay viewport-fixed wherever they sit in the tree. operhud.css's
   `.oh-root > *:not(.oh-grid-bg){position:relative}` (specificity 0,2,0) otherwise drops a scrim
   into the layout. This descendant rule matches that specificity and, declared later, wins — so a
   modal/drawer scrim works at any depth inside the root. */
.oh-root .bp-modal-scrim, .oh-root .bp-drawer-scrim { position: fixed; }

/* overlays are server-rendered into the DOM and revealed by JS (blocks.js) toggling [hidden];
   [hidden] already means display:none, but be explicit so an open scrim lays out as designed. */
.bp-modal-scrim[hidden], .bp-drawer-scrim[hidden], .palette-scrim[hidden],
.bp-menu[hidden], .bp-dd-menu[hidden], .bp-pop-card[hidden],
.palette-item[hidden], .palette-group[hidden],
[data-tab-panel][hidden], [data-tree-children][hidden],
[data-filter-item][hidden] { display: none; }

/* ------------------------------------------------------------
   CONNECTOR SETTINGS — searchable checkbox pickers (project allow-list /
   label exclusion-list on /connectors/todoist). Token-only, no new look.
   ------------------------------------------------------------ */
.cc-pick-list { margin-top: 10px; max-height: 320px; overflow-y: auto; display: flex;
  flex-direction: column; gap: 2px; border: 1px solid var(--line); padding: 6px; }
.cc-pick-list .bp-choice { width: 100%; justify-content: flex-start; padding: 5px 7px; }
.cc-pick-list .bp-choice:hover { background: var(--accent-soft); }
.cc-pick-note { color: var(--text-2); font-size: var(--t4); margin-top: 8px; }
.cc-pick-empty { color: var(--text-2); font-size: var(--t4); padding: 8px 4px; }
.cc-pick-warn { grid-column: 1 / -1; }
.cc-actions { display: flex; gap: 10px; align-items: center; margin-top: 14px; }

/* ------------------------------------------------------------
   BREATHING ROOM — wider canvas + taller table rows by default,
   plus a real "comfy" density. data-density lives on <html>
   ("compact" is the shell default; the top-bar toggle flips it
   to "comfy" and blocks.js persists the choice). theme.css's
   density tokens are barely consumed by the bp-* components, so
   the mode is applied here, at the high-traffic surfaces.
   ------------------------------------------------------------ */
.bp-content-inner { max-width: 1260px; }
.bp-table th, .bp-table td { padding: 11px 12px; }

[data-density="comfy"] .bp-content { padding: 34px 38px 90px; }
[data-density="comfy"] .bp-spec { padding: 22px; }
[data-density="comfy"] .oh-readout .oh-panel { padding: 18px 20px; }
[data-density="comfy"] .bp-table th,
[data-density="comfy"] .bp-table td { padding: 13px 14px; }
[data-density="comfy"] .cc-target-row { padding: 12px 14px; }
[data-density="comfy"] .cc-prompt-card { padding: 16px 18px; }
[data-density="comfy"] .cc-prompt-grid { gap: 18px; }

/* Clickable connector card (whole panel is the link). */
.cc-card-link { display: block; text-decoration: none; color: inherit; }
.cc-card-link .cc-card { transition: border-color .14s ease, background .14s ease; }
.cc-card-link:hover .cc-card { border-color: var(--accent); background: var(--accent-soft); }
.cc-card-cue { display: inline-flex; align-items: center; gap: 5px; color: var(--text-2);
  font-size: var(--t4); text-transform: uppercase; letter-spacing: .08em; }
.cc-card-link:hover .cc-card-cue { color: var(--accent); }

/* Card-as-button via [data-card-href] (blocks.js): cards that contain interactive children
   (buttons, forms) can't be wrapped in an <a>, so JS navigates instead. Same hover read. */
[data-card-href] { cursor: pointer; transition: border-color .14s ease, background .14s ease; }
[data-card-href]:hover { border-color: var(--accent); background: var(--accent-soft); }
[data-card-href]:hover .cc-card-cue { color: var(--accent); }

/* Icon picker (icon_pick component): the chosen tile reads as selected. */
.cc-iconpick [data-icon-val].on { border-color: var(--accent); color: var(--accent);
  background: var(--accent-soft); }

/* "Saving…" overlay shown while a filter save re-syncs (blocks.js reveals it on submit).
   Scoped under .oh-root so it beats operhud's `.oh-root > *{position:relative}` (0,2,0) — otherwise
   the overlay drops into normal flow at the bottom of the page instead of covering it. */
.oh-root .cc-overlay { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center;
  background: color-mix(in oklch, var(--bg-1) 92%, transparent);
  -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px); }
.oh-root .cc-overlay[hidden] { display: none; }
.cc-overlay-card { position: relative; min-width: 280px; padding: 26px 30px; text-align: center;
  border: 1px solid var(--line); background: var(--bg-2); }
.cc-overlay-title { font-size: var(--t2); letter-spacing: .14em; }
.cc-overlay-sub { color: var(--text-2); font-size: var(--t4); margin-top: 12px;
  animation: pulse 1.3s ease-in-out infinite; }
.cc-overlay-bar { position: relative; height: 3px; margin-top: 16px; overflow: hidden;
  background: var(--line); }
.cc-overlay-bar > span { position: absolute; top: 0; left: 0; height: 100%; width: 40%;
  background: var(--accent); animation: cc-indet 1.1s ease-in-out infinite; }
@keyframes cc-indet { 0% { left: -40%; } 100% { left: 100%; } }

/* ─── Page transitions — per-block "CRT power-cycle" + text fade ──────────────────────────────
   Instead of cycling the whole page, each MAIN BLOCK animates on its own. JS-driven (blocks.js)
   so the feedback is INSTANT on click, not deferred until the next page loads.

     · Bordered boxes (panels, tiles, cards…) → CRT power-cycle: collapse to a bright scanline on
       leave, power on by expanding from the line on arrival. blocks.js finds them by their computed
       border and keeps only the TOP-LEVEL ones, so a table inside a panel rides the panel (no
       recursion). Each box pivots about its OWN centre. The pinch is purely VERTICAL — no jitter.
     · Free text / titles (.oh-hero, .bp-sec-h) → a clean opacity FADE. (This used to be a glitch
       that shook the text side-to-side; that jitter is removed everywhere — nothing shakes now.)
     · The top bar and the grid background never animate — blocks.js only scans `.oh-root`'s
       content children, never `.bp-top` / `.oh-grid-bg`.

   blocks.js adds these classes per element. Durations are mirrored by the *_MS constants there.
   Tune the look in the keyframes below. Reduced-motion → no animation (and content shown at once).

   PERFORMANCE RULE for every keyframe here: animate ONLY transform / opacity / clip-path — these
   run on the compositor. No `filter` (it forced an extra render surface per box and ran on the
   main thread in some engines — the source of visible jank when a whole dashboard cycled at once).
   The collapse must END at opacity 0: with fill-mode `both` the element HOLDS its last frame until
   the next page actually arrives (network time included), so a visible end state lingered as a
   thin bright line across the screen. */
.cc-box-out  { animation: cc-crt-out  160ms cubic-bezier(.55, 0, .9, .35) both; will-change: transform, opacity; }
.cc-box-in   { animation: cc-crt-in   260ms cubic-bezier(.16, .9, .2, 1)  both; will-change: transform, opacity; }
.cc-text-out { animation: cc-fade-out 140ms ease both; }
.cc-text-in  { animation: cc-fade-in  220ms ease both; }

@keyframes cc-crt-out {
  0%   { transform: scaleY(1);    opacity: 1;   }
  70%  { transform: scaleY(.03);  opacity: .85; }
  100% { transform: scaleY(.002); opacity: 0;   } /* fully off — nothing left behind */
}
@keyframes cc-crt-in {
  0%   { transform: scaleY(.02);  opacity: 0; }
  18%  { transform: scaleY(.08);  opacity: 1; } /* the power-on line: visible for ~45ms, then... */
  100% { transform: scaleY(1);    }             /* ...rapid expansion (strong ease-out) */
}
/* Text fade — pure opacity, NO transform/skew/clip. The old version slice-shifted the text
   side-to-side ("jitter"); that's intentionally gone so titles just fade with the boxes. */
@keyframes cc-fade-out {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes cc-fade-in {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}

/* Flash guard: keep animated content hidden until blocks.js has tagged it for the power-on, so
   nothing shows un-animated for a frame. `.cc-prep` is set pre-paint by the inline <head> script
   and removed by blocks.js (or its own failsafe timer if JS never runs). The persistent top-bar
   controls (brand, Home link, search, theme, logout) and the grid stay shown — only the page-
   specific top-bar bits (the crumb leaf + meta) are hidden alongside the main content, so they can
   fade in with everything else while the controls read as static across the navigation. */
.cc-prep .oh-root > *:not(.bp-top):not(.oh-grid-bg),
.cc-prep .bp-top .bp-crumb .sep,
.cc-prep .bp-top .bp-crumb .sep + .cur,
.cc-prep .bp-top-meta { visibility: hidden; }

/* ─── Tables — their own, calmer treatment (NOT the CRT pinch) ────────────────────────────────
   A big table under the CRT scaleY pinch jitters and distorts badly — the
   motion is far more visible on a tall grid than on a compact panel. So any block that IS or
   CONTAINS a `.bp-table` opts out of `cc-box-*`/`cc-text-*` and uses these instead (blocks.js routes
   it). The look: a top-down "scanline reveal" — the rows wipe in from the top edge, no horizontal
   shift and no vertical squash. This drives BOTH the page transition (leave/arrive) AND in-place
   data refreshes (show/hide completed, post-sync), so every table — now and future — animates the
   same way. Durations mirrored by the *_MS constants in blocks.js. Same compositor-only +
   end-at-opacity-0 rules as the CRT keyframes above. */
.cc-table-out { animation: cc-table-out 160ms cubic-bezier(.55, 0, .9, .35) both; transform-origin: top center; will-change: clip-path, opacity; }
.cc-table-in  { animation: cc-table-in  260ms cubic-bezier(.16, .9, .2, 1)  both; transform-origin: top center; will-change: clip-path, opacity; }
@keyframes cc-table-out {
  0%   { opacity: 1; clip-path: inset(0 0 0 0);   }
  100% { opacity: 0; clip-path: inset(0 0 94% 0); }
}
@keyframes cc-table-in {
  0%   { opacity: 0; clip-path: inset(0 0 94% 0); }
  35%  { opacity: 1; }
  100% { opacity: 1; clip-path: inset(0 0 0 0);   }
}

/* In-place refresh "busy" state — a region whose data is being re-fetched (blocks.js adds it during
   the fetch, then swaps the new HTML in under cc-table-in / a skeleton fade). Dim + lock clicks so a
   stale table reads as pending without collapsing the layout. The side widgets instead show skeleton
   placeholders (the `skeleton` component markup, injected by blocks.js) during their fetch. */
.cc-busy { opacity: .5; pointer-events: none; transition: opacity .14s ease; }
.cc-region { position: relative; }
.cc-skel-fade { animation: cc-frag-in 260ms cubic-bezier(.16, .9, .2, 1) both; transform-origin: top; }

/* In-page fragment swaps (skeleton → loaded content) keep using document.startViewTransition in
   blocks.js, tagged `view-transition-class: cc-frag` — a quick power-on echo, scoped to the box. */
@keyframes cc-frag-out { to { opacity: 0; } }
@keyframes cc-frag-in  { from { opacity: 0; transform: scaleY(.6); } to { opacity: 1; transform: scaleY(1); } }

/* ─── Target rows — a target as its own block (goal cards + goal detail) ─────────────────────
   Label + window caption on the head line, then the bar line: progress (flex) + the "+1" button.
   The caption no longer shares the bar's row, so every bar in a list is the SAME width.
   `.cc-target-row` is excluded from box detection in blocks.js (CONTROL_SEL) — these are row
   furniture, not panels; they ride their container (or fade) instead of CRT-pinching one by one. */
.cc-target-row { border: 1px solid var(--line-soft); padding: 10px 12px;
  display: flex; flex-direction: column; gap: 8px; }
.cc-target-head { display: flex; align-items: baseline; gap: 10px; }
.cc-target-label { flex: 1; min-width: 0; font-size: var(--t3); font-weight: 600;
  color: var(--text); overflow-wrap: anywhere; }
.cc-target-cap { font-size: var(--t4); color: var(--text-2); white-space: nowrap; }
.cc-target-bar { display: flex; align-items: center; gap: 10px; }
.cc-target-bar > .bp-progress { flex: 1; min-width: 0; }

/* Hover-revealed card action (e.g. the Edit button on a goal card): invisible until the card is
   hovered or the control is keyboard-focused, so the cap stays quiet. */
.oh-panel .cc-hover-action { opacity: 0; transition: opacity .14s ease; }
.oh-panel:hover .cc-hover-action, .oh-panel:focus-within .cc-hover-action { opacity: 1; }

/* Watch-list prompt cards — the dashboard's dedicated section (one bordered card per light). */
.cc-prompt-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: 14px; align-items: start; margin-bottom: 22px; }
.cc-prompt-card { position: relative; border: 1px solid var(--line);
  background: var(--oh-panel-bg, var(--bg-1)); padding: 13px 15px;
  display: flex; flex-direction: column; gap: 9px; }
.cc-prompt-title { font-size: var(--t3); font-weight: 600; color: var(--text); }
.cc-prompt-body { font-size: var(--t4); color: var(--text-2); line-height: 1.5; }

/* Voice journal (/journal + /journal/capture) — the captain's log. The recorder fills the
   viewport: the page → panel → stage chain is a flex column, the scope grows so the waveform's
   centreline sits mid-screen, and the transcript is a full-height read-only terminal (bp-term
   clothes, no input). The REC light pulses with opacity only (motion perf rule). */
.cc-journal-page { display: flex; flex-direction: column; padding-bottom: 26px; }
.cc-journal-page .bp-content-inner { flex: 1; display: flex; flex-direction: column;
  min-height: 0; width: 100%; }
.cc-journal-page .oh-panel { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.cc-journal-stage { flex: 1; min-height: 0; display: grid; grid-template-columns: 1fr 380px;
  gap: 20px; align-items: stretch; }
@media (max-width: 900px) { .cc-journal-stage { grid-template-columns: 1fr; } }
.cc-journal-left { display: flex; flex-direction: column; gap: 14px; min-width: 0;
  min-height: 0; }
.cc-journal-scope { position: relative; flex: 1; min-height: 220px;
  border: 1px solid var(--line-soft); background: var(--bg-2); }
.cc-journal-scope canvas { display: block; width: 100%; height: 100%; }
.cc-journal-status { display: flex; align-items: center; gap: 14px; font-size: var(--t4);
  color: var(--text-2); letter-spacing: .08em; text-transform: uppercase; flex: none; }
.cc-journal-status #journal-state { color: var(--text); font-weight: 600; }
.cc-journal-rec { width: 8px; height: 8px; border-radius: 50%; background: var(--text-2);
  flex: none; }
.cc-journal-rec.is-live { background: oklch(62% .19 25); animation: cc-rec-pulse 1.4s ease-in-out
  infinite; }
@keyframes cc-rec-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
.cc-journal-controls { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  flex: none; }
.cc-journal-hint { font-size: var(--t4); color: var(--text-2); }
.cc-journal-hint kbd { font-size: 10px; border: 1px solid var(--line); padding: 1px 5px; }
@media (pointer: coarse) { .cc-journal-hint { display: none; } }
.cc-journal-error { font-size: var(--t3); color: var(--text); border: 1px solid var(--line);
  border-left: 2px solid oklch(62% .19 25); padding: 10px 12px; flex: none; }
.cc-journal-term { min-height: 0; height: 100%; }
.cc-journal-term .bp-term-body { max-height: none; }
.cc-journal-feed { color: var(--text); }
.cc-journal-lost { color: var(--text-2); font-size: var(--t4); }
.cc-journal-prose { margin: 0; font-size: var(--t3); line-height: 1.7; color: var(--text);
  white-space: pre-wrap; overflow-wrap: anywhere; }
.cc-journal-card { cursor: pointer; }
@media (max-width: 900px) {
  .cc-journal-page .oh-panel { flex: none; }
  .cc-journal-stage { flex: none; }
  .cc-journal-scope { min-height: 200px; flex: none; height: 220px; }
  .cc-journal-term { height: auto; min-height: 260px; }
  .cc-journal-term .bp-term-body { max-height: 320px; }
}
@media (prefers-reduced-motion: reduce) {
  .cc-journal-rec.is-live { animation: none; }
  .cc-journal-term .bp-term-cursor.on { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .cc-box-out, .cc-box-in, .cc-text-out, .cc-text-in,
  .cc-table-out, .cc-table-in, .cc-skel-fade { animation: none; }
  .cc-prep .oh-root > *, .cc-prep .bp-top * { visibility: visible; }
}
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(.cc-frag) { animation: cc-frag-out 120ms ease both; }
  ::view-transition-new(.cc-frag) { animation: cc-frag-in 260ms cubic-bezier(.16, .9, .2, 1) both;
    transform-origin: top; }
}

/* ─── Second brain (/wiki) — rendered note markdown + resolved wikilinks ──────────────────────
   The body is markdown-rendered to HTML (web/wiki_render.py); style it on-brand within the four
   type sizes. Links stay greyscale-accent; dangling [[links]] read as muted/unfinished. */
.cc-wiki-prose { font-size: var(--t3); line-height: 1.7; color: var(--text); overflow-wrap: anywhere; }
.cc-wiki-prose h1, .cc-wiki-prose h2, .cc-wiki-prose h3 { color: var(--text); font-weight: 700;
  letter-spacing: .02em; margin: 1.4em 0 .5em; }
.cc-wiki-prose h1 { font-size: var(--t2); }
.cc-wiki-prose h2 { font-size: var(--t2); }
.cc-wiki-prose h3 { font-size: var(--t3); text-transform: uppercase; color: var(--text-2); }
.cc-wiki-prose > :first-child { margin-top: 0; }
.cc-wiki-prose p { margin: 0 0 .9em; }
.cc-wiki-prose ul, .cc-wiki-prose ol { margin: 0 0 .9em; padding-left: 1.4em; }
.cc-wiki-prose li { margin: .25em 0; }
.cc-wiki-prose a { color: var(--accent); text-decoration: none; border-bottom: 1px solid var(--line); }
.cc-wiki-prose a:hover { border-bottom-color: var(--accent); }
.cc-wiki-prose code { font-family: inherit; font-size: var(--t4); background: var(--bg-1);
  border: 1px solid var(--line); padding: .05em .35em; }
.cc-wiki-prose pre { background: var(--bg-1); border: 1px solid var(--line); padding: 12px;
  overflow-x: auto; margin: 0 0 .9em; }
.cc-wiki-prose pre code { background: none; border: 0; padding: 0; }
.cc-wiki-prose blockquote { margin: 0 0 .9em; padding-left: 12px; border-left: 2px solid var(--line);
  color: var(--text-2); }
/* Resolved wikilink; entity link; dangling link (to a note that doesn't exist yet). */
.cc-wiki-link { color: var(--accent); text-decoration: none; border-bottom: 1px solid var(--line); }
.cc-wiki-link:hover { border-bottom-color: var(--accent); }
.cc-wiki-link.entity { border-bottom-style: dashed; }
.cc-wiki-link.dangling { color: var(--text-2); border-bottom: 1px dashed var(--warn); cursor: help; }
@media (max-width: 900px) {
  .cc-wiki-detail { grid-template-columns: 1fr !important; }
}

/* ─── Second brain (/wiki) — Obsidian-style split: graph stage left, compact catalogue right ──
   The graph canvas (wiki-graph.js) fills the left half via the flex chain (main → inner → panel →
   stage, each flex:1 + min-height:0); the right half scrolls its own card list so the graph stays
   put. Cards are the compact variant — the catalogue lives in half the width here. */
.cc-wiki-page { display: flex; flex-direction: column; padding-bottom: 26px; }
.cc-wiki-page .bp-content-inner { flex: 1; display: flex; flex-direction: column; min-height: 0;
  width: 100%; max-width: 1680px; }
.cc-wiki-stage { flex: 1; min-height: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 20px;
  align-items: stretch; }
.cc-wiki-left { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.cc-wiki-graph-panel { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.cc-wiki-graph-body { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 10px; }
.cc-wiki-graph { position: relative; flex: 1; min-height: 320px; border: 1px solid var(--line-soft);
  background: var(--bg-2); }
.cc-wiki-graph canvas { display: block; width: 100%; height: 100%; cursor: grab; }
.cc-wiki-graph canvas.is-node { cursor: pointer; }
.cc-wiki-graph canvas.is-grabbing { cursor: grabbing; }
.cc-wiki-graph-foot { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; flex: none;
  font-size: var(--t4); color: var(--text-2); }
.cc-graph-legend { display: flex; gap: 12px; flex-wrap: wrap; letter-spacing: .06em;
  text-transform: uppercase; }
.cc-graph-hint { margin-left: auto; white-space: nowrap; }
.cc-wiki-right { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.cc-wiki-cards { flex: 1; min-height: 0; overflow-y: auto; padding-right: 2px; }
.cc-wiki-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 14px; align-items: start; }
.cc-wiki-card.bp-spec { padding: 12px 14px; }
.cc-wiki-card .bp-spec-cap { margin-bottom: 9px; }
[data-density="comfy"] .cc-wiki-card.bp-spec { padding: 16px; }
.cc-note-excerpt { font-size: var(--t4); color: var(--text-2); line-height: 1.55;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
@media (max-width: 1100px) {
  /* Stack: the graph becomes a fixed-height band, the cards flow and the page scrolls normally. */
  .cc-wiki-page { display: block; }
  .cc-wiki-stage { display: flex; flex-direction: column; }
  .cc-wiki-graph-panel { flex: none; }
  .cc-wiki-graph { flex: none; height: 340px; min-height: 0; }
  .cc-wiki-cards { flex: none; overflow: visible; }
}

/* ─── Calendar (/calendar) — month grid + the "this week" home strip ─────────────────────────
   Blueprint cells: hairline borders, no radius, today framed in the mono accent, out-of-month days
   faded. Each tracked calendar wears one tone from the curated palette below (assigned/edited on
   /connectors/gcal); an event chip carries it as a left-tick (a small dot of colour, not a flood —
   the text stays mono). The same day-cell vocabulary scales down into the home "this week" strip. */

/* Curated calendar palette — eight muted, well-separated hues. Kept low-chroma so they read as
   quiet signal squares on both the dark and light blueprint backgrounds, not a rainbow. Tone keys
   (cal-1..cal-8) are mirrored in web/calendar_palette.py. */
:root {
  --cal-1: oklch(0.68 0.12 250);  /* Blue    */
  --cal-2: oklch(0.71 0.10 195);  /* Teal    */
  --cal-3: oklch(0.72 0.13 150);  /* Green   */
  --cal-4: oklch(0.72 0.09 120);  /* Olive   */
  --cal-5: oklch(0.77 0.12 75);   /* Amber   */
  --cal-6: oklch(0.66 0.16 25);   /* Rose    */
  --cal-7: oklch(0.64 0.16 330);  /* Magenta */
  --cal-8: oklch(0.63 0.13 295);  /* Violet  */
}

.cc-cal-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.cc-cal-title { font-size: var(--t2); font-weight: 700; color: var(--text); letter-spacing: .04em;
  text-transform: uppercase; }
.cc-cal-spacer { flex: 1; }

.cc-cal-head { display: grid; grid-template-columns: repeat(7, 1fr); gap: 8px; margin-bottom: 8px; }
.cc-cal-head span { font-size: var(--t4); color: var(--text-2); letter-spacing: .08em;
  text-transform: uppercase; padding-left: 2px; }

/* The month is a stack of week rows; each row carries the 7 day cells + an absolutely-positioned
   overlay that draws multi-day bars + location ribbons ACROSS cells (the grid itself stays put). */
.cc-cal-weeks { display: flex; flex-direction: column; gap: 8px; }
.cc-cal-week { position: relative; }
.cc-cal-grid7 { display: grid; grid-template-columns: repeat(7, 1fr); gap: 8px; }
.cc-cal-cell { border: 1px solid var(--line); background: var(--bg-2); min-height: 106px;
  padding: 7px 8px; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
/* reserves the vertical band the week's ribbons/bars occupy, so single-day chips sit below them */
.cc-cal-spanpad { flex: none; height: var(--span-h, 0px); }
.cc-cal-cell.is-out { opacity: .42; }
.cc-cal-cell.is-today { border-color: var(--accent); }
.cc-cal-daynum { font-size: var(--t4); color: var(--text-2); font-variant-numeric: tabular-nums; }
.cc-cal-cell.is-today .cc-cal-daynum { color: var(--accent); font-weight: 700; }

/* Each event chip is its own faintly tone-tinted block (no link underline) — the tint + the colour
   tick delimit one event from the next, calmer than an underline. */
.cc-cal-ev { display: flex; align-items: baseline; gap: 6px; font-size: var(--t4); color: var(--text);
  border-left: 2px solid var(--cal-tone, var(--accent-soft)); padding: 1px 5px 1px 6px;
  line-height: 1.4; min-width: 0; text-decoration: none;
  background: color-mix(in oklch, var(--cal-tone, var(--accent)) 10%, transparent); }
.cc-cal-ev:hover { background: color-mix(in oklch, var(--cal-tone, var(--accent)) 22%, transparent); }
.cc-cal-ev .t { color: var(--text-2); flex: none; font-variant-numeric: tabular-nums; }
.cc-cal-ev .x { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* All-day events have no time anchor, so give them a slightly stronger wash of their tone. */
.cc-cal-ev.is-allday {
  background: color-mix(in oklch, var(--cal-tone, var(--accent)) 20%, transparent); }
.cc-cal-more { font-size: var(--t4); color: var(--text-2); padding-left: 6px; }
[data-density="comfy"] .cc-cal-cell { min-height: 126px; padding: 10px; }

/* Per-week overlay: bars + ribbons live in a 7-col grid laid exactly over the week's cells, with
   one grid row per lane (heights set inline). It starts below the day-number row. */
.cc-cal-overlay { position: absolute; left: 0; right: 0; top: var(--ovl-top, 25px);
  display: grid; grid-template-columns: repeat(7, 1fr); column-gap: 8px; pointer-events: none; }
.cc-cal-overlay > a { pointer-events: auto; align-self: start; min-width: 0; text-decoration: none;
  overflow: hidden; white-space: nowrap; }
[data-density="comfy"] .cc-cal-week { --ovl-top: 28px; }

/* Multi-day event bar — a chip stretched across the days it covers. */
.cc-cal-bar { display: flex; align-items: center; gap: 6px; height: 19px; margin: 0 1px;
  padding: 0 6px; font-size: var(--t4); color: var(--text);
  border-left: 2px solid var(--cal-tone, var(--accent));
  background: color-mix(in oklch, var(--cal-tone, var(--accent)) 24%, var(--bg-2)); }
.cc-cal-bar .t { color: var(--text-2); flex: none; font-variant-numeric: tabular-nums; }
.cc-cal-bar .x { overflow: hidden; text-overflow: ellipsis; }
.cc-cal-bar.cont-l { margin-left: 0; border-left: none; padding-left: 8px; }
.cc-cal-bar.cont-r { margin-right: 0; }

/* Location ribbon — a thin tone rectangle FRAMING the days you're there (full cell height, raised
   over the date row), with the place name riding on the top edge of the first cell. Notes travel
   without taking an event slot. The frame is its own full-height overlay layer. */
.cc-cal-frames { position: absolute; inset: 0; display: grid; grid-template-columns: repeat(7, 1fr);
  grid-template-rows: 1fr; column-gap: 8px; pointer-events: none; z-index: 1; }
.cc-cal-ribbon { grid-row: 1; align-self: stretch; position: relative; pointer-events: none;
  border: 1.5px solid var(--cal-tone, var(--accent)); }
.cc-cal-ribbon.cont-l { border-left: none; }
.cc-cal-ribbon.cont-r { border-right: none; }
/* the label straddles the top border (legend-style), in the bright calendar tone — not washed out */
.cc-cal-ribbon-tag { position: absolute; top: 0; left: 6px; transform: translateY(-50%);
  display: inline-flex; align-items: center; gap: 4px; height: 13px; padding: 0 5px;
  max-width: calc(100% - 12px); background: var(--bg-1); color: var(--cal-tone, var(--accent));
  font-size: 9px; letter-spacing: .05em; text-transform: uppercase; white-space: nowrap;
  text-decoration: none; pointer-events: auto; }
.cc-cal-ribbon-tag .bpi-svg { color: var(--cal-tone, var(--accent)); flex: none; }
.cc-cal-ribbon-tag .x { overflow: hidden; text-overflow: ellipsis; }

/* Calendar legend — name + tone swatch for each tracked calendar, under the toolbar. */
.cc-cal-legend { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: -4px 0 16px; }
.cc-cal-legend-item { display: inline-flex; align-items: center; gap: 7px; font-size: var(--t4);
  color: var(--text-2); letter-spacing: .02em; }
.cc-cal-dot { width: 9px; height: 9px; flex: none; background: var(--cal-tone, var(--accent));
  border: 1px solid color-mix(in oklch, var(--cal-tone, var(--accent)) 55%, var(--line)); }

/* "This week" home strip — the month-grid day cell, scaled for the bento (a wide panel). */
.cc-week-strip { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.cc-week-day { border: 1px solid var(--line); background: var(--bg-2); min-height: 88px; padding: 6px;
  display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.cc-week-day.is-today { border-color: var(--accent); }
.cc-week-dow { display: flex; justify-content: space-between; align-items: baseline;
  font-size: var(--t4); color: var(--text-2); letter-spacing: .06em; text-transform: uppercase; }
.cc-week-day.is-today .cc-week-dow { color: var(--accent); }
.cc-week-dow b { color: var(--text); font-weight: 700; }
.cc-week-ev { font-size: var(--t4); color: var(--text); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; border-left: 2px solid var(--cal-tone, var(--accent-soft));
  padding-left: 5px; }
.cc-week-empty { font-size: var(--t4); color: var(--text-2); opacity: .5; }

/* Per-calendar colour picker on /connectors/gcal — a row per calendar (checkbox + a strip of tone
   swatches). The list does NOT scroll (unlike the Todoist pickers) so a swatch row never clips. */
.cc-cal-pick-list { margin-top: 10px; display: flex; flex-direction: column; gap: 2px;
  border: 1px solid var(--line); padding: 6px; }
.cc-cal-pick-row { display: flex; align-items: center; gap: 12px; padding: 4px 6px; }
.cc-cal-pick-row .bp-choice { flex: 1; min-width: 0; justify-content: flex-start; }
.cc-sw-group { display: inline-flex; gap: 4px; flex: none; }
.cc-sw { width: 16px; height: 16px; padding: 0; flex: none; cursor: pointer; background: var(--sw);
  border: 1px solid color-mix(in oklch, var(--sw) 55%, var(--line)); opacity: .45;
  transition: opacity .12s ease; }
.cc-sw:hover { opacity: .8; }
.cc-sw.on { opacity: 1; box-shadow: 0 0 0 1px var(--bg-2), 0 0 0 2px var(--sw); }
/* The swatch dot used inline in the legend/help text + the calendar dropdown options. */
.cc-swatch { display: inline-block; width: 10px; height: 10px; vertical-align: -1px;
  background: var(--sw); border: 1px solid color-mix(in oklch, var(--sw) 55%, var(--line)); }

/* Custom select — a visible keyboard-focus ring + the arrow-key "active" option (blocks.js). */
.bp-select-btn:focus-visible { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent);
  outline: none; }
.bp-menu-item.active { background: var(--accent-soft); color: var(--text); }

/* Two-up form rows (the event modal's start/end date + time pairs). The default .bp-field
   min-width (220px) would push the second field out of the ~460px modal — let them shrink. */
.cc-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.cc-form-row .bp-field { min-width: 0; }

@media (max-width: 760px) {
  .cc-cal-cell { min-height: 78px; }
  .cc-cal-ev .t { display: none; }
}

/* ---- Phone kiosk: large, touch-first, portrait-friendly (the sideloaded webview) ---- */
/* Breathing room top + bottom so content clears the notch / status bar / gesture area on a phone
   (env(safe-area-inset-*) honours the cutout — needs viewport-fit=cover, set on the kiosk page). */
/* The shared top bar is a fixed 46px, too short for the 3-line clock → it clipped. Let the kiosk
   bar grow to fit, pad it below the notch, and shrink the clock so it sits cleanly inside. */
.bp-top.cc-kiosk-top {
  height: auto;
  min-height: 58px;
  align-items: center;
  padding-top: calc(env(safe-area-inset-top, 0px) + 12px);
  padding-bottom: 10px;
}
.cc-kiosk-top .oh-clock { font-size: var(--t2); line-height: 1.15; }
.cc-kiosk-top .oh-date,
.cc-kiosk-top .oh-status-line { font-size: var(--t4); }
.cc-kiosk .bp-content-inner {
  padding-top: 28px;
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 56px);
}
.cc-kiosk .cc-kiosk-presence { display: flex; align-items: center; gap: 12px; margin: 18px 0 6px; }
.cc-kiosk .cc-prompt-card { padding: 16px; }
.cc-kiosk .cc-prompt-card .cc-prompt-title { font-size: var(--t2); font-weight: 500; }
/* Big tap targets — an old phone behind glass needs forgiving hit areas. */
.cc-kiosk .cc-prompt-card .bp-btn { min-height: 46px; padding: 0 20px; font-size: var(--t3); }
.cc-kiosk form { margin: 0; }

/* Battery indicator in the kiosk top bar. */
.cc-kiosk-battery { display: flex; align-items: center; gap: 4px; font-size: var(--t4);
  color: var(--text-2); margin-right: 12px; white-space: nowrap; }
.cc-kiosk-battery.warn { color: #d4a017; }
.cc-kiosk-battery.err { color: var(--tone-err); }

/* App launcher grid — 2 columns of square tiles. */
.cc-kiosk-app-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 22px; }
.cc-kiosk-app-tile { aspect-ratio: 1; border: 1px solid var(--line); display: flex;
  flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  text-decoration: none; color: var(--text);
  transition: border-color .14s ease, background .14s ease; cursor: pointer; position: relative; }
.cc-kiosk-app-tile:hover, .cc-kiosk-app-tile:active { border-color: var(--accent);
  background: var(--accent-soft); }
.cc-kiosk-app-tile.stub { opacity: .38; pointer-events: none; }
.cc-kiosk-app-tile-label { font-size: var(--t3); font-weight: 600; text-transform: uppercase;
  letter-spacing: .08em; }
.cc-kiosk-app-tile-badge { position: absolute; top: 10px; right: 10px; }

/* Back nav on sub-pages (e.g. watchlist). */
.cc-kiosk-back { display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
  color: var(--text-2); font-size: var(--t4); text-transform: uppercase; letter-spacing: .08em;
  margin-bottom: 18px; }
.cc-kiosk-back:hover { color: var(--text); }

/* ── /hermes full-width tab layout ─────────────────────────────────────────── */

/* Page shell: full width, no max-width cap, fills viewport height below topbar */
.cc-hermes-page {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0 20px 16px;
  box-sizing: border-box;
  width: 100%;
}

/* Section header row: title + meta on left, tab buttons on right */
.cc-hermes-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 0 12px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.cc-hermes-sec-n { color: var(--accent); font-size: var(--t4); margin-right: 2px; }
.cc-hermes-sec-t { font-size: var(--t3); font-weight: 600; text-transform: uppercase;
  letter-spacing: .08em; }
.cc-hermes-meta  { font-size: var(--t4); color: var(--text-2); margin-left: 8px; }
.cc-hermes-tabs-nav { margin-left: auto; display: flex; gap: 0; }

/* Tab buttons */
.cc-tab-btn {
  font-family: inherit;
  font-size: var(--t4);
  text-transform: uppercase;
  letter-spacing: .08em;
  background: none;
  border: 1px solid var(--line);
  color: var(--text-2);
  padding: 4px 16px;
  cursor: pointer;
  margin-left: -1px; /* collapse borders */
}
.cc-tab-btn:first-child { margin-left: 0; }
.cc-tab-btn.cc-tab-active {
  border-color: var(--accent);
  color: var(--accent);
  z-index: 1;
  position: relative;
}
.cc-tab-btn:hover:not(.cc-tab-active) { color: var(--text); border-color: var(--text-2); }

/* Tab content area: fills remaining height */
.cc-hermes-tab-wrap {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  margin-top: 0;
}
.cc-hermes-tab {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.cc-hermes-tab[hidden] { display: none !important; }

/* Session selector bar — horizontal strip above the terminal */
.cc-sess-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--line);
  border-top: 1px solid var(--line);
  background: var(--bg-1);
  flex-shrink: 0;
  overflow-x: auto;
  white-space: nowrap;
}
.cc-sess-bar-label { font-size: var(--t4); color: var(--text-2); margin-right: 4px; }

/* Individual session chips in the bar */
.cc-sess-chip {
  font-family: inherit;
  font-size: var(--t4);
  background: none;
  border: 1px solid var(--line);
  color: var(--text-2);
  padding: 2px 10px;
  cursor: pointer;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cc-sess-chip:hover  { border-color: var(--accent); color: var(--accent); }
.cc-sess-chip.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* Chip + its hover-revealed × delete box. The × is a sibling overlaid on the chip's right edge
   (room reserved via padding so it never covers the title), revealed on hover/focus like a
   browser tab's close button — no layout shift, and never a button nested inside a button. */
.cc-sess-chip-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
.cc-sess-chip-wrap .cc-sess-chip { max-width: 200px; padding-right: 22px; }
.cc-sess-x {
  position: absolute;
  right: 1px; top: 1px; bottom: 1px;
  display: flex;
  align-items: center;
  padding: 0 6px;
  font-family: inherit;
  font-size: var(--t3);
  line-height: 1;
  background: var(--bg-1);
  border: 0;
  color: var(--text-2);
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s linear;
}
.cc-sess-chip-wrap:hover .cc-sess-x,
.cc-sess-chip-wrap:focus-within .cc-sess-x { opacity: 1; }
.cc-sess-chip-wrap:has(.cc-sess-chip.active) .cc-sess-x { background: var(--accent-soft); }
.cc-sess-x:hover { color: var(--tone-err); }

/* New-session button */
.cc-sess-new {
  font-family: inherit;
  font-size: var(--t4);
  background: none;
  border: 1px solid var(--line);
  color: var(--text-2);
  padding: 2px 10px;
  cursor: pointer;
  flex-shrink: 0;
}
.cc-sess-new:hover { border-color: var(--accent); color: var(--accent); }

/* Status indicator */
.cc-term-status { font-size: var(--t4); color: var(--text-2); margin-left: auto; flex-shrink: 0; }
.cc-term-status.ok  { color: var(--tone-ok);  }
.cc-term-status.err { color: var(--tone-err); }

/* xterm container — fills all remaining height, stable dimensions for correct rendering */
#cc-hermes-term {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  /* bg-0 so xterm background blends */
  background: var(--bg-0);
  /* Small inset so the term canvas never clips the border */
  padding: 4px;
  box-sizing: border-box;
}

/* Board tab: scrollable */
.cc-hermes-board-tab {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 16px 0;
}
