geometryview 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +37 -2
  2. package/bin/geometryview.mjs +65 -0
  3. package/package.json +42 -4
  4. package/src/bridge.mjs +77 -0
  5. package/src/mcp.mjs +73 -0
  6. package/src/native.mjs +183 -0
  7. package/src/page.mjs +147 -0
  8. package/src/tools.mjs +265 -0
  9. package/src/web.mjs +173 -0
  10. package/src/websocket.mjs +104 -0
  11. package/web/LICENSE +21 -0
  12. package/web/NOTICES.md +49 -0
  13. package/web/bridge.js +29 -0
  14. package/web/build/geometryview-web.mjs +2 -0
  15. package/web/build/geometryview-web.wasm +0 -0
  16. package/web/chrome.js +541 -0
  17. package/web/engine.js +453 -0
  18. package/web/fonts/IBMPlexMono-Medium.ttf +0 -0
  19. package/web/fonts/IBMPlexMono-Regular.ttf +0 -0
  20. package/web/fonts/IBMPlexMono-SemiBold.ttf +0 -0
  21. package/web/fonts/LICENSE.txt +93 -0
  22. package/web/icons/icon-180.png +0 -0
  23. package/web/icons/icon-192.png +0 -0
  24. package/web/icons/icon-32.png +0 -0
  25. package/web/icons/icon-512.png +0 -0
  26. package/web/icons.js +50 -0
  27. package/web/import-worker.js +329 -0
  28. package/web/index.html +17 -0
  29. package/web/input.js +255 -0
  30. package/web/isolation-worker.js +21 -0
  31. package/web/library-rows.js +120 -0
  32. package/web/library-store.js +368 -0
  33. package/web/library.js +306 -0
  34. package/web/manifest.webmanifest +14 -0
  35. package/web/occt/geometryview-occt-web.mjs +2 -0
  36. package/web/occt/geometryview-occt-web.wasm +0 -0
  37. package/web/occt/geometryview-step-web.mjs +2 -0
  38. package/web/occt/geometryview-step-web.wasm +0 -0
  39. package/web/occt-threads/geometryview-step-web.mjs +2 -0
  40. package/web/occt-threads/geometryview-step-web.wasm +0 -0
  41. package/web/occt-worker.js +45 -0
  42. package/web/structure.js +435 -0
  43. package/web/toolbar-fold.js +49 -0
  44. package/web/ui.js +134 -0
  45. package/web/viewer.css +558 -0
  46. package/web/viewer.js +1163 -0
package/web/viewer.css ADDED
@@ -0,0 +1,558 @@
1
+ /* The viewer's chrome, drawn as the Mac app draws its own: paper, ink and sticker cards in IBM
2
+ Plex Mono, with the same palette, line weight, radii, offset shadows and metrics, which
3
+ Docs/design/chrome-spec.md lists. A card's border is inside its box, as on the Mac, so a padding
4
+ here is the Mac's inset less the 1.5px line. Tools/compare_chrome.py checks the two agree. */
5
+
6
+ @font-face {
7
+ font-family: "Plex Mono";
8
+ src: url("fonts/IBMPlexMono-Regular.ttf") format("truetype");
9
+ font-weight: 400;
10
+ }
11
+ @font-face {
12
+ font-family: "Plex Mono";
13
+ src: url("fonts/IBMPlexMono-Medium.ttf") format("truetype");
14
+ font-weight: 500;
15
+ }
16
+ @font-face {
17
+ font-family: "Plex Mono";
18
+ src: url("fonts/IBMPlexMono-SemiBold.ttf") format("truetype");
19
+ font-weight: 600;
20
+ }
21
+
22
+ :root {
23
+ --paper: #f2efe7;
24
+ --sheet: #f7f5ee;
25
+ --sheet-sunk: #ece8dd;
26
+ --ink: #231e16;
27
+ --ink-soft: #3a342a;
28
+ --muted: #6f685c;
29
+ --faint: #9a9284;
30
+ --rule: #ddd6c8;
31
+ --cream: #fff4d6;
32
+ --cream-wash: #fbf5e2;
33
+ --cobalt: #76a0e2;
34
+ --cobalt-mid: #5a82c6;
35
+ --cobalt-wash: #dde6f4;
36
+ --mustard: #f4b829;
37
+ --tomato-wash: #f8ddd3;
38
+ --ink-on-dark: #eef4ff;
39
+ --muted-on-dark: #a9bedf;
40
+ --line: 1.5px;
41
+ --radius-card: 12px;
42
+ --radius-chip: 8px;
43
+ --shadow-card: 3px;
44
+ --shadow-chip: 2px;
45
+ --pill: 30px;
46
+ --ease: cubic-bezier(0.3, 0.7, 0.2, 1);
47
+ --text-xs: 10.5px;
48
+ --text-s: 11.5px;
49
+ --text-m: 12.5px;
50
+ --text-l: 13.5px;
51
+ --text-heading: 15px;
52
+ color-scheme: light;
53
+ }
54
+
55
+ * { box-sizing: border-box; }
56
+ [hidden] { display: none !important; }
57
+
58
+ html, body {
59
+ margin: 0;
60
+ height: 100%;
61
+ overflow: hidden;
62
+ background: var(--paper);
63
+ color: var(--ink);
64
+ font: 400 var(--text-m)/1.3 "Plex Mono", Menlo, monospace;
65
+ -webkit-font-smoothing: antialiased;
66
+ -webkit-user-select: none;
67
+ user-select: none;
68
+ -webkit-tap-highlight-color: transparent;
69
+ }
70
+
71
+ #viewport {
72
+ position: fixed;
73
+ inset: 0;
74
+ width: 100%;
75
+ height: 100%;
76
+ display: block;
77
+ touch-action: none;
78
+ outline: none;
79
+ }
80
+
81
+ button, input { font: inherit; color: inherit; }
82
+ button { cursor: pointer; }
83
+ button:disabled { cursor: default; }
84
+
85
+ @media (prefers-reduced-motion: reduce) {
86
+ * { transition: none !important; }
87
+ }
88
+
89
+ /* ---- Cards ------------------------------------------------------------------------------ */
90
+
91
+ .card {
92
+ position: fixed;
93
+ background: var(--sheet);
94
+ border: var(--line) solid var(--ink);
95
+ border-radius: var(--radius-card);
96
+ box-shadow: var(--shadow-card) var(--shadow-card) 0 var(--ink);
97
+ z-index: 2;
98
+ }
99
+
100
+ .card h2 {
101
+ margin: 0 0 2px;
102
+ font-size: var(--text-xs);
103
+ font-weight: 500;
104
+ letter-spacing: 0.04em;
105
+ color: var(--muted);
106
+ }
107
+
108
+ /* ---- Controls --------------------------------------------------------------------------- */
109
+
110
+ .pill {
111
+ display: inline-flex;
112
+ align-items: center;
113
+ justify-content: center;
114
+ gap: 6px;
115
+ height: var(--pill);
116
+ min-width: var(--pill);
117
+ padding: 0 10.5px;
118
+ border: var(--line) solid var(--ink);
119
+ border-radius: calc(var(--pill) / 2);
120
+ background: var(--sheet);
121
+ font-size: var(--text-s);
122
+ font-weight: 500;
123
+ white-space: nowrap;
124
+ transition: background 0.16s var(--ease);
125
+ }
126
+ .pill.icon { padding: 0; width: var(--pill); }
127
+ .pill:hover:not(:disabled) { background: var(--cream); }
128
+ .pill:active:not(:disabled) { transform: translate(1px, 1px); }
129
+ .pill.on { background: var(--ink); color: var(--paper); }
130
+ .pill:disabled { opacity: 0.4; }
131
+ .pill.ghostly { border-color: transparent; background: transparent; }
132
+ .pill.ghostly:hover { border-color: var(--ink); }
133
+
134
+ .segmented {
135
+ position: relative;
136
+ display: inline-flex;
137
+ height: var(--pill);
138
+ border-radius: calc(var(--pill) / 2);
139
+ overflow: hidden;
140
+ background: var(--sheet);
141
+ flex: none;
142
+ }
143
+ .segmented::after {
144
+ content: "";
145
+ position: absolute;
146
+ inset: 0;
147
+ border: var(--line) solid var(--ink);
148
+ border-radius: inherit;
149
+ pointer-events: none;
150
+ }
151
+ .segmented button {
152
+ border: none;
153
+ box-shadow: inset var(--line) 0 0 var(--ink);
154
+ background: transparent;
155
+ padding: 0 11px;
156
+ font-size: var(--text-s);
157
+ font-weight: 500;
158
+ white-space: nowrap;
159
+ transition: background 0.16s var(--ease);
160
+ }
161
+ .segmented button:first-child { box-shadow: none; }
162
+ .segmented button:hover { background: var(--cream-wash); }
163
+ .segmented button.on {
164
+ font-weight: 600;
165
+ background: linear-gradient(to top, transparent var(--line), var(--ink) var(--line),
166
+ var(--ink) calc(var(--line) + 2px), var(--cream) calc(var(--line) + 2px));
167
+ }
168
+ .segmented.disabled { opacity: 0.4; pointer-events: none; }
169
+
170
+ .slider {
171
+ -webkit-appearance: none;
172
+ appearance: none;
173
+ width: 120px;
174
+ height: 16px;
175
+ background: transparent;
176
+ flex: 1;
177
+ }
178
+ .slider::-webkit-slider-runnable-track { height: 3px; border-radius: 1.5px; background: var(--rule); }
179
+ .slider::-webkit-slider-thumb {
180
+ -webkit-appearance: none;
181
+ width: 16px; height: 16px; margin-top: -6.5px;
182
+ border-radius: 8px; border: var(--line) solid var(--ink); background: var(--cream);
183
+ }
184
+ .slider::-moz-range-track { height: 3px; border-radius: 1.5px; background: var(--rule); }
185
+ .slider::-moz-range-thumb { width: 13px; height: 13px; border-radius: 8px; border: var(--line) solid var(--ink); background: var(--cream); }
186
+ .slider:disabled { opacity: 0.35; }
187
+
188
+ kbd.cap {
189
+ display: inline-block;
190
+ min-width: 20px;
191
+ height: 20px;
192
+ padding: 0 3.75px;
193
+ border: 1.25px solid var(--ink);
194
+ border-radius: 5px;
195
+ background: var(--paper);
196
+ box-shadow: 1px 1px 0 var(--ink);
197
+ font: 600 var(--text-xs)/17.5px "Plex Mono", Menlo, monospace;
198
+ text-align: center;
199
+ }
200
+
201
+ .chip {
202
+ display: inline-flex;
203
+ align-items: center;
204
+ gap: 8px;
205
+ padding: 3.5px 8.5px;
206
+ background: var(--sheet);
207
+ border: var(--line) solid var(--ink);
208
+ border-radius: var(--radius-chip);
209
+ box-shadow: var(--shadow-chip) var(--shadow-chip) 0 var(--ink);
210
+ font-size: var(--text-s);
211
+ white-space: nowrap;
212
+ }
213
+
214
+ /* ---- The masthead ----------------------------------------------------------------------- */
215
+
216
+ .masthead {
217
+ position: fixed;
218
+ left: 24px;
219
+ top: 10px;
220
+ max-width: 50%;
221
+ z-index: 1;
222
+ pointer-events: none;
223
+ }
224
+ .masthead .title, .masthead .subtitle { text-shadow: 0 0 3px var(--paper); }
225
+ .masthead .title { font-size: var(--text-l); font-weight: 600; }
226
+ .masthead .subtitle { margin-top: 2px; font-size: var(--text-s); color: var(--muted); }
227
+ .masthead .progress {
228
+ position: relative;
229
+ margin-top: 10px;
230
+ width: 240px;
231
+ height: 6px;
232
+ border: var(--line) solid var(--ink);
233
+ border-radius: 3px;
234
+ background: var(--sheet);
235
+ overflow: hidden;
236
+ }
237
+ /* Slid in from the left rather than widened, so it moves without layout. */
238
+ .masthead .progress .fill {
239
+ position: absolute;
240
+ inset: 0;
241
+ background: var(--mustard);
242
+ border-right: var(--line) solid var(--ink);
243
+ transform: translateX(-100%);
244
+ transition: transform 0.22s var(--ease);
245
+ }
246
+ .masthead.on-dark .title { color: var(--ink-on-dark); }
247
+ .masthead.on-dark .subtitle { color: var(--muted-on-dark); }
248
+
249
+ /* ---- The structure and library cards ---------------------------------------------------- */
250
+
251
+ .structure {
252
+ left: 18px;
253
+ top: 72px;
254
+ bottom: 96px;
255
+ width: 300px;
256
+ display: flex;
257
+ flex-direction: column;
258
+ padding: 10.5px 14.5px 6.5px;
259
+ gap: 10px;
260
+ }
261
+ .structure > .segmented { align-self: flex-start; }
262
+
263
+ .searchbox {
264
+ display: flex;
265
+ align-items: center;
266
+ gap: 6px;
267
+ height: 28px;
268
+ padding: 0 10px;
269
+ border: var(--line) solid var(--rule);
270
+ border-radius: 14px;
271
+ background: #fff;
272
+ color: var(--muted);
273
+ }
274
+ .searchbox:focus-within { border-color: var(--ink); }
275
+ .searchbox .glyph { display: inline-flex; }
276
+ .searchbox input {
277
+ flex: 1;
278
+ min-width: 0;
279
+ border: none;
280
+ outline: none;
281
+ background: transparent;
282
+ font-size: var(--text-m);
283
+ color: var(--ink);
284
+ -webkit-user-select: text;
285
+ user-select: text;
286
+ }
287
+ .searchbox input::-webkit-search-cancel-button { display: none; }
288
+ .searchbox input::placeholder { color: var(--muted); opacity: 1; }
289
+ .searchbox .match { font-size: var(--text-xs); color: var(--muted); white-space: nowrap; }
290
+
291
+ .tree {
292
+ position: relative;
293
+ flex: 1;
294
+ overflow-y: auto;
295
+ overflow-x: hidden;
296
+ margin: 0 -10px;
297
+ overscroll-behavior: contain;
298
+ }
299
+ .tree .spacer { width: 1px; }
300
+ .tree .rows { position: absolute; left: 0; right: 0; top: 0; will-change: transform; }
301
+
302
+ /* A row: its chevron at 8 + 14 a level (the padding, set per row), a 16-wide slot for the glyph or
303
+ swatch 3 after it, the name 7 after that, and at the end the count 14 from the edge. */
304
+ .tree .row {
305
+ position: relative;
306
+ isolation: isolate;
307
+ display: flex;
308
+ align-items: center;
309
+ height: 28px;
310
+ padding-right: 8px;
311
+ font-size: var(--text-m);
312
+ cursor: default;
313
+ }
314
+ body.phone .tree .row { height: 30px; }
315
+ /* The plate behind a row, inset from it: cream under the pointer, and while selected the cobalt
316
+ wash with a bar down its left edge, clipped to its corners. */
317
+ .tree .row::after {
318
+ content: "";
319
+ position: absolute;
320
+ inset: 1px 6px;
321
+ border-radius: 6px;
322
+ z-index: -1;
323
+ }
324
+ .tree .row:hover::after, .tree .row.highlighted::after { background: var(--cream); }
325
+ .tree .row.selected::after { background: linear-gradient(to right, var(--cobalt-mid) 3px, var(--cobalt-wash) 3px); }
326
+ .tree .chevron {
327
+ flex: none;
328
+ display: inline-flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ width: 16px;
332
+ height: 16px;
333
+ padding: 0;
334
+ border: none;
335
+ background: transparent;
336
+ color: var(--ink);
337
+ }
338
+ .tree .chevron.open { transform: rotate(90deg); }
339
+ .tree .glyph { flex: none; display: inline-flex; justify-content: center; width: 16px; margin-left: 3px; color: var(--ink); }
340
+ /* A part's colour as a printed chip: the colour, and below a diagonal across 55% of the way, a
341
+ shade of it 16% toward ink, both set as one gradient over the whole tile, under its line. */
342
+ .tree .swatch {
343
+ flex: none;
344
+ width: 12px;
345
+ height: 12px;
346
+ margin: 0 2px 0 5px;
347
+ border: 1.25px solid var(--ink);
348
+ border-radius: 3px;
349
+ background-color: var(--sheet-sunk);
350
+ background-origin: border-box;
351
+ }
352
+ .tree .swatch.small { width: 9px; height: 9px; margin: 0 3.5px 0 6.5px; border-radius: 2px; }
353
+ .tree .name { flex: 1; min-width: 0; margin-left: 7px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
354
+ .tree .count, .tree .mark { flex: none; margin: 0 6px 0 8px; font-size: var(--text-s); color: var(--muted); display: inline-flex; }
355
+ .tree .actions { display: none; flex: none; gap: 1px; margin-left: 4px; }
356
+ .tree .row:hover .actions { display: inline-flex; }
357
+ .tree .row:hover .actions ~ .count, .tree .row:hover .actions ~ .mark, .tree .row:hover .actions ~ .pill { display: none; }
358
+ .tree .rowbutton {
359
+ display: inline-flex;
360
+ align-items: center;
361
+ justify-content: center;
362
+ width: 22px;
363
+ height: 22px;
364
+ padding: 0;
365
+ border: 1.25px solid transparent;
366
+ border-radius: 6px;
367
+ background: transparent;
368
+ color: var(--ink);
369
+ transition: background 0.16s var(--ease), border-color 0.16s var(--ease);
370
+ }
371
+ .tree .rowbutton:hover { border-color: var(--ink); background: var(--sheet); }
372
+ .tree .rowbutton.on { border-color: var(--ink); background: var(--sheet-sunk); }
373
+ .tree .row.hidden-node .name { color: var(--faint); text-decoration: line-through; text-decoration-color: var(--faint); }
374
+ .tree .row.ghosted-node .name { color: var(--muted); }
375
+
376
+ .structure .footer {
377
+ display: flex;
378
+ flex-direction: column;
379
+ gap: 8px;
380
+ padding-top: 10px;
381
+ border-top: var(--line) solid var(--rule);
382
+ }
383
+ .structure .footer .counts { display: flex; flex-direction: column; gap: 2px; font-size: var(--text-s); font-weight: 500; }
384
+ .structure .footer .buttons { display: flex; gap: 8px; }
385
+
386
+ .structure .nothing { margin: 0; color: var(--muted); font-size: var(--text-s); }
387
+
388
+ /* The Library: the switcher and the two ways in, then folders and recent files as tree rows. */
389
+ .library .header { display: flex; align-items: center; gap: 8px; }
390
+ .library .header .grow { flex: 1; }
391
+ .library .row.heading .name { font-weight: 600; }
392
+ .library .row.message { color: var(--muted); font-size: var(--text-s); }
393
+ .library .row.missing .name { color: var(--faint); }
394
+ .library .row .pill.small { height: 22px; padding: 0 8px; margin: 0 4px 0 8px; font-size: var(--text-xs); }
395
+ body.phone .library .actions { display: inline-flex; }
396
+ body.phone .library .actions ~ .count { display: none; }
397
+
398
+ /* ---- The selection card ----------------------------------------------------------------- */
399
+
400
+ .selection {
401
+ right: 18px;
402
+ top: 172px;
403
+ width: 286px;
404
+ padding: 10.5px 12.5px 12.5px;
405
+ display: flex;
406
+ flex-direction: column;
407
+ gap: 12px;
408
+ }
409
+ .selection .kind { font-size: var(--text-xs); font-weight: 500; color: var(--muted); letter-spacing: 0.04em; }
410
+ .selection .name { margin-top: 3px; font-size: var(--text-l); font-weight: 600; overflow-wrap: anywhere; }
411
+ .selection .path { margin-top: 3px; font-size: var(--text-xs); color: var(--muted); overflow-wrap: anywhere; }
412
+ .selection hr { width: 100%; margin: 0; border: none; border-top: var(--line) solid var(--ink); }
413
+ .selection .stats { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
414
+ .selection .number { font-size: 26px; font-weight: 600; line-height: 1.1; }
415
+ .selection .unit { font-size: var(--text-s); font-weight: 500; color: var(--ink-soft); }
416
+ .selection .actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
417
+ .selection .pill { width: 100%; justify-content: flex-start; }
418
+
419
+ /* ---- The toolbar and its cards ---------------------------------------------------------- */
420
+
421
+ .toolbar {
422
+ left: calc(318px + (100% - 318px) / 2);
423
+ bottom: 22px;
424
+ transform: translateX(-50%);
425
+ display: flex;
426
+ align-items: center;
427
+ gap: 8px;
428
+ padding: 6.5px 8.5px;
429
+ max-width: calc(100% - 336px);
430
+ overflow: hidden;
431
+ }
432
+ /* Tools keep their size; those that don't fit fold into More (toolbar-fold.js). */
433
+ .toolbar > * { flex: none; }
434
+ .toolbar > .folded { display: none !important; }
435
+ .toolbar .rule { flex: none; width: var(--line); height: 22px; background: var(--rule); }
436
+
437
+ .menu {
438
+ position: fixed;
439
+ display: flex;
440
+ flex-direction: column;
441
+ padding: 6px;
442
+ background: var(--sheet);
443
+ border: var(--line) solid var(--ink);
444
+ border-radius: var(--radius-chip);
445
+ box-shadow: var(--shadow-chip) var(--shadow-chip) 0 var(--ink);
446
+ z-index: 5;
447
+ }
448
+ .menu button { display: flex; align-items: center; gap: 8px; border: none; background: transparent; padding: 6px 12px; border-radius: 6px; text-align: left; font-size: var(--text-s); }
449
+ .menu button:hover:not(:disabled) { background: var(--cream); }
450
+ .menu button:disabled { opacity: 0.4; }
451
+ .menu .icon, .menu .check { display: inline-flex; flex: none; width: 15px; justify-content: center; }
452
+ .menu.views .icon, .menu.views .check { display: none; }
453
+ .menu button > span:nth-child(2) { flex: 1; padding-right: 12px; }
454
+ .menu .heading { padding: 8px 12px 4px; font-size: var(--text-xs); font-weight: 500; color: var(--muted); letter-spacing: 0.04em; }
455
+
456
+ .popover {
457
+ left: calc(318px + (100% - 318px) / 2);
458
+ bottom: 80px;
459
+ transform: translateX(-50%);
460
+ padding: 12.5px 14.5px 14.5px;
461
+ display: flex;
462
+ flex-direction: column;
463
+ gap: 10px;
464
+ max-width: calc(100% - 24px);
465
+ }
466
+ .popover .row { display: flex; align-items: center; gap: 8px; }
467
+ .look .label { width: 92px; flex: none; font-size: var(--text-s); font-weight: 500; }
468
+ .look .label.disabled { color: var(--faint); }
469
+ .look .note { margin: -4px 0 0 100px; font-size: var(--text-xs); color: var(--muted); }
470
+ .section .row { gap: 10px; }
471
+ .section .axis { width: 14px; font-size: var(--text-s); font-weight: 600; }
472
+ .popover.section { width: 340px; }
473
+ .section .slider { width: 160px; }
474
+
475
+ /* ---- Overlays --------------------------------------------------------------------------- */
476
+
477
+ .status {
478
+ left: 50%;
479
+ top: 50%;
480
+ transform: translate(-50%, -50%);
481
+ width: 360px;
482
+ max-width: calc(100% - 32px);
483
+ padding: 18.5px;
484
+ display: flex;
485
+ flex-direction: column;
486
+ align-items: flex-start;
487
+ gap: 8px;
488
+ }
489
+ .status h2 { margin: 0; font-size: var(--text-heading); font-weight: 600; color: var(--ink); letter-spacing: 0; }
490
+ .status p { margin: 0; color: var(--muted); font-size: var(--text-m); overflow-wrap: anywhere; }
491
+ .status .buttons { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
492
+ .status .samples { align-self: stretch; display: flex; flex-direction: column; margin-top: 10px; padding-top: 12px; border-top: var(--line) solid var(--rule); }
493
+ .status .samples .heading { margin: 0 0 4px; font-size: var(--text-xs); font-weight: 500; color: var(--muted); letter-spacing: 0.04em; }
494
+ .status .sample { display: flex; align-items: baseline; gap: 12px; margin: 0 -8px; padding: 6px 8px; border: none; border-radius: 6px; background: transparent; text-align: left; font-size: var(--text-s); color: var(--ink); }
495
+ .status .sample:hover { background: var(--cream); }
496
+ .status .sample .name { flex: 1; }
497
+ .status .sample .size { font-size: var(--text-xs); color: var(--muted); }
498
+
499
+ .hint.hiding { background: var(--tomato-wash); }
500
+ .hint {
501
+ position: fixed;
502
+ left: calc(318px + (100% - 318px) / 2);
503
+ bottom: 86px;
504
+ transform: translateX(-50%);
505
+ z-index: 3;
506
+ }
507
+ .hover {
508
+ position: fixed;
509
+ z-index: 4;
510
+ pointer-events: none;
511
+ }
512
+ .hover .name { font-weight: 500; }
513
+ .hover .face { color: var(--muted); }
514
+
515
+ .dropzone {
516
+ position: fixed;
517
+ inset: 14px;
518
+ display: flex;
519
+ align-items: center;
520
+ justify-content: center;
521
+ border-radius: 18px;
522
+ background: rgba(255, 244, 214, 0.7) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='1' rx='17' style='width:calc(100%25 - 2px);height:calc(100%25 - 2px)' fill='none' stroke='%23231e16' stroke-width='2' stroke-dasharray='8 6'/%3E%3C/svg%3E");
523
+ z-index: 6;
524
+ pointer-events: none;
525
+ }
526
+ .dropzone .words { font-size: var(--text-l); font-weight: 600; }
527
+
528
+ .sheet {
529
+ position: fixed;
530
+ inset: 0;
531
+ display: flex;
532
+ align-items: center;
533
+ justify-content: center;
534
+ background: rgba(242, 239, 231, 0.55);
535
+ z-index: 7;
536
+ }
537
+ .sheet .card { position: relative; padding: 16.5px 18.5px; }
538
+ .sheet h2 { font-size: var(--text-l); font-weight: 600; color: var(--ink); margin-bottom: 12px; letter-spacing: 0; }
539
+ .sheet .grid { display: grid; grid-template-columns: auto auto; gap: 8px 12px; align-items: center; font-size: var(--text-s); }
540
+ .sheet .keys { display: flex; gap: 4px; justify-content: flex-end; }
541
+
542
+ /* ---- Phones: the tree and cards give way to the view ------------------------------------ */
543
+
544
+ .phone-only { display: none !important; }
545
+ body.phone .phone-only { display: inline-flex !important; }
546
+ body.phone .desktop-only { display: none !important; }
547
+
548
+ body.phone .structure { left: 8px; right: 8px; top: auto; bottom: calc(76px + env(safe-area-inset-bottom)); width: auto; height: 45%; }
549
+ body.phone .selection { right: 8px; left: 8px; top: calc(56px + env(safe-area-inset-top)); width: auto; }
550
+ body.phone .toolbar { left: 50%; bottom: calc(10px + env(safe-area-inset-bottom)); max-width: calc(100% - 16px); }
551
+ body.phone .popover, body.phone .hint { left: 50%; bottom: calc(70px + env(safe-area-inset-bottom)); }
552
+ body.phone .masthead { top: calc(10px + env(safe-area-inset-top)); left: 16px; max-width: 70%; }
553
+ body.phone .look .label { width: 70px; }
554
+ body.phone .look .note { margin-left: 78px; }
555
+
556
+ @media (max-width: 900px) {
557
+ .toolbar, .popover, .hint { left: 50%; max-width: calc(100% - 16px); }
558
+ }