@ingcreators/annot-core 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.
Files changed (65) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +201 -0
  3. package/README.md +56 -0
  4. package/dist/auto-capture-options.d.ts +78 -0
  5. package/dist/editor/arrow-markers.d.ts +142 -0
  6. package/dist/editor/bake-translate.d.ts +192 -0
  7. package/dist/editor/font-registry.d.ts +58 -0
  8. package/dist/editor/gradient-utils.d.ts +23 -0
  9. package/dist/editor/history-core.d.ts +48 -0
  10. package/dist/editor/icons/brand-icons.d.ts +53 -0
  11. package/dist/editor/icons/material-symbols.d.ts +105 -0
  12. package/dist/editor/icons/registry.d.ts +179 -0
  13. package/dist/editor/icons/render.d.ts +22 -0
  14. package/dist/editor/icons/sanitize.d.ts +60 -0
  15. package/dist/editor/index.d.ts +13 -0
  16. package/dist/editor/path-utils.d.ts +32 -0
  17. package/dist/editor/property-schema.d.ts +263 -0
  18. package/dist/editor/rich-text-mapper.d.ts +8 -0
  19. package/dist/editor/selection-geometry.d.ts +66 -0
  20. package/dist/editor/shape-utils.d.ts +27 -0
  21. package/dist/editor/svg-format.d.ts +68 -0
  22. package/dist/editor/svg-id-utils.d.ts +37 -0
  23. package/dist/editor/svg-to-annotation-shapes.d.ts +34 -0
  24. package/dist/editor/text-utils.d.ts +212 -0
  25. package/dist/editor/tool-lifecycle.d.ts +56 -0
  26. package/dist/editor/tool-options.d.ts +126 -0
  27. package/dist/editor/tool-panel-adapter.d.ts +105 -0
  28. package/dist/editor/tool-preset-serde.d.ts +43 -0
  29. package/dist/editor/tool-registry.d.ts +320 -0
  30. package/dist/editor/tool-style-reader.d.ts +36 -0
  31. package/dist/editor/tool-style-writer.d.ts +33 -0
  32. package/dist/editor/toolbar-icons.d.ts +84 -0
  33. package/dist/editor/transform-utils.d.ts +127 -0
  34. package/dist/editor/viewport-math.d.ts +69 -0
  35. package/dist/encode/index.d.ts +4 -0
  36. package/dist/encode/options.d.ts +79 -0
  37. package/dist/encode/png8.d.ts +10 -0
  38. package/dist/headless.d.ts +29 -0
  39. package/dist/icons/index.d.ts +15 -0
  40. package/dist/icons/types.d.ts +108 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.js +3164 -0
  43. package/dist/storage/errors.d.ts +59 -0
  44. package/dist/storage/index.d.ts +6 -0
  45. package/dist/storage/metadata-cache.d.ts +231 -0
  46. package/dist/storage/path.d.ts +49 -0
  47. package/dist/storage/thumbnail-cache.d.ts +110 -0
  48. package/dist/storage/thumbnail.d.ts +31 -0
  49. package/dist/storage/types.d.ts +677 -0
  50. package/dist/utils/assert.d.ts +31 -0
  51. package/dist/utils/constants.d.ts +10 -0
  52. package/dist/utils/dash-utils.d.ts +6 -0
  53. package/dist/utils/desktop-bridge.d.ts +349 -0
  54. package/dist/utils/filename.d.ts +48 -0
  55. package/dist/utils/id.d.ts +21 -0
  56. package/dist/utils/index.d.ts +5 -0
  57. package/dist/utils/types.d.ts +20 -0
  58. package/dist/xmp/xmp-browser.d.ts +39 -0
  59. package/dist/zip/zip-builder.d.ts +8 -0
  60. package/dist/zip/zip-bytes.d.ts +22 -0
  61. package/package.json +58 -0
  62. package/styles/editor.css +1912 -0
  63. package/styles/fonts.css +46 -0
  64. package/styles/property-panel.css +779 -0
  65. package/styles/toolbar.css +673 -0
@@ -0,0 +1,1912 @@
1
+ /* ingcreators brand theme — design tokens.
2
+ *
3
+ * The two `:root` blocks below define the dark (default) and light
4
+ * palettes. Tokens are grouped into five semantic sections in the
5
+ * SAME ORDER in both blocks so a side-by-side diff between palettes
6
+ * stays readable. Adding a new token? Pick the section that matches
7
+ * its USE, not its colour family. Adding a new section? Reflect it
8
+ * in `docs/design-system.md` and in `ThemeTokenName` in
9
+ * `packages/editor/src/theme-overrides.ts` (the symmetry test in
10
+ * `theme-overrides.test.ts` fails the build if you forget).
11
+ *
12
+ * Contrast targets (WCAG 2.1 AA):
13
+ * text-primary on bg-primary: ≥ 7:1 (AAA)
14
+ * text-secondary on bg-primary: ≥ 4.5:1 (AA)
15
+ * text-muted on bg-primary / bg-secondary: ≥ 4.5:1 (AA)
16
+ * accent (interactive) on bg-primary: ≥ 4.5:1
17
+ * borders: ≥ 3:1 for non-text UI
18
+ * focus ring: ≥ 3:1 and ≥ 2px thick
19
+ *
20
+ * See `docs/design-system.md` for the full token reference and
21
+ * `docs/plans/design-system-foundations.md` for the roadmap (the
22
+ * `--annot-*` Phase 2 namespace migration is what these grouping
23
+ * comments anticipate).
24
+ */
25
+
26
+ /* Dark theme (default) ----------------------------------------- */
27
+ :root {
28
+ /* === Surface — page / panel backdrops + chromeless decoration === */
29
+ --annot-bg-primary: #0f1730;
30
+ --annot-bg-secondary: #0b1020;
31
+ --annot-bg-panel: #121933;
32
+ --annot-bg-panel-deep: #0f1730;
33
+ --annot-border-color: rgba(255, 255, 255, 0.22); /* bumped from 0.08 for ≥3:1 on interactive */
34
+ --annot-border-subtle: rgba(255, 255, 255, 0.08); /* keeps old value for decorative dividers */
35
+ --annot-shadow: 0 20px 50px rgba(0, 0, 0, 0.28);
36
+
37
+ /* === Content — foreground text + reading primitives === */
38
+ --annot-text-primary: #eef2ff; /* ~18:1 on bg-primary (AAA) */
39
+ --annot-text-secondary: #b7c0e0; /* ~10:1 on bg-primary (AAA) */
40
+ --annot-text-muted: #9097b8; /* ~6.2:1 on bg-primary (AA) — was 5.3:1 */
41
+ --annot-preview-line: #b7c0e0;
42
+
43
+ /* === Accent — brand colour, active highlight, focus indicator === */
44
+ --annot-accent: #7c9cff; /* ~7.5:1 on bg-primary (AAA) */
45
+ --annot-accent-2: #7ef0c5;
46
+ --annot-accent-bg: rgba(124, 156, 255, 0.18);
47
+ --annot-accent-hover: rgba(124, 156, 255, 0.26);
48
+ --annot-active-bg: rgba(124, 156, 255, 0.18);
49
+ --annot-active-border: #7c9cff;
50
+ --annot-chip-bg: rgba(124, 156, 255, 0.14);
51
+ --annot-focus-ring: #7ef0c5; /* accent-2 green contrasts against blue accent for clarity */
52
+
53
+ /* === Interaction — pointer + form states === */
54
+ --annot-hover-bg: rgba(255, 255, 255, 0.08);
55
+ --annot-hover-border: rgba(255, 255, 255, 0.22);
56
+ --annot-choice-bg: transparent;
57
+ --annot-choice-hover: rgba(124, 156, 255, 0.1);
58
+ --annot-choice-active: rgba(124, 156, 255, 0.18);
59
+ --annot-input-bg: rgba(255, 255, 255, 0.06);
60
+ --annot-input-border: rgba(255, 255, 255, 0.22); /* ≥3:1 for form borders */
61
+
62
+ /* === Canvas — editor backdrop + transparency-grid checkerboard === */
63
+ --annot-canvas-bg: #0b1020;
64
+ --annot-canvas-check: #121933;
65
+ }
66
+
67
+ /* Light theme -------------------------------------------------- */
68
+ :root.light {
69
+ /* === Surface === */
70
+ --annot-bg-primary: #f5f7ff;
71
+ --annot-bg-secondary: #ffffff;
72
+ --annot-bg-panel: #ffffff;
73
+ --annot-bg-panel-deep: #fafbff;
74
+ --annot-border-color: #bfc6de; /* ~3:1 against bg-primary for visible UI boundaries */
75
+ --annot-border-subtle: #d8ddef; /* fainter dividers */
76
+ --annot-shadow: rgba(0, 0, 0, 0.08);
77
+
78
+ /* === Content === */
79
+ --annot-text-primary: #0b1020; /* ~16:1 on bg-primary (AAA) */
80
+ --annot-text-secondary: #3a4568; /* ~8:1 on bg-primary (AAA) — was #4a5580 (~6:1) */
81
+ --annot-text-muted: #616984; /* ~4.6:1 on bg-primary (AA) — was #8890b0 (~3.2:1 FAIL) */
82
+ --annot-preview-line: #2a3050;
83
+
84
+ /* === Accent === */
85
+ --annot-accent: #3456c9; /* ~6.3:1 on bg-primary (AA+) — was #5b7ee5 (~4.2:1 marginal) */
86
+ --annot-accent-2: #188a5c; /* ~5.2:1 for secondary accent */
87
+ --annot-accent-bg: #edf1ff;
88
+ --annot-accent-hover: #dce4ff;
89
+ --annot-active-bg: #dce4ff;
90
+ --annot-active-border: #3456c9;
91
+ --annot-chip-bg: #edf1ff;
92
+ --annot-focus-ring: #3456c9;
93
+
94
+ /* === Interaction === */
95
+ --annot-hover-bg: #eef1fa;
96
+ --annot-hover-border: #8893b8;
97
+ --annot-choice-bg: transparent;
98
+ --annot-choice-hover: #eef1fa;
99
+ --annot-choice-active: #dce4ff;
100
+ --annot-input-bg: #ffffff;
101
+ --annot-input-border: #8893b8;
102
+
103
+ /* === Canvas === */
104
+ --annot-canvas-bg: #e8ecf5;
105
+ --annot-canvas-check: #dde2ef;
106
+ }
107
+
108
+ * {
109
+ margin: 0;
110
+ padding: 0;
111
+ box-sizing: border-box;
112
+ }
113
+
114
+ html,
115
+ body {
116
+ width: 100%;
117
+ height: 100%;
118
+ overflow: hidden;
119
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
120
+ background: var(--annot-bg-primary, #2a2a2a);
121
+ color: var(--annot-text-primary, #e0e0e0);
122
+ }
123
+
124
+ #canvas-container {
125
+ position: absolute;
126
+ top: 48px;
127
+ left: 0;
128
+ right: 0;
129
+ bottom: 28px;
130
+ overflow: auto;
131
+ background: var(--annot-canvas-bg);
132
+ background-image:
133
+ linear-gradient(45deg, var(--annot-canvas-check) 25%, transparent 25%),
134
+ linear-gradient(-45deg, var(--annot-canvas-check) 25%, transparent 25%),
135
+ linear-gradient(45deg, transparent 75%, var(--annot-canvas-check) 75%),
136
+ linear-gradient(-45deg, transparent 75%, var(--annot-canvas-check) 75%);
137
+ background-size: 20px 20px;
138
+ background-position:
139
+ 0 0,
140
+ 0 10px,
141
+ 10px -10px,
142
+ -10px 0;
143
+ }
144
+
145
+ /* Editor SVG root.
146
+ *
147
+ * Two selectors — id `#svg-root` and attribute `[data-annot-shell-root]` —
148
+ * resolve to the same SVG element across hosts. The PWA + Tauri desktop
149
+ * ship a pre-baked `<svg id="svg-root">` in their index.html so first-
150
+ * render CSS hits the styled element before JS boots; the VSCode webview
151
+ * (and any future host that doesn't pre-bake the SVG) lets `EditorShell`
152
+ * create an anonymous `<svg data-annot-shell-root="1">` inside its
153
+ * container. Both surfaces match the same rules so editor styling stays
154
+ * consistent regardless of how the SVG was provisioned. */
155
+ #svg-root,
156
+ [data-annot-shell-root] {
157
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
158
+ background: #fff;
159
+ cursor: default;
160
+ display: block;
161
+ margin: 20px auto;
162
+ /* SVG `<text>` is selectable by default. During a drag — especially
163
+ when smart-guide overlays appear and the cursor crosses an
164
+ annotation's text — the browser would otherwise interpret the
165
+ pointer travel as a text-selection range and paint the blue
166
+ highlight on the just-touched run. The contentEditable text-edit
167
+ overlay opts back in via its own inline `user-select: text`. */
168
+ user-select: none;
169
+ -webkit-user-select: none;
170
+ }
171
+
172
+ /* The contentEditable overlay lives inside a `<foreignObject>` —
173
+ bring text selection back so the user can mark / replace runs
174
+ while editing. */
175
+ #svg-root foreignObject [contenteditable="true"],
176
+ [data-annot-shell-root] foreignObject [contenteditable="true"] {
177
+ user-select: text;
178
+ -webkit-user-select: text;
179
+ }
180
+
181
+ #statusbar {
182
+ position: absolute;
183
+ bottom: 0;
184
+ left: 0;
185
+ right: 0;
186
+ height: 28px;
187
+ background: var(--annot-bg-panel, #1e1e1e);
188
+ border-top: 1px solid var(--annot-border-color, #333);
189
+ display: flex;
190
+ align-items: center;
191
+ padding: 0 12px;
192
+ gap: 16px;
193
+ font-size: 12px;
194
+ color: #888;
195
+ }
196
+
197
+ /* Save toast notification */
198
+ .save-toast {
199
+ position: fixed;
200
+ bottom: 48px;
201
+ left: 50%;
202
+ transform: translateX(-50%) translateY(20px);
203
+ background: var(--annot-accent, #00d4ff);
204
+ color: #000;
205
+ padding: 6px 24px;
206
+ border-radius: 20px;
207
+ font-size: 13px;
208
+ font-weight: 600;
209
+ opacity: 0;
210
+ transition:
211
+ opacity 0.3s,
212
+ transform 0.3s;
213
+ z-index: 1000;
214
+ pointer-events: none;
215
+ }
216
+
217
+ .save-toast.show {
218
+ opacity: 1;
219
+ transform: translateX(-50%) translateY(0);
220
+ }
221
+
222
+ /* ---- Tag editor ---- */
223
+
224
+ .tag-editor {
225
+ display: flex;
226
+ align-items: center;
227
+ gap: 4px;
228
+ }
229
+
230
+ .tag-chips {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 3px;
234
+ flex-wrap: wrap;
235
+ }
236
+
237
+ .tag-chip {
238
+ display: inline-flex;
239
+ align-items: center;
240
+ gap: 2px;
241
+ padding: 1px 6px;
242
+ border-radius: 10px;
243
+ background: var(--annot-chip-bg, #16213e);
244
+ color: var(--annot-text-primary);
245
+ font-size: 11px;
246
+ white-space: nowrap;
247
+ border: 1px solid var(--annot-border-color);
248
+ }
249
+
250
+ .tag-chip-label {
251
+ pointer-events: none;
252
+ }
253
+
254
+ .tag-chip-delete {
255
+ border: none;
256
+ background: none;
257
+ color: var(--annot-text-muted);
258
+ font-size: 12px;
259
+ cursor: pointer;
260
+ padding: 0 1px;
261
+ line-height: 1;
262
+ }
263
+
264
+ .tag-chip-delete:hover {
265
+ color: #e44;
266
+ }
267
+
268
+ .tag-add-btn {
269
+ border: 1px dashed var(--annot-border-color);
270
+ background: transparent;
271
+ color: var(--annot-text-muted);
272
+ font-size: 11px;
273
+ padding: 1px 8px;
274
+ border-radius: 10px;
275
+ cursor: pointer;
276
+ white-space: nowrap;
277
+ }
278
+
279
+ .tag-add-btn:hover {
280
+ border-color: var(--annot-accent);
281
+ color: var(--annot-accent);
282
+ }
283
+
284
+ .tag-input-row {
285
+ position: absolute;
286
+ bottom: 100%;
287
+ left: 0;
288
+ margin-bottom: 4px;
289
+ display: flex;
290
+ gap: 4px;
291
+ background: var(--annot-bg-panel-deep, #1e1e2e);
292
+ border: 1px solid var(--annot-border-color);
293
+ border-radius: 6px;
294
+ padding: 6px 8px;
295
+ box-shadow: 0 -4px 12px var(--annot-shadow);
296
+ z-index: 200;
297
+ }
298
+
299
+ .tag-input {
300
+ height: 24px;
301
+ border: 1px solid var(--annot-input-border, #444);
302
+ border-radius: 4px;
303
+ background: var(--annot-input-bg, #2a2a2a);
304
+ color: var(--annot-text-primary);
305
+ font-size: 12px;
306
+ padding: 0 6px;
307
+ outline: none;
308
+ }
309
+
310
+ .tag-input-key {
311
+ width: 80px;
312
+ }
313
+
314
+ .tag-input-value {
315
+ width: 120px;
316
+ }
317
+
318
+ .tag-input:focus {
319
+ border-color: var(--annot-accent);
320
+ }
321
+
322
+ .tag-ok-btn,
323
+ .tag-cancel-btn {
324
+ width: 24px;
325
+ height: 24px;
326
+ border: none;
327
+ border-radius: 4px;
328
+ cursor: pointer;
329
+ font-size: 14px;
330
+ display: flex;
331
+ align-items: center;
332
+ justify-content: center;
333
+ }
334
+
335
+ .tag-ok-btn {
336
+ background: var(--annot-accent);
337
+ color: #000;
338
+ }
339
+
340
+ .tag-ok-btn:hover {
341
+ opacity: 0.8;
342
+ }
343
+
344
+ .tag-cancel-btn {
345
+ background: transparent;
346
+ color: var(--annot-text-muted);
347
+ }
348
+
349
+ .tag-cancel-btn:hover {
350
+ color: #e44;
351
+ }
352
+
353
+ /* Tag chips in gallery cards */
354
+ .gallery-tag-chips {
355
+ display: flex;
356
+ flex-wrap: wrap;
357
+ gap: 2px;
358
+ margin-top: 3px;
359
+ }
360
+
361
+ .gallery-tag {
362
+ font-size: 10px;
363
+ padding: 0 5px;
364
+ border-radius: 8px;
365
+ background: var(--annot-chip-bg, #16213e);
366
+ color: var(--annot-text-secondary);
367
+ border: 1px solid var(--annot-border-color);
368
+ white-space: nowrap;
369
+ }
370
+
371
+ #statusbar span {
372
+ white-space: nowrap;
373
+ }
374
+
375
+ /* Zoom controls */
376
+ #zoom-controls {
377
+ display: flex;
378
+ align-items: center;
379
+ gap: 2px;
380
+ background: var(--annot-input-bg, #2a2a2a);
381
+ border: 1px solid var(--annot-border-color, #444);
382
+ border-radius: 4px;
383
+ padding: 0 2px;
384
+ }
385
+
386
+ .zoom-btn {
387
+ width: 22px;
388
+ height: 22px;
389
+ border: none;
390
+ background: transparent;
391
+ color: var(--annot-text-secondary, #888);
392
+ font-size: 16px;
393
+ cursor: pointer;
394
+ display: flex;
395
+ align-items: center;
396
+ justify-content: center;
397
+ border-radius: 3px;
398
+ }
399
+
400
+ .zoom-btn:hover {
401
+ background: var(--annot-hover-bg, #333);
402
+ color: var(--annot-accent, #00d4ff);
403
+ }
404
+
405
+ .zoom-label {
406
+ min-width: 42px;
407
+ height: 22px;
408
+ border: none;
409
+ background: transparent;
410
+ color: var(--annot-text-primary, #ccc);
411
+ font-size: 11px;
412
+ cursor: pointer;
413
+ text-align: center;
414
+ padding: 0 4px;
415
+ }
416
+
417
+ .zoom-label:hover {
418
+ color: var(--annot-accent, #00d4ff);
419
+ }
420
+
421
+ .zoom-select-wrap {
422
+ position: relative;
423
+ }
424
+
425
+ .zoom-menu {
426
+ position: absolute;
427
+ bottom: 100%;
428
+ left: 50%;
429
+ transform: translateX(-50%);
430
+ margin-bottom: 4px;
431
+ background: var(--annot-bg-panel-deep, #1e1e2e);
432
+ border: 1px solid var(--annot-border-color, #444);
433
+ border-radius: 6px;
434
+ overflow: hidden;
435
+ box-shadow: 0 -4px 16px var(--annot-shadow, rgba(0, 0, 0, 0.4));
436
+ z-index: 200;
437
+ min-width: 90px;
438
+ }
439
+
440
+ .zoom-menu-item {
441
+ display: block;
442
+ width: 100%;
443
+ padding: 6px 14px;
444
+ border: none;
445
+ background: transparent;
446
+ color: var(--annot-text-primary, #ccc);
447
+ font-size: 12px;
448
+ text-align: left;
449
+ cursor: pointer;
450
+ white-space: nowrap;
451
+ }
452
+
453
+ .zoom-menu-item:hover {
454
+ background: var(--annot-choice-hover, #2a2a40);
455
+ color: var(--annot-accent, #00d4ff);
456
+ }
457
+
458
+ .zoom-menu-item.active {
459
+ color: var(--annot-accent, #00d4ff);
460
+ font-weight: bold;
461
+ }
462
+
463
+ .zoom-menu-sep {
464
+ height: 1px;
465
+ background: var(--annot-border-color, #444);
466
+ margin: 2px 0;
467
+ }
468
+
469
+ /* ---- Gallery toolbar ---- */
470
+
471
+ .gallery-toolbar-title {
472
+ font-size: 16px;
473
+ font-weight: 700;
474
+ color: var(--annot-accent);
475
+ padding: 0 4px;
476
+ white-space: nowrap;
477
+ }
478
+
479
+ .gallery-toolbar-search {
480
+ flex: 1;
481
+ max-width: 360px;
482
+ height: 32px;
483
+ padding: 0 10px;
484
+ border: 1px solid var(--annot-border-color);
485
+ border-radius: 6px;
486
+ background: var(--annot-input-bg);
487
+ color: var(--annot-text-primary);
488
+ font-size: 13px;
489
+ outline: none;
490
+ }
491
+
492
+ .gallery-toolbar-search:focus {
493
+ border-color: var(--annot-accent);
494
+ }
495
+
496
+ /* ---- File details drawer ----
497
+ *
498
+ * Right-side slide-in panel that consolidates file metadata and full tag
499
+ * management. Non-modal (canvas stays interactive outside the drawer) and
500
+ * pinned below editor-header + toolbar and above the statusbar.
501
+ */
502
+ .file-details-backdrop {
503
+ position: absolute;
504
+ top: 48px;
505
+ right: 0;
506
+ bottom: 28px;
507
+ left: 0;
508
+ background: rgba(0, 0, 0, 0);
509
+ pointer-events: none;
510
+ transition: background 0.2s ease-out;
511
+ z-index: 19;
512
+ }
513
+ .file-details-backdrop.open {
514
+ background: rgba(0, 0, 0, 0.2);
515
+ pointer-events: auto;
516
+ }
517
+
518
+ .file-details-drawer {
519
+ position: absolute;
520
+ top: 48px;
521
+ right: 0;
522
+ bottom: 28px;
523
+ width: 360px;
524
+ max-width: 80vw;
525
+ background: var(--annot-bg-panel);
526
+ border-left: 1px solid var(--annot-border-color);
527
+ box-shadow: -4px 0 28px rgba(0, 0, 0, 0.35);
528
+ transform: translateX(calc(100% + 28px)); /* include shadow in off-screen */
529
+ transition: transform 0.22s ease-out;
530
+ z-index: 20;
531
+ overflow-y: auto;
532
+ display: flex;
533
+ flex-direction: column;
534
+ }
535
+ .file-details-drawer.open {
536
+ transform: translateX(0);
537
+ }
538
+
539
+ .file-details-header {
540
+ display: flex;
541
+ align-items: center;
542
+ justify-content: space-between;
543
+ padding: 14px 18px;
544
+ border-bottom: 1px solid var(--annot-border-color);
545
+ flex-shrink: 0;
546
+ }
547
+ .file-details-title {
548
+ margin: 0;
549
+ font-size: 15px;
550
+ font-weight: 700;
551
+ letter-spacing: -0.02em;
552
+ color: var(--annot-text-primary);
553
+ }
554
+ .file-details-close {
555
+ width: 32px;
556
+ height: 32px;
557
+ border: none;
558
+ background: transparent;
559
+ color: var(--annot-text-secondary);
560
+ border-radius: 6px;
561
+ cursor: pointer;
562
+ font-size: 20px;
563
+ display: flex;
564
+ align-items: center;
565
+ justify-content: center;
566
+ }
567
+ .file-details-close:hover {
568
+ background: var(--annot-hover-bg);
569
+ color: var(--annot-text-primary);
570
+ }
571
+
572
+ .file-details-section {
573
+ padding: 14px 18px;
574
+ border-bottom: 1px solid var(--annot-border-subtle);
575
+ }
576
+ .file-details-section:last-child {
577
+ border-bottom: none;
578
+ }
579
+ .file-details-section-title {
580
+ margin: 0 0 10px;
581
+ font-size: 11px;
582
+ font-weight: 700;
583
+ text-transform: uppercase;
584
+ letter-spacing: 0.08em;
585
+ color: var(--annot-text-muted);
586
+ }
587
+
588
+ .file-details-row {
589
+ display: grid;
590
+ grid-template-columns: 92px 1fr;
591
+ gap: 12px;
592
+ padding: 5px 0;
593
+ font-size: 13px;
594
+ line-height: 1.45;
595
+ }
596
+ .file-details-row-label {
597
+ color: var(--annot-text-muted);
598
+ }
599
+ .file-details-row-value {
600
+ color: var(--annot-text-primary);
601
+ word-break: break-word;
602
+ min-width: 0;
603
+ }
604
+ .file-details-row-value.mono {
605
+ font-family:
606
+ SF Mono,
607
+ Menlo,
608
+ Consolas,
609
+ monospace;
610
+ font-size: 12px;
611
+ }
612
+ .file-details-row-value.selectable {
613
+ user-select: text;
614
+ }
615
+ a.file-details-row-value {
616
+ color: var(--annot-accent);
617
+ text-decoration: none;
618
+ }
619
+ a.file-details-row-value:hover {
620
+ text-decoration: underline;
621
+ }
622
+
623
+ .file-details-tags-editor {
624
+ /* TagEditor renders its own layout; this wrapper just sizes it. */
625
+ min-height: 32px;
626
+ }
627
+
628
+ /* Author avatar inside the Last commit section. Rendered inline with
629
+ * the author name so the row height stays consistent with the other
630
+ * metadata rows. */
631
+ .file-details-avatar {
632
+ width: 16px;
633
+ height: 16px;
634
+ border-radius: 50%;
635
+ margin-right: 6px;
636
+ vertical-align: middle;
637
+ display: inline-block;
638
+ }
639
+
640
+ /* Abbreviated commit SHA shown alongside the message. Monospace so it
641
+ * reads as a git ref rather than prose. */
642
+ .file-details-sha {
643
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
644
+ font-size: 11px;
645
+ color: var(--annot-text-secondary);
646
+ padding: 1px 4px;
647
+ border-radius: 3px;
648
+ background: var(--annot-input-bg, rgba(255, 255, 255, 0.04));
649
+ }
650
+
651
+ /* External-link section ("View on GitHub", etc). One link per row,
652
+ * icon + label, styled like a prominent action rather than a value. */
653
+ .file-details-link-row {
654
+ display: block;
655
+ }
656
+ .file-details-external-link,
657
+ .file-details-external-link:visited {
658
+ display: inline-flex;
659
+ align-items: center;
660
+ gap: 6px;
661
+ color: var(--annot-accent);
662
+ text-decoration: none;
663
+ font-size: 13px;
664
+ padding: 4px 0;
665
+ }
666
+ .file-details-external-link:hover {
667
+ text-decoration: underline;
668
+ }
669
+ .file-details-external-link annot-icon {
670
+ font-size: 16px;
671
+ }
672
+
673
+ /* Anchors inside a row value (e.g. the commit-message link in the
674
+ * Last commit section). Override the browser's default :visited
675
+ * color — on a dark panel it drops to a near-black purple that the
676
+ * user reported as unreadable. Keeping the accent color across
677
+ * states matches how `a.file-details-row-value` handles the
678
+ * top-level Source link. */
679
+ .file-details-row-value a,
680
+ .file-details-row-value a:visited {
681
+ color: var(--annot-accent);
682
+ text-decoration: underline;
683
+ }
684
+ .file-details-row-value a:hover {
685
+ opacity: 0.85;
686
+ }
687
+
688
+ /* Inline rename affordance on the Name row. The input occupies the
689
+ * same visual slot as a read-only span value — NO border or padding
690
+ * offset — so the text baseline and left edge line up perfectly with
691
+ * the "Location", "Dimensions", etc. rows below. Only a subtle box-
692
+ * shadow hover / focus state hints that the field is editable.
693
+ * Matches the Finder / macOS settings-panel pattern where values
694
+ * inline-edit without visible field chrome. */
695
+ .file-details-name-wrap {
696
+ min-width: 0;
697
+ }
698
+ .file-details-name-input {
699
+ display: block;
700
+ width: 100%;
701
+ border: none;
702
+ outline: none;
703
+ background: transparent;
704
+ color: var(--annot-text-primary);
705
+ font: inherit;
706
+ font-size: 13px;
707
+ line-height: 1.45;
708
+ padding: 0;
709
+ margin: 0;
710
+ border-radius: 3px;
711
+ transition:
712
+ background 0.15s,
713
+ box-shadow 0.15s;
714
+ min-width: 0;
715
+ text-overflow: ellipsis;
716
+ }
717
+ .file-details-name-input:hover:not(:focus) {
718
+ background: var(--annot-hover-bg);
719
+ box-shadow: inset 0 -1px 0 var(--annot-border-subtle);
720
+ }
721
+ .file-details-name-input:focus {
722
+ background: var(--annot-input-bg, rgba(255, 255, 255, 0.04));
723
+ box-shadow: inset 0 0 0 1px var(--annot-accent);
724
+ text-overflow: clip;
725
+ }
726
+ .file-details-name-input:disabled {
727
+ opacity: 0.55;
728
+ cursor: wait;
729
+ }
730
+
731
+ .file-details-name-error {
732
+ color: var(--danger, #ff7070);
733
+ font-size: 11px;
734
+ line-height: 1.3;
735
+ margin-top: 4px;
736
+ min-height: 0; /* collapses when empty */
737
+ }
738
+ .file-details-name-error:empty {
739
+ display: none;
740
+ }
741
+
742
+ /* ---- Editor header ----
743
+ *
744
+ * #editor-header sits above #toolbar in editor mode and carries the
745
+ * per-file context (brand, breadcrumb + filename, save status, help,
746
+ * theme). It is hidden by default and revealed via body.editor-mode
747
+ * so that gallery mode keeps its single-row top (brand + search + …).
748
+ *
749
+ * When editor-header is visible, #toolbar is pushed down by 48px and
750
+ * #canvas-container by another 48px so both rows stay visible.
751
+ */
752
+ #editor-header {
753
+ display: none;
754
+ }
755
+ /* Editor header — compact single-row layout, 48px:
756
+ *
757
+ * [A] Device › test › image-1234.png ⓘ [?] [☀]
758
+ *
759
+ * Industry-standard pattern used by Google Drive, Finder, VS Code, etc.
760
+ * The filename is the final breadcrumb segment (styled bold + active),
761
+ * so path and filename share one coherent unit. Tags moved to the
762
+ * file-details drawer (opened via ⓘ), leaving the header free for
763
+ * navigation + a pair of global actions.
764
+ */
765
+ body.editor-mode #editor-header {
766
+ position: absolute;
767
+ top: 0;
768
+ left: 0;
769
+ right: 0;
770
+ height: 48px;
771
+ display: flex;
772
+ align-items: center;
773
+ padding: 0 14px;
774
+ gap: 10px;
775
+ /* One level deeper than the toolbar (--bg-panel) so the header
776
+ * reads as page-level context and the toolbar as the elevated
777
+ * "action surface". Same palette in both themes (dark: #0f1730
778
+ * vs #121933; light: #fafbff vs #ffffff) — subtle, just enough
779
+ * to establish hierarchy without adding visual noise. */
780
+ background: var(--annot-bg-panel-deep);
781
+ border-bottom: 1px solid var(--annot-border-color);
782
+ z-index: 11;
783
+ /* `overflow: visible` so the custom CSS tooltip on header buttons
784
+ * (positioned at `bottom: -40px`) can extend below the 48px header
785
+ * without being clipped. Long breadcrumb paths are already
786
+ * controlled by the `.editor-header-path` container's own
787
+ * `overflow: hidden` + per-segment `text-overflow: ellipsis`, so
788
+ * dropping the parent's clip has no effect on the breadcrumb
789
+ * layout. */
790
+ overflow: visible;
791
+ }
792
+
793
+ .editor-header-brand {
794
+ width: 36px;
795
+ height: 36px;
796
+ border: none;
797
+ background: transparent;
798
+ padding: 0;
799
+ border-radius: 8px;
800
+ cursor: pointer;
801
+ display: flex;
802
+ align-items: center;
803
+ justify-content: center;
804
+ flex-shrink: 0;
805
+ transition: background 0.15s;
806
+ }
807
+ .editor-header-brand:hover {
808
+ background: var(--annot-hover-bg);
809
+ }
810
+ .editor-header-brand:focus-visible {
811
+ outline: 2px solid var(--annot-accent);
812
+ outline-offset: 2px;
813
+ }
814
+
815
+ /* Breadcrumb as full path + filename (the last, bold, non-clickable
816
+ * "active" segment).
817
+ *
818
+ * Flex-shrink priorities (from least important → most important):
819
+ * ancestor segments (Device, test, …) : flex-shrink: 1 → shrink first
820
+ * filename : flex-shrink: 0 → stays visible
821
+ * info button : flex-shrink: 0 → stays visible
822
+ *
823
+ * Result: when space is tight, only the ancestor path segments ellipsize.
824
+ * The filename and info button remain fully visible. When space is
825
+ * abundant (as in the usual case), filename renders at natural width
826
+ * with no artificial max-width cap.
827
+ */
828
+ .editor-header-path {
829
+ flex: 0 1 auto;
830
+ min-width: 0;
831
+ overflow: hidden;
832
+ }
833
+ .editor-header-path .breadcrumb-item {
834
+ min-width: 0;
835
+ overflow: hidden;
836
+ text-overflow: ellipsis;
837
+ white-space: nowrap;
838
+ flex-shrink: 1;
839
+ }
840
+ .editor-header-path .breadcrumb-filename {
841
+ flex-shrink: 0;
842
+ font-weight: 600;
843
+ color: var(--annot-text-primary);
844
+ }
845
+ .editor-header-path .breadcrumb-filename:hover {
846
+ background: none;
847
+ color: var(--annot-text-primary);
848
+ cursor: default;
849
+ }
850
+ .editor-header-path .editor-header-info-btn {
851
+ flex-shrink: 0;
852
+ }
853
+
854
+ /* File actions cluster in the editor header right side (Open / Copy /
855
+ * Save ▼). Reuses .header-info-btn for baseline styling so these
856
+ * buttons match the help + theme toggle next to them. Grouped in their
857
+ * own container so there's subtle spacing between "file actions" and
858
+ * "global actions" (help, theme). */
859
+ .editor-header-file-actions {
860
+ display: flex;
861
+ align-items: center;
862
+ gap: 2px;
863
+ margin-right: 4px;
864
+ padding-right: 4px;
865
+ border-right: 1px solid var(--annot-border-subtle);
866
+ flex-shrink: 0;
867
+ }
868
+ /* The header Save dropdown arrow needs its own layout — the shared
869
+ * .tool-dropdown-arrow in toolbar.css is absolutely positioned below a
870
+ * tool icon (vertical-stack case), which doesn't fit horizontal-header
871
+ * placement. Reset it and let flex lay it out next to the save icon. */
872
+ .header-save-wrap {
873
+ display: inline-flex;
874
+ align-items: center;
875
+ gap: 0;
876
+ }
877
+ .header-save-wrap .tool-dropdown-arrow {
878
+ position: static;
879
+ transform: none;
880
+ width: 14px;
881
+ height: 28px;
882
+ background: transparent;
883
+ color: var(--annot-text-muted);
884
+ border: none;
885
+ font-size: 14px;
886
+ cursor: pointer;
887
+ display: flex;
888
+ align-items: center;
889
+ justify-content: center;
890
+ margin-left: -4px;
891
+ }
892
+ .header-save-wrap .tool-dropdown-arrow:hover {
893
+ color: var(--annot-accent);
894
+ background: var(--annot-hover-bg);
895
+ border-radius: 4px;
896
+ }
897
+
898
+ /* Inline rename input that replaces the filename span on double-click.
899
+ * Styled to match the filename's visual weight (same font-weight,
900
+ * size, line-height) so swapping in/out of edit mode doesn't shift
901
+ * the baseline or the row height. */
902
+ .breadcrumb-filename-input {
903
+ flex-shrink: 0;
904
+ min-width: 160px;
905
+ max-width: 420px;
906
+ font: inherit;
907
+ font-weight: 600;
908
+ font-size: 13px;
909
+ line-height: 1.4;
910
+ color: var(--annot-text-primary);
911
+ background: var(--annot-input-bg, rgba(255, 255, 255, 0.04));
912
+ border: 1px solid var(--annot-accent);
913
+ border-radius: 4px;
914
+ padding: 1px 6px;
915
+ outline: none;
916
+ }
917
+ .breadcrumb-filename-input:disabled {
918
+ opacity: 0.55;
919
+ cursor: wait;
920
+ }
921
+
922
+ /* In editor mode, the top #toolbar is replaced by the left
923
+ * #editor-sidebar. Hide the top toolbar entirely and make room for
924
+ * the sidebar on the left edge of the canvas area.
925
+ *
926
+ * Doc mode + split-editor mode follow the same chrome contract:
927
+ * each surface ships its own dedicated header (`<annot-doc-header>`
928
+ * / `.split-editor-header`) carrying brand + actions + theme
929
+ * toggle, so the gallery's search-bearing #toolbar is redundant
930
+ * and the search box (meaningless on a single-doc editor or a
931
+ * modal split-editor task) would otherwise leak into chrome. */
932
+ body.editor-mode #toolbar,
933
+ body.annot-doc-mode #toolbar,
934
+ body.split-editor-active #toolbar {
935
+ display: none;
936
+ }
937
+ #editor-sidebar {
938
+ display: none; /* hidden by default (gallery mode) */
939
+ }
940
+ body.editor-mode #editor-sidebar {
941
+ position: absolute;
942
+ top: 48px;
943
+ left: 0;
944
+ bottom: 28px;
945
+ width: 48px;
946
+ background: var(--annot-bg-panel);
947
+ border-right: 1px solid var(--annot-border-color);
948
+ display: flex;
949
+ flex-direction: column;
950
+ align-items: center;
951
+ padding: 6px 0;
952
+ gap: 2px;
953
+ z-index: 10;
954
+ /* `overflow: visible` (both axes) so tooltips positioned to the
955
+ * right of a tool button can escape the 48px strip into the canvas
956
+ * area. Per CSS spec, mixing `overflow-x: visible` with
957
+ * `overflow-y: auto` causes the browser to promote `visible` to
958
+ * `auto`, which would re-introduce the clipping. So we stay fully
959
+ * visible and accept that on extremely short viewports the
960
+ * bottom-most tools may overflow past the sidebar's visual end.
961
+ * With 9 tools × ~38 px = ~342 px total + 6 px top padding, the
962
+ * full stack fits in any viewport ≥ 396 px tall — well below the
963
+ * ~500 px minimum any modern laptop / tablet provides.
964
+ *
965
+ * The old "clip dropdown-arrow bleed" justification no longer
966
+ * applies: the caret element was removed when the variant badge
967
+ * took over the flyout-trigger role, and the badge's hit-zone
968
+ * expansion (::before inset: -6px) stays within the button's
969
+ * 36 px footprint. */
970
+ overflow: visible;
971
+ }
972
+ body.editor-mode #canvas-container {
973
+ top: 48px; /* below editor-header only */
974
+ left: 48px; /* right of the sidebar */
975
+ }
976
+
977
+ /* Right property panel — context-aware:
978
+ * Tool active (not Select) → shows tool default properties
979
+ * Selection present → shows selection properties
980
+ * Otherwise → hidden (body.has-right-panel removed)
981
+ * Host (app.ts) toggles `body.has-right-panel` to show/hide.
982
+ */
983
+ #editor-right-panel {
984
+ display: none;
985
+ }
986
+ body.editor-mode.has-right-panel #editor-right-panel {
987
+ position: absolute;
988
+ top: 48px;
989
+ right: 0;
990
+ bottom: 28px;
991
+ width: 280px;
992
+ background: var(--annot-bg-panel);
993
+ border-left: 1px solid var(--annot-border-color);
994
+ display: flex;
995
+ flex-direction: column;
996
+ z-index: 10;
997
+ overflow-y: auto;
998
+ }
999
+ body.editor-mode.has-right-panel #canvas-container {
1000
+ right: 280px; /* leave room for the right panel */
1001
+ }
1002
+ body.editor-mode.has-right-panel .file-details-drawer,
1003
+ body.editor-mode.has-right-panel .file-details-backdrop {
1004
+ /* The details drawer also lives on the right — let it slide over
1005
+ * the property panel so the user can still access file details
1006
+ * while a tool/selection is active. */
1007
+ z-index: 25;
1008
+ }
1009
+
1010
+ .editor-right-panel-section {
1011
+ /* Tightened from 14 px → 10 px vertical to densify the right
1012
+ * panel for the screenshot-annotation flow (Figma-class
1013
+ * density). Horizontal stays at 16 px so the level-2 column
1014
+ * alignment with `.prop-panel-docked` is preserved. */
1015
+ padding: 10px 16px;
1016
+ border-bottom: 1px solid var(--annot-border-subtle);
1017
+ }
1018
+ /* Dynamic section title (e.g. "Rectangle" / "Arrow" / "Callout").
1019
+ * Rendered as a readable, sentence-case heading rather than the
1020
+ * older "SECTION" uppercase pill — the title now names the element
1021
+ * the user is editing, so readability matters more than category
1022
+ * emphasis. Sentence case + slightly larger weight makes it read as
1023
+ * "this IS the thing" rather than "a section labeled …". */
1024
+ .editor-right-panel-section-title {
1025
+ /* Was 0 0 10px — tightened to 6 px so the section title binds
1026
+ * closer to the first level-2 sub-section header below it. */
1027
+ margin: 0 0 6px;
1028
+ font-size: 13px;
1029
+ font-weight: 600;
1030
+ color: var(--annot-text-primary);
1031
+ }
1032
+
1033
+ /* Actions section — transform / arrange / align / group button
1034
+ * rows (rotate / flip / z-order / align / distribute / group /
1035
+ * ungroup). Shown only when selection is non-empty. Markup
1036
+ * mirrors the Selected card: the section's body wrapper is a
1037
+ * `.prop-panel.prop-panel-docked`, and each sub-group is a
1038
+ * `.pp-section` with a `.pp-section-header` label. That gives
1039
+ * Actions the same level-2 alignment as TYPE / FILL / LINE for
1040
+ * free, no Actions-specific overrides required. The only thing
1041
+ * still needing a custom class is the row container, where the
1042
+ * tight 2 px button gap (vs `.pp-section-body`'s 6 px) is
1043
+ * intentional for icon-button rows. */
1044
+ .editor-right-panel-actions-row {
1045
+ display: flex;
1046
+ gap: 2px;
1047
+ /* Indent the action row slightly so it visually hangs off the
1048
+ * `.pp-section-header` above, matching `.pp-section-body`'s 10
1049
+ * px indent in the property panel. */
1050
+ padding-left: 10px;
1051
+ }
1052
+ .editor-right-panel-actions-row:not(:last-child) {
1053
+ margin-bottom: 0;
1054
+ }
1055
+
1056
+ /* Action buttons inherit the full 36×36 `.toolbar-btn` size + 20 px
1057
+ * font-size from the sidebar/toolbar rules so Icon weight matches
1058
+ * across surfaces. Panel is 208 px inner after padding; 4 buttons
1059
+ * × 36 + 3 × 2 gap = 150 px, leaving ~58 px margin — comfortably
1060
+ * fits without needing a compact-size override. */
1061
+
1062
+ /* Custom-SVG action buttons (flip / z-order) — SVG renders at 22 px
1063
+ * to match the optical weight of the 20 px Material-Symbols glyph
1064
+ * in a 36 px button. `currentColor` inside the SVG picks up the
1065
+ * button's color + hover / active states automatically. */
1066
+ .editor-right-panel-actions-row .action-btn-svg > svg {
1067
+ width: 22px;
1068
+ height: 22px;
1069
+ }
1070
+
1071
+ /* Tooltips on action buttons sit INSIDE the 280 px right panel,
1072
+ * whose `overflow-y: auto` implicitly clips horizontal overflow too.
1073
+ * The default centered tooltip (left:50%, nowrap) extends past the
1074
+ * panel's left/right edges for the EXTREMITY buttons (leftmost
1075
+ * overflows left, rightmost overflows right) and gets clipped.
1076
+ *
1077
+ * Fix: keep the tooltip BELOW (matching the rest of the editor's
1078
+ * tooltip convention — all toolbar buttons drop downward), but
1079
+ * anchor to the INSIDE edge for the first / last buttons of each
1080
+ * row so the bubble always extends INTO the panel rather than out
1081
+ * of it. Middle buttons stay centered as usual. Multi-line wrapping
1082
+ * (`white-space: normal`) keeps the bubble narrow enough to fit the
1083
+ * column width. */
1084
+ .editor-right-panel-actions-row .toolbar-btn[data-tooltip]::after {
1085
+ bottom: auto;
1086
+ top: calc(100% + 6px);
1087
+ left: 50%;
1088
+ transform: translateX(-50%);
1089
+ max-width: 180px;
1090
+ white-space: normal;
1091
+ text-align: center;
1092
+ line-height: 1.3;
1093
+ }
1094
+ /* First button: align tooltip's LEFT edge to the button's left edge
1095
+ * so it extends rightward into the panel (never past panel-left). */
1096
+ .editor-right-panel-actions-row .toolbar-btn:first-child[data-tooltip]::after {
1097
+ left: 0;
1098
+ right: auto;
1099
+ transform: none;
1100
+ text-align: left;
1101
+ }
1102
+ /* Last button: mirror — align tooltip's RIGHT edge to the button's
1103
+ * right edge so it extends leftward (never past panel-right). */
1104
+ .editor-right-panel-actions-row .toolbar-btn:last-child[data-tooltip]::after {
1105
+ left: auto;
1106
+ right: 0;
1107
+ transform: none;
1108
+ text-align: right;
1109
+ }
1110
+
1111
+ /* Apply-redactions-to-image button — labeled action surface in the
1112
+ * right-panel's apply-redactions section (`#renderApplyRedactions`
1113
+ * in `right-panel.ts`). Phase 3 of
1114
+ * `_done/redact-burn-into-image.md` initially reused the
1115
+ * `.toolbar-btn` class but that's a 36 × 36 icon-only chrome —
1116
+ * dropping a label inside overflows the box. This is the dedicated
1117
+ * labeled-button rule: full-width row, icon + label, opaque
1118
+ * destructive background so the user recognises it as the
1119
+ * irreversibility-confirming gesture. */
1120
+ .annot-apply-redactions-btn {
1121
+ display: flex;
1122
+ align-items: center;
1123
+ justify-content: center;
1124
+ gap: 8px;
1125
+ width: 100%;
1126
+ min-height: 36px;
1127
+ padding: 8px 12px;
1128
+ border: 1px solid var(--annot-border-color);
1129
+ border-radius: 8px;
1130
+ background: var(--annot-bg-elevated, var(--annot-bg-panel));
1131
+ color: var(--annot-text-primary);
1132
+ font-size: 13px;
1133
+ font-weight: 500;
1134
+ cursor: pointer;
1135
+ transition:
1136
+ background 0.1s,
1137
+ border-color 0.1s;
1138
+ }
1139
+ .annot-apply-redactions-btn:hover:not(:disabled) {
1140
+ background: var(--annot-hover-bg);
1141
+ border-color: var(--annot-hover-border);
1142
+ }
1143
+ .annot-apply-redactions-btn:focus-visible {
1144
+ outline: 2px solid var(--annot-focus-ring, var(--annot-accent));
1145
+ outline-offset: 2px;
1146
+ }
1147
+ .annot-apply-redactions-btn:disabled {
1148
+ opacity: 0.5;
1149
+ cursor: not-allowed;
1150
+ }
1151
+ .annot-apply-redactions-btn > annot-icon {
1152
+ font-size: 18px;
1153
+ flex-shrink: 0;
1154
+ }
1155
+ .annot-apply-redactions-btn > .annot-apply-redactions-label {
1156
+ flex: 1;
1157
+ min-width: 0;
1158
+ text-align: left;
1159
+ white-space: normal;
1160
+ /* Allow up to two lines for a graceful long-language wrap; tall
1161
+ * single-line containers (the common case) just stay one line. */
1162
+ line-height: 1.3;
1163
+ }
1164
+
1165
+ /* Empty-state shown when the panel has no active tool AND no
1166
+ * selection. Keeps the always-visible panel feeling intentional
1167
+ * rather than "broken / blank". */
1168
+ .editor-right-panel-empty {
1169
+ flex: 1;
1170
+ display: flex;
1171
+ flex-direction: column;
1172
+ align-items: center;
1173
+ justify-content: center;
1174
+ gap: 8px;
1175
+ padding: 48px 24px;
1176
+ text-align: center;
1177
+ color: var(--annot-text-muted);
1178
+ }
1179
+ .editor-right-panel-empty-icon {
1180
+ font-size: 36px;
1181
+ opacity: 0.4;
1182
+ font-variation-settings:
1183
+ "FILL" 0,
1184
+ "wght" 300,
1185
+ "GRAD" 0,
1186
+ "opsz" 40;
1187
+ }
1188
+ .editor-right-panel-empty-title {
1189
+ margin: 0;
1190
+ font-size: 13px;
1191
+ font-weight: 600;
1192
+ color: var(--annot-text-secondary);
1193
+ }
1194
+ .editor-right-panel-empty-hint {
1195
+ margin: 0;
1196
+ font-size: 12px;
1197
+ line-height: 1.5;
1198
+ max-width: 180px;
1199
+ }
1200
+
1201
+ /* Scratchpad section — always-visible bottom area of the right panel.
1202
+ * Pinned to the bottom by margin-top:auto inside the flex-column
1203
+ * container so tool/selection content fills the top and scratchpad
1204
+ * stays anchored. Top border differentiates it as a distinct area. */
1205
+ .editor-right-panel-scratchpad {
1206
+ margin-top: auto;
1207
+ border-top: 1px solid var(--annot-border-color);
1208
+ border-bottom: none;
1209
+ background: var(--annot-bg-panel-deep);
1210
+ flex-shrink: 0;
1211
+ }
1212
+
1213
+ .scratchpad-section-header {
1214
+ display: flex;
1215
+ align-items: center;
1216
+ justify-content: space-between;
1217
+ margin-bottom: 10px;
1218
+ }
1219
+ .scratchpad-section-header .editor-right-panel-section-title {
1220
+ margin: 0;
1221
+ }
1222
+
1223
+ .scratchpad-save-btn {
1224
+ width: 22px;
1225
+ height: 22px;
1226
+ border: 1px solid var(--annot-border-color);
1227
+ border-radius: 4px;
1228
+ background: transparent;
1229
+ color: var(--annot-text-secondary);
1230
+ cursor: pointer;
1231
+ display: flex;
1232
+ align-items: center;
1233
+ justify-content: center;
1234
+ font-size: 14px;
1235
+ transition:
1236
+ background 0.15s,
1237
+ border-color 0.15s,
1238
+ color 0.15s;
1239
+ }
1240
+ .scratchpad-save-btn:not(:disabled):hover {
1241
+ background: var(--annot-accent-bg);
1242
+ border-color: var(--annot-accent);
1243
+ color: var(--annot-accent);
1244
+ }
1245
+ .scratchpad-save-btn:disabled {
1246
+ opacity: 0.35;
1247
+ cursor: not-allowed;
1248
+ }
1249
+ .scratchpad-save-btn annot-icon {
1250
+ font-size: 16px;
1251
+ }
1252
+
1253
+ .scratchpad-grid {
1254
+ display: grid;
1255
+ grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
1256
+ gap: 6px;
1257
+ }
1258
+
1259
+ .scratchpad-item {
1260
+ position: relative;
1261
+ aspect-ratio: 1 / 1;
1262
+ background: var(--annot-canvas-bg, #0b1020);
1263
+ border: 1px solid var(--annot-border-subtle);
1264
+ border-radius: 6px;
1265
+ cursor: pointer;
1266
+ overflow: hidden;
1267
+ display: flex;
1268
+ align-items: center;
1269
+ justify-content: center;
1270
+ transition:
1271
+ border-color 0.15s,
1272
+ transform 0.15s;
1273
+ }
1274
+ .scratchpad-item:hover {
1275
+ border-color: var(--annot-accent);
1276
+ transform: translateY(-1px);
1277
+ }
1278
+ /* Armed state — the user clicked this item and the paste tool is
1279
+ * waiting for a canvas click. Stronger accent so the "which item
1280
+ * will land next?" question is answered at a glance. */
1281
+ .scratchpad-item.active {
1282
+ border-color: var(--annot-accent);
1283
+ background: var(--annot-accent-bg);
1284
+ box-shadow: 0 0 0 1px var(--annot-accent) inset;
1285
+ }
1286
+ .scratchpad-item-thumb {
1287
+ max-width: 80%;
1288
+ max-height: 80%;
1289
+ object-fit: contain;
1290
+ pointer-events: none;
1291
+ user-select: none;
1292
+ }
1293
+
1294
+ .scratchpad-item-delete {
1295
+ position: absolute;
1296
+ top: 2px;
1297
+ right: 2px;
1298
+ width: 16px;
1299
+ height: 16px;
1300
+ border: none;
1301
+ border-radius: 50%;
1302
+ background: rgba(0, 0, 0, 0.6);
1303
+ color: #fff;
1304
+ font-size: 12px;
1305
+ display: flex;
1306
+ align-items: center;
1307
+ justify-content: center;
1308
+ cursor: pointer;
1309
+ opacity: 0;
1310
+ transition: opacity 0.15s;
1311
+ font-variation-settings:
1312
+ "FILL" 0,
1313
+ "wght" 500,
1314
+ "GRAD" 0,
1315
+ "opsz" 20;
1316
+ }
1317
+ .scratchpad-item:hover .scratchpad-item-delete {
1318
+ opacity: 1;
1319
+ }
1320
+ .scratchpad-item-delete:hover {
1321
+ background: var(--danger, #ff7070);
1322
+ }
1323
+
1324
+ .scratchpad-empty {
1325
+ margin: 0;
1326
+ font-size: 12px;
1327
+ color: var(--annot-text-muted);
1328
+ line-height: 1.5;
1329
+ }
1330
+
1331
+ /* ---- Save status indicator ----
1332
+ *
1333
+ * Sits between the filename and the right-edge help/theme actions in the
1334
+ * editor header. Reports the save lifecycle without blocking; matches
1335
+ * the Google Docs / Notion convention of a small, muted indicator that
1336
+ * briefly animates while writing.
1337
+ */
1338
+ /* Padding matches .breadcrumb-item (2px 6px) so vertical rhythm aligns
1339
+ * with the filename segment it sits next to. Icon kept at 14px to keep
1340
+ * the indicator visually "inline metadata" rather than a chunky chip. */
1341
+ .save-status {
1342
+ display: inline-flex;
1343
+ align-items: center;
1344
+ gap: 4px;
1345
+ padding: 2px 8px;
1346
+ border-radius: 6px;
1347
+ font-size: 12px;
1348
+ font-weight: 500;
1349
+ line-height: 1;
1350
+ white-space: nowrap;
1351
+ user-select: none;
1352
+ flex-shrink: 0; /* stays intact next to the filename */
1353
+ color: var(--annot-text-muted);
1354
+ transition:
1355
+ color 0.15s,
1356
+ background 0.15s;
1357
+ }
1358
+ .save-status-icon {
1359
+ font-size: 14px;
1360
+ font-variation-settings:
1361
+ "FILL" 0,
1362
+ "wght" 400,
1363
+ "GRAD" 0,
1364
+ "opsz" 20;
1365
+ }
1366
+
1367
+ /* Saved — neutral, at rest */
1368
+ .save-status.save-status-saved {
1369
+ color: var(--annot-text-muted);
1370
+ }
1371
+
1372
+ /* Edited — slightly brighter than saved, signals "something's queued" */
1373
+ .save-status.save-status-pending {
1374
+ color: var(--annot-text-secondary);
1375
+ }
1376
+
1377
+ /* Saving — accent color + spinning icon */
1378
+ .save-status.save-status-saving {
1379
+ color: var(--annot-accent);
1380
+ }
1381
+ .save-status.save-status-saving .save-status-icon {
1382
+ animation: save-status-spin 1s linear infinite;
1383
+ }
1384
+ @keyframes save-status-spin {
1385
+ from {
1386
+ transform: rotate(0deg);
1387
+ }
1388
+ to {
1389
+ transform: rotate(360deg);
1390
+ }
1391
+ }
1392
+
1393
+ /* Error — danger color, stays visible until next save attempt */
1394
+ .save-status.save-status-error {
1395
+ color: var(--danger, #ff7070);
1396
+ background: rgba(255, 112, 112, 0.1);
1397
+ }
1398
+
1399
+ /* The descendant rule `.editor-header-path .editor-header-info-btn`
1400
+ * earlier in the file intentionally has higher specificity — it's a
1401
+ * contextual tweak for buttons inside the breadcrumb path. Keeping
1402
+ * the base class here with a documented suppression is cleaner than
1403
+ * fragmenting the "editor header info button" styling across two
1404
+ * sections just to satisfy the linter. */
1405
+ /* biome-ignore lint/style/noDescendingSpecificity: base rule deliberately follows contextual .editor-header-path descendant */
1406
+ .editor-header-info-btn {
1407
+ width: 28px;
1408
+ height: 28px;
1409
+ border: none;
1410
+ background: transparent;
1411
+ color: var(--annot-text-muted);
1412
+ border-radius: 4px;
1413
+ cursor: pointer;
1414
+ font-size: 18px;
1415
+ font-variation-settings:
1416
+ "FILL" 0,
1417
+ "wght" 400,
1418
+ "GRAD" 0,
1419
+ "opsz" 20;
1420
+ display: flex;
1421
+ align-items: center;
1422
+ justify-content: center;
1423
+ flex-shrink: 0;
1424
+ transition:
1425
+ background 0.15s,
1426
+ color 0.15s;
1427
+ }
1428
+ .editor-header-info-btn:hover {
1429
+ background: var(--annot-hover-bg);
1430
+ color: var(--annot-accent);
1431
+ }
1432
+ .editor-header-info-btn:focus-visible {
1433
+ outline: 2px solid var(--annot-accent);
1434
+ outline-offset: 2px;
1435
+ }
1436
+
1437
+ /* ---- Breadcrumb ---- */
1438
+
1439
+ .breadcrumb {
1440
+ display: flex;
1441
+ align-items: center;
1442
+ gap: 2px;
1443
+ overflow: hidden;
1444
+ white-space: nowrap;
1445
+ }
1446
+
1447
+ /* Breadcrumb items may be either <button> (clickable ancestors) or
1448
+ * <span> (the non-clickable filename segment). Without the font resets
1449
+ * below, <button> falls back to the UA system font while <span>
1450
+ * inherits Sora — the two have different metrics and the filename
1451
+ * appears visually lower than sibling clickable segments.
1452
+ *
1453
+ * `font: inherit` pulls the parent's full font specification into the
1454
+ * button, then we override the few properties we actually want to pin
1455
+ * (size, weight). Setting `display: inline-flex + align-items: center`
1456
+ * also normalizes vertical alignment between span and button so the
1457
+ * glyph baselines line up across element types.
1458
+ *
1459
+ * Same pattern as `.editor-header-info-btn` above: the descendant
1460
+ * rule `.editor-header-path .breadcrumb-item` earlier in the file
1461
+ * is a contextual override, and the base rule lives here with the
1462
+ * rest of the breadcrumb section. */
1463
+ /* biome-ignore lint/style/noDescendingSpecificity: base rule deliberately follows contextual .editor-header-path descendant */
1464
+ .breadcrumb-item {
1465
+ font: inherit;
1466
+ font-size: 13px;
1467
+ font-weight: 400;
1468
+ line-height: 1.4;
1469
+ display: inline-flex;
1470
+ align-items: center;
1471
+ border: none;
1472
+ background: none;
1473
+ color: var(--annot-text-secondary);
1474
+ cursor: pointer;
1475
+ padding: 2px 6px;
1476
+ border-radius: 4px;
1477
+ white-space: nowrap;
1478
+ }
1479
+
1480
+ .breadcrumb-item:hover {
1481
+ background: var(--annot-hover-bg);
1482
+ color: var(--annot-accent);
1483
+ }
1484
+
1485
+ .breadcrumb-item.active {
1486
+ color: var(--annot-text-primary);
1487
+ font-weight: 600;
1488
+ cursor: default;
1489
+ }
1490
+
1491
+ .breadcrumb-item.active:hover {
1492
+ background: none;
1493
+ color: var(--annot-text-primary);
1494
+ }
1495
+
1496
+ .breadcrumb-sep {
1497
+ color: var(--annot-text-muted);
1498
+ font-size: 16px;
1499
+ user-select: none;
1500
+ }
1501
+
1502
+ /* ---- Folder cards ---- */
1503
+
1504
+ .gallery-folder {
1505
+ border-style: dashed;
1506
+ }
1507
+
1508
+ .gallery-folder:hover {
1509
+ border-style: solid;
1510
+ }
1511
+
1512
+ .gallery-folder-icon {
1513
+ display: flex;
1514
+ align-items: center;
1515
+ justify-content: center;
1516
+ background: var(--annot-bg-secondary);
1517
+ }
1518
+
1519
+ .gallery-folder-name {
1520
+ font-weight: 600;
1521
+ }
1522
+
1523
+ /* ---- Gallery panel ---- */
1524
+
1525
+ /* #gallery-container removed — gallery now renders inside #file-manager > #main-content */
1526
+
1527
+ .gallery-panel {
1528
+ /* Block display is required because `.gallery-panel` is now applied
1529
+ * to `<annot-gallery-page>` (a custom element, defaults to inline);
1530
+ * without this the horizontal padding collapses around the block
1531
+ * `.gallery-grid` child and the left/right gutter disappears. */
1532
+ display: block;
1533
+ padding: 16px 24px;
1534
+ /* Fill the available main-content area — let the gallery grid wrap
1535
+ * naturally instead of clamping to a fixed max-width. */
1536
+ margin: 0 auto;
1537
+ }
1538
+
1539
+ .gallery-grid {
1540
+ display: grid;
1541
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
1542
+ gap: 16px;
1543
+ }
1544
+
1545
+ .gallery-empty {
1546
+ grid-column: 1 / -1;
1547
+ text-align: center;
1548
+ padding: 60px 20px;
1549
+ color: var(--annot-text-muted);
1550
+ font-size: 14px;
1551
+ }
1552
+
1553
+ .gallery-item {
1554
+ border: 1px solid var(--annot-border-color);
1555
+ border-radius: 8px;
1556
+ overflow: hidden;
1557
+ background: var(--annot-bg-panel);
1558
+ cursor: pointer;
1559
+ transition:
1560
+ border-color 0.15s,
1561
+ transform 0.15s;
1562
+ }
1563
+
1564
+ .gallery-item:hover {
1565
+ border-color: var(--annot-accent);
1566
+ transform: translateY(-2px);
1567
+ }
1568
+
1569
+ .gallery-thumb {
1570
+ aspect-ratio: 16 / 9;
1571
+ overflow: hidden;
1572
+ background: #111;
1573
+ }
1574
+
1575
+ .gallery-thumb img {
1576
+ width: 100%;
1577
+ height: 100%;
1578
+ /* Thumbnails are pre-rendered at 16:9 with letterboxing / top-crop
1579
+ * baked in, so the card just displays them 1:1. `contain` prevents
1580
+ * any further browser-side upscaling and keeps short images crisp. */
1581
+ object-fit: contain;
1582
+ object-position: center;
1583
+ display: block;
1584
+ /* Progressive enhancement: browsers that don't recognise `high-
1585
+ * quality` fall back to `-webkit-optimize-contrast` or the plain
1586
+ * `auto` declaration earlier in the stack. This is a deliberate
1587
+ * cross-browser pattern, not a typo. */
1588
+ /* biome-ignore-start lint/suspicious/noDuplicateProperties: deliberate CSS fallback stack */
1589
+ image-rendering: auto;
1590
+ image-rendering: -webkit-optimize-contrast;
1591
+ image-rendering: high-quality;
1592
+ /* biome-ignore-end lint/suspicious/noDuplicateProperties: deliberate CSS fallback stack */
1593
+ }
1594
+
1595
+ /* Document-card fallback when `thumbnailDataUrl` is empty —
1596
+ * Phase 11 of `annot-html-document.md` shipped doc cards but
1597
+ * with no thumb-generation path, so the default `.gallery-thumb`
1598
+ * `background:#111` showed through as a black rectangle. The
1599
+ * `<annot-icon spec="article">` fallback rendered into the top-
1600
+ * left corner without sizing because nothing centered it. The
1601
+ * rules below: center the icon, give it a readable size, swap
1602
+ * the dark background for a friendlier panel-coloured tile so
1603
+ * docs feel like papers / preview tiles rather than missing
1604
+ * media. Documents with image blocks override this via the
1605
+ * pre-rendered `<img>` (sibling `.gallery-thumb img` rule
1606
+ * above). */
1607
+ .gallery-document-item .gallery-thumb {
1608
+ background: var(--annot-bg-panel, #2a2a2a);
1609
+ display: flex;
1610
+ align-items: center;
1611
+ justify-content: center;
1612
+ border: 1px solid var(--annot-border-color, transparent);
1613
+ }
1614
+ .gallery-document-item .gallery-thumb annot-icon {
1615
+ width: 56px;
1616
+ height: 56px;
1617
+ color: var(--annot-text-secondary, #9ca3af);
1618
+ }
1619
+ .gallery-document-item .gallery-thumb annot-icon svg {
1620
+ width: 100%;
1621
+ height: 100%;
1622
+ }
1623
+
1624
+ .gallery-item-info {
1625
+ padding: 8px 10px;
1626
+ position: relative;
1627
+ }
1628
+
1629
+ .gallery-item-name {
1630
+ font-size: 12px;
1631
+ color: var(--annot-text-primary);
1632
+ white-space: nowrap;
1633
+ overflow: hidden;
1634
+ text-overflow: ellipsis;
1635
+ padding-right: 20px;
1636
+ }
1637
+
1638
+ .gallery-item-meta {
1639
+ font-size: 11px;
1640
+ color: var(--annot-text-muted);
1641
+ margin-top: 2px;
1642
+ }
1643
+
1644
+ .gallery-item-delete {
1645
+ position: absolute;
1646
+ top: 6px;
1647
+ right: 6px;
1648
+ width: 20px;
1649
+ height: 20px;
1650
+ border: none;
1651
+ border-radius: 50%;
1652
+ background: rgba(200, 0, 0, 0.6);
1653
+ color: #fff;
1654
+ font-size: 14px;
1655
+ line-height: 20px;
1656
+ text-align: center;
1657
+ cursor: pointer;
1658
+ opacity: 0;
1659
+ transition: opacity 0.15s;
1660
+ padding: 0;
1661
+ }
1662
+
1663
+ .gallery-item:hover .gallery-item-delete {
1664
+ opacity: 1;
1665
+ }
1666
+
1667
+ .gallery-item-delete:hover {
1668
+ background: rgba(220, 0, 0, 0.9);
1669
+ }
1670
+
1671
+ /* ---- Selection checkbox ---- */
1672
+
1673
+ .gallery-checkbox {
1674
+ position: absolute;
1675
+ top: 6px;
1676
+ left: 6px;
1677
+ width: 24px;
1678
+ height: 24px;
1679
+ display: flex;
1680
+ align-items: center;
1681
+ justify-content: center;
1682
+ border-radius: 50%;
1683
+ background: rgba(0, 0, 0, 0.4);
1684
+ color: #888;
1685
+ cursor: pointer;
1686
+ opacity: 0;
1687
+ transition: opacity 0.15s;
1688
+ z-index: 2;
1689
+ }
1690
+
1691
+ .gallery-checkbox annot-icon {
1692
+ font-size: 18px;
1693
+ }
1694
+
1695
+ .gallery-item:hover .gallery-checkbox,
1696
+ .gallery-item.selected .gallery-checkbox {
1697
+ opacity: 1;
1698
+ }
1699
+
1700
+ .gallery-item.selected .gallery-checkbox {
1701
+ background: var(--annot-accent);
1702
+ color: #000;
1703
+ }
1704
+
1705
+ .gallery-item.selected {
1706
+ border-color: var(--annot-accent);
1707
+ box-shadow: 0 0 0 2px var(--annot-accent);
1708
+ }
1709
+
1710
+ .gallery-item {
1711
+ position: relative;
1712
+ }
1713
+
1714
+ /* ---- Selection action bar ---- */
1715
+
1716
+ .gallery-action-bar {
1717
+ display: flex;
1718
+ align-items: center;
1719
+ gap: 8px;
1720
+ }
1721
+
1722
+ .gallery-footer-text {
1723
+ font-size: 12px;
1724
+ color: var(--annot-text-secondary);
1725
+ white-space: nowrap;
1726
+ }
1727
+
1728
+ .gallery-action-btn {
1729
+ border: 1px solid var(--annot-border-color);
1730
+ background: var(--annot-bg-panel);
1731
+ color: var(--annot-text-primary);
1732
+ font-size: 12px;
1733
+ padding: 2px 10px;
1734
+ border-radius: 4px;
1735
+ cursor: pointer;
1736
+ white-space: nowrap;
1737
+ display: flex;
1738
+ align-items: center;
1739
+ gap: 3px;
1740
+ }
1741
+
1742
+ .gallery-action-btn:hover {
1743
+ border-color: var(--annot-accent);
1744
+ color: var(--annot-accent);
1745
+ }
1746
+
1747
+ .gallery-action-danger:hover {
1748
+ border-color: #e44;
1749
+ color: #e44;
1750
+ }
1751
+
1752
+ /* ---- Folder picker dialog ---- */
1753
+
1754
+ .folder-picker-overlay {
1755
+ position: fixed;
1756
+ inset: 0;
1757
+ background: rgba(0, 0, 0, 0.5);
1758
+ display: flex;
1759
+ align-items: center;
1760
+ justify-content: center;
1761
+ z-index: 1000;
1762
+ }
1763
+
1764
+ .folder-picker-dialog {
1765
+ background: var(--annot-bg-panel);
1766
+ border: 1px solid var(--annot-border-color);
1767
+ border-radius: 10px;
1768
+ padding: 16px;
1769
+ min-width: 300px;
1770
+ max-width: 450px;
1771
+ max-height: 60vh;
1772
+ display: flex;
1773
+ flex-direction: column;
1774
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
1775
+ }
1776
+
1777
+ .folder-picker-title {
1778
+ font-size: 15px;
1779
+ font-weight: 600;
1780
+ color: var(--annot-text-primary);
1781
+ margin-bottom: 12px;
1782
+ }
1783
+
1784
+ .folder-picker-list {
1785
+ overflow-y: auto;
1786
+ flex: 1;
1787
+ display: flex;
1788
+ flex-direction: column;
1789
+ gap: 2px;
1790
+ margin-bottom: 12px;
1791
+ }
1792
+
1793
+ .folder-picker-item {
1794
+ display: block;
1795
+ width: 100%;
1796
+ padding: 8px 12px;
1797
+ border: none;
1798
+ border-radius: 6px;
1799
+ background: transparent;
1800
+ color: var(--annot-text-primary);
1801
+ font-size: 13px;
1802
+ text-align: left;
1803
+ cursor: pointer;
1804
+ white-space: nowrap;
1805
+ overflow: hidden;
1806
+ text-overflow: ellipsis;
1807
+ }
1808
+
1809
+ .folder-picker-item:hover {
1810
+ background: var(--annot-choice-hover);
1811
+ color: var(--annot-accent);
1812
+ }
1813
+
1814
+ .folder-picker-cancel {
1815
+ align-self: flex-end;
1816
+ border: 1px solid var(--annot-border-color);
1817
+ background: transparent;
1818
+ color: var(--annot-text-secondary);
1819
+ font-size: 13px;
1820
+ padding: 6px 16px;
1821
+ border-radius: 6px;
1822
+ cursor: pointer;
1823
+ }
1824
+
1825
+ .folder-picker-cancel:hover {
1826
+ border-color: var(--annot-accent);
1827
+ color: var(--annot-accent);
1828
+ }
1829
+
1830
+ /* =============================================================================
1831
+ * Elements panel — sidebar list of detected DOM elements (button /
1832
+ * link / input / heading / etc.) for a screenshot captured by the
1833
+ * browser extension. Rows show icon + label + sub-type. Hover →
1834
+ * highlight on canvas; click → insert a rectangle annotation.
1835
+ * Section is hidden entirely when the current image has no metadata.
1836
+ * ============================================================================= */
1837
+ .editor-right-panel-elements {
1838
+ /* Inherits .editor-right-panel-section padding/border. */
1839
+ }
1840
+ .editor-right-panel-elements-hint {
1841
+ margin: 0 0 8px 0;
1842
+ font-size: 11px;
1843
+ color: var(--annot-text-muted);
1844
+ }
1845
+ .editor-right-panel-elements-search {
1846
+ width: 100%;
1847
+ height: 28px;
1848
+ padding: 0 8px;
1849
+ border: 1px solid var(--annot-border-color);
1850
+ border-radius: 6px;
1851
+ background: var(--annot-bg-panel-deep);
1852
+ color: var(--annot-text-primary);
1853
+ font-size: 12px;
1854
+ margin-bottom: 8px;
1855
+ box-sizing: border-box;
1856
+ }
1857
+ .editor-right-panel-elements-search:focus {
1858
+ outline: none;
1859
+ border-color: var(--annot-accent);
1860
+ }
1861
+ .editor-right-panel-elements-list {
1862
+ display: flex;
1863
+ flex-direction: column;
1864
+ gap: 1px;
1865
+ max-height: 320px;
1866
+ overflow-y: auto;
1867
+ /* Negative margin pulls the list to the section padding edge so
1868
+ * scroll bar doesn't waste horizontal space. */
1869
+ margin: 0 -4px;
1870
+ }
1871
+ .editor-right-panel-element-row {
1872
+ display: flex;
1873
+ align-items: center;
1874
+ gap: 8px;
1875
+ padding: 6px 8px;
1876
+ border: none;
1877
+ background: transparent;
1878
+ border-radius: 4px;
1879
+ cursor: pointer;
1880
+ text-align: left;
1881
+ font-size: 12px;
1882
+ color: var(--annot-text-primary);
1883
+ transition: background 0.1s;
1884
+ width: 100%;
1885
+ }
1886
+ .editor-right-panel-element-row:hover {
1887
+ background: var(--annot-hover-bg);
1888
+ }
1889
+ .editor-right-panel-element-icon {
1890
+ font-size: 16px;
1891
+ color: var(--annot-text-secondary);
1892
+ flex-shrink: 0;
1893
+ }
1894
+ .editor-right-panel-element-label {
1895
+ flex: 1;
1896
+ overflow: hidden;
1897
+ text-overflow: ellipsis;
1898
+ white-space: nowrap;
1899
+ min-width: 0;
1900
+ }
1901
+ .editor-right-panel-element-sub {
1902
+ font-size: 10px;
1903
+ color: var(--annot-text-muted);
1904
+ text-transform: lowercase;
1905
+ flex-shrink: 0;
1906
+ }
1907
+ .editor-right-panel-elements-empty {
1908
+ padding: 12px 4px;
1909
+ font-size: 11px;
1910
+ color: var(--annot-text-muted);
1911
+ text-align: center;
1912
+ }