@oharness/server 0.7.1 → 0.9.0

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 (73) hide show
  1. package/dist/browser-pane.d.ts +48 -0
  2. package/dist/browser-pane.js +185 -0
  3. package/dist/browser-pane.js.map +1 -0
  4. package/dist/files.d.ts +40 -1
  5. package/dist/files.js +94 -3
  6. package/dist/files.js.map +1 -1
  7. package/dist/i18n.js +190 -0
  8. package/dist/i18n.js.map +1 -1
  9. package/dist/main.js +20 -0
  10. package/dist/main.js.map +1 -1
  11. package/dist/page/client/account.d.ts +8 -0
  12. package/dist/page/client/account.js +1895 -0
  13. package/dist/page/client/account.js.map +1 -0
  14. package/dist/page/client/artifacts.d.ts +7 -0
  15. package/dist/page/client/artifacts.js +767 -0
  16. package/dist/page/client/artifacts.js.map +1 -0
  17. package/dist/page/client/base.d.ts +8 -0
  18. package/dist/page/client/base.js +404 -0
  19. package/dist/page/client/base.js.map +1 -0
  20. package/dist/page/client/browser.d.ts +21 -0
  21. package/dist/page/client/browser.js +906 -0
  22. package/dist/page/client/browser.js.map +1 -0
  23. package/dist/page/client/composer.d.ts +8 -0
  24. package/dist/page/client/composer.js +1714 -0
  25. package/dist/page/client/composer.js.map +1 -0
  26. package/dist/page/client/dialogs.d.ts +7 -0
  27. package/dist/page/client/dialogs.js +724 -0
  28. package/dist/page/client/dialogs.js.map +1 -0
  29. package/dist/page/client/events.d.ts +7 -0
  30. package/dist/page/client/events.js +288 -0
  31. package/dist/page/client/events.js.map +1 -0
  32. package/dist/page/client/files.d.ts +7 -0
  33. package/dist/page/client/files.js +412 -0
  34. package/dist/page/client/files.js.map +1 -0
  35. package/dist/page/client/index.d.ts +16 -0
  36. package/dist/page/client/index.js +50 -0
  37. package/dist/page/client/index.js.map +1 -0
  38. package/dist/page/client/panels.d.ts +7 -0
  39. package/dist/page/client/panels.js +659 -0
  40. package/dist/page/client/panels.js.map +1 -0
  41. package/dist/page/client/pickers.d.ts +7 -0
  42. package/dist/page/client/pickers.js +624 -0
  43. package/dist/page/client/pickers.js.map +1 -0
  44. package/dist/page/client/routines.d.ts +6 -0
  45. package/dist/page/client/routines.js +928 -0
  46. package/dist/page/client/routines.js.map +1 -0
  47. package/dist/page/client/shell.d.ts +6 -0
  48. package/dist/page/client/shell.js +700 -0
  49. package/dist/page/client/shell.js.map +1 -0
  50. package/dist/page/client/state.d.ts +7 -0
  51. package/dist/page/client/state.js +1283 -0
  52. package/dist/page/client/state.js.map +1 -0
  53. package/dist/page/client/terminal.d.ts +7 -0
  54. package/dist/page/client/terminal.js +347 -0
  55. package/dist/page/client/terminal.js.map +1 -0
  56. package/dist/page/client/transcript.d.ts +8 -0
  57. package/dist/page/client/transcript.js +866 -0
  58. package/dist/page/client/transcript.js.map +1 -0
  59. package/dist/page/client/usage.d.ts +7 -0
  60. package/dist/page/client/usage.js +954 -0
  61. package/dist/page/client/usage.js.map +1 -0
  62. package/dist/page/style.d.ts +1 -0
  63. package/dist/page/style.js +3016 -0
  64. package/dist/page/style.js.map +1 -0
  65. package/dist/page.js +96 -13871
  66. package/dist/page.js.map +1 -1
  67. package/dist/server.d.ts +24 -0
  68. package/dist/server.js +492 -23
  69. package/dist/server.js.map +1 -1
  70. package/dist/terminal.d.ts +34 -0
  71. package/dist/terminal.js +211 -0
  72. package/dist/terminal.js.map +1 -0
  73. package/package.json +9 -8
@@ -0,0 +1,3016 @@
1
+ import { THEME_CSS } from "../theme.js";
2
+ // The palette lives in theme.ts; everything below reads it through var().
3
+ export const STYLE = String.raw `
4
+ ${THEME_CSS}
5
+ *{box-sizing:border-box}
6
+ /* The hidden attribute works through the browser's own display:none, which is
7
+ a bare element rule any class with a display beats — so .hidden = true left
8
+ the node on screen wherever one had been set. It was guarded per class for a
9
+ while, which meant every new flex box was one forgotten line away from the
10
+ same bug, and the sign-in dialog had already found it twice: the account
11
+ column and the website button both painted for the vendors they are not for.
12
+ Once, here, rather than as a rule to remember. */
13
+ [hidden]{display:none!important}
14
+ /* The native scrollbar draws a filled track with a hairline down its edge,
15
+ which reads as a frame boxing in whatever is scrolling. Ours is a thumb on
16
+ nothing: transparent track, and the inset comes from a transparent border
17
+ so the thumb keeps its full rounded shape. */
18
+ ::-webkit-scrollbar{width:11px;height:11px}
19
+ ::-webkit-scrollbar-track,::-webkit-scrollbar-corner{background:transparent}
20
+ ::-webkit-scrollbar-thumb{
21
+ background:var(--track);background-clip:content-box;
22
+ border:3px solid transparent;border-radius:8px;
23
+ }
24
+ ::-webkit-scrollbar-thumb:hover{background:var(--dim);background-clip:content-box}
25
+ html,body{height:100%}
26
+ body{
27
+ margin:0;background:var(--bg);color:var(--fg);display:flex;height:100dvh;overflow:hidden;
28
+ font:15px/1.7 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Inter,sans-serif;
29
+ -webkit-font-smoothing:antialiased;
30
+ }
31
+ code,pre,.mono{font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace}
32
+
33
+ /* sidebar */
34
+ aside{
35
+ width:236px;flex:0 0 236px;background:var(--panel);
36
+ display:flex;flex-direction:column;overflow:hidden;transition:flex-basis .18s,width .18s;
37
+ }
38
+ /* Folded away on a desktop. Zero width rather than display:none, for the
39
+ slide, and so nothing inside has to be rebuilt when it comes back. */
40
+ body.nav-collapsed>aside{width:0;flex-basis:0}
41
+ #nav-collapse,#nav-expand{
42
+ width:28px;height:28px;border:0;border-radius:7px;background:transparent;color:var(--dim);
43
+ cursor:pointer;padding:0;display:grid;place-items:center;flex:0 0 auto;-webkit-app-region:no-drag;
44
+ }
45
+ #nav-collapse{margin-left:auto}
46
+ #nav-collapse:hover,#nav-expand:hover{background:var(--hover);color:var(--fg)}
47
+ #nav-collapse svg,#nav-expand svg{width:16px;height:16px;display:block;shape-rendering:geometricPrecision}
48
+ #nav-expand{display:none;position:absolute;z-index:5;top:7px;left:8px}
49
+ body.nav-collapsed #nav-expand{display:grid}
50
+ body.nav-collapsed #tabs{padding-left:44px}
51
+ /* The traffic lights sit where the sidebar's corner was; step past them. */
52
+ body.inset-titlebar.nav-collapsed #nav-expand{left:80px}
53
+ body.inset-titlebar.nav-collapsed #tabs{padding-left:116px;-webkit-app-region:drag}
54
+ body.inset-titlebar.nav-collapsed #tabs>*{-webkit-app-region:no-drag}
55
+ aside header{padding:15px 14px 8px;display:flex;gap:8px;align-items:center}
56
+ /* The desktop shell hides the title bar, so on macOS the traffic lights float
57
+ * over this corner. The wordmark moves below them, and the strip they sit in
58
+ * becomes the window's drag handle — without it a title-bar-less window has
59
+ * nothing to grab. */
60
+ body.inset-titlebar aside header{padding-top:38px;-webkit-app-region:drag}
61
+ .mark{width:18px;height:18px;color:var(--fg);opacity:.85;flex:0 0 auto}
62
+ .mark svg{width:18px;height:18px;display:block}
63
+ /* Slightly tightened and a touch heavier than body text, which is what makes
64
+ * a wordmark read as a name rather than as a heading. */
65
+ .wordmark{font-size:14px;font-weight:650;letter-spacing:-.02em}
66
+ .project{
67
+ display:flex;align-items:center;gap:8px;width:calc(100% - 16px);margin:0 8px 6px;
68
+ padding:6px 8px;border:0;border-radius:8px;background:transparent;color:var(--fg);
69
+ font:inherit;font-size:13px;cursor:pointer;text-align:left;
70
+ }
71
+ .project:hover{background:var(--hover)}
72
+ .avatar{
73
+ width:18px;height:18px;border-radius:5px;background:var(--accent);color:var(--bg);
74
+ font-size:10px;font-weight:650;display:grid;place-items:center;flex:0 0 auto;
75
+ }
76
+ .project-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
77
+ .project .chev{opacity:.45}
78
+ /* Held apart from the nav above it: those five rows are the app's furniture
79
+ and everything below is the work, and with four pixels between them the
80
+ first project read as a sixth nav item. */
81
+ #sessions{flex:1;overflow-y:auto;padding:4px 8px 8px;margin-top:10px}
82
+ /*
83
+ * A project heads its sessions as a row like any other, not as a fold with a
84
+ * disclosure arrow: a folder is already the thing that opens, so the arrow was
85
+ * a second control saying what the icon and the rows underneath it say.
86
+ */
87
+ .fold{
88
+ display:flex;align-items:center;gap:8px;width:100%;height:30px;padding:0 8px;
89
+ border:0;border-radius:7px;background:transparent;color:var(--fg);
90
+ font:inherit;font-size:13px;cursor:pointer;text-align:left;
91
+ }
92
+ .fold:hover{background:var(--hover)}
93
+ /* An open project keeps its folder lit — with the arrow gone, that and the
94
+ * rows underneath are what say it is open. */
95
+ .fold .ico-sm{width:16px;height:16px;flex:0 0 auto;opacity:.5}
96
+ .fold.open .ico-sm,.fold:hover .ico-sm{opacity:.9}
97
+ .fold-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
98
+ /* The routine group's run count, where a project header keeps its actions. */
99
+ .fold .count{flex:0 0 auto;font-size:11px;color:var(--dim);padding-right:4px}
100
+ /* A gap between projects, but not above the first one. The selector names the
101
+ row rather than the button since the header grew a menu control beside it. */
102
+ .fold-row + .fold-row,.session + .fold-row,.show-more + .fold-row{margin-top:6px}
103
+ /* Nested rows line up with the project's name, not with its folder. */
104
+ .session.nested{padding-left:32px}
105
+ /* The pin, on the row it holds up. Beside the name rather than out at the
106
+ edge, where the count and the ⋯ already live and a third thing would crowd
107
+ them; dim, because it marks a row rather than asking for attention. */
108
+ .pin-mark{
109
+ width:11px;height:11px;flex:0 0 auto;margin-left:5px;opacity:.5;
110
+ vertical-align:-1px;display:inline-block;
111
+ }
112
+ /* Dragging: the row being carried fades, and the one under the pointer grows
113
+ the edge it would be dropped against. A line on the row rather than a
114
+ floating element, because this list is rebuilt on every repaint and a
115
+ separate indicator would have to be hunted down and removed each time. */
116
+ .session.dragging,.fold-row.dragging{opacity:.4}
117
+ .session.drop-before,.fold-row.drop-before{box-shadow:inset 0 2px 0 -1px var(--accent)}
118
+ .session.drop-after,.fold-row.drop-after{box-shadow:inset 0 -2px 0 -1px var(--accent)}
119
+ /*
120
+ * The tail of a long project, one click away. A project the user is living in
121
+ * can hold dozens of sessions, and the sidebar is also how every *other*
122
+ * project is reached — so each one shows its recent handful and says so.
123
+ */
124
+ .show-more{
125
+ height:26px;padding:0 8px;border:0;border-radius:7px;background:transparent;
126
+ color:var(--dim);font:inherit;font-size:12.5px;cursor:pointer;text-align:left;
127
+ display:flex;align-items:center;width:100%;
128
+ }
129
+ .show-more.nested{padding-left:32px}
130
+ .show-more:hover{background:var(--hover);color:var(--fg)}
131
+ /*
132
+ * One line, not two. The previous version reserved a second row for metadata
133
+ * and then hid it with opacity, so every entry stood twice as tall as the text
134
+ * it showed and the list read as broken spacing.
135
+ */
136
+ .session{
137
+ height:30px;padding:0 8px;border-radius:7px;cursor:pointer;
138
+ display:flex;align-items:center;gap:8px;
139
+ }
140
+ .session:hover{background:var(--hover)}
141
+ .session.active{background:var(--sel)}
142
+ .session .title{
143
+ flex:1;min-width:0;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
144
+ }
145
+ .session .sub{font-size:11px;color:var(--dim);flex:0 0 auto}
146
+ /*
147
+ * The overflow button takes the metadata's place on hover rather than sitting
148
+ * beside it: the row is one line and already full. It stays visible while its
149
+ * menu is open, so the row does not appear to lose the control the menu
150
+ * belongs to when the pointer moves down onto it.
151
+ */
152
+ .session:hover .sub,.session.menu-open .sub{display:none}
153
+ .session .more{
154
+ display:none;width:20px;height:20px;border:0;border-radius:5px;background:transparent;
155
+ color:var(--dim);cursor:pointer;padding:0;place-items:center;flex:0 0 auto;
156
+ }
157
+ .session:hover .more,.session.menu-open .more{display:grid}
158
+ .session .more:hover,.session.menu-open .more{background:var(--sel);color:var(--fg)}
159
+ .session .more .ico-sm{width:14px;height:14px;opacity:1}
160
+ /*
161
+ * Anchored in viewport coordinates, not inside the row: the list scrolls and
162
+ * clips, so a menu positioned within it would be cut off at the edges.
163
+ */
164
+ .row-menu{
165
+ position:fixed;z-index:60;min-width:172px;max-width:228px;padding:5px;border-radius:10px;
166
+ background:var(--bg);box-shadow:0 1px 2px #0000000f,0 8px 28px #00000024;
167
+ }
168
+ @media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .row-menu{box-shadow:0 1px 2px #0000004d,0 8px 28px #00000080}}
169
+ :root[data-theme="dark"] .row-menu{box-shadow:0 1px 2px #0000004d,0 8px 28px #00000080}
170
+ /* The account menu is as wide as the account row it opens from, edge to edge.
171
+ Sized for a session row's three commands, it came out a third narrower than
172
+ the row under it, so the two read as unrelated boxes; the width is set on
173
+ open from the row itself, and this lifts the caps that would clamp it. */
174
+ .row-menu.fit-anchor{min-width:0;max-width:none}
175
+ .row-menu.account-menu{
176
+ padding:10px;max-height:calc(100vh - 24px);overflow-y:auto;
177
+ }
178
+ /*
179
+ * Destructive is carried by the label's colour, not by a filled red bar. A
180
+ * solid block reads as an alert that has already happened rather than as a
181
+ * choice, and it fights every other menu on the page for attention.
182
+ */
183
+ .row-menu .danger{color:var(--err)}
184
+ .row-menu .danger:hover,.row-menu .danger.armed{background:var(--hover)}
185
+ /* What the next click actually does, above the button that does it. */
186
+ .row-menu .menu-note{
187
+ padding:4px 8px 8px;font-size:11.5px;line-height:1.45;color:var(--dim);
188
+ }
189
+ /* The account's own two facts, on a surface of their own. A fill separates
190
+ them from the things to click without drawing a line to do it — the menu
191
+ already had two rules in it and a third would have made a list of five
192
+ entries look like three lists. */
193
+ .row-menu .menu-head{
194
+ padding:9px 10px;margin-bottom:4px;border-radius:8px;background:var(--field);
195
+ }
196
+ /* A section label, not a surface: the fill belongs to the account's facts
197
+ above, and on a one-word caption it read as a second card pressed against
198
+ Dashboard. Space above it does the separating instead. */
199
+ .row-menu .menu-head.menu-section{
200
+ margin:10px 0 2px;padding:4px 10px;background:transparent;
201
+ }
202
+ .menu-head-main{font-size:14px;font-weight:500;color:var(--fg);font-variant-numeric:tabular-nums}
203
+ .menu-head-sub{margin-top:2px;font-size:12px;color:var(--dim)}
204
+ /* The billing card borrows .menu-head's surface but not its caption type: the
205
+ uppercase and tracking belong to section labels, and on a balance and a
206
+ plan name they turned two facts into one shouted line. */
207
+ .row-menu .menu-head.account-card{text-transform:none;letter-spacing:0;font-weight:400;font-size:12.5px}
208
+ .menu-head-top{display:flex;align-items:center;justify-content:space-between;gap:8px}
209
+ .menu-head-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dim)}
210
+ .plan-pill{
211
+ display:inline-flex;align-items:center;gap:5px;flex:0 0 auto;max-width:60%;
212
+ padding:2px 8px;border-radius:999px;background:var(--field);color:var(--dim);
213
+ font-size:11.5px;font-weight:500;white-space:nowrap;
214
+ }
215
+ .plan-pill-dot{width:6px;height:6px;flex:0 0 auto;border-radius:50%;background:currentColor;opacity:.7}
216
+ .plan-pill-name{overflow:hidden;text-overflow:ellipsis}
217
+ .plan-pill-status{font-weight:400;opacity:.8}
218
+ /* Live is the brand's lime, the colour the app gives to things that are on. */
219
+ .plan-pill.ok{background:var(--brand-tint);color:var(--brand-ink)}
220
+ .plan-pill.ok .plan-pill-dot{background:var(--brand);opacity:1}
221
+ .plan-pill.warn{background:color-mix(in srgb,var(--warn) 16%,transparent);color:var(--warn)}
222
+ .plan-pill.warn .plan-pill-dot{opacity:1}
223
+ .menu-head-amount{display:flex;align-items:baseline;gap:5px;margin-top:6px}
224
+ .menu-head-num{font-size:20px;font-weight:600;letter-spacing:-.01em;line-height:1.2;color:var(--fg);font-variant-numeric:tabular-nums}
225
+ .menu-head-unit{font-size:12px;color:var(--dim)}
226
+ .menu-billing-actions{display:flex;gap:7px;margin-top:10px}
227
+ .menu-billing-action{
228
+ flex:1;min-height:30px;padding:6px 10px;border:0;border-radius:7px;
229
+ background:var(--bg);color:var(--fg);box-shadow:inset 0 0 0 1px var(--line);
230
+ font:inherit;font-size:12.5px;font-weight:500;cursor:pointer;
231
+ }
232
+ .menu-billing-action:hover{background:var(--hover)}
233
+ .menu-billing-action.primary{background:var(--accent);color:var(--bg);box-shadow:none}
234
+ .menu-billing-action.primary:hover{opacity:.88}
235
+ .menu-billing-action:disabled{cursor:not-allowed;opacity:.45}
236
+ /* One entry carrying the accent, for the one that is an offer rather than a
237
+ destination. var(--field) was too close to the menu's own surface to read as
238
+ anything in the light theme — a highlight nobody can see is worse than none,
239
+ because it costs the layout and returns nothing. */
240
+ .menu-item.accent{background:var(--accent);color:var(--bg)}
241
+ .menu-item.accent:hover{background:var(--accent);opacity:.88}
242
+ /* The theme row: a label and three targets, laid out like a settings line
243
+ rather than a command. */
244
+ .menu-theme{
245
+ display:flex;align-items:center;justify-content:space-between;gap:8px;
246
+ padding:4px 8px;min-height:32px;
247
+ }
248
+ .theme-group{display:flex;gap:2px;padding:2px;border-radius:8px;background:var(--field)}
249
+ .theme-btn{
250
+ display:grid;place-items:center;width:26px;height:22px;border:0;border-radius:6px;
251
+ background:transparent;color:var(--dim);cursor:pointer;padding:0;
252
+ }
253
+ .theme-btn:hover{color:var(--fg)}
254
+ .theme-btn[aria-pressed="true"]{background:var(--bg);color:var(--fg)}
255
+ .theme-btn .ico-sm{width:14px;height:14px}
256
+ /* The letter that does the same thing without the mouse. Dim, right-aligned,
257
+ and only ever one character — a menu whose right edge is a column of chords
258
+ is a keyboard reference, not a menu. */
259
+ .row-menu .menu-key{
260
+ margin-left:auto;font:inherit;font-size:11px;color:var(--dim);opacity:.75;
261
+ flex:0 0 auto;padding-left:12px;
262
+ }
263
+ .row-menu .danger .menu-key{color:var(--dim)}
264
+ /* A rule, not a gap: the group below it is the one that cannot be undone. */
265
+ .row-menu .menu-sep{height:1px;margin:5px 8px;background:var(--line)}
266
+ /* Sized from the title it stands in for, so the row does not jump when a
267
+ rename starts. */
268
+ .session .title-edit{
269
+ width:100%;border:0;border-radius:5px;padding:0;margin:0;
270
+ background:var(--field-focus);color:var(--fg);font:inherit;font-size:inherit;
271
+ }
272
+ .session .title-edit:focus{outline:none}
273
+ #nav{padding:2px 8px 4px;display:flex;flex-direction:column;gap:1px}
274
+ .nav-item{
275
+ height:30px;padding:0 8px;border-radius:7px;border:0;background:transparent;color:var(--fg);
276
+ font:inherit;font-size:13px;cursor:pointer;display:flex;align-items:center;gap:9px;text-align:left;
277
+ }
278
+ .nav-item:hover{background:var(--hover)}
279
+ .nav-item.active{background:var(--sel)}
280
+ /*
281
+ * Drawn, not typed. Unicode glyphs come from whatever font happens to have
282
+ * them, so ◇ ◷ ⚙ arrive at three different stroke weights and optical sizes —
283
+ * which is exactly what the row of them looked like.
284
+ */
285
+ .nav-item .ico{width:16px;height:16px;flex:0 0 auto}
286
+ .nav-item .count{margin-left:auto;font-size:11px;color:var(--dim)}
287
+
288
+ /* main */
289
+ main{flex:1;display:flex;flex-direction:column;min-width:0}
290
+ /*
291
+ * The top bar takes no room until it has something to say.
292
+ *
293
+ * On a wide screen the burger is hidden, the changes button appears only once
294
+ * a file has been touched, and the context meter only once the window is worth
295
+ * watching — so this spent most of its life as forty pixels of nothing between
296
+ * the window edge and the conversation. The padding is what carried that
297
+ * height, so the padding is what goes.
298
+ */
299
+ #top{padding:0 18px;display:flex;gap:6px;align-items:center;flex-wrap:wrap}
300
+ #top:has(#changes-btn:not([hidden])){padding:10px 18px}
301
+
302
+ /*
303
+ * A built element, not a styled <select>.
304
+ *
305
+ * A native select renders its popup and its focus ring with the platform's
306
+ * own chrome — on macOS that is the blue rounded rectangle in the screenshot,
307
+ * and no amount of appearance:none reaches it. Owning the markup is the
308
+ * only way the control matches the rest of the page.
309
+ */
310
+ .picker{position:relative}
311
+ .picker-btn{
312
+ height:26px;max-width:220px;padding:0 5px 0 8px;border:0;border-radius:7px;
313
+ background:transparent;color:var(--fg);font:inherit;font-size:12.5px;cursor:pointer;
314
+ display:flex;align-items:center;gap:2px;
315
+ }
316
+ .picker-btn:hover{background:var(--hover)}
317
+ .picker.open .picker-btn{background:var(--hover)}
318
+ /*
319
+ * A drawn chevron, sized to the label. The Unicode ⌄ comes from whatever font
320
+ * has it, so it landed at a different weight from the SVG icons beside it and
321
+ * sat slightly off the text baseline.
322
+ */
323
+ .caret{width:13px;height:13px;flex:0 0 auto}
324
+ /* A discovered id like google/gemini-3.6-flash:batch would otherwise take the
325
+ * whole bar and push the send button off the end. */
326
+ .picker-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
327
+ .menu{
328
+ position:absolute;bottom:calc(100% + 8px);left:0;z-index:40;
329
+ min-width:280px;max-width:min(440px,84vw);
330
+ background:var(--bg);border-radius:12px;padding:6px;
331
+ box-shadow:0 1px 2px #0000000f,0 8px 28px #00000024;
332
+ display:flex;flex-direction:column;
333
+ }
334
+ .menu-search{padding:3px 3px 5px}
335
+ .menu-search input{
336
+ width:100%;height:30px;border:0;border-radius:8px;background:var(--field);color:var(--fg);
337
+ padding:0 10px;font:inherit;font-size:12.5px;
338
+ }
339
+ .menu-search input:focus{outline:none;background:var(--field-focus)}
340
+ /* Eighty-one skills with no way to narrow them is a list nobody reads to the
341
+ end. Matches the name and the description both, because the name is terse
342
+ and chosen by whoever wrote the skill. */
343
+ /* Quiet: it is a provenance note on a row whose subject is the name. */
344
+ .mk-tag{
345
+ margin-left:7px;padding:1px 5px;border-radius:4px;vertical-align:1px;
346
+ background:var(--sel);color:var(--dim);font-size:10px;font-weight:500;
347
+ letter-spacing:.02em;text-transform:uppercase;
348
+ }
349
+ .mk-search{
350
+ width:100%;height:32px;margin:10px 0 2px;border:0;border-radius:8px;
351
+ background:var(--field);color:var(--fg);padding:0 11px;font:inherit;font-size:13px;
352
+ }
353
+ .mk-search:focus{outline:none;background:var(--field-focus)}
354
+ .menu-body{overflow-y:auto;max-height:min(46vh,340px)}
355
+ .menu-footer{padding:5px 3px 3px;box-shadow:0 -1px 0 var(--line)}
356
+ .menu-item.cursor{background:var(--hover)}
357
+ .menu-empty{padding:10px 8px;font-size:12.5px;color:var(--dim)}
358
+ @media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .menu{box-shadow:0 1px 2px #0000004d,0 8px 28px #00000080}}
359
+ :root[data-theme="dark"] .menu{box-shadow:0 1px 2px #0000004d,0 8px 28px #00000080}
360
+ .menu-item{
361
+ display:flex;align-items:center;gap:8px;width:100%;min-height:32px;padding:7px 8px;border:0;
362
+ border-radius:7px;background:transparent;color:var(--fg);font:inherit;font-size:13px;
363
+ cursor:pointer;text-align:left;
364
+ }
365
+ .menu-item:hover{background:var(--hover)}
366
+ .menu-item .tick{width:12px;opacity:0;flex:0 0 auto}
367
+ .menu-item.on .tick{opacity:1}
368
+ .menu-item .menu-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
369
+ .menu-item .sub{margin-left:auto;font-size:11px;color:var(--dim);flex:0 0 auto}
370
+ /* The vendor's mark, on every row of the model menu and on the bar itself.
371
+ *
372
+ * A picker holding four hundred models across two dozen vendors is read by
373
+ * shape before it is read by name: half the ids in it are the vendor's name
374
+ * repeated, and the rest are strings like gemini-3.6-flash:batch that say
375
+ * nothing about who answers them. The mark is what makes the list scannable,
376
+ * and on the composer it is what tells someone, without reading, that the
377
+ * session is pointed at the official plan rather than at a key of their own.
378
+ *
379
+ * Same two shapes as the connector list: the vendor's own icon when the
380
+ * server has one, and a lettered tile when it does not. Sized to the row —
381
+ * the type is 13px, so the mark is 15px and not the 28px tile a card gets. */
382
+ /* One block per vendor, whatever the vendor ships.
383
+ *
384
+ * A column of these is scanned by its left edge, and the edge has to be the
385
+ * same one every time. Left alone, it is not: a vendor shipping a tile fills
386
+ * its square, a vendor shipping bare ink covers half of it, and one shipping
387
+ * a thin outline covers less than that — the eye finds no common edge and
388
+ * reads the lighter rows as hanging off the line, even where the boxes are
389
+ * centred on the text to the pixel.
390
+ *
391
+ * So a mark that did not bring a background is given one: the same faint
392
+ * fill, the same radius, the same inset. A mark that did bring one keeps it
393
+ * and fills the box, because drawing a second shape around a shape is the
394
+ * thing this is trying to avoid. Either way the block is identical and the
395
+ * ink inside it is the vendor's business. */
396
+ .vmark{width:15px;height:15px;flex:0 0 auto;border-radius:4px;object-fit:contain;
397
+ padding:1.5px;box-sizing:border-box;background:var(--sel)}
398
+ .vmark.filled{padding:0;background:none}
399
+ /* The fallback, and the reason a missing icon is not a broken row: a lettered
400
+ tile in a colour of its own still tells two vendors apart at a glance. */
401
+ .vmono{
402
+ width:15px;height:15px;flex:0 0 auto;border-radius:4px;box-sizing:border-box;
403
+ display:grid;place-items:center;color:#fff;
404
+ font-size:9px;font-weight:600;letter-spacing:0;text-transform:uppercase;
405
+ }
406
+ /* Our own glyph is drawn rather than fetched: the official plan is the one
407
+ row that must have a mark on a machine that has never been online. */
408
+ /* Typed as svg.vglyph so it outranks the .ico-sm every drawn icon carries:
409
+ .6 is right for a chrome glyph and too faint for a vendor's mark. Ours is
410
+ bare strokes, so it takes the same block as any other bare mark. */
411
+ svg.vglyph{width:15px;height:15px;opacity:1;
412
+ padding:1.5px;box-sizing:border-box;border-radius:4px;background:var(--sel)}
413
+ /* Black-on-transparent is what a good half of these vendors ship, and in the
414
+ dark theme that is a black square on a black row. Inverted only where the
415
+ bytes were measured to be exactly that. */
416
+ @media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .vmark.invert-dark{filter:invert(1)}}
417
+ :root[data-theme="dark"] .vmark.invert-dark{filter:invert(1)}
418
+ /* Smaller in the group header, which is 10.5px uppercase, and given the gap
419
+ the composer bar's 2px does not provide. */
420
+ .menu-group .vmark,.menu-group .vmono,.menu-group .vglyph{width:13px;height:13px;font-size:8px}
421
+ .picker-btn .vmark,.picker-btn .vmono,.picker-btn .vglyph{width:14px;height:14px;margin-right:5px}
422
+ /* The mode's glyph: a bare stroke, so it takes the row's icon weight and, on
423
+ the bar, the vendor mark's size and gap so the two pickers line up. */
424
+ .menu-item .mode-mark{opacity:.7}
425
+ .menu-item.on .mode-mark,.menu-item.cursor .mode-mark{opacity:1}
426
+ .picker-btn .mode-mark{width:14px;height:14px;margin-right:5px;opacity:.85}
427
+ /* On a card the row is a title over a line of description, so the mark is
428
+ sized against the block rather than against the type, and sits on the
429
+ title's line instead of floating in the middle of the two. */
430
+ /* Centred on the naming block, not on the first line of it. A 20px tile
431
+ * beside a title over a description is read as the row's own mark, and a
432
+ * mark that sits on the title alone leaves the row bottom-heavy — the same
433
+ * complaint as the margin above, one level in. */
434
+ details.provider>summary .vmark,details.provider>summary .vmono,details.provider>summary .vglyph,
435
+ .pp-item .vmark,.pp-item .vmono,.pp-item .vglyph{
436
+ width:20px;height:20px;border-radius:5px;font-size:11px;
437
+ }
438
+ details.provider>summary .vmark,.pp-item .vmark,
439
+ details.provider>summary .vglyph,.pp-item .vglyph{padding:2.5px}
440
+ details.provider>summary .vmark.filled,.pp-item .vmark.filled{padding:0}
441
+ .menu-head{
442
+ padding:8px 10px 4px;font-size:11px;font-weight:600;letter-spacing:.04em;
443
+ text-transform:uppercase;color:var(--dim);
444
+ }
445
+ .menu-head:first-child{padding-top:4px}
446
+ .menu-group{
447
+ display:flex;align-items:center;gap:7px;width:100%;border:0;background:transparent;
448
+ font:inherit;font-size:10.5px;color:var(--dim);padding:7px 8px 5px;cursor:pointer;
449
+ letter-spacing:.04em;text-transform:uppercase;text-align:left;
450
+ }
451
+ .menu-group:hover{color:var(--fg)}
452
+ .menu-group .chev{
453
+ width:0;height:0;flex:0 0 auto;
454
+ border-left:4px solid currentColor;border-top:3px solid transparent;
455
+ border-bottom:3px solid transparent;opacity:.6;transition:transform .15s;
456
+ }
457
+ .menu-group.open .chev{transform:rotate(90deg)}
458
+ .menu-group .g-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
459
+ .menu-group .g-count{opacity:.7;letter-spacing:0}
460
+ /* Marks one vendor out of a list of two dozen, so it reads at a glance
461
+ without shouting: the accent is the page's own, not a colour of its own. */
462
+ .menu-group .g-tag{
463
+ font-size:9.5px;letter-spacing:0;text-transform:none;padding:1px 5px;
464
+ border-radius:4px;background:var(--accent);color:var(--bg);opacity:.85;
465
+ }
466
+ /* The geometry .mk-tag established, which is what a badge looks like here.
467
+ Quieter than the accent a group header wears: a vendor is marked once at the
468
+ top of its list, while this can repeat down twenty rows, and twenty accent
469
+ pills is a column of noise. The two selected states have to move it off
470
+ var(--sel) — that is what the row under the cursor is painted with, and a
471
+ pill the same colour as the row it sits on is not a pill. */
472
+ .menu-item .m-tag{
473
+ flex:0 0 auto;padding:1px 5px;border-radius:4px;background:var(--sel);
474
+ color:var(--dim);font-size:10px;font-weight:500;letter-spacing:.02em;
475
+ text-transform:uppercase;
476
+ }
477
+ .menu-item:hover .m-tag,.menu-item.cursor .m-tag{background:var(--field)}
478
+ .menu-more{
479
+ width:100%;padding:8px;border:0;border-radius:7px;background:transparent;
480
+ color:var(--fg);font:inherit;font-size:12.5px;cursor:pointer;text-align:left;
481
+ display:flex;align-items:center;gap:8px;
482
+ }
483
+ .menu-more:hover{background:var(--sel)}
484
+ .ico-sm{width:15px;height:15px;flex:0 0 auto;opacity:.6}
485
+ .menu-more:hover .ico-sm{opacity:.9}
486
+ /*
487
+ * The running total, on its own line under the composer.
488
+ *
489
+ * Right-aligned and 11.5px: it is read when someone looks for it and ignored
490
+ * the rest of the time, so it sits at the end of the line the send button
491
+ * ends, a step quieter than the model name above it. The row keeps its height
492
+ * whether or not there is anything in it — a session that costs its first
493
+ * cent should not push the composer up a line while someone is typing in it.
494
+ */
495
+ #meta{
496
+ max-width:720px;margin:0 auto;padding:7px 6px 0;min-height:17px;
497
+ color:var(--dim);font-size:11.5px;display:flex;gap:6px 10px;align-items:center;
498
+ justify-content:flex-end;flex-wrap:wrap;
499
+ }
500
+ /*
501
+ * The context meter: how much of the model's window this session is using.
502
+ *
503
+ * Its own track colour rather than --line. Every other neutral token here is a
504
+ * hairline at 6-10% alpha, which at 64x4 pixels disappears into the background
505
+ * in both schemes — what was left read as a stray green dash rather than a bar
506
+ * with something still to fill. The fill itself is set from the value (green,
507
+ * then warn, then err) in applyState.
508
+ */
509
+ #ctx{width:64px;height:4px;background:var(--track);border-radius:2px;overflow:hidden}
510
+ #ctx i{display:block;height:100%;width:0;background:var(--ok);transition:width .3s}
511
+ #ctx,#ctxtext{display:none}
512
+ #meta.has-ctx #ctx,#meta.has-ctx #ctxtext{display:block}
513
+
514
+ /*
515
+ * Over the transcript, not over the whole window.
516
+ *
517
+ * Tabs are sessions; the drawer beside them shows one artifact whichever tab
518
+ * is in front. Spanning both left a band of empty chrome above the drawer that
519
+ * belonged to neither.
520
+ *
521
+ * The strip is the shelf the tabs stand on, in the sidebar's grey, and the
522
+ * front tab is cut from the page under it — the shape every browser uses.
523
+ * It used to be the other way round: a transparent strip on the white
524
+ * transcript with the front tab as a grey pill, so the row read as part of
525
+ * the conversation rather than as the chrome above it. No bottom padding,
526
+ * or a grey line would sit between the front tab and the page it is part of.
527
+ */
528
+ #tabs{
529
+ display:flex;align-items:center;gap:2px;padding:6px 12px 0;overflow-x:auto;flex:0 0 auto;
530
+ background:var(--panel);
531
+ }
532
+ #tabs:empty{display:none}
533
+ .tab{
534
+ display:flex;align-items:center;gap:7px;max-width:230px;height:30px;padding:0 6px 0 10px;
535
+ border:0;border-radius:8px 8px 0 0;background:transparent;color:var(--dim);
536
+ font:inherit;font-size:12.5px;cursor:pointer;flex:0 0 auto;
537
+ }
538
+ .tab:hover{background:var(--hover);color:var(--fg)}
539
+ .tab.on{background:var(--bg);color:var(--fg)}
540
+ .tab .label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
541
+ /* Which project a tab is in, ahead of its title. Set quieter than the title,
542
+ which is still what identifies it. */
543
+ .tab .where{
544
+ max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
545
+ font-size:11px;opacity:.6;flex:0 0 auto;
546
+ }
547
+ .tab .where::after{content:"/";padding-left:5px;opacity:.5}
548
+ .tab .dot{width:6px;height:6px;border-radius:50%;background:var(--brand);flex:0 0 auto}
549
+ .tab .x{
550
+ width:18px;height:18px;padding:0;border:0;border-radius:5px;background:transparent;color:inherit;
551
+ display:none;place-items:center;cursor:pointer;opacity:0;flex:0 0 auto;
552
+ }
553
+ /* A tab's controls take no room until the pointer or focus is on it: an idle
554
+ tab spends its width on its title, and a hovered one gives some back to its
555
+ buttons, where the title then ends in an ellipsis. Hidden rather than only
556
+ transparent — a transparent button still holds its space. A running
557
+ browser keeps its button, because its dot is what says so. */
558
+ .tab:hover .x,.tab:focus-within .x{display:grid}
559
+ /* The tab's browser button's size. Much smaller and the 1.5px strokes thin
560
+ below a pixel and blur; the full 16px outweighs the tab's own text. */
561
+ .tab .x svg{width:14px;height:14px}
562
+ .tab:hover .x,.tab.on .x{opacity:.55}
563
+ .tab .x:hover{opacity:1;background:var(--sel)}
564
+ .tab-browser{
565
+ position:relative;width:22px;height:22px;padding:0;border:0;border-radius:6px;
566
+ display:grid;place-items:center;background:transparent;color:inherit;cursor:pointer;
567
+ opacity:0;flex:0 0 auto;
568
+ }
569
+ .tab .tab-browser{display:none}
570
+ .tab:hover .tab-browser,.tab:focus-within .tab-browser,.tab .tab-browser.running{display:grid}
571
+ .tab:hover .tab-browser,.tab.on .tab-browser,.tab-browser.running{opacity:.62}
572
+ .tab-browser:hover,.tab-browser.on{opacity:1;background:var(--sel)}
573
+ .tab-browser svg{width:14px;height:14px}
574
+ .tab-browser i{display:none;position:absolute;right:2px;top:2px;width:5px;height:5px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 1.5px var(--panel)}
575
+ .tab.on .tab-browser i{box-shadow:0 0 0 1.5px var(--bg)}
576
+ .tab-browser.running i{display:block}
577
+ #tab-add{
578
+ width:26px;height:26px;padding:0;border:0;border-radius:7px;background:transparent;color:var(--dim);
579
+ display:grid;place-items:center;cursor:pointer;flex:0 0 auto;
580
+ }
581
+ #tab-add svg{width:14px;height:14px}
582
+ #tab-add:hover{background:var(--hover);color:var(--fg)}
583
+ #split{flex:1;display:flex;min-height:0}
584
+ /*
585
+ * The transcript, the jump button and the composer, as one column.
586
+ *
587
+ * They used to be siblings of #split, which meant the drawers beside it
588
+ * stopped at the composer's top edge instead of running to the bottom of the
589
+ * window — a rendered artifact sat in a box with a dead strip under it.
590
+ * "position:relative" is what #jump anchors to; it belongs over this column
591
+ * rather than over the drawer.
592
+ */
593
+ #main{flex:1;display:flex;flex-direction:column;min-width:0;position:relative}
594
+ #log{flex:1;overflow-y:auto;padding:20px 16px;scroll-behavior:smooth;min-width:0}
595
+ /*
596
+ * Models, Routines and Settings are views, not messages. They used to be
597
+ * painted into the transcript, which wiped the conversation behind them and
598
+ * left the composer sitting under a settings page inviting a reply to nobody.
599
+ * They get their own pane, and the composer goes away while it is up.
600
+ */
601
+ #panel-view{flex:1;overflow-y:auto;padding:28px 24px 24px;min-width:0}
602
+ /*
603
+ * The browser pane.
604
+ *
605
+ * Shaped like #review, because it is the same thing: a column that slides in
606
+ * beside the transcript. Collapsing to zero width rather than display:none is
607
+ * what gives it the transition, and what keeps the image element alive so the
608
+ * stream is not torn down and restarted on every toggle.
609
+ *
610
+ * No outlines anywhere. The pane is a filled surface, and the takeover button
611
+ * says it is armed by filling with the accent — the same way the sidebar marks
612
+ * its active row. A ring would be the only ring in the app.
613
+ */
614
+ #browser-pane{
615
+ width:0;flex:0 0 0;background:var(--panel);
616
+ display:flex;flex-direction:column;overflow:hidden;position:relative;transition:flex-basis .18s,width .18s;
617
+ }
618
+ #browser-pane.open{
619
+ width:var(--browser-pane-width,min(46vw,720px));
620
+ flex:0 0 var(--browser-pane-width,min(46vw,720px));
621
+ /* Clipped rather than hidden, with a margin, so the resize handle can reach
622
+ past the pane's edge into the transcript. */
623
+ overflow:clip;overflow-clip-margin:6px;
624
+ }
625
+ /* 15px to aim at, straddling the boundary: 6px over the transcript's edge and
626
+ the 9px gutter the native page leaves free. Only the gutter is visible, and
627
+ the pill sits in its middle. */
628
+ #browser-resizer{
629
+ position:absolute;z-index:4;left:-6px;top:0;bottom:0;width:15px;cursor:col-resize;outline:0;
630
+ }
631
+ #browser-resizer::before{
632
+ content:"";position:absolute;left:9px;top:50%;width:3px;height:34px;border-radius:999px;
633
+ background:var(--dim);opacity:0;transform:translateY(-50%) scaleY(.65);
634
+ transition:opacity .12s,transform .16s;
635
+ }
636
+ #browser-resizer:hover::before,#browser-resizer:focus-visible::before,body.browser-resizing #browser-resizer::before{
637
+ opacity:.55;transform:translateY(-50%) scaleY(1);
638
+ }
639
+ body.browser-resizing{user-select:none;cursor:col-resize}
640
+ body.browser-resizing #browser-pane{transition:none}
641
+ #browser-head{
642
+ min-height:78px;padding:9px 10px 9px 14px;display:grid;
643
+ grid-template-columns:minmax(0,1fr) auto;grid-template-rows:32px 24px;
644
+ align-items:start;column-gap:10px;row-gap:4px;font-size:12px;flex:0 0 auto;
645
+ }
646
+ #browser-mark{
647
+ width:24px;height:32px;display:grid;place-items:center;flex:0 0 auto;
648
+ color:var(--dim);background:transparent;box-shadow:none;
649
+ }
650
+ #browser-mark svg{width:17px;height:17px}
651
+ #browser-context{display:contents}
652
+ #browser-name-line,#browser-meta{display:flex;align-items:center;min-width:0}
653
+ #browser-name-line{grid-column:1;grid-row:1;gap:7px;height:32px;line-height:32px}
654
+ #browser-title{font-weight:600;font-size:12.5px}
655
+ #browser-status{
656
+ display:inline-flex;align-items:center;gap:4px;color:var(--dim);font-size:10.5px;
657
+ padding:1px 6px;border-radius:999px;background:var(--field);line-height:16px;
658
+ }
659
+ #browser-status[hidden]{display:none}
660
+ #browser-status i{width:5px;height:5px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 2px color-mix(in srgb,var(--ok) 14%,transparent)}
661
+ #browser-meta{grid-column:1/-1;grid-row:2;gap:5px;color:var(--dim);font-size:10.5px;line-height:20px}
662
+ #browser-where{
663
+ flex:1;min-width:0;height:24px;padding:2px 8px;border:0;border-radius:6px;
664
+ outline:0;background:var(--field);color:var(--dim);font:inherit;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
665
+ font-size:10.5px;text-overflow:ellipsis;
666
+ }
667
+ #browser-where:hover{background:var(--hover)}
668
+ #browser-where:focus{background:var(--bg);color:var(--fg);box-shadow:inset 0 0 0 1px var(--line)}
669
+ #browser-hint{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
670
+ #browser-hint:not([hidden])::before{content:"\00b7";margin-right:7px;color:var(--dim)}
671
+ #browser-actions{grid-column:2;grid-row:1;height:32px;display:flex;align-items:center;gap:2px}
672
+ #browser-reload{
673
+ width:24px;height:24px;padding:0;border:0;border-radius:6px;background:transparent;
674
+ color:var(--dim);cursor:pointer;display:grid;place-items:center;flex:0 0 auto;
675
+ }
676
+ #browser-reload svg{width:14px;height:14px}
677
+ #browser-reload:hover{background:var(--hover);color:var(--fg)}
678
+ #browser-reload:disabled{opacity:.3;cursor:default;background:transparent}
679
+ #browser-takeover{
680
+ height:32px;padding:0 10px;border:0;border-radius:9px;background:var(--bg);
681
+ color:var(--fg);cursor:pointer;font:inherit;font-size:11px;flex:0 0 auto;
682
+ display:inline-flex;align-items:center;gap:6px;
683
+ }
684
+ #browser-takeover svg{width:13px;height:13px;color:var(--dim)}
685
+ #browser-takeover:hover{background:var(--hover)}
686
+ #browser-takeover.on{background:var(--accent);color:var(--bg)}
687
+ #browser-takeover.on svg{color:inherit}
688
+ #browser-stop,#browser-hide,#browser-import,#browser-ext-get{
689
+ width:32px;height:32px;padding:0;border:0;border-radius:9px;background:transparent;
690
+ color:var(--dim);cursor:pointer;display:grid;place-items:center;flex:0 0 auto;
691
+ }
692
+ #browser-stop svg,#browser-hide svg,#browser-import svg,#browser-ext-get svg{width:16px;height:16px}
693
+ #browser-import[hidden],#browser-ext-get[hidden]{display:none}
694
+ #browser-import:hover,#browser-ext-get:hover{background:var(--hover);color:var(--fg)}
695
+ #browser-stop:hover{background:color-mix(in srgb,var(--err) 12%,transparent);color:var(--err)}
696
+ #browser-stop:disabled{opacity:.3;cursor:default;background:transparent;color:var(--dim)}
697
+ #browser-hide:hover{background:var(--hover);color:var(--fg)}
698
+ #browser-tabs{
699
+ height:34px;flex:0 0 34px;display:flex;align-items:end;gap:3px;padding:4px 10px 0;
700
+ overflow-x:auto;overflow-y:hidden;scrollbar-width:none;position:relative;
701
+ }
702
+ #browser-tabs[hidden]{display:none}
703
+ #browser-tabs::-webkit-scrollbar{display:none}
704
+ /* The scrollbar is hidden, so a side with tabs past it fades out instead. */
705
+ #browser-tabs.more-left{-webkit-mask-image:linear-gradient(to right,transparent,#000 28px);mask-image:linear-gradient(to right,transparent,#000 28px)}
706
+ #browser-tabs.more-right{-webkit-mask-image:linear-gradient(to left,transparent,#000 28px);mask-image:linear-gradient(to left,transparent,#000 28px)}
707
+ #browser-tabs.more-left.more-right{
708
+ -webkit-mask-image:linear-gradient(to right,transparent,#000 28px,#000 calc(100% - 28px),transparent);
709
+ mask-image:linear-gradient(to right,transparent,#000 28px,#000 calc(100% - 28px),transparent);
710
+ }
711
+ /* Tabs shrink toward 84px before the strip scrolls, as a browser's do. */
712
+ .browser-page-tab{
713
+ height:29px;min-width:84px;max-width:190px;border-radius:7px 7px 0 0;
714
+ background:transparent;color:var(--dim);font:inherit;font-size:10.5px;
715
+ display:flex;align-items:center;flex:0 1 190px;overflow:hidden;
716
+ }
717
+ .browser-page-tab:hover{background:var(--hover);color:var(--fg)}
718
+ .browser-page-tab.active{background:var(--bg);color:var(--fg)}
719
+ .browser-page-tab-select{
720
+ height:100%;min-width:0;flex:1;padding:0 4px 0 10px;border:0;background:transparent;color:inherit;
721
+ font:inherit;cursor:pointer;display:flex;align-items:center;gap:7px;
722
+ }
723
+ .browser-page-tab i{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.45;flex:0 0 auto}
724
+ .browser-page-tab.active i{background:var(--ok);opacity:1}
725
+ .browser-page-tab span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
726
+ .browser-page-tab-close{
727
+ width:24px;height:24px;margin-right:3px;padding:0;border:0;border-radius:6px;background:transparent;
728
+ color:inherit;font:inherit;font-size:15px;line-height:1;cursor:pointer;opacity:.45;flex:0 0 auto;
729
+ }
730
+ .browser-page-tab-close:hover{background:var(--sel);opacity:1}
731
+ #browser-suggest{
732
+ display:flex;align-items:center;gap:8px;margin:6px 10px 0;padding:5px 5px 5px 12px;flex:0 0 auto;
733
+ border-radius:9px;background:color-mix(in srgb,var(--accent) 9%,var(--field));color:var(--fg);font-size:11.5px;
734
+ }
735
+ #browser-suggest[hidden]{display:none}
736
+ /* Connecting the person's own browser: a quiet pill among the pane's own
737
+ controls, shown only while the extension is missing and closable for good. */
738
+ #browser-ext{display:inline-flex;align-items:center;height:24px;border-radius:7px;background:var(--field);flex:0 0 auto}
739
+ #browser-ext[hidden]{display:none}
740
+ #browser-ext button{
741
+ height:24px;padding:0;border:0;background:transparent;color:var(--dim);font:inherit;font-size:11px;
742
+ cursor:pointer;display:inline-flex;align-items:center;border-radius:7px;
743
+ }
744
+ #browser-ext-open{gap:5px;padding:0 4px 0 8px!important}
745
+ #browser-ext-open svg{width:13px;height:13px}
746
+ #browser-ext-hide{width:22px;justify-content:center}
747
+ #browser-ext-hide svg{width:11px;height:11px}
748
+ #browser-ext button:hover{color:var(--fg);background:var(--hover)}
749
+ /* Getting the extension: the link, the folder, and the steps between them. */
750
+ .ext-help{margin-top:8px}
751
+ .ext-help a.ext-get{display:inline-flex;align-items:center;gap:6px}
752
+ .ext-help a.ext-get svg{width:15px;height:15px;flex:none}
753
+ .ext-help ol{margin:8px 0 0;padding-left:18px;color:var(--dim);font-size:12px;line-height:1.55}
754
+ .ext-help code{font-size:11.5px;padding:1px 5px;border-radius:5px;background:var(--field)}
755
+ #browser-suggest-text{flex:1;min-width:0;line-height:1.35}
756
+ #browser-suggest-import{
757
+ height:26px;padding:0 10px;border:0;border-radius:7px;background:var(--accent);color:var(--bg);
758
+ font:inherit;font-size:11px;cursor:pointer;flex:0 0 auto;
759
+ }
760
+ #browser-suggest-import:hover{opacity:.88}
761
+ #browser-suggest-dismiss{
762
+ width:26px;height:26px;padding:0;border:0;border-radius:7px;background:transparent;
763
+ color:var(--dim);cursor:pointer;display:grid;place-items:center;flex:0 0 auto;
764
+ }
765
+ #browser-suggest-dismiss svg{width:13px;height:13px}
766
+ #browser-suggest-dismiss:hover{background:var(--hover);color:var(--fg)}
767
+ #browser-body{flex:1;min-height:0;display:flex;flex-direction:column;padding:0;overflow:hidden}
768
+ /* White where the native page will be, panel-coloured under the resize handle,
769
+ which the native view stops short of so the handle stays grabbable. */
770
+ .browser-native-surface{flex:1;min-width:0;min-height:0;overflow:hidden;background:linear-gradient(to right,var(--panel) 9px,#fff 9px)}
771
+ #browser-frame{
772
+ width:100%;height:100%;display:block;object-fit:contain;
773
+ -webkit-user-select:none;user-select:none;
774
+ }
775
+ #browser-pane.live #browser-frame{cursor:crosshair}
776
+ .browser-idle{margin:auto;text-align:center;max-width:32ch}
777
+ /* One column at a time on a narrow window: two would leave neither readable. */
778
+ #browser-hide .ico-x{display:none}
779
+ #browser-scrim{display:none}
780
+ /* On a narrow screen there is no room for a column, so the pane is a drawer:
781
+ it slides in over the conversation from the right, leaving a strip of it
782
+ showing under a scrim, and a tap on the scrim or the × closes it. */
783
+ @media (max-width:900px){
784
+ #browser-pane{
785
+ position:fixed;z-index:60;top:0;right:0;bottom:0;flex:none;
786
+ width:min(calc(100vw - 44px),560px);transform:translateX(100%);visibility:hidden;
787
+ transition:transform .22s ease,visibility 0s linear .22s;
788
+ }
789
+ #browser-pane.open{
790
+ width:min(calc(100vw - 44px),560px);flex:none;transform:none;visibility:visible;
791
+ transition:transform .22s ease;box-shadow:-12px 0 40px #00000026;
792
+ }
793
+ #browser-scrim:not([hidden]){display:block;position:fixed;inset:0;z-index:59;background:#0000004d}
794
+ #browser-resizer{display:none}
795
+ #browser-hide .ico-dash{display:none}
796
+ #browser-hide .ico-x{display:block}
797
+ #browser-head{padding-top:max(10px,env(safe-area-inset-top))}}
798
+ #panel-notes{max-width:720px;margin:0 auto}
799
+ #panel-notes:empty{display:none}
800
+ #panel-notes>*{margin:0 0 8px}
801
+ body.panelled #composer,body.panelled #jump,body.panelled #changes-btn{display:none}
802
+ #review{
803
+ width:0;flex:0 0 0;background:var(--panel);
804
+ display:flex;flex-direction:column;overflow:hidden;transition:flex-basis .18s,width .18s;
805
+ }
806
+ #review.open{width:min(46vw,620px);flex:0 0 min(46vw,620px)}
807
+ #review-head{
808
+ padding:12px 16px 8px;display:flex;
809
+ justify-content:space-between;align-items:center;font-size:12px;
810
+ }
811
+ #review-sum{color:var(--dim);font-size:11px}
812
+ #files{max-height:34%;overflow-y:auto;padding:0 8px 8px}
813
+ .file{
814
+ padding:7px 14px;cursor:pointer;display:flex;gap:8px;align-items:center;font-size:12.5px;
815
+ border-left:2px solid transparent;
816
+ }
817
+ .file:hover{background:var(--bg)}
818
+ .file.active{background:var(--sel)}
819
+ .file .p{font-family:ui-monospace,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
820
+ .file .n{font-size:11px;font-family:ui-monospace,monospace}
821
+ .file .plus{color:var(--ok)} .file .minus{color:var(--err)}
822
+ .file .tag{font-size:10px;color:var(--dim);background:var(--field);border-radius:4px;padding:1px 5px}
823
+ #diff{flex:1;overflow:auto;padding:0}
824
+ #diff pre.diff{padding:8px 0}
825
+ .d-add,.d-del,.d-same{padding:0 12px}
826
+ .hunk{color:var(--dim);background:var(--field);padding:3px 12px;font-size:11px}
827
+ /*
828
+ * The files pane: the same column as #review, a little wider because it holds
829
+ * two — the tree, and the file picked from it.
830
+ */
831
+ #explorer{
832
+ width:0;flex:0 0 0;background:var(--panel);
833
+ display:flex;flex-direction:column;overflow:hidden;transition:flex-basis .18s,width .18s;
834
+ /* Above the transcript column, whose composer bar can spill sideways when
835
+ the window is narrow enough to squeeze it. */
836
+ position:relative;z-index:2;
837
+ }
838
+ #explorer.open{width:var(--explorer-width,min(52vw,860px));flex:0 0 var(--explorer-width,min(52vw,860px));overflow:clip;overflow-clip-margin:6px}
839
+ #explorer-head{display:flex;align-items:center;gap:8px;padding:10px 10px 6px 16px;font-size:12.5px}
840
+ #explorer-title{font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
841
+ #explorer-actions{display:flex;gap:2px}
842
+ #explorer-actions button{
843
+ width:28px;height:28px;border:0;border-radius:7px;background:transparent;color:var(--dim);
844
+ cursor:pointer;padding:0;display:grid;place-items:center;
845
+ }
846
+ #explorer-actions button:hover{background:var(--hover);color:var(--fg)}
847
+ #explorer-actions svg{width:15px;height:15px}
848
+ #explorer-body{flex:1;display:flex;min-height:0}
849
+ #explorer-tree{width:var(--explorer-tree-width,min(40%,260px));flex:0 0 auto;overflow:auto;padding:2px 6px 12px}
850
+ /* The line between the tree and the file, and the handle that moves it: a
851
+ gap wide enough to grab, drawn only while it is being pointed at. */
852
+ #explorer-split{flex:0 0 7px;margin:0 -3px;position:relative;z-index:1;cursor:col-resize;touch-action:none;outline:0}
853
+ #explorer-split::before{
854
+ content:"";position:absolute;left:2px;top:50%;width:3px;height:34px;border-radius:999px;
855
+ background:var(--dim);opacity:0;transform:translateY(-50%) scaleY(.65);transition:opacity .12s,transform .16s;
856
+ }
857
+ .explorer-row{
858
+ display:flex;align-items:center;gap:5px;width:100%;border:0;background:transparent;color:var(--fg);
859
+ font:inherit;font-size:12.5px;text-align:left;padding:4px 8px;border-radius:6px;cursor:pointer;
860
+ white-space:nowrap;
861
+ }
862
+ .explorer-row:hover{background:var(--hover)}
863
+ .explorer-row.on{background:var(--sel)}
864
+ .explorer-row svg{width:14px;height:14px;flex:0 0 auto;color:var(--dim)}
865
+ .explorer-caret{width:12px;flex:0 0 12px;display:grid;place-items:center}
866
+ .explorer-caret svg{width:11px;height:11px}
867
+ .explorer-name{overflow:hidden;text-overflow:ellipsis;min-width:0}
868
+ #explorer-view{flex:1;min-width:0;display:flex;flex-direction:column;background:var(--bg)}
869
+ .explorer-hint{padding:16px;font-size:12.5px;margin:0}
870
+ .explorer-bar{display:flex;align-items:center;gap:8px;padding:8px 12px;font-size:12px}
871
+ .explorer-path{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left}
872
+ .explorer-size{color:var(--dim);font-size:11px;flex:0 0 auto}
873
+ .explorer-btn{
874
+ border:0;background:var(--field);color:var(--fg);border-radius:6px;padding:3px 9px;
875
+ font:inherit;font-size:11.5px;cursor:pointer;flex:0 0 auto;
876
+ }
877
+ .explorer-btn:hover{background:var(--sel)}
878
+ .explorer-content{flex:1;overflow:auto;min-height:0}
879
+ .explorer-code{display:flex;align-items:flex-start;min-width:max-content}
880
+ .explorer-code pre{margin:0;font-size:12.5px;line-height:1.55;padding:8px 12px 16px}
881
+ .explorer-gutter{color:var(--dim);text-align:right;user-select:none;opacity:.7;padding-right:10px!important;position:sticky;left:0;background:var(--bg)}
882
+ .explorer-src{overflow:visible!important;padding-left:4px!important}
883
+ .explorer-md{padding:12px 20px 24px;max-width:780px}
884
+ .explorer-img{display:block;max-width:calc(100% - 32px);margin:16px;image-rendering:auto}
885
+ .explorer-video{display:block;max-width:calc(100% - 32px);max-height:calc(100% - 32px);margin:16px;background:#000;border-radius:8px}
886
+ .explorer-audio{display:block;width:calc(100% - 32px);margin:16px}
887
+ .explorer-content.explorer-fill{overflow:hidden;display:flex}
888
+ .explorer-pdf{flex:1;width:100%;height:100%;border:0;background:var(--bg)}
889
+ /* Resize handles: an invisible strip on an edge, with a short pill drawn on
890
+ hover or while dragging — the same look as the browser pane's handle. */
891
+ .resizer-col{position:absolute;z-index:4;left:-6px;top:0;bottom:0;width:15px;cursor:col-resize;outline:0;touch-action:none}
892
+ .resizer-col::before{
893
+ content:"";position:absolute;left:6px;top:50%;width:3px;height:34px;border-radius:999px;
894
+ background:var(--dim);opacity:0;transform:translateY(-50%) scaleY(.65);transition:opacity .12s,transform .16s;
895
+ }
896
+ .resizer-row{position:absolute;z-index:4;left:0;right:0;top:-6px;height:13px;cursor:row-resize;outline:0;touch-action:none}
897
+ .resizer-row::before{
898
+ content:"";position:absolute;top:5px;left:50%;height:3px;width:34px;border-radius:999px;
899
+ background:var(--dim);opacity:0;transform:translateX(-50%) scaleX(.65);transition:opacity .12s,transform .16s;
900
+ }
901
+ .resizer-col:hover::before,.resizer-col:focus-visible::before,
902
+ #explorer-split:hover::before,#explorer-split:focus-visible::before,body.dragging-x #explorer-split::before{
903
+ opacity:.55;transform:translateY(-50%) scaleY(1);
904
+ }
905
+ .resizer-row:hover::before,.resizer-row:focus-visible::before,body.dragging-y .resizer-row::before{
906
+ opacity:.55;transform:translateX(-50%) scaleX(1);
907
+ }
908
+ body.dragging{user-select:none;-webkit-user-select:none}
909
+ body.dragging-x{cursor:col-resize}
910
+ body.dragging-y{cursor:row-resize}
911
+ body.dragging #explorer{transition:none}
912
+ body.dragging iframe{pointer-events:none}
913
+ /*
914
+ * The terminal, under the composer and as wide as the conversation. Its
915
+ * height is whatever it was last dragged to.
916
+ */
917
+ #terminal{
918
+ flex:0 0 var(--terminal-height,clamp(180px,34vh,420px));min-height:120px;position:relative;
919
+ display:flex;flex-direction:column;background:var(--panel);
920
+ }
921
+ #terminal[hidden]{display:none}
922
+ #terminal-head{display:flex;align-items:center;gap:4px;padding:5px 8px 3px 8px;font-size:12px;flex:0 0 auto;min-width:0}
923
+ #terminal-tabs{display:flex;gap:2px;overflow-x:auto;min-width:0;flex:0 1 auto;scrollbar-width:none}
924
+ #terminal-tabs::-webkit-scrollbar{display:none}
925
+ .terminal-tab{
926
+ display:flex;align-items:center;gap:6px;height:26px;padding:0 4px 0 8px;border-radius:6px;
927
+ color:var(--dim);cursor:pointer;flex:0 0 auto;max-width:180px;user-select:none;
928
+ }
929
+ .terminal-tab:hover{background:var(--hover);color:var(--fg)}
930
+ .terminal-tab.on{background:var(--bg);color:var(--fg)}
931
+ .terminal-tab>svg{width:13px;height:13px;flex:0 0 auto}
932
+ .terminal-tab-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
933
+ .terminal-tab-x{
934
+ width:18px;height:18px;border:0;border-radius:4px;background:transparent;color:inherit;padding:0;
935
+ display:grid;place-items:center;cursor:pointer;opacity:0;flex:0 0 auto;
936
+ }
937
+ .terminal-tab:hover .terminal-tab-x,.terminal-tab.on .terminal-tab-x{opacity:.6}
938
+ .terminal-tab-x:hover{opacity:1;background:var(--sel)}
939
+ .terminal-tab-x svg{width:12px;height:12px}
940
+ #terminal-where{flex:1;min-width:0;text-align:right;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:0 6px}
941
+ #terminal-new,#terminal-hide{
942
+ width:26px;height:26px;border:0;border-radius:6px;background:transparent;color:var(--dim);
943
+ cursor:pointer;padding:0;display:grid;place-items:center;flex:0 0 auto;
944
+ }
945
+ #terminal-new:hover,#terminal-hide:hover{background:var(--hover);color:var(--fg)}
946
+ #terminal-new svg,#terminal-hide svg{width:14px;height:14px}
947
+ #terminal-body{flex:1;min-height:0;position:relative;background:var(--bg)}
948
+ .terminal-host{position:absolute;inset:4px 0 6px 12px}
949
+ .terminal-host[hidden]{display:none}
950
+ .terminal-host .xterm{height:100%}
951
+ .terminal-host .xterm-viewport{background:transparent!important}
952
+ .tab-tool{
953
+ width:22px;height:22px;border:0;border-radius:6px;background:transparent;color:inherit;
954
+ display:grid;place-items:center;padding:0;cursor:pointer;flex:0 0 auto;opacity:0;
955
+ }
956
+ .tab .tab-tool{display:none}
957
+ .tab:hover .tab-tool,.tab:focus-within .tab-tool{display:grid}
958
+ .tab:hover .tab-tool,.tab.on .tab-tool{opacity:.62}
959
+ .tab-tool:hover,.tab-tool.on{opacity:1;background:var(--sel)}
960
+ .tab-tool svg{width:14px;height:14px}
961
+ #changes-btn{
962
+ border:0;background:var(--panel);color:var(--fg);border-radius:7px;
963
+ padding:5px 11px;font:inherit;font-size:12px;cursor:pointer;
964
+ }
965
+ #changes-btn:hover{background:var(--sel)}
966
+ #changes-btn b{font-weight:600}
967
+ /**
968
+ * The artifact drawer, deliberately the same shape as #review.
969
+ *
970
+ * Both answer "what did this turn produce"; making one look like a different
971
+ * kind of surface than the other would suggest a difference that is not there.
972
+ */
973
+ #shelf{
974
+ width:0;flex:0 0 0;background:var(--panel);
975
+ display:flex;flex-direction:column;overflow:hidden;transition:flex-basis .18s,width .18s;
976
+ }
977
+ #shelf.open{width:min(46vw,620px);flex:0 0 min(46vw,620px)}
978
+ /*
979
+ * Full width, by hiding the column beside it rather than by growing past it:
980
+ * the drawer keeps its own scroll and its own children, so nothing inside has
981
+ * to know it is being shown large.
982
+ */
983
+ /*
984
+ * Full screen is the whole window, not "the transcript stepped aside".
985
+ *
986
+ * Taken out of the flex row entirely: leaving it in meant the sidebar and the
987
+ * top bar stayed, so the artifact got wider without ever getting the screen.
988
+ */
989
+ #shelf.full{
990
+ position:fixed;inset:0;z-index:60;width:100%;height:100%;
991
+ flex:1 1 auto;border-radius:0;
992
+ }
993
+ body.shelf-full #main,body.shelf-full aside,body.shelf-full #top{display:none}
994
+ #shelf-body{flex:1;overflow:auto;display:flex;flex-direction:column;min-height:0}
995
+ /* The panel's only chrome: what this is, on the left; what to do with it, on
996
+ the right. It replaced a title row above it that said the same thing. */
997
+ #shelf-bar{
998
+ display:flex;gap:8px;align-items:center;padding:8px 10px 8px 14px;
999
+ font-size:11px;color:var(--dim);
1000
+ }
1001
+ #shelf-bar .ver-btn{
1002
+ display:flex;align-items:center;gap:2px;height:22px;padding:0 4px 0 7px;
1003
+ border:0;border-radius:6px;background:transparent;color:var(--dim);
1004
+ font:inherit;font-size:11px;cursor:pointer;
1005
+ }
1006
+ #shelf-bar .ver-btn:hover{background:var(--hover);color:var(--fg)}
1007
+ #shelf-bar a{color:var(--dim);text-decoration:none;margin-left:auto}
1008
+ #shelf-bar a:hover{color:var(--fg);text-decoration:underline}
1009
+ /* A switch, for the settings that are a state rather than an action.
1010
+ "Turn on" is a button: you press it and something happens. Whether sync is
1011
+ on is not that — it is a thing that is currently true or false, and a
1012
+ control shaped like a lever says so without being read. */
1013
+ .switch{
1014
+ position:relative;flex:0 0 auto;width:34px;height:20px;padding:0;border:0;
1015
+ border-radius:999px;background:var(--line);cursor:pointer;
1016
+ transition:background .15s;
1017
+ }
1018
+ .switch::after{
1019
+ content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;
1020
+ border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);
1021
+ transition:transform .15s;
1022
+ }
1023
+ .switch[aria-checked="true"]{background:var(--brand)}
1024
+ .switch[aria-checked="true"]::after{transform:translateX(14px)}
1025
+ /* Disabled means "not yet", not "never": it stays legible, and the row says
1026
+ why. Hiding it instead left people looking for a control that exists. */
1027
+ .switch:disabled{opacity:.45;cursor:default}
1028
+ .switch:focus-visible{outline:none;filter:brightness(.9)}
1029
+ .share-btn{
1030
+ border:1px solid var(--line);background:var(--field);color:var(--fg);
1031
+ border-radius:5px;padding:2px 8px;font:inherit;font-size:11px;cursor:pointer;
1032
+ }
1033
+ .share-btn:hover:not(:disabled){background:var(--sel)}
1034
+ .share-btn:disabled{opacity:.6;cursor:default}
1035
+ /* The frame is white in both themes: an artifact brings its own background,
1036
+ and letting the panel's dark surface show through a transparent one made
1037
+ black-on-transparent documents unreadable. */
1038
+ #shelf-frame{flex:1;width:100%;border:0;background:#fff;min-height:0}
1039
+ #shelf-source{
1040
+ flex:1;margin:0;padding:12px;overflow:auto;white-space:pre-wrap;word-break:break-word;
1041
+ font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;
1042
+ }
1043
+ #shelf-note{padding:10px 14px;color:var(--dim);font-size:12px}
1044
+ /*
1045
+ * The artifact's card in the transcript.
1046
+ *
1047
+ * Deliberately quieter than a tool card: it is an offer to go and look at
1048
+ * something, not a record of a command that ran.
1049
+ */
1050
+ /*
1051
+ * The card an artifact leaves in the conversation.
1052
+ *
1053
+ * Sized and spaced like the tool cards it sits between rather than like a
1054
+ * banner: it is one line of the transcript that happens to be clickable, and
1055
+ * the moment it starts shouting it competes with the artifact itself.
1056
+ */
1057
+ .art-card{
1058
+ display:flex;align-items:center;gap:11px;max-width:720px;margin:10px auto 0;
1059
+ padding:9px 10px 9px 11px;border-radius:10px;background:var(--panel);cursor:pointer;
1060
+ border:0;transition:background .12s;
1061
+ }
1062
+ /* Borderless: the surface is the boundary. A rule around it made a row of the
1063
+ transcript look like a dialog, and next to the tool cards it sits between —
1064
+ which have none — it was the only thing drawing a box around itself. */
1065
+ .art-card:hover{background:var(--sel)}
1066
+ .art-card:focus-visible{outline:none;background:var(--sel)}
1067
+ .art-card-mark{
1068
+ display:flex;align-items:center;justify-content:center;flex:0 0 auto;
1069
+ width:28px;height:28px;border-radius:7px;background:var(--field);color:var(--fg);
1070
+ }
1071
+ .art-card-mark svg{width:15px;height:15px}
1072
+ .art-card-text{min-width:0;flex:1}
1073
+ .art-card-title{
1074
+ font-size:13.5px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
1075
+ }
1076
+ .art-card-sub{font-size:11.5px;color:var(--dim);margin-top:1px}
1077
+ .art-card-open{
1078
+ display:flex;align-items:center;justify-content:center;flex:0 0 auto;
1079
+ width:22px;height:22px;color:var(--fg);
1080
+ }
1081
+ .art-card-open svg{width:14px;height:14px}
1082
+ #shelf-bar .share-btn{
1083
+ border:0;background:var(--field);color:var(--fg);border-radius:6px;
1084
+ padding:2px 8px;font:inherit;font-size:11px;cursor:pointer;
1085
+ }
1086
+ #shelf-bar .share-btn:hover{background:var(--sel)}
1087
+ #shelf-bar .share-btn.primary-action{
1088
+ display:flex;align-items:center;gap:5px;height:28px;padding:0 10px;
1089
+ font-size:12px;border-radius:7px;
1090
+ }
1091
+ #shelf-bar .share-btn.primary-action svg{width:14px;height:14px}
1092
+ #shelf-bar .share-btn:disabled{opacity:.55;cursor:default}
1093
+ /* Not decoration: it is the difference between the link showing what the
1094
+ author thinks it shows and showing a revision behind. */
1095
+ #shelf-bar .stale{color:var(--warn);font-size:11px}
1096
+ #shelf-bar .shelf-title{
1097
+ font-size:13px;font-weight:500;color:var(--fg);
1098
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:46%;
1099
+ }
1100
+ #shelf-bar .shelf-gap{flex:1}
1101
+ /* The panel's own controls: drawn, not spelled, and furthest from the actions
1102
+ that act on the artifact rather than on the panel. */
1103
+ #shelf-bar .pane-btn{
1104
+ display:flex;align-items:center;justify-content:center;width:28px;height:28px;
1105
+ border:0;border-radius:7px;background:transparent;color:var(--fg);cursor:pointer;padding:0;
1106
+ }
1107
+ #shelf-bar .pane-btn:hover{background:var(--hover)}
1108
+ #shelf-bar .pane-btn svg{width:16px;height:16px}
1109
+ #shelf-bar .icon-link{
1110
+ display:flex;align-items:center;justify-content:center;width:28px;height:28px;
1111
+ border-radius:7px;color:var(--fg);margin-left:0;
1112
+ }
1113
+ #shelf-bar .icon-link:hover{background:var(--hover);text-decoration:none}
1114
+ #shelf-bar .icon-link svg{width:16px;height:16px}
1115
+ /* One cluster, so three glyphs read as a set of controls rather than as three
1116
+ unrelated marks drifting between the type and the publish button. */
1117
+ #shelf-bar .shelf-icons{display:flex;align-items:center;gap:2px;margin-right:4px}
1118
+
1119
+ /*
1120
+ * The composer's .menu opens upward from a bottom-anchored picker. This one is
1121
+ * placed against a button that can sit anywhere in a scrolling panel, so it is
1122
+ * positioned from the viewport instead and reuses only the surface styling.
1123
+ */
1124
+ .menu.pop{position:fixed;bottom:auto;left:auto;min-width:220px}
1125
+ #log{padding:28px 24px 8px}
1126
+ /*
1127
+ * One column width, named once.
1128
+ *
1129
+ * A turn and a connection card are different things that must line up, and
1130
+ * before this they lined up by both happening to be laid out inside the pane —
1131
+ * which stopped being true the moment one of them got a max-width. On a wide
1132
+ * window the card then ran the full 1600px while every message sat in a 720px
1133
+ * column, so the one element asking for a decision was the one that did not
1134
+ * look like it belonged to the conversation.
1135
+ */
1136
+ .column{max-width:720px;margin-left:auto;margin-right:auto}
1137
+ .turn{position:relative;margin-bottom:26px}
1138
+
1139
+ /*
1140
+ * The connection card: an app's own mark, its name, what is being asked, and
1141
+ * the two answers. It is the only place a third party's brand appears in the
1142
+ * transcript, so it stays inside the column and keeps the site's own shapes —
1143
+ * a fill and a radius, no outline.
1144
+ */
1145
+ .connection-card{display:flex;flex-direction:column;gap:10px;padding:14px 16px}
1146
+ .connection-head{display:flex;align-items:center;gap:10px}
1147
+ /*
1148
+ * Fixed box, contained image: a vendor logo is whatever aspect ratio the vendor
1149
+ * chose, and letting it set its own height made cards jump by ten pixels
1150
+ * depending on which app was asking.
1151
+ */
1152
+ .connection-logo{width:28px;height:28px;border-radius:7px;object-fit:contain;flex:none;background:var(--bg)}
1153
+ .connection-name{font-size:14px;font-weight:600}
1154
+ .connection-card p{margin:0;color:var(--dim);font-size:13px;line-height:1.5}
1155
+ /* The answers sit at the end of the row: this card is a question, and the two
1156
+ * replies belong where a reader arrives last rather than under the sentence
1157
+ * they are answering. */
1158
+ .connection-actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:2px}
1159
+ .connection-card .err{color:var(--fg)}
1160
+
1161
+ /* A question from the agent, in the transcript rather than a modal: answering
1162
+ * may take a look back up the conversation, which a modal would cover. Options
1163
+ * are filled rows; the chosen one takes the accent fill, never a ring. */
1164
+ .question-card{background:var(--panel);border-radius:12px;padding:14px 16px;margin-bottom:10px;display:flex;flex-direction:column;gap:10px;outline:none}
1165
+ .q-head{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
1166
+ .q-tag,.q-tab{font:inherit;font-size:11.5px;line-height:1.7;padding:0 9px;border:0;border-radius:999px;background:var(--field);color:var(--dim)}
1167
+ .q-tab{cursor:pointer}
1168
+ .q-tab:hover{background:var(--hover);color:var(--fg)}
1169
+ .q-tab.on{background:var(--sel);color:var(--fg)}
1170
+ .q-tab.done::after{content:" ✓"}
1171
+ .q-hint{font-size:11.5px;color:var(--dim);margin-left:auto}
1172
+ .q-text{font-size:14.5px;line-height:1.5;font-weight:600;overflow-wrap:anywhere}
1173
+ .q-layout{display:flex;flex-direction:column;gap:8px;min-width:0}
1174
+ .q-options{display:flex;flex-direction:column;gap:4px}
1175
+ .q-opt{
1176
+ display:flex;align-items:flex-start;gap:10px;width:100%;text-align:left;border:0;border-radius:9px;
1177
+ padding:8px 10px;background:var(--bg);color:var(--fg);font:inherit;cursor:pointer;
1178
+ }
1179
+ .q-opt:hover,.q-opt:focus-visible{outline:none;background:var(--sel)}
1180
+ .q-opt.on{background:var(--accent);color:var(--bg)}
1181
+ .q-opt.on:focus-visible{background:var(--accent);opacity:.9}
1182
+ .q-key{
1183
+ flex:none;min-width:18px;height:18px;border-radius:5px;background:var(--field);color:var(--dim);
1184
+ font-size:11px;display:grid;place-items:center;margin-top:1px;
1185
+ }
1186
+ .q-opt.on .q-key{background:#ffffff26;color:inherit}
1187
+ .q-words{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
1188
+ .q-label{font-size:13.5px;line-height:1.45;overflow-wrap:anywhere}
1189
+ .q-desc{font-size:12px;line-height:1.45;color:var(--dim);overflow-wrap:anywhere}
1190
+ .q-opt.on .q-desc{color:inherit;opacity:.75}
1191
+ .q-check{flex:none;width:14px;font-size:12px;line-height:20px}
1192
+ /* The typed answer is a field, so it keeps the plain row fill whether empty,
1193
+ focused or filled; a filled one is marked by its key alone, which takes the
1194
+ accent the picked options wear across the whole row. */
1195
+ .q-other{cursor:text;align-items:center;padding-top:12px;padding-bottom:12px}
1196
+ .q-other:focus-within,.q-other.on,.q-other.on:focus-visible{background:var(--bg);color:var(--fg);opacity:1}
1197
+ .q-other.on .q-key{background:var(--accent);color:var(--bg)}
1198
+ .q-other-input::placeholder{color:var(--dim)}
1199
+ .q-other-input{flex:1;min-width:0;border:0;outline:none;background:transparent;color:inherit;font:inherit;font-size:13.5px;padding:0}
1200
+ .q-preview{
1201
+ margin:0;border-radius:9px;background:var(--bg);padding:10px 12px;font-size:12px;line-height:1.55;
1202
+ white-space:pre-wrap;overflow:auto;max-height:280px;
1203
+ }
1204
+ .q-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
1205
+ .q-actions .q-keys{margin-right:auto}
1206
+ .q-actions button{padding:7px 13px;border:0;border-radius:9px;background:var(--field);color:var(--fg);font:inherit;font-size:13px;cursor:pointer}
1207
+ .q-actions button:hover{background:var(--sel)}
1208
+ .q-actions button.approve{background:var(--accent);color:var(--bg)}
1209
+ .q-actions button.approve:hover{opacity:.88}
1210
+ .q-actions button.quiet{background:transparent;color:var(--dim)}
1211
+ .q-actions button.quiet:hover{background:var(--hover);color:var(--fg)}
1212
+ .q-actions button:disabled{opacity:.45;cursor:default}
1213
+ .q-keys{font-size:11.5px;color:var(--dim)}
1214
+ .q-error{font-size:12px;color:var(--err)}
1215
+ .question-card.settled{gap:4px;padding:10px 14px}
1216
+ .q-settled-title{font-size:12px;color:var(--dim)}
1217
+ .q-settled-line{display:flex;gap:8px;font-size:13px;line-height:1.5;overflow-wrap:anywhere}
1218
+ .q-settled-head{color:var(--dim);flex:none}
1219
+ .who{
1220
+ font-size:11.5px;color:var(--dim);margin-bottom:6px;font-weight:500;letter-spacing:.01em;
1221
+ }
1222
+ .user .bubble{
1223
+ white-space:pre-wrap;word-break:break-word;font-size:15.5px;font-weight:500;
1224
+ line-height:1.6;
1225
+ }
1226
+ /* Big enough to recognize a screenshot by, small enough that three of them do
1227
+ not push the reply off the screen. The full size is one click away. */
1228
+ .user .shots{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
1229
+ .user .shots img{
1230
+ max-width:220px;max-height:160px;border-radius:8px;cursor:zoom-in;
1231
+ background:var(--bg);object-fit:cover;
1232
+ }
1233
+ .user{margin-bottom:18px}
1234
+ .panel{max-width:720px;margin:8px auto 0}
1235
+ .panel-loading{display:flex;align-items:center;justify-content:center;gap:8px;padding-block:48px;font-size:13px}
1236
+ .panel h2{font-size:18px;font-weight:600;letter-spacing:-.01em;margin:0 0 14px}
1237
+ /* Only the gap below. The horizontal margin belongs to whatever is placing the
1238
+ * card: setting all four here quietly reset the auto margins that keep a card
1239
+ * in the reading column, and the card sat flush left while every message beside
1240
+ * it was centred. */
1241
+ .row-card{background:var(--panel);border-radius:10px;padding:12px 14px;margin-bottom:8px}
1242
+ .memory-field{margin:14px 0}
1243
+ .memory-label{display:block;font-size:12.5px;font-weight:500;margin-bottom:2px}
1244
+ .memory-field .muted{margin-bottom:6px}
1245
+ /* Filled, not outlined — the same shape .art-search uses. A box drawn with a
1246
+ * line around it is the one thing this site does not do, and three of them
1247
+ * stacked made the editor look like a form bolted onto the page rather than
1248
+ * part of the note. Focus deepens the fill rather than drawing a ring: no
1249
+ * border marks a focused thing anywhere here. */
1250
+ .memory-panel input,.memory-panel textarea{background:var(--field);color:var(--fg);border:0;border-radius:8px;padding:10px 12px;font:inherit;line-height:1.5;box-sizing:border-box}
1251
+ .memory-panel input:focus,.memory-panel textarea:focus{outline:none;background:var(--field-focus)}
1252
+ .memory-panel textarea{resize:vertical;min-height:130px}
1253
+ .memory-panel summary{cursor:pointer;padding:6px 0;font-weight:500}
1254
+ .memory-panel .memory-path{overflow-wrap:anywhere;color:var(--dim)}
1255
+ .memory-panel .memory-search{width:100%}
1256
+ .memory-summary{padding:14px 16px;margin-bottom:10px}
1257
+ .memory-state{font-size:15px;font-weight:600}
1258
+ .memory-summary p{margin:4px 0}
1259
+ .memory-error{color:var(--warn);overflow-wrap:anywhere}
1260
+ .memory-summary .memory-actions{flex-wrap:wrap;align-items:center;margin-top:12px}
1261
+ .memory-summary .signin-site{width:auto;flex:0 0 auto;padding:0 16px}
1262
+ .memory-about{margin:6px 0 4px;color:var(--dim)}
1263
+ .memory-about summary{cursor:pointer;width:max-content}
1264
+ /* Prose, so it breaks between words; the shared .small breaks anywhere, which suits a path. */
1265
+ .memory-panel .section-title+.memory-notes-intro{margin:-4px 0 10px;word-break:normal;overflow-wrap:anywhere}
1266
+ .memory-empty{padding:14px 16px}
1267
+ .memory-empty-title{font-size:13.5px;font-weight:500;margin-bottom:4px}
1268
+ .memory-empty p{margin:0;word-break:normal;overflow-wrap:anywhere}
1269
+ /* Built like a settings row: one flex line, the marker drawn rather than left
1270
+ * to the browser, so the summary text and everything below it share one left
1271
+ * edge. */
1272
+ .memory-panel details.memory-note>summary{display:flex;align-items:center;gap:8px;list-style:none;padding:2px 0 6px}
1273
+ .memory-panel details.memory-note>summary::-webkit-details-marker{display:none}
1274
+ .memory-panel details.memory-note[open]>summary .chev{transform:rotate(90deg)}
1275
+ .memory-note-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1276
+ .memory-id{flex:0 0 auto;font-size:11px;color:var(--dim);letter-spacing:.02em;user-select:all}
1277
+ /* No card of its own — the note row is already one — and indented to the
1278
+ * summary's text rather than the card's edge, which is what .setting-body does
1279
+ * for the same shape. The 15px is the marker column: a chevron and the gap
1280
+ * after it, the same distance the summary text is pushed. Left at the form's
1281
+ * own padding the fields sat nine pixels to the right of the title they
1282
+ * belonged to, which is the sort of gap that reads as a mistake without
1283
+ * anyone being able to say what is wrong. */
1284
+ .memory-editor{margin:0;padding:2px 0 4px 15px;background:transparent}
1285
+ /* The form's own background is the page's, which on a card reads as a second
1286
+ * card inside the first one — the same doubling the padding was making. */
1287
+ .memory-actions{display:flex;gap:8px;margin-top:14px}
1288
+ .memory-recent{margin:6px 0}
1289
+ .memory-recent>summary{cursor:pointer;color:var(--dim)}
1290
+ .memory-recent>div{padding:2px 0 2px 15px}
1291
+ .memory-backup{display:flex;align-items:center;gap:12px;padding:8px 0 8px 15px}
1292
+ .memory-backup-text{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
1293
+ .memory-versions{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin:10px 0}
1294
+ .memory-version h4{margin:0 0 6px}
1295
+ .memory-version pre{margin:0;background:var(--field);border-radius:8px;padding:10px 12px;overflow-wrap:anywhere}
1296
+ .memory-links{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 8px 15px;color:var(--dim)}
1297
+ .memory-link{background:var(--field);color:var(--fg);border:0;border-radius:6px;padding:2px 8px;font:inherit;font-size:12px;cursor:pointer}
1298
+ .memory-link.missing{color:var(--dim);cursor:default;text-decoration:line-through}
1299
+ .memory-panel select{background:var(--field);color:var(--fg);border:0;border-radius:8px;padding:10px 12px;font:inherit}
1300
+ /*
1301
+ * A settings row: name, value, and everything else behind the triangle.
1302
+ *
1303
+ * The value is held to one line and cut with an ellipsis rather than wrapped,
1304
+ * so the column of rows stays a column of rows however long a path is; the
1305
+ * whole of it is printed again inside, where there is room for it. Opened,
1306
+ * the row keeps its background, so what belongs to it is obvious without a
1307
+ * border drawn around every card on the page.
1308
+ */
1309
+ details.setting{background:var(--panel);border-radius:10px;margin:0 0 8px}
1310
+ details.setting>summary{
1311
+ display:flex;align-items:center;gap:10px;padding:12px 14px;
1312
+ cursor:pointer;list-style:none;border-radius:10px;
1313
+ }
1314
+ details.setting>summary::-webkit-details-marker{display:none}
1315
+ details.setting>summary:hover{background:var(--hover)}
1316
+ details.setting[open]>summary{border-radius:10px 10px 0 0}
1317
+ details.setting[open]>summary .chev{transform:rotate(90deg)}
1318
+ details.setting>summary .ghost{flex:0 0 auto;height:26px;font-size:12px;margin-left:0}
1319
+ details.setting>summary .switch{flex:0 0 auto}
1320
+ details.setting>summary:focus-visible{outline:none;background:var(--hover)}
1321
+ /*
1322
+ * The artifacts panel: a search field over the whole catalogue.
1323
+ *
1324
+ * Full width and at the top, because with every session's work in one list the
1325
+ * first move is to narrow it — the field is the panel's subject, not a filter
1326
+ * tucked beside a heading.
1327
+ */
1328
+ .art-search{
1329
+ width:100%;height:34px;border:0;border-radius:9px;background:var(--field);
1330
+ color:var(--fg);font:inherit;font-size:13px;padding:0 12px;margin:0 0 4px;
1331
+ }
1332
+ .art-search:focus{outline:none;background:var(--field-focus)}
1333
+ .art-search::-webkit-search-cancel-button{cursor:pointer}
1334
+ .art-summary:not(:empty){margin:0 0 8px;padding:0 2px}
1335
+ /* Which session the rows under it came from. Quiet, because it is a divider
1336
+ between groups of the thing being looked for, not one of them. */
1337
+ .art-group{
1338
+ display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dim);
1339
+ margin:16px 0 6px;padding:0 2px;
1340
+ }
1341
+ .art-group:first-child{margin-top:10px}
1342
+ /* A session title is a sentence someone typed, and some of them are long. */
1343
+ .art-group>span:first-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1344
+ .art-when{margin-left:auto}
1345
+ .art-note{margin:14px 0 0}
1346
+ .setting-name{font-weight:500;font-size:13.5px;flex:0 0 auto}
1347
+ .setting-value{
1348
+ flex:1;min-width:0;font-size:12px;color:var(--dim);
1349
+ white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right;
1350
+ }
1351
+ /* Indented to the name, not to the card: the chevron and its gap put the
1352
+ title 30px in, and a body that started at the card's edge read as a
1353
+ paragraph the row had dropped rather than the inside of it. */
1354
+ .setting-body{padding:4px 14px 16px 30px}
1355
+ /* The shared .small breaks anywhere, which is right for a path and wrong for
1356
+ a sentence: the hint here came back as "the trail is never s / ynced". The
1357
+ prose breaks at spaces; the paths under it keep breaking anywhere. */
1358
+ .setting-body .small:not(.mono){word-break:normal;overflow-wrap:anywhere}
1359
+ .setting-body>*+*{margin-top:8px}
1360
+ .setting-list>*+*{margin-top:6px}
1361
+ .setting-list>.mono{margin-top:3px}
1362
+ /* Lists that could be forty lines long scroll where they are rather than
1363
+ growing the page under them. */
1364
+ .scroll-box{
1365
+ max-height:200px;overflow-y:auto;background:var(--field);
1366
+ border-radius:6px;padding:7px 9px;
1367
+ }
1368
+ .trail-line{padding:2px 0}
1369
+ /* On its own page the trail is the page: it flows rather than scrolling in a
1370
+ box, and the box's hairline goes with it — a list this long framed by a
1371
+ rule reads as a field somebody is meant to type in. */
1372
+ .audit-controls{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
1373
+ .panel.audit .audit-list{margin:4px 0 12px}
1374
+ .setting-body .audit-list{
1375
+ max-height:220px;overflow-y:auto;border-radius:8px;padding:4px;background:var(--field);
1376
+ }
1377
+ /* One grid for the whole trail, so time, outcome and tool line up down the
1378
+ page; each row is a subgrid of it so a row can still take a hover fill. */
1379
+ .audit-list{
1380
+ display:grid;grid-template-columns:max-content max-content minmax(0,1fr) max-content;
1381
+ column-gap:12px;font-size:12.5px;
1382
+ }
1383
+ .audit-row{
1384
+ display:grid;grid-column:1/-1;grid-template-columns:subgrid;align-items:center;
1385
+ padding:5px 8px;border-radius:7px;
1386
+ }
1387
+ .audit-row:hover{background:var(--hover)}
1388
+ .audit-row.denied{background:color-mix(in srgb,var(--err) 5%,transparent)}
1389
+ .audit-row.denied:hover{background:color-mix(in srgb,var(--err) 9%,transparent)}
1390
+ .audit-day{
1391
+ grid-column:1/-1;font-size:11.5px;font-weight:600;color:var(--dim);
1392
+ letter-spacing:.02em;padding:14px 8px 4px;
1393
+ }
1394
+ .audit-day:first-child{padding-top:2px}
1395
+ .audit-time{font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:11.5px;color:var(--dim);font-variant-numeric:tabular-nums}
1396
+ .audit-pill{
1397
+ display:inline-flex;align-items:center;gap:5px;justify-self:start;
1398
+ font-size:11px;font-weight:600;line-height:1;padding:4px 8px 4px 7px;border-radius:999px;
1399
+ }
1400
+ .audit-pill::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
1401
+ .audit-pill.allowed{color:var(--ok);background:color-mix(in srgb,var(--ok) 12%,transparent)}
1402
+ .audit-pill.denied{color:var(--err);background:color-mix(in srgb,var(--err) 12%,transparent)}
1403
+ .audit-pill.unanswered{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,transparent)}
1404
+ .audit-what{display:flex;align-items:baseline;gap:6px;min-width:0;white-space:nowrap}
1405
+ .audit-tool{font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-weight:600;color:var(--fg);flex:none}
1406
+ .audit-arg{
1407
+ font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;color:var(--dim);
1408
+ overflow:hidden;text-overflow:ellipsis;min-width:0;
1409
+ }
1410
+ .audit-notes{display:flex;gap:4px;justify-self:end}
1411
+ .audit-tag{
1412
+ font-size:10.5px;color:var(--dim);background:var(--field);padding:2px 7px;border-radius:5px;white-space:nowrap;
1413
+ }
1414
+ .audit-tag.remembered{color:var(--brand-ink);background:var(--brand-tint)}
1415
+ .audit-tag.unanswered{color:var(--warn);background:color-mix(in srgb,var(--warn) 12%,transparent)}
1416
+ .seg-item .seg-dot{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:6px;vertical-align:1px}
1417
+ .seg-item .seg-count{margin-left:6px;font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums}
1418
+ @media (max-width:640px){
1419
+ .audit-list{grid-template-columns:max-content minmax(0,1fr);row-gap:0}
1420
+ .audit-row{grid-template-columns:max-content minmax(0,1fr);row-gap:3px}
1421
+ .audit-time{grid-column:1}
1422
+ .audit-pill{grid-column:2;justify-self:start}
1423
+ .audit-what{grid-column:1/-1}
1424
+ .audit-notes{grid-column:1/-1;justify-self:start}
1425
+ .audit-notes:empty{display:none}
1426
+ }
1427
+ /* A row of things to do with what is above them. Links styled as the ghost
1428
+ buttons beside them, because a download is a link and pretending otherwise
1429
+ costs the browser's own save machinery. */
1430
+ .row-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
1431
+ .row-actions a.ghost{
1432
+ text-decoration:none;height:26px;font-size:12px;
1433
+ display:inline-flex;align-items:center;
1434
+ }
1435
+ .section-title{
1436
+ font-size:13px;font-weight:600;color:var(--dim);margin:22px 0 8px;letter-spacing:.01em;
1437
+ }
1438
+ /*
1439
+ * The window picker, and the chart under it.
1440
+ *
1441
+ * Six series colours, defined once and used by both the lines and their
1442
+ * legend swatches so a colour never means two things. They are hues rather
1443
+ * than shades of the accent: the chart's whole job is telling models apart,
1444
+ * and five tints of one colour cannot do it.
1445
+ */
1446
+ .seg{display:inline-flex;gap:2px;padding:2px;border-radius:9px;background:var(--field);margin:2px 0 12px}
1447
+ /* A segmented choice on a settings row sits in the row's line, like a switch. */
1448
+ .setting summary .setting-seg{margin:0 0 0 auto;flex:none}
1449
+ .setting summary .setting-seg .seg-item{padding:3px 10px;font-size:12px}
1450
+ /* Whether something a row depends on is there: a dot and a sentence. */
1451
+ .setting-status{display:flex;align-items:center;gap:7px;margin-top:8px;color:var(--dim)}
1452
+ .status-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--dim);opacity:.6}
1453
+ .status-dot.ok{background:var(--ok);opacity:1}
1454
+ /* The presets and the calendar on one line, on the same baseline. */
1455
+ .usage-window{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
1456
+ .perms-card{display:flex;flex-direction:column;gap:10px}
1457
+ .perms-card .setting-list{margin:0}
1458
+ /* The mode, as the one fact the rest of the page qualifies. */
1459
+ .perm-mode{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
1460
+ .perm-mode-name{
1461
+ display:inline-flex;align-items:center;gap:7px;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
1462
+ font-size:13px;font-weight:600;padding:6px 12px 6px 10px;border-radius:999px;
1463
+ }
1464
+ .perm-mode-name::before{content:"";width:7px;height:7px;border-radius:50%;background:currentColor}
1465
+ .perm-mode-name.read-only{color:var(--ok);background:color-mix(in srgb,var(--ok) 12%,transparent)}
1466
+ .perm-mode-name.workspace-write{color:var(--brand-ink);background:var(--brand-tint)}
1467
+ .perm-mode-name.full-access{color:var(--err);background:color-mix(in srgb,var(--err) 12%,transparent)}
1468
+ .perm-mode-steps{display:flex;gap:4px;flex-wrap:wrap}
1469
+ .perm-mode-step{font-size:11.5px;color:var(--dim);padding:3px 9px;border-radius:6px;background:var(--field)}
1470
+ .perm-mode-step.on{color:var(--fg);background:var(--bg);font-weight:600}
1471
+ /* Rules, one group per verdict, each rule a chip in that verdict's colour. */
1472
+ .perm-group{display:flex;flex-direction:column;gap:6px}
1473
+ .perm-group-head{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:600}
1474
+ .perm-group-head .n{font-weight:400;color:var(--dim);font-variant-numeric:tabular-nums}
1475
+ .perm-group-head.allow{color:var(--ok)} .perm-group-head.ask{color:var(--warn)}
1476
+ .perm-group-head.deny{color:var(--err)} .perm-group-head.session{color:var(--brand-ink)}
1477
+ .perm-chips{display:flex;flex-wrap:wrap;gap:5px}
1478
+ .perm-chip{
1479
+ font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:12px;
1480
+ padding:3px 9px;border-radius:6px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
1481
+ }
1482
+ .perm-chip.allow{color:var(--ok);background:color-mix(in srgb,var(--ok) 10%,transparent)}
1483
+ .perm-chip.ask{color:var(--warn);background:color-mix(in srgb,var(--warn) 12%,transparent)}
1484
+ .perm-chip.deny{color:var(--err);background:color-mix(in srgb,var(--err) 10%,transparent)}
1485
+ .perm-chip.session{color:var(--brand-ink);background:var(--brand-tint)}
1486
+ .perm-empty{
1487
+ display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--dim);
1488
+ padding:10px 12px;border-radius:8px;background:var(--field);
1489
+ }
1490
+ .perm-roots{
1491
+ display:flex;flex-direction:column;max-height:280px;overflow-y:auto;
1492
+ background:var(--field);border-radius:8px;padding:4px;
1493
+ }
1494
+ .perm-root{
1495
+ display:flex;align-items:baseline;gap:0;min-width:0;padding:4px 8px;border-radius:6px;
1496
+ font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:12px;white-space:nowrap;
1497
+ }
1498
+ .perm-root:hover{background:var(--hover)}
1499
+ .perm-root .dir{color:var(--dim);overflow:hidden;text-overflow:ellipsis;min-width:0;direction:rtl;text-align:left}
1500
+ .perm-root .dir bdi{direction:ltr}
1501
+ .perm-root .base{color:var(--fg);font-weight:600;flex:none}
1502
+ .perm-count{
1503
+ display:inline-block;margin-left:6px;font-size:11px;font-weight:600;color:var(--dim);
1504
+ background:var(--field);padding:1px 7px;border-radius:999px;vertical-align:1px;
1505
+ }
1506
+ /* ---- date range picker ----------------------------------------------------
1507
+ The site's own component, class for class. Borderless like everything else
1508
+ here: fills for the chosen days, a shadow to lift the panel, and no ring on
1509
+ any state. */
1510
+ .drp{position:relative;display:inline-flex;margin:2px 0 12px}
1511
+ .drp-trigger{
1512
+ display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 11px;
1513
+ border:0;border-radius:9px;background:var(--field);color:var(--dim);
1514
+ font:inherit;font-size:12.5px;font-variant-numeric:tabular-nums;cursor:pointer;
1515
+ }
1516
+ .drp-trigger:hover{color:var(--fg)}
1517
+ .drp-trigger.on{background:var(--sel);color:var(--fg)}
1518
+ .drp-trigger svg{flex:none;width:14px;height:14px;opacity:.75}
1519
+ .drp-pop{
1520
+ position:absolute;top:calc(100% + 8px);left:0;z-index:30;width:max-content;
1521
+ max-width:calc(100vw - 32px);padding:16px;border-radius:14px;background:var(--bg);
1522
+ box-shadow:0 12px 40px #00000029,0 2px 6px #0000000f;
1523
+ }
1524
+ @media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .drp-pop{background:var(--panel)}}
1525
+ :root[data-theme="dark"] .drp-pop{background:var(--panel)}
1526
+ .drp-nav{
1527
+ position:absolute;top:12px;right:12px;left:12px;display:flex;
1528
+ justify-content:space-between;pointer-events:none;
1529
+ }
1530
+ .drp-arrow{
1531
+ display:grid;place-items:center;width:30px;height:30px;padding:0;border:0;
1532
+ border-radius:999px;background:transparent;color:var(--dim);
1533
+ cursor:pointer;pointer-events:auto;
1534
+ }
1535
+ .drp-arrow svg{width:16px;height:16px}
1536
+ .drp-arrow:hover:not(:disabled){background:var(--hover);color:var(--fg)}
1537
+ .drp-arrow:disabled{opacity:.3;cursor:default}
1538
+ .drp-months{display:flex;justify-content:center;gap:28px}
1539
+ .drp-month-name{
1540
+ height:30px;margin-top:-4px;font-size:14px;font-weight:600;line-height:30px;
1541
+ text-align:center;color:var(--fg);
1542
+ }
1543
+ .drp-grid{display:grid;grid-template-columns:repeat(7,34px);grid-auto-rows:34px;row-gap:2px;margin-top:6px}
1544
+ .drp-weekday{display:grid;place-items:center;font-size:11.5px;font-weight:500;color:var(--dim)}
1545
+ /* A day is a full-width cell, so the range fill runs unbroken across a week;
1546
+ the number sits in a circle inside it. */
1547
+ .drp-day{
1548
+ position:relative;display:grid;place-items:center;width:34px;height:34px;padding:0;
1549
+ border:0;border-radius:0;background:transparent;color:var(--fg);
1550
+ font:inherit;font-size:13px;font-variant-numeric:tabular-nums;cursor:pointer;
1551
+ }
1552
+ .drp-day>span{
1553
+ position:relative;display:grid;place-items:center;width:30px;height:30px;
1554
+ border-radius:999px;transition:background .12s ease;
1555
+ }
1556
+ .drp-day:hover:not(:disabled)>span{background:var(--hover)}
1557
+ .drp-day:disabled{color:var(--dim);opacity:.45;cursor:default}
1558
+ .drp-day.is-inside{background:var(--field)}
1559
+ .drp-day.is-start{background:linear-gradient(to right,transparent 50%,var(--field) 50%)}
1560
+ .drp-day.is-end{background:linear-gradient(to left,transparent 50%,var(--field) 50%)}
1561
+ .drp-day.is-edge>span,.drp-day.is-edge:hover>span{background:var(--fg);color:var(--bg);font-weight:600}
1562
+ /* Today: a small dot under the number, so it reads without a ring. */
1563
+ .drp-day.is-today>span::after{
1564
+ content:"";position:absolute;bottom:3px;left:50%;translate:-50% 0;
1565
+ width:4px;height:4px;border-radius:999px;background:currentColor;opacity:.55;
1566
+ }
1567
+ .drp-day:focus-visible{outline:none}
1568
+ .drp-day:focus-visible>span{background:var(--sel)}
1569
+ .drp-foot{
1570
+ display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
1571
+ gap:10px 16px;margin-top:14px;padding-top:12px;font-size:13px;color:var(--dim);
1572
+ box-shadow:0 -1px 0 var(--line);
1573
+ }
1574
+ .drp-summary strong{color:var(--fg);font-weight:600;font-variant-numeric:tabular-nums}
1575
+ .drp-actions{display:inline-flex;gap:6px;margin-left:auto}
1576
+ .drp-cancel,.drp-apply{
1577
+ padding:7px 14px;border:0;border-radius:10px;font:inherit;font-size:13px;cursor:pointer;
1578
+ }
1579
+ .drp-cancel{background:transparent;color:var(--dim)}
1580
+ .drp-cancel:hover{background:var(--hover);color:var(--fg)}
1581
+ .drp-apply{background:var(--fg);color:var(--bg)}
1582
+ .drp-apply:hover:not(:disabled){opacity:.88}
1583
+ .drp-apply:disabled{opacity:.35;cursor:default}
1584
+ /* One month on a narrow screen — the later one, which holds today — and the
1585
+ panel floats over the middle of it rather than hanging off a trigger the
1586
+ width of the screen cannot accommodate. */
1587
+ @media (max-width:640px){
1588
+ .drp-month.is-first{display:none}
1589
+ .drp-pop{
1590
+ position:fixed;top:18vh;left:50%;width:min(100vw - 32px,300px);translate:-50% 0;
1591
+ }
1592
+ }
1593
+ .seg-item{
1594
+ border:0;background:transparent;color:var(--dim);font:inherit;font-size:12.5px;
1595
+ padding:5px 11px;border-radius:7px;cursor:pointer;
1596
+ }
1597
+ .seg-item:hover{color:var(--fg)}
1598
+ .seg-item.on{background:var(--bg);color:var(--fg)}
1599
+ .chart-card{display:flex;flex-direction:column;gap:6px}
1600
+ .chart{width:100%;height:150px;overflow:visible}
1601
+ .chart-rule{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke}
1602
+ /* The selected tint, not the hover one: this band sits behind coloured bars
1603
+ and has to read against them, where a 4% wash disappears. */
1604
+ .chart-hover{fill:var(--sel);pointer-events:none}
1605
+ .chart-scale span:first-child{font-variant-numeric:tabular-nums}
1606
+ .chart-bar{shape-rendering:crispEdges}
1607
+ .chart-scale{display:flex;justify-content:space-between;font-size:11.5px;color:var(--fg)}
1608
+ .chart-span{display:flex;justify-content:space-between}
1609
+ .chart-legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:2px}
1610
+ .legend-item{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;color:var(--dim)}
1611
+ .swatch{width:9px;height:9px;border-radius:3px;flex:0 0 auto}
1612
+ .s0{fill:#5b8def;background:#5b8def}
1613
+ .s1{fill:#e0803c;background:#e0803c}
1614
+ .s2{fill:#3fa87a;background:#3fa87a}
1615
+ .s3{fill:#b06ad4;background:#b06ad4}
1616
+ .s4{fill:#d15b7a;background:#d15b7a}
1617
+ .s5{fill:#8a8f98;background:#8a8f98}
1618
+ /* Three totals across the top of the usage page. They wrap rather than shrink:
1619
+ a figure that has been squeezed to fit is a figure nobody can read at a
1620
+ glance, which is the only thing a figure is for. */
1621
+ .figures{display:flex;flex-wrap:wrap;gap:12px 34px}
1622
+ /* The shared .small breaks anywhere, which is right for a path and wrong for a
1623
+ sentence: on a narrow window "83% cached" came back as "83% ca / ched". Here
1624
+ the line is prose with one long model id in it, so it breaks at spaces and
1625
+ splits a word only when the word alone will not fit. */
1626
+ .panel.usage .small{word-break:normal;overflow-wrap:anywhere}
1627
+ .figure-label{font-size:11.5px;color:var(--dim);letter-spacing:.01em}
1628
+ .figure-value{font-size:22px;font-weight:600;letter-spacing:-.02em;margin:2px 0 1px}
1629
+ .panel>.section-title:first-of-type{margin-top:4px}
1630
+ /* One card per section, rows divided by spacing rather than borders. */
1631
+ .section{background:var(--panel);border-radius:12px;overflow:hidden}
1632
+ .section details.provider + details.provider{box-shadow:0 -1px 0 var(--line)}
1633
+ details.provider{padding:0}
1634
+ /* Padding on all four sides and no margin, because the row is measured
1635
+ * between the dividers and not around the box that draws the hover.
1636
+ *
1637
+ * The .panel .row-head rule carries a 14px bottom margin for the single
1638
+ * heading row it was written for, and a provider card's summary is a
1639
+ * .row-head too. It
1640
+ * landed outside the summary and inside the divider, so every card had 12px
1641
+ * of air above its content and 26px below — the whole row reading as pinned
1642
+ * to its top edge, which is what it was. */
1643
+ .panel details.provider>summary,details.provider>summary{
1644
+ padding:14px;cursor:pointer;list-style:none;margin:0;gap:10px;
1645
+ }
1646
+ details.provider>summary::-webkit-details-marker{display:none}
1647
+ .naming{flex:1;min-width:0}
1648
+ .name-line{display:flex;align-items:center;gap:8px}
1649
+ .row-desc{font-size:12px;color:var(--dim);margin-top:1px}
1650
+ .chev{
1651
+ width:0;height:0;flex:0 0 auto;margin-right:2px;
1652
+ border-left:4.5px solid currentColor;border-top:3.5px solid transparent;
1653
+ border-bottom:3.5px solid transparent;opacity:.4;transition:transform .15s;
1654
+ }
1655
+ details.provider[open]>summary .chev{transform:rotate(90deg)}
1656
+ details.provider>summary:hover{background:var(--hover)}
1657
+ .provider-body{padding:2px 14px 14px}
1658
+ .row-head .count{font-size:11px;margin-left:2px}
1659
+ .row-head{display:flex;align-items:center;gap:10px;margin-bottom:4px}
1660
+ .row-title{font-weight:500;font-size:13.5px}
1661
+ .row-cron{font-size:12px;color:var(--dim)}
1662
+ .tag{font-size:10.5px;color:var(--dim);background:var(--field);border-radius:4px;padding:1px 5px}
1663
+ .tag.rec{color:var(--bg);background:var(--accent);opacity:.85}
1664
+ .row-head .ghost{margin-left:auto;height:26px;font-size:12px}
1665
+ /* A second action sits beside the first rather than splitting the free space
1666
+ * with it. */
1667
+ .row-head .ghost+.ghost,.row-head .ghost+.switch,.row-head .switch+.ghost{margin-left:0}
1668
+ .routine-where{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
1669
+ .routine-history{margin-top:6px}
1670
+ .routine-history>summary{cursor:pointer;width:max-content}
1671
+ .routine-runs{display:flex;flex-direction:column;gap:2px;margin-top:6px}
1672
+ .routine-run{display:flex;gap:10px;align-items:baseline;text-align:left;border:0;background:transparent;color:var(--fg);font:inherit;padding:4px 6px;border-radius:6px}
1673
+ button.routine-run{cursor:pointer}
1674
+ button.routine-run:hover{background:var(--hover)}
1675
+ .routine-run.failed{color:var(--err)}
1676
+ .small{font-size:12px;white-space:pre-wrap;word-break:break-all}
1677
+ .keyrow{display:flex;gap:6px;margin:8px 0 6px}
1678
+ .keyrow input{
1679
+ flex:1;height:30px;border:0;border-radius:8px;background:var(--field);color:var(--fg);
1680
+ padding:0 10px;font:inherit;font-size:12.5px;
1681
+ }
1682
+ .keyrow input:focus{outline:none;background:var(--field-focus)}
1683
+ /* The way in on an account-backed plan, and the only control on the card that
1684
+ * is filled: everything below it is an alternative for someone who cannot use
1685
+ * a browser here. Its own class rather than .primary, which is the composer's
1686
+ * 30x30 icon button and would collapse this into a circle. */
1687
+ /* No rule above it. The padding and the button's own hover surface already
1688
+ separate the account from the list — a hairline as well is a second answer
1689
+ to a question the whitespace had settled, and it is the only line left in a
1690
+ sidebar that has none. */
1691
+ /* A column, not a row. Signed in this used to hold the account button and
1692
+ nothing else, and now a subscriber gets a Top up row above it — the thing
1693
+ a subscriber does most often here, which had been the fourth entry of a
1694
+ menu behind the name. */
1695
+ /* The desktop update hint, above the account. A filled row, never an
1696
+ outline: a quiet fill for an ordinary release, the accent for one marked
1697
+ important. The window's menu and the dialogs are the loud path; this is the
1698
+ one that waits to be clicked. */
1699
+ #update-hint{flex:0 0 auto;padding:4px 8px 0}
1700
+ #update-hint button{
1701
+ width:100%;height:32px;display:flex;align-items:center;gap:8px;
1702
+ padding:0 10px;border:0;border-radius:8px;background:var(--field);color:var(--fg);
1703
+ font:inherit;font-size:12.5px;cursor:pointer;text-align:left;
1704
+ }
1705
+ #update-hint button:hover{background:var(--field-focus)}
1706
+ #update-hint button:disabled{cursor:default}
1707
+ #update-hint .dot{width:6px;height:6px;border-radius:50%;background:var(--accent);flex:0 0 auto}
1708
+ #update-hint .label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1709
+ #update-hint .pct{font-size:11px;color:var(--dim)}
1710
+ #update-hint.important button{background:var(--accent);color:var(--bg)}
1711
+ #update-hint.important button:hover{opacity:.9}
1712
+ #update-hint.important .dot{background:var(--bg)}
1713
+ #update-hint.important .pct{color:inherit;opacity:.8}
1714
+ #account{
1715
+ display:flex;flex-direction:column;align-items:stretch;gap:2px;
1716
+ flex:0 0 auto;padding:8px;
1717
+ }
1718
+ /* 10px across rather than the nav rows' 8: the account row under it starts
1719
+ with a 24px avatar inset 6px, and at 10px this row's 16px icon lands on
1720
+ the avatar's centre line rather than 4px to the left of it. The balance
1721
+ sits in the aside slot the nav rows keep for a count, because it is what
1722
+ decides whether to click. */
1723
+ #account .topup{width:100%;padding:0 10px}
1724
+ /* Signed out this holds one nav row and nothing else, so the padding that
1725
+ frames an account goes away and the row sits flush with the ones above it.
1726
+ 8px across, matching the nav list above, so the icon lands on the same
1727
+ vertical as every other row rather than 4px inside it. */
1728
+ /* Not 4px, which is the gap between two rows in a list. This row is the last
1729
+ thing in the window, so the space under it is the window edge rather than
1730
+ another row, and at 4px the text sat on the frame. 10px below, and a row
1731
+ taller than the nav rows above: a footer is allowed to be the roomiest row
1732
+ in the sidebar, and this one has to hold a click aimed at from the corner. */
1733
+ #account.out{padding:4px 8px 10px}
1734
+ #account .nav-item{width:100%}
1735
+ #account.out .nav-item{height:34px}
1736
+ /* The whole row is the control. It used to be a label with a Sign out button
1737
+ parked beside it, which gave the rarest action in the sidebar the most
1738
+ reliable target in it, and left the name — the thing worth clicking, because
1739
+ everything about the account is behind it — inert. */
1740
+ .account-btn{
1741
+ flex:1;min-width:0;display:flex;align-items:center;gap:8px;
1742
+ padding:5px 6px;border:0;border-radius:8px;background:transparent;color:var(--fg);
1743
+ font:inherit;cursor:pointer;text-align:left;
1744
+ }
1745
+ .account-btn:hover,#account .account-btn.menu-open{background:var(--hover)}
1746
+ #account .who-name{
1747
+ flex:1;min-width:0;
1748
+ font-size:12px;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
1749
+ }
1750
+ #account .acct-caret{width:12px;height:12px;flex:0 0 auto;opacity:.45}
1751
+ .account-btn:hover .acct-caret,#account .account-btn.menu-open .acct-caret{opacity:.8}
1752
+ #account .avatar{
1753
+ width:24px;height:24px;border-radius:50%;flex:0 0 auto;display:grid;place-items:center;
1754
+ overflow:hidden;background:var(--accent);color:var(--bg);font-size:11px;font-weight:600;
1755
+ }
1756
+ /* Over the initial rather than instead of it: the letter is what shows while a
1757
+ picture loads, and what is left if it never does. */
1758
+ #account .avatar img{grid-area:1/1;width:100%;height:100%;object-fit:cover}
1759
+ #account .avatar.out{background:var(--line);color:var(--dim)}
1760
+ button.signin-site{
1761
+ flex:1;height:32px;border:0;border-radius:8px;background:var(--accent);color:var(--bg);
1762
+ font:inherit;font-size:12.5px;font-weight:500;cursor:pointer;
1763
+ }
1764
+ button.signin-site:hover{opacity:.88}
1765
+ button.signin-site:disabled{opacity:.4;cursor:default}
1766
+ .pw-balance{display:flex;align-items:baseline;gap:6px;margin:0}
1767
+ .pw-balance strong{font-size:22px;font-weight:600;font-variant-numeric:tabular-nums}
1768
+ /* The paywall's own width, and its own padding. Everything here is scoped to
1769
+ #paywall: the same .cn-body and .dlg-actions serve the connect dialog, and
1770
+ the last time a rule meant for one of them was written without a scope it
1771
+ resized three screens nobody had complained about. */
1772
+ #paywall{width:min(620px,94vw);max-width:min(620px,94vw)}
1773
+ #paywall .cn-body{padding:16px 22px 4px;gap:14px}
1774
+ #paywall .dlg-actions{padding:18px 22px 20px}
1775
+ #paywall .cn-head{padding:20px 22px 2px}
1776
+ /* Importing a sign-in. Scoped for the same reason as #paywall above; the site
1777
+ rows are filled, and a chosen one takes the selection fill, never a ring. */
1778
+ #browser-import-dialog{width:min(560px,94vw);max-width:min(560px,94vw)}
1779
+ #browser-import-dialog .cn-head{padding:20px 22px 2px}
1780
+ #browser-import-dialog .cn-body{padding:14px 22px 4px;gap:10px}
1781
+ #browser-import-dialog .dlg-actions{padding:16px 22px 20px}
1782
+ #browser-import-dialog .dlg-actions button:disabled{opacity:.4;cursor:default}
1783
+ #browser-import-dialog input[type=text]{height:38px;padding:0 13px;font-size:13px}
1784
+ .bi-label{font-size:12px;color:var(--dim)}
1785
+ .bi-sites{display:flex;flex-direction:column;gap:4px;max-height:240px;overflow:auto}
1786
+ .bi-site{display:flex;align-items:center;gap:10px;padding:8px 11px;border-radius:9px;background:var(--field);cursor:pointer}
1787
+ .bi-site:hover,.bi-site:has(input:focus-visible){background:var(--hover)}
1788
+ .bi-site.on{background:var(--sel)}
1789
+ #browser-import-dialog .bi-site input{width:auto;height:auto;margin:0;padding:0;flex:0 0 auto;accent-color:var(--accent);outline:none}
1790
+ .bi-site-text{display:flex;flex-direction:column;min-width:0;flex:1}
1791
+ .bi-site-name{font-size:13px;font-weight:550}
1792
+ .bi-site-title{font-size:11.5px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1793
+ .bi-site-tabs{font-size:11px;color:var(--dim);flex:0 0 auto}
1794
+ .bi-note{margin:0;font-size:12px;color:var(--dim);line-height:1.5}
1795
+ .bi-warn{color:var(--fg)}
1796
+ .bi-prompt{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--dim)}
1797
+ .bi-prompt span{flex:1;min-width:0}
1798
+ .bi-prompt select{
1799
+ height:30px;padding:0 8px;border:0;border-radius:8px;outline:none;
1800
+ background:var(--field);color:var(--fg);font:inherit;font-size:12px;cursor:pointer;
1801
+ }
1802
+ .bi-prompt select:hover,.bi-prompt select:focus-visible{background:var(--hover)}
1803
+ /* Two across on a screen with room, one on a phone. Four tiers in a single
1804
+ column made a dialog taller than the window it interrupts, which is the one
1805
+ shape a modal should not have. */
1806
+ .pw-plans{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
1807
+ @media (max-width:560px){.pw-plans{grid-template-columns:1fr}}
1808
+ /* Every row carries the colour. This dialog interrupts a turn that could not
1809
+ * be paid for, so each row is the way out of it rather than one recommendation
1810
+ * and a set of alternatives — and an outlined row beside a filled one reads as
1811
+ * the option that is switched off. The advice this dialog gives is still
1812
+ * there: it is the order, which puts a top-up first for a subscriber and a
1813
+ * subscription first for everyone else. */
1814
+ /* Its own class rather than .primary, for the reason written above #account:
1815
+ * .primary is the composer's 30x30 icon button, and inheriting its width,
1816
+ * height and display:grid collapsed this row into a square with the label
1817
+ * clipped inside it. */
1818
+ /* Quiet cards, one mark.
1819
+ *
1820
+ * These used to be filled with the accent, every one of them, on the reasoning
1821
+ * that each row is the way out of a turn that could not be paid for. That held
1822
+ * when they were a short flat list. At four tiers plus a ladder of top-ups it
1823
+ * is eight blocks of inverted colour in a small dialog, and the eye has
1824
+ * nowhere to rest — the reader is choosing between shades rather than reading
1825
+ * prices.
1826
+ *
1827
+ * The site's pricing page settled this and says why in its own stylesheet: the
1828
+ * offers should read as columns of type rather than as a row of frames, and on
1829
+ * a screen that is mostly numbers the frames are the loudest thing on it. So
1830
+ * the fill goes quiet and the recommended rung takes a deeper fill. No outline
1831
+ * marks anything here, the recommended rung included.
1832
+ */
1833
+ button.pw-plan{
1834
+ display:flex;flex-direction:column;align-items:stretch;gap:3px;width:100%;
1835
+ min-height:52px;padding:10px 12px;border:1px solid transparent;border-radius:10px;
1836
+ background:var(--field);color:var(--fg);font:inherit;font-size:12.5px;cursor:pointer;text-align:left;
1837
+ }
1838
+ button.pw-plan.featured{background:var(--sel)}
1839
+ .pw-plan-top{display:flex;align-items:center;justify-content:space-between;gap:10px}
1840
+ button.pw-plan:hover{background:var(--sel)}
1841
+ button.pw-plan.featured:hover{background:var(--hover)}
1842
+ button.pw-plan:disabled{opacity:.4;cursor:default}
1843
+ .pw-plan-name{display:flex;align-items:center;gap:6px;min-width:0;font-weight:500}
1844
+ .pw-plan-under{display:flex;align-items:center;justify-content:space-between;gap:8px}
1845
+ .pw-plan-credits{color:var(--dim);font-variant-numeric:tabular-nums;font-size:11.5px}
1846
+ .pw-plan-under>span:first-child{color:var(--dim);font-variant-numeric:tabular-nums;font-size:11.5px}
1847
+ /* Sits under the price rather than beside the name: the badge up there says
1848
+ which rung to take, this one says what taking it for a year costs. */
1849
+ .pw-save{font-size:11px;font-weight:500;color:var(--fg);flex:0 0 auto;font-variant-numeric:tabular-nums}
1850
+ /* The money, hard against the right edge and in the same tabular figures as
1851
+ the credits, so four rows read as a column of prices rather than four
1852
+ sentences that happen to end in numbers. */
1853
+ .pw-plan-price{color:var(--fg);font-weight:500;font-variant-numeric:tabular-nums;flex:0 0 auto;white-space:nowrap}
1854
+ /* The one inverted thing left, and the same shape the site uses for it. */
1855
+ .pw-tag{
1856
+ font-size:10.5px;font-weight:600;letter-spacing:.02em;padding:1px 7px;border-radius:999px;
1857
+ background:var(--fg);color:var(--bg);flex:0 0 auto;
1858
+ }
1859
+ /* One choice about the four rows beneath, so it is a control rather than four
1860
+ more buttons. */
1861
+ .pw-toggle{display:flex;gap:4px;padding:3px;border-radius:10px;background:var(--field);margin-bottom:2px}
1862
+ .pw-tab{
1863
+ flex:1;height:28px;border:0;border-radius:8px;background:transparent;color:var(--dim);
1864
+ font:inherit;font-size:12.5px;cursor:pointer;
1865
+ }
1866
+ .pw-tab:hover{color:var(--fg)}
1867
+ .pw-tab.on{background:var(--bg);color:var(--fg);font-weight:500}
1868
+ /* A labelled break, not a rule: what follows is a different kind of purchase,
1869
+ and a bare line would read as the list simply continuing. */
1870
+ .pw-sep{
1871
+ display:flex;align-items:center;gap:8px;margin:4px 0 2px;
1872
+ font-size:11.5px;color:var(--dim);
1873
+ }
1874
+ .pw-sep::after{content:"";flex:1;height:1px;background:var(--line)}
1875
+ .provider-advanced{margin-top:8px}
1876
+ .provider-advanced>summary{
1877
+ font-size:12px;color:var(--dim);cursor:pointer;list-style:none;padding:2px 0;
1878
+ }
1879
+ .provider-advanced>summary::-webkit-details-marker{display:none}
1880
+ .provider-advanced>summary:hover{color:var(--fg)}
1881
+ .row-head .act{margin-left:0}
1882
+ .more-row{
1883
+ width:100%;display:flex;align-items:center;gap:8px;border:0;border-radius:12px;
1884
+ background:var(--panel);color:var(--dim);font:inherit;font-size:13px;cursor:pointer;
1885
+ padding:12px 14px;margin-top:10px;
1886
+ }
1887
+ .more-row:hover{color:var(--fg)}
1888
+ /*
1889
+ * The display only applies when open. A dialog is hidden by the browser's own
1890
+ * display:none, which any unconditional author display overrides — the same
1891
+ * mistake that left both picker menus permanently on screen.
1892
+ */
1893
+ /* Browsing what is on offer is a list with a sentence under every name and a
1894
+ verb beside it, which is a page rather than a popup — the menu could show
1895
+ the names or the sentences, never both. */
1896
+ /* Wider than the other dialogs, and the max-width has to be raised with it:
1897
+ the base rule caps every dialog at 560px, which is right for a question and
1898
+ wrong for a list that wants two columns. */
1899
+ #market{width:min(860px,94vw);max-width:min(860px,94vw);max-height:76vh}
1900
+ #market[open]{display:flex;flex-direction:column}
1901
+ .mk-head{padding:18px 20px 10px;position:relative}
1902
+ #mk-list{overflow-y:auto;padding:2px 12px 14px;flex:1}
1903
+ /* Where every other window puts it. A "Done" under the list meant scrolling
1904
+ past eighty rows to leave, for a dialog that changes nothing on the way out. */
1905
+ .mk-close{
1906
+ position:absolute;top:14px;right:14px;width:28px;height:28px;border-radius:8px;
1907
+ border:0;background:transparent;color:var(--dim);display:grid;place-items:center;cursor:pointer;
1908
+ }
1909
+ .mk-close:hover{background:var(--hover);color:var(--fg)}
1910
+ .mk-close .ico-sm{width:14px;height:14px;opacity:1}
1911
+ .mk-group{
1912
+ font-size:10.5px;color:var(--dim);letter-spacing:.04em;text-transform:uppercase;
1913
+ padding:12px 8px 6px;
1914
+ }
1915
+ /* Two columns where there is room for two, one where there is not. Twenty
1916
+ rows in a single file is a scroll; the same twenty in pairs is a glance. */
1917
+ .mk-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:0 10px}
1918
+ .mk-item{display:flex;align-items:flex-start;gap:11px;padding:10px 8px;border-radius:9px}
1919
+ .mk-item:hover{background:var(--hover)}
1920
+ .mk-item .ico-sm{
1921
+ width:28px;height:28px;margin-top:1px;padding:6px;box-sizing:border-box;
1922
+ border-radius:7px;background:var(--sel);opacity:.75;
1923
+ }
1924
+ /* A light tile behind every mark. Plenty of brands ship a black-on-transparent
1925
+ logo, which in the dark theme is a black square on a black row — and there
1926
+ is no way to tell from the bytes which ones those are. */
1927
+ /* Sized against the row, not against the other glyphs on the page. A row here
1928
+ is a title over two lines of description, and a 18px mark beside 34px of
1929
+ text reads as a bullet point rather than as the thing the row is about. */
1930
+ .mk-logo,.mk-mono{
1931
+ width:28px;height:28px;margin-top:1px;flex:0 0 auto;
1932
+ border-radius:7px;box-sizing:border-box;
1933
+ }
1934
+ /* No tile behind it: a mark that already has its own shape does not want a
1935
+ second one drawn around it. What the tile was solving is solved instead by
1936
+ invert-dark, which is only put on the marks that actually need it. */
1937
+ .mk-logo{object-fit:contain}
1938
+ /* Plenty of brands ship a black glyph on transparency, which in the dark theme
1939
+ is a black shape on a black row. Inverting turns it white, which is what
1940
+ those same brands ship as their own dark-mode mark. Applied only to marks
1941
+ measured to be dark, flat and mostly transparent — a logo with colour in it,
1942
+ or one carrying its own background, is left exactly as its owner drew it. */
1943
+ @media (prefers-color-scheme:dark){
1944
+ :root:not([data-theme="light"]) .mk-logo.invert-dark{filter:invert(1)}
1945
+ }
1946
+ :root[data-theme="dark"] .mk-logo.invert-dark{filter:invert(1)}
1947
+ /* The fallback, and the reason the icon is allowed to fail: a lettered tile
1948
+ still tells two adjacent rows apart, which is the whole job. */
1949
+ .mk-mono{display:grid;place-items:center;font-size:13px;font-weight:600;color:#fff}
1950
+ .mk-naming{flex:1;min-width:0}
1951
+ .mk-name{font-size:13.5px;font-weight:500}
1952
+ .mk-desc{font-size:12px;color:var(--dim);margin-top:2px;line-height:1.45}
1953
+ .mk-desc.warn{color:var(--warn)}
1954
+ .mk-acts{display:flex;align-items:center;gap:6px;flex:0 0 auto;margin-top:1px}
1955
+ .mk-state{font-size:11.5px;color:var(--dim)}
1956
+ .mk-btn{
1957
+ height:26px;padding:0 10px;border:0;border-radius:7px;background:var(--sel);color:var(--fg);
1958
+ font:inherit;font-size:12px;cursor:pointer;
1959
+ }
1960
+ .mk-btn:hover{background:var(--hover)}
1961
+ .mk-btn.go{background:var(--accent);color:var(--bg)}
1962
+ .mk-btn:disabled{opacity:.5;cursor:default}
1963
+ .mk-icon-btn{
1964
+ width:26px;height:26px;border:0;border-radius:7px;background:transparent;color:var(--dim);
1965
+ font:inherit;font-size:15px;line-height:1;cursor:pointer;display:grid;place-items:center;padding:0;
1966
+ }
1967
+ .mk-icon-btn:hover{background:var(--sel);color:var(--fg)}
1968
+ .mk-icon-btn.drop:hover{color:var(--err)}
1969
+ #provider-picker{width:min(420px,92vw);max-height:70vh}
1970
+ #project-picker{width:min(380px,92vw);max-height:60vh}
1971
+ #browse{width:min(460px,92vw);max-height:70vh}
1972
+ #browse[open]{display:flex;flex-direction:column}
1973
+ #browse-list{overflow-y:auto;padding:4px 8px;flex:1}
1974
+ #browse-here{display:block;padding:2px 4px;color:var(--dim);word-break:break-all}
1975
+ #project-picker[open]{display:flex;flex-direction:column}
1976
+ #proj-list{overflow-y:auto;padding:4px 8px}
1977
+ .proj-foot{padding:4px 8px 10px;box-shadow:0 -1px 0 var(--line)}
1978
+ .proj-item{
1979
+ width:100%;display:flex;align-items:center;gap:9px;border:0;border-radius:8px;
1980
+ background:transparent;color:var(--fg);font:inherit;font-size:13px;cursor:pointer;
1981
+ padding:8px;text-align:left;
1982
+ }
1983
+ .proj-item:hover{background:var(--hover)}
1984
+ .proj-item .path{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1985
+ /* Two controls, one row: opening a project and forgetting it are different
1986
+ acts, and a button inside a button is not a thing. The forget stays hidden
1987
+ until the row is under the pointer — it is the rarer of the two by a wide
1988
+ margin, and a column of them reads as a list of things to delete. */
1989
+ /* The project header, plus the controls that act on the whole project: a new
1990
+ session there, and the menu. Always shown, in place of a count — they are
1991
+ what the header is for. */
1992
+ .fold-row{display:flex;align-items:center}
1993
+ .fold-row .fold{flex:1;min-width:0}
1994
+ .fold-row .more{
1995
+ display:grid;width:20px;height:20px;border:0;border-radius:5px;background:transparent;
1996
+ color:var(--dim);cursor:pointer;padding:0;place-items:center;flex:0 0 auto;margin-right:6px;
1997
+ }
1998
+ .fold-row .fold-new{margin-right:2px}
1999
+ .fold-row .more:hover,.fold-row .fold.menu-open ~ .more:last-child{background:var(--sel);color:var(--fg)}
2000
+ .fold-row .more .ico-sm{width:14px;height:14px;opacity:1}
2001
+ .proj-row{display:flex;align-items:center;border-radius:8px}
2002
+ .proj-row:hover{background:var(--hover)}
2003
+ .proj-row .proj-item:hover{background:transparent}
2004
+ .proj-forget{
2005
+ border:0;background:transparent;color:var(--dim);font:inherit;font-size:13px;
2006
+ cursor:pointer;padding:6px 9px;border-radius:8px;opacity:0;flex:none;
2007
+ }
2008
+ .proj-row:hover .proj-forget,.proj-forget:focus-visible{opacity:1}
2009
+ .proj-forget:hover{background:var(--sel);color:var(--fg)}
2010
+ /* The command palette.
2011
+ *
2012
+ * The sidebar shows five sessions per project and folds the rest away, which
2013
+ * is right for browsing and wrong for "the one about the parser, two days
2014
+ * ago". So this is a search field over every session in every tree opened
2015
+ * here, the projects themselves, and the few actions worth a shortcut. */
2016
+ #palette{width:min(560px,92vw);max-height:min(60vh,520px)}
2017
+ #palette[open]{display:flex;flex-direction:column}
2018
+ /* One left edge for everything: the field's magnifier, each row's icon, the
2019
+ group labels and the key hints all start 22px in, and the field's text
2020
+ starts where the row labels do. Rows sit 8px in so their highlight has room;
2021
+ their own 14px padding brings the content back to the shared edge. */
2022
+ .cmdk-head{position:relative;padding:12px 12px 8px}
2023
+ .cmdk-head .ico-sm{position:absolute;left:22px;top:calc(50% - 5.5px);pointer-events:none}
2024
+ #palette .cmdk-head input{height:40px;padding:0 10px 0 35px;font-size:14px}
2025
+ #cmdk-list{overflow-y:auto;padding:0 8px 8px;flex:1;scroll-padding-block:4px}
2026
+ .cmdk-group{
2027
+ padding:12px 14px 5px;font-size:10.5px;letter-spacing:.04em;text-transform:uppercase;
2028
+ color:var(--dim);
2029
+ }
2030
+ .cmdk-group:first-child{padding-top:6px}
2031
+ .cmdk-item{
2032
+ width:100%;display:flex;align-items:center;gap:10px;border:0;border-radius:8px;
2033
+ background:transparent;color:var(--fg);font:inherit;font-size:13px;cursor:pointer;
2034
+ padding:8px 14px;text-align:left;
2035
+ }
2036
+ /* Hover and the keyboard cursor are the same highlight: a list driven by both
2037
+ at once needs one answer to "which row does Enter take". */
2038
+ .cmdk-item.cursor{background:var(--sel)}
2039
+ .cmdk-item .ico-sm{opacity:.6}
2040
+ .cmdk-item .cmdk-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
2041
+ .cmdk-item .cmdk-sub{
2042
+ font-size:11px;color:var(--dim);flex:0 0 auto;max-width:45%;
2043
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
2044
+ }
2045
+ .cmdk-empty{padding:12px 14px;font-size:12.5px;color:var(--dim)}
2046
+ .cmdk-foot{
2047
+ display:flex;gap:16px;padding:9px 22px 11px;font-size:11px;color:var(--dim);
2048
+ box-shadow:0 -1px 0 var(--line);
2049
+ }
2050
+ .cmdk-foot kbd{
2051
+ font:inherit;font-size:10.5px;border:1px solid currentColor;border-radius:4px;
2052
+ padding:0 4px;margin-right:5px;opacity:.7;
2053
+ }
2054
+ #provider-picker[open]{display:flex;flex-direction:column}
2055
+ /* Connecting one vendor. Small on purpose: it asks for a single value, and a
2056
+ * panel-sized surface for one field reads as more work than it is. */
2057
+ /* Two columns when there is an account behind the dialog, one when there is
2058
+ * not — the width follows, because a 640px shell around a single key field is
2059
+ * a lot of empty room. */
2060
+ #connect{width:min(460px,92vw);max-width:min(760px,94vw)}
2061
+ /* 640 was sized for a key field with a paragraph beside it. On an account
2062
+ * plan this is a window that asks somebody to sign in, opened over a full
2063
+ * screen, and at that width the lede and supporting points wrapped every two
2064
+ * or three words — a column of ragged fragments reads as fine print. */
2065
+ #connect:has(#cn-aside:not([hidden])){width:min(760px,94vw)}
2066
+ .cn-grid{display:grid;grid-template-columns:1fr}
2067
+ #connect:has(#cn-aside:not([hidden])) .cn-grid{grid-template-columns:minmax(0,.85fr) minmax(0,1fr)}
2068
+ .cn-aside{
2069
+ display:flex;flex-direction:column;gap:20px;padding:30px 28px;
2070
+ background:var(--hover);border-right:1px solid var(--line);
2071
+ border-radius:16px 0 0 16px;
2072
+ }
2073
+ .cn-brand{display:flex;align-items:center;gap:9px;font-weight:600;font-size:15px}
2074
+ .cn-brand svg{width:22px;height:22px;flex:0 0 auto}
2075
+ .cn-aside-lede{font-size:13px;color:var(--dim);line-height:1.55}
2076
+ .cn-points{margin:2px 0 0;padding:0;list-style:none;display:flex;flex-direction:column;gap:12px}
2077
+ .cn-points li{
2078
+ font-size:12.5px;color:var(--dim);line-height:1.5;padding-left:16px;position:relative;
2079
+ }
2080
+ /* A dash rather than a bullet: list markers down the side of a 200px
2081
+ * column read as a form to fill in, which is the thing on the other side. */
2082
+ .cn-points li::before{content:"—";position:absolute;left:0;opacity:.55}
2083
+ .cn-main{display:flex;flex-direction:column;min-width:0;position:relative}
2084
+ /* What the ? explains, as a bubble under it rather than a paragraph in the
2085
+ * body: peeking at a detail must not push the field and the buttons down, or
2086
+ * the button being aimed at moves out from under the pointer. The row menu's
2087
+ * fill and shadow, and no outline, like every other thing that floats here. */
2088
+ .cn-tip{
2089
+ position:absolute;top:50px;right:16px;z-index:5;width:min(300px,calc(100% - 32px));
2090
+ padding:10px 12px;border-radius:10px;background:var(--bg);
2091
+ box-shadow:0 1px 2px #0000000f,0 8px 28px #00000024;
2092
+ }
2093
+ .cn-tip>div+div{margin-top:4px}
2094
+ /* Below this the two columns would leave the form narrower than its own
2095
+ * labels, which is the width the fields actually need. */
2096
+ @media (max-width:700px){
2097
+ #connect:has(#cn-aside:not([hidden])) .cn-grid{grid-template-columns:1fr}
2098
+ .cn-aside{display:none}
2099
+ }
2100
+ .cn-head{padding:24px 24px 2px;display:flex;align-items:flex-start;gap:10px}
2101
+ .cn-naming{flex:1;min-width:0}
2102
+ .cn-title{font-weight:600;font-size:17px;letter-spacing:-.01em}
2103
+ .cn-head .row-desc{margin-top:2px}
2104
+ /* Where the key is written, and which variable replaces it, is worth being
2105
+ able to find and not worth reading twice. Folded behind a mark that says
2106
+ there is more, rather than printed under a field whose answer most people
2107
+ already have in the clipboard. */
2108
+ .cn-why{
2109
+ flex:none;width:20px;height:20px;border:0;border-radius:50%;
2110
+ background:var(--field);color:var(--dim);font:inherit;font-size:11.5px;
2111
+ line-height:1;cursor:pointer;
2112
+ }
2113
+ .cn-why:hover{background:var(--sel);color:var(--fg)}
2114
+ .cn-why[aria-expanded="true"]{background:var(--accent);color:var(--bg)}
2115
+ /* The way out, drawn rather than written. The actions row already carries a
2116
+ worded cancel, but this dialog is the first thing a new account sees and it
2117
+ opens over whatever the user was doing — a mark in the corner is where a
2118
+ window that appeared uninvited is looked for first, and it stays put while
2119
+ the row below it changes wording between sending a code and verifying one. */
2120
+ .cn-x{
2121
+ flex:none;width:22px;height:22px;border:0;border-radius:50%;padding:0;
2122
+ background:transparent;color:var(--dim);cursor:pointer;
2123
+ display:flex;align-items:center;justify-content:center;
2124
+ }
2125
+ .cn-x svg{width:13px;height:13px}
2126
+ .cn-x:hover{background:var(--hover);color:var(--fg)}
2127
+ .cn-body{padding:16px 24px 0;display:flex;flex-direction:column;gap:12px}
2128
+ .cn-body input{
2129
+ width:100%;height:42px;border:0;border-radius:10px;background:var(--field);color:var(--fg);
2130
+ /* Room on the right for the reveal control and for whatever a password
2131
+ manager injects into a type=password field — both sit over the text
2132
+ otherwise, on top of the one value being typed. */
2133
+ padding:0 40px 0 13px;font:inherit;font-size:14px;
2134
+ }
2135
+ .cn-body input:focus{outline:none;background:var(--field-focus)}
2136
+ /* Each note on its own line. Run together, the sentence wrapped inside the
2137
+ env var name — ANTHROPIC_API_KE / Y — and put the link in the middle of
2138
+ a paragraph, where it reads as part of the sentence rather than a thing to
2139
+ click. */
2140
+ .cn-note{font-size:12.5px;color:var(--dim);line-height:1.5}
2141
+ /* The way in, at the size of the thing it is. The shared rule sizes this
2142
+ * button for an account row, where it sits in a line of controls; here it is
2143
+ * the one thing the window exists for and stands over a 42px field. */
2144
+ .cn-body button.signin-site{height:44px;border-radius:10px;font-size:14px;font-weight:550}
2145
+ .cn-note .mono{white-space:nowrap}
2146
+ .cn-local{display:flex;gap:6px;align-items:flex-start}
2147
+ .cn-local .ico-sm{width:13px;height:13px;flex:0 0 auto;margin-top:1.5px;color:var(--fg)}
2148
+ .cn-local strong{color:var(--fg);font-weight:600}
2149
+ /* The dialog ends on its buttons, so it needs the padding the shared rule
2150
+ leaves to whatever follows. */
2151
+ #connect .dlg-actions{padding:18px 24px 22px}
2152
+ /* Two buttons and a link, not two groups: the pair stays together on the left
2153
+ and only the link floats, so the shared rule's gap is turned off here. */
2154
+ #connect .dlg-actions button.quiet{margin-right:0}
2155
+ /* An anchor, not a button, so the shared button.quiet rule does not reach it —
2156
+ and it is the one thing here that leaves the app, so it sits apart from the
2157
+ two that do not. */
2158
+ #connect .dlg-actions a.quiet{margin-left:auto;text-decoration:none}
2159
+ /* Cancel and Get a key are buttons, and look it: the grey fill the other
2160
+ * dialog buttons have, where Connect carries the colour. Bare text beside a
2161
+ * filled button read as a caption, not as something to press. */
2162
+ #connect .dlg-actions button.quiet,#connect .dlg-actions a.quiet{
2163
+ padding:10px 16px;border-radius:10px;background:var(--field);color:var(--fg);
2164
+ font-size:14px;line-height:normal;
2165
+ }
2166
+ #connect .dlg-actions button.quiet:hover,#connect .dlg-actions a.quiet:hover{background:var(--sel)}
2167
+ .pp-head{padding:12px 12px 6px}
2168
+ .pp-head input{
2169
+ width:100%;height:34px;border:0;border-radius:9px;background:var(--field);color:var(--fg);
2170
+ padding:0 11px;font:inherit;font-size:13px;
2171
+ }
2172
+ .pp-head input:focus{outline:none;background:var(--field-focus)}
2173
+ #pp-list{overflow-y:auto;padding:4px 8px 12px}
2174
+ .pp-provider{border-radius:9px}
2175
+ .pp-provider[open]{background:var(--panel)}
2176
+ .pp-item{
2177
+ width:100%;display:flex;align-items:center;gap:9px;border-radius:9px;list-style:none;
2178
+ color:var(--fg);font:inherit;font-size:13px;cursor:pointer;padding:9px 9px;text-align:left;
2179
+ }
2180
+ .pp-item::-webkit-details-marker{display:none}
2181
+ .pp-item:hover{background:var(--hover)}
2182
+ .pp-provider[open] .chev{transform:rotate(90deg)}
2183
+ .pp-body{padding:0 9px 10px}
2184
+ .pp-body .ghost{margin-top:8px;height:28px;font-size:12px}
2185
+ .pp-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
2186
+ .pp-item .muted{font-size:11px;flex:0 0 auto}
2187
+ .chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
2188
+ .chip{
2189
+ border:0;background:var(--field);color:var(--fg);border-radius:8px;padding:5px 10px;
2190
+ font:inherit;font-size:12.5px;cursor:pointer;display:flex;align-items:center;gap:7px;
2191
+ }
2192
+ .chip:hover{background:var(--sel)}
2193
+ .chip.on{background:var(--accent);color:var(--bg)}
2194
+ .chip .sub{font-size:11px;opacity:.6}
2195
+ /* A pill, not a word: .sub beside it is a quieter copy of the name, and this
2196
+ is a different kind of claim about the model rather than more of its name.
2197
+ Same geometry as .mk-tag and the picker's own. Both other chip states repaint
2198
+ the background this sits on — hover takes var(--sel), selected takes the
2199
+ accent — so the pill is moved each time rather than dissolving into it. */
2200
+ .chip .free{
2201
+ flex:0 0 auto;padding:1px 5px;border-radius:4px;background:var(--sel);
2202
+ color:var(--dim);font-size:10px;font-weight:500;letter-spacing:.02em;
2203
+ text-transform:uppercase;
2204
+ }
2205
+ .chip:hover .free{background:var(--field)}
2206
+ .chip.on .free{background:var(--bg);color:var(--accent)}
2207
+ .panel .row-head h2{margin:0}
2208
+ .panel .row-head{margin-bottom:14px}
2209
+ .muted{color:var(--dim)}
2210
+ #empty{max-width:720px;margin:7vh auto 0;text-align:left}
2211
+ /* The sidebar lockup is 18px and often off-screen at narrow widths, so the
2212
+ empty state carries its own full-size one: same geometry, scaled up, and it
2213
+ is the largest thing on the screen — the prompt below it drops to 20px so
2214
+ the two do not compete. */
2215
+ .empty-brand{display:flex;align-items:center;gap:18px;margin:0 0 30px}
2216
+ .brand-mark{width:68px;height:68px;color:var(--fg);flex:0 0 auto}
2217
+ .brand-mark svg{width:68px;height:68px;display:block}
2218
+ .brand-name{font-size:42px;font-weight:650;letter-spacing:-.035em;line-height:1.1}
2219
+ #empty h1{font-size:21px;font-weight:550;letter-spacing:-.01em;margin:0 0 8px}
2220
+ #empty p{color:var(--dim);margin:0 0 22px;font-size:13.5px}
2221
+ /* Stacked, not a row of pills: each one is a sentence rather than a word, and
2222
+ three sentences side by side either wrap mid-phrase or shrink the type. Down
2223
+ the page they read as a short list of offers, which is what they are. */
2224
+ #starters{display:flex;flex-direction:column;align-items:flex-start;gap:8px}
2225
+ #starters button{
2226
+ border:0;background:var(--panel);color:var(--fg);border-radius:999px;
2227
+ padding:8px 15px;font:inherit;font-size:13px;cursor:pointer;text-align:left;
2228
+ max-width:100%;
2229
+ }
2230
+ #starters button:hover{background:var(--hover)}
2231
+ /* Parked in the turn's own bottom margin: laid out in flow it reserved ~30px
2232
+ under every reply whether or not it was visible, which read as a stray gap
2233
+ after a reload — and collapsing it on hover would shove the transcript. */
2234
+ .actions{
2235
+ position:absolute;left:0;top:100%;margin-top:1px;
2236
+ display:flex;gap:4px;opacity:0;transition:opacity .12s;
2237
+ }
2238
+ .turn:hover .actions,.actions:focus-within{opacity:1}
2239
+ .act{
2240
+ border:0;background:transparent;color:var(--dim);border-radius:6px;
2241
+ padding:3px 8px;font:inherit;font-size:12px;cursor:pointer;
2242
+ display:inline-flex;align-items:center;gap:5px;
2243
+ }
2244
+ .act:hover{background:var(--hover);color:var(--fg)}
2245
+ /* Sized to the label it sits beside, not to the 16px grid the sidebar uses. */
2246
+ .act .ico-sm{width:13px;height:13px;opacity:.9}
2247
+ .md{word-break:break-word}
2248
+ .md-p{margin:0 0 10px;white-space:pre-wrap}
2249
+ .md-h{margin:16px 0 8px;font-size:14px}
2250
+ .md-h1{font-size:18px} .md-h2{font-size:16px} .md-h3{font-size:15px}
2251
+ .md>.md-h:first-child{margin-top:0}
2252
+ .md-list{margin:0 0 10px;padding-left:22px}
2253
+ .md-list .md-list{margin:2px 0 0}
2254
+ .md-list>li{margin:2px 0}
2255
+ .md-loose>li>.md-p{margin:0 0 6px}
2256
+ .md-list>li.md-task{list-style:none}
2257
+ .md-check{margin:0 7px 0 -20px;vertical-align:-1px}
2258
+ .md-quote{margin:0 0 10px;padding-left:12px;border-left:3px solid var(--line);color:var(--dim)}
2259
+ .md-code{background:var(--field);border-radius:5px;padding:1.5px 5px;font-size:13px}
2260
+ .md-link{color:var(--accent)}
2261
+ .md-dead{text-decoration:underline dotted;text-underline-offset:3px}
2262
+ .md-hr{border:0;height:1px;background:var(--line);margin:14px 0}
2263
+ .md-table-wrap{overflow-x:auto;margin:0 0 12px}
2264
+ .md-table{border-collapse:collapse;font-size:13px}
2265
+ .md-table th,.md-table td{padding:6px 12px;text-align:left;vertical-align:top}
2266
+ .md-table th{font-weight:600;background:var(--field)}
2267
+ .md-table tbody tr:nth-child(even){background:var(--hover)}
2268
+ .code-wrap{border-radius:10px;overflow:hidden;margin:0 0 12px;background:var(--panel)}
2269
+ .code-bar{
2270
+ display:flex;justify-content:space-between;align-items:center;padding:4px 10px;
2271
+ font-size:11px;color:var(--dim);
2272
+ }
2273
+ .code-copy{background:none;border:0;color:var(--dim);cursor:pointer;font:inherit;font-size:11px}
2274
+ .code-copy:hover{color:var(--fg)}
2275
+ pre.code{margin:0;padding:10px 12px;overflow-x:auto;font-size:12.5px;line-height:1.55}
2276
+ .t-kw{color:var(--kw)} .t-str{color:var(--str)} .t-com{color:var(--com)} .t-num{color:var(--num)}
2277
+
2278
+ /* tool cards */
2279
+ details.tool{border-radius:9px;margin:0 0 6px;background:var(--panel);overflow:hidden}
2280
+ details.tool>summary{
2281
+ padding:7px 12px;cursor:pointer;display:flex;gap:8px;align-items:center;
2282
+ font-size:13px;list-style:none;
2283
+ }
2284
+ details.tool>summary::-webkit-details-marker{display:none}
2285
+ /* The native disclosure marker is hidden so it does not vary by browser; put
2286
+ * an explicit one after the duration so a closed card still looks clickable. */
2287
+ details.tool>summary::after{
2288
+ content:"";width:6px;height:6px;flex:0 0 auto;margin:0 2px 0 3px;
2289
+ border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
2290
+ color:var(--dim);opacity:.7;transform:rotate(-45deg);transition:transform .15s,opacity .12s;
2291
+ }
2292
+ details.tool[open]>summary::after{transform:rotate(45deg)}
2293
+ details.tool>summary:hover::after{opacity:1}
2294
+ .tool .dot{width:7px;height:7px;border-radius:50%;background:var(--brand);flex:0 0 auto}
2295
+ .tool.done .dot{background:var(--ok)}
2296
+ .tool.failed .dot{background:var(--err)}
2297
+ .tool .name{font-family:ui-monospace,monospace}
2298
+ .tool .ms{color:var(--dim);font-size:11px;margin-left:auto}
2299
+ .tool-body{padding:2px 12px 10px}
2300
+ .tool-body pre{margin:0;overflow-x:auto;font-size:12px;max-height:340px}
2301
+ .tool-body .label{font-size:11px;color:var(--dim);margin:0 0 4px}
2302
+ .tool-body .kv{margin:0 0 8px}
2303
+ .tool-summary{font-size:12.5px;line-height:1.5;margin:0 0 6px}
2304
+ details.tool-raw>summary{font-size:11px;color:var(--dim);cursor:pointer;margin:0 0 4px}
2305
+ pre.diff{margin:0;font-size:12px;line-height:1.5;overflow-x:auto}
2306
+ .d-add{background:var(--add-bg);color:var(--add)}
2307
+ .d-del{background:var(--del-bg);color:var(--del)}
2308
+ .d-same{color:var(--dim)}
2309
+
2310
+ /* A line of text, not a box: it reads as part of the reply — what was done
2311
+ * between two paragraphs — and the box is kept for what opens under it. The
2312
+ * chevron trails the words, where it points at what a click will show. */
2313
+ details.work{margin:0 0 12px}
2314
+ /* What a media tool made, under the steps that made it. No frame: the image is
2315
+ its own edge, and a border would only box it in. */
2316
+ .media-strip{display:flex;flex-wrap:wrap;align-items:flex-start;gap:12px;margin:0 0 14px}
2317
+ .media-item{margin:0;display:flex;flex-direction:column;gap:5px;max-width:100%}
2318
+ /* Sized by the picture itself, so the corners are the picture's corners: a
2319
+ box of fixed proportions around it showed as a panel-coloured frame
2320
+ wherever the two shapes differed. */
2321
+ .media-item img,.media-item video{display:block;width:auto;height:auto;max-width:min(360px,100%);max-height:440px;border-radius:12px;cursor:zoom-in}
2322
+ .media-item video{max-width:min(480px,100%);cursor:auto}
2323
+ /* A clip, drawn by the page: a filled card, no outline, like everything else
2324
+ that sits on the conversation. */
2325
+ .media-item.audio{width:min(360px,100%)}
2326
+ .media-item audio{display:none}
2327
+ .audio-card{display:flex;align-items:center;gap:12px;padding:10px 14px 10px 10px;border-radius:14px;background:var(--panel)}
2328
+ .audio-play{
2329
+ width:36px;height:36px;flex:0 0 auto;border:0;border-radius:50%;padding:0;
2330
+ display:grid;place-items:center;background:var(--fg);color:var(--bg);cursor:pointer;
2331
+ }
2332
+ .audio-play:hover{opacity:.85}
2333
+ .audio-play svg{width:14px;height:14px}
2334
+ .audio-play svg path{fill:currentColor}
2335
+ .audio-card.playing .audio-play svg path{fill:none;stroke-width:2.2}
2336
+ .audio-meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:7px}
2337
+ .audio-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px;font-size:12.5px}
2338
+ .audio-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg);cursor:pointer}
2339
+ .audio-name:hover{text-decoration:underline;text-underline-offset:2px}
2340
+ .audio-time{flex:0 0 auto;color:var(--dim);font-size:11.5px;font-variant-numeric:tabular-nums}
2341
+ /* Thin to look at, tall to hit: the pseudo-element widens the target. */
2342
+ .audio-track{position:relative;height:4px;border-radius:2px;background:var(--hover);cursor:pointer}
2343
+ .audio-track::before{content:"";position:absolute;left:0;right:0;top:-7px;bottom:-7px}
2344
+ .audio-track:focus-visible{outline:none;background:var(--sel)}
2345
+ .audio-fill{height:100%;width:0;border-radius:2px;background:var(--fg);pointer-events:none}
2346
+ .media-item figcaption{max-width:360px;font-size:11.5px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}
2347
+ .media-item figcaption:hover{color:var(--fg)}
2348
+ details.work>summary{
2349
+ padding:3px 0;cursor:pointer;font-size:13.5px;color:var(--dim);list-style:none;
2350
+ display:flex;gap:6px;align-items:center;width:max-content;max-width:100%;
2351
+ }
2352
+ details.work>summary:hover{color:var(--fg)}
2353
+ details.work>summary::-webkit-details-marker{display:none}
2354
+ details.work>summary::after{
2355
+ content:"";width:6px;height:6px;flex:0 0 auto;margin-left:2px;
2356
+ border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
2357
+ transform:rotate(-45deg);transition:transform .15s;
2358
+ }
2359
+ details.work[open]>summary::after{transform:rotate(45deg)}
2360
+ .work-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
2361
+ details.work.failed>summary{color:var(--err)}
2362
+ .work-body{padding:8px;margin-top:4px;border-radius:10px;background:var(--panel)}
2363
+ .work-body details.tool{margin-bottom:6px}
2364
+ /* Spacing separates neighbouring calls; keeping it after the final one makes
2365
+ * a single card's summary sit visibly above the centre of the filled panel. */
2366
+ .work-body details.tool:last-child{margin-bottom:0}
2367
+ /* the working indicator */
2368
+ .working{display:flex;align-items:center;gap:9px;color:var(--dim);font-size:13.5px;margin:0 0 10px}
2369
+ .working-time{font-size:12px;opacity:.7}
2370
+ .spinner{
2371
+ width:12px;height:12px;flex:0 0 auto;border-radius:50%;
2372
+ border:1.5px solid var(--line);border-top-color:var(--brand);
2373
+ animation:spin .7s linear infinite;
2374
+ }
2375
+ @keyframes spin{to{transform:rotate(360deg)}}
2376
+ /* A silver band sweeps the brand-coloured label while the model is still
2377
+ thinking; the reasoning block stops, and falls back to --dim, once
2378
+ sealThinking drops its spinner. */
2379
+ .working-label,
2380
+ details.thinking>summary:has(.spinner) .t-label{
2381
+ background:linear-gradient(90deg,var(--brand-ink) 0%,var(--brand-ink) 42%,var(--brand-shine) 50%,var(--brand-ink) 58%,var(--brand-ink) 100%);
2382
+ background-size:250% 100%;
2383
+ -webkit-background-clip:text;background-clip:text;
2384
+ -webkit-text-fill-color:transparent;color:transparent;
2385
+ animation:shimmer 1.8s linear infinite;
2386
+ }
2387
+ @keyframes shimmer{from{background-position:100% 0}to{background-position:0 0}}
2388
+ @media (prefers-reduced-motion:reduce){
2389
+ /* A spinner is decoration; the elapsed seconds carry the same information. */
2390
+ .spinner{animation:none;border-top-color:var(--line)}
2391
+ .working-label,
2392
+ details.thinking>summary:has(.spinner) .t-label{
2393
+ animation:none;background:none;-webkit-text-fill-color:currentColor;color:var(--brand-ink);
2394
+ }
2395
+ }
2396
+
2397
+ /* reasoning */
2398
+ details.thinking{margin:0 0 10px}
2399
+ details.thinking>summary{
2400
+ display:flex;align-items:center;gap:8px;cursor:pointer;list-style:none;
2401
+ color:var(--dim);font-size:13px;
2402
+ }
2403
+ details.thinking>summary::-webkit-details-marker{display:none}
2404
+ details.thinking>summary:hover{color:var(--fg)}
2405
+ /* A chevron after the label says the block opens; it turns down when open. */
2406
+ details.thinking>summary::after{
2407
+ content:"";width:5px;height:5px;flex:0 0 auto;margin-left:2px;
2408
+ border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
2409
+ transform:rotate(-45deg);transition:transform .15s;
2410
+ }
2411
+ details.thinking[open]>summary::after{transform:translateY(-2px) rotate(45deg)}
2412
+ .thinking-body{
2413
+ white-space:pre-wrap;color:var(--dim);font-size:13.5px;line-height:1.6;
2414
+ margin-top:8px;padding-left:12px;border-left:2px solid var(--line);
2415
+ }
2416
+ /* the tool card dot pulses while its call is in flight */
2417
+ .tool:not(.done):not(.failed) .dot{animation:pulse 1.1s ease-in-out infinite}
2418
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
2419
+ @media (prefers-reduced-motion:reduce){.tool .dot{animation:none}}
2420
+ .notice{color:var(--dim);font-size:12.5px;margin:0 0 8px}
2421
+ .err{color:var(--err);white-space:pre-wrap;margin:0 0 8px}
2422
+
2423
+ /* composer */
2424
+ form{padding:6px 24px 18px;background:var(--bg)}
2425
+ .composer{
2426
+ position:relative;
2427
+ max-width:720px;margin:0 auto;border-radius:16px;
2428
+ background:var(--field);padding:16px 16px 10px;transition:background .15s,box-shadow .15s;
2429
+ }
2430
+ /* The top edge, as a grab handle. It spans the box rather than sitting in a
2431
+ corner, because the edge is what a person aims at; the bar only appears
2432
+ under the cursor, so an idle composer keeps its clean top line. */
2433
+ #grip{
2434
+ position:absolute;top:0;left:0;right:0;height:12px;cursor:ns-resize;
2435
+ display:grid;place-items:center;touch-action:none;
2436
+ }
2437
+ /* Faint rather than hidden: a handle nobody can see is a handle nobody
2438
+ finds. A pill, not a rule across the top — this is a thing to grab, and the
2439
+ design carries no borders. */
2440
+ #grip::before{
2441
+ content:"";width:32px;height:3px;border-radius:999px;
2442
+ background:var(--dim);opacity:.2;transition:opacity .12s,width .12s;
2443
+ }
2444
+ #grip:hover::before,#grip.dragging::before{opacity:.5;width:40px}
2445
+ /* While dragging, the pointer owns the screen: without this a fast drag
2446
+ selects the transcript behind it. */
2447
+ body.resizing{user-select:none;cursor:ns-resize}
2448
+ .composer:focus-within{background:var(--field-focus)}
2449
+ textarea{
2450
+ width:100%;resize:none;min-height:56px;max-height:32vh;padding:0;border:0;
2451
+ background:transparent;color:var(--fg);font:inherit;line-height:1.6;
2452
+ }
2453
+ textarea:focus{outline:none}
2454
+ textarea::placeholder{color:var(--dim)}
2455
+ .input-wrap{position:relative}
2456
+ /* Block, not the inline-block a textarea is by default: inline leaves room
2457
+ under the baseline for a descender, so the wrapper ends up a few pixels
2458
+ taller than the field inside it and the two scroll by different amounts. */
2459
+ /* Above the backdrop, and that needs saying: the mirror is absolutely
2460
+ positioned, which puts it in front of a static sibling however the two are
2461
+ ordered in the markup. Without this the chip paints over the words it is
2462
+ drawn for. */
2463
+ .input-wrap textarea{display:block;position:relative;z-index:1}
2464
+ /* The field draws the caret and the selection; the mirror draws the words.
2465
+ With the glyphs in the mirror a mention can carry something a textarea
2466
+ never could — an icon in the slot the @ sign would have taken — because
2467
+ the field only has to agree on where each glyph lands, not on what is
2468
+ drawn there. The selection is translucent so the words under it stay
2469
+ readable; an opaque highlight would sit on top of the mirror. */
2470
+ /* The native caret is hidden and one is drawn instead. A textarea offers
2471
+ caret-color and nothing else — no width — and at one device pixel the bar
2472
+ is hard to find on a pale field. See placeCaret(). */
2473
+ .input-wrap textarea{color:transparent;caret-color:transparent}
2474
+ #caret{
2475
+ position:absolute;width:2px;border-radius:1px;background:var(--brand);
2476
+ pointer-events:none;z-index:2;opacity:0;
2477
+ }
2478
+ #caret.on{animation:caret-blink 1.06s steps(1,end) infinite}
2479
+ @keyframes caret-blink{0%,55%{opacity:1}56%,100%{opacity:0}}
2480
+ /* Solid while the caret is moving: a bar that blinks out mid-keystroke reads
2481
+ as a dropped character. The class is taken off and put back to restart the
2482
+ animation, which is what makes the phase reset. */
2483
+ #caret.on.moving{animation:none;opacity:1}
2484
+ @media (prefers-reduced-motion:reduce){#caret.on{animation:none;opacity:1}}
2485
+ /* Measures where the caret goes: the mirror's metrics, none of its ink. */
2486
+ #caret-probe{visibility:hidden}
2487
+ .input-wrap textarea::selection{background:var(--brand-tint)}
2488
+ /* Every metric that decides where a glyph lands has to match the field: font,
2489
+ line-height, wrapping, and the padding, which is zero on this textarea.
2490
+ Six pixels wider on each side with the text pushed back in by six: the
2491
+ glyphs land where the textarea puts them, and the chip's background — drawn
2492
+ outside its own advance width — has somewhere to go instead of being cut off
2493
+ at the start of a line. */
2494
+ #input-mirror,#caret-probe{
2495
+ position:absolute;top:0;bottom:0;left:-6px;right:-6px;padding:0 6px;border:0;
2496
+ font:inherit;line-height:1.6;white-space:pre-wrap;overflow-wrap:break-word;
2497
+ color:var(--fg);pointer-events:none;overflow:hidden;
2498
+ }
2499
+ /**
2500
+ * The floating chip's surface, inline. No remove button — Backspace takes the
2501
+ * whole mention instead — and the icon lives in the slot the @ sign vacated.
2502
+ *
2503
+ * White on the field, dark on the dark field — the same two tokens the chip
2504
+ * above the box uses. Vertical padding does not enter the inline advance and
2505
+ * the line-height leaves room for it. Horizontal padding is given back as
2506
+ * negative margin so no glyph moves: the wash reaches into the whitespace on
2507
+ * either side of the mention. A space is four pixels wide, and the menu
2508
+ * inserts two of them on each side (see roomBefore), which is what allows
2509
+ * four of padding and still four of clearance to the next word.
2510
+ *
2511
+ * The shadow is a lift, not an outline. A hard ring made each mention read as
2512
+ * a small input field dropped into the middle of a sentence.
2513
+ */
2514
+ #input-mirror mark{
2515
+ background:var(--bg);color:var(--fg);border-radius:6px;
2516
+ padding:3px 4px;margin:0 -4px;
2517
+ box-shadow:var(--mention-lift);
2518
+ box-decoration-break:clone;-webkit-box-decoration-break:clone;
2519
+ }
2520
+ /* The @ sign's own advance width, measured on every paint, with the icon of
2521
+ what was mentioned drawn in it instead. The width is the field's, so the
2522
+ name after it lands exactly where the field puts it. */
2523
+ #input-mirror mark .at{
2524
+ display:inline-block;width:var(--at-w,13.5px);vertical-align:-1.5px;
2525
+ color:var(--dim);
2526
+ }
2527
+ #input-mirror mark .at svg{width:11px;height:11px;display:block;margin-left:.5px}
2528
+ /* The mirror sits exactly under the textarea, so every metric that decides
2529
+ where a glyph lands has to match: font, line-height, wrapping, and the
2530
+ padding, which is zero on the composer's textarea and must be zero here. */
2531
+ textarea.boxed{
2532
+ border:0;background:var(--field);border-radius:6px;
2533
+ padding:7px 9px;resize:vertical;min-height:0;font:inherit;font-size:12px;
2534
+ font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
2535
+ }
2536
+ textarea.boxed:focus{outline:none;background:var(--field-focus)}
2537
+ .composer-bar{display:flex;align-items:center;gap:6px;margin-top:10px}
2538
+ .composer.drag{background:var(--field-focus);outline:2px dashed var(--line);outline-offset:-6px}
2539
+ .icon-btn{
2540
+ width:26px;height:26px;border:0;border-radius:7px;background:transparent;color:var(--fg);
2541
+ font:inherit;cursor:pointer;display:grid;place-items:center;
2542
+ }
2543
+ .icon-btn svg{width:14px;height:14px}
2544
+ .icon-btn:hover{background:var(--hover)}
2545
+ /* The folder it runs in, set at the same weight as the model beside it: three
2546
+ settings on one bar, none of them shouting over the others. */
2547
+ #workspace-picker .picker-btn{gap:6px}
2548
+ #workspace-picker .ico-sm{width:13px;height:13px;opacity:1;flex:0 0 auto}
2549
+ /*
2550
+ * Reasoning effort. A setting of its own beside the model rather than rows in
2551
+ * the model list: the two are picked independently, and folding tiers into
2552
+ * the list would multiply every model by six.
2553
+ *
2554
+ * The panel is a rail of stops read left to right, faster to smarter, because
2555
+ * the tiers are one quantity and a list would present them as unrelated
2556
+ * options. Stops come from the model: a tier it lacks is not drawn at all.
2557
+ */
2558
+ #effort-picker .picker-btn{gap:5px}
2559
+ #effort-picker .picker-btn .ico-sm{width:13px;height:13px;opacity:1}
2560
+ #effort-picker .menu{min-width:272px;width:272px;padding:14px 14px 12px;left:auto;right:0}
2561
+ .effort-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px}
2562
+ .effort-title{font-size:13px;color:var(--dim)}
2563
+ .effort-current{font-size:13px;font-weight:600;color:var(--fg)}
2564
+ .effort-desc{margin-top:3px;font-size:12px;line-height:1.4;color:var(--dim);min-height:34px}
2565
+ .effort-ends{display:flex;justify-content:space-between;margin:10px 0 6px;font-size:11.5px;color:var(--dim)}
2566
+ .effort-rail{
2567
+ --stop:26px;position:relative;display:flex;align-items:center;justify-content:space-between;
2568
+ height:26px;border-radius:13px;background:var(--hover);
2569
+ }
2570
+ /* Filled up to the centre of the chosen stop, so the amount reads at a glance. */
2571
+ .effort-fill{position:absolute;left:0;top:0;bottom:0;border-radius:13px;background:var(--line);transition:width .15s}
2572
+ .effort-stop{
2573
+ position:relative;z-index:1;width:26px;height:26px;padding:0;border:0;outline:0;border-radius:50%;
2574
+ background:transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;
2575
+ }
2576
+ .effort-stop:focus{outline:0}
2577
+ .effort-stop:focus-visible .effort-dot{background:var(--fg)}
2578
+ .effort-stop:hover .effort-dot{background:var(--fg);opacity:.7}
2579
+ .effort-dot{width:6px;height:6px;border-radius:50%;background:var(--dim);opacity:.6}
2580
+ .effort-knob{width:18px;height:18px;border-radius:50%;background:var(--fg)}
2581
+ .effort-names{display:flex;justify-content:space-between;margin-top:6px}
2582
+ .effort-name{width:26px;display:flex;justify-content:center;font-size:11px;color:var(--dim);white-space:nowrap}
2583
+ .effort-name.on{color:var(--fg);font-weight:600}
2584
+ #attachments{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
2585
+ #attachments:empty{display:none}
2586
+ /* Outside the composer, tracking its width so the rows line up with the box
2587
+ they are waiting on. min-width:0 twice over — here and on the line inside
2588
+ each row — because a flex item defaults to refusing to shrink below its
2589
+ content, and one long queued line would push the page sideways at phone
2590
+ width instead of being cut. */
2591
+ #queued{
2592
+ display:flex;flex-direction:column;gap:4px;min-width:0;
2593
+ max-width:720px;margin:0 auto 6px;
2594
+ }
2595
+ #queued:empty{display:none}
2596
+ /* Full width, one per row, because these are sentences rather than file
2597
+ names: a chip that fits three words of a queued message says nothing about
2598
+ which message it is. --panel, not --field: out here the backdrop is the
2599
+ page, and the field's 2% wash is invisible against it. */
2600
+ .q{display:flex;align-items:center;gap:8px;background:var(--panel);border-radius:10px;padding:7px 10px 7px 12px;font-size:12px;min-width:0}
2601
+ /* min-width:0 or the ellipsis never engages: a flex item refuses to shrink
2602
+ below its content by default, so at phone width the row pushed the page
2603
+ sideways instead of cutting the line. */
2604
+ .q .n{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dim)}
2605
+ /* Not ".mark": that is the connector glyph, and it is a fixed 18px box. */
2606
+ .q .tag{flex:0 0 auto;color:var(--dim);font-size:11px}
2607
+ /* Joining the turn already running, rather than waiting behind it. The brand
2608
+ colour is what this app uses for the harness being alive, which is exactly
2609
+ what the difference is. */
2610
+ .q.soon .tag{color:var(--brand-ink)}
2611
+ :root[data-theme="dark"] .q.soon .tag{color:var(--brand)}
2612
+ .q .go{
2613
+ flex:0 0 auto;height:22px;padding:0 9px;border:0;border-radius:7px;
2614
+ background:var(--hover);color:var(--fg);cursor:pointer;
2615
+ font:inherit;font-size:11px;white-space:nowrap;
2616
+ }
2617
+ .q .go:hover{background:var(--brand-tint);color:var(--brand-ink)}
2618
+ :root[data-theme="dark"] .q .go:hover{color:var(--brand)}
2619
+ .q .x{border:0;background:transparent;color:var(--dim);cursor:pointer;font:inherit;padding:0 2px}
2620
+ .q .x:hover{color:var(--err)}
2621
+ .att{
2622
+ display:flex;align-items:center;gap:7px;background:var(--bg);border-radius:8px;
2623
+ padding:5px 8px;font-size:12px;max-width:260px;
2624
+ }
2625
+ .att img{width:26px;height:26px;object-fit:cover;border-radius:5px;flex:0 0 auto}
2626
+ .att .n{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
2627
+ .att .sz{color:var(--dim);font-size:11px;flex:0 0 auto}
2628
+ .att .x{border:0;background:transparent;color:var(--dim);cursor:pointer;font:inherit;padding:0 2px}
2629
+ .att .x:hover{color:var(--err)}
2630
+ /* A referenced path is not an uploaded file: nothing was copied anywhere, so
2631
+ the chip carries a mark for what it points at instead of a size. */
2632
+ .att .ico-sm{width:13px;height:13px;opacity:.65}
2633
+ .att .where{color:var(--dim);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
2634
+ /* Wider than the model picker's: these rows carry a name and the folder it
2635
+ sits in, and truncating the folder away leaves three files called index.ts. */
2636
+ #attach-picker .menu{min-width:min(380px,86vw)}
2637
+ #attach-picker .menu-item{cursor:pointer;gap:9px;padding-left:9px;padding-right:9px}
2638
+ #attach-picker .menu-item .tick{width:auto;opacity:.55;margin-left:auto;padding-left:8px}
2639
+ #attach-picker .menu-item.on .tick{opacity:.85}
2640
+ /* A step up from hover, not a slab of ink. The keyboard cursor and the pointer
2641
+ should not look identical — one of them means Enter takes this row — but the
2642
+ menu is a quiet surface and the difference is worth one tone, not two. */
2643
+ #attach-picker .menu-item.cursor{background:var(--sel)}
2644
+ #attach-picker .menu-item.cursor .ico-sm{opacity:.8}
2645
+ #attach-picker .menu-item .ico-sm{opacity:.55}
2646
+ /* The stacked rows carry a name over a sentence, so they are already two lines
2647
+ tall; the mark beside them is a label for the kind, not an illustration, and
2648
+ at 16px it was the widest thing in a row whose job is the words. */
2649
+ /* Centred on the name, not on the row. A stacked row is a name over a
2650
+ sentence, so its box centre sits somewhere in the gap between the two lines
2651
+ and a mark aligned to it reads as sagging. The name's line box is 22px at
2652
+ this size and the mark is 13px, so half the difference puts one centre on
2653
+ the other. The rule above this one sets the 1px the flush-left tick needed
2654
+ when every row was a single line; on these it is the same 1px of sag. */
2655
+ #attach-picker .menu-item.stacked .ico-sm{width:13px;height:13px;margin-top:4.5px}
2656
+ /* The vendor mark at the row's glyph size, the mono tile lettered to match. */
2657
+ #attach-picker .menu-item .mk-logo,#attach-picker .menu-item .mk-mono{
2658
+ width:16px;height:16px;border-radius:4px;margin-top:3px;font-size:9px;
2659
+ }
2660
+ #attach-picker .menu-item.stacked .tick{margin-top:0}
2661
+ #attach-picker .menu-item.stacked{padding-top:5px;padding-bottom:5px}
2662
+ #attach-picker .menu-item.stacked .stack{gap:1px}
2663
+ #attach-picker .menu-item.on .ico-sm{opacity:.9}
2664
+ .menu-hint{padding:6px 8px 8px;font-size:11.5px;color:var(--dim)}
2665
+ /* A row that has something to say about itself: the name on one line and a
2666
+ sentence under it. Beside the name, that sentence took the whole row and
2667
+ left the name ellipsed to nothing. */
2668
+ .menu-item.stacked{align-items:flex-start;padding-top:7px;padding-bottom:7px}
2669
+ .menu-item.stacked .ico-sm,.menu-item.stacked .tick{margin-top:1px}
2670
+ .menu-item .stack{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
2671
+ .menu-item .note{
2672
+ font-size:11.5px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
2673
+ }
2674
+ /* Removing what a row describes, from the row itself. Faint until the row is
2675
+ under the pointer or the keyboard: it is the rarer of the two things anyone
2676
+ comes to this list to do. */
2677
+ .row-drop{
2678
+ margin-left:auto;flex:0 0 auto;width:20px;height:20px;border:0;border-radius:6px;
2679
+ background:transparent;color:var(--dim);font:inherit;font-size:14px;line-height:1;
2680
+ cursor:pointer;display:grid;place-items:center;padding:0;opacity:.5;
2681
+ }
2682
+ .menu-item:hover .row-drop,.menu-item.cursor .row-drop{opacity:1}
2683
+ .row-drop:hover{background:var(--sel);color:var(--err)}
2684
+ /* The + menu has no search box of its own, so the menu itself takes focus for
2685
+ the arrow keys. A ring around a popup that was just clicked open says
2686
+ nothing the row highlight is not already saying. */
2687
+ .menu:focus{outline:none}
2688
+ /* No ring and no border marks a focused or selected thing anywhere in the app.
2689
+ That includes the browser's own focus outline, which drew a blue ring round
2690
+ the account button while its menu was open. Keyboard focus is carried by a
2691
+ fill instead; the zero-specificity selector leaves every control that
2692
+ already paints its own background alone. */
2693
+ :focus,:focus-visible{outline:none}
2694
+ :where(button,a,summary,[role="button"],[tabindex]):focus-visible{background-color:var(--hover)}
2695
+ /* The triangle the group headers use, turned on a row that leads somewhere:
2696
+ the + opens onto four offers, and three of them go one screen deeper. */
2697
+ .menu-item .chev{
2698
+ width:0;height:0;flex:0 0 auto;margin-left:auto;
2699
+ border-left:4px solid currentColor;border-top:3px solid transparent;
2700
+ border-bottom:3px solid transparent;opacity:.45;
2701
+ }
2702
+ .menu-item .sub + .chev{margin-left:6px}
2703
+ .menu-item:hover .chev,.menu-item.cursor .chev{opacity:.8}
2704
+ .composer-spacer{flex:1}
2705
+ button.primary{
2706
+ width:30px;height:30px;border:0;border-radius:8px;background:var(--accent);color:var(--bg);
2707
+ font:inherit;cursor:pointer;display:grid;place-items:center;padding:0;
2708
+ }
2709
+ button.primary svg{width:16px;height:16px}
2710
+ button.primary:disabled{opacity:.3;cursor:default}
2711
+ /* Idle with messages still waiting: send releases them, and the button says
2712
+ so by quietly breathing rather than by growing an outline. */
2713
+ #send.has-queue{animation:queue-wait 2.4s ease-in-out infinite}
2714
+ @keyframes queue-wait{0%,100%{opacity:1}50%{opacity:.55}}
2715
+ @media (prefers-reduced-motion:reduce){#send.has-queue{animation:none;opacity:.8}}
2716
+ button.ghost,a.ghost{
2717
+ height:30px;padding:0 12px;border:0;border-radius:8px;
2718
+ background:var(--sel);color:var(--fg);font:inherit;font-size:12.5px;cursor:pointer;
2719
+ }
2720
+ button.ghost:hover,a.ghost:hover{background:var(--hover)}
2721
+ #jump{
2722
+ position:absolute;right:24px;bottom:118px;border-radius:999px;padding:7px 15px;border:0;
2723
+ background:var(--bg);color:var(--fg);cursor:pointer;font:inherit;font-size:12px;
2724
+ box-shadow:0 1px 2px #0000000f,0 6px 20px #0000001f;
2725
+ }
2726
+
2727
+ dialog{
2728
+ border:0;border-radius:16px;background:var(--bg);color:var(--fg);
2729
+ padding:0;max-width:min(560px,92vw);font:inherit;
2730
+ box-shadow:0 2px 6px #00000014,0 20px 60px #00000033;
2731
+ }
2732
+ dialog::backdrop{background:rgba(0,0,0,.55)}
2733
+ /* The approval dialog.
2734
+ *
2735
+ * Everything here is arranged around one question: what exactly is about to
2736
+ * happen to which file. The parameters used to be dumped as JSON, which put
2737
+ * escaped newlines and the tool's own field names in front of the one thing
2738
+ * worth reading — so the body renders the payload instead, and the plumbing
2739
+ * moved behind a toggle.
2740
+ *
2741
+ * Three parts and no rules between them: what (head), the payload (one filled
2742
+ * block, aligned with the head), and the answer. Hairlines between four
2743
+ * sections made the middle read as leftovers from different components. */
2744
+ #ask{width:min(620px,94vw);max-width:min(620px,94vw);padding-bottom:10px}
2745
+ .ask-head{padding:18px 20px 12px;display:flex;flex-direction:column;gap:6px;min-width:0}
2746
+ .ask-tag{
2747
+ align-self:flex-start;font-size:11.5px;line-height:1.7;padding:0 9px;border-radius:999px;
2748
+ background:var(--field);color:var(--dim);
2749
+ }
2750
+ .ask-title{display:flex;align-items:center;gap:10px;min-width:0}
2751
+ .ask-logo{width:22px;height:22px;border-radius:6px;object-fit:contain;flex:none}
2752
+ .ask-target{font-size:15px;line-height:1.45;font-weight:600;overflow-wrap:anywhere;min-width:0}
2753
+ .ask-target.mono{
2754
+ font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
2755
+ font-size:13.5px;font-weight:500;white-space:pre-wrap;
2756
+ }
2757
+ .ask-target .dir{color:var(--dim);font-weight:400}
2758
+ .ask-meta{font-size:12px;line-height:1.5;color:var(--dim);overflow-wrap:anywhere}
2759
+ .ask-body{margin:0 20px;border-radius:10px;background:var(--field);max-height:280px;overflow:auto;padding:8px 0}
2760
+ .ask-body.clamped{max-height:none;overflow:hidden}
2761
+ .ask-line{
2762
+ display:flex;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
2763
+ font-size:12.5px;line-height:1.6;white-space:pre-wrap;word-break:break-word;
2764
+ }
2765
+ .ask-line .n{
2766
+ flex:0 0 36px;text-align:right;padding-right:12px;color:var(--dim);
2767
+ opacity:.65;user-select:none;
2768
+ }
2769
+ .ask-body.diff .ask-line .n{flex-basis:28px;text-align:center;padding-right:4px}
2770
+ .ask-line .s{flex:1;padding-right:14px}
2771
+ .ask-line.add{background:var(--add-bg)}
2772
+ .ask-line.add .s{color:var(--add)}
2773
+ .ask-line.del{background:var(--del-bg)}
2774
+ .ask-line.del .s{color:var(--del)}
2775
+ .ask-body .ask-code{
2776
+ margin:0;padding:2px 14px;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
2777
+ font-size:12.5px;line-height:1.6;white-space:pre-wrap;overflow-wrap:anywhere;
2778
+ }
2779
+ .ask-body .kv{padding:4px 14px}
2780
+ .ask-more{display:flex;gap:4px;padding:6px 14px 0}
2781
+ .ask-more button{
2782
+ border:0;border-radius:7px;background:transparent;color:var(--dim);
2783
+ font:inherit;font-size:12px;padding:4px 8px;cursor:pointer;
2784
+ }
2785
+ .ask-more button:hover{background:var(--hover);color:var(--fg)}
2786
+ /* The three allow options are one decision at three scopes, not three
2787
+ * decisions, so they sit on one row as peers. Splitting them across two rows
2788
+ * under a "remember the answer" heading asked the same question twice. */
2789
+ .dlg-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:16px 20px 4px}
2790
+ /* Holds the way out apart from the answers without a spacer element: a growing
2791
+ * spacer claims width of its own, which forced the row to wrap one button early
2792
+ * on a narrow window. The quiet button leads its row, so the gap goes after it. */
2793
+ .dlg-actions button.quiet{margin-right:auto}
2794
+ .dlg-actions button{
2795
+ padding:8px 14px;border:0;border-radius:9px;
2796
+ background:var(--field);color:var(--fg);font:inherit;font-size:13.5px;cursor:pointer;
2797
+ display:inline-flex;align-items:center;gap:7px;
2798
+ }
2799
+ .dlg-actions button:hover{background:var(--sel)}
2800
+ /* Once is the default answer and the one Enter picks, so it carries the fill.
2801
+ * The other two are the same answer held longer — same size, same row, less
2802
+ * weight. Denying is the safe answer and gets no colour at all: the red used
2803
+ * to sit on "No", which argued against the harmless choice. */
2804
+ /* Not "primary": that class is already the 30x30 icon button in the composer,
2805
+ * and reusing it here collapsed this one into a circle. */
2806
+ .dlg-actions button.approve{background:var(--accent);color:var(--bg)}
2807
+ .dlg-actions button.approve:hover{opacity:.88}
2808
+ /* The default focus ring draws a hard blue rectangle around whichever button
2809
+ * opens focused, which reads as a border on the dialog's one filled control.
2810
+ * Keyboard users still get a visible state — it just comes from the fill. */
2811
+ .dlg-actions button:focus-visible{outline:none;background:var(--sel)}
2812
+ .dlg-actions button.approve:focus-visible{background:var(--accent);opacity:.88}
2813
+ .dlg-actions button.quiet{background:transparent;color:var(--dim)}
2814
+ .dlg-actions button.quiet:hover{background:var(--hover);color:var(--fg)}
2815
+ .dlg-actions kbd{
2816
+ font:inherit;font-size:11px;opacity:.7;background:var(--field);border-radius:4px;
2817
+ padding:0 5px;line-height:1.6;
2818
+ }
2819
+ .dlg-actions button.approve kbd{background:#ffffff26}
2820
+ /* Only while a "remember" answer is focused, and taking no room otherwise: a
2821
+ * reserved empty line under the buttons read as the dialog being unfinished. */
2822
+ .ask-scope{display:grid;padding:6px 20px 2px;font-size:11.5px;line-height:1.6;color:var(--dim)}
2823
+ .ask-scope>span{
2824
+ grid-area:1/1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
2825
+ opacity:0;pointer-events:none;transition:opacity .12s ease;
2826
+ }
2827
+ .ask-scope[data-show="once"]>[data-scope="once"],.ask-scope[data-show="session"]>[data-scope="session"],
2828
+ .ask-scope[data-show="project"]>[data-scope="project"]{opacity:1;pointer-events:auto}
2829
+
2830
+ /* Parameters as a reader wants them: names on the left, values as values —
2831
+ * text with its line breaks, lists as chips — instead of JSON with line numbers. */
2832
+ .kv{display:grid;grid-template-columns:minmax(64px,max-content) minmax(0,1fr);column-gap:14px;row-gap:6px;font-size:12.5px;line-height:1.55}
2833
+ .kv-k{color:var(--dim);font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;overflow-wrap:anywhere}
2834
+ .kv-v{min-width:0;overflow-wrap:anywhere;white-space:pre-wrap}
2835
+ .kv-v.empty{color:var(--dim)}
2836
+ .kv-chip{display:inline-block;margin:0 4px 2px 0;padding:0 8px;border-radius:999px;background:var(--sel);font-size:12px;white-space:nowrap}
2837
+ .kv-v pre{margin:0;font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere}
2838
+
2839
+ /*
2840
+ * Phone-only chrome, declared before the breakpoint that shows it: a base
2841
+ * rule written after the media query wins the cascade, which is how the
2842
+ * burger spent a release at display:none on every width.
2843
+ */
2844
+ #burger{
2845
+ display:none;width:36px;height:36px;margin-left:-8px;border:0;border-radius:8px;
2846
+ background:none;color:var(--fg);font:inherit;font-size:18px;cursor:pointer;place-items:center;
2847
+ }
2848
+ #burger:hover{background:var(--hover)}
2849
+ #nav-close{
2850
+ display:none;width:32px;height:32px;margin-left:auto;border:0;border-radius:8px;
2851
+ background:transparent;color:var(--dim);cursor:pointer;padding:0;place-items:center;
2852
+ }
2853
+ #nav-close svg{width:14px;height:14px}
2854
+ #nav-close:hover{background:var(--hover);color:var(--fg)}
2855
+ #scrim{display:none;position:fixed;inset:0;z-index:19;background:#00000059}
2856
+ #review-close{
2857
+ display:none;width:32px;height:32px;border:0;border-radius:8px;background:transparent;
2858
+ color:var(--fg);cursor:pointer;padding:0;place-items:center;margin-left:8px;
2859
+ }
2860
+ #review-close svg{width:16px;height:16px}
2861
+
2862
+ /*
2863
+ * A phone.
2864
+ *
2865
+ * The desktop layout is three columns that share one window; a phone has room
2866
+ * for one of them at a time. The sidebar becomes a drawer over the transcript,
2867
+ * the review and artifact panes take the whole screen while they are open,
2868
+ * and the composer's bar is split in two: where the session runs and what it
2869
+ * may do sit above the text, the model and the send button below it.
2870
+ */
2871
+ @media (max-width:760px){
2872
+ body>aside{
2873
+ position:fixed;inset:0 auto 0 0;z-index:20;width:min(280px,84vw);flex-basis:auto;
2874
+ transform:translateX(-100%);transition:transform .2s;
2875
+ padding-bottom:env(safe-area-inset-bottom);
2876
+ }
2877
+ body>aside.open{transform:none;box-shadow:0 0 0 1px #0000000a,0 12px 40px #00000040}
2878
+ body.nav-open #scrim{display:block}
2879
+ #nav-close{display:grid}
2880
+ #nav-collapse,body.nav-collapsed #nav-expand{display:none}
2881
+ body.nav-collapsed>aside{width:min(280px,84vw);flex-basis:auto}
2882
+ body.nav-collapsed #tabs{padding-left:12px}
2883
+ #burger{display:grid}
2884
+ #top{padding:6px 10px}
2885
+ #top:has(#changes-btn:not([hidden])){padding:6px 10px}
2886
+
2887
+ /* Both panes are the screen while open, and each gets a way out of its own:
2888
+ the top bar with the changes button is underneath them. */
2889
+ #review.open,#shelf.open{
2890
+ position:fixed;inset:0;z-index:50;width:100%;height:100%;flex:none;transition:none;
2891
+ }
2892
+ #review-head{padding:10px 10px 8px 16px}
2893
+ #review-close{display:grid}
2894
+ #explorer.open{position:fixed;inset:0;z-index:50;width:100%;height:100%;flex:none;transition:none}
2895
+ #explorer-head{padding-top:max(10px,env(safe-area-inset-top))}
2896
+ #explorer-body{flex-direction:column}
2897
+ #explorer-resizer{display:none}
2898
+ #explorer-tree{width:auto;height:var(--explorer-tree-height,38%);max-height:none}
2899
+ /* Stacked, so the handle between them drags up and down — and is a strip
2900
+ a thumb can find rather than a hairline. */
2901
+ #explorer-split{flex:0 0 18px;margin:0;cursor:row-resize}
2902
+ #explorer-split::before{left:50%;top:7px;width:34px;height:4px;opacity:.4;transform:translateX(-50%)}
2903
+ body.dragging-y #explorer-split::before{opacity:.7;transform:translateX(-50%)}
2904
+ .explorer-row{padding-top:8px;padding-bottom:8px}
2905
+ #explorer-actions button{width:34px;height:34px}
2906
+ /* A phone has room for the conversation or a shell, and the shell needs the
2907
+ keyboard: while it is open the composer steps aside, and the handle on
2908
+ top is a visible grip. */
2909
+ body.terminal-open #composer,body.terminal-open #jump{display:none}
2910
+ #terminal{flex-basis:var(--terminal-height,55%);padding-bottom:env(safe-area-inset-bottom)}
2911
+ #terminal-resizer{top:0;height:18px}
2912
+ #terminal-resizer::before{top:6px;opacity:.4;transform:translateX(-50%)}
2913
+ #terminal-head{padding-top:14px}
2914
+ #terminal-new,#terminal-hide{width:34px;height:34px}
2915
+ .terminal-tab{height:32px}
2916
+ .terminal-tab-x{opacity:.6;width:24px;height:24px}
2917
+ #terminal-where{display:none}
2918
+ .terminal-host{inset:4px 0 6px 6px}
2919
+ #files{max-height:40%}
2920
+ #shelf-bar{flex-wrap:wrap;row-gap:4px}
2921
+
2922
+ #log{padding:16px 14px 8px}
2923
+ #panel-view{padding:20px 14px 24px}
2924
+ .empty-brand{gap:12px;margin-bottom:22px}
2925
+ .brand-mark,.brand-mark svg{width:48px;height:48px}
2926
+ .brand-name{font-size:30px}
2927
+ #empty{margin-top:4vh}
2928
+
2929
+ /* The composer. The bar's children become items of the box itself so they
2930
+ can be dealt into two rows around the field; the bar element stays in the
2931
+ markup for the desktop layout and the scripts that hang menus off it. */
2932
+ form{padding:6px 12px calc(12px + env(safe-area-inset-bottom))}
2933
+ .composer{
2934
+ position:relative;display:flex;flex-wrap:wrap;align-items:center;
2935
+ column-gap:6px;row-gap:8px;padding:10px 12px 10px;
2936
+ }
2937
+ .composer-bar{display:contents}
2938
+ #attachments{order:-3;flex:0 0 100%;margin-bottom:0}
2939
+ #workspace-picker,#mode-picker{order:-2}
2940
+ .input-wrap{order:-1;flex:0 0 100%}
2941
+ .composer .picker{position:static;min-width:0}
2942
+ .composer .picker-btn{max-width:100%;height:32px}
2943
+ #send,#stop{flex:0 0 auto}
2944
+ /* No hover on a touch screen, so the bar is always drawn, and the strip is
2945
+ taller because a thumb is not a cursor. */
2946
+ #grip{height:18px}
2947
+ #grip::before{opacity:.3;width:40px}
2948
+ textarea{max-height:32dvh}
2949
+ /* Menus span the box rather than hang off one picker: from a picker on the
2950
+ right they otherwise open past the edge, where overflow:hidden loses them. */
2951
+ .composer .menu,#attach-picker .menu{
2952
+ left:0;right:0;width:auto;min-width:0;max-width:none;bottom:calc(100% + 8px);
2953
+ }
2954
+ .menu-body{max-height:min(50dvh,340px)}
2955
+ #effort-picker .menu{left:0;width:auto;padding:16px 16px 14px}
2956
+ .effort-rail{--stop:40px;height:40px;border-radius:20px}
2957
+ .effort-fill{border-radius:20px}
2958
+ .effort-stop,.effort-name{width:40px}
2959
+ .effort-stop{height:40px}
2960
+ .effort-knob{width:28px;height:28px}
2961
+ #jump{right:14px;bottom:160px}
2962
+
2963
+ /* Dialogs, with the keyboard up: vh is the height without it. */
2964
+ #market{max-height:82dvh}
2965
+ #provider-picker,#browse{max-height:76dvh}
2966
+ #project-picker{max-height:70dvh}
2967
+ .mk-grid{grid-template-columns:minmax(0,1fr)}
2968
+ .mk-search{padding-right:44px}
2969
+ .ask-body{max-height:min(260px,40dvh)}
2970
+ .ask-line .n{flex:0 0 28px;padding-right:6px}
2971
+ .ask-head{padding:16px 16px 10px}
2972
+ .ask-body{margin:0 16px}
2973
+ .kv{grid-template-columns:minmax(0,1fr);row-gap:2px}
2974
+ .kv-v{margin-bottom:6px}
2975
+ }
2976
+
2977
+ /*
2978
+ * A finger, not a pointer.
2979
+ *
2980
+ * Hover reveals nothing on a touch screen, so what the desktop shows on hover
2981
+ * is shown outright; and a field under 16px makes iOS zoom the page when it
2982
+ * is focused, so every field is 16px here whatever it is elsewhere.
2983
+ */
2984
+ @media (hover:none) and (pointer:coarse){
2985
+ .session .more,.fold-row .more{display:grid;width:28px;height:28px}
2986
+ .proj-forget{opacity:1}
2987
+ .actions{position:static;opacity:1;margin-top:4px}
2988
+ .turn{margin-bottom:14px}
2989
+ .act{padding:6px 10px}
2990
+ .tab .x{opacity:.55;width:24px;height:24px}
2991
+ /* No hover to reveal them on a touch screen. */
2992
+ .tab .x,.tab .tab-tool,.tab .tab-browser{display:grid}
2993
+ #tab-add{width:32px;height:32px}
2994
+ .tab{height:34px}
2995
+ .nav-item,.session,.fold,.project{height:38px}
2996
+ .show-more{height:32px}
2997
+ .icon-btn{width:34px;height:34px}
2998
+ button.primary{width:36px;height:36px}
2999
+ button.ghost,a.ghost{height:36px}
3000
+ .att .x{padding:4px 6px}
3001
+ .cn-x,.mk-close,.theme-btn{width:36px;height:36px}
3002
+ .cn-why{width:28px;height:28px}
3003
+ .switch{width:40px;height:24px}
3004
+ .dlg-actions button{padding:10px 14px}
3005
+ input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),select,textarea,
3006
+ .menu-search input,.mk-search,.art-search,.keyrow input,.cn-body input,.pp-head input,
3007
+ textarea.boxed{font-size:16px}
3008
+ /* The chip layer under the field has to keep the field's metrics. */
3009
+ #input-mirror{font-size:16px}
3010
+ }
3011
+ /* Inside the browser extension's side panel: the phone layout, which the
3012
+ panel's width already selects, without the built-in browser. The person's
3013
+ own browser is the rest of the window. */
3014
+ .embed #browser-pane,.embed #browser-scrim,.embed .tab-browser{display:none!important}
3015
+ `;
3016
+ //# sourceMappingURL=style.js.map