fundus 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +4 -3
  2. package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css +1 -0
  3. package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css.br +0 -0
  4. package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css.gz +0 -0
  5. package/build/client/_app/immutable/chunks/B9HdDE5n.js +1 -0
  6. package/build/client/_app/immutable/chunks/B9HdDE5n.js.br +0 -0
  7. package/build/client/_app/immutable/chunks/B9HdDE5n.js.gz +0 -0
  8. package/build/client/_app/immutable/chunks/{DFRlZOKz.js → CAQ5okPf.js} +2 -2
  9. package/build/client/_app/immutable/chunks/CAQ5okPf.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/CAQ5okPf.js.gz +0 -0
  11. package/build/client/_app/immutable/chunks/Ccz62-Hi.js +1 -0
  12. package/build/client/_app/immutable/chunks/Ccz62-Hi.js.br +0 -0
  13. package/build/client/_app/immutable/chunks/Ccz62-Hi.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/ChVOZl2x.js +1 -0
  15. package/build/client/_app/immutable/chunks/ChVOZl2x.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/ChVOZl2x.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/CiBv7EZv.js +108 -0
  18. package/build/client/_app/immutable/chunks/CiBv7EZv.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/CiBv7EZv.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/UFAWUL-4.js +1 -0
  21. package/build/client/_app/immutable/chunks/UFAWUL-4.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/UFAWUL-4.js.gz +0 -0
  23. package/build/client/_app/immutable/chunks/YWIvyYJl.js +1 -0
  24. package/build/client/_app/immutable/chunks/YWIvyYJl.js.br +0 -0
  25. package/build/client/_app/immutable/chunks/YWIvyYJl.js.gz +0 -0
  26. package/build/client/_app/immutable/chunks/{BMORe2Gb.js → YpiA5Xry.js} +1 -1
  27. package/build/client/_app/immutable/chunks/YpiA5Xry.js.br +0 -0
  28. package/build/client/_app/immutable/chunks/{BMORe2Gb.js.gz → YpiA5Xry.js.gz} +0 -0
  29. package/build/client/_app/immutable/chunks/{DpFtnEPx.js → jjXOSnwf.js} +1 -1
  30. package/build/client/_app/immutable/chunks/jjXOSnwf.js.br +0 -0
  31. package/build/client/_app/immutable/chunks/jjXOSnwf.js.gz +0 -0
  32. package/build/client/_app/immutable/entry/app.Rv8f5zSo.js +2 -0
  33. package/build/client/_app/immutable/entry/app.Rv8f5zSo.js.br +0 -0
  34. package/build/client/_app/immutable/entry/app.Rv8f5zSo.js.gz +0 -0
  35. package/build/client/_app/immutable/entry/start.C5jRg_CF.js +1 -0
  36. package/build/client/_app/immutable/entry/start.C5jRg_CF.js.br +1 -0
  37. package/build/client/_app/immutable/entry/start.C5jRg_CF.js.gz +0 -0
  38. package/build/client/_app/immutable/nodes/0.Gv5d6CcW.js +1 -0
  39. package/build/client/_app/immutable/nodes/0.Gv5d6CcW.js.br +0 -0
  40. package/build/client/_app/immutable/nodes/0.Gv5d6CcW.js.gz +0 -0
  41. package/build/client/_app/immutable/nodes/1.8J9y5zyF.js +1 -0
  42. package/build/client/_app/immutable/nodes/1.8J9y5zyF.js.br +0 -0
  43. package/build/client/_app/immutable/nodes/1.8J9y5zyF.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/2.BNvTqsKZ.js +3 -0
  45. package/build/client/_app/immutable/nodes/2.BNvTqsKZ.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/2.BNvTqsKZ.js.gz +0 -0
  47. package/build/client/_app/immutable/nodes/3.CmlRI2XA.js +1 -0
  48. package/build/client/_app/immutable/nodes/3.CmlRI2XA.js.br +0 -0
  49. package/build/client/_app/immutable/nodes/3.CmlRI2XA.js.gz +0 -0
  50. package/build/client/_app/immutable/nodes/4.CHTKk5QR.js +2 -0
  51. package/build/client/_app/immutable/nodes/4.CHTKk5QR.js.br +0 -0
  52. package/build/client/_app/immutable/nodes/4.CHTKk5QR.js.gz +0 -0
  53. package/build/client/_app/version.json +1 -1
  54. package/build/client/_app/version.json.br +0 -0
  55. package/build/client/_app/version.json.gz +0 -0
  56. package/build/handler.js +7 -7
  57. package/build/index.js +7 -7
  58. package/build/server/chunks/chunks/{exports.js-aVVdkXgn.js → exports.js-BcB5q0h5.js} +2 -2
  59. package/build/server/chunks/chunks/{exports.js-aVVdkXgn.js.map → exports.js-BcB5q0h5.js.map} +1 -1
  60. package/build/server/chunks/chunks/{internal.js-C6cIfe5d.js → internal.js-B6eePlP4.js} +2 -2
  61. package/build/server/chunks/chunks/{internal.js-C6cIfe5d.js.map → internal.js-B6eePlP4.js.map} +1 -1
  62. package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
  63. package/build/server/chunks/chunks/{shared.js-BNd0HSxp.js → shared.js-BO7vJlPc.js} +2 -2
  64. package/build/server/chunks/chunks/{shared.js-BNd0HSxp.js.map → shared.js-BO7vJlPc.js.map} +1 -1
  65. package/build/server/chunks/chunks/{utils.js-6HSqnONB.js → utils.js-BTV5xnNv.js} +3 -3
  66. package/build/server/chunks/chunks/{utils.js-6HSqnONB.js.map → utils.js-BTV5xnNv.js.map} +1 -1
  67. package/build/server/chunks/{handler-rG16rikT.js → handler-DQdjBhko.js} +5 -5
  68. package/build/server/chunks/{handler-rG16rikT.js.map → handler-DQdjBhko.js.map} +1 -1
  69. package/build/server/chunks/{index.js-DYK_SfL5.js → index.js-DtKY5QD2.js} +5 -5
  70. package/build/server/chunks/{index.js-DYK_SfL5.js.map → index.js-DtKY5QD2.js.map} +1 -1
  71. package/build/server/chunks/{manifest.js-h2lDpEPs.js → manifest.js-CJMiboeI.js} +7 -7
  72. package/build/server/chunks/{manifest.js-h2lDpEPs.js.map → manifest.js-CJMiboeI.js.map} +1 -1
  73. package/build/server/chunks/nodes/{0.js-Db28RwnA.js → 0.js-Ce__dNtc.js} +2 -2
  74. package/build/server/chunks/nodes/{0.js-Db28RwnA.js.map → 0.js-Ce__dNtc.js.map} +1 -1
  75. package/build/server/chunks/nodes/1.js-BIwe5V8L.js +7 -0
  76. package/build/server/chunks/nodes/{1.js-D26Uv3YT.js.map → 1.js-BIwe5V8L.js.map} +1 -1
  77. package/build/server/chunks/nodes/2.js-Bj7c0eH1.js +7 -0
  78. package/build/server/chunks/nodes/{2.js-BeRlEFid.js.map → 2.js-Bj7c0eH1.js.map} +1 -1
  79. package/build/server/chunks/nodes/3.js-BMOf9FTK.js +7 -0
  80. package/build/server/chunks/nodes/{3.js-BRUNmCyG.js.map → 3.js-BMOf9FTK.js.map} +1 -1
  81. package/build/server/chunks/nodes/4.js-CNLb7Xc5.js +7 -0
  82. package/build/server/chunks/nodes/{4.js-CTHWqDf1.js.map → 4.js-CNLb7Xc5.js.map} +1 -1
  83. package/dist/runtime/chroma-key-video-lifecycle.d.ts +2 -2
  84. package/dist/runtime/chroma-key-video-lifecycle.js +221 -124
  85. package/dist/runtime/components/ChromaKeyVideo.svelte +13 -2
  86. package/dist/runtime/components/Slice.svelte +22 -12
  87. package/dist/runtime/components/Slice.svelte.d.ts +3 -6
  88. package/dist/runtime/components/SliceCanvas.svelte +51 -40
  89. package/dist/runtime/components/SliceCanvas.svelte.d.ts +2 -2
  90. package/dist/runtime/components/SliceDom.svelte +96 -42
  91. package/dist/runtime/components/SliceDom.svelte.d.ts +2 -2
  92. package/dist/runtime/slice-source-lifecycle.d.ts +14 -0
  93. package/dist/runtime/slice-source-lifecycle.js +70 -0
  94. package/package.json +4 -1
  95. package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css +0 -1
  96. package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css.br +0 -0
  97. package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css.gz +0 -0
  98. package/build/client/_app/immutable/chunks/BAva9YXm.js +0 -1
  99. package/build/client/_app/immutable/chunks/BAva9YXm.js.br +0 -0
  100. package/build/client/_app/immutable/chunks/BAva9YXm.js.gz +0 -0
  101. package/build/client/_app/immutable/chunks/BGAUmugt.js +0 -1
  102. package/build/client/_app/immutable/chunks/BGAUmugt.js.br +0 -0
  103. package/build/client/_app/immutable/chunks/BGAUmugt.js.gz +0 -0
  104. package/build/client/_app/immutable/chunks/BMORe2Gb.js.br +0 -0
  105. package/build/client/_app/immutable/chunks/CBygkKLq.js +0 -1
  106. package/build/client/_app/immutable/chunks/CBygkKLq.js.br +0 -0
  107. package/build/client/_app/immutable/chunks/CBygkKLq.js.gz +0 -0
  108. package/build/client/_app/immutable/chunks/Cil0r9BR.js +0 -108
  109. package/build/client/_app/immutable/chunks/Cil0r9BR.js.br +0 -0
  110. package/build/client/_app/immutable/chunks/Cil0r9BR.js.gz +0 -0
  111. package/build/client/_app/immutable/chunks/D-p-QSzz.js +0 -1
  112. package/build/client/_app/immutable/chunks/D-p-QSzz.js.br +0 -0
  113. package/build/client/_app/immutable/chunks/D-p-QSzz.js.gz +0 -0
  114. package/build/client/_app/immutable/chunks/D15yxLq9.js +0 -1
  115. package/build/client/_app/immutable/chunks/D15yxLq9.js.br +0 -0
  116. package/build/client/_app/immutable/chunks/D15yxLq9.js.gz +0 -0
  117. package/build/client/_app/immutable/chunks/DFRlZOKz.js.br +0 -0
  118. package/build/client/_app/immutable/chunks/DFRlZOKz.js.gz +0 -0
  119. package/build/client/_app/immutable/chunks/DpFtnEPx.js.br +0 -4
  120. package/build/client/_app/immutable/chunks/DpFtnEPx.js.gz +0 -0
  121. package/build/client/_app/immutable/entry/app.BaP08B0_.js +0 -2
  122. package/build/client/_app/immutable/entry/app.BaP08B0_.js.br +0 -0
  123. package/build/client/_app/immutable/entry/app.BaP08B0_.js.gz +0 -0
  124. package/build/client/_app/immutable/entry/start.DntfygIK.js +0 -1
  125. package/build/client/_app/immutable/entry/start.DntfygIK.js.br +0 -0
  126. package/build/client/_app/immutable/entry/start.DntfygIK.js.gz +0 -0
  127. package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js +0 -1
  128. package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js.br +0 -0
  129. package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js.gz +0 -0
  130. package/build/client/_app/immutable/nodes/1.8K7wLW9R.js +0 -1
  131. package/build/client/_app/immutable/nodes/1.8K7wLW9R.js.br +0 -0
  132. package/build/client/_app/immutable/nodes/1.8K7wLW9R.js.gz +0 -0
  133. package/build/client/_app/immutable/nodes/2.CB_PeUBC.js +0 -3
  134. package/build/client/_app/immutable/nodes/2.CB_PeUBC.js.br +0 -0
  135. package/build/client/_app/immutable/nodes/2.CB_PeUBC.js.gz +0 -0
  136. package/build/client/_app/immutable/nodes/3.qIkVQyP6.js +0 -1
  137. package/build/client/_app/immutable/nodes/3.qIkVQyP6.js.br +0 -0
  138. package/build/client/_app/immutable/nodes/3.qIkVQyP6.js.gz +0 -0
  139. package/build/client/_app/immutable/nodes/4.SmoVIy5D.js +0 -2
  140. package/build/client/_app/immutable/nodes/4.SmoVIy5D.js.br +0 -0
  141. package/build/client/_app/immutable/nodes/4.SmoVIy5D.js.gz +0 -0
  142. package/build/server/chunks/nodes/1.js-D26Uv3YT.js +0 -7
  143. package/build/server/chunks/nodes/2.js-BeRlEFid.js +0 -7
  144. package/build/server/chunks/nodes/3.js-BRUNmCyG.js +0 -7
  145. package/build/server/chunks/nodes/4.js-CTHWqDf1.js +0 -7
@@ -13,25 +13,28 @@
13
13
  canvas is enlarged by the overdraw's destination size and offset negatively
14
14
  (see `computeCanvasPaintLayout`), so the baked shadow/glow paints outside
15
15
  the layout box while the wrapper (and therefore the layout) keeps the box
16
- size — exactly where the DOM backend positions its overdraw cells.
16
+ size — exactly where the DOM backend positions its border-image paint.
17
17
  -->
18
18
  <script lang="ts">
19
- import type { SliceManifestEntry } from '../../core/index.js';
20
- import { computeCanvasPaintLayout, computeRenderCells } from '../geometry.js';
19
+ import {
20
+ computeCanvasPaintLayout,
21
+ computeRenderCells,
22
+ type CanvasPaintLayout
23
+ } from '../geometry.js';
21
24
  import { observeBorderBoxSize } from '../observe-border-box-size.js';
25
+ import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
22
26
 
23
27
  interface Props {
24
- src: SliceManifestEntry;
28
+ snapshot: LoadedSliceSnapshot | null;
25
29
  /** Accessible label for the rendered graphic. */
26
30
  alt?: string;
27
31
  class?: string;
28
32
  }
29
33
 
30
- let { src, alt = '', class: className = '' }: Props = $props();
34
+ let { snapshot, alt = '', class: className = '' }: Props = $props();
31
35
 
32
36
  let root = $state<HTMLDivElement>();
33
37
  let canvas = $state<HTMLCanvasElement>();
34
- let image = $state<HTMLImageElement>();
35
38
  let boxWidth = $state(0);
36
39
  let boxHeight = $state(0);
37
40
  let devicePixelRatio = $state(1);
@@ -39,23 +42,6 @@
39
42
  // box nor the entry changed (belt and braces alongside the ResizeObserver).
40
43
  let redrawTick = $state(0);
41
44
 
42
- // Load the source image once per entry `src` — the same plain URL the DOM
43
- // backend paints (slices are image-backed: the browser cache owns the bytes,
44
- // preload retains nothing to resolve).
45
- $effect(() => {
46
- const sourceUrl = src.src;
47
- const next = new Image();
48
- let cancelled = false;
49
- next.onload = () => {
50
- if (!cancelled) image = next;
51
- };
52
- next.src = sourceUrl;
53
- return () => {
54
- cancelled = true;
55
- next.onload = null;
56
- };
57
- });
58
-
59
45
  // Track the display's devicePixelRatio: a resolution media query fires
60
46
  // exactly when the DPR leaves the tracked value (browser zoom, moving the
61
47
  // window to another display). The tracked state must be READ first — a
@@ -83,40 +69,65 @@
83
69
  });
84
70
  });
85
71
 
86
- const layout = $derived(computeCanvasPaintLayout(src, boxWidth, boxHeight, devicePixelRatio));
72
+ const emptyLayout: CanvasPaintLayout = {
73
+ cssLeft: 0,
74
+ cssTop: 0,
75
+ cssWidth: 0,
76
+ cssHeight: 0,
77
+ backingWidth: 0,
78
+ backingHeight: 0,
79
+ originX: 0,
80
+ originY: 0
81
+ };
82
+ const layout = $derived.by(() => {
83
+ const current = snapshot;
84
+ return current
85
+ ? computeCanvasPaintLayout(current.entry, boxWidth, boxHeight, devicePixelRatio)
86
+ : emptyLayout;
87
+ });
87
88
 
88
- // Redraw when the box size, image, entry, DPR, or window changes.
89
+ // Redraw only when a committed snapshot, the box, DPR, or window changes.
89
90
  $effect(() => {
90
- void image;
91
- void src;
92
- void layout;
91
+ const current = snapshot;
92
+ const currentLayout = layout;
93
93
  void redrawTick;
94
- draw();
94
+ draw(current, currentLayout);
95
95
  });
96
96
 
97
- function draw(): void {
97
+ function draw(current: LoadedSliceSnapshot | null, currentLayout: CanvasPaintLayout): void {
98
98
  const element = canvas;
99
- const source = image;
100
- if (!element || !source) return;
101
- if (boxWidth === 0 || boxHeight === 0) {
99
+ if (!element) return;
100
+ if (!current || boxWidth === 0 || boxHeight === 0) {
102
101
  element.width = 0;
103
102
  element.height = 0;
104
103
  return;
105
104
  }
106
105
 
107
- element.width = layout.backingWidth;
108
- element.height = layout.backingHeight;
106
+ element.width = currentLayout.backingWidth;
107
+ element.height = currentLayout.backingHeight;
109
108
 
110
109
  const context = element.getContext('2d');
111
110
  if (!context) return;
112
111
  // Origin at the layout box's top-left, so the (possibly negative) cell
113
112
  // coordinates from computeRenderCells apply directly.
114
- context.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, layout.originX, layout.originY);
115
- context.clearRect(layout.cssLeft, layout.cssTop, layout.cssWidth, layout.cssHeight);
116
-
117
- for (const cell of computeRenderCells(src, boxWidth, boxHeight)) {
113
+ context.setTransform(
114
+ devicePixelRatio,
115
+ 0,
116
+ 0,
117
+ devicePixelRatio,
118
+ currentLayout.originX,
119
+ currentLayout.originY
120
+ );
121
+ context.clearRect(
122
+ currentLayout.cssLeft,
123
+ currentLayout.cssTop,
124
+ currentLayout.cssWidth,
125
+ currentLayout.cssHeight
126
+ );
127
+
128
+ for (const cell of computeRenderCells(current.entry, boxWidth, boxHeight)) {
118
129
  context.drawImage(
119
- source,
130
+ current.image,
120
131
  cell.sx,
121
132
  cell.sy,
122
133
  cell.sw,
@@ -1,6 +1,6 @@
1
- import type { SliceManifestEntry } from '../../core/index.js';
1
+ import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
2
2
  interface Props {
3
- src: SliceManifestEntry;
3
+ snapshot: LoadedSliceSnapshot | null;
4
4
  /** Accessible label for the rendered graphic. */
5
5
  alt?: string;
6
6
  class?: string;
@@ -5,33 +5,96 @@
5
5
  in the entry. Private: hosts render `Slice.svelte`, which dispatches between
6
6
  this and the canvas backend.
7
7
 
8
- Fills its container and measures it (ResizeObserver), then paints one cell per
9
- slice region as an absolutely-positioned window onto the source image: a full
10
- copy of the image is scaled and offset (percentages relative to the cell) so
11
- the cell shows exactly its source region. Cells abut on integer pixels; corners
12
- / caps keep their aspect ratio (no distortion), the stretch bands fill the rest.
13
- With overdraw the cells extend past the container (negative offsets), so the
14
- root switches to overflow: visible and the baked shadow/glow draws outside the
15
- layout box.
8
+ Fills its container and measures it (ResizeObserver), then paints the complete
9
+ slice through one CSS border-image. The browser still stretches the same source
10
+ regions computed by `computeRenderCells`, but rasterizes them as one border
11
+ image instead of separate clipped elements. Shared slice edges therefore cannot
12
+ expose the background under fractional transforms.
13
+ With overdraw the paint element extends past the container (negative offsets),
14
+ so the root switches to overflow: visible and the baked shadow/glow draws
15
+ outside the layout box.
16
16
  -->
17
17
  <script lang="ts">
18
- import type { SliceManifestEntry } from '../../core/index.js';
19
- import { computeRenderCells, effectiveOverdraw } from '../geometry.js';
18
+ import type { SliceInsets, SliceManifestEntry } from '../../core/index.js';
19
+ import { computeRenderCells, effectiveInsets, effectiveOverdraw } from '../geometry.js';
20
20
  import { observeBorderBoxSize } from '../observe-border-box-size.js';
21
+ import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
21
22
 
22
23
  interface Props {
23
- src: SliceManifestEntry;
24
+ snapshot: LoadedSliceSnapshot | null;
24
25
  /** Accessible label for the rendered graphic. */
25
26
  alt?: string;
26
27
  class?: string;
27
28
  }
28
29
 
29
- let { src, alt = '', class: className = '' }: Props = $props();
30
+ let { snapshot, alt = '', class: className = '' }: Props = $props();
30
31
 
31
32
  let root = $state<HTMLDivElement>();
32
33
  let boxWidth = $state(0);
33
34
  let boxHeight = $state(0);
34
35
 
36
+ interface BorderImagePaint {
37
+ left: number;
38
+ top: number;
39
+ width: number;
40
+ height: number;
41
+ slices: SliceInsets;
42
+ widths: SliceInsets;
43
+ }
44
+
45
+ function clamp(value: number, min: number, max: number): number {
46
+ return Math.max(min, Math.min(max, value));
47
+ }
48
+
49
+ function computeBorderImagePaint(
50
+ entry: SliceManifestEntry,
51
+ width: number,
52
+ height: number
53
+ ): BorderImagePaint | null {
54
+ const cells = computeRenderCells(entry, width, height);
55
+ if (cells.length === 0) return null;
56
+
57
+ const effective = effectiveInsets(entry);
58
+ const leftSlice = clamp(effective.left, 0, entry.sourceWidth);
59
+ const rightSlice = clamp(effective.right, 0, entry.sourceWidth - leftSlice);
60
+ const topSlice = clamp(effective.top, 0, entry.sourceHeight);
61
+ const bottomSlice = clamp(effective.bottom, 0, entry.sourceHeight - topSlice);
62
+ const slices = {
63
+ top: topSlice,
64
+ right: rightSlice,
65
+ bottom: bottomSlice,
66
+ left: leftSlice
67
+ };
68
+ const left = Math.min(...cells.map((cell) => cell.dx));
69
+ const top = Math.min(...cells.map((cell) => cell.dy));
70
+ const right = Math.max(...cells.map((cell) => cell.dx + cell.dw));
71
+ const bottom = Math.max(...cells.map((cell) => cell.dy + cell.dh));
72
+
73
+ return {
74
+ left,
75
+ top,
76
+ width: right - left,
77
+ height: bottom - top,
78
+ slices,
79
+ widths: {
80
+ top: cells.find((cell) => topSlice > 0 && cell.sy === 0 && cell.sh === topSlice)?.dh ?? 0,
81
+ right:
82
+ cells.find(
83
+ (cell) =>
84
+ rightSlice > 0 && cell.sx === entry.sourceWidth - rightSlice && cell.sw === rightSlice
85
+ )?.dw ?? 0,
86
+ bottom:
87
+ cells.find(
88
+ (cell) =>
89
+ bottomSlice > 0 &&
90
+ cell.sy === entry.sourceHeight - bottomSlice &&
91
+ cell.sh === bottomSlice
92
+ )?.dh ?? 0,
93
+ left: cells.find((cell) => leftSlice > 0 && cell.sx === 0 && cell.sw === leftSlice)?.dw ?? 0
94
+ }
95
+ };
96
+ }
97
+
35
98
  $effect(() => {
36
99
  const element = root;
37
100
  if (!element) return;
@@ -41,9 +104,11 @@
41
104
  });
42
105
  });
43
106
 
44
- const cells = $derived(computeRenderCells(src, boxWidth, boxHeight));
107
+ const entry = $derived(snapshot?.entry ?? null);
108
+ const paint = $derived(entry ? computeBorderImagePaint(entry, boxWidth, boxHeight) : null);
45
109
  const hasOverdraw = $derived.by(() => {
46
- const overdraw = effectiveOverdraw(src);
110
+ if (!entry) return false;
111
+ const overdraw = effectiveOverdraw(entry);
47
112
  return overdraw.top > 0 || overdraw.right > 0 || overdraw.bottom > 0 || overdraw.left > 0;
48
113
  });
49
114
  </script>
@@ -55,25 +120,18 @@
55
120
  role={alt ? 'img' : undefined}
56
121
  aria-label={alt || undefined}
57
122
  >
58
- {#each cells as cell (cell.key)}
123
+ {#if entry && paint}
59
124
  <div
60
- class="fundus-slice__cell"
61
- style:left="{cell.dx}px"
62
- style:top="{cell.dy}px"
63
- style:width="{cell.dw}px"
64
- style:height="{cell.dh}px"
65
- >
66
- <img
67
- src={src.src}
68
- alt=""
69
- draggable="false"
70
- style:width="{(src.sourceWidth / cell.sw) * 100}%"
71
- style:height="{(src.sourceHeight / cell.sh) * 100}%"
72
- style:left="{(-cell.sx / cell.sw) * 100}%"
73
- style:top="{(-cell.sy / cell.sh) * 100}%"
74
- />
75
- </div>
76
- {/each}
125
+ class="fundus-slice__paint"
126
+ style:left="{paint.left}px"
127
+ style:top="{paint.top}px"
128
+ style:width="{paint.width}px"
129
+ style:height="{paint.height}px"
130
+ style:border-image-source={`url(${JSON.stringify(entry.src)})`}
131
+ style:border-image-slice={`${paint.slices.top} ${paint.slices.right} ${paint.slices.bottom} ${paint.slices.left} fill`}
132
+ style:border-image-width={`${paint.widths.top}px ${paint.widths.right}px ${paint.widths.bottom}px ${paint.widths.left}px`}
133
+ ></div>
134
+ {/if}
77
135
  </div>
78
136
 
79
137
  <style>
@@ -84,21 +142,17 @@
84
142
  overflow: hidden;
85
143
  }
86
144
 
87
- /* Overdraw cells deliberately extend past the box — don't clip them. */
145
+ /* Overdraw deliberately extends the paint past the box — don't clip it. */
88
146
  .fundus-slice--overdraw {
89
147
  overflow: visible;
90
148
  }
91
149
 
92
- .fundus-slice__cell {
93
- position: absolute;
94
- overflow: hidden;
95
- }
96
-
97
- .fundus-slice__cell img {
150
+ .fundus-slice__paint {
98
151
  position: absolute;
99
- max-width: none;
100
- max-height: none;
152
+ box-sizing: border-box;
153
+ border-width: 0;
154
+ border-style: solid;
155
+ border-image-repeat: stretch;
101
156
  pointer-events: none;
102
- user-select: none;
103
157
  }
104
158
  </style>
@@ -1,6 +1,6 @@
1
- import type { SliceManifestEntry } from '../../core/index.js';
1
+ import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
2
2
  interface Props {
3
- src: SliceManifestEntry;
3
+ snapshot: LoadedSliceSnapshot | null;
4
4
  /** Accessible label for the rendered graphic. */
5
5
  alt?: string;
6
6
  class?: string;
@@ -0,0 +1,14 @@
1
+ import type { SliceManifestEntry } from '../core/index.js';
2
+ export interface LoadedSliceSnapshot {
3
+ entry: SliceManifestEntry;
4
+ image: HTMLImageElement;
5
+ }
6
+ export type SliceImageLoader = (url: string) => Promise<HTMLImageElement>;
7
+ export declare function loadSliceImage(url: string): Promise<HTMLImageElement>;
8
+ /** Coordinates requested slice entries with the last image-ready snapshot. */
9
+ export declare class SliceSourceLifecycle {
10
+ #private;
11
+ constructor(commit: (snapshot: LoadedSliceSnapshot) => void, loadImage?: SliceImageLoader);
12
+ request(entry: SliceManifestEntry): void;
13
+ dispose(): void;
14
+ }
@@ -0,0 +1,70 @@
1
+ export function loadSliceImage(url) {
2
+ const image = new Image();
3
+ image.decoding = 'async';
4
+ image.src = url;
5
+ return image.decode().then(() => image);
6
+ }
7
+ /** Coordinates requested slice entries with the last image-ready snapshot. */
8
+ export class SliceSourceLifecycle {
9
+ #loadImage;
10
+ #commit;
11
+ #requestedEntry = null;
12
+ #displayedSnapshot = null;
13
+ #inFlight = new Map();
14
+ #disposed = false;
15
+ constructor(commit, loadImage = loadSliceImage) {
16
+ this.#commit = commit;
17
+ this.#loadImage = loadImage;
18
+ }
19
+ request(entry) {
20
+ if (this.#disposed)
21
+ return;
22
+ this.#requestedEntry = entry;
23
+ const displayed = this.#displayedSnapshot;
24
+ if (displayed?.entry.src === entry.src) {
25
+ this.#commitSnapshot({ entry, image: displayed.image });
26
+ return;
27
+ }
28
+ if (this.#inFlight.has(entry.src))
29
+ return;
30
+ let promise;
31
+ try {
32
+ promise = Promise.resolve(this.#loadImage(entry.src));
33
+ }
34
+ catch (error) {
35
+ promise = Promise.reject(error);
36
+ }
37
+ const load = { promise };
38
+ this.#inFlight.set(entry.src, load);
39
+ void promise.then((image) => this.#settleSuccess(entry.src, load, image), () => this.#settleFailure(entry.src, load));
40
+ }
41
+ dispose() {
42
+ if (this.#disposed)
43
+ return;
44
+ this.#disposed = true;
45
+ this.#requestedEntry = null;
46
+ this.#displayedSnapshot = null;
47
+ this.#inFlight.clear();
48
+ }
49
+ #settleSuccess(url, load, image) {
50
+ if (this.#inFlight.get(url) !== load)
51
+ return;
52
+ this.#inFlight.delete(url);
53
+ if (this.#disposed)
54
+ return;
55
+ const requested = this.#requestedEntry;
56
+ if (!requested || requested.src !== url)
57
+ return;
58
+ this.#commitSnapshot({ entry: requested, image });
59
+ }
60
+ #settleFailure(url, load) {
61
+ if (this.#inFlight.get(url) === load)
62
+ this.#inFlight.delete(url);
63
+ }
64
+ #commitSnapshot(snapshot) {
65
+ if (this.#disposed)
66
+ return;
67
+ this.#displayedSnapshot = snapshot;
68
+ this.#commit(snapshot);
69
+ }
70
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fundus",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -67,7 +67,10 @@
67
67
  "@sveltejs/vite-plugin-svelte": "^7.1.2",
68
68
  "@types/ffprobe-static": "^2.0.3",
69
69
  "@types/node": "^24.13.3",
70
+ "@vitest/browser": "^3.2.7",
70
71
  "fuse.js": "^7.4.2",
72
+ "happy-dom": "^20.11.0",
73
+ "playwright": "^1.61.1",
71
74
  "prettier": "^3.8.3",
72
75
  "prettier-plugin-svelte": "^4.1.0",
73
76
  "publint": "^0.3.21",
@@ -1 +0,0 @@
1
- .playback.svelte-nchstv{align-items:center;gap:6px;width:100%;display:flex}.btn.svelte-nchstv{background:var(--panel-raised);border-radius:var(--radius);width:28px;height:28px;color:var(--text);border:1px solid #0000;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.btn.svelte-nchstv:hover:not(:disabled){border-color:var(--border)}.btn.svelte-nchstv:focus-visible{border-color:var(--accent);outline:none}.btn.svelte-nchstv:disabled{opacity:.4;cursor:not-allowed}.btn.active.svelte-nchstv{background:var(--accent-soft);color:var(--accent)}.btn.svelte-nchstv svg:where(.svelte-nchstv){width:16px;height:16px;display:block}.time.svelte-nchstv{color:var(--text-dim);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.volume-group.svelte-nchstv{align-items:center;gap:6px;margin-left:auto;display:inline-flex}.range.svelte-nchstv{appearance:none;background:var(--panel-raised);cursor:pointer;border-radius:999px;height:4px}.range.scrub.svelte-nchstv{flex:1;min-width:0;margin:0 2px}.range.volume.svelte-nchstv{flex:none;width:72px}.range.svelte-nchstv:disabled{opacity:.4;cursor:not-allowed}.range.svelte-nchstv::-webkit-slider-thumb{appearance:none;background:var(--accent);border:none;border-radius:50%;width:12px;height:12px}.range.svelte-nchstv::-moz-range-thumb{background:var(--accent);border:none;border-radius:50%;width:12px;height:12px}.range.svelte-nchstv:focus-visible{outline:none}.range.svelte-nchstv:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px var(--accent-soft)}.range.svelte-nchstv:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px var(--accent-soft)}.audio-waveform.svelte-1liihyn{flex-direction:column;gap:8px;width:100%;height:100%;min-height:0;display:flex}.wave.svelte-1liihyn{cursor:pointer;touch-action:none;flex:1;width:100%;min-height:0;display:block}.footer.svelte-1liihyn{flex:none;align-items:center;display:flex}.stage.svelte-1ww4cvx{border:1px solid var(--border);border-radius:8px;flex:1;justify-content:center;align-items:center;min-height:0;display:flex;overflow:auto}.audio-frame.svelte-1ww4cvx{width:min(90%,480px);height:128px}p.svelte-1ww4cvx{color:var(--text-dim)}.scrub-number.svelte-nhap92{flex:1;min-width:0;display:flex;position:relative}.input.svelte-nhap92{width:100%;min-width:0;color:var(--text);font-variant-numeric:tabular-nums;appearance:textfield;background:0 0;border:none;outline:none;flex:1;margin:0;padding:0;font-size:11px}.scrub-number.with-suffix.svelte-nhap92 .input:where(.svelte-nhap92){flex:none}.suffix.svelte-nhap92{color:var(--text-dim);font-variant-numeric:tabular-nums;-webkit-user-select:none;user-select:none;flex:none;margin-left:2px;font-size:11px}.scrub-number.right.svelte-nhap92 .input:where(.svelte-nhap92){text-align:right}.input.svelte-nhap92::-webkit-outer-spin-button{appearance:none;margin:0}.input.svelte-nhap92::-webkit-inner-spin-button{appearance:none;margin:0}.scrub-surface.svelte-nhap92{cursor:ew-resize;touch-action:none;position:absolute;inset:0}.number-field.svelte-368zap{flex-direction:column;gap:4px;min-width:0;display:flex}.caption.svelte-368zap{letter-spacing:.27px;color:#ffffffb2;-webkit-user-select:none;user-select:none;font-size:9px;font-weight:500;line-height:14px}.number-input.svelte-368zap{border-radius:4px;align-items:center;gap:4px;min-width:0;height:24px;padding:0 6px 0 0;display:flex;position:relative;overflow:hidden}.number-input.svelte-368zap:focus-within{border-color:var(--accent)}.fill.svelte-368zap{pointer-events:none;z-index:0;background:#4a4a4a;height:100%;position:absolute;top:0;left:0}.icon-slot.svelte-368zap{z-index:1;width:24px;height:24px;color:var(--text-dim);-webkit-user-select:none;user-select:none;flex:none;justify-content:center;align-items:center;font-size:11px;font-weight:500;display:flex;position:relative}.number-input.svelte-368zap .scrub-number{z-index:1}h3.svelte-1ol624f{letter-spacing:0;text-transform:none;color:#fff;margin:0 0 10px;font-size:11px;font-weight:400}.select-field.svelte-bczi6w{flex-direction:column;gap:4px;min-width:0;display:flex}.caption.svelte-bczi6w{letter-spacing:.27px;color:#ffffffb2;-webkit-user-select:none;user-select:none;font-size:9px;font-weight:500;line-height:14px}.select-input.svelte-bczi6w{border-radius:4px;align-items:center;gap:4px;min-width:0;height:24px;padding:0 6px 0 0;display:flex}.select-input.svelte-bczi6w:focus-within{border-color:var(--accent)}.icon-slot.svelte-bczi6w{width:24px;height:24px;color:var(--text-dim);-webkit-user-select:none;user-select:none;flex:none;justify-content:center;align-items:center;font-size:11px;font-weight:500;display:flex}select.svelte-bczi6w{width:100%;min-width:0;height:100%;color:var(--text);background:0 0;border:none;outline:none;flex:1;margin:0;padding:0;font-size:11px}section.svelte-m9vwwu{border-bottom:1px solid var(--border-soft);padding:12px}.hint.svelte-m9vwwu{color:var(--text-faint);margin:4px 0 0;font-size:11px}.info.svelte-m9vwwu{font-variant-numeric:tabular-nums;margin:0}.file.svelte-m9vwwu{color:var(--text-dim);word-break:break-all}section.svelte-1by8n8t{border-bottom:1px solid var(--border-soft);padding:12px}.field.svelte-1by8n8t{margin-bottom:8px}.fundus-slice.svelte-1820t2z{width:100%;height:100%;position:relative;overflow:hidden}.fundus-slice--overdraw.svelte-1820t2z{overflow:visible}.fundus-slice__cell.svelte-1820t2z{position:absolute;overflow:hidden}.fundus-slice__cell.svelte-1820t2z img:where(.svelte-1820t2z){pointer-events:none;-webkit-user-select:none;user-select:none;max-width:none;max-height:none;position:absolute}.fundus-slice-canvas.svelte-xdsbrz{width:100%;height:100%;position:relative}canvas.svelte-xdsbrz{pointer-events:none;display:block;position:absolute}.fundus-chroma-key-video.svelte-r30mxh{width:100%;height:100%;position:relative}canvas.svelte-r30mxh,img.svelte-r30mxh{width:100%;height:100%;display:block}video.svelte-r30mxh{visibility:hidden;pointer-events:none;width:100%;height:100%;position:absolute;inset:0}.stage.svelte-10eps7e{border:1px solid var(--border);border-radius:8px;flex:1;justify-content:center;align-items:center;min-height:0;display:flex;overflow:auto}.stage.svelte-10eps7e img{object-fit:contain;max-width:90%;max-height:90%}img.svelte-gdduef{object-fit:contain;max-width:100%;max-height:100%}section.svelte-1yd7n22{border-bottom:1px solid var(--border-soft);padding:12px}.modes.svelte-1yd7n22{flex-direction:column;gap:4px;display:flex}.modes.svelte-1yd7n22 button:where(.svelte-1yd7n22){text-align:left;background:var(--panel-raised);color:var(--text);border-radius:var(--radius);border:1px solid #0000;padding:6px 10px}.modes.svelte-1yd7n22 button:where(.svelte-1yd7n22):hover{border-color:var(--border)}.modes.svelte-1yd7n22 button.on:where(.svelte-1yd7n22){background:var(--accent-soft);border-color:var(--accent);color:var(--accent);font-weight:600}.insets.svelte-1yd7n22{grid-template-columns:1fr 1fr;gap:8px;min-width:0;display:grid}.insets.single.svelte-1yd7n22{grid-template-columns:1fr}.preview-panel.svelte-1mjaycs{flex-direction:column;height:100%;min-height:0;display:flex}.bar.svelte-1mjaycs{flex:none;align-items:center;gap:10px;padding:8px 0;display:flex}.size.svelte-1mjaycs{color:var(--text-dim);font-variant-numeric:tabular-nums}.backends.svelte-1mjaycs{background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);gap:2px;margin-left:auto;padding:2px;display:inline-flex}.backends.svelte-1mjaycs button:where(.svelte-1mjaycs){color:var(--text-dim);background:0 0;border:none;border-radius:4px;padding:2px 8px}.backends.svelte-1mjaycs button.on:where(.svelte-1mjaycs){background:var(--accent-soft);color:var(--accent);font-weight:600}.reset.svelte-1mjaycs{background:var(--panel-raised);border:1px solid var(--border);color:var(--text);border-radius:var(--radius);padding:4px 10px}.reset.svelte-1mjaycs:hover{border-color:var(--accent)}.stage.svelte-1mjaycs{border:1px solid var(--border);border-radius:var(--radius-large);flex:1;min-height:0;padding:24px;overflow:auto}.box.svelte-1mjaycs{position:relative;box-shadow:0 0 0 1px #ffffff1f}.box.svelte-1mjaycs .fill{position:absolute;inset:0}.handle.svelte-1mjaycs{touch-action:none;background:0 0;position:absolute}.handle.edge-right.svelte-1mjaycs{cursor:ew-resize;width:10px;height:100%;top:0;right:-4px}.handle.edge-bottom.svelte-1mjaycs{cursor:ns-resize;width:100%;height:10px;bottom:-4px;left:0}.handle.corner.svelte-1mjaycs{cursor:nwse-resize;background:var(--accent);width:16px;height:16px;box-shadow:0 0 0 2px var(--canvas);border-radius:3px;bottom:-6px;right:-6px}.canvas.svelte-1jfrg6d{flex-direction:column;height:100%;min-height:0;display:flex}.toolbar.svelte-1jfrg6d{flex:none;align-items:center;gap:8px;padding:8px 0;display:flex}.toolbar.svelte-1jfrg6d button:where(.svelte-1jfrg6d){background:var(--panel-raised);border:1px solid var(--border);color:var(--text);border-radius:var(--radius);padding:4px 10px}.toolbar.svelte-1jfrg6d button:where(.svelte-1jfrg6d):hover{border-color:var(--accent)}.toolbar.svelte-1jfrg6d button.active:where(.svelte-1jfrg6d){background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}.zoom.svelte-1jfrg6d{text-align:center;min-width:44px;color:var(--text-dim);font-variant-numeric:tabular-nums}.dims.svelte-1jfrg6d{color:var(--text-dim);font-variant-numeric:tabular-nums;margin-left:auto}.viewport.svelte-1jfrg6d{touch-action:none;cursor:grab;border:1px solid var(--border);border-radius:var(--radius-large);flex:1;min-height:0;position:relative;overflow:hidden}.viewport.panning.svelte-1jfrg6d{cursor:grabbing}.image-wrap.svelte-1jfrg6d{transform-origin:0 0;position:absolute;top:0;left:0;box-shadow:0 0 0 1px #ffffff14}.image-wrap.svelte-1jfrg6d img:where(.svelte-1jfrg6d){width:100%;height:100%;image-rendering:pixelated;pointer-events:none;-webkit-user-select:none;user-select:none;display:block}.center-rect.svelte-1jfrg6d{pointer-events:none;position:absolute}.center-rect.side-edges.svelte-1jfrg6d{border-left:1px dashed #0d99ff99;border-right:1px dashed #0d99ff99}.center-rect.cap-edges.svelte-1jfrg6d{border-top:1px dashed #0d99ff99;border-bottom:1px dashed #0d99ff99}.overdraw-band.svelte-1jfrg6d{pointer-events:none;background:#38bdf829;position:absolute}.line.svelte-1jfrg6d{background:var(--accent);touch-action:none;position:absolute}.line.overdraw-line.svelte-1jfrg6d{background:#38bdf8 padding-box content-box}.line.overdraw-line.svelte-1jfrg6d .tag:where(.svelte-1jfrg6d){background:#38bdf8}.line.v.svelte-1jfrg6d{cursor:ew-resize;box-sizing:content-box;background-clip:content-box;width:1px;height:100%;margin-left:-.5px;padding:0 7px;top:0;transform:translate(-7px)}.line.h.svelte-1jfrg6d{cursor:ns-resize;box-sizing:content-box;background-clip:content-box;width:100%;height:1px;margin-top:-.5px;padding:7px 0;left:0;transform:translateY(-7px)}.line.active.svelte-1jfrg6d{background-color:var(--text);background-clip:content-box}.tag.svelte-1jfrg6d{background:var(--accent);color:var(--text);font-variant-numeric:tabular-nums;pointer-events:none;border-radius:4px;padding:1px 5px;font-size:11px;font-weight:600;position:absolute}.line.v.svelte-1jfrg6d .tag:where(.svelte-1jfrg6d){top:4px;left:50%;transform:translate(-50%)}.line.h.svelte-1jfrg6d .tag:where(.svelte-1jfrg6d){top:50%;left:4px;transform:translateY(-50%)}.line.v.overdraw-line.svelte-1jfrg6d .tag:where(.svelte-1jfrg6d){top:auto;bottom:4px}.line.h.overdraw-line.svelte-1jfrg6d .tag:where(.svelte-1jfrg6d){left:auto;right:4px}.editor.svelte-1ea2lvt{flex-direction:column;flex:1;min-height:0;display:flex}.tabs.svelte-1ea2lvt{flex:none;gap:4px;margin-bottom:4px;display:inline-flex}.tabs.svelte-1ea2lvt button:where(.svelte-1ea2lvt){border-radius:var(--radius);color:var(--text-dim);background:0 0;border:none;padding:4px 10px}.tabs.svelte-1ea2lvt button.on:where(.svelte-1ea2lvt){background:var(--accent-soft);color:var(--accent)}.pane.svelte-1ea2lvt{flex:1;min-height:0}.empty.svelte-1ea2lvt{color:var(--text-dim);flex:1;justify-content:center;align-items:center;display:flex}section.svelte-ij1927{border-bottom:1px solid var(--border-soft);padding:12px}.field.svelte-ij1927{margin-bottom:8px}.stage.svelte-qls820{border:1px solid var(--border);border-radius:8px;flex:1;justify-content:center;align-items:center;min-height:0;display:flex;overflow:auto}.preview-column.svelte-qls820{flex-direction:column;align-items:center;gap:8px;display:flex}.video-frame.svelte-qls820{width:min(90%,480px)}.video-frame.svelte-qls820 video{width:100%;height:100%}p.svelte-qls820{color:var(--text-dim)}video.svelte-ejgo9x{object-fit:contain;max-width:100%;max-height:100%}.picker.svelte-56kutb{flex-direction:column;gap:4px;min-width:0;margin-bottom:8px;display:flex}.caption.svelte-56kutb{letter-spacing:.27px;color:#ffffffb2;-webkit-user-select:none;user-select:none;font-size:9px;font-weight:500;line-height:14px}.reference-input.svelte-56kutb{border-radius:4px;align-items:center;min-width:0;height:24px;padding:0;display:flex;position:relative}.reference-input.svelte-56kutb:focus-within{border-color:var(--accent)}.current.svelte-56kutb{min-width:0;height:100%;color:var(--text);text-align:left;background:0 0;border:none;outline:none;flex:1;align-items:center;gap:4px;padding:0 6px 0 0;display:flex}.current.empty.svelte-56kutb{color:var(--text-faint)}.preview-slot.svelte-56kutb{box-sizing:border-box;background:#4a4a4a;border-radius:3px 0 0 3px;flex:none;justify-content:center;align-items:center;width:24px;height:24px;margin-left:-1px;padding:2px;display:flex;overflow:hidden}.preview.svelte-56kutb{object-fit:contain;width:100%;height:100%}.missing-preview.svelte-56kutb{color:var(--warning);font-weight:700}.clear.svelte-56kutb{border:none;border-left:1px solid var(--border-soft);width:24px;height:24px;color:var(--text-dim);background:0 0;border-radius:0 3px 3px 0;outline:none;flex:none;padding:0}.clear.svelte-56kutb:hover{color:var(--danger)}.id.svelte-56kutb,.placeholder.svelte-56kutb{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.dangling.svelte-56kutb{color:var(--warning)}.dropdown.svelte-56kutb{z-index:10;background:var(--panel);border:1px solid var(--border);border-radius:var(--radius);max-height:240px;padding:4px;position:absolute;top:calc(100% + 4px);left:0;right:0;overflow-y:auto;box-shadow:0 8px 24px #00000080}.option.svelte-56kutb{border-radius:var(--radius);width:100%;color:var(--text);text-align:left;background:0 0;border:none;align-items:center;gap:6px;padding:4px 6px;display:flex}.option.svelte-56kutb .thumbnail:where(.svelte-56kutb){object-fit:contain;background:var(--canvas);border-radius:3px;flex:none;width:24px;height:24px}.option.svelte-56kutb:hover{background:var(--panel-raised)}.option.selected.svelte-56kutb{background:var(--accent-soft);color:var(--accent)}.hint.svelte-56kutb{color:var(--text-faint);margin:4px;font-size:11px}.color-field.svelte-gjs979{flex-direction:column;gap:4px;min-width:0;display:flex}.caption.svelte-gjs979{letter-spacing:.27px;color:#ffffffb2;-webkit-user-select:none;user-select:none;font-size:9px;font-weight:500;line-height:14px}.color-input.svelte-gjs979{border-radius:4px;align-items:center;gap:4px;min-width:0;height:24px;padding:0 6px 0 0;display:flex}.color-input.svelte-gjs979:focus-within{border-color:var(--accent)}.swatch-slot.svelte-gjs979{flex:none;justify-content:center;align-items:center;width:24px;height:24px;display:flex}.swatch.svelte-gjs979{cursor:pointer;background:0 0;border:1px solid #ffffff3d;border-radius:3px;width:16px;height:16px;margin:0;padding:0;overflow:hidden}.swatch.svelte-gjs979::-webkit-color-swatch-wrapper{padding:0}.swatch.svelte-gjs979::-webkit-color-swatch{border:none}.hex-input.svelte-gjs979{width:100%;min-width:0;color:var(--text);font-variant-numeric:tabular-nums;text-transform:uppercase;background:0 0;border:none;outline:none;flex:1;margin:0;padding:0;font-size:11px}section.svelte-rsjkek{border-bottom:1px solid var(--border-soft);padding:12px}.field.svelte-rsjkek{margin-bottom:8px}.stage.svelte-1y3xro5{border:1px solid var(--border);border-radius:8px;flex:1;justify-content:center;align-items:center;min-height:0;display:flex;overflow:auto}.preview-column.svelte-1y3xro5{flex-direction:column;align-items:center;gap:8px;display:flex}.compare-row.svelte-1y3xro5{justify-content:center;align-items:flex-start;gap:16px;width:100%;max-width:760px;display:flex}figure.svelte-1y3xro5{flex-direction:column;flex:1 1 0;align-items:center;gap:6px;min-width:0;margin:0;display:flex}figcaption.svelte-1y3xro5{text-transform:uppercase;letter-spacing:.06em;color:var(--text-faint);font-size:10px}.video-frame.svelte-1y3xro5{width:100%}.video-frame.svelte-1y3xro5 video{width:100%;height:100%}p.svelte-1y3xro5{color:var(--text-dim)}section.svelte-3jblpy{border-bottom:1px solid var(--border-soft);padding:12px}.field.svelte-3jblpy{margin-bottom:8px}.backdrop.svelte-1f5yh58{z-index:100;background:#0000008c;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.dialog.svelte-1f5yh58{background:var(--panel);border:1px solid var(--border);border-radius:8px;width:360px;padding:16px;box-shadow:0 12px 32px #00000080}h2.svelte-1f5yh58{margin:0 0 4px;font-size:12px}.file-name.svelte-1f5yh58{color:var(--text-dim);font-variant-numeric:tabular-nums;margin:0}.destination.svelte-1f5yh58{color:var(--text-faint);margin:2px 0 16px}.row.svelte-1f5yh58{align-items:center;gap:8px;margin-bottom:8px;display:flex}.label.svelte-1f5yh58{width:64px;color:var(--text-dim);flex:none}.row.svelte-1f5yh58 input:where(.svelte-1f5yh58){flex:1}.row.manifests.svelte-1f5yh58{align-items:flex-start}.row.manifests.svelte-1f5yh58>.label:where(.svelte-1f5yh58){line-height:28px}.manifest-list.svelte-1f5yh58{flex:1;max-height:132px;overflow-y:auto}.empty-manifests.svelte-1f5yh58{color:var(--text-faint);margin:0;font-size:11px}.segmented.svelte-1f5yh58{border:1px solid var(--border);border-radius:var(--radius);display:inline-flex;overflow:hidden}.segmented.svelte-1f5yh58 button:where(.svelte-1f5yh58){background:var(--panel-raised);color:var(--text-dim);border:none;padding:4px 12px}.segmented.svelte-1f5yh58 button.on:where(.svelte-1f5yh58){background:var(--accent);color:var(--text)}.hint.svelte-1f5yh58{color:var(--text-dim);margin:4px 0 0;font-size:11px}.hint.warn.svelte-1f5yh58,.error.svelte-1f5yh58{color:var(--warning)}.actions.svelte-1f5yh58{justify-content:flex-end;gap:8px;margin-top:16px;display:flex}.backdrop.svelte-n5o24b{z-index:100;background:#0000008c;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.dialog.svelte-n5o24b{border:1px solid var(--border);background:var(--panel);border-radius:8px;width:420px;padding:16px;box-shadow:0 12px 32px #00000080}h2.svelte-n5o24b{margin:0 0 4px;font-size:12px}.destination.svelte-n5o24b{color:var(--text-faint);margin:0 0 16px}.row.svelte-n5o24b{gap:12px;display:flex}.field.svelte-n5o24b{color:var(--text-dim);flex-direction:column;flex:1;gap:4px;margin-bottom:10px;display:flex}.field.wide.svelte-n5o24b{width:100%}.warning.svelte-n5o24b{color:var(--warning);margin:6px 0 0;font-size:11px}.actions.svelte-n5o24b{justify-content:flex-end;gap:8px;margin-top:16px;display:flex}.directory.svelte-ftwms1{background:var(--panel);border-right:1px solid var(--border);flex-direction:column;width:220px;min-width:180px;max-width:320px;min-height:0;display:flex}header.svelte-ftwms1{border-bottom:1px solid var(--border-soft);align-items:center;height:40px;padding:0 8px 0 12px;display:flex}header.svelte-ftwms1 strong:where(.svelte-ftwms1){font-size:11px}.actions.svelte-ftwms1{margin-left:auto;display:flex}.icon-button.svelte-ftwms1{width:24px;height:24px;color:var(--text-dim);border-radius:var(--radius-small);background:0 0;border:0}.icon-button.svelte-ftwms1:hover:not(:disabled){background:var(--panel-raised);color:var(--text)}.icon-button.danger.svelte-ftwms1:hover:not(:disabled){color:var(--danger)}.icon-button.svelte-ftwms1:disabled{opacity:.3;cursor:default}.tree.svelte-ftwms1{padding:6px 4px;overflow:auto}.folder-row.svelte-ftwms1,.asset-row.svelte-ftwms1{padding-left:calc((var(--depth) + 2) * 14px);display:flex}.row.svelte-ftwms1{color:var(--text-dim);border-radius:var(--radius-small);text-align:left;background:0 0;border:0;align-items:center;gap:6px;min-width:0;height:28px;display:flex}.row.special.svelte-ftwms1{width:100%;padding:0 8px}.row.folder.svelte-ftwms1{flex:1;padding:0 8px 0 2px}.row.asset.svelte-ftwms1{cursor:grab;flex:1;padding:0 8px 0 2px}.row.asset.svelte-ftwms1:active{cursor:grabbing}.row.svelte-ftwms1:hover,.row.selected.svelte-ftwms1{background:var(--panel-raised);color:var(--text)}.row.asset.current.svelte-ftwms1,.row.selected.svelte-ftwms1{background:var(--accent-soft);color:var(--accent)}.row.drop-target.svelte-ftwms1{outline:1px solid var(--accent);background:var(--accent-soft)}.glyph.svelte-ftwms1{width:14px;color:var(--text-faint);flex:none;justify-content:center;align-items:center;display:flex}.asset-glyph.svelte-ftwms1{font-size:7px}.asset-warning.svelte-ftwms1{color:var(--warning);flex:none;margin-left:auto}.label.svelte-ftwms1{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.count.svelte-ftwms1{color:var(--text-faint);margin-left:auto;font-size:10px}.dialog-row.svelte-ftwms1{align-items:center;gap:8px;display:flex}.dialog-row.svelte-ftwms1 span:where(.svelte-ftwms1){width:44px;color:var(--text-dim)}.dialog-row.svelte-ftwms1 input:where(.svelte-ftwms1){flex:1}.dialog-path.svelte-ftwms1,.dialog-text.svelte-ftwms1,.dialog-warning.svelte-ftwms1{margin:8px 0 0}.dialog-path.svelte-ftwms1{color:var(--text-faint)}.dialog-warning.svelte-ftwms1{color:var(--warning)}.context-menu.svelte-10jgqdh{z-index:200;background:var(--panel-raised);border:1px solid var(--border);border-radius:6px;width:168px;padding:4px;position:fixed;box-shadow:0 8px 24px #00000073}button.svelte-10jgqdh{width:100%;color:var(--text);font:inherit;text-align:left;background:0 0;border:0;border-radius:4px;padding:6px 8px}button.svelte-10jgqdh:hover,button.svelte-10jgqdh:focus-visible{background:var(--accent-soft);outline:none}button.danger.svelte-10jgqdh{color:var(--danger)}hr.svelte-10jgqdh{background:var(--border);border:0;height:1px;margin:4px 8px}
@@ -1 +0,0 @@
1
- import{F as e,H as t,I as n,K as r,O as i,P as a,S as o,Y as s,_ as c,i as l,j as u,k as d,nt as f,q as p,rt as m,st as h,u as g}from"./BMORe2Gb.js";import"./xihTtKlq.js";function _(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} kB`:`${(e/(1024*1024)).toFixed(2)} MB`}function v(e){return`${(e/1e3).toFixed(2)} s`}function y(e,t){let{maxWidth:n,...r}=e;return t===null||!Number.isFinite(t)||t<=0?r:{...r,maxWidth:Math.round(t)}}function b(e,t){return e.defaultPrevented||e.isComposing||t.busy?null:e.key===`Escape`?`cancel`:e.key===`Enter`&&!t.confirmDisabled?`confirm`:null}function x(e){return e instanceof Element&&e.closest(`button, select, textarea, [contenteditable]:not([contenteditable="false"])`)!==null}function S(e,t){let n=b(e,t);n===null||n===`confirm`&&x(e.target)||(e.preventDefault(),e.stopPropagation(),n===`confirm`?t.onConfirm():t.onCancel())}var C=u(`<div class="backdrop svelte-oozc38" role="presentation"><div class="dialog svelte-oozc38" role="dialog" tabindex="-1"><h2 class="svelte-oozc38"> </h2> <!> <div class="actions svelte-oozc38"><button class="control">Cancel</button> <button> </button></div></div></div>`);function w(a,u){m(u,!0);let _=l(u,`danger`,3,!1),v=l(u,`confirmDisabled`,3,!1),y=l(u,`busy`,3,!1);var b=C();n(`keydown`,r,e=>S(e,{busy:y(),confirmDisabled:v(),onConfirm:u.onConfirm,onCancel:u.onCancel}));var x=p(b),w=p(x),T=p(w,!0);h(w);var E=s(w,2);o(E,()=>u.children);var D=s(E,2),O=p(D),k=s(O,2);let A;var j=p(k,!0);h(k),h(D),h(x),h(b),t(()=>{g(x,`aria-label`,u.title),i(T,u.title),O.disabled=y(),A=c(k,1,`control`,null,A,{primary:!_(),danger:_()}),k.disabled=v()||y(),i(j,u.confirmLabel)}),e(`click`,b,e=>{e.target===e.currentTarget&&!y()&&u.onCancel()}),e(`click`,O,function(...e){u.onCancel?.apply(this,e)}),e(`click`,k,function(...e){u.onConfirm?.apply(this,e)}),d(a,b),f()}a([`click`]);export{y as a,v as i,S as n,_ as r,w as t};
@@ -1 +0,0 @@
1
- import{H as e,O as t,_ as n,i as r,j as i,k as a,q as o,st as s,u as c}from"./BMORe2Gb.js";import"./xihTtKlq.js";var l=i(`<span> </span>`);function u(i,u){let d=r(u,`overlay`,3,!1);var f=l();let p;var m=o(f,!0);s(f),e(()=>{c(f,`title`,u.title),p=n(f,1,`svelte-d2mzbr`,null,p,{edited:u.kind===`edited`,overlay:d()}),t(m,u.kind===`new`?`New`:`Edited`)}),a(i,f)}export{u as t};
@@ -1 +0,0 @@
1
- import{D as e,H as t,L as n,Q as r,S as i,U as a,Y as o,Z as s,_ as c,a as l,i as u,j as d,k as f,nt as p,q as m,rt as h,st as g,u as _,y as v,z as y}from"./BMORe2Gb.js";import"./xihTtKlq.js";function b({anchor:e,panel:t,viewport:n,align:r,gap:i=6,padding:a=8}){let o=r===`right`?e.right-t.width:e.left,s=Math.max(a,n.width-t.width-a),c=Math.min(Math.max(o,a),s),l=e.bottom+i,u=e.top-i-t.height,d=Math.max(a,n.height-t.height-a),f=l+t.height>n.height-a&&u>=a?u:l;return{left:c,top:Math.min(Math.max(f,a),d)}}var x=d(`<div role="dialog"><!></div>`),S=d(`<div class="popover-anchor svelte-1rznwff"><!> <!></div>`);function C(d,C){h(C,!0);let w=u(C,`open`,15,!1),T=u(C,`align`,3,`left`),E=u(C,`portal`,3,!1),D=r(void 0),O=r(void 0),k=[`button:not([disabled])`,`[href]`,`input:not([disabled])`,`select:not([disabled])`,`textarea:not([disabled])`,`[tabindex]:not([tabindex="-1"])`].join(`,`);function A(){w(!w())}function j(e){if(!E())return{destroy(){}};document.body.append(e);let t=0;function r(){if(!n(D))return;let t=b({anchor:n(D).getBoundingClientRect(),panel:e.getBoundingClientRect(),viewport:{width:window.innerWidth,height:window.innerHeight},align:T()});e.style.left=`${t.left}px`,e.style.top=`${t.top}px`}return t=requestAnimationFrame(r),window.addEventListener(`resize`,r),window.addEventListener(`scroll`,r,!0),{destroy(){cancelAnimationFrame(t),window.removeEventListener(`resize`,r),window.removeEventListener(`scroll`,r,!0),e.remove()}}}a(()=>{if(!w())return;let e=n(D)?.querySelector(k);y().then(()=>{w()&&n(O)?.querySelector(k)?.focus()});function t(e){let t=e.target;!n(D)?.contains(t)&&!n(O)?.contains(t)&&w(!1)}function r(e){e.key===`Escape`&&w(!1)}return window.addEventListener(`pointerdown`,t,!0),window.addEventListener(`keydown`,r),()=>{window.removeEventListener(`pointerdown`,t,!0),window.removeEventListener(`keydown`,r),y().then(()=>{!w()&&e?.isConnected&&e.focus()})}});var M=S(),N=m(M);i(N,()=>C.trigger,()=>({toggle:A,open:w()}));var P=o(N,2),F=e=>{var r=x();let a;i(m(r),()=>C.children),g(r),l(r,e=>s(O,e),()=>n(O)),v(r,e=>j?.(e)),t(()=>{a=c(r,1,`popover svelte-1rznwff`,null,a,{floating:E(),right:T()===`right`}),_(r,`aria-label`,C.label)}),f(e,r)};e(P,e=>{w()&&e(F)}),g(M),l(M,e=>s(D,e),()=>n(D)),f(d,M),p()}export{C as t};