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