Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/www/css/workspace.css

16.0 KiB, 1 run

created by r2519314175:1145, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1/* workspace.css — the controls that arrange Daimond rather than run it.
2 *
3 * Three surfaces live here, and they are deliberately layered so that no one of
4 * them has to carry everything:
5 *
6 * the chip row what is on screen, and one click to change it,
7 * the gallery every panel there is, searchable, for when the row cannot
8 * hold them all,
9 * the palette the same reach from the keyboard, at any fleet size.
10 *
11 * The row is allowed to be incomplete precisely because the other two are not.
12 */
13
14/* ── The chip row ────────────────────────────────────────────────────────────
15 Chips are TOGGLES, not summons: every panel keeps a chip whether it is open
16 or shut, so a chip never moves when its neighbour opens. An earlier design
17 showed only the closed panels, which meant the row was longest when the app
18 was emptiest and every chip shifted sideways each time one was used. */
19/* The row clips, so it has to leave room for the ring its own chips draw.
20 `.ptag:focus-visible` asks for a 2px outline at 2px offset and had 0.0px on
21 every side, so the entire keyboard affordance for the primary panel switcher
22 was two vertical arcs -- the same "border that stopped at the corners"
23 signature as the selected chat tile and the selected tag chip before it. The
24 padding is cancelled by negative margins so the top bar does not grow. */
25/* A TRANSIENT ELEMENT MUST NEVER SIT IN THE FLOW OF CLICKABLE TARGETS. Either
26 it lives outside that flow, or the space it occupies is reserved whether or
27 not it is showing. That is the rule this row is laid out to, and it is the
28 general form of the two notes below it: a chip that appears where a chip did
29 not, or a glyph that widens the chip it is on, both move somebody's target
30 while they are reaching for it.
31 `flex: 1 1 auto` is what enforces it here. The row used to shrink to its own
32 contents inside a right-anchored group, so anything that appeared to its
33 RIGHT -- the sync pill, the pairing button, a held-sweep notice -- moved every
34 chip in it. Taking the room instead fixes the row's left edge at the wordmark,
35 and nothing that happens further along the bar can reach it. */
36.panel-tags {
37 display: flex;
38 align-items: center;
39 gap: 5px;
40 min-width: 0;
41 flex: 1 1 auto;
42 overflow: hidden;
43 padding: 4px;
44 margin: -4px 2px -4px -4px;
45}
46/* `flex: none`, not `min-width: 0`: the groups were shrinking past content
47 that cannot shrink, so at 761-768px the stage and dock chips were drawn
48 on top of each other. The row's own overflow and its `... N` chip are
49 what handle a row too full. */
50.ptag-group { display: flex; align-items: center; gap: 5px; flex: none; }
51
52/* Zone is legible by position first -- rail, stage and dock groups run
53 left to right in the order the zones themselves do -- and the divider only
54 confirms it. Position matters more than colour here because the two zones
55 behave differently: opening a dock panel ADDS one, while opening a stage
56 panel evicts whichever guest is sitting beside the daimon. */
57.ptag-div { width: 1px; height: 16px; background: var(--border-2); margin: 0 3px; flex: none; }
58
59.ptag {
60 font-size: var(--fs-2xs);
61 letter-spacing: 0.02em;
62 padding: 4px 10px;
63 border-radius: 999px;
64 background: var(--bg-tertiary);
65 color: var(--text-secondary);
66 border: 1px solid var(--border-strong);
67 white-space: nowrap;
68}
69.ptag:hover { color: var(--text-primary); border-color: var(--accent); }
70.ptag:disabled { opacity: 0.4; cursor: not-allowed; }
71.ptag:disabled:hover { color: var(--text-secondary); border-color: var(--border); }
72
73/* An open panel's chip. Filled rather than outlined, so the row reads as a map
74 of what is on screen at a glance. */
75.ptag.on {
76 background: var(--accent-soft);
77 border-color: var(--accent);
78 color: var(--accent-text);
79}
80.ptag.on:hover { color: var(--text-primary); }
81
82/* A stage chip that would displace the guest beside the chat says so before it
83 is clicked -- but it must do so WITHOUT changing its own width. A glyph
84 appended here re-flowed every chip to its right the moment a stage seat
85 filled, which is exactly the shifting the toggle row exists to prevent. A
86 border style costs no layout. */
87.ptag-stage.will-evict { border-style: dashed; }
88
89.ptag-more { flex: none; font-family: var(--font); letter-spacing: 0.08em; padding: 4px 9px; }
90.ptag-more .n { font-size: var(--fs-4xs); opacity: 0.8; margin-left: 3px; letter-spacing: 0; }
91
92/* ── Popover furniture, shared by the gallery and the settings menu ───────── */
93.pop {
94 position: fixed;
95 z-index: 3000;
96 background: var(--bg-secondary);
97 border: 1px solid var(--border-2);
98 border-radius: var(--radius-lg);
99 box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
100 padding: 10px;
101 min-width: 260px;
102 max-width: min(92vw, 380px);
103 max-height: min(72vh, 620px);
104 overflow-y: auto;
105}
106.pop[hidden] { display: none; }
107.pop-head {
108 font-size: var(--fs-2xs);
109 text-transform: uppercase;
110 letter-spacing: 0.05em;
111 color: var(--text-muted);
112 padding: 6px 6px 4px;
113}
114.pop-head:not(:first-child) { margin-top: 6px; border-top: 1px solid var(--border); }
115.pop-note {
116 font-size: var(--fs-2xs);
117 color: var(--text-muted);
118 padding: 8px 6px 2px;
119 border-top: 1px solid var(--border);
120 margin-top: 6px;
121 line-height: 1.5;
122}
123/* The language/currency pickers in the appearance menu. Sized to the popover,
124 not to a form -- .settings-select reads oversized at 380px. */
125.pop .set-pick { padding: 2px 6px 8px; }
126.pop .set-pick select {
127 width: 100%;
128 font-size: var(--fs-xs);
129 padding: 6px 8px;
130 background: var(--bg-tertiary);
131 color: var(--text-primary);
132 border: 1px solid var(--border-strong);
133 border-radius: var(--radius-sm);
134}
135/* A note explaining the picker directly above it is one thought, not a new
136 section -- no divider. */
137.pop .set-pick + .pop-note { border-top: none; margin-top: 0; padding-top: 2px; }
138.pop-note kbd {
139 font-family: var(--font-mono);
140 font-size: var(--fs-4xs);
141 border: 1px solid var(--border-2);
142 border-radius: 4px;
143 padding: 1px 4px;
144 color: var(--text-secondary);
145}
146
147/* ── The settings menu ───────────────────────────────────────────────────────
148 Theme, reading size and the dock's tiling, in one place. They belong together
149 because they are the same kind of decision: how the app should look and sit,
150 as opposed to what it should do. */
151.menu-btn { gap: 5px; }
152.menu-btn .ic { width: 18px; height: 18px; }
153
154.set-row { display: flex; align-items: center; gap: 8px; padding: 5px 6px; }
155.set-row > .lbl { flex: 1; font-size: var(--fs-sm); color: var(--text-primary); }
156
157/* A segmented control: the options of one setting, side by side, so the choice
158 and what else was available are one glance rather than two. */
159.seg { display: flex; gap: 3px; flex-wrap: wrap; padding: 2px 6px 8px; }
160.seg button {
161 /* `flex-basis: 0`, not `auto`: with `auto` each option starts from the width
162 of its own contents and only the SPARE room is shared out, so a row of
163 five tiles came out 66.3 / 54.6 / 65.6 / 65.6 / 76.6px — the "1" tile 22px
164 narrower than "3x2" beside it. A segmented control that divides its row
165 unevenly reads as five buttons rather than one control, which is the
166 opposite of what a segment is for. From a zero basis the row divides
167 exactly, and `min-width` still stops it collapsing on a phone. */
168 flex: 1 1 0;
169 min-width: 54px;
170 background: var(--bg-tertiary);
171 border: 1px solid var(--border-strong);
172 color: var(--text-secondary);
173 border-radius: var(--radius-sm);
174 padding: 6px 8px;
175 font-size: var(--fs-xs);
176 white-space: nowrap;
177}
178.seg button:hover { border-color: var(--accent); color: var(--text-primary); }
179.seg button[aria-pressed="true"] {
180 background: var(--accent-soft);
181 border-color: var(--accent);
182 color: var(--accent-text);
183}
184
185/* The palette picker. A list rather than a segmented row, because ten of them
186 in three bands is something a reader looks DOWN, and because the bands are
187 the answer to the only question anyone brings to it -- "a light one" -- which
188 a row of ten equal buttons cannot answer at all. Full width of the menu, so
189 the longest palette name is never the reason it truncates. */
190.theme-pick {
191 display: block;
192 width: calc(100% - 12px);
193 margin: 2px 6px 8px;
194 background: var(--bg-tertiary);
195 border: 1px solid var(--border-strong);
196 color: var(--text-primary);
197 border-radius: var(--radius-sm);
198 padding: 6px 8px;
199 font-size: var(--fs-xs);
200 cursor: pointer;
201}
202.theme-pick:hover { border-color: var(--accent); }
203.theme-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
204/* The dropped list is drawn by the platform, which does not inherit the panel's
205 colours on every browser; naming both keeps a light palette's list off a
206 dark-on-dark default. */
207.theme-pick option,
208.theme-pick optgroup { background: var(--bg-secondary); color: var(--text-primary); }
209
210/* The reading size, shown as the thing it changes: the sample is set in the
211 size being chosen, so the control demonstrates rather than describes. */
212.size-row { display: flex; align-items: center; gap: 8px; padding: 2px 6px 8px; }
213.size-row button {
214 width: 32px; height: 30px;
215 background: var(--bg-tertiary);
216 border: 1px solid var(--border-strong);
217 color: var(--text-secondary);
218 border-radius: var(--radius-sm);
219}
220.size-row button:hover:not(:disabled) { border-color: var(--accent); color: var(--text-primary); }
221.size-row button:disabled { opacity: 0.35; cursor: not-allowed; }
222.size-row .sample {
223 flex: 1;
224 text-align: center;
225 color: var(--text-primary);
226 font-size: var(--fs-base);
227 line-height: 1.2;
228 overflow: hidden;
229 white-space: nowrap;
230}
231.size-row .pct { font-size: var(--fs-2xs); color: var(--text-muted); font-family: var(--font-mono); min-width: 38px; text-align: right; }
232
233/* A grid option drawn as the grid it selects, because "2 x 3" is a description
234 of a picture and the picture is smaller than the sentence. */
235.grid-opt { display: flex; flex-direction: column; align-items: center; gap: 5px; }
236/* Every picture is a different height -- one column of four is 30px, three of
237 two is 14px -- and the caption sits under whatever its own picture ends at,
238 so the five words came out on five different lines, a visible zigzag along
239 the row. Reserving the tallest picture's height and centring the rows in it
240 puts every caption on one line. 30px is 4 rows of 6px with 3 gaps of 2px,
241 which is the tallest grid the engine offers. */
242.grid-opt .cells { display: grid; gap: 2px; min-height: 30px; align-content: center; }
243.grid-opt .cells i {
244 display: block;
245 width: 9px; height: 6px;
246 border-radius: 1px;
247 background: currentColor;
248 opacity: 0.55;
249}
250.grid-opt[aria-pressed="true"] .cells i { opacity: 1; }
251.grid-opt .cells i.maybe { background: none; box-shadow: inset 0 0 0 1px currentColor; }
252.grid-opt .cap { font-size: var(--fs-4xs); letter-spacing: 0.02em; }
253
254/* Keyboard focus, themed. The browser's own ring is drawn in a colour of its
255 choosing -- black, on a pale theme -- which on a segmented control reads as a
256 second kind of "selected" beside the one that means it. */
257.seg button:focus-visible,
258.size-row button:focus-visible,
259.ptag:focus-visible,
260.gal-pin:focus-visible {
261 outline: 2px solid var(--accent);
262 outline-offset: 2px;
263}
264
265/* ── The gallery: every panel, searchable ────────────────────────────────── */
266.gal-search {
267 width: 100%;
268 box-sizing: border-box;
269 background: var(--bg-tertiary);
270 border: 1px solid var(--border-strong);
271 border-radius: var(--radius-sm);
272 color: var(--text-primary);
273 font-size: var(--fs-sm);
274 padding: 7px 10px;
275 margin-bottom: 6px;
276}
277.gal-search:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
278
279.gal-row {
280 display: flex;
281 align-items: center;
282 gap: 8px;
283 width: 100%;
284 padding: 7px 8px;
285 border-radius: var(--radius-sm);
286 background: none;
287 border: 1px solid transparent;
288 color: var(--text-primary);
289 font-size: var(--fs-sm);
290 text-align: left;
291}
292.gal-row:hover, .gal-row:focus-visible { background: var(--bg-hover); outline: none; }
293.gal-row .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
294.gal-row .state { font-size: var(--fs-2xs); color: var(--text-muted); flex: none; }
295.gal-row.is-open .state { color: var(--ok); }
296
297/* Pinning is what keeps the row predictable: the user says what sits in the
298 header, and nothing reorders itself behind their back. */
299.gal-pin {
300 flex: none;
301 width: 26px; height: 26px;
302 display: flex; align-items: center; justify-content: center;
303 background: none; border: none; border-radius: var(--radius-sm);
304 color: var(--text-muted);
305}
306.gal-pin:hover { background: var(--bg-tertiary); color: var(--text-primary); }
307.gal-pin[aria-pressed="true"] { color: var(--accent); }
308.gal-pin .ic { width: 15px; height: 15px; }
309
310.gal-empty { padding: 14px 8px; font-size: var(--fs-sm); color: var(--text-muted); text-align: center; }
311
312/* ── The palette ─────────────────────────────────────────────────────────────
313 Invisible until asked for, and complete when it arrives. It exists so the
314 visible surfaces are free NOT to be complete. */
315.pal-scrim {
316 position: fixed; inset: 0;
317 background: rgba(0, 0, 0, 0.45);
318 z-index: 4000;
319 display: flex;
320 align-items: flex-start;
321 justify-content: center;
322 padding: 12vh 16px 16px;
323}
324.pal-scrim[hidden] { display: none; }
325.pal-box {
326 width: 100%;
327 max-width: 520px;
328 background: var(--bg-secondary);
329 border: 1px solid var(--border-2);
330 border-radius: var(--radius-lg);
331 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
332 overflow: hidden;
333 display: flex;
334 flex-direction: column;
335 max-height: 70vh;
336}
337/* The search box and the closer share a row, so the rule that used to divide
338 the box from the list now divides the ROW from it -- otherwise the cross sat
339 below the line it belongs above. */
340.pal-top {
341 flex: none;
342 display: flex; align-items: center; gap: 4px;
343 padding-right: 8px;
344 border-bottom: 1px solid var(--border);
345}
346.pal-input {
347 flex: 1; min-width: 0;
348 box-sizing: border-box;
349 background: none;
350 border: none;
351 color: var(--text-primary);
352 font-size: var(--fs-xl);
353 padding: 14px 16px;
354}
355.pal-input:focus { outline: none; }
356.pal-list { list-style: none; margin: 0; padding: 6px; overflow-y: auto; }
357.pal-item {
358 display: flex;
359 align-items: center;
360 gap: 10px;
361 padding: 9px 10px;
362 border-radius: var(--radius-sm);
363 font-size: var(--fs-sm);
364 color: var(--text-primary);
365 cursor: pointer;
366}
367.pal-item .kind {
368 font-size: var(--fs-4xs);
369 text-transform: uppercase;
370 letter-spacing: 0.05em;
371 color: var(--text-muted);
372 border: 1px solid var(--border);
373 border-radius: 999px;
374 padding: 2px 7px;
375 flex: none;
376}
377.pal-item .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
378.pal-item .hint { font-size: var(--fs-2xs); color: var(--text-muted); flex: none; }
379.pal-item[aria-selected="true"] { background: var(--accent-soft); color: var(--accent-text); }
380.pal-item[aria-selected="true"] .hint { color: var(--accent-text); }
381.pal-empty { padding: 20px; text-align: center; color: var(--text-muted); font-size: var(--fs-sm); }
382
383/* Below the phone breakpoint the row is the FOOTER. It used to be hidden here,
384 on the reasoning that "a chip row on a 380px screen is a scrolling mess" and
385 that the drawer and the four-destination bottom bar answered the question it
386 would answer. The owner's verdict on that bar, 2026-08-27: "the footer in
387 mobile view is currently useless to me". Four seats named four panels out of
388 seventeen, and the other thirteen were reachable only through a drawer.
389 So the row moves into the bar rather than being hidden from it -- one row,
390 still `#panel-tags`, moved by `placeChips` in js/mobile.js -- and it scrolls
391 sideways, which is what a phone does with a list too long for its width.
392 Its rules are in css/mobile.css, with the rest of the phone shell. */
393@media (max-width: 760px) {
394 .pal-scrim { padding-top: 6vh; }
395}