summaryrefslogtreecommitdiff
path: root/web/assets/style.css.tmpl
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-02 23:43:08 -0300
committerGabriel Schneider <[email protected]>2026-08-03 09:54:39 -0300
commit5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e (patch)
treef12f4b700fff4ff6f7e818d2a542672ba0b8d76e /web/assets/style.css.tmpl
parenta9263daee9413c5eff3c1f1bebcd224442fd8685 (diff)
downloadnotevi-5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e.tar.gz
notevi-5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e.zip
rebrand to notevi: one CLI over the jj sidecar
vr and vrsite become a single binary. vrsite/ folds into a web package in one module (0x4200.cafe/notevi); "notevi web" serves and exports exactly what vrsite did, and "notevi read/grep/note/query" is unchanged. The sidecar is renamed with it: notevi_log, notevi-log.jsonl, and the description "private: notevi log". The pre-rebrand names are still recognized, so an old repository opens and reads; it is renamed in place on the first write, or up front with "notevi migrate DIR...". That rename cannot be a single mv inside jj run. jj only auto-tracks a *new* file in the run working copy below a size limit it does not take from the command line, so writing a whole log under a name the change has never held is silently dropped while jj reports success. ensureLogFile creates the file empty first and lets every later byte be a modification of a tracked file, which snapshots at any size; that also fixes the same latent bug when importing a large legacy vr-log.jsonl. Adds a bem-te-vi mark (favicon and nav brand) and a README. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'web/assets/style.css.tmpl')
-rw-r--r--web/assets/style.css.tmpl592
1 files changed, 592 insertions, 0 deletions
diff --git a/web/assets/style.css.tmpl b/web/assets/style.css.tmpl
new file mode 100644
index 0000000..d555950
--- /dev/null
+++ b/web/assets/style.css.tmpl
@@ -0,0 +1,592 @@
+/* generated from zed themes "{{.DarkName}}" (dark) + "{{.LightName}}" (light) */
+:root {
+{{range .DarkVars}} {{.K}}: {{.V}};
+{{end}} color-scheme: dark;
+ --note-bg: color-mix(in srgb, var(--note) 4%, transparent);
+}
+@media (prefers-color-scheme: light) {
+ :root:not([data-theme="dark"]) {
+{{range .LightVars}} {{.K}}: {{.V}};
+{{end}} color-scheme: light;
+ }
+}
+:root[data-theme="light"] {
+{{range .LightVars}} {{.K}}: {{.V}};
+{{end}} color-scheme: light;
+}
+
+* { box-sizing: border-box; }
+body {
+ margin: 0;
+ display: grid;
+ grid-template-columns: 280px minmax(0, 1fr);
+ background: var(--bg);
+ color: var(--fg);
+ font: 15px/1.5 ui-sans-serif, system-ui, sans-serif;
+}
+:root[data-mode="notes"] body.with-aside,
+body.with-aside { grid-template-columns: 280px minmax(0, 1fr) 380px; }
+:root[data-mode="trace"] body.with-aside { grid-template-columns: 280px minmax(0, 1fr); }
+
+/* ── focus: trace or notes ─────────────────────────────────────
+ The two views compete for the same pixels — line background, gutter
+ color, tree markers — so the interface shows one at a time. */
+:root[data-mode="trace"] .notes-only { display: none !important; }
+:root[data-mode="notes"] .trace-only { display: none !important; }
+:root[data-mode="notes"] ol.code li.cov { box-shadow: none; }
+:root[data-mode="notes"] ol.code li.cov::before { color: var(--ln); font-weight: 400; }
+:root[data-mode="notes"] nav a.file::after,
+:root[data-mode="notes"] .filetree a.file::after { display: none; }
+:root[data-mode="trace"] ol.code li.noted { background: none; cursor: default; }
+:root[data-mode="trace"] ol.code li.noted::before { color: var(--ln); }
+:root[data-mode="trace"] nav a.file.has-notes::before,
+:root[data-mode="trace"] .filetree a.file.has-notes::before { content: none; }
+:root[data-mode="notes"] nav a.file { color: var(--muted); }
+:root[data-mode="notes"] nav a.file.has-notes { color: var(--fg); }
+
+.modes { display: flex; gap: 0; border: 1px solid var(--border); border-radius: 5px; overflow: hidden; }
+.modes .mode {
+ background: none; border: 0; color: var(--muted);
+ font: inherit; font-size: 11px; padding: 2px 8px; cursor: pointer;
+}
+.modes .mode:hover { background: var(--hover); color: var(--fg); }
+.modes .mode[aria-pressed="true"] { background: var(--hover); color: var(--fg); font-weight: 600; }
+
+a { color: var(--accent); text-decoration: none; }
+a:hover { text-decoration: underline; }
+code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em; }
+h1 { font-size: 1.1rem; margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
+h2 { font-size: 0.95rem; margin: 1.4em 0 0.5em; }
+
+/* ── left tree ─────────────────────────────────────────────── */
+nav {
+ border-right: 1px solid var(--border);
+ background: var(--panel);
+ padding: 10px 8px;
+ position: sticky; top: 0;
+ height: 100vh; overflow: auto;
+ font-size: 13px;
+}
+nav > header {
+ font-weight: 600; margin-bottom: 10px;
+ display: flex; align-items: center; justify-content: space-between; gap: 8px;
+}
+nav > header a { color: var(--fg); }
+nav .brand { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+/* the bem-te-vi; inline so the brand keeps ellipsing its title */
+nav .brand .mark { width: 1.15em; height: 1.15em; vertical-align: -0.22em; margin-right: 0.4em; }
+#filetoggle {
+ display: block; margin: 0 0 6px auto;
+ background: none; color: var(--muted); border: 0;
+ font: inherit; font-size: 11px; padding: 1px 3px; cursor: pointer;
+}
+#filetoggle:hover { color: var(--fg); }
+:root[data-files="hidden"] #filelist { display: none; }
+
+/* ── the current change ────────────────────────────────────────
+ Everything on screen is read at one change: the tree, the code, the
+ coverage, the notes, and anything you write. It says so, always. */
+.changebar {
+ margin-bottom: 10px; padding: 7px 8px;
+ border: 1px solid var(--border); border-left: 3px solid var(--accent);
+ border-radius: 6px; background: var(--bg);
+}
+.changebar.away { border-left-color: var(--note); }
+.changebar .cid { display: flex; align-items: baseline; gap: 6px; }
+.changebar .lbl { color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; }
+.changebar .cid code { font-size: 13px; font-weight: 600; color: var(--fg); }
+.changebar .cid a code { color: var(--accent); }
+.changebar .flags { color: var(--note); font-size: 10px; font-style: italic; margin-left: auto; }
+.changebar .desc {
+ margin: 3px 0 0; font-size: 11.5px; color: var(--muted);
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+}
+.changebar .counts { margin: 5px 0 0; font-size: 11px; color: var(--muted); }
+.changebar .counts b { color: var(--fg); font-weight: 600; }
+.changebar .away { color: var(--note); }
+.changebar .scope { display: flex; gap: 0; margin-top: 7px; border: 1px solid var(--border); border-radius: 5px; overflow: hidden; }
+.changebar .scope a {
+ flex: 1; text-align: center; padding: 2px 4px;
+ font-size: 11px; color: var(--muted); text-decoration: none;
+}
+.changebar .scope a:hover { background: var(--hover); color: var(--fg); }
+.changebar .scope a.on { background: var(--hover); color: var(--fg); font-weight: 600; }
+.changebar .revjump { margin-top: 6px; }
+.changebar .revjump input { width: 100%; font-family: ui-monospace, Menlo, monospace; font-size: 11px; }
+.changebar .back { margin: 6px 0 0; font-size: 11px; }
+
+/* a note written at another change: it pins to line numbers that have moved */
+.note.away { border-style: dashed; opacity: 0.82; }
+.note.away:hover { opacity: 1; }
+.atchg.away { color: var(--note); }
+.atchg.away code { color: inherit; }
+.find, .filters input, .filters select, .notform input, .notform textarea, .jjform input, .thememenu input {
+ background: var(--bg); color: var(--fg);
+ border: 1px solid var(--border); border-radius: 5px;
+ font: inherit; font-size: 12px; padding: 3px 6px;
+}
+.find { width: 100%; margin-bottom: 6px; }
+.find:focus, .filters input:focus, .notform textarea:focus, .jjform input:focus {
+ outline: 1px solid var(--accent); border-color: var(--accent);
+}
+#tree.filtering a.file.hit { background: color-mix(in srgb, var(--accent) 14%, transparent); }
+nav details { padding-left: 10px; }
+nav summary { cursor: pointer; color: var(--muted); user-select: none; }
+nav summary:hover { color: var(--fg); }
+nav ul { list-style: none; margin: 0; padding-left: 10px; }
+nav a.file {
+ display: block; position: relative;
+ padding: 1px 6px 3px; border-radius: 4px;
+ color: var(--fg);
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
+}
+nav a.file:hover { background: var(--hover); text-decoration: none; }
+nav a.file[aria-current] { background: var(--hover); }
+nav a.file::after {
+ content: ""; position: absolute; left: 6px; bottom: 1px; height: 2px;
+ width: calc(var(--pct) - 12px); max-width: calc(100% - 12px);
+ background: var(--cov); border-radius: 1px;
+}
+nav a.file.has-notes::before { content: "◆ "; color: var(--note); font-size: 9px; vertical-align: 2px; }
+nav a.file.plain { color: var(--muted); }
+nav a.file.plain::after { display: none; }
+#tree { margin-top: 8px; }
+#tree > details { padding-left: 0; }
+summary .dot {
+ display: inline-block; width: 7px; height: 7px; border-radius: 99px;
+ background: var(--cov); margin-left: 6px;
+}
+summary .nmark { color: var(--note); font-size: 9px; margin-left: 4px; vertical-align: 1px; }
+nav details.t > summary, .filetree details.t > summary { color: var(--fg); }
+nav > footer {
+ margin-top: 14px; border-top: 1px solid var(--border); padding-top: 8px;
+ display: flex; justify-content: space-between; align-items: center; gap: 8px;
+}
+
+/* ── the theme picker ──────────────────────────────────────────
+ Every theme on the machine, previewed as you move through it. The
+ menu is fixed rather than absolute so the scrolling tree above it
+ cannot clip it. */
+#themebtn {
+ background: var(--panel); color: var(--muted);
+ border: 1px solid var(--border); border-radius: 4px;
+ font: inherit; font-size: 11px; padding: 1px 6px; cursor: pointer;
+ max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+}
+#themebtn:hover, #themebtn[aria-expanded="true"] { color: var(--fg); background: var(--hover); }
+.thememenu {
+ position: fixed; left: 8px; bottom: 8px; z-index: 20;
+ width: 264px; max-height: min(60vh, 520px);
+ display: flex; flex-direction: column; gap: 6px;
+ background: var(--panel); border: 1px solid var(--border);
+ border-radius: 7px; padding: 8px;
+ box-shadow: 0 8px 30px rgb(0 0 0 / 0.35);
+}
+.thememenu input { width: 100%; }
+.thememenu ul { list-style: none; margin: 0; padding: 0; overflow: auto; flex: 1; }
+.thememenu li {
+ display: flex; align-items: center; gap: 7px;
+ padding: 3px 5px; border-radius: 4px; cursor: pointer;
+ font-size: 12px; color: var(--fg);
+}
+/* the menu is hidden until it is opened and the filter hides rows: both are
+ the hidden attribute, which a display of our own would otherwise outrank */
+.thememenu[hidden], .thememenu li[hidden] { display: none; }
+.thememenu li.on { background: var(--hover); }
+.thememenu li .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.thememenu li .src { margin-left: auto; color: var(--muted); font-size: 10px; white-space: nowrap; }
+.thememenu li.cur .nm::after { content: " ✓"; color: var(--accent); }
+.thememenu .sw { display: flex; flex: none; border: 1px solid var(--border); border-radius: 3px; overflow: hidden; }
+.thememenu .sw i { display: block; width: 7px; height: 14px; }
+.thememenu .sw.none { width: 42px; border-color: transparent; }
+.thememenu .hint { margin: 0; font-size: 10px; }
+
+/* files.html full tree */
+.filetree { font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; padding-bottom: 40vh; }
+.filetree details { padding-left: 18px; }
+.filetree summary { cursor: pointer; color: var(--muted); }
+.filetree summary:hover { color: var(--fg); }
+.filetree ul { list-style: none; margin: 0; padding-left: 18px; }
+.filetree a.file { display: inline-block; position: relative; padding: 0 6px 2px; border-radius: 4px; color: var(--fg); }
+.filetree a.file.plain { color: var(--muted); }
+.filetree a.file:not(.plain)::after {
+ content: ""; position: absolute; left: 6px; bottom: 0; height: 2px;
+ width: calc(var(--pct) - 12px); max-width: calc(100% - 12px);
+ background: var(--cov); border-radius: 1px;
+}
+.filetree a.file.has-notes::before { content: "◆ "; color: var(--note); font-size: 9px; vertical-align: 2px; }
+.stub { padding: 24px 16px; }
+
+/* ── main ──────────────────────────────────────────────────── */
+main { min-width: 0; }
+.filehead {
+ position: sticky; top: 0; z-index: 2;
+ background: color-mix(in srgb, var(--bg) 88%, transparent);
+ backdrop-filter: blur(6px);
+ border-bottom: 1px solid var(--border);
+ padding: 10px 16px;
+}
+.meta { margin: 4px 0 0; color: var(--muted); font-size: 12.5px; }
+.meta code { color: var(--fg); }
+.greps code { margin-right: 8px; }
+.chip {
+ display: inline-block; padding: 0 8px; border-radius: 99px;
+ background: color-mix(in srgb, var(--cov) 22%, transparent);
+ border: 1px solid var(--cov); color: var(--fg); font-size: 12px;
+}
+main > section, .notegroup { padding: 0 16px; max-width: 1100px; }
+table { border-collapse: collapse; width: 100%; font-size: 13.5px; }
+th { text-align: left; color: var(--muted); font-weight: 500; }
+th, td { padding: 4px 12px 4px 0; border-bottom: 1px solid var(--border); }
+data.bar {
+ display: inline-block; min-width: 140px; padding-left: 4px;
+ font-size: 12px; color: var(--muted);
+ background: linear-gradient(90deg, color-mix(in srgb, var(--cov) 35%, transparent) var(--pct), transparent var(--pct));
+ border-radius: 3px;
+}
+
+/* ── code view ─────────────────────────────────────────────── */
+.codewrap { overflow-x: auto; }
+ol.code {
+ list-style: none; margin: 0; padding: 0;
+ font: 12.5px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
+ counter-reset: ln;
+ tab-size: 4;
+ content-visibility: auto;
+ contain-intrinsic-size: auto var(--h, auto);
+}
+ol.code:first-of-type { padding-top: 8px; }
+ol.code:last-of-type { padding-bottom: 40vh; }
+ol.code li {
+ counter-increment: ln;
+ white-space: pre;
+ min-height: 1lh;
+ padding-right: 16px;
+ /* a #L42 jump must clear the sticky header, or the line lands under it */
+ scroll-margin-top: 76px;
+}
+ol.code li::before {
+ content: counter(ln);
+ display: inline-block; width: 5ch; margin-right: 1.5ch;
+ text-align: right; color: var(--ln);
+ user-select: none;
+}
+ol.code li.cov { box-shadow: inset 3px 0 var(--cov); }
+ol.code li.cov::before { color: var(--cov); font-weight: 700; }
+ol.code li.noted { background: var(--note-bg); cursor: pointer; }
+ol.code li.noted::before { color: color-mix(in srgb, var(--note) 55%, var(--ln)); }
+ol.code li:target { background: color-mix(in srgb, var(--accent) 18%, transparent); }
+
+/* ── notes ─────────────────────────────────────────────────── */
+aside {
+ border-left: 1px solid var(--border);
+ background: var(--panel);
+ position: sticky; top: 0; height: 100vh; overflow: auto;
+ padding: 10px 12px; font-size: 13px;
+}
+aside summary { cursor: pointer; color: var(--muted); }
+.note {
+ border: 1px solid var(--border);
+ border-left: 3px solid var(--cov);
+ border-radius: 6px;
+ padding: 8px 10px; margin: 10px 0;
+ background: var(--bg);
+}
+.note header { display: flex; gap: 8px; align-items: baseline; font-size: 12px; }
+.note header a { font-family: ui-monospace, Menlo, monospace; }
+.note header em { color: var(--note); font-style: normal; border: 1px solid var(--note); border-radius: 3px; padding: 0 4px; font-size: 11px; }
+.note header b { margin-left: auto; color: var(--cov); font-weight: 600; }
+.note p { margin: 6px 0 0; line-height: 1.45; }
+.note.flash { animation: flash 1.2s ease-out; }
+
+/* A note body is markdown. It is prose about code inside a card that is
+ itself only ~320px wide in the columns, so every block here is tuned down
+ from document scale: headings barely outrank the text, rules and quotes are
+ hairlines, and nothing may push the card wider than its column. The
+ selectors carry .body so they outrank .turn p in the conversation view,
+ where notes are cards too. */
+.note .body > :first-child { margin-top: 6px; }
+.note .body p,
+.note .body ul, .note .body ol, .note .body blockquote,
+.note .body pre, .note .body table { margin: 6px 0 0; }
+.note .body p { line-height: 1.45; white-space: normal; }
+.note .body :is(h1, h2, h3, h4, h5, h6) {
+ margin: 10px 0 0; font-size: 1em; font-weight: 600; line-height: 1.3;
+}
+.note .body h1, .note .body h2 { color: var(--fg); font-size: 1.05em; }
+.note .body ul, .note .body ol { padding-left: 1.3em; }
+.note .body li { margin: 2px 0; line-height: 1.45; }
+.note .body li > p { margin: 0; }
+.note .body li::marker { color: var(--muted); }
+.note .body blockquote {
+ border-left: 2px solid var(--border); padding-left: 8px; color: var(--muted);
+}
+.note .body hr { border: none; border-top: 1px solid var(--border); margin: 10px 0 0; }
+.note .body code { background: var(--hover); border-radius: 3px; padding: 0 3px; }
+.note .body a { overflow-wrap: anywhere; }
+.note .body img { max-width: 100%; height: auto; }
+/* a fenced block: the code view's spans without its line-number scaffolding */
+.note .body pre.mdcode {
+ background: var(--panel); border: 1px solid var(--border); border-radius: 4px;
+ padding: 6px 8px; overflow-x: auto;
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em;
+ line-height: 1.4; tab-size: 4;
+}
+.note .body pre.mdcode code { background: none; padding: 0; font-size: inherit; }
+.note .body table { border-collapse: collapse; display: block; overflow-x: auto; font-size: 0.95em; }
+.note .body :is(th, td) { border: 1px solid var(--border); padding: 2px 6px; text-align: left; }
+.note .body th { color: var(--muted); font-weight: 600; }
+.note .body input[type="checkbox"] { margin-right: 4px; vertical-align: -1px; }
+@keyframes flash { 0% { background: color-mix(in srgb, var(--cov) 30%, var(--bg)); } 100% { background: var(--bg); } }
+
+.note footer { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; font-size: 11px; }
+.note footer code { color: var(--muted); }
+.note .model { opacity: 0.85; }
+.note .conv { margin-left: auto; }
+.note .parent { color: var(--muted); }
+.note .relations {
+ color: var(--note); text-decoration: none;
+ background: var(--note-bg); border-radius: 3px; padding: 0 4px;
+}
+.note .relations:hover { color: var(--accent); text-decoration: underline; }
+.notecols { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 0 14px; }
+
+/* the permalink: the one link on a card that names nothing but the note, so
+ it is the one to hand someone else. Quiet until wanted. */
+.note .perma { color: var(--ln); font-variant-numeric: tabular-nums; text-decoration: none; }
+.note .perma:hover { color: var(--accent); text-decoration: underline; }
+.note .perma.self { color: var(--muted); }
+
+/* ── one note, on its own page ─────────────────────────────────
+ What a shared link opens: the note, then the lines it is about. Wider
+ than a card in a list — nothing is competing for the room here. */
+.notepage { padding-bottom: 30vh; }
+.notepage .onenote .note { margin: 0 0 14px; max-width: 760px; }
+.notepage .exhead { margin: 0 0 6px; }
+.notepage .excerpt { border: 1px solid var(--border); border-radius: 6px; background: var(--panel); }
+.notepage .excerpt ol.code { padding: 6px 0; }
+.notepage .excerpt ol.code:last-of-type { padding-bottom: 6px; }
+.notepage .more { margin-top: 20px; }
+.filehead h1 .lns { color: var(--muted); font-weight: 400; }
+.filehead .kind { color: var(--note); font-style: normal; border: 1px solid var(--note); border-radius: 3px; padding: 0 4px; font-size: 11px; }
+.filehead .ago { color: var(--muted); }
+/* the excerpt exists to show what the note is about, so its lines stay
+ marked in either focus — the toggle is about reading a whole file */
+:root[data-mode="trace"] .excerpt ol.code li.noted { background: var(--note-bg); cursor: default; }
+:root[data-mode="trace"] .excerpt ol.code li.noted::before { color: color-mix(in srgb, var(--note) 55%, var(--ln)); }
+
+/* Incoming reply and mention edges share one recursively nested discussion.
+ The narrow rail is intentionally HN-like: enough hierarchy to scan without
+ turning the existing cards into a second design system. */
+.discussion { max-width: 820px; margin-top: 24px; scroll-margin-top: 16px; }
+.discussion h2 { margin-bottom: 6px; }
+.discussion h2 .meta { font-size: 11px; font-weight: 400; margin-left: 6px; }
+.threadnode {
+ display: grid; grid-template-columns: 20px minmax(0, 1fr);
+ align-items: stretch;
+}
+.threadedge { position: relative; color: var(--muted); text-align: center; font-size: 11px; }
+.threadedge::after {
+ content: ""; position: absolute; top: 20px; bottom: 0; left: 50%;
+ border-left: 1px solid var(--border);
+}
+.threadedge span { position: relative; z-index: 1; background: var(--bg); }
+.via-mention > .threadedge { color: var(--note); }
+.via-mention > .threadedge::after { border-left-style: dashed; border-left-color: var(--note); }
+.threadcontent { min-width: 0; }
+.threadcontent > .note { margin: 7px 0; }
+.threadchildren { min-width: 0; }
+.threadref {
+ margin: 8px 0; padding: 5px 8px; border: 1px dashed var(--border);
+ border-radius: 5px; background: var(--panel);
+}
+.replybox { max-width: 760px; margin: 4px 0 10px; }
+.replybox > summary { cursor: pointer; color: var(--muted); font-size: 11px; }
+.replybox[open] > summary { color: var(--accent); }
+.replyform { margin: 5px 0 10px; padding-left: 8px; border-left: 2px solid var(--border); }
+.replyform textarea { width: 100%; box-sizing: border-box; }
+.threadnode .replybox { margin-left: 1px; }
+
+/* ── conversation ──────────────────────────────────────────── */
+.convpage { padding: 0 16px 40vh; max-width: 1100px; }
+.turn {
+ display: grid; grid-template-columns: 12ch 8ch minmax(0, 1fr);
+ gap: 0 10px; align-items: baseline;
+ padding: 2px 0 2px 8px; border-left: 2px solid transparent;
+}
+.turn .role {
+ color: var(--muted); font-size: 11px; text-align: right;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+}
+.turn time { color: var(--ln); font-size: 11px; font-variant-numeric: tabular-nums; }
+.turn p { margin: 0; line-height: 1.5; white-space: pre-wrap; }
+.turn .what {
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+}
+.t-say { padding: 8px 0 8px 8px; }
+.t-user { border-left-color: var(--accent); }
+.t-user .role, .t-user p { color: var(--fg); }
+.t-assistant .role { color: var(--cov); }
+.t-thinking p { color: var(--muted); font-style: italic; }
+.t-tool .role { color: var(--note); }
+.t-result .what { color: var(--muted); }
+.t-result.bad .what { color: var(--note); }
+.t-notevi { border-left-color: var(--cov); background: color-mix(in srgb, var(--cov) 7%, transparent); }
+.t-notevi .role { color: var(--cov); font-weight: 600; }
+/* A note is what you came to the conversation to find, so it breaks the run
+ of log lines: the note color's rail, a tinted band, a card instead of a
+ line. scroll-margin clears the sticky header — the anchored one is
+ scrolled to on load. */
+.t-note {
+ padding: 8px 0 8px 8px;
+ border-left-color: var(--note);
+ background: color-mix(in srgb, var(--note) 7%, transparent);
+}
+.t-note .role { color: var(--note); font-weight: 600; }
+.t-note .role::before { content: "◆ "; font-size: 9px; vertical-align: 1px; }
+.t-note .note { max-width: 640px; margin: 0; scroll-margin-top: 96px; }
+.t-note.here { background: color-mix(in srgb, var(--accent) 12%, transparent); }
+.t-note.here .note { border-color: var(--accent); border-left-color: var(--accent); }
+/* the whole card is the way back to the code; the links inside still work */
+.notewrap { cursor: pointer; }
+.notewrap:hover .note, .notewrap:focus-visible .note {
+ border-color: var(--accent); background: var(--hover);
+}
+.notewrap:focus-visible { outline: none; }
+.notewrap[data-href] .note::after {
+ content: "open in source →"; display: block;
+ margin-top: 6px; color: var(--accent); font-size: 11px; opacity: 0;
+}
+.notewrap:hover .note::after, .notewrap:focus-visible .note::after { opacity: 1; }
+
+/* ── live activity ─────────────────────────────────────────── */
+.activity { margin-bottom: 10px; }
+.actlist { display: flex; flex-direction: column; gap: 3px; }
+.act {
+ display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
+ gap: 6px; align-items: baseline; font-size: 11px; color: var(--muted);
+ opacity: 0.55;
+}
+.act.on { opacity: 1; }
+.act .what, .acttable .what {
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+ font-family: ui-monospace, Menlo, monospace;
+}
+.acttable tr.on td { color: var(--fg); }
+.acttable .pulse { color: var(--accent); font-size: 9px; margin-left: 5px; animation: pulse 2s ease-in-out infinite; }
+@keyframes pulse { 50% { opacity: 0.25; } }
+.acttable .dir { max-width: 18ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+
+/* ── filters / forms ───────────────────────────────────────── */
+.filters {
+ display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
+ padding: 10px 0; position: sticky; top: 0; z-index: 1;
+ background: var(--bg);
+}
+.filters input[type="search"] { min-width: 15ch; flex: 1 1 18ch; }
+.filters .dates { display: flex; gap: 6px; align-items: center; }
+.filters .datef { font-size: 11px; color: var(--muted); display: flex; gap: 4px; align-items: center; }
+.filters button, .jjform button, .notform button {
+ background: var(--panel); color: var(--fg);
+ border: 1px solid var(--border); border-radius: 5px;
+ font: inherit; font-size: 12px; padding: 3px 10px; cursor: pointer;
+}
+.filters button:hover, .jjform button:hover, .notform button:hover { background: var(--hover); }
+.count { margin: 0 0 8px; }
+.busy { visibility: hidden; color: var(--accent); margin-right: 6px; }
+.busy.htmx-request { visibility: visible; animation: pulse 1s linear infinite; }
+.notespage, .jjpage { padding: 0 16px 40vh; max-width: 1100px; }
+.err {
+ margin: 12px 16px; padding: 8px 10px; border-radius: 6px;
+ border: 1px solid var(--note); color: var(--note);
+ font-family: ui-monospace, Menlo, monospace; font-size: 12.5px; white-space: pre-wrap;
+}
+
+/* ── add a note ────────────────────────────────────────────── */
+.addnote { margin: 10px 0; }
+.addnote > summary { cursor: pointer; color: var(--muted); font-size: 13px; }
+.notform { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 14px; }
+.notform .row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
+.notform .row.end { justify-content: space-between; }
+.notform input[type="number"] { width: 8ch; }
+.notform textarea { resize: vertical; font-family: inherit; }
+.notform .on code { color: var(--fg); }
+.asidehead { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
+aside .meta { font-size: 11px; }
+
+/* lines picked to comment on, and lines you arrived at from a note */
+ol.code li.sel, ol.code li.focus { background: color-mix(in srgb, var(--accent) 16%, transparent); }
+ol.code li.sel::before, ol.code li.focus::before { color: var(--accent); }
+ol.code li.focus { box-shadow: inset 3px 0 var(--accent); }
+
+/* ── jj ────────────────────────────────────────────────────── */
+.jjform { display: flex; gap: 6px; align-items: center; padding: 10px 0; }
+.jjform input[type="search"] { flex: 1; font-family: ui-monospace, Menlo, monospace; }
+.jjform input[type="number"] { width: 8ch; }
+.jjlog { font: 12.5px/1.7 ui-monospace, SFMono-Regular, Menlo, monospace; }
+.jjrow { display: flex; gap: 8px; align-items: baseline; white-space: pre; }
+.jjrow:hover { background: var(--hover); }
+.jjrow .graph { color: var(--muted); white-space: pre; }
+.jjrow .id { font-weight: 600; }
+.jjrow .commit { color: var(--muted); }
+.jjrow .when, .jjrow .who { color: var(--muted); }
+.jjrow .refs { color: var(--note); }
+.jjrow .flags { color: var(--note); font-style: italic; }
+.jjrow .desc { color: var(--fg); overflow: hidden; text-overflow: ellipsis; }
+.jjrow.blank { color: var(--muted); }
+pre.desc {
+ margin: 0 0 12px; padding: 10px 12px; border-radius: 6px;
+ border: 1px solid var(--border); background: var(--panel);
+ font: 12.5px/1.5 ui-monospace, Menlo, monospace; white-space: pre-wrap;
+}
+table.changed { width: auto; }
+table.changed td { border: none; padding: 1px 12px 1px 0; }
+table.changed .st { color: var(--muted); font-size: 11px; width: 7ch; }
+table.changed .st-added { color: var(--accent); }
+table.changed .st-deleted { color: var(--note); }
+
+/* ── project hub ───────────────────────────────────────────── */
+.projects-main { padding-bottom: 20vh; }
+.projects-main > section { max-width: 1200px; }
+.project-refresh { margin: 10px 0; }
+.project-refresh button, .project-card button, .project-action {
+ display: inline-block; background: var(--panel); color: var(--fg);
+ border: 1px solid var(--border); border-radius: 5px;
+ font: inherit; font-size: 12px; padding: 5px 10px; cursor: pointer;
+}
+.project-refresh button:hover, .project-card button:hover, .project-action:hover {
+ background: var(--hover); text-decoration: none;
+}
+.project-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 10px; }
+.project-card { min-width: 0; padding: 12px; border: 1px solid var(--border); border-radius: 7px; background: var(--panel); }
+.project-card > header { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
+.project-card h3 { min-width: 0; margin: 0; font: 600 14px/1.3 ui-monospace, Menlo, monospace; overflow: hidden; text-overflow: ellipsis; }
+.project-path { margin: 8px 0; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.project-state { flex: none; padding: 1px 6px; border: 1px solid var(--border); border-radius: 99px; color: var(--muted); font-size: 10px; text-transform: uppercase; }
+.project-state.active { border-color: var(--accent); color: var(--accent); }
+.project-state.git { color: var(--note); }
+.project-card form, .project-card .project-action { margin-top: 10px; }
+.project-message { max-width: 1168px; }
+.ok { margin: 12px 16px; padding: 8px 10px; border: 1px solid var(--accent); border-radius: 6px; color: var(--accent); }
+.project-card[hidden] { display: none; }
+
+/* ── agent coverage colors ──────────────────────────────────
+ A bitmask is its agents' colors averaged. The blend is left to the
+ browser so these rules hold for any theme: a theme only has to name
+ --ag0..7, which every theme in themes.css does. */
+{{range .Masks}}.am{{.Mask}} { --cov: {{.Mix}}; }
+{{end}}
+/* ── syntax (theme colors, italics and weights are vars too, so
+ switching theme switches those with the colors) ─────────── */
+{{range .Syntax}}.s-{{.Class}} { color: var(--s-{{.Class}}, inherit); font-style: var(--s-{{.Class}}-fs, inherit); font-weight: var(--s-{{.Class}}-fw, inherit); }
+{{end}}
+@media (max-width: 1100px) {
+ body, body.with-aside { grid-template-columns: 1fr; }
+ nav, aside { position: static; height: auto; border: none; border-bottom: 1px solid var(--border); }
+}
+@media (max-width: 620px) {
+ .threadnode { grid-template-columns: 14px minmax(0, 1fr); }
+ .discussion h2 .meta { display: block; margin: 2px 0 0; }
+ .replyform { padding-left: 5px; }
+}