@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,779 @@
1
+ .prop-panel {
2
+ position: absolute;
3
+ top: 52px;
4
+ right: 8px;
5
+ background: var(--annot-bg-panel-deep);
6
+ border: 1px solid var(--annot-border-color);
7
+ border-radius: 8px;
8
+ padding: 10px 12px;
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 8px;
12
+ z-index: 20;
13
+ box-shadow: 0 4px 16px var(--annot-shadow);
14
+ min-width: 200px;
15
+ max-height: calc(100vh - 100px);
16
+ overflow-y: auto;
17
+ }
18
+
19
+ /* Docked variant — used when PropertyPanel is embedded in a host
20
+ * sidebar instead of floating on the canvas. Strips the absolute
21
+ * positioning + chrome so the panel flows naturally inside its
22
+ * container. The host is responsible for sizing/positioning the
23
+ * container (e.g. #editor-right-panel). */
24
+ .prop-panel-docked {
25
+ position: static;
26
+ top: auto;
27
+ right: auto;
28
+ border: none;
29
+ border-radius: 0;
30
+ /* Tight inner pad — the outer `.editor-right-panel-section`
31
+ * already supplies its own 10 px vertical, so 6 px here keeps
32
+ * the docked-panel column compact without doubling up. The
33
+ * 16 px horizontal nests inside the section's 16 px to
34
+ * establish the level-2 column gutter. */
35
+ padding: 6px 16px;
36
+ box-shadow: none;
37
+ max-height: none;
38
+ min-width: 0;
39
+ background: transparent;
40
+ }
41
+
42
+ .prop-row {
43
+ display: flex;
44
+ align-items: center;
45
+ gap: 6px;
46
+ }
47
+
48
+ .prop-label {
49
+ font-size: 11px;
50
+ color: var(--annot-text-secondary);
51
+ width: 36px;
52
+ flex-shrink: 0;
53
+ }
54
+
55
+ .prop-color {
56
+ width: 26px;
57
+ height: 26px;
58
+ border: 1px solid var(--annot-input-border);
59
+ border-radius: 4px;
60
+ padding: 0;
61
+ cursor: pointer;
62
+ background: transparent;
63
+ }
64
+
65
+ .prop-color::-webkit-color-swatch-wrapper {
66
+ padding: 2px;
67
+ }
68
+ .prop-color::-webkit-color-swatch {
69
+ border: none;
70
+ border-radius: 2px;
71
+ }
72
+
73
+ .prop-btn {
74
+ width: 26px;
75
+ height: 26px;
76
+ border: 1px solid var(--annot-input-border);
77
+ border-radius: 4px;
78
+ background: var(--annot-chip-bg);
79
+ color: var(--annot-text-secondary);
80
+ font-size: 13px;
81
+ cursor: pointer;
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ }
86
+
87
+ .prop-btn:hover {
88
+ border-color: var(--annot-accent);
89
+ color: var(--annot-accent);
90
+ }
91
+
92
+ /* Section with label + choice list */
93
+ .prop-section {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 4px;
97
+ }
98
+
99
+ .prop-section-label {
100
+ font-size: 11px;
101
+ color: var(--annot-text-secondary);
102
+ padding-bottom: 2px;
103
+ border-bottom: 1px solid var(--annot-border-color);
104
+ }
105
+
106
+ /* Vertical choice list (width / style) */
107
+ .prop-choice-list {
108
+ display: flex;
109
+ flex-direction: column;
110
+ gap: 1px;
111
+ }
112
+
113
+ .prop-choice-item {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 8px;
117
+ padding: 4px 8px;
118
+ border-radius: 4px;
119
+ cursor: pointer;
120
+ transition: background 0.1s;
121
+ }
122
+
123
+ .prop-choice-item:hover {
124
+ background: var(--annot-choice-hover);
125
+ }
126
+
127
+ .prop-choice-item.active {
128
+ background: var(--annot-choice-active);
129
+ }
130
+
131
+ .prop-choice-item svg {
132
+ flex-shrink: 0;
133
+ }
134
+
135
+ .prop-choice-label {
136
+ font-size: 11px;
137
+ color: var(--annot-text-secondary);
138
+ }
139
+
140
+ .prop-choice-item.active .prop-choice-label {
141
+ color: var(--annot-accent);
142
+ }
143
+
144
+ /* Horizontal chip list (font size) */
145
+ .prop-choice-horizontal {
146
+ flex-direction: row;
147
+ flex-wrap: wrap;
148
+ gap: 4px;
149
+ }
150
+
151
+ /* Variant-picker chip — sized + styled to match `.tool-flyout-chip` /
152
+ * `.tool-flyout-chip-svg` in the toolbar flyout so "next draw"
153
+ * (Tool mode) and "this element's variant" (Selection mode) share
154
+ * the same chip vocabulary across toolbar + property panel.
155
+ * The .pp-color-chip subclass overrides width/height inline (28×20)
156
+ * for Highlight color swatches — its inline styles win over these
157
+ * class defaults. */
158
+ .prop-choice-chip {
159
+ width: 32px;
160
+ height: 32px;
161
+ padding: 0;
162
+ border: 1px solid transparent;
163
+ border-radius: 6px;
164
+ background: transparent;
165
+ color: var(--annot-text-primary);
166
+ font-size: 22px;
167
+ cursor: pointer;
168
+ display: inline-flex;
169
+ align-items: center;
170
+ justify-content: center;
171
+ transition:
172
+ background 0.12s,
173
+ border-color 0.12s,
174
+ color 0.12s;
175
+ }
176
+
177
+ /* SVG glyphs render at a consistent 22×22 — same size as the
178
+ * ligature glyphs (font-size 22px) so SVG + ligature chips read as
179
+ * one family inside the same row. For tall/wide SVGs with explicit
180
+ * width/height attrs the `object-fit: contain`-like scaling is
181
+ * handled by preserveAspectRatio (SVG default), so non-square icons
182
+ * stay legible. */
183
+ .prop-choice-chip > svg {
184
+ width: 22px;
185
+ height: 22px;
186
+ }
187
+
188
+ .prop-choice-chip:hover {
189
+ background: var(--annot-hover-bg);
190
+ border-color: var(--annot-accent);
191
+ color: var(--annot-accent);
192
+ }
193
+
194
+ .prop-choice-chip.active {
195
+ /* "Filled" active state — solid `--annot-accent` background +
196
+ * contrasting glyph. Mirrors the variant flyout chip's
197
+ * active treatment (`.tool-flyout-chip.active` in
198
+ * `core/styles/toolbar.css`) so the right panel's TYPE
199
+ * picker reads with the same three-state hierarchy:
200
+ *
201
+ * Inactive — no border, default glyph
202
+ * Hover — accent border + tinted bg + accent glyph
203
+ * Active — accent fill + contrasting glyph (filled chip)
204
+ *
205
+ * The earlier "tinted bg + blue glyph" pattern shared every
206
+ * visual property with the hover state apart from a small
207
+ * delta in bg saturation (PWA dark: 18 % blue active vs 8 %
208
+ * white hover overlay). On screens / themes where the
209
+ * hover-bg / choice-active difference is subtle, the two
210
+ * states became visually indistinguishable — TYPE pickers
211
+ * lost their "what's currently selected" cue.
212
+ *
213
+ * VSCode contrast budget: bg = `--annot-accent` =
214
+ * `--vscode-button-background`; the inline override in
215
+ * `packages/vscode/src/webview/index.html` switches glyph to
216
+ * `--vscode-button-foreground` (the workbench-paired ≥4.5:1
217
+ * partner) so the white-on-blue contrast holds across
218
+ * every shipping theme. */
219
+ background: var(--annot-accent);
220
+ border-color: var(--annot-accent);
221
+ color: var(--annot-bg-panel);
222
+ }
223
+
224
+ .prop-number-input {
225
+ width: 52px;
226
+ height: 26px;
227
+ border: 1px solid var(--annot-input-border);
228
+ border-radius: 4px;
229
+ background: var(--annot-input-bg);
230
+ color: var(--annot-text-primary);
231
+ font-size: 13px;
232
+ padding: 0 6px;
233
+ text-align: center;
234
+ }
235
+
236
+ /* Preview line color adapts to theme */
237
+ .preview-line {
238
+ stroke: var(--annot-preview-line);
239
+ }
240
+
241
+ /* Color palette */
242
+ .color-palette {
243
+ display: flex;
244
+ flex-direction: column;
245
+ gap: 2px;
246
+ }
247
+
248
+ .color-palette-label {
249
+ font-size: 10px;
250
+ color: var(--annot-text-muted);
251
+ padding: 2px 0;
252
+ }
253
+
254
+ .color-palette-row {
255
+ display: flex;
256
+ gap: 2px;
257
+ }
258
+
259
+ .color-swatch {
260
+ width: 18px;
261
+ height: 18px;
262
+ border: 1px solid transparent;
263
+ border-radius: 2px;
264
+ cursor: pointer;
265
+ transition: transform 0.1s;
266
+ }
267
+
268
+ .color-swatch:hover {
269
+ transform: scale(1.25);
270
+ z-index: 1;
271
+ border-color: var(--annot-accent);
272
+ }
273
+
274
+ .color-swatch.active {
275
+ border: 2px solid var(--annot-accent);
276
+ border-radius: 3px;
277
+ }
278
+
279
+ .color-palette-custom {
280
+ margin-top: 4px;
281
+ display: flex;
282
+ align-items: center;
283
+ }
284
+
285
+ .color-palette-custom-btn {
286
+ font-size: 11px;
287
+ color: var(--annot-accent);
288
+ background: none;
289
+ border: none;
290
+ cursor: pointer;
291
+ padding: 2px 0;
292
+ }
293
+
294
+ .color-palette-custom-btn:hover {
295
+ text-decoration: underline;
296
+ }
297
+
298
+ .color-palette-hidden-input {
299
+ width: 0;
300
+ height: 0;
301
+ padding: 0;
302
+ border: none;
303
+ opacity: 0;
304
+ position: absolute;
305
+ }
306
+
307
+ /* ---- Category / row typography hierarchy ----
308
+ *
309
+ * Previously category headers (Type / Fill / Line / Label) and row
310
+ * labels (Color / Width / Dash type / …) used nearly identical
311
+ * styling — 13 px bold for headers, 12 px regular for rows — which
312
+ * read as "slightly bigger text" rather than a clear category /
313
+ * attribute distinction.
314
+ *
315
+ * New hierarchy (Figma / Linear / Notion-style):
316
+ * Category header → SMALL UPPERCASE, muted, letter-spaced
317
+ * → reads as a "section label" at a glance
318
+ * Row label → normal sentence case body text
319
+ * → reads as a "property name"
320
+ *
321
+ * The contrast in size + case + weight + spacing makes the grouping
322
+ * obvious without needing borders / boxes, keeping the panel flat
323
+ * and uncluttered.
324
+ */
325
+
326
+ /* Vertical rhythm — Gestalt proximity principle, dense:
327
+ * Section → Section : 8px (clear category break)
328
+ * Row → Row : 4px (list items)
329
+ * Header → Body : 4px (parent-child grouping)
330
+ *
331
+ * Annot's right panel is a tool surface that competes with the
332
+ * canvas for screen real estate; users adjust properties dozens
333
+ * of times per session, so density-over-airiness wins. Original
334
+ * 20 : 6 : 4 ratio (PowerPoint-class) was halved on the section-
335
+ * to-section gap (Figma is closer to 8 px). The header-to-body
336
+ * gap stays 4 px because that proximity is already at the floor —
337
+ * making it any tighter would merge into the header.
338
+ *
339
+ * If you find yourself wanting more breathing room, the right
340
+ * fix is usually to drop a row, not to widen the gap. */
341
+ .pp-section {
342
+ margin: 8px 0 0 0;
343
+ }
344
+ .pp-section:first-child {
345
+ margin-top: 0;
346
+ }
347
+
348
+ .pp-section-header {
349
+ display: block;
350
+ user-select: none;
351
+ /* Small + uppercase + letter-spaced — canonical "section header"
352
+ * treatment in professional SaaS UIs (Figma right-panel, Linear,
353
+ * Notion databases). Uppercase + tracking give it visual weight
354
+ * despite the small font. */
355
+ font-size: 10px;
356
+ font-weight: 700;
357
+ letter-spacing: 0.7px;
358
+ text-transform: uppercase;
359
+ color: var(--annot-text-muted);
360
+ padding: 0 0 4px 0;
361
+ }
362
+ .pp-section-body {
363
+ display: flex;
364
+ flex-direction: column;
365
+ /* Was 6 px — tightened to 4 px (Figma-class density) so the
366
+ * Color / Width / Dash type / etc. property rows pack closer.
367
+ * Each row is already a complete visual unit (label + control
368
+ * with its own border/background) so it doesn't need much
369
+ * gap to stay readable. */
370
+ gap: 4px;
371
+ /* Indent rows so they hang off their category header. The
372
+ * uppercase tracked header stays flush at the section's left
373
+ * edge, establishing the outer column. */
374
+ padding-left: 10px;
375
+ }
376
+
377
+ /* Radio group — paint type choice (none / solid / gradient) */
378
+ .pp-radio-list {
379
+ display: flex;
380
+ flex-direction: column;
381
+ gap: 4px;
382
+ padding: 0 0 4px 12px;
383
+ }
384
+ .pp-radio {
385
+ display: flex;
386
+ align-items: center;
387
+ gap: 6px;
388
+ font-size: 13px;
389
+ color: var(--annot-text-primary);
390
+ cursor: pointer;
391
+ user-select: none;
392
+ }
393
+ .pp-radio input[type="radio"] {
394
+ accent-color: var(--annot-accent);
395
+ margin: 0;
396
+ cursor: pointer;
397
+ }
398
+
399
+ /* Two-column label-value row — the dominant layout in PowerPoint's
400
+ * side panel. Label hugs the left, control takes remaining width. */
401
+ .pp-row {
402
+ display: grid;
403
+ /* 120px label column accommodates the longest row labels
404
+ * ("Begin arrow type", "End arrow size", "Transparency") with a
405
+ * few px of breathing room. The remaining space goes to the
406
+ * control. Panel widened from 240→280 px in P1 gives us the
407
+ * extra width to work with. */
408
+ grid-template-columns: 120px 1fr;
409
+ align-items: center;
410
+ gap: 6px;
411
+ /* Was 28 px — tightened to 24 px to match the natural height
412
+ * of the in-row controls (`.pp-number` is 24 px, `.pp-color`
413
+ * 26 px). 28 px was adding dead vertical above/below each row
414
+ * for no visual benefit. */
415
+ min-height: 24px;
416
+ }
417
+ .pp-row-label {
418
+ /* Row / attribute label — subdued secondary color so the VALUE
419
+ * (color swatch, number, pulldown) reads as the primary content
420
+ * of each row. Paired with the uppercase category header above,
421
+ * the hierarchy reads as: CATEGORY (label) → attribute (value),
422
+ * matching professional design-tool property panels. */
423
+ font-size: 12px;
424
+ font-weight: 400;
425
+ color: var(--annot-text-secondary);
426
+ white-space: nowrap;
427
+ }
428
+
429
+ /* Numeric input with unit suffix — width (pt), transparency (%). */
430
+ .pp-number {
431
+ display: inline-flex;
432
+ align-items: center;
433
+ border: 1px solid var(--annot-border-color);
434
+ border-radius: 4px;
435
+ background: var(--bg-input, var(--annot-bg-panel));
436
+ overflow: hidden;
437
+ height: 24px;
438
+ min-width: 0;
439
+ }
440
+ .pp-number input {
441
+ border: none;
442
+ outline: none;
443
+ background: transparent;
444
+ color: var(--annot-text-primary);
445
+ font-size: 12px;
446
+ text-align: right;
447
+ width: 100%;
448
+ min-width: 0;
449
+ padding: 0 4px;
450
+ }
451
+ .pp-number input::-webkit-outer-spin-button,
452
+ .pp-number input::-webkit-inner-spin-button {
453
+ -webkit-appearance: none;
454
+ margin: 0;
455
+ }
456
+ .pp-number input[type="number"] {
457
+ -moz-appearance: textfield;
458
+ }
459
+ .pp-number-unit {
460
+ color: var(--annot-text-muted);
461
+ font-size: 11px;
462
+ padding-right: 4px;
463
+ user-select: none;
464
+ }
465
+
466
+ /* Slider paired with a numeric input — PowerPoint's transparency /
467
+ * brightness rows. The slider is the wide visual scrubber; the input
468
+ * gives precise values. */
469
+ .pp-slider-row {
470
+ display: grid;
471
+ /* Match the .pp-row label column so slider rows (Transparency, etc)
472
+ * line up with the rest of the label column. */
473
+ grid-template-columns: 108px 1fr 56px;
474
+ align-items: center;
475
+ gap: 6px;
476
+ min-height: 28px;
477
+ }
478
+ .pp-slider-row input[type="range"] {
479
+ width: 100%;
480
+ margin: 0;
481
+ accent-color: var(--annot-accent);
482
+ }
483
+
484
+ /* PowerPoint-style dropdown button — visual preview + caret. Popup
485
+ * content uses `.pp-select-popup` (inside the shared .tool-flyout). */
486
+ .pp-select {
487
+ display: inline-flex;
488
+ align-items: center;
489
+ justify-content: space-between;
490
+ gap: 4px;
491
+ height: 24px;
492
+ padding: 0 2px 0 6px;
493
+ background: var(--bg-input, var(--annot-bg-panel));
494
+ border: 1px solid var(--annot-border-color);
495
+ border-radius: 4px;
496
+ color: var(--annot-text-primary);
497
+ cursor: pointer;
498
+ font-size: 12px;
499
+ }
500
+ .pp-select:hover {
501
+ border-color: var(--annot-accent);
502
+ }
503
+ .pp-select:disabled {
504
+ opacity: 0.5;
505
+ cursor: not-allowed;
506
+ }
507
+ .pp-select-preview {
508
+ display: inline-flex;
509
+ align-items: center;
510
+ gap: 4px;
511
+ min-width: 0;
512
+ flex: 1;
513
+ }
514
+ .pp-select-preview svg {
515
+ display: block;
516
+ }
517
+ .pp-select-caret {
518
+ font-size: 16px;
519
+ color: var(--annot-text-muted);
520
+ }
521
+
522
+ /* Popup content for pp-select. Grid layout so icon galleries
523
+ * (arrow types) read as PowerPoint's visual pickers. */
524
+ .tool-flyout-pp-select {
525
+ padding: 6px;
526
+ /* Let the popup shrink to the widest row — PowerPoint's dropdowns
527
+ * size to content rather than a fixed min-width, which avoids the
528
+ * awkward empty band to the right of short previews (dashes, caps,
529
+ * joins). Callers can still opt into a larger minimum via the
530
+ * custom-select's `popupWidth` option when they need it. */
531
+ width: max-content;
532
+ }
533
+ .pp-select-grid {
534
+ display: grid;
535
+ grid-template-columns: 1fr;
536
+ gap: 2px;
537
+ }
538
+ .pp-select-item {
539
+ display: inline-flex;
540
+ align-items: center;
541
+ justify-content: flex-start;
542
+ gap: 6px;
543
+ padding: 4px 8px;
544
+ border: 1px solid transparent;
545
+ border-radius: 4px;
546
+ background: transparent;
547
+ color: var(--annot-text-primary);
548
+ font-size: 12px;
549
+ cursor: pointer;
550
+ text-align: left;
551
+ }
552
+ .pp-select-item:hover {
553
+ background: var(--annot-hover-bg);
554
+ }
555
+ .pp-select-item.active {
556
+ background: var(--annot-choice-hover, var(--annot-hover-bg));
557
+ border-color: var(--annot-accent);
558
+ color: var(--annot-accent);
559
+ }
560
+ .pp-select-item svg {
561
+ display: block;
562
+ flex-shrink: 0;
563
+ }
564
+
565
+ /* Color swatch button — the color pulldown affordance. */
566
+ .pp-color-btn {
567
+ width: 100%;
568
+ height: 24px;
569
+ padding: 2px;
570
+ background: var(--bg-input, var(--annot-bg-panel));
571
+ border: 1px solid var(--annot-border-color);
572
+ border-radius: 4px;
573
+ cursor: pointer;
574
+ display: flex;
575
+ align-items: center;
576
+ gap: 6px;
577
+ }
578
+ .pp-color-swatch {
579
+ width: 20px;
580
+ height: 16px;
581
+ border-radius: 2px;
582
+ border: 1px solid var(--annot-border-color);
583
+ flex-shrink: 0;
584
+ }
585
+ .pp-color-btn span:has(svg),
586
+ .pp-color-btn > span:last-child {
587
+ font-size: 16px;
588
+ color: var(--annot-text-muted);
589
+ margin-left: auto;
590
+ }
591
+
592
+ /* "No fill" state for color swatches — diagonal strike from top-right
593
+ * to bottom-left over a white background. Same visual as PowerPoint's
594
+ * "No fill" chip so the meaning reads without needing a label. */
595
+ .pp-color-swatch-none {
596
+ background:
597
+ linear-gradient(
598
+ to top left,
599
+ transparent 0 calc(50% - 0.8px),
600
+ var(--annot-accent, #e74c3c) calc(50% - 0.8px) calc(50% + 0.8px),
601
+ transparent calc(50% + 0.8px) 100%
602
+ ),
603
+ #ffffff;
604
+ }
605
+
606
+ /* "No fill" button inside the color-pull popover. Shown ABOVE the
607
+ * palette grid for shape-fill pickers that support absence as a
608
+ * meaningful choice. */
609
+ .pp-color-none-btn {
610
+ display: block;
611
+ width: 100%;
612
+ padding: 6px 8px;
613
+ margin-bottom: 8px;
614
+ background: var(--bg-input, var(--annot-bg-panel));
615
+ color: var(--annot-text-primary);
616
+ border: 1px solid var(--annot-border-color);
617
+ border-radius: 4px;
618
+ cursor: pointer;
619
+ font-size: 12px;
620
+ text-align: left;
621
+ }
622
+ .pp-color-none-btn:hover {
623
+ background: var(--annot-hover-bg);
624
+ border-color: var(--annot-accent);
625
+ }
626
+
627
+ /* Chip-row container for variant/type pickers inside a pp-section.
628
+ * Mirrors the existing `prop-choice-horizontal` but lives inside a
629
+ * pp-section-body so the padding matches other pp-rows. */
630
+ .pp-type-row {
631
+ display: flex;
632
+ gap: 6px;
633
+ flex-wrap: wrap;
634
+ /* Leave headroom for the active-state outline ring (see
635
+ * .pp-color-chip.active below) so the ring doesn't clip against
636
+ * the section edge. */
637
+ padding: 3px;
638
+ }
639
+
640
+ /* Color-swatch chip — used by the Highlight Type picker. The chip
641
+ * frame inherits base .prop-choice-chip styling (32×32 transparent
642
+ * shell, accent border/bg on hover+active), matching other Type
643
+ * chips. The preview color lives on an INNER ::before square
644
+ * (--swatch-color driven by JS), sized smaller than the chip so
645
+ * the active-state accent border + bg tint show around it. This
646
+ * unifies the Highlight selection affordance with every other
647
+ * variant picker — no bespoke double-ring / checkmark needed. */
648
+ .prop-choice-chip.pp-color-chip::before {
649
+ content: "";
650
+ display: block;
651
+ width: 18px;
652
+ height: 18px;
653
+ border-radius: 3px;
654
+ background: var(--swatch-color, transparent);
655
+ /* Subtle inset stroke so light colors (e.g. yellow on a light
656
+ * theme) still have a visible edge against the chip frame. */
657
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12) inset;
658
+ }
659
+
660
+ /* Done button row — currently used by Freehand to explicitly end a
661
+ * continuous drawing session (draw.io-style). Full-width so it reads
662
+ * as a terminal action, distinct from the setting rows above. */
663
+ .pp-done-row {
664
+ display: flex;
665
+ padding: 8px 0 2px 0;
666
+ }
667
+ .pp-done-btn {
668
+ width: 100%;
669
+ padding: 8px 12px;
670
+ border: 1px solid var(--annot-accent);
671
+ border-radius: 6px;
672
+ background: var(--annot-accent);
673
+ color: var(--annot-bg-panel, #fff);
674
+ font-size: 13px;
675
+ font-weight: 600;
676
+ cursor: pointer;
677
+ transition:
678
+ opacity 0.12s,
679
+ transform 0.08s;
680
+ }
681
+ .pp-done-btn:hover {
682
+ opacity: 0.9;
683
+ }
684
+ .pp-done-btn:active {
685
+ transform: translateY(1px);
686
+ }
687
+
688
+ /* Safety: keep PP widgets inside the panel width even when the content
689
+ * would otherwise force horizontal overflow (long labels, wide
690
+ * previews, etc). `min-width: 0` is the well-known grid escape hatch
691
+ * that lets flex/grid children shrink below their content's natural
692
+ * width instead of overflowing their parent. */
693
+ .pp-row > *,
694
+ .pp-slider-row > * {
695
+ min-width: 0;
696
+ }
697
+ .pp-section,
698
+ .pp-section-body {
699
+ min-width: 0;
700
+ overflow: hidden;
701
+ }
702
+ .pp-select {
703
+ max-width: 100%;
704
+ min-width: 0;
705
+ }
706
+ .pp-select-preview svg {
707
+ max-width: 100%;
708
+ height: auto;
709
+ }
710
+
711
+ /* Icon-grid variant of the custom-select popup (arrow type / size
712
+ * pickers). PowerPoint renders these as uniform icon buttons — no
713
+ * text labels, centered previews, tighter padding. We apply these
714
+ * rules unconditionally since text-only options stay centered
715
+ * cleanly too. */
716
+ .pp-select-grid {
717
+ gap: 2px;
718
+ }
719
+ .pp-select-item {
720
+ justify-content: center;
721
+ padding: 6px;
722
+ min-height: 28px;
723
+ }
724
+ /* When a grid has multiple columns (set inline by JS), items become
725
+ * uniform square-ish cells matching PowerPoint's gallery look. */
726
+ .pp-select-grid[style*="repeat"] .pp-select-item {
727
+ aspect-ratio: auto;
728
+ min-width: 0;
729
+ }
730
+
731
+ /* Custom up/down spinner for pp-number (PowerPoint-style). The
732
+ * browser's native spin buttons are suppressed above so these
733
+ * stacked caret buttons take over. Chevrons are drawn with CSS
734
+ * borders so no extra font / SVG is needed. */
735
+ .pp-number-spinner {
736
+ display: flex;
737
+ flex-direction: column;
738
+ border-left: 1px solid var(--annot-border-color);
739
+ align-self: stretch;
740
+ }
741
+ .pp-number-spinner button {
742
+ flex: 1;
743
+ width: 14px;
744
+ min-height: 10px;
745
+ padding: 0;
746
+ margin: 0;
747
+ border: none;
748
+ background: transparent;
749
+ color: var(--annot-text-muted);
750
+ cursor: pointer;
751
+ display: flex;
752
+ align-items: center;
753
+ justify-content: center;
754
+ transition:
755
+ background 0.1s,
756
+ color 0.1s;
757
+ }
758
+ .pp-number-spinner button:hover {
759
+ background: var(--annot-hover-bg);
760
+ color: var(--annot-accent);
761
+ }
762
+ .pp-number-spinner button:active {
763
+ background: var(--annot-choice-hover, var(--annot-hover-bg));
764
+ }
765
+ .pp-number-spin-up::before,
766
+ .pp-number-spin-down::before {
767
+ content: "";
768
+ display: block;
769
+ width: 0;
770
+ height: 0;
771
+ border-left: 3px solid transparent;
772
+ border-right: 3px solid transparent;
773
+ }
774
+ .pp-number-spin-up::before {
775
+ border-bottom: 4px solid currentColor;
776
+ }
777
+ .pp-number-spin-down::before {
778
+ border-top: 4px solid currentColor;
779
+ }