scrollback 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. scrollback/__init__.py +8 -0
  2. scrollback/assets/icon-256.png +0 -0
  3. scrollback/assets/icon.icns +0 -0
  4. scrollback/cli.py +1139 -0
  5. scrollback/clipboard.py +34 -0
  6. scrollback/export.py +293 -0
  7. scrollback/fts.py +307 -0
  8. scrollback/highlight.py +128 -0
  9. scrollback/katexbundle.py +81 -0
  10. scrollback/launcher_install.py +209 -0
  11. scrollback/launchers/scrollback.bat +19 -0
  12. scrollback/launchers/scrollback.command +19 -0
  13. scrollback/launchers/scrollback.desktop +10 -0
  14. scrollback/launchers/scrollback.sh +12 -0
  15. scrollback/mathspan.py +180 -0
  16. scrollback/minimd.py +205 -0
  17. scrollback/models.py +135 -0
  18. scrollback/serialize.py +83 -0
  19. scrollback/serverconfig.py +66 -0
  20. scrollback/sources/__init__.py +6 -0
  21. scrollback/sources/aider.py +244 -0
  22. scrollback/sources/base.py +117 -0
  23. scrollback/sources/claudecode.py +631 -0
  24. scrollback/sources/codex.py +281 -0
  25. scrollback/sources/opencode.py +357 -0
  26. scrollback/sources/registry.py +39 -0
  27. scrollback/store.py +384 -0
  28. scrollback/termrender.py +170 -0
  29. scrollback/web/__init__.py +1 -0
  30. scrollback/web/app.py +359 -0
  31. scrollback/web/static/app.js +1245 -0
  32. scrollback/web/static/apple-touch-icon.png +0 -0
  33. scrollback/web/static/favicon.png +0 -0
  34. scrollback/web/static/favicon.svg +41 -0
  35. scrollback/web/static/index.html +75 -0
  36. scrollback/web/static/style.css +628 -0
  37. scrollback/web/static/vendor/highlight.min.js +1213 -0
  38. scrollback/web/static/vendor/hljs-dark.min.css +10 -0
  39. scrollback/web/static/vendor/hljs-light.min.css +10 -0
  40. scrollback/web/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  41. scrollback/web/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  42. scrollback/web/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  43. scrollback/web/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  44. scrollback/web/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  45. scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  46. scrollback/web/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  47. scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  48. scrollback/web/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  49. scrollback/web/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  50. scrollback/web/static/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  51. scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  52. scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  53. scrollback/web/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  54. scrollback/web/static/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  55. scrollback/web/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  56. scrollback/web/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  57. scrollback/web/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  58. scrollback/web/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  59. scrollback/web/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  60. scrollback/web/static/vendor/katex/katex.min.css +1 -0
  61. scrollback/web/static/vendor/katex/katex.min.js +1 -0
  62. scrollback/web/static/vendor/marked.min.js +6 -0
  63. scrollback/web/static/vendor/purify.min.js +3 -0
  64. scrollback/webopen.py +96 -0
  65. scrollback-0.1.0.dist-info/METADATA +391 -0
  66. scrollback-0.1.0.dist-info/RECORD +69 -0
  67. scrollback-0.1.0.dist-info/WHEEL +4 -0
  68. scrollback-0.1.0.dist-info/entry_points.txt +4 -0
  69. scrollback-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,628 @@
1
+ /* scrollback web UI
2
+ Design: a developer instrument for reading machine transcripts.
3
+ - Identity DATA stays monospace (ids, times, tool names, code).
4
+ - Reading TITLES + prose use a readable sans for scannability.
5
+ - Each agent has a colour "rail" so sources are distinguishable at a glance.
6
+ - Light + dark themes; performance via windowed rendering. */
7
+
8
+ :root {
9
+ --opencode: #b8791f;
10
+ --claudecode: #b5512f;
11
+ --codex: #3a8f5b;
12
+ --aider: #7a6ad1;
13
+ --focus: #1f8a9c;
14
+
15
+ --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
16
+ --ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
17
+
18
+ --rail-w: 27rem;
19
+ --bar-h: 3.25rem;
20
+ }
21
+
22
+ /* dark (default) */
23
+ :root, :root[data-theme="dark"] {
24
+ --ink: #14171d;
25
+ --panel: #1b1f27;
26
+ --panel-2: #232834;
27
+ --line: #2d333f;
28
+ --fg: #e8e5dc;
29
+ --fg-dim: #9aa0ad;
30
+ --fg-faint: #646b78;
31
+ --mark-bg: #3a4d2a;
32
+ --opencode-soft: rgba(224,164,88,.14);
33
+ --claudecode-soft: rgba(194,103,74,.14);
34
+ --codex-soft: rgba(58,143,91,.16);
35
+ --aider-soft: rgba(122,106,209,.16);
36
+ --focus-soft: rgba(95,179,196,.16);
37
+ }
38
+
39
+ /* light */
40
+ :root[data-theme="light"] {
41
+ --ink: #f6f4ee;
42
+ --panel: #ffffff;
43
+ --panel-2: #efece3;
44
+ --line: #ddd8cc;
45
+ --fg: #21242b;
46
+ --fg-dim: #5e636e;
47
+ --fg-faint: #9aa0ac;
48
+ --mark-bg: #fde68a;
49
+ --opencode: #9a6510;
50
+ --claudecode: #a8421f;
51
+ --codex: #2f7a4a;
52
+ --aider: #5b4cb8;
53
+ --focus: #0f6f7e;
54
+ --opencode-soft: rgba(154,101,16,.12);
55
+ --claudecode-soft: rgba(168,66,31,.12);
56
+ --codex-soft: rgba(47,122,74,.12);
57
+ --aider-soft: rgba(91,76,184,.12);
58
+ --focus-soft: rgba(15,111,126,.12);
59
+ }
60
+
61
+ * { box-sizing: border-box; }
62
+
63
+ html, body {
64
+ margin: 0;
65
+ height: 100%;
66
+ background: var(--ink);
67
+ color: var(--fg);
68
+ font-family: var(--ui);
69
+ font-size: 14px;
70
+ -webkit-font-smoothing: antialiased;
71
+ }
72
+
73
+ kbd {
74
+ font-family: var(--mono);
75
+ font-size: 11px;
76
+ color: var(--fg-dim);
77
+ border: 1px solid var(--line);
78
+ border-radius: 4px;
79
+ padding: 1px 6px;
80
+ background: var(--ink);
81
+ }
82
+
83
+ button { font-family: inherit; }
84
+
85
+ /* ---------- top command bar ---------- */
86
+
87
+ .bar {
88
+ height: var(--bar-h);
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 1rem;
92
+ padding: 0 1rem;
93
+ border-bottom: 1px solid var(--line);
94
+ background: var(--panel);
95
+ position: sticky;
96
+ top: 0;
97
+ z-index: 10;
98
+ }
99
+
100
+ .brand { display: flex; align-items: baseline; gap: .5rem; }
101
+ .brand-mark { font-family: var(--mono); color: var(--focus); font-weight: 700; letter-spacing: -1px; }
102
+ .brand-name { font-family: var(--mono); font-weight: 600; letter-spacing: .02em; }
103
+
104
+ .search { flex: 1; display: flex; align-items: center; gap: .5rem; max-width: 48rem; }
105
+ .search input {
106
+ flex: 1;
107
+ background: var(--ink);
108
+ border: 1px solid var(--line);
109
+ border-radius: 8px;
110
+ color: var(--fg);
111
+ font-family: var(--mono);
112
+ font-size: 13px;
113
+ padding: .5rem .75rem;
114
+ outline: none;
115
+ transition: border-color .15s, box-shadow .15s;
116
+ }
117
+ .search input:focus {
118
+ border-color: var(--focus);
119
+ box-shadow: 0 0 0 3px var(--focus-soft);
120
+ }
121
+ /* segmented search-scope control */
122
+ .search-scope {
123
+ display: flex;
124
+ border: 1px solid var(--line);
125
+ border-radius: 8px;
126
+ overflow: hidden;
127
+ flex: none;
128
+ }
129
+ .scope-btn {
130
+ font-family: var(--mono);
131
+ font-size: 11.5px;
132
+ color: var(--fg-dim);
133
+ background: var(--ink);
134
+ border: none;
135
+ padding: .45rem .7rem;
136
+ cursor: pointer;
137
+ transition: background .12s, color .12s;
138
+ }
139
+ .scope-btn + .scope-btn { border-left: 1px solid var(--line); }
140
+ .scope-btn[aria-pressed="true"] {
141
+ background: var(--focus-soft);
142
+ color: var(--focus);
143
+ font-weight: 600;
144
+ }
145
+ .scope-btn:hover { color: var(--fg); }
146
+
147
+ /* section labels inside the combined (titles + contents) result list */
148
+ .group-label {
149
+ font-family: var(--mono);
150
+ font-size: 10.5px;
151
+ text-transform: uppercase;
152
+ letter-spacing: .1em;
153
+ color: var(--fg-faint);
154
+ padding: .6rem 1rem .3rem;
155
+ border-bottom: 1px solid var(--line);
156
+ background: var(--panel);
157
+ position: sticky; top: 0;
158
+ }
159
+
160
+ .bar-right { display: flex; align-items: center; gap: .5rem; }
161
+ .icon-btn {
162
+ background: var(--ink);
163
+ border: 1px solid var(--line);
164
+ border-radius: 8px;
165
+ color: var(--fg-dim);
166
+ cursor: pointer;
167
+ padding: .4rem .55rem;
168
+ font-size: 14px;
169
+ line-height: 1;
170
+ }
171
+ .icon-btn:hover { color: var(--fg); border-color: var(--focus); }
172
+
173
+ /* ---------- filter row (clear active/inactive) ---------- */
174
+
175
+ .filters {
176
+ display: flex;
177
+ align-items: center;
178
+ gap: .6rem;
179
+ padding: .5rem 1rem;
180
+ border-bottom: 1px solid var(--line);
181
+ background: var(--ink);
182
+ flex-wrap: wrap;
183
+ }
184
+ .filters-label {
185
+ font-family: var(--mono);
186
+ font-size: 11px;
187
+ color: var(--fg-faint);
188
+ text-transform: uppercase;
189
+ letter-spacing: .08em;
190
+ }
191
+ .srcfilter { display: flex; gap: .45rem; }
192
+ .src-toggle {
193
+ font-family: var(--mono);
194
+ font-size: 12px;
195
+ border-radius: 999px;
196
+ padding: .3rem .8rem;
197
+ cursor: pointer;
198
+ display: flex;
199
+ align-items: center;
200
+ gap: .45rem;
201
+ user-select: none;
202
+ border: 1.5px solid var(--src, var(--line));
203
+ background: transparent;
204
+ color: var(--fg-dim);
205
+ transition: background .12s, color .12s, opacity .12s;
206
+ }
207
+ .src-toggle .dot {
208
+ width: 9px; height: 9px; border-radius: 50%;
209
+ background: var(--src);
210
+ box-shadow: 0 0 0 0 transparent;
211
+ }
212
+ .src-toggle .check { font-weight: 700; }
213
+ .src-toggle[aria-pressed="true"] {
214
+ background: var(--src-soft);
215
+ color: var(--fg);
216
+ }
217
+ .src-toggle[aria-pressed="false"] {
218
+ opacity: .5;
219
+ border-style: dashed;
220
+ }
221
+ .src-toggle[aria-pressed="false"] .dot { background: var(--fg-faint); }
222
+
223
+ /* a known source with no data on this machine: shown but disabled */
224
+ .src-toggle.src-unavailable {
225
+ opacity: .4;
226
+ border-style: dotted;
227
+ cursor: not-allowed;
228
+ color: var(--fg-faint);
229
+ background: transparent;
230
+ }
231
+ .src-toggle.src-unavailable .dot { background: var(--fg-faint); }
232
+ .src-toggle.src-unavailable:hover { color: var(--fg-faint); }
233
+
234
+ .date-filters { display: flex; align-items: center; gap: .35rem; margin-left: auto; }
235
+ .date-filters input {
236
+ font-family: var(--mono);
237
+ font-size: 12px;
238
+ background: var(--ink);
239
+ border: 1px solid var(--line);
240
+ border-radius: 6px;
241
+ color: var(--fg);
242
+ padding: .25rem .4rem;
243
+ color-scheme: dark light;
244
+ }
245
+ .date-filters label { font-family: var(--mono); font-size: 11px; color: var(--fg-faint); }
246
+
247
+ /* ---------- layout ---------- */
248
+
249
+ .layout {
250
+ display: grid;
251
+ grid-template-columns: var(--rail-w) 1fr;
252
+ height: calc(100vh - var(--bar-h) - 2.6rem);
253
+ }
254
+
255
+ /* ---------- session rail ---------- */
256
+
257
+ .rail { border-right: 1px solid var(--line); overflow-y: auto; background: var(--ink); }
258
+ .rail-head {
259
+ display: flex; justify-content: space-between; align-items: baseline;
260
+ padding: .55rem 1rem;
261
+ position: sticky; top: 0; background: var(--ink);
262
+ border-bottom: 1px solid var(--line); z-index: 2;
263
+ }
264
+ .count { font-family: var(--mono); color: var(--fg); font-size: 12px; }
265
+ .rail-hint { font-size: 11px; color: var(--fg-faint); text-transform: uppercase; letter-spacing: .08em; }
266
+
267
+ .sessions { list-style: none; margin: 0; padding: 0; }
268
+
269
+ .session {
270
+ position: relative;
271
+ padding: .7rem 1rem .7rem 1.2rem;
272
+ border-bottom: 1px solid var(--line);
273
+ cursor: pointer;
274
+ }
275
+ .session::before {
276
+ content: "";
277
+ position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
278
+ background: var(--src);
279
+ }
280
+ .session:hover { background: var(--panel); }
281
+ .session.active { background: var(--panel-2); }
282
+ .session.active::before { width: 4px; }
283
+
284
+ .s-title {
285
+ font-family: var(--ui);
286
+ font-size: 14.5px;
287
+ font-weight: 550;
288
+ line-height: 1.35;
289
+ color: var(--fg);
290
+ margin-bottom: .3rem;
291
+ /* clamp long titles to two readable lines */
292
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
293
+ overflow: hidden;
294
+ }
295
+ .s-meta {
296
+ font-family: var(--mono);
297
+ font-size: 11px;
298
+ color: var(--fg-dim);
299
+ display: flex; gap: .55rem; flex-wrap: wrap; align-items: center;
300
+ }
301
+ .s-src { color: var(--src); font-weight: 600; }
302
+ .s-badge {
303
+ font-size: 10px; padding: 0 .35rem; border-radius: 4px;
304
+ border: 1px solid var(--line); color: var(--fg-faint);
305
+ }
306
+ .s-dir { color: var(--fg-faint); }
307
+
308
+ /* subagent disclosure */
309
+ .s-children-toggle {
310
+ margin-top: .45rem;
311
+ font-family: var(--mono); font-size: 11px;
312
+ color: var(--focus); background: none; border: none; cursor: pointer;
313
+ padding: 0; display: inline-flex; align-items: center; gap: .3rem;
314
+ }
315
+ .s-children-toggle:hover { text-decoration: underline; }
316
+ .s-children { list-style: none; margin: .4rem 0 0; padding: 0; }
317
+ .s-child {
318
+ position: relative;
319
+ padding: .5rem .5rem .5rem 1rem;
320
+ margin-left: .25rem;
321
+ border-left: 2px solid var(--line);
322
+ cursor: pointer;
323
+ }
324
+ .s-child:hover { background: var(--panel); }
325
+ .s-child.active { background: var(--panel-2); }
326
+ .s-child .s-title { font-size: 13px; font-weight: 500; -webkit-line-clamp: 1; }
327
+ .s-child .s-meta { font-size: 10.5px; }
328
+
329
+ /* search-result variant */
330
+ .s-snippet {
331
+ font-family: var(--mono); font-size: 11.5px; color: var(--fg-dim);
332
+ margin-top: .4rem; line-height: 1.45;
333
+ }
334
+ .s-snippet mark { background: var(--mark-bg); color: var(--fg); border-radius: 2px; padding: 0 1px; }
335
+
336
+ /* ---------- reader ---------- */
337
+
338
+ /* The reader fills the pane and does not scroll itself; the open transcript
339
+ is a flex column whose header is frozen and whose message body scrolls. */
340
+ .reader { overflow: hidden; position: relative; display: flex; flex-direction: column; }
341
+
342
+ .empty {
343
+ height: 100%; display: flex; flex-direction: column;
344
+ align-items: center; justify-content: center; gap: .5rem;
345
+ color: var(--fg-dim); text-align: center; padding: 2rem;
346
+ }
347
+ .empty-mark { font-family: var(--mono); font-size: 2.5rem; color: var(--line); letter-spacing: -2px; }
348
+ .empty-sub { color: var(--fg-faint); font-size: 12px; }
349
+
350
+ /* onboarding empty-list (no sources detected) */
351
+ .empty-list { padding: 1.5rem 1rem; color: var(--fg-dim); cursor: default; }
352
+ .empty-list:hover { background: none; }
353
+ .empty-list::before { content: none; }
354
+ .empty-list-title { font-weight: 600; color: var(--fg); margin-bottom: .5rem; }
355
+ .empty-list-body { font-size: 13px; margin: .5rem 0; }
356
+ .empty-list-paths { margin: .4rem 0; padding-left: 1.2rem; }
357
+ .empty-list-paths li { margin: .25rem 0; }
358
+ .empty-list code {
359
+ font-family: var(--mono); font-size: 11.5px;
360
+ background: var(--panel-2); border-radius: 4px; padding: .1em .35em;
361
+ }
362
+
363
+ .transcript {
364
+ flex: 1; min-height: 0; /* fill the reader; allow body to shrink/scroll */
365
+ display: flex; flex-direction: column;
366
+ width: 100%; max-width: 56rem; margin: 0 auto;
367
+ }
368
+
369
+ /* frozen header */
370
+ .t-head {
371
+ flex: none;
372
+ border-left: 3px solid var(--src);
373
+ padding: 1.5rem 2rem 1rem 2rem;
374
+ border-bottom: 1px solid var(--line);
375
+ background: var(--ink);
376
+ transition: padding .15s ease;
377
+ }
378
+ .t-titlebar { display: flex; align-items: flex-start; gap: .6rem; }
379
+ .t-titlebar .t-title { flex: 1; min-width: 0; }
380
+ /* compact meta that only appears when the header is collapsed */
381
+ .t-mini-meta { display: none; }
382
+ .head-collapse {
383
+ flex: none; margin-top: .15rem;
384
+ font-size: 13px; line-height: 1; color: var(--fg-faint);
385
+ background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
386
+ padding: .25rem .5rem; cursor: pointer;
387
+ transition: color .12s, border-color .12s;
388
+ }
389
+ .head-collapse:hover { color: var(--fg); border-color: var(--focus); }
390
+
391
+ /* collapsed: hide the bulky rows, keep just the title -> more transcript room */
392
+ .head-collapsed .t-head { padding-top: .7rem; padding-bottom: .6rem; }
393
+ .head-collapsed .t-meta,
394
+ .head-collapsed .t-find,
395
+ .head-collapsed .t-actions { display: none; }
396
+ .head-collapsed .t-title {
397
+ font-size: 1.1rem; margin: 0;
398
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
399
+ }
400
+ .head-collapsed .t-mini-meta {
401
+ display: inline-flex; flex: none; gap: .6rem; align-self: center;
402
+ font-family: var(--mono); font-size: 11px; color: var(--fg-faint);
403
+ white-space: nowrap;
404
+ }
405
+ .head-collapsed .t-mini-meta .src { color: var(--src); font-weight: 600; }
406
+
407
+ /* scrolling message area */
408
+ .t-body {
409
+ flex: 1; min-height: 0; overflow-y: auto;
410
+ padding: 1rem 2rem 6rem 2rem;
411
+ }
412
+ .t-body::-webkit-scrollbar { width: 10px; }
413
+ .t-body::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }
414
+ .t-title { font-size: 1.45rem; margin: 0 0 .5rem; line-height: 1.25; font-weight: 650; }
415
+ .t-meta {
416
+ font-family: var(--mono); font-size: 12px; color: var(--fg-dim);
417
+ display: flex; flex-wrap: wrap; gap: .35rem 1rem;
418
+ }
419
+ .t-meta .src { color: var(--src); font-weight: 600; }
420
+ .t-meta .copy-id {
421
+ background: none; border: none; color: var(--focus); cursor: pointer;
422
+ font-family: var(--mono); font-size: 12px; padding: 0;
423
+ }
424
+
425
+ .t-find {
426
+ display: flex; gap: .4rem; align-items: center; margin-top: .8rem;
427
+ }
428
+ .t-find input {
429
+ font-family: var(--mono); font-size: 12px;
430
+ background: var(--ink); border: 1px solid var(--line); border-radius: 6px;
431
+ color: var(--fg); padding: .3rem .5rem; flex: 1; max-width: 18rem; outline: none;
432
+ }
433
+ .t-find input:focus { border-color: var(--focus); }
434
+ .t-find .find-count { font-family: var(--mono); font-size: 11px; color: var(--fg-faint); }
435
+
436
+ /* action bar: two stacked, labelled zones -- VIEW (how the transcript is
437
+ shown) and EXPORT (actions that produce a file/clipboard). */
438
+ .t-actions { display: flex; flex-direction: column; gap: .5rem; margin-top: .9rem; }
439
+ .bar-zone {
440
+ display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
441
+ padding-left: 3.4rem; position: relative;
442
+ }
443
+ .zone-label {
444
+ position: absolute; left: 0; top: .35rem;
445
+ font-family: var(--mono); font-size: 9.5px; font-weight: 600;
446
+ text-transform: uppercase; letter-spacing: .12em; color: var(--fg-faint);
447
+ }
448
+ .zone-note {
449
+ font-family: var(--ui); font-size: 10.5px; color: var(--fg-faint);
450
+ cursor: help;
451
+ }
452
+ .btn {
453
+ font-family: var(--mono); font-size: 11.5px; color: var(--fg);
454
+ background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
455
+ padding: .3rem .65rem; cursor: pointer;
456
+ transition: background .12s, border-color .12s;
457
+ }
458
+ .btn:hover { background: var(--panel-2); border-color: var(--focus); }
459
+ .btn .k { color: var(--fg-faint); }
460
+ .btn[aria-pressed="true"] { border-color: var(--focus); color: var(--focus); }
461
+ /* an export whose output tracks the math mode gets a subtle accent */
462
+ .btn.math-aware { border-style: dashed; }
463
+
464
+ .action-grp { display: flex; gap: .4rem; flex-wrap: wrap; align-items: center; }
465
+ .ctrl-grp {
466
+ display: flex; gap: .3rem; align-items: center;
467
+ padding: .2rem .4rem; border: 1px solid var(--line); border-radius: 8px;
468
+ background: var(--panel);
469
+ }
470
+ .ctrl-label {
471
+ font-family: var(--mono); font-size: 10px; text-transform: uppercase;
472
+ letter-spacing: .09em; color: var(--fg-faint); padding: 0 .15rem;
473
+ }
474
+
475
+ /* checkbox-style show/hide toggle: a leading box makes on/off obvious */
476
+ .toggle {
477
+ display: inline-flex; align-items: center; gap: .35rem;
478
+ font-family: var(--mono); font-size: 11.5px; color: var(--fg-dim);
479
+ background: transparent; border: 1px solid transparent; border-radius: 6px;
480
+ padding: .25rem .5rem; cursor: pointer;
481
+ transition: background .12s, color .12s, border-color .12s;
482
+ }
483
+ .toggle:hover { background: var(--panel-2); border-color: var(--line); }
484
+ .toggle .chk { font-size: 13px; line-height: 1; color: var(--fg-faint); }
485
+ .toggle.on { color: var(--focus); }
486
+ .toggle.on .chk { color: var(--focus); }
487
+
488
+ /* math dropdown */
489
+ .select {
490
+ font-family: var(--mono); font-size: 11.5px; color: var(--fg);
491
+ background: var(--panel-2); border: 1px solid var(--line); border-radius: 6px;
492
+ padding: .25rem 1.4rem .25rem .5rem; cursor: pointer;
493
+ appearance: none; -webkit-appearance: none;
494
+ background-image: linear-gradient(45deg, transparent 50%, var(--fg-faint) 50%),
495
+ linear-gradient(135deg, var(--fg-faint) 50%, transparent 50%);
496
+ background-position: right .65rem center, right .45rem center;
497
+ background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
498
+ transition: border-color .12s;
499
+ }
500
+ .select:hover, .select:focus { border-color: var(--focus); outline: none; }
501
+
502
+ /* messages */
503
+ .msg { position: relative; margin: 1.1rem 0; padding-left: 1rem; border-left: 2px solid var(--line); }
504
+ .msg.user { border-left-color: var(--focus); }
505
+
506
+ /* per-message copy button (appears in the corner on hover) */
507
+ .msg-copy {
508
+ position: absolute; top: 0; right: 0;
509
+ font-family: var(--mono); font-size: 13px; line-height: 1;
510
+ color: var(--fg-faint); background: var(--panel);
511
+ border: 1px solid var(--line); border-radius: 6px;
512
+ padding: .25rem .4rem; cursor: pointer;
513
+ opacity: 0; transition: opacity .12s, color .12s, border-color .12s;
514
+ }
515
+ .msg:hover .msg-copy, .msg-copy:focus { opacity: 1; }
516
+ .msg-copy:hover { color: var(--focus); border-color: var(--focus); }
517
+ .m-role {
518
+ font-family: var(--mono); font-size: 11px; text-transform: uppercase;
519
+ letter-spacing: .1em; color: var(--fg-dim); margin-bottom: .4rem;
520
+ display: flex; gap: .75rem; align-items: center;
521
+ }
522
+ .msg.user .m-role { color: var(--focus); }
523
+ .m-time { color: var(--fg-faint); letter-spacing: 0; }
524
+
525
+ .part { margin: .5rem 0; }
526
+ .part pre {
527
+ font-family: var(--mono); font-size: 12.5px; line-height: 1.55;
528
+ white-space: pre-wrap; word-break: break-word; margin: 0; color: var(--fg);
529
+ }
530
+ .part.text pre { font-family: var(--ui); font-size: 14px; line-height: 1.62; }
531
+
532
+ /* rendered markdown in assistant/user text parts */
533
+ .part.text.md { font-size: 14px; line-height: 1.62; }
534
+ .part.text.md > *:first-child { margin-top: 0; }
535
+ .part.text.md > *:last-child { margin-bottom: 0; }
536
+ .part.text.md h1, .part.text.md h2, .part.text.md h3,
537
+ .part.text.md h4, .part.text.md h5 {
538
+ line-height: 1.3; margin: 1.1em 0 .5em; font-weight: 650;
539
+ }
540
+ .part.text.md h1 { font-size: 1.4em; }
541
+ .part.text.md h2 { font-size: 1.25em; }
542
+ .part.text.md h3 { font-size: 1.1em; }
543
+ .part.text.md h4 { font-size: 1em; }
544
+ .part.text.md p { margin: .55em 0; }
545
+ .part.text.md ul, .part.text.md ol { margin: .5em 0; padding-left: 1.5em; }
546
+ .part.text.md li { margin: .2em 0; }
547
+ .part.text.md a { color: var(--focus); text-decoration: underline; text-underline-offset: 2px; }
548
+ .part.text.md blockquote {
549
+ margin: .6em 0; padding: .2em 0 .2em 1em;
550
+ border-left: 3px solid var(--line); color: var(--fg-dim);
551
+ }
552
+ .part.text.md hr { border: none; border-top: 1px solid var(--line); margin: 1.2em 0; }
553
+ .part.text.md code {
554
+ font-family: var(--mono); font-size: .88em;
555
+ background: var(--panel-2); border-radius: 4px; padding: .1em .35em;
556
+ }
557
+ .part.text.md pre {
558
+ font-family: var(--mono); font-size: 12.5px; line-height: 1.5;
559
+ background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
560
+ padding: .75rem .9rem; margin: .7em 0; overflow-x: auto;
561
+ }
562
+ .part.text.md pre code { background: none; padding: 0; font-size: 12.5px; }
563
+ .part.text.md table {
564
+ border-collapse: collapse; margin: .7em 0; font-size: 13px; display: block; overflow-x: auto;
565
+ }
566
+ .part.text.md th, .part.text.md td { border: 1px solid var(--line); padding: .35em .6em; text-align: left; }
567
+ .part.text.md th { background: var(--panel-2); font-weight: 600; }
568
+ .part.text.md img { max-width: 100%; border-radius: 6px; }
569
+ /* math: `latex` mode shows verbatim source as inline code; `rendered` mode
570
+ emits .math-tex spans that KaTeX replaces (display math becomes a block). */
571
+ .part.text.md .math-src {
572
+ font-family: var(--mono); font-size: .9em;
573
+ background: var(--panel-2); border-radius: 4px; padding: .1em .35em;
574
+ }
575
+ .part.text.md .math-display {
576
+ display: block; text-align: center; margin: .6em 0; overflow-x: auto;
577
+ }
578
+ .part.reasoning {
579
+ border-left: 2px solid var(--fg-faint); padding-left: .75rem;
580
+ opacity: .72; font-style: italic;
581
+ }
582
+ .part.tool {
583
+ background: var(--panel); border: 1px solid var(--line);
584
+ border-radius: 6px; padding: .5rem .7rem; margin: .6rem 0;
585
+ }
586
+ .tag {
587
+ font-family: var(--mono); font-size: 10.5px; text-transform: uppercase;
588
+ letter-spacing: .08em; color: var(--fg-faint); margin-bottom: .35rem; display: block;
589
+ }
590
+ .part.tool .tag { color: var(--opencode); }
591
+ .part.tool.is-error { border-color: var(--claudecode); }
592
+ .part.tool.is-error .tag { color: var(--claudecode); }
593
+
594
+ mark.find-hit { background: var(--mark-bg); color: var(--fg); border-radius: 2px; }
595
+ mark.find-hit.current { outline: 2px solid var(--focus); }
596
+
597
+ .load-more {
598
+ display: block; width: 100%; margin: 1rem 0;
599
+ font-family: var(--mono); font-size: 12px;
600
+ background: var(--panel); border: 1px dashed var(--line); border-radius: 8px;
601
+ color: var(--fg-dim); padding: .6rem; cursor: pointer;
602
+ }
603
+ .load-more:hover { border-color: var(--focus); color: var(--fg); }
604
+
605
+ /* hidden iframe used for printing a session */
606
+ .print-frame { position: absolute; width: 0; height: 0; border: 0; visibility: hidden; }
607
+
608
+ [hidden] { display: none !important; }
609
+
610
+ .toast {
611
+ position: fixed; bottom: 1.25rem; left: 50%; transform: translateX(-50%);
612
+ background: var(--panel-2); border: 1px solid var(--focus); color: var(--fg);
613
+ font-family: var(--mono); font-size: 12px; padding: .5rem .9rem;
614
+ border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.4); z-index: 50;
615
+ }
616
+
617
+ .loading { padding: 1.5rem 1rem; color: var(--fg-faint); font-family: var(--mono); font-size: 12px; }
618
+
619
+ .rail::-webkit-scrollbar, .reader::-webkit-scrollbar { width: 10px; }
620
+ .rail::-webkit-scrollbar-thumb, .reader::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }
621
+
622
+ @media (max-width: 880px) {
623
+ .layout { grid-template-columns: 1fr; }
624
+ .rail { display: none; }
625
+ .rail.show { display: block; position: fixed; inset: calc(var(--bar-h) + 2.6rem) 0 0 0; z-index: 20; }
626
+ .date-filters { display: none; }
627
+ }
628
+ @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }