@neta-art/cohub 10.0.0 → 11.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 (155) hide show
  1. package/README.md +42 -7
  2. package/dist/board/editor/animation.d.ts +3 -0
  3. package/dist/board/editor/animation.js +16 -1
  4. package/dist/board/editor/clipboard.js +7 -0
  5. package/dist/board/editor/copy.js +23 -18
  6. package/dist/board/editor/creation.d.ts +7 -3
  7. package/dist/board/editor/creation.js +5 -4
  8. package/dist/board/editor/deletion.js +3 -3
  9. package/dist/board/editor/document.d.ts +8 -2
  10. package/dist/board/editor/document.js +54 -7
  11. package/dist/board/editor/editor.d.ts +22 -7
  12. package/dist/board/editor/editor.js +12 -2
  13. package/dist/board/editor/index.d.ts +2 -2
  14. package/dist/board/editor/interaction.d.ts +8 -2
  15. package/dist/board/editor/interaction.js +34 -1
  16. package/dist/board/editor/query.d.ts +5 -5
  17. package/dist/board/editor/query.js +14 -12
  18. package/dist/board/editor/selection.js +2 -1
  19. package/dist/board/editor/snapshots.d.ts +0 -2
  20. package/dist/board/editor/snapshots.js +4 -20
  21. package/dist/board/export/index.d.ts +25 -4
  22. package/dist/board/export/index.js +45 -12
  23. package/dist/board/export/node/index.d.ts +5 -4
  24. package/dist/board/export/node/index.js +16 -11
  25. package/dist/board/export/node/scripts.d.ts +16 -0
  26. package/dist/board/export/node/scripts.js +258 -0
  27. package/dist/board/export/scene.d.ts +2 -1
  28. package/dist/board/export/scene.js +24 -7
  29. package/dist/board/model/animation.d.ts +4 -1
  30. package/dist/board/model/animation.js +34 -7
  31. package/dist/board/model/authoring.d.ts +20 -0
  32. package/dist/board/model/authoring.js +185 -0
  33. package/dist/board/model/export-plan.js +1 -0
  34. package/dist/board/model/file-preview.js +1 -1
  35. package/dist/board/model/file-snapshot.js +3 -2
  36. package/dist/board/model/image-key.js +7 -3
  37. package/dist/board/model/index.d.ts +15 -7
  38. package/dist/board/model/index.js +16 -9
  39. package/dist/board/model/items/builtin.js +2 -2
  40. package/dist/board/model/items/create.d.ts +19 -4
  41. package/dist/board/model/items/create.js +130 -9
  42. package/dist/board/model/items/index.d.ts +2 -2
  43. package/dist/board/model/items/index.js +2 -2
  44. package/dist/board/model/media-playback.d.ts +2 -1
  45. package/dist/board/model/media-playback.js +15 -5
  46. package/dist/board/model/media.js +7 -1
  47. package/dist/board/model/overlay-placement.d.ts +25 -0
  48. package/dist/board/model/overlay-placement.js +35 -0
  49. package/dist/board/model/replay.d.ts +1 -1
  50. package/dist/board/model/replay.js +1 -0
  51. package/dist/board/model/sampling.d.ts +19 -0
  52. package/dist/board/model/sampling.js +96 -0
  53. package/dist/board/model/script-example.d.ts +4 -0
  54. package/dist/board/model/script-example.js +21 -0
  55. package/dist/board/model/script-hit.d.ts +27 -0
  56. package/dist/board/model/script-hit.js +109 -0
  57. package/dist/board/model/script-index.d.ts +7 -0
  58. package/dist/board/model/script-index.js +15 -0
  59. package/dist/board/model/script-observers.d.ts +18 -0
  60. package/dist/board/model/script-observers.js +92 -0
  61. package/dist/board/model/script-scene.d.ts +24 -0
  62. package/dist/board/model/script-scene.js +294 -0
  63. package/dist/board/model/script.d.ts +149 -0
  64. package/dist/board/model/script.js +1 -0
  65. package/dist/board/render/board-background.js +5 -4
  66. package/dist/board/render/css-color.d.ts +6 -1
  67. package/dist/board/render/css-color.js +34 -20
  68. package/dist/board/render/diagnostics.d.ts +20 -0
  69. package/dist/board/render/diagnostics.js +117 -0
  70. package/dist/board/render/index.d.ts +9 -6
  71. package/dist/board/render/index.js +10 -7
  72. package/dist/board/render/media-interaction.js +1 -1
  73. package/dist/board/render/palette.d.ts +2 -1
  74. package/dist/board/render/palette.js +6 -2
  75. package/dist/board/render/renderers/arrow-card-renderer.js +3 -2
  76. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  77. package/dist/board/render/renderers/board-renderer-registry.d.ts +25 -5
  78. package/dist/board/render/renderers/board-renderer-registry.js +11 -4
  79. package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
  80. package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +18 -16
  81. package/dist/board/render/renderers/draw-card-renderer.js +3 -2
  82. package/dist/board/render/renderers/effect-card-renderer.js +22 -5
  83. package/dist/board/render/renderers/frame-card-renderer.js +30 -14
  84. package/dist/board/render/renderers/image-card-renderer.js +43 -4
  85. package/dist/board/render/renderers/shape-card-renderer.js +10 -27
  86. package/dist/board/render/renderers/task-card-renderer.d.ts +2 -1
  87. package/dist/board/render/renderers/task-card-renderer.js +220 -71
  88. package/dist/board/render/renderers/text-card-renderer.js +5 -3
  89. package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
  90. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  91. package/dist/board/render/sampling.d.ts +22 -0
  92. package/dist/board/render/sampling.js +21 -0
  93. package/dist/board/render/script-export.d.ts +11 -0
  94. package/dist/board/render/script-export.js +86 -0
  95. package/dist/board/render/script-host.d.ts +31 -0
  96. package/dist/board/render/script-host.js +446 -0
  97. package/dist/board/render/script-modules.d.ts +2 -0
  98. package/dist/board/render/script-modules.js +2 -0
  99. package/dist/board/render/script-runtime.d.ts +29 -0
  100. package/dist/board/render/script-runtime.js +223 -0
  101. package/dist/board/render/script-sandbox.d.ts +4 -0
  102. package/dist/board/render/script-sandbox.js +173 -0
  103. package/dist/board/render/script-texture.d.ts +11 -0
  104. package/dist/board/render/script-texture.js +33 -0
  105. package/dist/board/render/stroke.d.ts +2 -1
  106. package/dist/board/render/stroke.js +22 -1
  107. package/dist/board/render/text-measurement.js +1 -1
  108. package/dist/board/stage/assets.js +7 -2
  109. package/dist/board/stage/maintenance.d.ts +5 -0
  110. package/dist/board/stage/maintenance.js +11 -0
  111. package/dist/board/stage/scene.d.ts +2 -0
  112. package/dist/board/stage/scene.js +21 -2
  113. package/dist/board/stage/script-pointer.d.ts +20 -0
  114. package/dist/board/stage/script-pointer.js +41 -0
  115. package/dist/board/stage/stage.d.ts +21 -3
  116. package/dist/board/stage/stage.js +350 -47
  117. package/dist/board/stage/texture-pool.js +2 -3
  118. package/dist/chunks/environment.d.ts +305 -27
  119. package/dist/chunks/environment.js +12 -4
  120. package/dist/chunks/host-bridge.d.ts +2 -2
  121. package/dist/chunks/http.d.ts +90 -7
  122. package/dist/chunks/http.js +157 -64
  123. package/dist/chunks/transport.js +4 -2
  124. package/dist/chunks/websocket.d.ts +9 -1
  125. package/dist/chunks/websocket.js +16 -2
  126. package/dist/host-bridge.js +2 -1
  127. package/dist/http.d.ts +3 -3
  128. package/dist/index.d.ts +182 -4
  129. package/dist/index.js +1559 -299
  130. package/dist/protocol/dist/board-assets.d.ts +1 -0
  131. package/dist/protocol/dist/board-constants.js +2 -2
  132. package/dist/protocol/dist/board-model.d.ts +236 -11
  133. package/dist/protocol/dist/board-model.js +46 -17
  134. package/dist/protocol/dist/board-patch.js +2 -1
  135. package/dist/protocol/dist/board-schema.d.ts +4 -2
  136. package/dist/protocol/dist/board-schema.js +18 -4
  137. package/dist/protocol/dist/board-script-modules.d.ts +25 -0
  138. package/dist/protocol/dist/board-script-modules.js +127 -0
  139. package/dist/protocol/dist/board-task.d.ts +22 -0
  140. package/dist/{board/model/task.js → protocol/dist/board-task.js} +34 -7
  141. package/dist/protocol/dist/display/index.d.ts +1 -0
  142. package/dist/protocol/dist/generation/blocks.d.ts +8 -0
  143. package/dist/{generation-blocks.js → protocol/dist/generation/blocks.js} +14 -15
  144. package/dist/protocol/dist/index.d.ts +7 -2
  145. package/dist/protocol/dist/realtime/types.d.ts +1 -0
  146. package/dist/protocol/dist/rtc/index.d.ts +1 -0
  147. package/dist/protocol/dist/task/index.d.ts +4 -0
  148. package/package.json +2 -1
  149. package/dist/board/export/node/sketch.d.ts +0 -19
  150. package/dist/board/export/node/sketch.js +0 -146
  151. package/dist/board/model/task.d.ts +0 -11
  152. package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
  153. package/dist/board/render/sketch-host.d.ts +0 -14
  154. package/dist/board/render/sketch-host.js +0 -216
  155. package/dist/types.d.ts +0 -36
@@ -0,0 +1,117 @@
1
+ import { layoutBoardText } from "../../protocol/dist/board-layout.js";
2
+ import { itemBounds, rectsIntersect } from "../model/geometry.js";
3
+ import { boardFontFamily } from "../model/text-metrics.js";
4
+ import { BOARD_FONT_STACKS, ensureBoardTextMeasurement } from "./text-measurement.js";
5
+ import { DOMAdapter } from "pixi.js";
6
+ //#region src/board/render/diagnostics.ts
7
+ const glyphCache = /* @__PURE__ */ new Map();
8
+ let glyphContext;
9
+ function missingGlyph(character, font) {
10
+ if (/\s/u.test(character)) return false;
11
+ const key = `${font}:${character}`;
12
+ const cached = glyphCache.get(key);
13
+ if (cached !== void 0) return cached;
14
+ try {
15
+ glyphContext ??= DOMAdapter.get().createCanvas(64, 64).getContext("2d");
16
+ const ctx = glyphContext;
17
+ ctx.font = `32px ${font}`;
18
+ const absent = "􏿿";
19
+ if (ctx.measureText(character).width !== ctx.measureText(absent).width) {
20
+ if (glyphCache.size >= 2048) glyphCache.clear();
21
+ glyphCache.set(key, false);
22
+ return false;
23
+ }
24
+ const pixels = (text) => {
25
+ ctx.clearRect(0, 0, 64, 64);
26
+ ctx.fillStyle = "#000000";
27
+ ctx.fillText(text, 0, 40);
28
+ return ctx.getImageData(0, 0, 64, 64).data;
29
+ };
30
+ const a = pixels(character);
31
+ const b = pixels(absent);
32
+ const missing = a.some((n) => n !== 0) && a.every((n, i) => n === b[i]);
33
+ if (glyphCache.size >= 2048) glyphCache.clear();
34
+ glyphCache.set(key, missing);
35
+ return missing;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+ function measureBoardText(props) {
41
+ ensureBoardTextMeasurement();
42
+ const font = boardFontFamily(props.font, BOARD_FONT_STACKS);
43
+ const characters = [...new Set(props.text)].slice(0, 512).filter((char) => missingGlyph(char, font)).join("");
44
+ return {
45
+ ...layoutBoardText(props),
46
+ missingCharacters: characters
47
+ };
48
+ }
49
+ function inspectBoardScene(scene, limit = 100) {
50
+ ensureBoardTextMeasurement();
51
+ const diagnostics = [];
52
+ const texts = scene.items.filter((item) => item.type === "text" && scene.opacity(item.id) > .01);
53
+ const push = (entry) => {
54
+ if (diagnostics.length < limit) diagnostics.push(entry);
55
+ };
56
+ for (const item of scene.items) {
57
+ if (diagnostics.length >= limit) break;
58
+ if (item.type === "effect" && item.props.follow && item.props.kind !== "trail") push({
59
+ code: "effect-follow-unused",
60
+ item: item.id
61
+ });
62
+ }
63
+ let glyphBudget = 256;
64
+ for (const item of texts) {
65
+ if (diagnostics.length >= limit || item.type !== "text") break;
66
+ if (glyphBudget-- > 0) {
67
+ const measured = measureBoardText(item.props);
68
+ if (measured.missingCharacters) push({
69
+ code: "glyph-unavailable",
70
+ item: item.id,
71
+ characters: measured.missingCharacters
72
+ });
73
+ }
74
+ const bounds = itemBounds(item.frame);
75
+ let parentId = item.parent;
76
+ while (parentId) {
77
+ const parent = scene.get(parentId);
78
+ if (!parent) break;
79
+ if (parent.type === "frame" && parent.props.clip) {
80
+ const clip = itemBounds(parent.frame);
81
+ if (bounds.x < clip.x - 1 || bounds.y < clip.y - 1 || bounds.x + bounds.width > clip.x + clip.width + 1 || bounds.y + bounds.height > clip.y + clip.height + 1) {
82
+ push({
83
+ code: "text-clipped",
84
+ item: item.id,
85
+ related: parentId
86
+ });
87
+ break;
88
+ }
89
+ }
90
+ parentId = parent.parent;
91
+ }
92
+ }
93
+ const sorted = texts.map((item) => ({
94
+ item,
95
+ rect: itemBounds(item.frame)
96
+ })).sort((a, b) => a.rect.x - b.rect.x);
97
+ let comparisons = 0;
98
+ for (let i = 0; i < sorted.length && diagnostics.length < limit; i++) {
99
+ const a = sorted[i];
100
+ if (!a) continue;
101
+ for (let j = i + 1; j < sorted.length; j++) {
102
+ const b = sorted[j];
103
+ if (!b) continue;
104
+ if (b.rect.x >= a.rect.x + a.rect.width) break;
105
+ if (++comparisons > 1e4) return diagnostics;
106
+ if (a.item.parent === b.item.parent && rectsIntersect(a.rect, b.rect)) push({
107
+ code: "text-overlap",
108
+ item: a.item.id,
109
+ related: b.item.id
110
+ });
111
+ if (diagnostics.length >= limit) break;
112
+ }
113
+ }
114
+ return diagnostics;
115
+ }
116
+ //#endregion
117
+ export { inspectBoardScene, measureBoardText };
@@ -1,14 +1,17 @@
1
- import { BoardCardRenderer, BoardCardRendererResolver, BoardRenderContext, BoardRenderPalette, BoardSketchHost, createBoardCardRendererResolver, getBoardCardRenderer } from "./renderers/board-renderer-registry.js";
1
+ import { BoardCardRenderer, BoardCardRendererResolver, BoardRenderContext, BoardRenderLabels, BoardRenderPalette, BoardScriptHost, DEFAULT_BOARD_RENDER_LABELS, createBoardCardRendererResolver, getBoardCardRenderer } from "./renderers/board-renderer-registry.js";
2
+ import { createBoardRenderSampler } from "./sampling.js";
3
+ import { BoardRenderDiagnostic, inspectBoardScene, measureBoardText } from "./diagnostics.js";
2
4
  import { BoardBackgroundContext, createBoardBackground, updateBoardBackground } from "./board-background.js";
3
5
  import { ClipGroup, clippingAncestor, isClippingFrame, syncClipGroup, traceFrameOutline } from "./clip.js";
4
- import { parseBoardCssColor } from "./css-color.js";
6
+ import { BoardCssPaint, parseBoardCssColor, parseBoardCssPaint } from "./css-color.js";
5
7
  import { BoardMediaAction, boardMediaActionAt, mediaPlayBadgeHit, mediaPlayBadgeVisible } from "./media-interaction.js";
6
- import { defaultBoardPalette, itemColor } from "./palette.js";
7
- import { BoardSketchHostOptions, BrowserBoardSketchHost, createBoardSketchHost } from "./sketch-host.js";
8
- import { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
8
+ import { defaultBoardPalette, itemAlpha, itemColor } from "./palette.js";
9
+ import { BoardScriptHostOptions, BrowserBoardScriptHost, createBoardScriptHost } from "./script-host.js";
10
+ import { prepareBrowserBoardScripts } from "./script-export.js";
11
+ import { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
9
12
  import { BOARD_EFFECT_MAX_PARTICLES } from "./renderers/effect-card-renderer.js";
10
13
  import { TASK_CARD_FULL_DETAIL_ZOOM } from "./renderers/task-card-renderer.js";
11
14
  import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
12
15
  import { getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket } from "./text-resolution.js";
13
16
  import { VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, loadVideoThumbnailTexture, videoTextureNaturalSize, videoThumbnailSize } from "./video-thumbnail.js";
14
- export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, BoardBackgroundContext, BoardCardRenderer, BoardCardRendererResolver, BoardMediaAction, BoardRenderContext, BoardRenderPalette, BoardSketchHost, BoardSketchHostOptions, BrowserBoardSketchHost, ClipGroup, TASK_CARD_FULL_DETAIL_ZOOM, VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, boardMediaActionAt, clippingAncestor, createBoardBackground, createBoardCardRendererResolver, createBoardSketchHost, dashPattern, defaultBoardPalette, endAngle, ensureBoardTextMeasurement, getBoardCardRenderer, getBoardResolution, installBoardTextMeasurement, isClippingFrame, itemColor, loadVideoThumbnailTexture, mediaPlayBadgeHit, mediaPlayBadgeVisible, parseBoardCssColor, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceFrameOutline, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize };
17
+ export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, BoardBackgroundContext, BoardCardRenderer, BoardCardRendererResolver, BoardCssPaint, BoardMediaAction, BoardRenderContext, BoardRenderDiagnostic, BoardRenderLabels, BoardRenderPalette, BoardScriptHost, BoardScriptHostOptions, BrowserBoardScriptHost, ClipGroup, DEFAULT_BOARD_RENDER_LABELS, TASK_CARD_FULL_DETAIL_ZOOM, VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, boardMediaActionAt, clippingAncestor, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, dashPattern, defaultBoardPalette, endAngle, ensureBoardTextMeasurement, getBoardCardRenderer, getBoardResolution, inspectBoardScene, installBoardTextMeasurement, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, roundRectPoints, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceFrameOutline, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize };
@@ -1,14 +1,17 @@
1
- import { parseBoardCssColor } from "./css-color.js";
1
+ import { parseBoardCssColor, parseBoardCssPaint } from "./css-color.js";
2
+ import { defaultBoardPalette, itemAlpha, itemColor } from "./palette.js";
3
+ import { createBoardRenderSampler } from "./sampling.js";
4
+ import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
5
+ import { inspectBoardScene, measureBoardText } from "./diagnostics.js";
2
6
  import { createBoardBackground, updateBoardBackground } from "./board-background.js";
3
7
  import { clippingAncestor, isClippingFrame, syncClipGroup, traceFrameOutline } from "./clip.js";
4
8
  import { getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket } from "./text-resolution.js";
5
9
  import { TASK_CARD_FULL_DETAIL_ZOOM } from "./renderers/task-card-renderer.js";
6
10
  import { boardMediaActionAt, mediaPlayBadgeHit, mediaPlayBadgeVisible } from "./media-interaction.js";
7
- import { defaultBoardPalette, itemColor } from "./palette.js";
8
- import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
9
- import { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
11
+ import { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
10
12
  import { BOARD_EFFECT_MAX_PARTICLES } from "./renderers/effect-card-renderer.js";
11
- import { createBoardCardRendererResolver, getBoardCardRenderer } from "./renderers/board-renderer-registry.js";
12
- import { createBoardSketchHost } from "./sketch-host.js";
13
+ import { DEFAULT_BOARD_RENDER_LABELS, createBoardCardRendererResolver, getBoardCardRenderer } from "./renderers/board-renderer-registry.js";
14
+ import { createBoardScriptHost } from "./script-host.js";
15
+ import { prepareBrowserBoardScripts } from "./script-export.js";
13
16
  import { VIDEO_THUMBNAIL_MAX_EDGE, loadVideoThumbnailTexture, videoTextureNaturalSize, videoThumbnailSize } from "./video-thumbnail.js";
14
- export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, TASK_CARD_FULL_DETAIL_ZOOM, VIDEO_THUMBNAIL_MAX_EDGE, boardMediaActionAt, clippingAncestor, createBoardBackground, createBoardCardRendererResolver, createBoardSketchHost, dashPattern, defaultBoardPalette, endAngle, ensureBoardTextMeasurement, getBoardCardRenderer, getBoardResolution, installBoardTextMeasurement, isClippingFrame, itemColor, loadVideoThumbnailTexture, mediaPlayBadgeHit, mediaPlayBadgeVisible, parseBoardCssColor, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceFrameOutline, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize };
17
+ export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, DEFAULT_BOARD_RENDER_LABELS, TASK_CARD_FULL_DETAIL_ZOOM, VIDEO_THUMBNAIL_MAX_EDGE, boardMediaActionAt, clippingAncestor, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, dashPattern, defaultBoardPalette, endAngle, ensureBoardTextMeasurement, getBoardCardRenderer, getBoardResolution, inspectBoardScene, installBoardTextMeasurement, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, roundRectPoints, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceFrameOutline, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize };
@@ -1,4 +1,4 @@
1
- import { featuredTaskArtifact } from "../model/task.js";
1
+ import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
2
2
  import "./renderers/task-card-renderer.js";
3
3
  //#region src/board/render/media-interaction.ts
4
4
  function mediaPlayBadgeVisible(item, zoom, options) {
@@ -4,6 +4,7 @@ import { BoardColorId } from "../model/palette.js";
4
4
  import { BoardRenderContext, BoardRenderPalette } from "./renderers/board-renderer-registry.js";
5
5
  //#region src/board/render/palette.d.ts
6
6
  declare function defaultBoardPalette(mode: "dark" | "light"): BoardRenderPalette;
7
+ declare function itemAlpha(context: Pick<BoardRenderContext, "colorScheme">, color: BoardColor | undefined): number;
7
8
  declare function itemColor(context: Pick<BoardRenderContext, "colors" | "colorScheme" | "palette">, color: BoardColor | undefined, fallback: BoardColorId, part?: "stroke" | "fill" | "label"): number;
8
9
  //#endregion
9
- export { defaultBoardPalette, itemColor };
10
+ export { defaultBoardPalette, itemAlpha, itemColor };
@@ -1,5 +1,5 @@
1
1
  import { resolveItemColor } from "../model/palette.js";
2
- import { parseBoardCssColor } from "./css-color.js";
2
+ import { parseBoardCssColor, parseBoardCssPaint } from "./css-color.js";
3
3
  //#region src/board/render/palette.ts
4
4
  const DARK = {
5
5
  bg: 1315860,
@@ -28,9 +28,13 @@ const LIGHT = {
28
28
  function defaultBoardPalette(mode) {
29
29
  return { ...mode === "light" ? LIGHT : DARK };
30
30
  }
31
+ function itemAlpha(context, color) {
32
+ const value = typeof color === "object" && color ? color[context.colorScheme] : color;
33
+ return value ? parseBoardCssPaint(value)?.alpha ?? 1 : 1;
34
+ }
31
35
  function itemColor(context, color, fallback, part = "stroke") {
32
36
  if (((typeof color === "object" && color ? color[context.colorScheme] : color) ?? fallback) === "neutral" && part !== "fill") return context.palette.text;
33
37
  return resolveItemColor(color, fallback, context.colors, context.colorScheme, parseBoardCssColor, part);
34
38
  }
35
39
  //#endregion
36
- export { defaultBoardPalette, itemColor };
40
+ export { defaultBoardPalette, itemAlpha, itemColor };
@@ -1,8 +1,8 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { pathPointAt, resolveSceneArrow } from "../../model/arrow-geometry.js";
3
+ import { itemAlpha, itemColor } from "../palette.js";
3
4
  import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
4
5
  import { drawFarStroke } from "./far-plate.js";
5
- import { itemColor } from "../palette.js";
6
6
  import { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline } from "../stroke.js";
7
7
  import { Container, Graphics, Text } from "pixi.js";
8
8
  //#region src/board/render/renderers/arrow-card-renderer.ts
@@ -16,6 +16,7 @@ function sync(container, item, context) {
16
16
  const { props, style } = item;
17
17
  const selected = context.selectedIds.has(item.id);
18
18
  const hovered = context.hoveredId === item.id;
19
+ parts.line.alpha = itemAlpha(context, style.stroke);
19
20
  const color = itemColor(context, style.stroke, "brand");
20
21
  const size = style.strokeWidth ?? 2.5;
21
22
  syncTextResolution(parts.label, parts, context.zoom);
@@ -130,7 +131,7 @@ const arrowCardRenderer = {
130
131
  drawFarStroke(graphics, trimPolyline(resolved.path, arrow.style.trim), {
131
132
  color: itemColor(context, arrow.style.stroke, "brand"),
132
133
  width: arrow.style.strokeWidth ?? 2.5,
133
- alpha: .85
134
+ alpha: .85 * itemAlpha(context, arrow.style.stroke)
134
135
  });
135
136
  },
136
137
  destroy: (container) => {
@@ -1,4 +1,5 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
+ import { getMediaResourceTitle } from "../../model/media.js";
2
3
  import { drawAudioWaveform } from "../audio-waveform.js";
3
4
  import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
4
5
  import { positionShell } from "./base-card-renderer.js";
@@ -15,7 +16,7 @@ function sync(container, item, context) {
15
16
  const { width, height } = item.frame;
16
17
  const selected = context.selectedIds.has(item.id);
17
18
  const hovered = context.hoveredId === item.id;
18
- const title = item.props.snapshot?.title ?? item.props.src.split("/").pop() ?? "Audio";
19
+ const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
19
20
  syncTextResolution(parts.label, parts, context.zoom);
20
21
  if (parts.label.text !== title) parts.label.text = title;
21
22
  syncTextWrapWidth(parts.label, parts, Math.max(1, width - 24), false);
@@ -1,4 +1,5 @@
1
- import { BoardSettings, BoardSketchItem } from "../../../protocol/dist/board-model.js";
1
+ import { BoardCustomItem, BoardDocument, BoardSettings, BoardTaskItem } from "../../../protocol/dist/board-model.js";
2
+ import { BoardFrame } from "../../../protocol/dist/board-layout.js";
2
3
  import "../../../protocol/dist/index.js";
3
4
  import { BoardScene, BoardSceneItem, SceneItem } from "../../model/scene.js";
4
5
  import { BoardShapeColors } from "../../model/palette.js";
@@ -16,11 +17,22 @@ type BoardRenderPalette = {
16
17
  epic: number;
17
18
  legendary: number;
18
19
  };
19
- type BoardSketchHost = {
20
- frame: (item: SceneItem<BoardSketchItem>, time: number, resolution: number) => Texture | null;
20
+ type BoardScriptHost = {
21
+ document?: (document: BoardDocument, time: number) => BoardDocument;
22
+ ready?: (id: string) => boolean;
23
+ revision?: (id: string) => number;
24
+ frame: (item: SceneItem<BoardCustomItem>, time: number, resolution: number) => Texture | null;
21
25
  error: (id: string) => string | null;
22
26
  release: (id: string) => void;
23
27
  };
28
+ type BoardRenderLabels = {
29
+ queued: string;
30
+ generating: string;
31
+ failed: string;
32
+ previewUnavailable: string;
33
+ noPreview: string;
34
+ };
35
+ declare const DEFAULT_BOARD_RENDER_LABELS: BoardRenderLabels;
24
36
  type BoardRenderContext = {
25
37
  settings: BoardSettings;
26
38
  scene: BoardScene;
@@ -33,7 +45,14 @@ type BoardRenderContext = {
33
45
  rendererType: "gpu" | "canvas";
34
46
  zoom: number;
35
47
  time: number;
36
- sketches?: BoardSketchHost;
48
+ reducedMotion: boolean;
49
+ labels: BoardRenderLabels;
50
+ taskRun: (taskRunId: string) => {
51
+ status: BoardTaskItem["props"]["snapshot"]["status"];
52
+ startedAt?: string;
53
+ } | null;
54
+ frameAt?: (id: string, time: number) => BoardFrame | undefined;
55
+ scripts?: BoardScriptHost;
37
56
  assetKey: (item: BoardSceneItem) => string | null;
38
57
  getTexture: (key: string) => Texture | null;
39
58
  hasError: (key: string) => boolean;
@@ -45,6 +64,7 @@ type BoardCardRenderer = {
45
64
  id: string;
46
65
  canRender: (item: BoardSceneItem, context: BoardRenderContext) => boolean;
47
66
  animated?: boolean;
67
+ tick?: (container: Container, clock: number) => number | null;
48
68
  create: (item: BoardSceneItem, context: BoardRenderContext) => Container;
49
69
  update: (container: Container, item: BoardSceneItem, context: BoardRenderContext) => void;
50
70
  renderFar?: (graphics: Graphics, item: BoardSceneItem, context: BoardRenderContext) => void;
@@ -59,4 +79,4 @@ declare function createBoardCardRendererResolver(renderers?: Iterable<BoardCardR
59
79
  /** The built-in renderers alone. */
60
80
  declare const getBoardCardRenderer: BoardCardRendererResolver;
61
81
  //#endregion
62
- export { BoardCardRenderer, BoardCardRendererResolver, BoardRenderContext, BoardRenderPalette, BoardSketchHost, createBoardCardRendererResolver, getBoardCardRenderer };
82
+ export { BoardCardRenderer, BoardCardRendererResolver, BoardRenderContext, BoardRenderLabels, BoardRenderPalette, BoardScriptHost, DEFAULT_BOARD_RENDER_LABELS, createBoardCardRendererResolver, getBoardCardRenderer };
@@ -1,18 +1,25 @@
1
+ import { ensureBoardTextMeasurement } from "../text-measurement.js";
1
2
  import { fileCardRenderer } from "./file-card-renderer.js";
2
3
  import { taskCardRenderer } from "./task-card-renderer.js";
3
- import { ensureBoardTextMeasurement } from "../text-measurement.js";
4
4
  import { arrowCardRenderer } from "./arrow-card-renderer.js";
5
5
  import { audioCardRenderer } from "./audio-card-renderer.js";
6
6
  import { drawCardRenderer } from "./draw-card-renderer.js";
7
7
  import { effectCardRenderer } from "./effect-card-renderer.js";
8
8
  import { frameCardRenderer } from "./frame-card-renderer.js";
9
9
  import { shapeCardRenderer } from "./shape-card-renderer.js";
10
- import { sketchCardRenderer } from "./sketch-card-renderer.js";
10
+ import { customCardRenderer } from "./custom-card-renderer.js";
11
11
  import { imageCardRenderer } from "./image-card-renderer.js";
12
12
  import { textCardRenderer } from "./text-card-renderer.js";
13
13
  import { unknownCardRenderer } from "./unknown-card-renderer.js";
14
14
  import { videoCardRenderer } from "./video-card-renderer.js";
15
15
  //#region src/board/render/renderers/board-renderer-registry.ts
16
+ const DEFAULT_BOARD_RENDER_LABELS = {
17
+ queued: "Queued",
18
+ generating: "Generating",
19
+ failed: "Failed",
20
+ previewUnavailable: "Preview unavailable",
21
+ noPreview: "No preview"
22
+ };
16
23
  const builtinCardRenderers = [
17
24
  textCardRenderer,
18
25
  imageCardRenderer,
@@ -25,7 +32,7 @@ const builtinCardRenderers = [
25
32
  arrowCardRenderer,
26
33
  frameCardRenderer,
27
34
  effectCardRenderer,
28
- sketchCardRenderer
35
+ customCardRenderer
29
36
  ];
30
37
  /**
31
38
  * Pick a renderer per item: `renderers` first, then the built-ins. Items no
@@ -41,4 +48,4 @@ function createBoardCardRendererResolver(renderers = []) {
41
48
  /** The built-in renderers alone. */
42
49
  const getBoardCardRenderer = createBoardCardRendererResolver();
43
50
  //#endregion
44
- export { createBoardCardRendererResolver, getBoardCardRenderer };
51
+ export { DEFAULT_BOARD_RENDER_LABELS, createBoardCardRendererResolver, getBoardCardRenderer };
@@ -0,0 +1,5 @@
1
+ import { BoardCardRenderer } from "./board-renderer-registry.js";
2
+ //#region src/board/render/renderers/custom-card-renderer.d.ts
3
+ declare const customCardRenderer: BoardCardRenderer;
4
+ //#endregion
5
+ export { customCardRenderer };
@@ -3,26 +3,28 @@ import { getBoardResolution } from "../text-resolution.js";
3
3
  import { positionShell } from "./base-card-renderer.js";
4
4
  import { drawFarPlate } from "./far-plate.js";
5
5
  import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
6
- //#region src/board/render/renderers/sketch-card-renderer.ts
6
+ //#region src/board/render/renderers/custom-card-renderer.ts
7
7
  const partsByContainer = /* @__PURE__ */ new WeakMap();
8
8
  function sync(container, item, context) {
9
9
  const parts = partsByContainer.get(container);
10
10
  if (!parts) return;
11
11
  if (parts.id !== item.id) {
12
- context.sketches?.release(parts.id);
12
+ context.scripts?.release(parts.id);
13
13
  parts.id = item.id;
14
14
  }
15
15
  positionShell(parts.root, item);
16
16
  const { width, height } = item.frame;
17
17
  const resolution = Math.min(4, Math.max(.25, context.zoom * getBoardResolution()));
18
- const texture = context.sketches?.frame(item, context.time, resolution) ?? null;
19
- const error = context.sketches?.error(item.id) ?? null;
18
+ const texture = context.scripts?.frame(item, context.time, resolution) ?? null;
19
+ const error = context.scripts?.error(item.id) ?? null;
20
20
  parts.sprite.visible = Boolean(texture);
21
- if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
21
+ const display = texture ?? Texture.EMPTY;
22
+ if (parts.sprite.texture !== display) parts.sprite.texture = display;
22
23
  parts.sprite.width = width;
23
24
  parts.sprite.height = height;
24
25
  const selected = context.selectedIds.has(item.id);
25
- const message = texture ? "" : error ? `${item.props.src}\n${error}` : item.props.src;
26
+ const ready = Boolean(texture) || context.scripts?.ready?.(item.id) === true;
27
+ const message = error ? `${item.script.src}\n${error}` : ready ? "" : item.script.src;
26
28
  const sig = [
27
29
  width,
28
30
  height,
@@ -35,21 +37,21 @@ function sync(container, item, context) {
35
37
  if (sig === parts.sig) return;
36
38
  parts.sig = sig;
37
39
  parts.placeholder.clear();
38
- if (!texture || selected) parts.placeholder.rect(0, 0, width, height).stroke({
40
+ if (!ready || selected || error) parts.placeholder.rect(0, 0, width, height).stroke({
39
41
  color: selected ? context.palette.brand : context.palette.border,
40
42
  width: selected ? 2 : 1,
41
43
  alpha: .8
42
44
  });
43
- parts.label.visible = !texture;
45
+ parts.label.visible = !ready || Boolean(error);
44
46
  parts.label.text = message;
45
47
  parts.label.style.fill = error ? context.colors.rose.stroke : context.palette.muted;
46
48
  parts.label.style.wordWrapWidth = Math.max(40, width - 16);
47
49
  parts.label.position.set(width / 2, height / 2);
48
50
  }
49
- const sketchCardRenderer = {
50
- id: "sketch-card",
51
- canRender: (item) => item.type === "sketch",
52
- animated: true,
51
+ const customCardRenderer = {
52
+ id: "custom-card",
53
+ canRender: (item) => item.type === "custom",
54
+ animated: false,
53
55
  create: (item, context) => {
54
56
  const root = new Container();
55
57
  const sprite = new Sprite(Texture.EMPTY);
@@ -75,11 +77,11 @@ const sketchCardRenderer = {
75
77
  label,
76
78
  sig: ""
77
79
  });
78
- if (item.type === "sketch") sync(root, item, context);
80
+ if (item.type === "custom") sync(root, item, context);
79
81
  return root;
80
82
  },
81
83
  update: (container, item, context) => {
82
- if (item.type === "sketch") sync(container, item, context);
84
+ if (item.type === "custom") sync(container, item, context);
83
85
  },
84
86
  renderFar: (graphics, item, context) => {
85
87
  drawFarPlate(graphics, item.frame, {
@@ -89,10 +91,10 @@ const sketchCardRenderer = {
89
91
  },
90
92
  destroy: (container, context) => {
91
93
  const parts = partsByContainer.get(container);
92
- if (parts) context.sketches?.release(parts.id);
94
+ if (parts) context.scripts?.release(parts.id);
93
95
  parts?.root.destroy({ children: true });
94
96
  partsByContainer.delete(container);
95
97
  }
96
98
  };
97
99
  //#endregion
98
- export { sketchCardRenderer };
100
+ export { customCardRenderer };
@@ -1,8 +1,8 @@
1
1
  import { applyMatrix } from "../../../protocol/dist/board-layout.js";
2
2
  import { computeDrawBounds, createStrokeRibbonBuilder, trimDrawPoints } from "../../model/draw-geometry.js";
3
+ import { itemAlpha, itemColor } from "../palette.js";
3
4
  import { positionShell } from "./base-card-renderer.js";
4
5
  import { drawFarStroke, farStrokeSamples } from "./far-plate.js";
5
- import { itemColor } from "../palette.js";
6
6
  import { Container, Graphics, Mesh, MeshGeometry, Texture } from "pixi.js";
7
7
  //#region src/board/render/renderers/draw-card-renderer.ts
8
8
  const partsByContainer = /* @__PURE__ */ new WeakMap();
@@ -77,6 +77,7 @@ function sync(container, item, context) {
77
77
  }
78
78
  const stroke = parts.stroke;
79
79
  if (!stroke) return;
80
+ stroke.alpha = itemAlpha(context, item.style.stroke);
80
81
  if (stroke instanceof Mesh) stroke.tint = color;
81
82
  const { box } = parts;
82
83
  const sx = item.frame.width / box.width;
@@ -120,7 +121,7 @@ const drawCardRenderer = {
120
121
  drawFarStroke(graphics, samples.map((point) => applyMatrix(matrix, point)), {
121
122
  color: itemColor(context, draw.style.stroke, "brand"),
122
123
  width: draw.style.strokeWidth ?? 4,
123
- alpha: 1
124
+ alpha: itemAlpha(context, draw.style.stroke)
124
125
  });
125
126
  },
126
127
  destroy: (container) => {
@@ -1,6 +1,6 @@
1
+ import { itemAlpha, itemColor } from "../palette.js";
1
2
  import { positionShell } from "./base-card-renderer.js";
2
3
  import { drawFarPlate } from "./far-plate.js";
3
- import { itemColor } from "../palette.js";
4
4
  import { Container, Graphics } from "pixi.js";
5
5
  //#region src/board/render/renderers/effect-card-renderer.ts
6
6
  const BOARD_EFFECT_MAX_PARTICLES = 4e3;
@@ -109,11 +109,27 @@ function drawTrail(graphics, parts, item, context, color) {
109
109
  parts.trail.length = 0;
110
110
  return;
111
111
  }
112
- const head = {
113
- x: target.frame.x + target.frame.width / 2 - item.frame.x,
114
- y: target.frame.y + target.frame.height / 2 - item.frame.y,
115
- t: time
112
+ const angle = item.frame.rotation * Math.PI / 180;
113
+ const localPoint = (frame, t) => {
114
+ const dx = frame.x + frame.width / 2 - item.frame.x - item.frame.width / 2;
115
+ const dy = frame.y + frame.height / 2 - item.frame.y - item.frame.height / 2;
116
+ return {
117
+ x: dx * Math.cos(angle) + dy * Math.sin(angle) + item.frame.width / 2,
118
+ y: -dx * Math.sin(angle) + dy * Math.cos(angle) + item.frame.height / 2,
119
+ t
120
+ };
116
121
  };
122
+ const head = localPoint(target.frame, time);
123
+ if (context.frameAt) {
124
+ parts.trail.length = 0;
125
+ const start = Math.max(0, time - item.props.life);
126
+ const step = Math.max(1e3 / 30, item.props.life / 63);
127
+ for (let t = Math.ceil(start / step) * step; t < time; t += step) {
128
+ const past = context.frameAt(target.id, t);
129
+ if (past) parts.trail.push(localPoint(past, t));
130
+ }
131
+ parts.trail.push(head);
132
+ }
117
133
  const last = parts.trail.at(-1);
118
134
  if (last && (time < last.t || time - last.t > item.props.life)) parts.trail.length = 0;
119
135
  if (!last || Math.hypot(head.x - last.x, head.y - last.y) > .5) parts.trail.push(head);
@@ -140,6 +156,7 @@ function sync(container, item, context) {
140
156
  const seed = hashString(props.seed ?? item.id);
141
157
  const time = Math.max(0, context.time);
142
158
  const graphics = parts.graphics;
159
+ graphics.alpha = itemAlpha(context, item.style.fill ?? item.style.stroke);
143
160
  graphics.clear();
144
161
  switch (props.kind) {
145
162
  case "particles":
@@ -1,8 +1,9 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
+ import { itemAlpha, itemColor } from "../palette.js";
2
3
  import { syncTextResolution } from "../text-resolution.js";
3
4
  import { createLabel, positionShell } from "./base-card-renderer.js";
4
5
  import { drawFarPlate } from "./far-plate.js";
5
- import { itemColor } from "../palette.js";
6
+ import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
6
7
  import { Container, Graphics } from "pixi.js";
7
8
  //#region src/board/render/renderers/frame-card-renderer.ts
8
9
  const partsByContainer = /* @__PURE__ */ new WeakMap();
@@ -16,13 +17,15 @@ function sync(container, item, context) {
16
17
  const selected = context.selectedIds.has(item.id);
17
18
  const hovered = context.hoveredId === item.id;
18
19
  const fill = itemColor(context, style.fill ?? style.stroke, "neutral", "fill");
19
- const fillAlpha = style.fillOpacity ?? (style.fill ? 1 : DEFAULT_WASH);
20
+ const fillAlpha = (style.fillOpacity ?? (style.fill ? 1 : DEFAULT_WASH)) * itemAlpha(context, style.fill ?? style.stroke);
20
21
  const baseStroke = itemColor(context, style.stroke, "neutral");
21
- const stroke = selected ? context.palette.brand : baseStroke;
22
- const alpha = selected ? 1 : hovered ? .85 : .55;
22
+ const stroke = selected ? context.palette.brand : hovered && style.stroke ? context.palette.muted : baseStroke;
23
+ const alpha = (selected || style.stroke ? 1 : hovered ? .85 : .55) * itemAlpha(context, style.stroke);
23
24
  const radius = style.radius ?? 4;
24
25
  const strokeWidth = style.strokeWidth ?? 1.5;
26
+ const decorated = props.decoration !== "none";
25
27
  const visualSig = [
28
+ props.decoration,
26
29
  frame.width,
27
30
  frame.height,
28
31
  selected,
@@ -30,23 +33,36 @@ function sync(container, item, context) {
30
33
  fill,
31
34
  fillAlpha,
32
35
  stroke,
36
+ alpha,
33
37
  radius,
34
- strokeWidth
38
+ strokeWidth,
39
+ style.dash,
40
+ style.trim
35
41
  ].join("|");
36
42
  if (visualSig !== parts.visualSig) {
37
43
  parts.visualSig = visualSig;
38
44
  parts.box.clear();
39
- parts.box.roundRect(0, 0, frame.width, frame.height, radius).fill({
45
+ if (decorated) parts.box.roundRect(0, 0, frame.width, frame.height, radius).fill({
40
46
  color: fill,
41
47
  alpha: fillAlpha
42
48
  });
43
- if (strokeWidth > 0 || selected) parts.box.roundRect(0, 0, frame.width, frame.height, radius).stroke({
44
- color: stroke,
45
- width: selected ? Math.max(2, strokeWidth) : strokeWidth,
46
- alpha
47
- });
49
+ const width = selected ? Math.max(2, strokeWidth) : strokeWidth;
50
+ const pattern = decorated ? dashPattern(style.dash, width) : null;
51
+ const trim = decorated ? style.trim ?? 1 : 1;
52
+ if ((decorated && strokeWidth > 0 || selected || hovered) && trim > 0) {
53
+ if (pattern || trim < 1) tracePolyline(parts.box, trimPolyline(roundRectPoints(frame.width, frame.height, radius), trim), pattern);
54
+ else parts.box.roundRect(0, 0, frame.width, frame.height, radius);
55
+ parts.box.stroke({
56
+ color: stroke,
57
+ width,
58
+ alpha,
59
+ cap: pattern || trim < 1 ? "round" : "butt",
60
+ join: "round"
61
+ });
62
+ }
48
63
  }
49
- const title = locked ? `🔒 ${props.label || "Frame"}` : props.label || "Frame";
64
+ parts.label.visible = decorated;
65
+ const title = locked ? `🔒 ${props.label}`.trim() : props.label;
50
66
  const labelColor = style.stroke ? baseStroke : context.palette.muted;
51
67
  const textSig = [title, labelColor].join("|");
52
68
  if (textSig !== parts.textSig) {
@@ -84,10 +100,10 @@ const frameCardRenderer = {
84
100
  if (item.type === "frame") sync(container, item, context);
85
101
  },
86
102
  renderFar: (graphics, item, context) => {
87
- if (item.type !== "frame") return;
103
+ if (item.type !== "frame" || item.props.decoration === "none") return;
88
104
  drawFarPlate(graphics, item.frame, {
89
105
  fill: itemColor(context, item.style.fill ?? item.style.stroke, "neutral", "fill"),
90
- fillAlpha: Math.max(.07, item.style.fillOpacity ?? (item.style.fill ? 1 : 0))
106
+ fillAlpha: Math.max(.07, item.style.fillOpacity ?? (item.style.fill ? 1 : 0)) * itemAlpha(context, item.style.fill ?? item.style.stroke)
91
107
  });
92
108
  },
93
109
  destroy: (container) => {