k0-board 0.1.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/.claude/skills/k0-import/SKILL.md +83 -0
- package/CHANGELOG.md +57 -0
- package/LICENSE +21 -0
- package/README.md +688 -0
- package/bin/k0-board.js +456 -0
- package/bin/prompt.js +41 -0
- package/menubar/Info.plist +25 -0
- package/menubar/K0MenuBar.swift +561 -0
- package/menubar/build.sh +28 -0
- package/package.json +45 -0
- package/platform/contract.js +134 -0
- package/platform/darwin/extras.js +254 -0
- package/platform/darwin/index.js +24 -0
- package/platform/darwin/metrics.js +59 -0
- package/platform/darwin/power.js +133 -0
- package/platform/darwin/service.js +143 -0
- package/platform/darwin/shell.js +59 -0
- package/platform/darwin/terminal.js +324 -0
- package/platform/index.js +97 -0
- package/platform/linux/index.js +60 -0
- package/platform/linux/metrics.js +88 -0
- package/platform/linux/power.js +137 -0
- package/platform/linux/service.js +111 -0
- package/platform/linux/shell.js +97 -0
- package/platform/linux/tray.py +229 -0
- package/platform/shared/command.js +26 -0
- package/platform/shared/ps.js +40 -0
- package/platform/shared/run.js +99 -0
- package/platform/shared/tmux.js +242 -0
- package/platform/win32/index.js +53 -0
- package/platform/win32/metrics.js +72 -0
- package/platform/win32/power.js +109 -0
- package/platform/win32/powershell.js +44 -0
- package/platform/win32/service.js +111 -0
- package/platform/win32/shell.js +64 -0
- package/platform/win32/terminal.js +209 -0
- package/platform/win32/tray.ps1 +159 -0
- package/server/db.js +262 -0
- package/server/files.js +488 -0
- package/server/git.js +157 -0
- package/server/index.js +636 -0
- package/server/launcher.js +121 -0
- package/server/machine.js +213 -0
- package/server/mode.js +182 -0
- package/server/paths.js +67 -0
- package/server/pdf.js +110 -0
- package/server/projects.js +152 -0
- package/server/sessions.js +341 -0
- package/server/watcher.js +200 -0
- package/web/base.css +134 -0
- package/web/board.css +561 -0
- package/web/board.js +777 -0
- package/web/favicon.js +41 -0
- package/web/favicon.svg +8 -0
- package/web/files.css +535 -0
- package/web/files.html +63 -0
- package/web/files.js +877 -0
- package/web/fonts/caveat-latin-ext.woff2 +0 -0
- package/web/fonts/caveat-latin.woff2 +0 -0
- package/web/fuzzy.js +57 -0
- package/web/index.html +112 -0
- package/web/md.js +238 -0
- package/web/mentions.js +188 -0
- package/web/refs.js +176 -0
- package/web/title.js +26 -0
- package/web/units.js +33 -0
- package/web/view.js +248 -0
package/web/board.css
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/* The house variables — colours, fonts, scale — live in base.css, which belongs to both
|
|
2
|
+
pages. From here down there is only the board. */
|
|
3
|
+
@import url('/base.css');
|
|
4
|
+
|
|
5
|
+
/* The page no longer scrolls: it is the board that moves underneath the view. */
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
background: var(--bg);
|
|
9
|
+
color: var(--ink);
|
|
10
|
+
font-family: var(--sans);
|
|
11
|
+
font-size: 1rem;
|
|
12
|
+
line-height: 1.45;
|
|
13
|
+
-webkit-font-smoothing: antialiased;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* ── The top bar ─────────────────────────────────────────────────────── */
|
|
20
|
+
.topbar {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: 16px;
|
|
24
|
+
padding: 12px 22px;
|
|
25
|
+
background: #fffdf7;
|
|
26
|
+
border-bottom: 1px solid var(--line);
|
|
27
|
+
flex: none;
|
|
28
|
+
}
|
|
29
|
+
.topbar h1 {
|
|
30
|
+
margin: 0;
|
|
31
|
+
font: 700 1.35rem/1 var(--mono);
|
|
32
|
+
letter-spacing: 1px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* The legend IS the filter: click a status and it leaves the board. */
|
|
36
|
+
.filters { display: flex; gap: 8px; flex: 1; flex-wrap: wrap; }
|
|
37
|
+
.filters button {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 7px;
|
|
41
|
+
border: 1px solid var(--line);
|
|
42
|
+
background: #fffdf7;
|
|
43
|
+
border-radius: 999px;
|
|
44
|
+
padding: 5px 13px 5px 9px;
|
|
45
|
+
font-size: .82rem;
|
|
46
|
+
font-weight: 600;
|
|
47
|
+
color: var(--ink);
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
.filters button i {
|
|
51
|
+
width: 13px;
|
|
52
|
+
height: 13px;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
flex: none;
|
|
55
|
+
border: 1px solid rgba(0, 0, 0, .12);
|
|
56
|
+
}
|
|
57
|
+
.filters button[aria-pressed="false"] { opacity: .38; }
|
|
58
|
+
.filters button[aria-pressed="false"] i { background: transparent !important; }
|
|
59
|
+
.filters button:hover { border-color: #bdb6a5; }
|
|
60
|
+
|
|
61
|
+
/* ── How the computer is doing ────────────────────────────────────────
|
|
62
|
+
At the end of the bar, always there. The dot is the kernel's judgement — green, amber, red —
|
|
63
|
+
and the rest of the picture comes out on hover. Clicking it switches the weights on the cards
|
|
64
|
+
off and on again. */
|
|
65
|
+
.gauge {
|
|
66
|
+
flex: none;
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
gap: 7px;
|
|
70
|
+
border: 1px solid var(--line);
|
|
71
|
+
background: #fffdf7;
|
|
72
|
+
border-radius: 999px;
|
|
73
|
+
padding: 5px 14px;
|
|
74
|
+
font-size: .78rem;
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
font-variant-numeric: tabular-nums;
|
|
77
|
+
color: var(--muted);
|
|
78
|
+
white-space: nowrap;
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
}
|
|
81
|
+
/* `display: flex` beats the browser's `[hidden]`: until there is a sample, it hides here. */
|
|
82
|
+
.gauge[hidden] { display: none; }
|
|
83
|
+
.gauge:hover { border-color: #bdb6a5; background: #fff; }
|
|
84
|
+
.gauge i {
|
|
85
|
+
width: 9px;
|
|
86
|
+
height: 9px;
|
|
87
|
+
border-radius: 50%;
|
|
88
|
+
flex: none;
|
|
89
|
+
background: var(--calm);
|
|
90
|
+
}
|
|
91
|
+
.gauge.warn i { background: var(--warn); }
|
|
92
|
+
.gauge.bad i { background: var(--alert); }
|
|
93
|
+
/* Solid red only when it is critical: the one moment it is allowed to interrupt. */
|
|
94
|
+
.gauge.bad { color: var(--alert); border-color: #f1c9c9; }
|
|
95
|
+
|
|
96
|
+
/* The "+" sits next to k0, at the start of the bar: that is where a card is born.
|
|
97
|
+
The sign is drawn, not typed: a font glyph never sits in the middle of the circle. */
|
|
98
|
+
.new {
|
|
99
|
+
flex: none;
|
|
100
|
+
padding: 0;
|
|
101
|
+
width: 34px;
|
|
102
|
+
height: 34px;
|
|
103
|
+
border-radius: 999px;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
}
|
|
108
|
+
.new svg { width: 17px; height: 17px; display: block; }
|
|
109
|
+
|
|
110
|
+
/* ── The four modes ───────────────────────────────────────────────────
|
|
111
|
+
At the end of the bar, a row of four: how awake the machine has to stay. Switched off they are
|
|
112
|
+
pale outlines like the statuses filtered away; the lit one takes the colour of its mode. The
|
|
113
|
+
colour is not decoration: from across the room — which is what driving mode is for — you do not
|
|
114
|
+
focus on the icon, but you do see the colour. */
|
|
115
|
+
.modes {
|
|
116
|
+
flex: none;
|
|
117
|
+
display: flex;
|
|
118
|
+
gap: 3px;
|
|
119
|
+
}
|
|
120
|
+
.modes button {
|
|
121
|
+
flex: none;
|
|
122
|
+
padding: 0;
|
|
123
|
+
width: 34px;
|
|
124
|
+
height: 34px;
|
|
125
|
+
border-radius: 999px;
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
justify-content: center;
|
|
129
|
+
color: var(--ink);
|
|
130
|
+
opacity: .34;
|
|
131
|
+
}
|
|
132
|
+
.modes button svg { width: 20px; height: 20px; display: block; }
|
|
133
|
+
.modes button:hover { opacity: .72; }
|
|
134
|
+
.modes button[aria-checked="true"] { opacity: 1; }
|
|
135
|
+
|
|
136
|
+
/* One colour per mode, climbing like the scale: grey asleep, green for the machine holding, amber
|
|
137
|
+
for you being there, blue for watching from a distance. The blue is driving mode's usual one,
|
|
138
|
+
the only one of the four that was already familiar. */
|
|
139
|
+
.modes button[aria-checked="true"][data-mode="sleep"] {
|
|
140
|
+
background: #dfe3e8;
|
|
141
|
+
border-color: #b4bdc8;
|
|
142
|
+
color: #3d4854;
|
|
143
|
+
}
|
|
144
|
+
.modes button[aria-checked="true"][data-mode="away"] {
|
|
145
|
+
background: var(--COMPLETED);
|
|
146
|
+
border-color: #7fc99a;
|
|
147
|
+
color: #1d5233;
|
|
148
|
+
}
|
|
149
|
+
.modes button[aria-checked="true"][data-mode="nerd"] {
|
|
150
|
+
background: var(--IDLE);
|
|
151
|
+
border-color: #e8c766;
|
|
152
|
+
color: #6b5210;
|
|
153
|
+
}
|
|
154
|
+
.modes button[aria-checked="true"][data-mode="driving"] {
|
|
155
|
+
background: var(--WORKING);
|
|
156
|
+
border-color: #7fa9e8;
|
|
157
|
+
color: #1c3d6e;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* On but not all the way: the permission is missing, or the battery is nearly flat, and with the
|
|
161
|
+
lid closed it will not hold. It is not an error worth stopping everything for, it is a promise
|
|
162
|
+
not to make: an amber ring around it, and the title says the rest. */
|
|
163
|
+
.modes button[data-partial] { box-shadow: 0 0 0 2px #e0a72e; }
|
|
164
|
+
|
|
165
|
+
/* ── The board ───────────────────────────────────────────────────────── */
|
|
166
|
+
/* The viewport is the fixed window, the board is the content sliding behind it: one transform on
|
|
167
|
+
.board, and no scrollbar anywhere. */
|
|
168
|
+
.viewport {
|
|
169
|
+
flex: 1;
|
|
170
|
+
min-height: 0;
|
|
171
|
+
position: relative;
|
|
172
|
+
overflow: hidden;
|
|
173
|
+
}
|
|
174
|
+
.board {
|
|
175
|
+
display: flex;
|
|
176
|
+
gap: 24px;
|
|
177
|
+
align-items: flex-start;
|
|
178
|
+
padding: 26px 40px 46px;
|
|
179
|
+
width: max-content; /* so offsetWidth is the real width of all the columns */
|
|
180
|
+
transform-origin: 0 0;
|
|
181
|
+
will-change: transform;
|
|
182
|
+
}
|
|
183
|
+
.column { flex: 0 0 268px; }
|
|
184
|
+
.column > h2 {
|
|
185
|
+
margin: 0 0 14px 4px;
|
|
186
|
+
font: 600 1.5rem/1 var(--hand);
|
|
187
|
+
color: #5f5a4e;
|
|
188
|
+
}
|
|
189
|
+
.column > h2 small {
|
|
190
|
+
font-family: var(--sans);
|
|
191
|
+
color: var(--muted);
|
|
192
|
+
font-size: .72rem;
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
margin-left: 7px;
|
|
195
|
+
vertical-align: middle;
|
|
196
|
+
}
|
|
197
|
+
/* The column's "+": from here a card is born already addressed to this repository.
|
|
198
|
+
It sits to the left of the name, faded, and darkens when you hover over it. */
|
|
199
|
+
.column .add {
|
|
200
|
+
padding: 0;
|
|
201
|
+
width: 19px;
|
|
202
|
+
height: 19px;
|
|
203
|
+
margin-right: 5px;
|
|
204
|
+
border: none;
|
|
205
|
+
background: none;
|
|
206
|
+
border-radius: 5px;
|
|
207
|
+
color: #5f5a4e;
|
|
208
|
+
opacity: .32;
|
|
209
|
+
vertical-align: middle;
|
|
210
|
+
display: inline-flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
justify-content: center;
|
|
213
|
+
}
|
|
214
|
+
.column .add svg { width: 15px; height: 15px; display: block; }
|
|
215
|
+
.column .add:hover { opacity: 1; background: rgba(0, 0, 0, .07); }
|
|
216
|
+
|
|
217
|
+
.empty {
|
|
218
|
+
position: absolute;
|
|
219
|
+
inset: 0;
|
|
220
|
+
margin: 0;
|
|
221
|
+
place-items: center;
|
|
222
|
+
text-align: center;
|
|
223
|
+
color: var(--muted);
|
|
224
|
+
display: none;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* ── The edges ───────────────────────────────────────────────────────── */
|
|
228
|
+
/* The faintest shadow on the side where the board continues: it is what says that bringing the
|
|
229
|
+
pointer there moves you along. Where it has run out, it goes dark. */
|
|
230
|
+
.edges { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
|
|
231
|
+
.edges i { position: absolute; opacity: 0; transition: opacity .18s ease; }
|
|
232
|
+
.edges .l, .edges .r { top: 0; bottom: 0; width: 38px; }
|
|
233
|
+
.edges .t, .edges .b { left: 0; right: 0; height: 38px; }
|
|
234
|
+
.edges .l { left: 0; background: linear-gradient(to right, rgba(0, 0, 0, .09), transparent); }
|
|
235
|
+
.edges .r { right: 0; background: linear-gradient(to left, rgba(0, 0, 0, .09), transparent); }
|
|
236
|
+
.edges .t { top: 0; background: linear-gradient(to bottom, rgba(0, 0, 0, .09), transparent); }
|
|
237
|
+
.edges .b { bottom: 0; background: linear-gradient(to top, rgba(0, 0, 0, .09), transparent); }
|
|
238
|
+
.edges.can-l .l, .edges.can-r .r, .edges.can-t .t, .edges.can-b .b { opacity: 1; }
|
|
239
|
+
|
|
240
|
+
/* ── Zoom ────────────────────────────────────────────────────────────── */
|
|
241
|
+
/* Bottom right, where you expect to find it. The percentage is a button: clicked, it goes back to
|
|
242
|
+
100%. The last one fits the whole board on the screen. */
|
|
243
|
+
.zoom {
|
|
244
|
+
position: absolute;
|
|
245
|
+
right: 16px;
|
|
246
|
+
bottom: 16px;
|
|
247
|
+
z-index: 4;
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
gap: 2px;
|
|
251
|
+
padding: 4px;
|
|
252
|
+
background: #fffdf7;
|
|
253
|
+
border: 1px solid var(--line);
|
|
254
|
+
border-radius: 999px;
|
|
255
|
+
box-shadow: 0 3px 12px rgba(0, 0, 0, .12);
|
|
256
|
+
}
|
|
257
|
+
.zoom button {
|
|
258
|
+
padding: 0;
|
|
259
|
+
width: 30px;
|
|
260
|
+
height: 30px;
|
|
261
|
+
border: none;
|
|
262
|
+
background: none;
|
|
263
|
+
border-radius: 999px;
|
|
264
|
+
color: var(--ink);
|
|
265
|
+
display: flex;
|
|
266
|
+
align-items: center;
|
|
267
|
+
justify-content: center;
|
|
268
|
+
}
|
|
269
|
+
.zoom button:hover { background: #f0ede3; border: none; }
|
|
270
|
+
.zoom button svg { width: 16px; height: 16px; display: block; }
|
|
271
|
+
.zoom #z-level {
|
|
272
|
+
width: auto;
|
|
273
|
+
padding: 0 9px;
|
|
274
|
+
font-size: .8rem;
|
|
275
|
+
font-weight: 600;
|
|
276
|
+
font-variant-numeric: tabular-nums;
|
|
277
|
+
color: var(--muted);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* ── Cards ───────────────────────────────────────────────────────────── */
|
|
281
|
+
.postit {
|
|
282
|
+
background: var(--BACKLOG);
|
|
283
|
+
border-radius: 2px;
|
|
284
|
+
padding: 13px 15px 12px;
|
|
285
|
+
margin-bottom: 16px;
|
|
286
|
+
box-shadow: 2px 3px 8px rgba(0, 0, 0, .17);
|
|
287
|
+
cursor: default;
|
|
288
|
+
transition: transform .12s ease, box-shadow .12s ease;
|
|
289
|
+
position: relative;
|
|
290
|
+
}
|
|
291
|
+
.postit:hover { box-shadow: 3px 6px 14px rgba(0, 0, 0, .24); z-index: 2; }
|
|
292
|
+
.postit.ASK { background: var(--ASK); }
|
|
293
|
+
.postit.PLANNED { background: var(--PLANNED); }
|
|
294
|
+
.postit.IDLE { background: var(--IDLE); }
|
|
295
|
+
.postit.WORKING { background: var(--WORKING); }
|
|
296
|
+
.postit.PLANNING { background: var(--PLANNING); }
|
|
297
|
+
.postit.BACKLOG { background: var(--BACKLOG); }
|
|
298
|
+
.postit.COMPLETED { background: var(--COMPLETED); opacity: .6; }
|
|
299
|
+
.postit.COMPLETED .title { text-decoration: line-through; }
|
|
300
|
+
/* Grinding away: the faintest breath, no blinking dots. */
|
|
301
|
+
.postit.WORKING, .postit.PLANNING { animation: breathe 2.6s ease-in-out infinite; }
|
|
302
|
+
@keyframes breathe { 50% { box-shadow: 2px 3px 8px rgba(0, 0, 0, .17), 0 0 0 3px rgba(59, 130, 246, .22); } }
|
|
303
|
+
|
|
304
|
+
/* The repository's name and, beside it, how it is doing: the two read together. */
|
|
305
|
+
.postit .head {
|
|
306
|
+
display: flex;
|
|
307
|
+
align-items: center;
|
|
308
|
+
gap: 7px;
|
|
309
|
+
margin-bottom: 3px;
|
|
310
|
+
}
|
|
311
|
+
.postit .tag {
|
|
312
|
+
font: 500 1.05rem/1 var(--hand);
|
|
313
|
+
color: rgba(0, 0, 0, .5);
|
|
314
|
+
min-width: 0;
|
|
315
|
+
}
|
|
316
|
+
/* The name is the session's: it is in monospace like the k0 mark at the top.
|
|
317
|
+
Monospace is heavier at the same size, which is why it drops to .94rem. */
|
|
318
|
+
.postit .title {
|
|
319
|
+
font-family: var(--mono);
|
|
320
|
+
font-weight: 700;
|
|
321
|
+
font-size: .94rem;
|
|
322
|
+
letter-spacing: 0;
|
|
323
|
+
word-break: break-word;
|
|
324
|
+
}
|
|
325
|
+
/* What the card is about. On imported sessions it is the only thing that says what happened in
|
|
326
|
+
there, so it sits right under the title — and in italics, so it is not mistaken for the prompt,
|
|
327
|
+
which is something that ends up in the terminal. */
|
|
328
|
+
.postit .desc {
|
|
329
|
+
font-size: .86rem;
|
|
330
|
+
font-style: italic;
|
|
331
|
+
color: rgba(0, 0, 0, .56);
|
|
332
|
+
margin-top: 6px;
|
|
333
|
+
display: -webkit-box;
|
|
334
|
+
-webkit-line-clamp: 4;
|
|
335
|
+
-webkit-box-orient: vertical;
|
|
336
|
+
overflow: hidden;
|
|
337
|
+
}
|
|
338
|
+
.postit .prompt {
|
|
339
|
+
font-size: .86rem;
|
|
340
|
+
color: rgba(0, 0, 0, .62);
|
|
341
|
+
margin-top: 6px;
|
|
342
|
+
/* The prompt's line breaks stay line breaks: a list has to read like a list. */
|
|
343
|
+
white-space: pre-wrap;
|
|
344
|
+
display: -webkit-box;
|
|
345
|
+
-webkit-line-clamp: 4;
|
|
346
|
+
-webkit-box-orient: vertical;
|
|
347
|
+
overflow: hidden;
|
|
348
|
+
}
|
|
349
|
+
.postit .foot {
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
gap: 8px;
|
|
353
|
+
margin-top: 10px;
|
|
354
|
+
font-size: .76rem;
|
|
355
|
+
color: rgba(0, 0, 0, .5);
|
|
356
|
+
min-height: 1em;
|
|
357
|
+
}
|
|
358
|
+
.postit .since { margin-left: auto; }
|
|
359
|
+
.postit .dead { font-style: italic; }
|
|
360
|
+
|
|
361
|
+
/* ── The last column: every repository ────────────────────────────────
|
|
362
|
+
Headings only, one per repository, in the order you last used them. No cards and no counts:
|
|
363
|
+
this is how you get into the files of a directory you are not working on — which without this
|
|
364
|
+
column would have no door at all — and how you give birth to the first card there. */
|
|
365
|
+
.column.repos { gap: 0; }
|
|
366
|
+
.column.repos .repo {
|
|
367
|
+
display: flex;
|
|
368
|
+
align-items: center;
|
|
369
|
+
gap: 4px;
|
|
370
|
+
padding: 3px 0;
|
|
371
|
+
min-width: 0;
|
|
372
|
+
}
|
|
373
|
+
.column.repos .name {
|
|
374
|
+
font: 500 1.1rem/1.25 var(--hand);
|
|
375
|
+
color: var(--muted);
|
|
376
|
+
min-width: 0;
|
|
377
|
+
overflow-wrap: anywhere;
|
|
378
|
+
}
|
|
379
|
+
.column.repos .repo:hover .name { color: var(--ink); }
|
|
380
|
+
.column.repos .git { margin-left: auto; }
|
|
381
|
+
|
|
382
|
+
/* ── The git mark ─────────────────────────────────────────────────────
|
|
383
|
+
One symbol: the dot if there is work that is nowhere yet, the arrow if it is committed but not
|
|
384
|
+
pushed, the tick if everything is safe. The detail is in the title, which comes out on hover.
|
|
385
|
+
|
|
386
|
+
And it is also the door to the files: it is a link, and it opens the viewer in another tab. */
|
|
387
|
+
.git {
|
|
388
|
+
display: inline-flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
gap: 3px;
|
|
391
|
+
flex: none;
|
|
392
|
+
font-family: var(--sans);
|
|
393
|
+
font-size: .74rem;
|
|
394
|
+
font-weight: 700;
|
|
395
|
+
font-variant-numeric: tabular-nums;
|
|
396
|
+
line-height: 1;
|
|
397
|
+
text-decoration: none;
|
|
398
|
+
color: inherit;
|
|
399
|
+
border-radius: 5px;
|
|
400
|
+
padding: 2px 3px;
|
|
401
|
+
margin: -2px -3px;
|
|
402
|
+
}
|
|
403
|
+
.git:hover { background: rgba(0, 0, 0, .09); }
|
|
404
|
+
.git svg { width: 12px; height: 12px; display: block; }
|
|
405
|
+
/* Two colours and no more: red if something is outstanding — which of the two the mark itself
|
|
406
|
+
says, not the colour — and green when everything is safe. */
|
|
407
|
+
.git.dirty, .git.push { color: var(--alert); }
|
|
408
|
+
.git.ok { color: var(--calm); }
|
|
409
|
+
/* Without git there is nothing to say about the work: the lens stays, but quietly. */
|
|
410
|
+
.git.none { color: rgba(0, 0, 0, .32); }
|
|
411
|
+
.column > h2 .git { margin-left: 8px; vertical-align: middle; }
|
|
412
|
+
|
|
413
|
+
/* ── What a session weighs ────────────────────────────────────────────
|
|
414
|
+
At the head of the card, after the repository name and the git mark: repository · changes ·
|
|
415
|
+
load. Grey like the rest of the row — the colour comes from the gauge at the top — except for
|
|
416
|
+
whichever is eating the most while the machine is struggling, which is the only one worth
|
|
417
|
+
starting with. */
|
|
418
|
+
.postit .load {
|
|
419
|
+
margin-left: auto;
|
|
420
|
+
flex: none;
|
|
421
|
+
font-size: .72rem;
|
|
422
|
+
font-weight: 600;
|
|
423
|
+
font-variant-numeric: tabular-nums;
|
|
424
|
+
color: rgba(0, 0, 0, .45);
|
|
425
|
+
cursor: default;
|
|
426
|
+
}
|
|
427
|
+
.postit .load.hot { color: var(--alert); }
|
|
428
|
+
|
|
429
|
+
/* The corner icon: the pencil opens the editor, and on a finished card — where there is nothing
|
|
430
|
+
left to edit — the bin takes its place. */
|
|
431
|
+
.postit .corner {
|
|
432
|
+
position: absolute;
|
|
433
|
+
top: 6px;
|
|
434
|
+
right: 6px;
|
|
435
|
+
width: 24px;
|
|
436
|
+
height: 24px;
|
|
437
|
+
padding: 0;
|
|
438
|
+
display: flex;
|
|
439
|
+
align-items: center;
|
|
440
|
+
justify-content: center;
|
|
441
|
+
border-radius: 6px;
|
|
442
|
+
background: rgba(255, 255, 255, .72);
|
|
443
|
+
border-color: rgba(0, 0, 0, .14);
|
|
444
|
+
color: rgba(0, 0, 0, .55);
|
|
445
|
+
opacity: 0;
|
|
446
|
+
transition: opacity .12s ease;
|
|
447
|
+
}
|
|
448
|
+
.postit .corner svg { width: 14px; height: 14px; display: block; }
|
|
449
|
+
.postit .corner.del { color: #b91c1c; }
|
|
450
|
+
.postit:hover .corner, .postit .corner:focus-visible { opacity: 1; }
|
|
451
|
+
/* The title must not end up underneath it. On the first row the container holds the space, so the
|
|
452
|
+
git mark stays next to the repository name instead of next to the pencil. */
|
|
453
|
+
.postit .head, .postit .title { padding-right: 26px; }
|
|
454
|
+
|
|
455
|
+
/* The buttons stay hidden until you hover: the dashboard stays clean. */
|
|
456
|
+
.postit .actions {
|
|
457
|
+
display: flex;
|
|
458
|
+
gap: 7px;
|
|
459
|
+
margin-top: 11px;
|
|
460
|
+
opacity: 0;
|
|
461
|
+
transition: opacity .12s ease;
|
|
462
|
+
}
|
|
463
|
+
.postit:hover .actions, .postit:focus-within .actions { opacity: 1; }
|
|
464
|
+
.postit .actions button {
|
|
465
|
+
font-size: .84rem;
|
|
466
|
+
font-weight: 600;
|
|
467
|
+
padding: 6px 13px;
|
|
468
|
+
background: rgba(255, 255, 255, .72);
|
|
469
|
+
border-color: rgba(0, 0, 0, .14);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/* ── Editor ───────────────────────────────────────────────────────────
|
|
473
|
+
The dialog and its buttons live in base.css, which belongs to both pages: the file viewer opens
|
|
474
|
+
one too. What is left here is only what belongs to a card's editor and to nothing else. */
|
|
475
|
+
|
|
476
|
+
/* Choosing the repository: a searchable field, most used at the top. */
|
|
477
|
+
.picker { position: relative; }
|
|
478
|
+
.picker-list {
|
|
479
|
+
position: absolute;
|
|
480
|
+
z-index: 5;
|
|
481
|
+
left: 0;
|
|
482
|
+
right: 0;
|
|
483
|
+
top: calc(100% + 4px);
|
|
484
|
+
max-height: 320px;
|
|
485
|
+
overflow-y: auto;
|
|
486
|
+
background: #fff;
|
|
487
|
+
border: 1px solid var(--line);
|
|
488
|
+
border-radius: 10px;
|
|
489
|
+
box-shadow: 0 12px 30px rgba(0, 0, 0, .18);
|
|
490
|
+
padding: 5px;
|
|
491
|
+
}
|
|
492
|
+
.picker-list[hidden] { display: none; }
|
|
493
|
+
.picker-list div {
|
|
494
|
+
padding: 9px 12px;
|
|
495
|
+
border-radius: 7px;
|
|
496
|
+
cursor: pointer;
|
|
497
|
+
font-size: .95rem;
|
|
498
|
+
font-weight: 500;
|
|
499
|
+
color: var(--ink);
|
|
500
|
+
display: flex;
|
|
501
|
+
align-items: center;
|
|
502
|
+
gap: 9px;
|
|
503
|
+
}
|
|
504
|
+
.picker-list div.on, .picker-list div:hover { background: #f0ede3; }
|
|
505
|
+
/* Where you are with the arrows: the row Enter picks, told apart from the one already chosen. */
|
|
506
|
+
.picker-list div.active { background: #e6e1d3; box-shadow: inset 0 0 0 1px #cdc6b4; }
|
|
507
|
+
.picker-list div b { font-weight: 700; }
|
|
508
|
+
.picker-list div em { margin-left: auto; font-style: normal; font-size: .74rem; color: var(--muted); font-weight: 400; }
|
|
509
|
+
|
|
510
|
+
.toast {
|
|
511
|
+
position: fixed;
|
|
512
|
+
bottom: 22px;
|
|
513
|
+
left: 50%;
|
|
514
|
+
transform: translateX(-50%) translateY(20px);
|
|
515
|
+
background: var(--ink);
|
|
516
|
+
color: #fffdf7;
|
|
517
|
+
padding: 12px 20px;
|
|
518
|
+
border-radius: 9px;
|
|
519
|
+
opacity: 0;
|
|
520
|
+
pointer-events: none;
|
|
521
|
+
transition: opacity .2s, transform .2s;
|
|
522
|
+
max-width: 80vw;
|
|
523
|
+
font-size: .92rem;
|
|
524
|
+
}
|
|
525
|
+
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
526
|
+
|
|
527
|
+
/* ── Driving mode ─────────────────────────────────────────────────────
|
|
528
|
+
Everything bigger, for when you glance at the board from across the room.
|
|
529
|
+
|
|
530
|
+
The base size goes from 17 to 22, and with it goes everything written in `rem` — which is
|
|
531
|
+
nearly all the text. What does NOT go up by itself is the things you click: those are in
|
|
532
|
+
pixels, and in pixels they stay until they are raised by hand. And they are exactly the point —
|
|
533
|
+
a 34px "+" and a 12px lens, glanced at from a distance, cannot be hit.
|
|
534
|
+
|
|
535
|
+
The switch is the server's to flip, not the browser's: `data-driving` on the `<html>` element
|
|
536
|
+
is only its shadow in here. */
|
|
537
|
+
html[data-driving] { font-size: 22px; }
|
|
538
|
+
|
|
539
|
+
/* The bar's "+" and the column's: a card is born from them, and it is the gesture you make most
|
|
540
|
+
often. */
|
|
541
|
+
html[data-driving] .new { width: 46px; height: 46px; }
|
|
542
|
+
html[data-driving] .new svg { width: 24px; height: 24px; }
|
|
543
|
+
html[data-driving] .modes button { width: 46px; height: 46px; }
|
|
544
|
+
html[data-driving] .modes button svg { width: 27px; height: 27px; }
|
|
545
|
+
html[data-driving] .column .add { width: 26px; height: 26px; }
|
|
546
|
+
html[data-driving] .column .add svg { width: 20px; height: 20px; }
|
|
547
|
+
|
|
548
|
+
/* The git lens: it is the door to the files, and at 12px it is a target for sitting still. */
|
|
549
|
+
html[data-driving] .git svg { width: 18px; height: 18px; }
|
|
550
|
+
|
|
551
|
+
/* The corner pencil. As well as growing, it stays visible: normally it appears on hover, and a
|
|
552
|
+
target that expects to be found before it will show itself is, in driving mode, not a target. */
|
|
553
|
+
html[data-driving] .postit .corner { width: 34px; height: 34px; opacity: 1; }
|
|
554
|
+
html[data-driving] .postit .corner svg { width: 20px; height: 20px; }
|
|
555
|
+
|
|
556
|
+
/* The zoom at the bottom right, which stays the only way to bring the board back into view. */
|
|
557
|
+
html[data-driving] .zoom button { width: 40px; height: 40px; }
|
|
558
|
+
html[data-driving] .zoom button svg { width: 22px; height: 22px; }
|
|
559
|
+
|
|
560
|
+
/* The column is in pixels too: with the text at 22 the cards would choke inside it. */
|
|
561
|
+
html[data-driving] .column { flex: 0 0 340px; }
|